3
# Copyright 2009 Dan Smith <dsmith@danplanet.com>
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
24
from d_rats.ui.main_common import MainWindowTab
25
from d_rats.ui import main_events
26
from d_rats.ui import conntest
27
from d_rats.sessions import rpc
28
from d_rats import station_status
29
from d_rats import signals
30
from d_rats import image
31
from d_rats import miscwidgets
32
from d_rats import inputdialog
33
from d_rats import utils
35
def prompt_for_account(config):
37
for section in config.options("incoming_email"):
38
info = config.get("incoming_email", section).split(",")
39
key = "%s on %s" % (info[1], info[0])
42
wl2k_call = config.get("user", "callsign")
43
wl2k_ssid = config.get("prefs", "msg_wl2k_ssid").strip()
45
wl2k_call = "%s-%s" % (wl2k_call, wl2k_ssid)
47
accounts["Other"] = ["", "", "", "", "", "110"]
48
accounts["WL2K"] = ["@WL2K", wl2k_call, "", "", "", "0"]
49
default = accounts.keys()[0]
51
account = miscwidgets.make_choice(accounts.keys(), False, default)
55
ussl = gtk.CheckButton()
56
port = gtk.SpinButton(gtk.Adjustment(110, 1, 65535, 1), digits=0)
58
disable = [host, user, pasw, ussl, port]
60
pasw.set_visibility(False)
62
def choose_account(box):
63
info = accounts[box.get_active_text()]
65
i.set_sensitive(not info[0])
66
host.set_text(info[0])
67
user.set_text(info[1])
68
pasw.set_text(info[2])
69
ussl.set_active(info[4] == "True")
70
port.set_value(int(info[5]))
71
account.connect("changed", choose_account)
72
choose_account(account)
74
d = inputdialog.FieldDialog(title="Select account")
75
d.add_field("Account", account)
76
d.add_field("Server", host)
77
d.add_field("Username", user)
78
d.add_field("Password", pasw)
79
d.add_field("Use SSL", ussl)
80
d.add_field("Port", port)
83
if r == gtk.RESPONSE_CANCEL:
86
return host.get_text(), user.get_text(), pasw.get_text(), \
87
str(ussl.get_active()), str(int(port.get_value()))
89
class StationsList(MainWindowTab):
91
"event" : signals.EVENT,
92
"notice" : signals.NOTICE,
93
"get-station-list" : signals.GET_STATION_LIST,
94
"ping-station" : signals.PING_STATION,
95
"ping-station-echo" : signals.PING_STATION_ECHO,
96
"incoming-chat-message" : signals.INCOMING_CHAT_MESSAGE,
97
"submit-rpc-job" : signals.SUBMIT_RPC_JOB,
98
"user-send-file" : signals.USER_SEND_FILE,
101
_signals = __gsignals__
104
self.__view.queue_draw()
108
def _mh(self, _action, station, port):
109
action = _action.get_name()
111
model = self.__view.get_model()
112
iter = model.get_iter_first()
114
_station, = model.get(iter, 0)
115
if _station == station:
117
iter = model.iter_next(iter)
120
# FIXME: Use the port we saw the user on
121
self.emit("ping-station", station, port)
122
elif action == "conntest":
123
ct = conntest.ConnTestAssistant(station, port)
124
ct.connect("ping-echo-station",
125
lambda a, *v: self.emit("ping-station-echo", *v))
127
elif action == "remove":
128
self.__calls.remove(station)
129
self._update_station_count()
131
elif action == "reset":
132
model.set(iter, 1, time.time())
133
elif action == "reqpos":
134
job = rpc.RPCPositionReport(station, "Position Request")
135
def log_result(job, state, result):
136
msg = result.get("rc", "(Error)")
138
event = main_events.Event(None,
139
"%s %s: %s" % (station,
142
self.emit("event", event)
143
print "Result: %s" % str(result)
144
job.set_station(station)
145
job.connect("state-change", log_result)
147
# FIXME: Send on the port where we saw this user
148
self.emit("submit-rpc-job", job, port)
149
elif action == "clearall":
152
self._update_station_count()
153
elif action == "pingall":
154
stationlist = self.emit("get-station-list")
155
for port in stationlist.keys():
156
print "Doing CQCQCQ ping on port %s" % port
157
self.emit("ping-station", "CQCQCQ", port)
158
elif action == "reqposall":
159
job = rpc.RPCPositionReport("CQCQCQ", "Position Request")
161
stationlist = self.emit("get-station-list")
162
for port in stationlist.keys():
163
self.emit("submit-rpc-job", job, port)
164
elif action == "sendfile":
165
fn = self._config.platform.gui_open_file()
170
if fnl.endswith(".jpg") or fnl.endswith(".jpeg") or \
171
fnl.endswith(".png") or fnl.endswith(".gif"):
172
fn = image.send_image(fn)
176
name = os.path.basename(fn)
177
self.emit("user-send-file", station, port, fn, name)
178
elif action == "version":
179
def log_result(job, state, result):
180
if state == "complete":
181
msg = "Station %s running D-RATS %s on %s" % (\
183
result.get("version", "Unknown"),
184
result.get("os", "Unknown"))
185
print "Station %s reports version info: %s" % (\
186
job.get_dest(), result)
189
msg = "No version response from %s" % job.get_dest()
190
event = main_events.Event(None, msg)
191
self.emit("event", event)
193
job = rpc.RPCGetVersion(station, "Version Request")
194
job.connect("state-change", log_result)
195
self.emit("submit-rpc-job", job, port)
196
elif action == "mcheck":
197
def log_result(job, state, result):
198
msg = "Mail check via %s: %s" % (job.get_dest(),
201
event = main_events.Event(None, msg)
202
self.emit("event", event)
204
vals = prompt_for_account(self._config)
208
job = rpc.RPCCheckMail(station, "Mail Check")
209
job.set_account(vals[0], vals[1], vals[2], vals[4], vals[3])
210
job.connect("state-change", log_result)
211
self.emit("submit-rpc-job", job, port)
213
def _make_station_menu(self, station, port):
217
<menuitem action="ping"/>
218
<menuitem action="conntest"/>
219
<menuitem action="reqpos"/>
220
<menuitem action="sendfile"/>
221
<menuitem action="version"/>
222
<menuitem action="mcheck"/>
224
<menuitem action="remove"/>
225
<menuitem action="reset"/>
227
<menuitem action="clearall"/>
228
<menuitem action="pingall"/>
229
<menuitem action="reqposall"/>
233
ag = gtk.ActionGroup("menu")
234
actions = [("ping", _("Ping"), None),
235
("conntest", _("Test Connectivity"), None),
236
("reqpos", _("Request Position"), None),
237
("sendfile", _("Send file"), None),
238
("remove", _("Remove"), gtk.STOCK_DELETE),
239
("reset", _("Reset"), gtk.STOCK_JUMP_TO),
240
("version", _("Get version"), gtk.STOCK_ABOUT),
241
("mcheck", _("Request mail check"), None)]
243
for action, label, stock in actions:
244
a = gtk.Action(action, label, None, stock)
245
a.connect("activate", self._mh, station, port)
246
a.set_sensitive(station is not None)
249
actions = [("clearall", _("Clear All"), gtk.STOCK_CLEAR),
250
("pingall", _("Ping All Stations"), None),
251
("reqposall", _("Request all positions"), None)]
252
for action, label, stock in actions:
253
a = gtk.Action(action, label, None, stock)
254
a.connect("activate", self._mh, station, port)
257
uim = gtk.UIManager()
258
uim.insert_action_group(ag, 0)
259
uim.add_ui_from_string(xml)
261
return uim.get_widget("/menu")
263
def _mouse_cb(self, view, event):
264
if event.button != 3:
267
if event.window == view.get_bin_window():
268
x, y = event.get_coords()
269
pathinfo = view.get_path_at_pos(int(x), int(y))
274
view.set_cursor_on_cell(pathinfo[0])
275
(model, iter) = view.get_selection().get_selected()
276
station, port = model.get(iter, 0, 5)
278
menu = self._make_station_menu(station, port)
279
menu.popup(None, None, None, event.button, event.time)
281
def __init__(self, wtree, config):
282
MainWindowTab.__init__(self, wtree, config, "main")
284
frame, self.__view, = self._getw("stations_frame", "stations_view")
286
store = gtk.ListStore(gobject.TYPE_STRING, # Station
287
gobject.TYPE_INT, # Timestamp
288
gobject.TYPE_STRING, # Message
289
gobject.TYPE_INT, # Status
290
gobject.TYPE_STRING, # Status message
291
gobject.TYPE_STRING) # Port
292
store.set_sort_column_id(1, gtk.SORT_DESCENDING)
293
self.__view.set_model(store)
296
self.__view.set_tooltip_column(2)
297
except AttributeError:
298
print "This version of GTK is old; disabling station tooltips"
300
self.__view.connect("button_press_event", self._mouse_cb)
302
def render_call(col, rend, model, iter):
303
call, ts, status = model.get(iter, 0, 1, 3)
304
sec = time.time() - ts
312
msg = "%s (%im)" % (call, (sec / 60))
314
msg = "%s (%ih %im)" % (call, sec / 3600, (sec % 3600) / 60)
316
msg = "%s (%id %ih)" % (call, sec / day, (sec % day) / 3600)
318
if status == station_status.STATUS_ONLINE:
320
elif status == station_status.STATUS_UNATTENDED:
322
elif status == station_status.STATUS_OFFLINE:
327
rend.set_property("markup", "<span color='%s'>%s</span>" % (color,
330
r = gtk.CellRendererText()
331
col = gtk.TreeViewColumn(_("Stations"), r, text=0)
332
col.set_cell_data_func(r, render_call)
333
self.__view.append_column(col)
336
self._update_station_count()
338
status, msg = self._getw("stations_status", "stations_smsg")
341
status.set_tooltip_text(_("This is the state other stations will " +
342
"see when requesting your status"))
343
msg.set_tooltip_text(_("This is the message other stations will " +
344
"see when requesting your status"))
345
except AttributeError:
349
self.__status = cb.get_active_text()
350
self._config.set("state", "status_state", self.__status)
353
self.__smsg = e.get_text()
354
self._config.set("state", "status_msg", self.__smsg)
356
for s in sorted(station_status.get_status_msgs().values()):
357
status.append_text(s)
359
status.connect("changed", set_status)
360
msg.connect("changed", set_smsg)
362
prev_status = self._config.get("state", "status_state")
363
if not utils.combo_select(status, prev_status):
364
utils.combo_select(status,
365
station_status.get_status_msgs().values()[0])
366
msg.set_text(self._config.get("state", "status_msg"))
370
gobject.timeout_add(30000, self._update)
372
def _update_station_count(self):
373
hdr, = self._getw("stations_header")
374
hdr.set_markup("<b>Stations (%i)</b>" % len(self.__calls))
376
def saw_station(self, station, port, status=0, smsg=""):
377
status_changed = False
379
if station == "CQCQCQ":
382
store = self.__view.get_model()
385
msg = "%s <b>%s</b> %s <i>%s</i>\r\n%s: <b>%s</b>" % \
389
time.strftime("%X %x",
394
status_val = station_status.get_status_msgs().get(status, "Unknown")
395
if station not in self.__calls:
397
msg += "\r\nStatus: <b>%s</b> (<i>%s</i>)" % (status_val, smsg)
398
self.__calls.append(station)
399
store.append((station, ts, msg, status, smsg, port))
400
self.__view.queue_draw()
401
status_changed = True
402
self._update_station_count()
404
iter = store.get_iter_first()
406
call, _status, _smsg = store.get(iter, 0, 3, 4)
408
status_changed = (status and (_status != status) or \
409
(smsg and (_smsg != smsg)))
411
if _status > 0 and status == 0:
416
msg += "\r\nStatus: <b>%s</b> (<i>%s</i>)" % (status_val,
418
store.set(iter, 1, ts, 2, msg, 3, status, 4, smsg, 5, port)
420
iter = store.iter_next(iter)
422
if status_changed and status > 0 and \
423
self._config.getboolean("prefs", "chat_showstatus"):
424
self.emit("incoming-chat-message",
427
"%s %s: %s (%s %s)" % (_("Now"), status_val, smsg,
430
def get_status(self):
431
sval = station_status.get_status_vals()[self.__status]
433
return sval, self.__smsg
435
def get_stations(self):
437
store = self.__view.get_model()
438
iter = store.get_iter_first()
440
call, ts, port = store.get(iter, 0, 1, 5)
441
station = station_status.Station(call)
442
station.set_heard(ts)
443
station.set_port(port)
444
stations.append(station)
445
iter = store.iter_next(iter)