1
|
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
|
2
|
# Copyright 2012 Tom Hayward <tom@tomh.us>
|
3
|
#
|
4
|
# This program is free software: you can redistribute it and/or modify
|
5
|
# it under the terms of the GNU General Public License as published by
|
6
|
# the Free Software Foundation, either version 3 of the License, or
|
7
|
# (at your option) any later version.
|
8
|
#
|
9
|
# This program is distributed in the hope that it will be useful,
|
10
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
# GNU General Public License for more details.
|
13
|
#
|
14
|
# You should have received a copy of the GNU General Public License
|
15
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
|
17
|
import os
|
18
|
import tempfile
|
19
|
import urllib
|
20
|
from glob import glob
|
21
|
import shutil
|
22
|
import time
|
23
|
|
24
|
import gtk
|
25
|
import gobject
|
26
|
gobject.threads_init()
|
27
|
|
28
|
if __name__ == "__main__":
|
29
|
import sys
|
30
|
sys.path.insert(0, "..")
|
31
|
|
32
|
from chirpui import inputdialog, common
|
33
|
try:
|
34
|
import serial
|
35
|
except ImportError,e:
|
36
|
common.log_exception()
|
37
|
common.show_error("\nThe Pyserial module is not installed!")
|
38
|
from chirp import platform, generic_xml, generic_csv, directory, util
|
39
|
from chirp import ic9x, kenwood_live, idrp, vx7, vx5, vx6
|
40
|
from chirp import CHIRP_VERSION, chirp_common, detect, errors
|
41
|
from chirp import icf, ic9x_icf
|
42
|
from chirpui import editorset, clone, miscwidgets, config, reporting, fips
|
43
|
from chirpui import bandplans
|
44
|
|
45
|
CONF = config.get()
|
46
|
|
47
|
KEEP_RECENT = 8
|
48
|
|
49
|
RB_BANDS = {
|
50
|
"--All--" : 0,
|
51
|
"10 meters (29MHz)" : 29,
|
52
|
"6 meters (54MHz)" : 5,
|
53
|
"2 meters (144MHz)" : 14,
|
54
|
"1.25 meters (220MHz)" : 22,
|
55
|
"70 centimeters (440MHz)" : 4,
|
56
|
"33 centimeters (900MHz)" : 9,
|
57
|
"23 centimeters (1.2GHz)" : 12,
|
58
|
}
|
59
|
|
60
|
def key_bands(band):
|
61
|
if band.startswith("-"):
|
62
|
return -1
|
63
|
|
64
|
amount, units, mhz = band.split(" ")
|
65
|
scale = units == "meters" and 100 or 1
|
66
|
|
67
|
return 100000 - (float(amount) * scale)
|
68
|
|
69
|
class ModifiedError(Exception):
|
70
|
pass
|
71
|
|
72
|
class ChirpMain(gtk.Window):
|
73
|
def get_current_editorset(self):
|
74
|
page = self.tabs.get_current_page()
|
75
|
if page is not None:
|
76
|
return self.tabs.get_nth_page(page)
|
77
|
else:
|
78
|
return None
|
79
|
|
80
|
def ev_tab_switched(self, pagenum=None):
|
81
|
def set_action_sensitive(action, sensitive):
|
82
|
self.menu_ag.get_action(action).set_sensitive(sensitive)
|
83
|
|
84
|
if pagenum is not None:
|
85
|
eset = self.tabs.get_nth_page(pagenum)
|
86
|
else:
|
87
|
eset = self.get_current_editorset()
|
88
|
|
89
|
upload_sens = bool(eset and
|
90
|
isinstance(eset.radio, chirp_common.CloneModeRadio))
|
91
|
|
92
|
if not eset or isinstance(eset.radio, chirp_common.LiveRadio):
|
93
|
save_sens = False
|
94
|
elif isinstance(eset.radio, chirp_common.NetworkSourceRadio):
|
95
|
save_sens = False
|
96
|
else:
|
97
|
save_sens = True
|
98
|
|
99
|
for i in ["import", "importsrc", "stock"]:
|
100
|
set_action_sensitive(i,
|
101
|
eset is not None and not eset.get_read_only())
|
102
|
|
103
|
for i in ["save", "saveas"]:
|
104
|
set_action_sensitive(i, save_sens)
|
105
|
|
106
|
for i in ["upload"]:
|
107
|
set_action_sensitive(i, upload_sens)
|
108
|
|
109
|
for i in ["cancelq"]:
|
110
|
set_action_sensitive(i, eset is not None and not save_sens)
|
111
|
|
112
|
for i in ["export", "close", "columns", "irbook", "irfinder",
|
113
|
"move_up", "move_dn", "exchange", "iradioreference",
|
114
|
"cut", "copy", "paste", "delete", "viewdeveloper"]:
|
115
|
set_action_sensitive(i, eset is not None)
|
116
|
|
117
|
def ev_status(self, editorset, msg):
|
118
|
self.sb_radio.pop(0)
|
119
|
self.sb_radio.push(0, msg)
|
120
|
|
121
|
def ev_usermsg(self, editorset, msg):
|
122
|
self.sb_general.pop(0)
|
123
|
self.sb_general.push(0, msg)
|
124
|
|
125
|
def ev_editor_selected(self, editorset, editortype):
|
126
|
mappings = {
|
127
|
"memedit" : ["view", "edit"],
|
128
|
}
|
129
|
|
130
|
for _editortype, actions in mappings.items():
|
131
|
for _action in actions:
|
132
|
action = self.menu_ag.get_action(_action)
|
133
|
action.set_sensitive(editortype.startswith(_editortype))
|
134
|
|
135
|
def _connect_editorset(self, eset):
|
136
|
eset.connect("want-close", self.do_close)
|
137
|
eset.connect("status", self.ev_status)
|
138
|
eset.connect("usermsg", self.ev_usermsg)
|
139
|
eset.connect("editor-selected", self.ev_editor_selected)
|
140
|
|
141
|
def do_diff_radio(self):
|
142
|
if self.tabs.get_n_pages() < 2:
|
143
|
common.show_error("Diff tabs requires at least two open tabs!")
|
144
|
return
|
145
|
|
146
|
esets = []
|
147
|
for i in range(0, self.tabs.get_n_pages()):
|
148
|
esets.append(self.tabs.get_nth_page(i))
|
149
|
|
150
|
d = gtk.Dialog(title="Diff Radios",
|
151
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
152
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
|
153
|
parent=self)
|
154
|
|
155
|
label = gtk.Label("")
|
156
|
label.set_markup("<b>-1</b> for either Mem # does a full-file hex " +
|
157
|
"dump with diffs highlighted.\n" +
|
158
|
"<b>-2</b> for first Mem # shows <b>only</b> the diffs.")
|
159
|
d.vbox.pack_start(label, True, True, 0)
|
160
|
label.show()
|
161
|
|
162
|
choices = []
|
163
|
for eset in esets:
|
164
|
choices.append("%s %s (%s)" % (eset.rthread.radio.VENDOR,
|
165
|
eset.rthread.radio.MODEL,
|
166
|
eset.filename))
|
167
|
choice_a = miscwidgets.make_choice(choices, False, choices[0])
|
168
|
choice_a.show()
|
169
|
chan_a = gtk.SpinButton()
|
170
|
chan_a.get_adjustment().set_all(1, -2, 999, 1, 10, 0)
|
171
|
chan_a.show()
|
172
|
hbox = gtk.HBox(False, 3)
|
173
|
hbox.pack_start(choice_a, 1, 1, 1)
|
174
|
hbox.pack_start(chan_a, 0, 0, 0)
|
175
|
hbox.show()
|
176
|
d.vbox.pack_start(hbox, 0, 0, 0)
|
177
|
|
178
|
choice_b = miscwidgets.make_choice(choices, False, choices[1])
|
179
|
choice_b.show()
|
180
|
chan_b = gtk.SpinButton()
|
181
|
chan_b.get_adjustment().set_all(1, -1, 999, 1, 10, 0)
|
182
|
chan_b.show()
|
183
|
hbox = gtk.HBox(False, 3)
|
184
|
hbox.pack_start(choice_b, 1, 1, 1)
|
185
|
hbox.pack_start(chan_b, 0, 0, 0)
|
186
|
hbox.show()
|
187
|
d.vbox.pack_start(hbox, 0, 0, 0)
|
188
|
|
189
|
r = d.run()
|
190
|
sel_a = choice_a.get_active_text()
|
191
|
sel_chan_a = chan_a.get_value()
|
192
|
sel_b = choice_b.get_active_text()
|
193
|
sel_chan_b = chan_b.get_value()
|
194
|
d.destroy()
|
195
|
if r == gtk.RESPONSE_CANCEL:
|
196
|
return
|
197
|
|
198
|
if sel_a == sel_b:
|
199
|
common.show_error("Can't diff the same tab!")
|
200
|
return
|
201
|
|
202
|
print "Selected %s@%i and %s@%i" % (sel_a, sel_chan_a,
|
203
|
sel_b, sel_chan_b)
|
204
|
name_a = os.path.basename (sel_a)
|
205
|
name_a = name_a[:name_a.rindex(")")]
|
206
|
name_b = os.path.basename (sel_b)
|
207
|
name_b = name_b[:name_b.rindex(")")]
|
208
|
diffwintitle = "%s@%i diff %s@%i" % (
|
209
|
name_a, sel_chan_a, name_b, sel_chan_b)
|
210
|
|
211
|
eset_a = esets[choices.index(sel_a)]
|
212
|
eset_b = esets[choices.index(sel_b)]
|
213
|
|
214
|
def _show_diff(mem_b, mem_a):
|
215
|
# Step 3: Show the diff
|
216
|
diff = common.simple_diff(mem_a, mem_b)
|
217
|
common.show_diff_blob(diffwintitle, diff)
|
218
|
|
219
|
def _get_mem_b(mem_a):
|
220
|
# Step 2: Get memory b
|
221
|
job = common.RadioJob(_show_diff, "get_raw_memory", int(sel_chan_b))
|
222
|
job.set_cb_args(mem_a)
|
223
|
eset_b.rthread.submit(job)
|
224
|
|
225
|
if sel_chan_a >= 0 and sel_chan_b >= 0:
|
226
|
# Diff numbered memory
|
227
|
# Step 1: Get memory a
|
228
|
job = common.RadioJob(_get_mem_b, "get_raw_memory", int(sel_chan_a))
|
229
|
eset_a.rthread.submit(job)
|
230
|
elif isinstance(eset_a.rthread.radio, chirp_common.CloneModeRadio) and\
|
231
|
isinstance(eset_b.rthread.radio, chirp_common.CloneModeRadio):
|
232
|
# Diff whole (can do this without a job, since both are clone-mode)
|
233
|
try:
|
234
|
addrfmt = CONF.get('hexdump_addrfmt', section='developer',
|
235
|
raw=True)
|
236
|
except:
|
237
|
pass
|
238
|
a = util.hexprint(eset_a.rthread.radio._mmap.get_packed(),
|
239
|
addrfmt=addrfmt)
|
240
|
b = util.hexprint(eset_b.rthread.radio._mmap.get_packed(),
|
241
|
addrfmt=addrfmt)
|
242
|
if sel_chan_a == -2:
|
243
|
diffsonly = True
|
244
|
else:
|
245
|
diffsonly = False
|
246
|
common.show_diff_blob(diffwintitle,
|
247
|
common.simple_diff(a, b, diffsonly))
|
248
|
else:
|
249
|
common.show_error("Cannot diff whole live-mode radios!")
|
250
|
|
251
|
def do_new(self):
|
252
|
eset = editorset.EditorSet(_("Untitled") + ".csv", self)
|
253
|
self._connect_editorset(eset)
|
254
|
eset.prime()
|
255
|
eset.show()
|
256
|
|
257
|
tab = self.tabs.append_page(eset, eset.get_tab_label())
|
258
|
self.tabs.set_current_page(tab)
|
259
|
|
260
|
def _do_manual_select(self, filename):
|
261
|
radiolist = {}
|
262
|
for drv, radio in directory.DRV_TO_RADIO.items():
|
263
|
if not issubclass(radio, chirp_common.CloneModeRadio):
|
264
|
continue
|
265
|
radiolist["%s %s" % (radio.VENDOR, radio.MODEL)] = drv
|
266
|
|
267
|
lab = gtk.Label("""<b><big>Unable to detect model!</big></b>
|
268
|
|
269
|
If you think that it is valid, you can select a radio model below to force an open attempt. If selecting the model manually works, please file a bug on the website and attach your image. If selecting the model does not work, it is likely that you are trying to open some other type of file.
|
270
|
""")
|
271
|
|
272
|
lab.set_justify(gtk.JUSTIFY_FILL)
|
273
|
lab.set_line_wrap(True)
|
274
|
lab.set_use_markup(True)
|
275
|
lab.show()
|
276
|
choice = miscwidgets.make_choice(sorted(radiolist.keys()), False,
|
277
|
sorted(radiolist.keys())[0])
|
278
|
d = gtk.Dialog(title="Detection Failed",
|
279
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
280
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
281
|
d.vbox.pack_start(lab, 0, 0, 0)
|
282
|
d.vbox.pack_start(choice, 0, 0, 0)
|
283
|
d.vbox.set_spacing(5)
|
284
|
choice.show()
|
285
|
d.set_default_size(400, 200)
|
286
|
#d.set_resizable(False)
|
287
|
r = d.run()
|
288
|
d.destroy()
|
289
|
if r != gtk.RESPONSE_OK:
|
290
|
return
|
291
|
try:
|
292
|
rc = directory.DRV_TO_RADIO[radiolist[choice.get_active_text()]]
|
293
|
return rc(filename)
|
294
|
except:
|
295
|
return
|
296
|
|
297
|
def do_open(self, fname=None, tempname=None):
|
298
|
if not fname:
|
299
|
types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"),
|
300
|
(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
|
301
|
(_("CSV Files") + " (*.csv)", "*.csv"),
|
302
|
(_("DAT Files") + " (*.dat)", "*.dat"),
|
303
|
(_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
|
304
|
(_("ICF Files") + " (*.icf)", "*.icf"),
|
305
|
(_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
|
306
|
(_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
|
307
|
(_("VX7 Commander Files") + " (*.vx7)", "*.vx7"),
|
308
|
]
|
309
|
fname = platform.get_platform().gui_open_file(types=types)
|
310
|
if not fname:
|
311
|
return
|
312
|
|
313
|
self.record_recent_file(fname)
|
314
|
|
315
|
if icf.is_icf_file(fname):
|
316
|
a = common.ask_yesno_question(\
|
317
|
_("ICF files cannot be edited, only displayed or imported "
|
318
|
"into another file. Open in read-only mode?"),
|
319
|
self)
|
320
|
if not a:
|
321
|
return
|
322
|
read_only = True
|
323
|
else:
|
324
|
read_only = False
|
325
|
|
326
|
if icf.is_9x_icf(fname):
|
327
|
# We have to actually instantiate the IC9xICFRadio to get its
|
328
|
# sub-devices
|
329
|
radio = ic9x_icf.IC9xICFRadio(fname)
|
330
|
else:
|
331
|
try:
|
332
|
radio = directory.get_radio_by_image(fname)
|
333
|
except errors.ImageDetectFailed:
|
334
|
radio = self._do_manual_select(fname)
|
335
|
if not radio:
|
336
|
return
|
337
|
print "Manually selected %s" % radio
|
338
|
except Exception, e:
|
339
|
common.log_exception()
|
340
|
common.show_error(os.path.basename(fname) + ": " + str(e))
|
341
|
return
|
342
|
|
343
|
first_tab = False
|
344
|
try:
|
345
|
eset = editorset.EditorSet(radio, self,
|
346
|
filename=fname,
|
347
|
tempname=tempname)
|
348
|
except Exception, e:
|
349
|
common.log_exception()
|
350
|
common.show_error(
|
351
|
_("There was an error opening {fname}: {error}").format(
|
352
|
fname=fname,
|
353
|
error=e))
|
354
|
return
|
355
|
|
356
|
eset.set_read_only(read_only)
|
357
|
self._connect_editorset(eset)
|
358
|
eset.show()
|
359
|
self.tabs.append_page(eset, eset.get_tab_label())
|
360
|
|
361
|
if hasattr(eset.rthread.radio, "errors") and \
|
362
|
eset.rthread.radio.errors:
|
363
|
msg = _("{num} errors during open:").format(
|
364
|
num=len(eset.rthread.radio.errors))
|
365
|
common.show_error_text(msg,
|
366
|
"\r\n".join(eset.rthread.radio.errors))
|
367
|
|
368
|
def do_live_warning(self, radio):
|
369
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
370
|
d.set_markup("<big><b>" + _("Note:") + "</b></big>")
|
371
|
msg = _("The {vendor} {model} operates in <b>live mode</b>. "
|
372
|
"This means that any changes you make are immediately sent "
|
373
|
"to the radio. Because of this, you cannot perform the "
|
374
|
"<u>Save</u> or <u>Upload</u> operations. If you wish to "
|
375
|
"edit the contents offline, please <u>Export</u> to a CSV "
|
376
|
"file, using the <b>File menu</b>.").format(vendor=radio.VENDOR,
|
377
|
model=radio.MODEL)
|
378
|
d.format_secondary_markup(msg)
|
379
|
|
380
|
again = gtk.CheckButton(_("Don't show this again"))
|
381
|
again.show()
|
382
|
d.vbox.pack_start(again, 0, 0, 0)
|
383
|
d.run()
|
384
|
CONF.set_bool("live_mode", again.get_active(), "noconfirm")
|
385
|
d.destroy()
|
386
|
|
387
|
def do_open_live(self, radio, tempname=None, read_only=False):
|
388
|
eset = editorset.EditorSet(radio, self, tempname=tempname)
|
389
|
eset.connect("want-close", self.do_close)
|
390
|
eset.connect("status", self.ev_status)
|
391
|
eset.set_read_only(read_only)
|
392
|
eset.show()
|
393
|
self.tabs.append_page(eset, eset.get_tab_label())
|
394
|
|
395
|
if isinstance(radio, chirp_common.LiveRadio):
|
396
|
reporting.report_model_usage(radio, "live", True)
|
397
|
if not CONF.get_bool("live_mode", "noconfirm"):
|
398
|
self.do_live_warning(radio)
|
399
|
|
400
|
def do_save(self, eset=None):
|
401
|
if not eset:
|
402
|
eset = self.get_current_editorset()
|
403
|
|
404
|
# For usability, allow Ctrl-S to short-circuit to Save-As if
|
405
|
# we are working on a yet-to-be-saved image
|
406
|
if not os.path.exists(eset.filename):
|
407
|
return self.do_saveas()
|
408
|
|
409
|
eset.save()
|
410
|
|
411
|
def do_saveas(self):
|
412
|
eset = self.get_current_editorset()
|
413
|
|
414
|
label = _("{vendor} {model} image file").format(\
|
415
|
vendor=eset.radio.VENDOR,
|
416
|
model=eset.radio.MODEL)
|
417
|
|
418
|
types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
|
419
|
eset.radio.FILE_EXTENSION)]
|
420
|
|
421
|
if isinstance(eset.radio, vx7.VX7Radio):
|
422
|
types += [(_("VX7 Commander") + " (*.vx7)", "vx7")]
|
423
|
elif isinstance(eset.radio, vx6.VX6Radio):
|
424
|
types += [(_("VX6 Commander") + " (*.vx6)", "vx6")]
|
425
|
elif isinstance(eset.radio, vx5.VX5Radio):
|
426
|
types += [(_("EVE") + " (*.eve)", "eve")]
|
427
|
types += [(_("VX5 Commander") + " (*.vx5)", "vx5")]
|
428
|
|
429
|
while True:
|
430
|
fname = platform.get_platform().gui_save_file(types=types)
|
431
|
if not fname:
|
432
|
return
|
433
|
|
434
|
if os.path.exists(fname):
|
435
|
dlg = inputdialog.OverwriteDialog(fname)
|
436
|
owrite = dlg.run()
|
437
|
dlg.destroy()
|
438
|
if owrite == gtk.RESPONSE_OK:
|
439
|
break
|
440
|
else:
|
441
|
break
|
442
|
|
443
|
try:
|
444
|
eset.save(fname)
|
445
|
except Exception,e:
|
446
|
d = inputdialog.ExceptionDialog(e)
|
447
|
d.run()
|
448
|
d.destroy()
|
449
|
|
450
|
def cb_clonein(self, radio, emsg=None):
|
451
|
radio.pipe.close()
|
452
|
reporting.report_model_usage(radio, "download", bool(emsg))
|
453
|
if not emsg:
|
454
|
self.do_open_live(radio, tempname="(" + _("Untitled") + ")")
|
455
|
else:
|
456
|
d = inputdialog.ExceptionDialog(emsg)
|
457
|
d.run()
|
458
|
d.destroy()
|
459
|
|
460
|
def cb_cloneout(self, radio, emsg= None):
|
461
|
radio.pipe.close()
|
462
|
reporting.report_model_usage(radio, "upload", True)
|
463
|
if emsg:
|
464
|
d = inputdialog.ExceptionDialog(emsg)
|
465
|
d.run()
|
466
|
d.destroy()
|
467
|
|
468
|
def _get_recent_list(self):
|
469
|
recent = []
|
470
|
for i in range(0, KEEP_RECENT):
|
471
|
fn = CONF.get("recent%i" % i, "state")
|
472
|
if fn:
|
473
|
recent.append(fn)
|
474
|
return recent
|
475
|
|
476
|
def _set_recent_list(self, recent):
|
477
|
for fn in recent:
|
478
|
CONF.set("recent%i" % recent.index(fn), fn, "state")
|
479
|
|
480
|
def update_recent_files(self):
|
481
|
i = 0
|
482
|
for fname in self._get_recent_list():
|
483
|
action_name = "recent%i" % i
|
484
|
path = "/MenuBar/file/recent"
|
485
|
|
486
|
old_action = self.menu_ag.get_action(action_name)
|
487
|
if old_action:
|
488
|
self.menu_ag.remove_action(old_action)
|
489
|
|
490
|
file_basename = os.path.basename(fname).replace("_", "__")
|
491
|
action = gtk.Action(action_name,
|
492
|
"_%i. %s" % (i+1, file_basename),
|
493
|
_("Open recent file {name}").format(name=fname),
|
494
|
"")
|
495
|
action.connect("activate", lambda a,f: self.do_open(f), fname)
|
496
|
mid = self.menu_uim.new_merge_id()
|
497
|
self.menu_uim.add_ui(mid, path,
|
498
|
action_name, action_name,
|
499
|
gtk.UI_MANAGER_MENUITEM, False)
|
500
|
self.menu_ag.add_action(action)
|
501
|
i += 1
|
502
|
|
503
|
def record_recent_file(self, filename):
|
504
|
|
505
|
recent_files = self._get_recent_list()
|
506
|
if filename not in recent_files:
|
507
|
if len(recent_files) == KEEP_RECENT:
|
508
|
del recent_files[-1]
|
509
|
recent_files.insert(0, filename)
|
510
|
self._set_recent_list(recent_files)
|
511
|
|
512
|
self.update_recent_files()
|
513
|
|
514
|
def import_stock_config(self, action, config):
|
515
|
eset = self.get_current_editorset()
|
516
|
count = eset.do_import(config)
|
517
|
|
518
|
def copy_shipped_stock_configs(self, stock_dir):
|
519
|
execpath = platform.get_platform().executable_path()
|
520
|
basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
|
521
|
if not os.path.exists(basepath):
|
522
|
basepath = "/usr/share/chirp/stock_configs"
|
523
|
|
524
|
files = glob(os.path.join(basepath, "*.csv"))
|
525
|
for fn in files:
|
526
|
if os.path.exists(os.path.join(stock_dir, os.path.basename(fn))):
|
527
|
print "Skipping existing stock config"
|
528
|
continue
|
529
|
try:
|
530
|
shutil.copy(fn, stock_dir)
|
531
|
print "Copying %s -> %s" % (fn, stock_dir)
|
532
|
except Exception, e:
|
533
|
print "ERROR: Unable to copy %s to %s: %s" % (fn, stock_dir, e)
|
534
|
return False
|
535
|
return True
|
536
|
|
537
|
def update_stock_configs(self):
|
538
|
stock_dir = platform.get_platform().config_file("stock_configs")
|
539
|
if not os.path.isdir(stock_dir):
|
540
|
try:
|
541
|
os.mkdir(stock_dir)
|
542
|
except Exception, e:
|
543
|
print "ERROR: Unable to create directory: %s" % stock_dir
|
544
|
return
|
545
|
if not self.copy_shipped_stock_configs(stock_dir):
|
546
|
return
|
547
|
|
548
|
def _do_import_action(config):
|
549
|
name = os.path.splitext(os.path.basename(config))[0]
|
550
|
action_name = "stock-%i" % configs.index(config)
|
551
|
path = "/MenuBar/radio/stock"
|
552
|
action = gtk.Action(action_name,
|
553
|
name,
|
554
|
_("Import stock "
|
555
|
"configuration {name}").format(name=name),
|
556
|
"")
|
557
|
action.connect("activate", self.import_stock_config, config)
|
558
|
mid = self.menu_uim.new_merge_id()
|
559
|
mid = self.menu_uim.add_ui(mid, path,
|
560
|
action_name, action_name,
|
561
|
gtk.UI_MANAGER_MENUITEM, False)
|
562
|
self.menu_ag.add_action(action)
|
563
|
|
564
|
def _do_open_action(config):
|
565
|
name = os.path.splitext(os.path.basename(config))[0]
|
566
|
action_name = "openstock-%i" % configs.index(config)
|
567
|
path = "/MenuBar/file/openstock"
|
568
|
action = gtk.Action(action_name,
|
569
|
name,
|
570
|
_("Open stock "
|
571
|
"configuration {name}").format(name=name),
|
572
|
"")
|
573
|
action.connect("activate", lambda a,c: self.do_open(c), config)
|
574
|
mid = self.menu_uim.new_merge_id()
|
575
|
mid = self.menu_uim.add_ui(mid, path,
|
576
|
action_name, action_name,
|
577
|
gtk.UI_MANAGER_MENUITEM, False)
|
578
|
self.menu_ag.add_action(action)
|
579
|
|
580
|
|
581
|
configs = glob(os.path.join(stock_dir, "*.csv"))
|
582
|
for config in configs:
|
583
|
_do_import_action(config)
|
584
|
_do_open_action(config)
|
585
|
|
586
|
def _confirm_experimental(self, rclass):
|
587
|
sql_key = "warn_experimental_%s" % directory.radio_class_id(rclass)
|
588
|
if CONF.is_defined(sql_key, "state") and \
|
589
|
not CONF.get_bool(sql_key, "state"):
|
590
|
return True
|
591
|
|
592
|
title = _("Proceed with experimental driver?")
|
593
|
text = rclass.get_prompts().experimental
|
594
|
msg = _("This radio's driver is experimental. "
|
595
|
"Do you want to proceed?")
|
596
|
resp, squelch = common.show_warning(msg, text,
|
597
|
title=title,
|
598
|
buttons=gtk.BUTTONS_YES_NO,
|
599
|
can_squelch=True)
|
600
|
if resp == gtk.RESPONSE_YES:
|
601
|
CONF.set_bool(sql_key, not squelch, "state")
|
602
|
return resp == gtk.RESPONSE_YES
|
603
|
|
604
|
def _show_instructions(self, radio, message):
|
605
|
if message is None:
|
606
|
return
|
607
|
|
608
|
if CONF.get_bool("clone_instructions", "noconfirm"):
|
609
|
return
|
610
|
|
611
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
612
|
d.set_markup("<big><b>" + _("{name} Instructions").format(
|
613
|
name=radio.get_name()) + "</b></big>")
|
614
|
msg = _("{instructions}").format(instructions=message)
|
615
|
d.format_secondary_markup(msg)
|
616
|
|
617
|
again = gtk.CheckButton(_("Don't show instructions for any radio again"))
|
618
|
again.show()
|
619
|
d.vbox.pack_start(again, 0, 0, 0)
|
620
|
h_button_box = d.vbox.get_children()[2]
|
621
|
try:
|
622
|
ok_button = h_button_box.get_children()[0]
|
623
|
ok_button.grab_default()
|
624
|
ok_button.grab_focus()
|
625
|
except AttributeError:
|
626
|
# don't grab focus on GTK+ 2.0
|
627
|
pass
|
628
|
d.run()
|
629
|
d.destroy()
|
630
|
CONF.set_bool("clone_instructions", again.get_active(), "noconfirm")
|
631
|
|
632
|
def do_download(self, port=None, rtype=None):
|
633
|
d = clone.CloneSettingsDialog(parent=self)
|
634
|
settings = d.run()
|
635
|
d.destroy()
|
636
|
if not settings:
|
637
|
return
|
638
|
|
639
|
rclass = settings.radio_class
|
640
|
if issubclass(rclass, chirp_common.ExperimentalRadio) and \
|
641
|
not self._confirm_experimental(rclass):
|
642
|
# User does not want to proceed with experimental driver
|
643
|
return
|
644
|
|
645
|
self._show_instructions(rclass, rclass.get_prompts().pre_download)
|
646
|
|
647
|
print "User selected %s %s on port %s" % (rclass.VENDOR,
|
648
|
rclass.MODEL,
|
649
|
settings.port)
|
650
|
|
651
|
try:
|
652
|
ser = serial.Serial(port=settings.port,
|
653
|
baudrate=rclass.BAUD_RATE,
|
654
|
rtscts=rclass.HARDWARE_FLOW,
|
655
|
timeout=0.25)
|
656
|
ser.flushInput()
|
657
|
except serial.SerialException, e:
|
658
|
d = inputdialog.ExceptionDialog(e)
|
659
|
d.run()
|
660
|
d.destroy()
|
661
|
return
|
662
|
|
663
|
radio = settings.radio_class(ser)
|
664
|
|
665
|
fn = tempfile.mktemp()
|
666
|
if isinstance(radio, chirp_common.CloneModeRadio):
|
667
|
ct = clone.CloneThread(radio, "in", cb=self.cb_clonein, parent=self)
|
668
|
ct.start()
|
669
|
else:
|
670
|
self.do_open_live(radio)
|
671
|
|
672
|
def do_upload(self, port=None, rtype=None):
|
673
|
eset = self.get_current_editorset()
|
674
|
radio = eset.radio
|
675
|
|
676
|
settings = clone.CloneSettings()
|
677
|
settings.radio_class = radio.__class__
|
678
|
|
679
|
d = clone.CloneSettingsDialog(settings, parent=self)
|
680
|
settings = d.run()
|
681
|
d.destroy()
|
682
|
if not settings:
|
683
|
return
|
684
|
prompts = radio.get_prompts()
|
685
|
|
686
|
prompt_before = prompts.display_pre_upload_prompt_before_opening_port \
|
687
|
and not CONF.get_bool("force_after", "upload_prompt")
|
688
|
|
689
|
if prompt_before == True:
|
690
|
print "Opening port after pre_upload prompt."
|
691
|
self._show_instructions(radio, prompts.pre_upload)
|
692
|
|
693
|
if isinstance(radio, chirp_common.ExperimentalRadio) and \
|
694
|
not self._confirm_experimental(radio.__class__):
|
695
|
# User does not want to proceed with experimental driver
|
696
|
return
|
697
|
|
698
|
try:
|
699
|
ser = serial.Serial(port=settings.port,
|
700
|
baudrate=radio.BAUD_RATE,
|
701
|
rtscts=radio.HARDWARE_FLOW,
|
702
|
timeout=0.25)
|
703
|
ser.flushInput()
|
704
|
except serial.SerialException, e:
|
705
|
d = inputdialog.ExceptionDialog(e)
|
706
|
d.run()
|
707
|
d.destroy()
|
708
|
return
|
709
|
|
710
|
if prompt_before == False:
|
711
|
print "Opening port before pre_upload prompt."
|
712
|
self._show_instructions(radio, prompts.pre_upload)
|
713
|
|
714
|
radio.set_pipe(ser)
|
715
|
|
716
|
ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self)
|
717
|
ct.start()
|
718
|
|
719
|
def do_close(self, tab_child=None):
|
720
|
if tab_child:
|
721
|
eset = tab_child
|
722
|
else:
|
723
|
eset = self.get_current_editorset()
|
724
|
|
725
|
if not eset:
|
726
|
return False
|
727
|
|
728
|
if eset.is_modified():
|
729
|
dlg = miscwidgets.YesNoDialog(title=_("Save Changes?"),
|
730
|
parent=self,
|
731
|
buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES,
|
732
|
gtk.STOCK_NO, gtk.RESPONSE_NO,
|
733
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
734
|
dlg.set_text(_("File is modified, save changes before closing?"))
|
735
|
res = dlg.run()
|
736
|
dlg.destroy()
|
737
|
if res == gtk.RESPONSE_YES:
|
738
|
self.do_save(eset)
|
739
|
elif res == gtk.RESPONSE_CANCEL:
|
740
|
raise ModifiedError()
|
741
|
|
742
|
eset.rthread.stop()
|
743
|
eset.rthread.join()
|
744
|
|
745
|
eset.prepare_close()
|
746
|
|
747
|
if eset.radio.pipe:
|
748
|
eset.radio.pipe.close()
|
749
|
|
750
|
if isinstance(eset.radio, chirp_common.LiveRadio):
|
751
|
action = self.menu_ag.get_action("openlive")
|
752
|
if action:
|
753
|
action.set_sensitive(True)
|
754
|
|
755
|
page = self.tabs.page_num(eset)
|
756
|
if page is not None:
|
757
|
self.tabs.remove_page(page)
|
758
|
|
759
|
return True
|
760
|
|
761
|
def do_import(self):
|
762
|
types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
|
763
|
(_("CHIRP Radio Images") + " (*.img)", "*.img"),
|
764
|
(_("CSV Files") + " (*.csv)", "*.csv"),
|
765
|
(_("DAT Files") + " (*.dat)", "*.dat"),
|
766
|
(_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
|
767
|
(_("ICF Files") + " (*.icf)", "*.icf"),
|
768
|
(_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
|
769
|
(_("Kenwood ITM Files") + " (*.itm)", "*.itm"),
|
770
|
(_("Travel Plus Files") + " (*.tpe)", "*.tpe"),
|
771
|
(_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
|
772
|
(_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
|
773
|
(_("VX7 Commander Files") + " (*.vx7)", "*.vx7")]
|
774
|
filen = platform.get_platform().gui_open_file(types=types)
|
775
|
if not filen:
|
776
|
return
|
777
|
|
778
|
eset = self.get_current_editorset()
|
779
|
count = eset.do_import(filen)
|
780
|
reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
|
781
|
|
782
|
def do_repeaterbook_prompt(self):
|
783
|
if not CONF.get_bool("has_seen_credit", "repeaterbook"):
|
784
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
785
|
d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" + \
|
786
|
"<i>North American Repeater Directory</i></big>")
|
787
|
d.format_secondary_markup("For more information about this " +\
|
788
|
"free service, please go to\r\n" +\
|
789
|
"http://www.repeaterbook.com")
|
790
|
d.run()
|
791
|
d.destroy()
|
792
|
CONF.set_bool("has_seen_credit", True, "repeaterbook")
|
793
|
|
794
|
default_state = "Oregon"
|
795
|
default_county = "--All--"
|
796
|
default_band = "--All--"
|
797
|
try:
|
798
|
try:
|
799
|
code = int(CONF.get("state", "repeaterbook"))
|
800
|
except:
|
801
|
code = CONF.get("state", "repeaterbook")
|
802
|
for k,v in fips.FIPS_STATES.items():
|
803
|
if code == v:
|
804
|
default_state = k
|
805
|
break
|
806
|
|
807
|
code = CONF.get("county", "repeaterbook")
|
808
|
for k,v in fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items():
|
809
|
if code == v:
|
810
|
default_county = k
|
811
|
break
|
812
|
|
813
|
code = int(CONF.get("band", "repeaterbook"))
|
814
|
for k,v in RB_BANDS.items():
|
815
|
if code == v:
|
816
|
default_band = k
|
817
|
break
|
818
|
except:
|
819
|
pass
|
820
|
|
821
|
state = miscwidgets.make_choice(sorted(fips.FIPS_STATES.keys()),
|
822
|
False, default_state)
|
823
|
county = miscwidgets.make_choice(sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()),
|
824
|
False, default_county)
|
825
|
band = miscwidgets.make_choice(sorted(RB_BANDS.keys(), key=key_bands),
|
826
|
False, default_band)
|
827
|
def _changed(box, county):
|
828
|
state = fips.FIPS_STATES[box.get_active_text()]
|
829
|
county.get_model().clear()
|
830
|
for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()):
|
831
|
county.append_text(fips_county)
|
832
|
county.set_active(0)
|
833
|
state.connect("changed", _changed, county)
|
834
|
|
835
|
d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
|
836
|
d.add_field("State", state)
|
837
|
d.add_field("County", county)
|
838
|
d.add_field("Band", band)
|
839
|
|
840
|
r = d.run()
|
841
|
d.destroy()
|
842
|
if r != gtk.RESPONSE_OK:
|
843
|
return False
|
844
|
|
845
|
code = fips.FIPS_STATES[state.get_active_text()]
|
846
|
county_id = fips.FIPS_COUNTIES[code][county.get_active_text()]
|
847
|
freq = RB_BANDS[band.get_active_text()]
|
848
|
CONF.set("state", str(code), "repeaterbook")
|
849
|
CONF.set("county", str(county_id), "repeaterbook")
|
850
|
CONF.set("band", str(freq), "repeaterbook")
|
851
|
|
852
|
return True
|
853
|
|
854
|
def do_repeaterbook(self, do_import):
|
855
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
856
|
if not self.do_repeaterbook_prompt():
|
857
|
self.window.set_cursor(None)
|
858
|
return
|
859
|
|
860
|
try:
|
861
|
code = "%02i" % int(CONF.get("state", "repeaterbook"))
|
862
|
except:
|
863
|
try:
|
864
|
code = CONF.get("state", "repeaterbook")
|
865
|
except:
|
866
|
code = '41' # Oregon default
|
867
|
|
868
|
try:
|
869
|
county = CONF.get("county", "repeaterbook")
|
870
|
except:
|
871
|
county = '%' # --All-- default
|
872
|
|
873
|
try:
|
874
|
band = int(CONF.get("band", "repeaterbook"))
|
875
|
except:
|
876
|
band = 14 # 2m default
|
877
|
|
878
|
query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php?" + \
|
879
|
"func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \
|
880
|
"&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%"
|
881
|
query = query % (code, band and band or "%%", county and county or "%%")
|
882
|
|
883
|
# Do this in case the import process is going to take a while
|
884
|
# to make sure we process events leading up to this
|
885
|
gtk.gdk.window_process_all_updates()
|
886
|
while gtk.events_pending():
|
887
|
gtk.main_iteration(False)
|
888
|
|
889
|
fn = tempfile.mktemp(".csv")
|
890
|
filename, headers = urllib.urlretrieve(query, fn)
|
891
|
if not os.path.exists(filename):
|
892
|
print "Failed, headers were:"
|
893
|
print str(headers)
|
894
|
common.show_error(_("RepeaterBook query failed"))
|
895
|
self.window.set_cursor(None)
|
896
|
return
|
897
|
|
898
|
class RBRadio(generic_csv.CSVRadio,
|
899
|
chirp_common.NetworkSourceRadio):
|
900
|
VENDOR = "RepeaterBook"
|
901
|
MODEL = ""
|
902
|
|
903
|
try:
|
904
|
# Validate CSV
|
905
|
radio = RBRadio(filename)
|
906
|
if radio.errors:
|
907
|
reporting.report_misc_error("repeaterbook",
|
908
|
("query=%s\n" % query) +
|
909
|
("\n") +
|
910
|
("\n".join(radio.errors)))
|
911
|
except errors.InvalidDataError, e:
|
912
|
common.show_error(str(e))
|
913
|
self.window.set_cursor(None)
|
914
|
return
|
915
|
except Exception, e:
|
916
|
common.log_exception()
|
917
|
|
918
|
reporting.report_model_usage(radio, "import", True)
|
919
|
|
920
|
self.window.set_cursor(None)
|
921
|
if do_import:
|
922
|
eset = self.get_current_editorset()
|
923
|
count = eset.do_import(filename)
|
924
|
else:
|
925
|
self.do_open_live(radio, read_only=True)
|
926
|
|
927
|
def do_przemienniki_prompt(self):
|
928
|
d = inputdialog.FieldDialog(title='przemienniki.net query',
|
929
|
parent=self)
|
930
|
fields = {
|
931
|
"Country":
|
932
|
(miscwidgets.make_choice(['by', 'cz', 'de', 'lt', 'pl',
|
933
|
'sk', 'uk'], False),
|
934
|
lambda x: str(x.get_active_text())),
|
935
|
"Band":
|
936
|
(miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
|
937
|
'23cm', '13cm', '3cm'], False, '2m'),
|
938
|
lambda x: str(x.get_active_text())),
|
939
|
"Mode":
|
940
|
(miscwidgets.make_choice(['fm', 'dv'], False),
|
941
|
lambda x: str(x.get_active_text())),
|
942
|
"Only Working":
|
943
|
(miscwidgets.make_choice(['', 'yes'], False),
|
944
|
lambda x: str(x.get_active_text())),
|
945
|
"Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
|
946
|
"Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
|
947
|
"Range": (gtk.Entry(), lambda x: int(x.get_text())),
|
948
|
}
|
949
|
for name in sorted(fields.keys()):
|
950
|
value, fn = fields[name]
|
951
|
d.add_field(name, value)
|
952
|
while d.run() == gtk.RESPONSE_OK:
|
953
|
query = "http://przemienniki.net/export/chirp.csv?"
|
954
|
args = []
|
955
|
for name, (value, fn) in fields.items():
|
956
|
if isinstance(value, gtk.Entry):
|
957
|
contents = value.get_text()
|
958
|
else:
|
959
|
contents = value.get_active_text()
|
960
|
if contents:
|
961
|
try:
|
962
|
_value = fn(value)
|
963
|
except ValueError:
|
964
|
common.show_error(_("Invalid value for %s") % name)
|
965
|
query = None
|
966
|
continue
|
967
|
|
968
|
args.append("=".join((name.replace(" ", "").lower(),
|
969
|
contents)))
|
970
|
query += "&".join(args)
|
971
|
print query
|
972
|
d.destroy()
|
973
|
return query
|
974
|
|
975
|
d.destroy()
|
976
|
return query
|
977
|
|
978
|
def do_przemienniki(self, do_import):
|
979
|
url = self.do_przemienniki_prompt()
|
980
|
if not url:
|
981
|
return
|
982
|
|
983
|
fn = tempfile.mktemp(".csv")
|
984
|
filename, headers = urllib.urlretrieve(url, fn)
|
985
|
if not os.path.exists(filename):
|
986
|
print "Failed, headers were:"
|
987
|
print str(headers)
|
988
|
common.show_error(_("Query failed"))
|
989
|
return
|
990
|
|
991
|
class PRRadio(generic_csv.CSVRadio,
|
992
|
chirp_common.NetworkSourceRadio):
|
993
|
VENDOR = "przemienniki.net"
|
994
|
MODEL = ""
|
995
|
|
996
|
try:
|
997
|
radio = PRRadio(filename)
|
998
|
except Exception, e:
|
999
|
common.show_error(str(e))
|
1000
|
return
|
1001
|
|
1002
|
if do_import:
|
1003
|
eset = self.get_current_editorset()
|
1004
|
count = eset.do_import(filename)
|
1005
|
else:
|
1006
|
self.do_open_live(radio, read_only=True)
|
1007
|
|
1008
|
def do_rfinder_prompt(self):
|
1009
|
fields = {"1Email" : (gtk.Entry(),
|
1010
|
lambda x: "@" in x),
|
1011
|
"2Password" : (gtk.Entry(),
|
1012
|
lambda x: x),
|
1013
|
"3Latitude" : (gtk.Entry(),
|
1014
|
lambda x: float(x) < 90 and \
|
1015
|
float(x) > -90),
|
1016
|
"4Longitude": (gtk.Entry(),
|
1017
|
lambda x: float(x) < 180 and \
|
1018
|
float(x) > -180),
|
1019
|
"5Range_in_Miles": (gtk.Entry(),
|
1020
|
lambda x: int(x) > 0 and int(x) < 5000),
|
1021
|
}
|
1022
|
|
1023
|
d = inputdialog.FieldDialog(title="RFinder Login", parent=self)
|
1024
|
for k in sorted(fields.keys()):
|
1025
|
d.add_field(k[1:].replace("_", " "), fields[k][0])
|
1026
|
fields[k][0].set_text(CONF.get(k[1:], "rfinder") or "")
|
1027
|
fields[k][0].set_visibility(k != "2Password")
|
1028
|
|
1029
|
while d.run() == gtk.RESPONSE_OK:
|
1030
|
valid = True
|
1031
|
for k in sorted(fields.keys()):
|
1032
|
widget, validator = fields[k]
|
1033
|
try:
|
1034
|
if validator(widget.get_text()):
|
1035
|
CONF.set(k[1:], widget.get_text(), "rfinder")
|
1036
|
continue
|
1037
|
except Exception:
|
1038
|
pass
|
1039
|
common.show_error("Invalid value for %s" % k[1:])
|
1040
|
valid = False
|
1041
|
break
|
1042
|
|
1043
|
if valid:
|
1044
|
d.destroy()
|
1045
|
return True
|
1046
|
|
1047
|
d.destroy()
|
1048
|
return False
|
1049
|
|
1050
|
def do_rfinder(self, do_import):
|
1051
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
1052
|
if not self.do_rfinder_prompt():
|
1053
|
self.window.set_cursor(None)
|
1054
|
return
|
1055
|
|
1056
|
lat = CONF.get_float("Latitude", "rfinder")
|
1057
|
lon = CONF.get_float("Longitude", "rfinder")
|
1058
|
passwd = CONF.get("Password", "rfinder")
|
1059
|
email = CONF.get("Email", "rfinder")
|
1060
|
miles = CONF.get_int("Range_in_Miles", "rfinder")
|
1061
|
|
1062
|
# Do this in case the import process is going to take a while
|
1063
|
# to make sure we process events leading up to this
|
1064
|
gtk.gdk.window_process_all_updates()
|
1065
|
while gtk.events_pending():
|
1066
|
gtk.main_iteration(False)
|
1067
|
|
1068
|
if do_import:
|
1069
|
eset = self.get_current_editorset()
|
1070
|
count = eset.do_import("rfinder://%s/%s/%f/%f/%i" % (email, passwd, lat, lon, miles))
|
1071
|
else:
|
1072
|
from chirp import rfinder
|
1073
|
radio = rfinder.RFinderRadio(None)
|
1074
|
radio.set_params((lat, lon), miles, email, passwd)
|
1075
|
self.do_open_live(radio, read_only=True)
|
1076
|
|
1077
|
self.window.set_cursor(None)
|
1078
|
|
1079
|
def do_radioreference_prompt(self):
|
1080
|
fields = {"1Username" : (gtk.Entry(), lambda x: x),
|
1081
|
"2Password" : (gtk.Entry(), lambda x: x),
|
1082
|
"3Zipcode" : (gtk.Entry(), lambda x: x),
|
1083
|
}
|
1084
|
|
1085
|
d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
|
1086
|
parent=self)
|
1087
|
for k in sorted(fields.keys()):
|
1088
|
d.add_field(k[1:], fields[k][0])
|
1089
|
fields[k][0].set_text(CONF.get(k[1:], "radioreference") or "")
|
1090
|
fields[k][0].set_visibility(k != "2Password")
|
1091
|
|
1092
|
while d.run() == gtk.RESPONSE_OK:
|
1093
|
valid = True
|
1094
|
for k in sorted(fields.keys()):
|
1095
|
widget, validator = fields[k]
|
1096
|
try:
|
1097
|
if validator(widget.get_text()):
|
1098
|
CONF.set(k[1:], widget.get_text(), "radioreference")
|
1099
|
continue
|
1100
|
except Exception:
|
1101
|
pass
|
1102
|
common.show_error("Invalid value for %s" % k[1:])
|
1103
|
valid = False
|
1104
|
break
|
1105
|
|
1106
|
if valid:
|
1107
|
d.destroy()
|
1108
|
return True
|
1109
|
|
1110
|
d.destroy()
|
1111
|
return False
|
1112
|
|
1113
|
def do_radioreference(self, do_import):
|
1114
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
1115
|
if not self.do_radioreference_prompt():
|
1116
|
self.window.set_cursor(None)
|
1117
|
return
|
1118
|
|
1119
|
username = CONF.get("Username", "radioreference")
|
1120
|
passwd = CONF.get("Password", "radioreference")
|
1121
|
zipcode = CONF.get("Zipcode", "radioreference")
|
1122
|
|
1123
|
# Do this in case the import process is going to take a while
|
1124
|
# to make sure we process events leading up to this
|
1125
|
gtk.gdk.window_process_all_updates()
|
1126
|
while gtk.events_pending():
|
1127
|
gtk.main_iteration(False)
|
1128
|
|
1129
|
if do_import:
|
1130
|
eset = self.get_current_editorset()
|
1131
|
count = eset.do_import("radioreference://%s/%s/%s" % (zipcode, username, passwd))
|
1132
|
else:
|
1133
|
try:
|
1134
|
from chirp import radioreference
|
1135
|
radio = radioreference.RadioReferenceRadio(None)
|
1136
|
radio.set_params(zipcode, username, passwd)
|
1137
|
self.do_open_live(radio, read_only=True)
|
1138
|
except errors.RadioError, e:
|
1139
|
common.show_error(e)
|
1140
|
|
1141
|
self.window.set_cursor(None)
|
1142
|
|
1143
|
def do_export(self):
|
1144
|
types = [(_("CSV Files") + " (*.csv)", "csv"),
|
1145
|
(_("CHIRP Files") + " (*.chirp)", "chirp"),
|
1146
|
]
|
1147
|
|
1148
|
eset = self.get_current_editorset()
|
1149
|
|
1150
|
if os.path.exists(eset.filename):
|
1151
|
base = os.path.basename(eset.filename)
|
1152
|
if "." in base:
|
1153
|
base = base[:base.rindex(".")]
|
1154
|
defname = base
|
1155
|
else:
|
1156
|
defname = "radio"
|
1157
|
|
1158
|
filen = platform.get_platform().gui_save_file(default_name=defname,
|
1159
|
types=types)
|
1160
|
if not filen:
|
1161
|
return
|
1162
|
|
1163
|
if os.path.exists(filen):
|
1164
|
dlg = inputdialog.OverwriteDialog(filen)
|
1165
|
owrite = dlg.run()
|
1166
|
dlg.destroy()
|
1167
|
if owrite != gtk.RESPONSE_OK:
|
1168
|
return
|
1169
|
os.remove(filen)
|
1170
|
|
1171
|
count = eset.do_export(filen)
|
1172
|
reporting.report_model_usage(eset.rthread.radio, "export", count > 0)
|
1173
|
|
1174
|
def do_about(self):
|
1175
|
d = gtk.AboutDialog()
|
1176
|
d.set_transient_for(self)
|
1177
|
import sys
|
1178
|
verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % ( \
|
1179
|
".".join([str(x) for x in gtk.gtk_version]),
|
1180
|
".".join([str(x) for x in gtk.pygtk_version]),
|
1181
|
sys.version.split()[0])
|
1182
|
|
1183
|
d.set_name("CHIRP")
|
1184
|
d.set_version(CHIRP_VERSION)
|
1185
|
d.set_copyright("Copyright 2013 Dan Smith (KK7DS)")
|
1186
|
d.set_website("http://chirp.danplanet.com")
|
1187
|
d.set_authors(("Dan Smith KK7DS <dsmith@danplanet.com>",
|
1188
|
_("With significant contributions from:"),
|
1189
|
"Tom KD7LXL",
|
1190
|
"Marco IZ3GME",
|
1191
|
"Jim KC9HI"
|
1192
|
))
|
1193
|
d.set_translator_credits("Polish: Grzegorz SQ2RBY" +
|
1194
|
os.linesep +
|
1195
|
"Italian: Fabio IZ2QDH" +
|
1196
|
os.linesep +
|
1197
|
"Dutch: Michael PD4MT" +
|
1198
|
os.linesep +
|
1199
|
"German: Benjamin HB9EUK" +
|
1200
|
os.linesep +
|
1201
|
"Hungarian: Attila HA7JA" +
|
1202
|
os.linesep +
|
1203
|
"Russian: Dmitry Slukin" +
|
1204
|
os.linesep +
|
1205
|
"Portuguese (BR): Crezivando PP7CJ")
|
1206
|
d.set_comments(verinfo)
|
1207
|
|
1208
|
d.run()
|
1209
|
d.destroy()
|
1210
|
|
1211
|
def do_documentation(self):
|
1212
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
|
1213
|
type=gtk.MESSAGE_INFO)
|
1214
|
|
1215
|
d.set_markup("<b><big>" + _("CHIRP Documentation") + "</big></b>\r\n")
|
1216
|
msg = _("Documentation for CHIRP, including FAQs, and help for common "
|
1217
|
"problems is available on the CHIRP web site, please go to\n\n"
|
1218
|
"<a href=\"http://chirp.danplanet.com/projects/chirp/wiki/"
|
1219
|
"Documentation\">"
|
1220
|
"http://chirp.danplanet.com/projects/chirp/wiki/"
|
1221
|
"Documentation</a>\n")
|
1222
|
d.format_secondary_markup(msg.replace("\n","\r\n"))
|
1223
|
d.run()
|
1224
|
d.destroy()
|
1225
|
|
1226
|
def do_columns(self):
|
1227
|
eset = self.get_current_editorset()
|
1228
|
driver = directory.get_driver(eset.rthread.radio.__class__)
|
1229
|
radio_name = "%s %s %s" % (eset.rthread.radio.VENDOR,
|
1230
|
eset.rthread.radio.MODEL,
|
1231
|
eset.rthread.radio.VARIANT)
|
1232
|
d = gtk.Dialog(title=_("Select Columns"),
|
1233
|
parent=self,
|
1234
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
1235
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
1236
|
|
1237
|
vbox = gtk.VBox()
|
1238
|
vbox.show()
|
1239
|
sw = gtk.ScrolledWindow()
|
1240
|
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
1241
|
sw.add_with_viewport(vbox)
|
1242
|
sw.show()
|
1243
|
d.vbox.pack_start(sw, 1, 1, 1)
|
1244
|
d.set_size_request(-1, 300)
|
1245
|
d.set_resizable(False)
|
1246
|
|
1247
|
label = gtk.Label(_("Visible columns for {radio}").format(radio=radio_name))
|
1248
|
label.show()
|
1249
|
vbox.pack_start(label)
|
1250
|
|
1251
|
fields = []
|
1252
|
memedit = eset.get_current_editor() #.editors["memedit"]
|
1253
|
unsupported = memedit.get_unsupported_columns()
|
1254
|
for colspec in memedit.cols:
|
1255
|
if colspec[0].startswith("_"):
|
1256
|
continue
|
1257
|
elif colspec[0] in unsupported:
|
1258
|
continue
|
1259
|
label = colspec[0]
|
1260
|
visible = memedit.get_column_visible(memedit.col(label))
|
1261
|
widget = gtk.CheckButton(label)
|
1262
|
widget.set_active(visible)
|
1263
|
fields.append(widget)
|
1264
|
vbox.pack_start(widget, 1, 1, 1)
|
1265
|
widget.show()
|
1266
|
|
1267
|
res = d.run()
|
1268
|
selected_columns = []
|
1269
|
if res == gtk.RESPONSE_OK:
|
1270
|
for widget in fields:
|
1271
|
colnum = memedit.col(widget.get_label())
|
1272
|
memedit.set_column_visible(colnum, widget.get_active())
|
1273
|
if widget.get_active():
|
1274
|
selected_columns.append(widget.get_label())
|
1275
|
|
1276
|
d.destroy()
|
1277
|
|
1278
|
CONF.set(driver, ",".join(selected_columns), "memedit_columns")
|
1279
|
|
1280
|
def do_hide_unused(self, action):
|
1281
|
eset = self.get_current_editorset()
|
1282
|
if eset is None:
|
1283
|
conf = config.get("memedit")
|
1284
|
conf.set_bool("hide_unused", action.get_active())
|
1285
|
else:
|
1286
|
for editortype, editor in eset.editors.iteritems():
|
1287
|
if "memedit" in editortype:
|
1288
|
editor.set_hide_unused(action.get_active())
|
1289
|
|
1290
|
def do_clearq(self):
|
1291
|
eset = self.get_current_editorset()
|
1292
|
eset.rthread.flush()
|
1293
|
|
1294
|
def do_copy(self, cut):
|
1295
|
eset = self.get_current_editorset()
|
1296
|
eset.get_current_editor().copy_selection(cut)
|
1297
|
|
1298
|
def do_paste(self):
|
1299
|
eset = self.get_current_editorset()
|
1300
|
eset.get_current_editor().paste_selection()
|
1301
|
|
1302
|
def do_delete(self):
|
1303
|
eset = self.get_current_editorset()
|
1304
|
eset.get_current_editor().copy_selection(True)
|
1305
|
|
1306
|
def do_toggle_report(self, action):
|
1307
|
if not action.get_active():
|
1308
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO,
|
1309
|
parent=self)
|
1310
|
d.set_markup("<b><big>" + _("Reporting is disabled") + "</big></b>")
|
1311
|
msg = _("The reporting feature of CHIRP is designed to help "
|
1312
|
"<u>improve quality</u> by allowing the authors to focus "
|
1313
|
"on the radio drivers used most often and errors "
|
1314
|
"experienced by the users. The reports contain no "
|
1315
|
"identifying information and are used only for statistical "
|
1316
|
"purposes by the authors. Your privacy is extremely "
|
1317
|
"important, but <u>please consider leaving this feature "
|
1318
|
"enabled to help make CHIRP better!</u>\n\n<b>Are you "
|
1319
|
"sure you want to disable this feature?</b>")
|
1320
|
d.format_secondary_markup(msg.replace("\n", "\r\n"))
|
1321
|
r = d.run()
|
1322
|
d.destroy()
|
1323
|
if r == gtk.RESPONSE_NO:
|
1324
|
action.set_active(not action.get_active())
|
1325
|
|
1326
|
conf = config.get()
|
1327
|
conf.set_bool("no_report", not action.get_active())
|
1328
|
|
1329
|
def do_toggle_no_smart_tmode(self, action):
|
1330
|
CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit")
|
1331
|
|
1332
|
def do_toggle_developer(self, action):
|
1333
|
conf = config.get()
|
1334
|
conf.set_bool("developer", action.get_active(), "state")
|
1335
|
|
1336
|
for name in ["viewdeveloper", "loadmod"]:
|
1337
|
devaction = self.menu_ag.get_action(name)
|
1338
|
devaction.set_visible(action.get_active())
|
1339
|
|
1340
|
def do_change_language(self):
|
1341
|
langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
|
1342
|
"Hungarian", "Russian", "Portuguese (BR)", "French"]
|
1343
|
d = inputdialog.ChoiceDialog(langs, parent=self,
|
1344
|
title="Choose Language")
|
1345
|
d.label.set_text(_("Choose a language or Auto to use the "
|
1346
|
"operating system default. You will need to "
|
1347
|
"restart the application before the change "
|
1348
|
"will take effect"))
|
1349
|
d.label.set_line_wrap(True)
|
1350
|
r = d.run()
|
1351
|
if r == gtk.RESPONSE_OK:
|
1352
|
print "Chose language %s" % d.choice.get_active_text()
|
1353
|
conf = config.get()
|
1354
|
conf.set("language", d.choice.get_active_text(), "state")
|
1355
|
d.destroy()
|
1356
|
|
1357
|
def load_module(self):
|
1358
|
types = [(_("Python Modules") + "*.py", "*.py")]
|
1359
|
filen = platform.get_platform().gui_open_file(types=types)
|
1360
|
if not filen:
|
1361
|
return
|
1362
|
|
1363
|
# We're in development mode, so we need to tell the directory to
|
1364
|
# allow a loaded module to override an existing driver, against
|
1365
|
# its normal better judgement
|
1366
|
directory.enable_reregistrations()
|
1367
|
|
1368
|
try:
|
1369
|
module = file(filen)
|
1370
|
code = module.read()
|
1371
|
module.close()
|
1372
|
pyc = compile(code, filen, 'exec')
|
1373
|
# See this for why:
|
1374
|
# http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec
|
1375
|
exec(pyc, globals(), globals())
|
1376
|
except Exception, e:
|
1377
|
common.log_exception()
|
1378
|
common.show_error("Unable to load module: %s" % e)
|
1379
|
|
1380
|
def mh(self, _action, *args):
|
1381
|
action = _action.get_name()
|
1382
|
|
1383
|
if action == "quit":
|
1384
|
gtk.main_quit()
|
1385
|
elif action == "new":
|
1386
|
self.do_new()
|
1387
|
elif action == "open":
|
1388
|
self.do_open()
|
1389
|
elif action == "save":
|
1390
|
self.do_save()
|
1391
|
elif action == "saveas":
|
1392
|
self.do_saveas()
|
1393
|
elif action.startswith("download"):
|
1394
|
self.do_download(*args)
|
1395
|
elif action.startswith("upload"):
|
1396
|
self.do_upload(*args)
|
1397
|
elif action == "close":
|
1398
|
self.do_close()
|
1399
|
elif action == "import":
|
1400
|
self.do_import()
|
1401
|
elif action in ["qrfinder", "irfinder"]:
|
1402
|
self.do_rfinder(action[0] == "i")
|
1403
|
elif action in ["qradioreference", "iradioreference"]:
|
1404
|
self.do_radioreference(action[0] == "i")
|
1405
|
elif action == "export":
|
1406
|
self.do_export()
|
1407
|
elif action in ["qrbook", "irbook"]:
|
1408
|
self.do_repeaterbook(action[0] == "i")
|
1409
|
elif action in ["qpr", "ipr"]:
|
1410
|
self.do_przemienniki(action[0] == "i")
|
1411
|
elif action == "about":
|
1412
|
self.do_about()
|
1413
|
elif action == "documentation":
|
1414
|
self.do_documentation()
|
1415
|
elif action == "columns":
|
1416
|
self.do_columns()
|
1417
|
elif action == "hide_unused":
|
1418
|
self.do_hide_unused(_action)
|
1419
|
elif action == "cancelq":
|
1420
|
self.do_clearq()
|
1421
|
elif action == "report":
|
1422
|
self.do_toggle_report(_action)
|
1423
|
elif action == "channel_defaults":
|
1424
|
# The memedit thread also has an instance of bandplans.
|
1425
|
bp = bandplans.BandPlans(CONF)
|
1426
|
bp.select_bandplan(self)
|
1427
|
elif action == "no_smart_tmode":
|
1428
|
self.do_toggle_no_smart_tmode(_action)
|
1429
|
elif action == "developer":
|
1430
|
self.do_toggle_developer(_action)
|
1431
|
elif action in ["cut", "copy", "paste", "delete",
|
1432
|
"move_up", "move_dn", "exchange",
|
1433
|
"devshowraw", "devdiffraw"]:
|
1434
|
self.get_current_editorset().get_current_editor().hotkey(_action)
|
1435
|
elif action == "devdifftab":
|
1436
|
self.do_diff_radio()
|
1437
|
elif action == "language":
|
1438
|
self.do_change_language()
|
1439
|
elif action == "loadmod":
|
1440
|
self.load_module()
|
1441
|
else:
|
1442
|
return
|
1443
|
|
1444
|
self.ev_tab_switched()
|
1445
|
|
1446
|
def make_menubar(self):
|
1447
|
menu_xml = """
|
1448
|
<ui>
|
1449
|
<menubar name="MenuBar">
|
1450
|
<menu action="file">
|
1451
|
<menuitem action="new"/>
|
1452
|
<menuitem action="open"/>
|
1453
|
<menu action="openstock" name="openstock"/>
|
1454
|
<menu action="recent" name="recent"/>
|
1455
|
<menuitem action="save"/>
|
1456
|
<menuitem action="saveas"/>
|
1457
|
<menuitem action="loadmod"/>
|
1458
|
<separator/>
|
1459
|
<menuitem action="import"/>
|
1460
|
<menuitem action="export"/>
|
1461
|
<separator/>
|
1462
|
<menuitem action="close"/>
|
1463
|
<menuitem action="quit"/>
|
1464
|
</menu>
|
1465
|
<menu action="edit">
|
1466
|
<menuitem action="cut"/>
|
1467
|
<menuitem action="copy"/>
|
1468
|
<menuitem action="paste"/>
|
1469
|
<menuitem action="delete"/>
|
1470
|
<separator/>
|
1471
|
<menuitem action="move_up"/>
|
1472
|
<menuitem action="move_dn"/>
|
1473
|
<menuitem action="exchange"/>
|
1474
|
</menu>
|
1475
|
<menu action="view">
|
1476
|
<menuitem action="columns"/>
|
1477
|
<menuitem action="hide_unused"/>
|
1478
|
<menuitem action="no_smart_tmode"/>
|
1479
|
<menu action="viewdeveloper">
|
1480
|
<menuitem action="devshowraw"/>
|
1481
|
<menuitem action="devdiffraw"/>
|
1482
|
<menuitem action="devdifftab"/>
|
1483
|
</menu>
|
1484
|
<menuitem action="language"/>
|
1485
|
</menu>
|
1486
|
<menu action="radio" name="radio">
|
1487
|
<menuitem action="download"/>
|
1488
|
<menuitem action="upload"/>
|
1489
|
<menu action="importsrc" name="importsrc">
|
1490
|
<menuitem action="iradioreference"/>
|
1491
|
<menuitem action="irbook"/>
|
1492
|
<menuitem action="ipr"/>
|
1493
|
<menuitem action="irfinder"/>
|
1494
|
</menu>
|
1495
|
<menu action="querysrc" name="querysrc">
|
1496
|
<menuitem action="qradioreference"/>
|
1497
|
<menuitem action="qrbook"/>
|
1498
|
<menuitem action="qpr"/>
|
1499
|
<menuitem action="qrfinder"/>
|
1500
|
</menu>
|
1501
|
<menu action="stock" name="stock"/>
|
1502
|
<separator/>
|
1503
|
<menuitem action="channel_defaults"/>
|
1504
|
<separator/>
|
1505
|
<menuitem action="cancelq"/>
|
1506
|
</menu>
|
1507
|
<menu action="help">
|
1508
|
<menuitem action="about"/>
|
1509
|
<menuitem action="documentation"/>
|
1510
|
<menuitem action="report"/>
|
1511
|
<menuitem action="developer"/>
|
1512
|
</menu>
|
1513
|
</menubar>
|
1514
|
</ui>
|
1515
|
"""
|
1516
|
actions = [\
|
1517
|
('file', None, _("_File"), None, None, self.mh),
|
1518
|
('new', gtk.STOCK_NEW, None, None, None, self.mh),
|
1519
|
('open', gtk.STOCK_OPEN, None, None, None, self.mh),
|
1520
|
('openstock', None, _("Open stock config"), None, None, self.mh),
|
1521
|
('recent', None, _("_Recent"), None, None, self.mh),
|
1522
|
('save', gtk.STOCK_SAVE, None, None, None, self.mh),
|
1523
|
('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh),
|
1524
|
('loadmod', None, _("Load Module"), None, None, self.mh),
|
1525
|
('close', gtk.STOCK_CLOSE, None, None, None, self.mh),
|
1526
|
('quit', gtk.STOCK_QUIT, None, None, None, self.mh),
|
1527
|
('edit', None, _("_Edit"), None, None, self.mh),
|
1528
|
('cut', None, _("_Cut"), "<Ctrl>x", None, self.mh),
|
1529
|
('copy', None, _("_Copy"), "<Ctrl>c", None, self.mh),
|
1530
|
('paste', None, _("_Paste"), "<Ctrl>v", None, self.mh),
|
1531
|
('delete', None, _("_Delete"), "Delete", None, self.mh),
|
1532
|
('move_up', None, _("Move _Up"), "<Control>Up", None, self.mh),
|
1533
|
('move_dn', None, _("Move Dow_n"), "<Control>Down", None, self.mh),
|
1534
|
('exchange', None, _("E_xchange"), "<Control><Shift>x", None, self.mh),
|
1535
|
('view', None, _("_View"), None, None, self.mh),
|
1536
|
('columns', None, _("Columns"), None, None, self.mh),
|
1537
|
('viewdeveloper', None, _("Developer"), None, None, self.mh),
|
1538
|
('devshowraw', None, _('Show raw memory'), "<Control><Shift>r", None, self.mh),
|
1539
|
('devdiffraw', None, _("Diff raw memories"), "<Control><Shift>d", None, self.mh),
|
1540
|
('devdifftab', None, _("Diff tabs"), "<Control><Shift>t", None, self.mh),
|
1541
|
('language', None, _("Change language"), None, None, self.mh),
|
1542
|
('radio', None, _("_Radio"), None, None, self.mh),
|
1543
|
('download', None, _("Download From Radio"), "<Alt>d", None, self.mh),
|
1544
|
('upload', None, _("Upload To Radio"), "<Alt>u", None, self.mh),
|
1545
|
('import', None, _("Import"), "<Alt>i", None, self.mh),
|
1546
|
('export', None, _("Export"), "<Alt>x", None, self.mh),
|
1547
|
('importsrc', None, _("Import from data source"), None, None, self.mh),
|
1548
|
('iradioreference', None, _("RadioReference.com"), None, None, self.mh),
|
1549
|
('irfinder', None, _("RFinder"), None, None, self.mh),
|
1550
|
('irbook', None, _("RepeaterBook"), None, None, self.mh),
|
1551
|
('ipr', None, _("przemienniki.net"), None, None, self.mh),
|
1552
|
('querysrc', None, _("Query data source"), None, None, self.mh),
|
1553
|
('qradioreference', None, _("RadioReference.com"), None, None, self.mh),
|
1554
|
('qrfinder', None, _("RFinder"), None, None, self.mh),
|
1555
|
('qpr', None, _("przemienniki.net"), None, None, self.mh),
|
1556
|
('qrbook', None, _("RepeaterBook"), None, None, self.mh),
|
1557
|
('export_chirp', None, _("CHIRP Native File"), None, None, self.mh),
|
1558
|
('export_csv', None, _("CSV File"), None, None, self.mh),
|
1559
|
('stock', None, _("Import from stock config"), None, None, self.mh),
|
1560
|
('channel_defaults', None, _("Channel defaults"), None, None, self.mh),
|
1561
|
('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
|
1562
|
('help', None, _('Help'), None, None, self.mh),
|
1563
|
('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
|
1564
|
('documentation', None, _("Documentation"), None, None, self.mh),
|
1565
|
]
|
1566
|
|
1567
|
conf = config.get()
|
1568
|
re = not conf.get_bool("no_report");
|
1569
|
hu = conf.get_bool("hide_unused", "memedit", default=True)
|
1570
|
dv = conf.get_bool("developer", "state")
|
1571
|
st = not conf.get_bool("no_smart_tmode", "memedit")
|
1572
|
|
1573
|
toggles = [\
|
1574
|
('report', None, _("Report statistics"), None, None, self.mh, re),
|
1575
|
('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu),
|
1576
|
('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st),
|
1577
|
('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv),
|
1578
|
]
|
1579
|
|
1580
|
self.menu_uim = gtk.UIManager()
|
1581
|
self.menu_ag = gtk.ActionGroup("MenuBar")
|
1582
|
self.menu_ag.add_actions(actions)
|
1583
|
self.menu_ag.add_toggle_actions(toggles)
|
1584
|
|
1585
|
self.menu_uim.insert_action_group(self.menu_ag, 0)
|
1586
|
self.menu_uim.add_ui_from_string(menu_xml)
|
1587
|
|
1588
|
self.add_accel_group(self.menu_uim.get_accel_group())
|
1589
|
|
1590
|
self.recentmenu = self.menu_uim.get_widget("/MenuBar/file/recent")
|
1591
|
|
1592
|
# Initialize
|
1593
|
self.do_toggle_developer(self.menu_ag.get_action("developer"))
|
1594
|
|
1595
|
return self.menu_uim.get_widget("/MenuBar")
|
1596
|
|
1597
|
def make_tabs(self):
|
1598
|
self.tabs = gtk.Notebook()
|
1599
|
|
1600
|
return self.tabs
|
1601
|
|
1602
|
def close_out(self):
|
1603
|
num = self.tabs.get_n_pages()
|
1604
|
while num > 0:
|
1605
|
num -= 1
|
1606
|
print "Closing %i" % num
|
1607
|
try:
|
1608
|
self.do_close(self.tabs.get_nth_page(num))
|
1609
|
except ModifiedError:
|
1610
|
return False
|
1611
|
|
1612
|
gtk.main_quit()
|
1613
|
|
1614
|
return True
|
1615
|
|
1616
|
def make_status_bar(self):
|
1617
|
box = gtk.HBox(False, 2)
|
1618
|
|
1619
|
self.sb_general = gtk.Statusbar()
|
1620
|
self.sb_general.set_has_resize_grip(False)
|
1621
|
self.sb_general.show()
|
1622
|
box.pack_start(self.sb_general, 1,1,1)
|
1623
|
|
1624
|
self.sb_radio = gtk.Statusbar()
|
1625
|
self.sb_radio.set_has_resize_grip(True)
|
1626
|
self.sb_radio.show()
|
1627
|
box.pack_start(self.sb_radio, 1,1,1)
|
1628
|
|
1629
|
box.show()
|
1630
|
return box
|
1631
|
|
1632
|
def ev_delete(self, window, event):
|
1633
|
if not self.close_out():
|
1634
|
return True # Don't exit
|
1635
|
|
1636
|
def ev_destroy(self, window):
|
1637
|
if not self.close_out():
|
1638
|
return True # Don't exit
|
1639
|
|
1640
|
def setup_extra_hotkeys(self):
|
1641
|
accelg = self.menu_uim.get_accel_group()
|
1642
|
|
1643
|
memedit = lambda a: self.get_current_editorset().editors["memedit"].hotkey(a)
|
1644
|
|
1645
|
actions = [
|
1646
|
# ("action_name", "key", function)
|
1647
|
]
|
1648
|
|
1649
|
for name, key, fn in actions:
|
1650
|
a = gtk.Action(name, name, name, "")
|
1651
|
a.connect("activate", fn)
|
1652
|
self.menu_ag.add_action_with_accel(a, key)
|
1653
|
a.set_accel_group(accelg)
|
1654
|
a.connect_accelerator()
|
1655
|
|
1656
|
def _set_icon(self):
|
1657
|
execpath = platform.get_platform().executable_path()
|
1658
|
path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
|
1659
|
if not os.path.exists(path):
|
1660
|
path = "/usr/share/pixmaps/chirp.png"
|
1661
|
|
1662
|
if os.path.exists(path):
|
1663
|
self.set_icon_from_file(path)
|
1664
|
else:
|
1665
|
print "Icon %s not found" % path
|
1666
|
|
1667
|
def _updates(self, version):
|
1668
|
if not version:
|
1669
|
return
|
1670
|
|
1671
|
if version == CHIRP_VERSION:
|
1672
|
return
|
1673
|
|
1674
|
print "Server reports version %s is available" % version
|
1675
|
|
1676
|
# Report new updates every seven days
|
1677
|
intv = 3600 * 24 * 7
|
1678
|
|
1679
|
if CONF.is_defined("last_update_check", "state") and \
|
1680
|
(time.time() - CONF.get_int("last_update_check", "state")) < intv:
|
1681
|
return
|
1682
|
|
1683
|
CONF.set_int("last_update_check", int(time.time()), "state")
|
1684
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
|
1685
|
type=gtk.MESSAGE_INFO)
|
1686
|
d.set_property("text",
|
1687
|
_("A new version of CHIRP is available: " +
|
1688
|
"{ver}. ".format(ver=version) +
|
1689
|
"It is recommended that you upgrade, so " +
|
1690
|
"go to http://chirp.danplanet.com soon!"))
|
1691
|
d.run()
|
1692
|
d.destroy()
|
1693
|
|
1694
|
def _init_macos(self, menu_bar):
|
1695
|
try:
|
1696
|
import gtk_osxapplication
|
1697
|
macapp = gtk_osxapplication.OSXApplication()
|
1698
|
except ImportError, e:
|
1699
|
print "No MacOS support: %s" % e
|
1700
|
return
|
1701
|
|
1702
|
menu_bar.hide()
|
1703
|
macapp.set_menu_bar(menu_bar)
|
1704
|
|
1705
|
quititem = self.menu_uim.get_widget("/MenuBar/file/quit")
|
1706
|
quititem.hide()
|
1707
|
|
1708
|
aboutitem = self.menu_uim.get_widget("/MenuBar/help/about")
|
1709
|
macapp.insert_app_menu_item(aboutitem, 0)
|
1710
|
|
1711
|
documentationitem = self.menu_uim.get_widget("/MenuBar/help/documentation")
|
1712
|
macapp.insert_app_menu_item(documentationitem, 0)
|
1713
|
|
1714
|
macapp.set_use_quartz_accelerators(False)
|
1715
|
macapp.ready()
|
1716
|
|
1717
|
print "Initialized MacOS support"
|
1718
|
|
1719
|
def __init__(self, *args, **kwargs):
|
1720
|
gtk.Window.__init__(self, *args, **kwargs)
|
1721
|
|
1722
|
def expose(window, event):
|
1723
|
allocation = window.get_allocation()
|
1724
|
CONF.set_int("window_w", allocation.width, "state")
|
1725
|
CONF.set_int("window_h", allocation.height, "state")
|
1726
|
self.connect("expose_event", expose)
|
1727
|
|
1728
|
def state_change(window, event):
|
1729
|
CONF.set_bool(
|
1730
|
"window_maximized",
|
1731
|
event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED,
|
1732
|
"state")
|
1733
|
self.connect("window-state-event", state_change)
|
1734
|
|
1735
|
d = CONF.get("last_dir", "state")
|
1736
|
if d and os.path.isdir(d):
|
1737
|
platform.get_platform().set_last_dir(d)
|
1738
|
|
1739
|
vbox = gtk.VBox(False, 2)
|
1740
|
|
1741
|
self._recent = []
|
1742
|
|
1743
|
self.menu_ag = None
|
1744
|
mbar = self.make_menubar()
|
1745
|
|
1746
|
if os.name != "nt":
|
1747
|
self._set_icon() # Windows gets the icon from the exe
|
1748
|
if os.uname()[0] == "Darwin":
|
1749
|
self._init_macos(mbar)
|
1750
|
|
1751
|
vbox.pack_start(mbar, 0, 0, 0)
|
1752
|
|
1753
|
self.tabs = None
|
1754
|
tabs = self.make_tabs()
|
1755
|
tabs.connect("switch-page", lambda n, _, p: self.ev_tab_switched(p))
|
1756
|
tabs.connect("page-removed", lambda *a: self.ev_tab_switched())
|
1757
|
tabs.show()
|
1758
|
self.ev_tab_switched()
|
1759
|
vbox.pack_start(tabs, 1, 1, 1)
|
1760
|
|
1761
|
vbox.pack_start(self.make_status_bar(), 0, 0, 0)
|
1762
|
|
1763
|
vbox.show()
|
1764
|
|
1765
|
self.add(vbox)
|
1766
|
|
1767
|
try:
|
1768
|
width = CONF.get_int("window_w", "state")
|
1769
|
height = CONF.get_int("window_h", "state")
|
1770
|
except Exception:
|
1771
|
width = 800
|
1772
|
height = 600
|
1773
|
|
1774
|
self.set_default_size(width, height)
|
1775
|
if CONF.get_bool("window_maximized", "state"):
|
1776
|
self.maximize()
|
1777
|
self.set_title("CHIRP")
|
1778
|
|
1779
|
self.connect("delete_event", self.ev_delete)
|
1780
|
self.connect("destroy", self.ev_destroy)
|
1781
|
|
1782
|
if not CONF.get_bool("warned_about_reporting") and \
|
1783
|
not CONF.get_bool("no_report"):
|
1784
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self)
|
1785
|
d.set_markup("<b><big>" +
|
1786
|
_("Error reporting is enabled") +
|
1787
|
"</big></b>")
|
1788
|
d.format_secondary_markup(\
|
1789
|
_("If you wish to disable this feature you may do so in "
|
1790
|
"the <u>Help</u> menu"))
|
1791
|
d.run()
|
1792
|
d.destroy()
|
1793
|
CONF.set_bool("warned_about_reporting", True)
|
1794
|
|
1795
|
self.update_recent_files()
|
1796
|
self.update_stock_configs()
|
1797
|
self.setup_extra_hotkeys()
|
1798
|
|
1799
|
def updates_callback(ver):
|
1800
|
gobject.idle_add(self._updates, ver)
|
1801
|
|
1802
|
if not CONF.get_bool("skip_update_check", "state"):
|
1803
|
reporting.check_for_updates(updates_callback)
|