Project

General

Profile

Bug #3679 ยป mainapp.py

Robert Morris, 05/24/2016 09:52 PM

 
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
import webbrowser
21
from glob import glob
22
import shutil
23
import time
24
import logging
25
import gtk
26
import gobject
27
import sys
28

    
29
from chirp.ui import inputdialog, common
30
from chirp import platform, directory, util
31
from chirp.drivers import generic_xml, generic_csv
32
from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6
33
from chirp.drivers import icf, ic9x_icf
34
from chirp import CHIRP_VERSION, chirp_common, detect, errors
35
from chirp.ui import editorset, clone, miscwidgets, config, reporting, fips
36
from chirp.ui import bandplans
37

    
38
gobject.threads_init()
39

    
40
LOG = logging.getLogger(__name__)
41

    
42
if __name__ == "__main__":
43
    sys.path.insert(0, "..")
44

    
45
try:
46
    import serial
47
except ImportError, e:
48
    common.log_exception()
49
    common.show_error("\nThe Pyserial module is not installed!")
50

    
51

    
52
CONF = config.get()
53

    
54
KEEP_RECENT = 8
55

    
56
RB_BANDS = {
57
    "--All--":                  0,
58
    "10 meters (29MHz)":        29,
59
    "6 meters (54MHz)":         5,
60
    "2 meters (144MHz)":        14,
61
    "1.25 meters (220MHz)":     22,
62
    "70 centimeters (440MHz)":  4,
63
    "33 centimeters (900MHz)":  9,
64
    "23 centimeters (1.2GHz)":  12,
65
}
66

    
67

    
68
def key_bands(band):
69
    if band.startswith("-"):
70
        return -1
71

    
72
    amount, units, mhz = band.split(" ")
73
    scale = units == "meters" and 100 or 1
74

    
75
    return 100000 - (float(amount) * scale)
76

    
77

    
78
class ModifiedError(Exception):
79
    pass
80

    
81

    
82
class ChirpMain(gtk.Window):
83
    def get_current_editorset(self):
84
        page = self.tabs.get_current_page()
85
        if page is not None:
86
            return self.tabs.get_nth_page(page)
87
        else:
88
            return None
89

    
90
    def ev_tab_switched(self, pagenum=None):
91
        def set_action_sensitive(action, sensitive):
92
            self.menu_ag.get_action(action).set_sensitive(sensitive)
93

    
94
        if pagenum is not None:
95
            eset = self.tabs.get_nth_page(pagenum)
96
        else:
97
            eset = self.get_current_editorset()
98

    
99
        upload_sens = bool(eset and
100
                           isinstance(eset.radio, chirp_common.CloneModeRadio))
101

    
102
        if not eset or isinstance(eset.radio, chirp_common.LiveRadio):
103
            save_sens = False
104
        elif isinstance(eset.radio, chirp_common.NetworkSourceRadio):
105
            save_sens = False
106
        else:
107
            save_sens = True
108

    
109
        for i in ["import", "importsrc", "stock"]:
110
            set_action_sensitive(i,
111
                                 eset is not None and not eset.get_read_only())
112

    
113
        for i in ["save", "saveas"]:
114
            set_action_sensitive(i, save_sens)
115

    
116
        for i in ["upload"]:
117
            set_action_sensitive(i, upload_sens)
118

    
119
        for i in ["cancelq"]:
120
            set_action_sensitive(i, eset is not None and not save_sens)
121

    
122
        for i in ["export", "close", "columns", "irbook", "irfinder",
123
                  "move_up", "move_dn", "exchange", "iradioreference",
124
                  "cut", "copy", "paste", "delete", "viewdeveloper",
125
                  "all", "properties"]:
126
            set_action_sensitive(i, eset is not None)
127

    
128
    def ev_status(self, editorset, msg):
129
        self.sb_radio.pop(0)
130
        self.sb_radio.push(0, msg)
131

    
132
    def ev_usermsg(self, editorset, msg):
133
        self.sb_general.pop(0)
134
        self.sb_general.push(0, msg)
135

    
136
    def ev_editor_selected(self, editorset, editortype):
137
        mappings = {
138
            "memedit": ["view", "edit"],
139
            }
140

    
141
        for _editortype, actions in mappings.items():
142
            for _action in actions:
143
                action = self.menu_ag.get_action(_action)
144
                action.set_sensitive(editortype.startswith(_editortype))
145

    
146
    def _connect_editorset(self, eset):
147
        eset.connect("want-close", self.do_close)
148
        eset.connect("status", self.ev_status)
149
        eset.connect("usermsg", self.ev_usermsg)
150
        eset.connect("editor-selected", self.ev_editor_selected)
151

    
152
    def do_diff_radio(self):
153
        if self.tabs.get_n_pages() < 2:
154
            common.show_error("Diff tabs requires at least two open tabs!")
155
            return
156

    
157
        esets = []
158
        for i in range(0, self.tabs.get_n_pages()):
159
            esets.append(self.tabs.get_nth_page(i))
160

    
161
        d = gtk.Dialog(title="Diff Radios",
162
                       buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
163
                                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
164
                       parent=self)
165

    
166
        label = gtk.Label("")
167
        label.set_markup("<b>-1</b> for either Mem # does a full-file hex " +
168
                         "dump with diffs highlighted.\n" +
169
                         "<b>-2</b> for first Mem # shows " +
170
                         "<b>only</b> the diffs.")
171
        d.vbox.pack_start(label, True, True, 0)
172
        label.show()
173

    
174
        choices = []
175
        for eset in esets:
176
            choices.append("%s %s (%s)" % (eset.rthread.radio.VENDOR,
177
                                           eset.rthread.radio.MODEL,
178
                                           eset.filename))
179
        choice_a = miscwidgets.make_choice(choices, False, choices[0])
180
        choice_a.show()
181
        chan_a = gtk.SpinButton()
182
        chan_a.get_adjustment().set_all(1, -2, 999, 1, 10, 0)
183
        chan_a.show()
184
        hbox = gtk.HBox(False, 3)
185
        hbox.pack_start(choice_a, 1, 1, 1)
186
        hbox.pack_start(chan_a, 0, 0, 0)
187
        hbox.show()
188
        d.vbox.pack_start(hbox, 0, 0, 0)
189

    
190
        choice_b = miscwidgets.make_choice(choices, False, choices[1])
191
        choice_b.show()
192
        chan_b = gtk.SpinButton()
193
        chan_b.get_adjustment().set_all(1, -1, 999, 1, 10, 0)
194
        chan_b.show()
195
        hbox = gtk.HBox(False, 3)
196
        hbox.pack_start(choice_b, 1, 1, 1)
197
        hbox.pack_start(chan_b, 0, 0, 0)
198
        hbox.show()
199
        d.vbox.pack_start(hbox, 0, 0, 0)
200

    
201
        r = d.run()
202
        sel_a = choice_a.get_active_text()
203
        sel_chan_a = chan_a.get_value()
204
        sel_b = choice_b.get_active_text()
205
        sel_chan_b = chan_b.get_value()
206
        d.destroy()
207
        if r == gtk.RESPONSE_CANCEL:
208
            return
209

    
210
        if sel_a == sel_b:
211
            common.show_error("Can't diff the same tab!")
212
            return
213

    
214
        LOG.debug("Selected %s@%i and %s@%i" %
215
                  (sel_a, sel_chan_a, sel_b, sel_chan_b))
216
        name_a = os.path.basename(sel_a)
217
        name_a = name_a[:name_a.rindex(")")]
218
        name_b = os.path.basename(sel_b)
219
        name_b = name_b[:name_b.rindex(")")]
220
        diffwintitle = "%s@%i  diff  %s@%i" % (
221
            name_a, sel_chan_a, name_b, sel_chan_b)
222

    
223
        eset_a = esets[choices.index(sel_a)]
224
        eset_b = esets[choices.index(sel_b)]
225

    
226
        def _show_diff(mem_b, mem_a):
227
            # Step 3: Show the diff
228
            diff = common.simple_diff(mem_a, mem_b)
229
            common.show_diff_blob(diffwintitle, diff)
230

    
231
        def _get_mem_b(mem_a):
232
            # Step 2: Get memory b
233
            job = common.RadioJob(_show_diff, "get_raw_memory",
234
                                  int(sel_chan_b))
235
            job.set_cb_args(mem_a)
236
            eset_b.rthread.submit(job)
237

    
238
        if sel_chan_a >= 0 and sel_chan_b >= 0:
239
            # Diff numbered memory
240
            # Step 1: Get memory a
241
            job = common.RadioJob(_get_mem_b, "get_raw_memory",
242
                                  int(sel_chan_a))
243
            eset_a.rthread.submit(job)
244
        elif isinstance(eset_a.rthread.radio, chirp_common.CloneModeRadio) and\
245
                isinstance(eset_b.rthread.radio, chirp_common.CloneModeRadio):
246
            # Diff whole (can do this without a job, since both are clone-mode)
247
            try:
248
                addrfmt = CONF.get('hexdump_addrfmt', section='developer',
249
                                   raw=True)
250
            except:
251
                pass
252
            a = util.hexprint(eset_a.rthread.radio._mmap.get_packed(),
253
                              addrfmt=addrfmt)
254
            b = util.hexprint(eset_b.rthread.radio._mmap.get_packed(),
255
                              addrfmt=addrfmt)
256
            if sel_chan_a == -2:
257
                diffsonly = True
258
            else:
259
                diffsonly = False
260
            common.show_diff_blob(diffwintitle,
261
                                  common.simple_diff(a, b, diffsonly))
262
        else:
263
            common.show_error("Cannot diff whole live-mode radios!")
264

    
265
    def do_new(self):
266
        eset = editorset.EditorSet(_("Untitled") + ".csv", self)
267
        self._connect_editorset(eset)
268
        eset.prime()
269
        eset.show()
270

    
271
        tab = self.tabs.append_page(eset, eset.get_tab_label())
272
        self.tabs.set_current_page(tab)
273

    
274
    def _do_manual_select(self, filename):
275
        radiolist = {}
276
        for drv, radio in directory.DRV_TO_RADIO.items():
277
            if not issubclass(radio, chirp_common.CloneModeRadio):
278
                continue
279
            radiolist["%s %s" % (radio.VENDOR, radio.MODEL)] = drv
280

    
281
        lab = gtk.Label("""<b><big>Unable to detect model!</big></b>
282

    
283
If you think that it is valid, you can select a radio model below to
284
force an open attempt. If selecting the model manually works, please
285
file a bug on the website and attach your image. If selecting the model
286
does not work, it is likely that you are trying to open some other type
287
of file.
288
""")
289

    
290
        lab.set_justify(gtk.JUSTIFY_FILL)
291
        lab.set_line_wrap(True)
292
        lab.set_use_markup(True)
293
        lab.show()
294
        choice = miscwidgets.make_choice(sorted(radiolist.keys()), False,
295
                                         sorted(radiolist.keys())[0])
296
        d = gtk.Dialog(title="Detection Failed",
297
                       buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
298
                                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
299
        d.vbox.pack_start(lab, 0, 0, 0)
300
        d.vbox.pack_start(choice, 0, 0, 0)
301
        d.vbox.set_spacing(5)
302
        choice.show()
303
        d.set_default_size(400, 200)
304
        # d.set_resizable(False)
305
        r = d.run()
306
        d.destroy()
307
        if r != gtk.RESPONSE_OK:
308
            return
309
        try:
310
            rc = directory.DRV_TO_RADIO[radiolist[choice.get_active_text()]]
311
            return rc(filename)
312
        except:
313
            return
314

    
315
    def do_open(self, fname=None, tempname=None):
316
        if not fname:
317
            types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"),
318
                     (_("CHIRP Files") + " (*.chirp)", "*.chirp"),
319
                     (_("CSV Files") + " (*.csv)", "*.csv"),
320
                     (_("DAT Files") + " (*.dat)", "*.dat"),
321
                     (_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
322
                     (_("ICF Files") + " (*.icf)", "*.icf"),
323
                     (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
324
                     (_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
325
                     (_("VX7 Commander Files") + " (*.vx7)", "*.vx7"),
326
                     ]
327
            fname = platform.get_platform().gui_open_file(types=types)
328
            if not fname:
329
                return
330

    
331
        self.record_recent_file(fname)
332

    
333
        if icf.is_icf_file(fname):
334
            a = common.ask_yesno_question(
335
                _("ICF files cannot be edited, only displayed or imported "
336
                  "into another file. Open in read-only mode?"),
337
                self)
338
            if not a:
339
                return
340
            read_only = True
341
        else:
342
            read_only = False
343

    
344
        if icf.is_9x_icf(fname):
345
            # We have to actually instantiate the IC9xICFRadio to get its
346
            # sub-devices
347
            radio = ic9x_icf.IC9xICFRadio(fname)
348
        else:
349
            try:
350
                radio = directory.get_radio_by_image(fname)
351
            except errors.ImageDetectFailed:
352
                radio = self._do_manual_select(fname)
353
                if not radio:
354
                    return
355
                LOG.debug("Manually selected %s" % radio)
356
            except Exception, e:
357
                common.log_exception()
358
                common.show_error(os.path.basename(fname) + ": " + str(e))
359
                return
360

    
361
        first_tab = False
362
        try:
363
            eset = editorset.EditorSet(radio, self,
364
                                       filename=fname,
365
                                       tempname=tempname)
366
        except Exception, e:
367
            common.log_exception()
368
            common.show_error(
369
                _("There was an error opening {fname}: {error}").format(
370
                    fname=fname,
371
                    error=e))
372
            return
373

    
374
        eset.set_read_only(read_only)
375
        self._connect_editorset(eset)
376
        eset.show()
377
        self.tabs.append_page(eset, eset.get_tab_label())
378

    
379
        if hasattr(eset.rthread.radio, "errors") and \
380
                eset.rthread.radio.errors:
381
            msg = _("{num} errors during open:").format(
382
                num=len(eset.rthread.radio.errors))
383
            common.show_error_text(msg,
384
                                   "\r\n".join(eset.rthread.radio.errors))
385

    
386
    def do_live_warning(self, radio):
387
        d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
388
        d.set_markup("<big><b>" + _("Note:") + "</b></big>")
389
        msg = _("The {vendor} {model} operates in <b>live mode</b>. "
390
                "This means that any changes you make are immediately sent "
391
                "to the radio. Because of this, you cannot perform the "
392
                "<u>Save</u> or <u>Upload</u> operations. If you wish to "
393
                "edit the contents offline, please <u>Export</u> to a CSV "
394
                "file, using the <b>File menu</b>.")
395
        msg = msg.format(vendor=radio.VENDOR, model=radio.MODEL)
396
        d.format_secondary_markup(msg)
397

    
398
        again = gtk.CheckButton(_("Don't show this again"))
399
        again.show()
400
        d.vbox.pack_start(again, 0, 0, 0)
401
        d.run()
402
        CONF.set_bool("live_mode", again.get_active(), "noconfirm")
403
        d.destroy()
404

    
405
    def do_open_live(self, radio, tempname=None, read_only=False):
406
        eset = editorset.EditorSet(radio, self, tempname=tempname)
407
        eset.connect("want-close", self.do_close)
408
        eset.connect("status", self.ev_status)
409
        eset.set_read_only(read_only)
410
        eset.show()
411
        self.tabs.append_page(eset, eset.get_tab_label())
412

    
413
        if isinstance(radio, chirp_common.LiveRadio):
414
            reporting.report_model_usage(radio, "live", True)
415
            if not CONF.get_bool("live_mode", "noconfirm"):
416
                self.do_live_warning(radio)
417

    
418
    def do_save(self, eset=None):
419
        if not eset:
420
            eset = self.get_current_editorset()
421

    
422
        # For usability, allow Ctrl-S to short-circuit to Save-As if
423
        # we are working on a yet-to-be-saved image
424
        if not os.path.exists(eset.filename):
425
            return self.do_saveas()
426

    
427
        eset.save()
428

    
429
    def do_saveas(self):
430
        eset = self.get_current_editorset()
431

    
432
        label = _("{vendor} {model} image file").format(
433
            vendor=eset.radio.VENDOR,
434
            model=eset.radio.MODEL)
435

    
436
        types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
437
                 eset.radio.FILE_EXTENSION)]
438

    
439
        if isinstance(eset.radio, vx7.VX7Radio):
440
            types += [(_("VX7 Commander") + " (*.vx7)", "vx7")]
441
        elif isinstance(eset.radio, vx6.VX6Radio):
442
            types += [(_("VX6 Commander") + " (*.vx6)", "vx6")]
443
        elif isinstance(eset.radio, vx5.VX5Radio):
444
            types += [(_("EVE") + " (*.eve)", "eve")]
445
            types += [(_("VX5 Commander") + " (*.vx5)", "vx5")]
446

    
447
        while True:
448
            fname = platform.get_platform().gui_save_file(types=types)
449
            if not fname:
450
                return
451

    
452
            if os.path.exists(fname):
453
                dlg = inputdialog.OverwriteDialog(fname)
454
                owrite = dlg.run()
455
                dlg.destroy()
456
                if owrite == gtk.RESPONSE_OK:
457
                    break
458
            else:
459
                break
460

    
461
        try:
462
            eset.save(fname)
463
        except Exception, e:
464
            d = inputdialog.ExceptionDialog(e)
465
            d.run()
466
            d.destroy()
467

    
468
    def cb_clonein(self, radio, emsg=None):
469
        radio.pipe.close()
470
        reporting.report_model_usage(radio, "download", bool(emsg))
471
        if not emsg:
472
            self.do_open_live(radio, tempname="(" + _("Untitled") + ")")
473
        else:
474
            d = inputdialog.ExceptionDialog(emsg)
475
            d.run()
476
            d.destroy()
477

    
478
    def cb_cloneout(self, radio, emsg=None):
479
        radio.pipe.close()
480
        reporting.report_model_usage(radio, "upload", True)
481
        if emsg:
482
            d = inputdialog.ExceptionDialog(emsg)
483
            d.run()
484
            d.destroy()
485

    
486
    def _get_recent_list(self):
487
        recent = []
488
        for i in range(0, KEEP_RECENT):
489
            fn = CONF.get("recent%i" % i, "state")
490
            if fn:
491
                recent.append(fn)
492
        return recent
493

    
494
    def _set_recent_list(self, recent):
495
        for fn in recent:
496
            CONF.set("recent%i" % recent.index(fn), fn, "state")
497

    
498
    def update_recent_files(self):
499
        i = 0
500
        for fname in self._get_recent_list():
501
            action_name = "recent%i" % i
502
            path = "/MenuBar/file/recent"
503

    
504
            old_action = self.menu_ag.get_action(action_name)
505
            if old_action:
506
                self.menu_ag.remove_action(old_action)
507

    
508
            file_basename = os.path.basename(fname).replace("_", "__")
509
            action = gtk.Action(
510
                action_name, "_%i. %s" % (i+1, file_basename),
511
                _("Open recent file {name}").format(name=fname), "")
512
            action.connect("activate", lambda a, f: self.do_open(f), fname)
513
            mid = self.menu_uim.new_merge_id()
514
            self.menu_uim.add_ui(mid, path,
515
                                 action_name, action_name,
516
                                 gtk.UI_MANAGER_MENUITEM, False)
517
            self.menu_ag.add_action(action)
518
            i += 1
519

    
520
    def record_recent_file(self, filename):
521

    
522
        recent_files = self._get_recent_list()
523
        if filename not in recent_files:
524
            if len(recent_files) == KEEP_RECENT:
525
                del recent_files[-1]
526
            recent_files.insert(0, filename)
527
            self._set_recent_list(recent_files)
528

    
529
        self.update_recent_files()
530

    
531
    def import_stock_config(self, action, config):
532
        eset = self.get_current_editorset()
533
        count = eset.do_import(config)
534

    
535
    def copy_shipped_stock_configs(self, stock_dir):
536
        execpath = platform.get_platform().executable_path()
537
        basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
538
        if not os.path.exists(basepath):
539
            basepath = "/usr/share/chirp/stock_configs"
540

    
541
        files = glob(os.path.join(basepath, "*.csv"))
542
        for fn in files:
543
            if os.path.exists(os.path.join(stock_dir, os.path.basename(fn))):
544
                LOG.info("Skipping existing stock config")
545
                continue
546
            try:
547
                shutil.copy(fn, stock_dir)
548
                LOG.debug("Copying %s -> %s" % (fn, stock_dir))
549
            except Exception, e:
550
                LOG.error("Unable to copy %s to %s: %s" % (fn, stock_dir, e))
551
                return False
552
        return True
553

    
554
    def update_stock_configs(self):
555
        stock_dir = platform.get_platform().config_file("stock_configs")
556
        if not os.path.isdir(stock_dir):
557
            try:
558
                os.mkdir(stock_dir)
559
            except Exception, e:
560
                LOG.error("Unable to create directory: %s" % stock_dir)
561
                return
562
        if not self.copy_shipped_stock_configs(stock_dir):
563
            return
564

    
565
        def _do_import_action(config):
566
            name = os.path.splitext(os.path.basename(config))[0]
567
            action_name = "stock-%i" % configs.index(config)
568
            path = "/MenuBar/radio/stock"
569
            action = gtk.Action(action_name,
570
                                name,
571
                                _("Import stock "
572
                                  "configuration {name}").format(name=name),
573
                                "")
574
            action.connect("activate", self.import_stock_config, config)
575
            mid = self.menu_uim.new_merge_id()
576
            mid = self.menu_uim.add_ui(mid, path,
577
                                       action_name, action_name,
578
                                       gtk.UI_MANAGER_MENUITEM, False)
579
            self.menu_ag.add_action(action)
580

    
581
        def _do_open_action(config):
582
            name = os.path.splitext(os.path.basename(config))[0]
583
            action_name = "openstock-%i" % configs.index(config)
584
            path = "/MenuBar/file/openstock"
585
            action = gtk.Action(action_name,
586
                                name,
587
                                _("Open stock "
588
                                  "configuration {name}").format(name=name),
589
                                "")
590
            action.connect("activate", lambda a, c: self.do_open(c), config)
591
            mid = self.menu_uim.new_merge_id()
592
            mid = self.menu_uim.add_ui(mid, path,
593
                                       action_name, action_name,
594
                                       gtk.UI_MANAGER_MENUITEM, False)
595
            self.menu_ag.add_action(action)
596

    
597
        configs = glob(os.path.join(stock_dir, "*.csv"))
598
        for config in configs:
599
            _do_import_action(config)
600
            _do_open_action(config)
601

    
602
    def _confirm_experimental(self, rclass):
603
        sql_key = "warn_experimental_%s" % directory.radio_class_id(rclass)
604
        if CONF.is_defined(sql_key, "state") and \
605
                not CONF.get_bool(sql_key, "state"):
606
            return True
607

    
608
        title = _("Proceed with experimental driver?")
609
        text = rclass.get_prompts().experimental
610
        msg = _("This radio's driver is experimental. "
611
                "Do you want to proceed?")
612
        resp, squelch = common.show_warning(msg, text,
613
                                            title=title,
614
                                            buttons=gtk.BUTTONS_YES_NO,
615
                                            can_squelch=True)
616
        if resp == gtk.RESPONSE_YES:
617
            CONF.set_bool(sql_key, not squelch, "state")
618
        return resp == gtk.RESPONSE_YES
619

    
620
    def _show_instructions(self, radio, message):
621
        if message is None:
622
            return
623

    
624
        if CONF.get_bool("clone_instructions", "noconfirm"):
625
            return
626

    
627
        d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
628
        d.set_markup("<big><b>" + _("{name} Instructions").format(
629
                     name=radio.get_name()) + "</b></big>")
630
        msg = _("{instructions}").format(instructions=message)
631
        d.format_secondary_markup(msg)
632

    
633
        again = gtk.CheckButton(
634
            _("Don't show instructions for any radio again"))
635
        again.show()
636
        again.connect("toggled", lambda action:
637
            self.clonemenu.set_active(not action.get_active()))
638
        d.vbox.pack_start(again, 0, 0, 0)
639
        h_button_box = d.vbox.get_children()[2]
640
        try:
641
            ok_button = h_button_box.get_children()[0]
642
            ok_button.grab_default()
643
            ok_button.grab_focus()
644
        except AttributeError:
645
            # don't grab focus on GTK+ 2.0
646
            pass
647
        d.run()
648
        d.destroy()
649

    
650

    
651
    def do_download(self, port=None, rtype=None):
652
        d = clone.CloneSettingsDialog(parent=self)
653
        settings = d.run()
654
        d.destroy()
655
        if not settings:
656
            return
657

    
658
        rclass = settings.radio_class
659
        if issubclass(rclass, chirp_common.ExperimentalRadio) and \
660
                not self._confirm_experimental(rclass):
661
            # User does not want to proceed with experimental driver
662
            return
663

    
664
        self._show_instructions(rclass, rclass.get_prompts().pre_download)
665

    
666
        LOG.debug("User selected %s %s on port %s" %
667
                  (rclass.VENDOR, rclass.MODEL, settings.port))
668

    
669
        try:
670
            ser = serial.Serial(port=settings.port,
671
                                baudrate=rclass.BAUD_RATE,
672
                                rtscts=rclass.HARDWARE_FLOW,
673
                                timeout=0.25)
674
            ser.flushInput()
675
        except serial.SerialException, e:
676
            d = inputdialog.ExceptionDialog(e)
677
            d.run()
678
            d.destroy()
679
            return
680

    
681
        radio = settings.radio_class(ser)
682

    
683
        fn = tempfile.mktemp()
684
        if isinstance(radio, chirp_common.CloneModeRadio):
685
            ct = clone.CloneThread(radio, "in", cb=self.cb_clonein,
686
                                   parent=self)
687
            ct.start()
688
        else:
689
            self.do_open_live(radio)
690

    
691
    def do_upload(self, port=None, rtype=None):
692
        eset = self.get_current_editorset()
693
        radio = eset.radio
694

    
695
        settings = clone.CloneSettings()
696
        settings.radio_class = radio.__class__
697

    
698
        d = clone.CloneSettingsDialog(settings, parent=self)
699
        settings = d.run()
700
        d.destroy()
701
        if not settings:
702
            return
703
        prompts = radio.get_prompts()
704

    
705
        if prompts.display_pre_upload_prompt_before_opening_port is True:
706
            LOG.debug("Opening port after pre_upload prompt.")
707
            self._show_instructions(radio, prompts.pre_upload)
708

    
709
        if isinstance(radio, chirp_common.ExperimentalRadio) and \
710
                not self._confirm_experimental(radio.__class__):
711
            # User does not want to proceed with experimental driver
712
            return
713

    
714
        try:
715
            ser = serial.Serial(port=settings.port,
716
                                baudrate=radio.BAUD_RATE,
717
                                rtscts=radio.HARDWARE_FLOW,
718
                                timeout=0.25)
719
            ser.flushInput()
720
        except serial.SerialException, e:
721
            d = inputdialog.ExceptionDialog(e)
722
            d.run()
723
            d.destroy()
724
            return
725

    
726
        if prompts.display_pre_upload_prompt_before_opening_port is False:
727
            LOG.debug("Opening port before pre_upload prompt.")
728
            self._show_instructions(radio, prompts.pre_upload)
729

    
730
        radio.set_pipe(ser)
731

    
732
        ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self)
733
        ct.start()
734

    
735
    def do_close(self, tab_child=None):
736
        if tab_child:
737
            eset = tab_child
738
        else:
739
            eset = self.get_current_editorset()
740

    
741
        if not eset:
742
            return False
743

    
744
        if eset.is_modified():
745
            dlg = miscwidgets.YesNoDialog(
746
                title=_("Save Changes?"), parent=self,
747
                buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES,
748
                         gtk.STOCK_NO, gtk.RESPONSE_NO,
749
                         gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
750
            dlg.set_text(_("File is modified, save changes before closing?"))
751
            res = dlg.run()
752
            dlg.destroy()
753

    
754
            if res == gtk.RESPONSE_YES:
755
                self.do_save(eset)
756
            elif res != gtk.RESPONSE_NO:
757
                raise ModifiedError()
758

    
759
        eset.rthread.stop()
760
        eset.rthread.join()
761

    
762
        eset.prepare_close()
763

    
764
        if eset.radio.pipe:
765
            eset.radio.pipe.close()
766

    
767
        if isinstance(eset.radio, chirp_common.LiveRadio):
768
            action = self.menu_ag.get_action("openlive")
769
            if action:
770
                action.set_sensitive(True)
771

    
772
        page = self.tabs.page_num(eset)
773
        if page is not None:
774
            self.tabs.remove_page(page)
775

    
776
        return True
777

    
778
    def do_import(self):
779
        types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
780
                 (_("CHIRP Radio Images") + " (*.img)", "*.img"),
781
                 (_("CSV Files") + " (*.csv)", "*.csv"),
782
                 (_("DAT Files") + " (*.dat)", "*.dat"),
783
                 (_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
784
                 (_("ICF Files") + " (*.icf)", "*.icf"),
785
                 (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
786
                 (_("Kenwood ITM Files") + " (*.itm)", "*.itm"),
787
                 (_("Travel Plus Files") + " (*.tpe)", "*.tpe"),
788
                 (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
789
                 (_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
790
                 (_("VX7 Commander Files") + " (*.vx7)", "*.vx7")]
791
        filen = platform.get_platform().gui_open_file(types=types)
792
        if not filen:
793
            return
794

    
795
        eset = self.get_current_editorset()
796
        count = eset.do_import(filen)
797
        reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
798

    
799
    def do_repeaterbook_prompt(self):
800
        if not CONF.get_bool("has_seen_credit", "repeaterbook"):
801
            d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
802
            d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" +
803
                         "<i>North American Repeater Directory</i></big>")
804
            d.format_secondary_markup("For more information about this " +
805
                                      "free service, please go to\r\n" +
806
                                      "http://www.repeaterbook.com")
807
            d.run()
808
            d.destroy()
809
            CONF.set_bool("has_seen_credit", True, "repeaterbook")
810

    
811
        default_state = "Oregon"
812
        default_county = "--All--"
813
        default_band = "--All--"
814
        try:
815
            try:
816
                code = int(CONF.get("state", "repeaterbook"))
817
            except:
818
                code = CONF.get("state", "repeaterbook")
819
            for k, v in fips.FIPS_STATES.items():
820
                if code == v:
821
                    default_state = k
822
                    break
823

    
824
            code = CONF.get("county", "repeaterbook")
825
            items = fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items()
826
            for k, v in items:
827
                if code == v:
828
                    default_county = k
829
                    break
830

    
831
            code = int(CONF.get("band", "repeaterbook"))
832
            for k, v in RB_BANDS.items():
833
                if code == v:
834
                    default_band = k
835
                    break
836
        except:
837
            pass
838

    
839
        state = miscwidgets.make_choice(sorted(fips.FIPS_STATES.keys()),
840
                                        False, default_state)
841
        county = miscwidgets.make_choice(
842
            sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()),
843
            False, default_county)
844
        band = miscwidgets.make_choice(sorted(RB_BANDS.keys(), key=key_bands),
845
                                       False, default_band)
846

    
847
        def _changed(box, county):
848
            state = fips.FIPS_STATES[box.get_active_text()]
849
            county.get_model().clear()
850
            for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()):
851
                county.append_text(fips_county)
852
            county.set_active(0)
853

    
854
        state.connect("changed", _changed, county)
855

    
856
        d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
857
        d.add_field("State", state)
858
        d.add_field("County", county)
859
        d.add_field("Band", band)
860

    
861
        r = d.run()
862
        d.destroy()
863
        if r != gtk.RESPONSE_OK:
864
            return False
865

    
866
        code = fips.FIPS_STATES[state.get_active_text()]
867
        county_id = fips.FIPS_COUNTIES[code][county.get_active_text()]
868
        freq = RB_BANDS[band.get_active_text()]
869
        CONF.set("state", str(code), "repeaterbook")
870
        CONF.set("county", str(county_id), "repeaterbook")
871
        CONF.set("band", str(freq), "repeaterbook")
872

    
873
        return True
874

    
875
    def do_repeaterbook(self, do_import):
876
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
877
        if not self.do_repeaterbook_prompt():
878
            self.window.set_cursor(None)
879
            return
880

    
881
        try:
882
            code = "%02i" % int(CONF.get("state", "repeaterbook"))
883
        except:
884
            try:
885
                code = CONF.get("state", "repeaterbook")
886
            except:
887
                code = '41'  # Oregon default
888

    
889
        try:
890
            county = CONF.get("county", "repeaterbook")
891
        except:
892
            county = '%'  # --All-- default
893

    
894
        try:
895
            band = int(CONF.get("band", "repeaterbook"))
896
        except:
897
            band = 14  # 2m default
898

    
899
        query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php" + \
900
            "?func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \
901
            "&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%"
902
        query = query % (code,
903
                         band and band or "%%",
904
                         county and county or "%%")
905

    
906
        # Do this in case the import process is going to take a while
907
        # to make sure we process events leading up to this
908
        gtk.gdk.window_process_all_updates()
909
        while gtk.events_pending():
910
            gtk.main_iteration(False)
911

    
912
        fn = tempfile.mktemp(".csv")
913
        filename, headers = urllib.urlretrieve(query, fn)
914
        if not os.path.exists(filename):
915
            LOG.error("Failed, headers were: %s", headers)
916
            common.show_error(_("RepeaterBook query failed"))
917
            self.window.set_cursor(None)
918
            return
919

    
920
        class RBRadio(generic_csv.CSVRadio,
921
                      chirp_common.NetworkSourceRadio):
922
            VENDOR = "RepeaterBook"
923
            MODEL = ""
924

    
925
        try:
926
            # Validate CSV
927
            radio = RBRadio(filename)
928
            if radio.errors:
929
                reporting.report_misc_error("repeaterbook",
930
                                            ("query=%s\n" % query) +
931
                                            ("\n") +
932
                                            ("\n".join(radio.errors)))
933
        except errors.InvalidDataError, e:
934
            common.show_error(str(e))
935
            self.window.set_cursor(None)
936
            return
937
        except Exception, e:
938
            common.log_exception()
939

    
940
        reporting.report_model_usage(radio, "import", True)
941

    
942
        self.window.set_cursor(None)
943
        if do_import:
944
            eset = self.get_current_editorset()
945
            count = eset.do_import(filename)
946
        else:
947
            self.do_open_live(radio, read_only=True)
948

    
949
    def do_przemienniki_prompt(self):
950
        d = inputdialog.FieldDialog(title='przemienniki.net query',
951
                                    parent=self)
952
        fields = {
953
            "Country":
954
                (miscwidgets.make_choice(
955
                    ['at', 'bg', 'by', 'ch', 'cz', 'de', 'dk', 'es', 'fi',
956
                        'fr', 'hu', 'it', 'lt', 'lv', 'no', 'pl', 'ro', 'se',
957
                        'sk', 'ua', 'uk'], False),
958
                 lambda x: str(x.get_active_text())),
959
            "Band":
960
                (miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
961
                                          '23cm', '13cm', '3cm'], False, '2m'),
962
                 lambda x: str(x.get_active_text())),
963
            "Mode":
964
                (miscwidgets.make_choice(['fm', 'dv'], False),
965
                 lambda x: str(x.get_active_text())),
966
            "Only Working":
967
                (miscwidgets.make_choice(['', 'yes'], False),
968
                 lambda x: str(x.get_active_text())),
969
            "Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
970
            "Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
971
            "Range": (gtk.Entry(), lambda x: int(x.get_text())),
972
            }
973
        for name in sorted(fields.keys()):
974
            value, fn = fields[name]
975
            d.add_field(name, value)
976
        while d.run() == gtk.RESPONSE_OK:
977
            query = "http://przemienniki.net/export/chirp.csv?"
978
            args = []
979
            for name, (value, fn) in fields.items():
980
                if isinstance(value, gtk.Entry):
981
                    contents = value.get_text()
982
                else:
983
                    contents = value.get_active_text()
984
                if contents:
985
                    try:
986
                        _value = fn(value)
987
                    except ValueError:
988
                        common.show_error(_("Invalid value for %s") % name)
989
                        query = None
990
                        continue
991

    
992
                    args.append("=".join((name.replace(" ", "").lower(),
993
                                          contents)))
994
            query += "&".join(args)
995
            LOG.debug(query)
996
            d.destroy()
997
            return query
998

    
999
        d.destroy()
1000
        return query
1001

    
1002
    def do_przemienniki(self, do_import):
1003
        url = self.do_przemienniki_prompt()
1004
        if not url:
1005
            return
1006

    
1007
        fn = tempfile.mktemp(".csv")
1008
        filename, headers = urllib.urlretrieve(url, fn)
1009
        if not os.path.exists(filename):
1010
            LOG.error("Failed, headers were: %s", str(headers))
1011
            common.show_error(_("Query failed"))
1012
            return
1013

    
1014
        class PRRadio(generic_csv.CSVRadio,
1015
                      chirp_common.NetworkSourceRadio):
1016
            VENDOR = "przemienniki.net"
1017
            MODEL = ""
1018

    
1019
        try:
1020
            radio = PRRadio(filename)
1021
        except Exception, e:
1022
            common.show_error(str(e))
1023
            return
1024

    
1025
        if do_import:
1026
            eset = self.get_current_editorset()
1027
            count = eset.do_import(filename)
1028
        else:
1029
            self.do_open_live(radio, read_only=True)
1030

    
1031
    def do_rfinder_prompt(self):
1032
        fields = {"1Email": (gtk.Entry(), lambda x: "@" in x),
1033
                  "2Password": (gtk.Entry(), lambda x: x),
1034
                  "3Latitude": (gtk.Entry(),
1035
                                lambda x: float(x) < 90 and float(x) > -90),
1036
                  "4Longitude": (gtk.Entry(),
1037
                                 lambda x: float(x) < 180 and float(x) > -180),
1038
                  "5Range_in_Miles": (gtk.Entry(),
1039
                                      lambda x: int(x) > 0 and int(x) < 5000),
1040
                  }
1041

    
1042
        d = inputdialog.FieldDialog(title="RFinder Login", parent=self)
1043
        for k in sorted(fields.keys()):
1044
            d.add_field(k[1:].replace("_", " "), fields[k][0])
1045
            fields[k][0].set_text(CONF.get(k[1:], "rfinder") or "")
1046
            fields[k][0].set_visibility(k != "2Password")
1047

    
1048
        while d.run() == gtk.RESPONSE_OK:
1049
            valid = True
1050
            for k in sorted(fields.keys()):
1051
                widget, validator = fields[k]
1052
                try:
1053
                    if validator(widget.get_text()):
1054
                        CONF.set(k[1:], widget.get_text(), "rfinder")
1055
                        continue
1056
                except Exception:
1057
                    pass
1058
                common.show_error("Invalid value for %s" % k[1:])
1059
                valid = False
1060
                break
1061

    
1062
            if valid:
1063
                d.destroy()
1064
                return True
1065

    
1066
        d.destroy()
1067
        return False
1068

    
1069
    def do_rfinder(self, do_import):
1070
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
1071
        if not self.do_rfinder_prompt():
1072
            self.window.set_cursor(None)
1073
            return
1074

    
1075
        lat = CONF.get_float("Latitude", "rfinder")
1076
        lon = CONF.get_float("Longitude", "rfinder")
1077
        passwd = CONF.get("Password", "rfinder")
1078
        email = CONF.get("Email", "rfinder")
1079
        miles = CONF.get_int("Range_in_Miles", "rfinder")
1080

    
1081
        # Do this in case the import process is going to take a while
1082
        # to make sure we process events leading up to this
1083
        gtk.gdk.window_process_all_updates()
1084
        while gtk.events_pending():
1085
            gtk.main_iteration(False)
1086

    
1087
        if do_import:
1088
            eset = self.get_current_editorset()
1089
            rfstr = "rfinder://%s/%s/%f/%f/%i" % \
1090
                    (email, passwd, lat, lon, miles)
1091
            count = eset.do_import(rfstr)
1092
        else:
1093
            from chirp.drivers import rfinder
1094
            radio = rfinder.RFinderRadio(None)
1095
            radio.set_params((lat, lon), miles, email, passwd)
1096
            self.do_open_live(radio, read_only=True)
1097

    
1098
        self.window.set_cursor(None)
1099

    
1100
    def do_radioreference_prompt(self):
1101
        fields = {"1Username":  (gtk.Entry(), lambda x: x),
1102
                  "2Password":  (gtk.Entry(), lambda x: x),
1103
                  "3Zipcode":   (gtk.Entry(), lambda x: x),
1104
                  }
1105

    
1106
        d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
1107
                                    parent=self)
1108
        for k in sorted(fields.keys()):
1109
            d.add_field(k[1:], fields[k][0])
1110
            fields[k][0].set_text(CONF.get(k[1:], "radioreference") or "")
1111
            fields[k][0].set_visibility(k != "2Password")
1112

    
1113
        while d.run() == gtk.RESPONSE_OK:
1114
            valid = True
1115
            for k in sorted(fields.keys()):
1116
                widget, validator = fields[k]
1117
                try:
1118
                    if validator(widget.get_text()):
1119
                        CONF.set(k[1:], widget.get_text(), "radioreference")
1120
                        continue
1121
                except Exception:
1122
                    pass
1123
                common.show_error("Invalid value for %s" % k[1:])
1124
                valid = False
1125
                break
1126

    
1127
            if valid:
1128
                d.destroy()
1129
                return True
1130

    
1131
        d.destroy()
1132
        return False
1133

    
1134
    def do_radioreference(self, do_import):
1135
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
1136
        if not self.do_radioreference_prompt():
1137
            self.window.set_cursor(None)
1138
            return
1139

    
1140
        username = CONF.get("Username", "radioreference")
1141
        passwd = CONF.get("Password", "radioreference")
1142
        zipcode = CONF.get("Zipcode", "radioreference")
1143

    
1144
        # Do this in case the import process is going to take a while
1145
        # to make sure we process events leading up to this
1146
        gtk.gdk.window_process_all_updates()
1147
        while gtk.events_pending():
1148
            gtk.main_iteration(False)
1149

    
1150
        if do_import:
1151
            eset = self.get_current_editorset()
1152
            rrstr = "radioreference://%s/%s/%s" % (zipcode, username, passwd)
1153
            count = eset.do_import(rrstr)
1154
        else:
1155
            try:
1156
                from chirp import radioreference
1157
                radio = radioreference.RadioReferenceRadio(None)
1158
                radio.set_params(zipcode, username, passwd)
1159
                self.do_open_live(radio, read_only=True)
1160
            except errors.RadioError, e:
1161
                common.show_error(e)
1162

    
1163
        self.window.set_cursor(None)
1164

    
1165
    def do_export(self):
1166
        types = [(_("CSV Files") + " (*.csv)", "csv"),
1167
                 (_("CHIRP Files") + " (*.chirp)", "chirp"),
1168
                 ]
1169

    
1170
        eset = self.get_current_editorset()
1171

    
1172
        if os.path.exists(eset.filename):
1173
            base = os.path.basename(eset.filename)
1174
            if "." in base:
1175
                base = base[:base.rindex(".")]
1176
            defname = base
1177
        else:
1178
            defname = "radio"
1179

    
1180
        filen = platform.get_platform().gui_save_file(default_name=defname,
1181
                                                      types=types)
1182
        if not filen:
1183
            return
1184

    
1185
        if os.path.exists(filen):
1186
            dlg = inputdialog.OverwriteDialog(filen)
1187
            owrite = dlg.run()
1188
            dlg.destroy()
1189
            if owrite != gtk.RESPONSE_OK:
1190
                return
1191
            os.remove(filen)
1192

    
1193
        count = eset.do_export(filen)
1194
        reporting.report_model_usage(eset.rthread.radio, "export", count > 0)
1195

    
1196
    def do_about(self):
1197
        d = gtk.AboutDialog()
1198
        d.set_transient_for(self)
1199
        import sys
1200
        verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % (
1201
            ".".join([str(x) for x in gtk.gtk_version]),
1202
            ".".join([str(x) for x in gtk.pygtk_version]),
1203
            sys.version.split()[0])
1204

    
1205
        # Set url hook to handle user activating a URL link in the about dialog
1206
        gtk.about_dialog_set_url_hook(lambda dlg, url: webbrowser.open(url))
1207

    
1208
        d.set_name("CHIRP")
1209
        d.set_version(CHIRP_VERSION)
1210
        d.set_copyright("Copyright 2015 Dan Smith (KK7DS)")
1211
        d.set_website("http://chirp.danplanet.com")
1212
        d.set_authors(("Dan Smith KK7DS <dsmith@danplanet.com>",
1213
                       _("With significant contributions from:"),
1214
                       "Tom KD7LXL",
1215
                       "Marco IZ3GME",
1216
                       "Jim KC9HI"
1217
                       ))
1218
        d.set_translator_credits("Polish: Grzegorz SQ2RBY" +
1219
                                 os.linesep +
1220
                                 "Italian: Fabio IZ2QDH" +
1221
                                 os.linesep +
1222
                                 "Dutch: Michael PD4MT" +
1223
                                 os.linesep +
1224
                                 "German: Benjamin HB9EUK" +
1225
                                 os.linesep +
1226
                                 "Hungarian: Attila HA5JA" +
1227
                                 os.linesep +
1228
                                 "Russian: Dmitry Slukin" +
1229
                                 os.linesep +
1230
                                 "Portuguese (BR): Crezivando PP7CJ")
1231
        d.set_comments(verinfo)
1232

    
1233
        d.run()
1234
        d.destroy()
1235

    
1236
    def do_gethelp(self):
1237
        webbrowser.open("http://chirp.danplanet.com")
1238

    
1239
    def do_columns(self):
1240
        eset = self.get_current_editorset()
1241
        driver = directory.get_driver(eset.rthread.radio.__class__)
1242
        radio_name = "%s %s %s" % (eset.rthread.radio.VENDOR,
1243
                                   eset.rthread.radio.MODEL,
1244
                                   eset.rthread.radio.VARIANT)
1245
        d = gtk.Dialog(title=_("Select Columns"),
1246
                       parent=self,
1247
                       buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
1248
                                gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
1249

    
1250
        vbox = gtk.VBox()
1251
        vbox.show()
1252
        sw = gtk.ScrolledWindow()
1253
        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1254
        sw.add_with_viewport(vbox)
1255
        sw.show()
1256
        d.vbox.pack_start(sw, 1, 1, 1)
1257
        d.set_size_request(-1, 300)
1258
        d.set_resizable(False)
1259

    
1260
        labelstr = _("Visible columns for {radio}").format(radio=radio_name)
1261
        label = gtk.Label(labelstr)
1262
        label.show()
1263
        vbox.pack_start(label)
1264

    
1265
        fields = []
1266
        memedit = eset.get_current_editor()  # .editors["memedit"]
1267
        unsupported = memedit.get_unsupported_columns()
1268
        for colspec in memedit.cols:
1269
            if colspec[0].startswith("_"):
1270
                continue
1271
            elif colspec[0] in unsupported:
1272
                continue
1273
            label = colspec[0]
1274
            visible = memedit.get_column_visible(memedit.col(label))
1275
            widget = gtk.CheckButton(label)
1276
            widget.set_active(visible)
1277
            fields.append(widget)
1278
            vbox.pack_start(widget, 1, 1, 1)
1279
            widget.show()
1280

    
1281
        res = d.run()
1282
        selected_columns = []
1283
        if res == gtk.RESPONSE_OK:
1284
            for widget in fields:
1285
                colnum = memedit.col(widget.get_label())
1286
                memedit.set_column_visible(colnum, widget.get_active())
1287
                if widget.get_active():
1288
                    selected_columns.append(widget.get_label())
1289

    
1290
        d.destroy()
1291

    
1292
        CONF.set(driver, ",".join(selected_columns), "memedit_columns")
1293

    
1294
    def do_hide_unused(self, action):
1295
        eset = self.get_current_editorset()
1296
        if eset is None:
1297
            conf = config.get("memedit")
1298
            conf.set_bool("hide_unused", action.get_active())
1299
        else:
1300
            for editortype, editor in eset.editors.iteritems():
1301
                if "memedit" in editortype:
1302
                    editor.set_hide_unused(action.get_active())
1303

    
1304
    def do_clearq(self):
1305
        eset = self.get_current_editorset()
1306
        eset.rthread.flush()
1307

    
1308
    def do_copy(self, cut):
1309
        eset = self.get_current_editorset()
1310
        eset.get_current_editor().copy_selection(cut)
1311

    
1312
    def do_paste(self):
1313
        eset = self.get_current_editorset()
1314
        eset.get_current_editor().paste_selection()
1315

    
1316
    def do_delete(self):
1317
        eset = self.get_current_editorset()
1318
        eset.get_current_editor().copy_selection(True)
1319

    
1320
    def do_toggle_report(self, action):
1321
        if not action.get_active():
1322
            d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=self)
1323
            markup = "<b><big>" + _("Reporting is disabled") + "</big></b>"
1324
            d.set_markup(markup)
1325
            msg = _("The reporting feature of CHIRP is designed to help "
1326
                    "<u>improve quality</u> by allowing the authors to focus "
1327
                    "on the radio drivers used most often and errors "
1328
                    "experienced by the users. The reports contain no "
1329
                    "identifying information and are used only for "
1330
                    "statistical purposes by the authors. Your privacy is "
1331
                    "extremely important, but <u>please consider leaving "
1332
                    "this feature enabled to help make CHIRP better!</u>\n\n"
1333
                    "<b>Are you sure you want to disable this feature?</b>")
1334
            d.format_secondary_markup(msg.replace("\n", "\r\n"))
1335
            r = d.run()
1336
            d.destroy()
1337
            if r == gtk.RESPONSE_NO:
1338
                action.set_active(not action.get_active())
1339

    
1340
        conf = config.get()
1341
        conf.set_bool("no_report", not action.get_active())
1342

    
1343
    def do_toggle_no_smart_tmode(self, action):
1344
        CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit")
1345

    
1346
    def do_toggle_developer(self, action):
1347
        conf = config.get()
1348
        conf.set_bool("developer", action.get_active(), "state")
1349

    
1350
        for name in ["viewdeveloper", "loadmod"]:
1351
            devaction = self.menu_ag.get_action(name)
1352
            devaction.set_visible(action.get_active())
1353

    
1354
    def do_toggle_clone_instructions(self, action):
1355
        CONF.set_bool("clone_instructions", not action.get_active(), "noconfirm")
1356

    
1357
    def do_change_language(self):
1358
        langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
1359
                 "Hungarian", "Russian", "Portuguese (BR)", "French", "Spanish"]
1360
        d = inputdialog.ChoiceDialog(langs, parent=self,
1361
                                     title="Choose Language")
1362
        d.label.set_text(_("Choose a language or Auto to use the "
1363
                           "operating system default. You will need to "
1364
                           "restart the application before the change "
1365
                           "will take effect"))
1366
        d.label.set_line_wrap(True)
1367
        r = d.run()
1368
        if r == gtk.RESPONSE_OK:
1369
            LOG.debug("Chose language %s" % d.choice.get_active_text())
1370
            conf = config.get()
1371
            conf.set("language", d.choice.get_active_text(), "state")
1372
        d.destroy()
1373

    
1374
    def load_module(self):
1375
        types = [(_("Python Modules") + "*.py", "*.py")]
1376
        filen = platform.get_platform().gui_open_file(types=types)
1377
        if not filen:
1378
            return
1379

    
1380
        # We're in development mode, so we need to tell the directory to
1381
        # allow a loaded module to override an existing driver, against
1382
        # its normal better judgement
1383
        directory.enable_reregistrations()
1384

    
1385
        try:
1386
            module = file(filen)
1387
            code = module.read()
1388
            module.close()
1389
            pyc = compile(code, filen, 'exec')
1390
            # See this for why:
1391
            # http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec
1392
            exec(pyc, globals(), globals())
1393
        except Exception, e:
1394
            common.log_exception()
1395
            common.show_error("Unable to load module: %s" % e)
1396

    
1397
    def mh(self, _action, *args):
1398
        action = _action.get_name()
1399

    
1400
        if action == "quit":
1401
            gtk.main_quit()
1402
        elif action == "new":
1403
            self.do_new()
1404
        elif action == "open":
1405
            self.do_open()
1406
        elif action == "save":
1407
            self.do_save()
1408
        elif action == "saveas":
1409
            self.do_saveas()
1410
        elif action.startswith("download"):
1411
            self.do_download(*args)
1412
        elif action.startswith("upload"):
1413
            self.do_upload(*args)
1414
        elif action == "close":
1415
            self.do_close()
1416
        elif action == "import":
1417
            self.do_import()
1418
        elif action in ["qrfinder", "irfinder"]:
1419
            self.do_rfinder(action[0] == "i")
1420
        elif action in ["qradioreference", "iradioreference"]:
1421
            self.do_radioreference(action[0] == "i")
1422
        elif action == "export":
1423
            self.do_export()
1424
        elif action in ["qrbook", "irbook"]:
1425
            self.do_repeaterbook(action[0] == "i")
1426
        elif action in ["qpr", "ipr"]:
1427
            self.do_przemienniki(action[0] == "i")
1428
        elif action == "about":
1429
            self.do_about()
1430
        elif action == "gethelp":
1431
            self.do_gethelp()
1432
        elif action == "columns":
1433
            self.do_columns()
1434
        elif action == "hide_unused":
1435
            self.do_hide_unused(_action)
1436
        elif action == "cancelq":
1437
            self.do_clearq()
1438
        elif action == "report":
1439
            self.do_toggle_report(_action)
1440
        elif action == "channel_defaults":
1441
            # The memedit thread also has an instance of bandplans.
1442
            bp = bandplans.BandPlans(CONF)
1443
            bp.select_bandplan(self)
1444
        elif action == "no_smart_tmode":
1445
            self.do_toggle_no_smart_tmode(_action)
1446
        elif action == "developer":
1447
            self.do_toggle_developer(_action)
1448
        elif action == "clone_instructions":
1449
            self.do_toggle_clone_instructions(_action)
1450
        elif action in ["cut", "copy", "paste", "delete",
1451
                        "move_up", "move_dn", "exchange", "all",
1452
                        "devshowraw", "devdiffraw", "properties"]:
1453
            self.get_current_editorset().get_current_editor().hotkey(_action)
1454
        elif action == "devdifftab":
1455
            self.do_diff_radio()
1456
        elif action == "language":
1457
            self.do_change_language()
1458
        elif action == "loadmod":
1459
            self.load_module()
1460
        else:
1461
            return
1462

    
1463
        self.ev_tab_switched()
1464

    
1465
    def make_menubar(self):
1466
        menu_xml = """
1467
<ui>
1468
  <menubar name="MenuBar">
1469
    <menu action="file">
1470
      <menuitem action="new"/>
1471
      <menuitem action="open"/>
1472
      <menu action="openstock" name="openstock"/>
1473
      <menu action="recent" name="recent"/>
1474
      <menuitem action="save"/>
1475
      <menuitem action="saveas"/>
1476
      <menuitem action="loadmod"/>
1477
      <separator/>
1478
      <menuitem action="import"/>
1479
      <menuitem action="export"/>
1480
      <separator/>
1481
      <menuitem action="close"/>
1482
      <menuitem action="quit"/>
1483
    </menu>
1484
    <menu action="edit">
1485
      <menuitem action="cut"/>
1486
      <menuitem action="copy"/>
1487
      <menuitem action="paste"/>
1488
      <menuitem action="delete"/>
1489
      <separator/>
1490
      <menuitem action="all"/>
1491
      <separator/>
1492
      <menuitem action="move_up"/>
1493
      <menuitem action="move_dn"/>
1494
      <menuitem action="exchange"/>
1495
      <separator/>
1496
      <menuitem action="properties"/>
1497
    </menu>
1498
    <menu action="view">
1499
      <menuitem action="columns"/>
1500
      <menuitem action="hide_unused"/>
1501
      <menuitem action="no_smart_tmode"/>
1502
      <menu action="viewdeveloper">
1503
        <menuitem action="devshowraw"/>
1504
        <menuitem action="devdiffraw"/>
1505
        <menuitem action="devdifftab"/>
1506
      </menu>
1507
      <menuitem action="language"/>
1508
    </menu>
1509
    <menu action="radio" name="radio">
1510
      <menuitem action="download"/>
1511
      <menuitem action="upload"/>
1512
      <menu action="importsrc" name="importsrc">
1513
        <menuitem action="iradioreference"/>
1514
        <menuitem action="irbook"/>
1515
        <menuitem action="ipr"/>
1516
        <menuitem action="irfinder"/>
1517
      </menu>
1518
      <menu action="querysrc" name="querysrc">
1519
        <menuitem action="qradioreference"/>
1520
        <menuitem action="qrbook"/>
1521
        <menuitem action="qpr"/>
1522
        <menuitem action="qrfinder"/>
1523
      </menu>
1524
      <menu action="stock" name="stock"/>
1525
      <separator/>
1526
      <menuitem action="channel_defaults"/>
1527
      <separator/>
1528
      <menuitem action="cancelq"/>
1529
    </menu>
1530
    <menu action="help">
1531
      <menuitem action="gethelp"/>
1532
      <separator/>
1533
      <menuitem action="report"/>
1534
      <menuitem action="clone_instructions"/>
1535
      <menuitem action="developer"/>
1536
      <separator/>
1537
      <menuitem action="about"/>
1538
    </menu>
1539
  </menubar>
1540
</ui>
1541
"""
1542
        ALT_KEY = "<Alt>"
1543
        CTRL_KEY = "<Ctrl>"
1544
        if sys.platform == 'darwin':
1545
            ALT_KEY = "<Meta>"
1546
            CTRL_KEY = "<Meta>"
1547
        actions = [
1548
            ('file', None, _("_File"), None, None, self.mh),
1549
            ('new', gtk.STOCK_NEW, None, None, None, self.mh),
1550
            ('open', gtk.STOCK_OPEN, None, None, None, self.mh),
1551
            ('openstock', None, _("Open stock config"), None, None, self.mh),
1552
            ('recent', None, _("_Recent"), None, None, self.mh),
1553
            ('save', gtk.STOCK_SAVE, None, None, None, self.mh),
1554
            ('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh),
1555
            ('loadmod', None, _("Load Module"), None, None, self.mh),
1556
            ('close', gtk.STOCK_CLOSE, None, None, None, self.mh),
1557
            ('quit', gtk.STOCK_QUIT, None, None, None, self.mh),
1558
            ('edit', None, _("_Edit"), None, None, self.mh),
1559
            ('cut', None, _("_Cut"), "%sx" % CTRL_KEY, None, self.mh),
1560
            ('copy', None, _("_Copy"), "%sc" % CTRL_KEY, None, self.mh),
1561
            ('paste', None, _("_Paste"),
1562
             "%sv" % CTRL_KEY, None, self.mh),
1563
            ('delete', None, _("_Delete"), "Delete", None, self.mh),
1564
            ('all', None, _("Select _All"), None, None, self.mh),
1565
            ('move_up', None, _("Move _Up"),
1566
             "%sUp" % CTRL_KEY, None, self.mh),
1567
            ('move_dn', None, _("Move Dow_n"),
1568
             "%sDown" % CTRL_KEY, None, self.mh),
1569
            ('exchange', None, _("E_xchange"),
1570
             "%s<Shift>x" % CTRL_KEY, None, self.mh),
1571
            ('properties', None, _("P_roperties"), None, None, self.mh),
1572
            ('view', None, _("_View"), None, None, self.mh),
1573
            ('columns', None, _("Columns"), None, None, self.mh),
1574
            ('viewdeveloper', None, _("Developer"), None, None, self.mh),
1575
            ('devshowraw', None, _('Show raw memory'),
1576
             "%s<Shift>r" % CTRL_KEY, None, self.mh),
1577
            ('devdiffraw', None, _("Diff raw memories"),
1578
             "%s<Shift>d" % CTRL_KEY, None, self.mh),
1579
            ('devdifftab', None, _("Diff tabs"),
1580
             "%s<Shift>t" % CTRL_KEY, None, self.mh),
1581
            ('language', None, _("Change language"), None, None, self.mh),
1582
            ('radio', None, _("_Radio"), None, None, self.mh),
1583
            ('download', None, _("Download From Radio"),
1584
             "%sd" % ALT_KEY, None, self.mh),
1585
            ('upload', None, _("Upload To Radio"),
1586
             "%su" % ALT_KEY, None, self.mh),
1587
            ('import', None, _("Import"), "%si" % ALT_KEY, None, self.mh),
1588
            ('export', None, _("Export"), "%sx" % ALT_KEY, None, self.mh),
1589
            ('importsrc', None, _("Import from data source"),
1590
             None, None, self.mh),
1591
            ('iradioreference', None, _("RadioReference.com"),
1592
             None, None, self.mh),
1593
            ('irfinder', None, _("RFinder"), None, None, self.mh),
1594
            ('irbook', None, _("RepeaterBook"), None, None, self.mh),
1595
            ('ipr', None, _("przemienniki.net"), None, None, self.mh),
1596
            ('querysrc', None, _("Query data source"), None, None, self.mh),
1597
            ('qradioreference', None, _("RadioReference.com"),
1598
             None, None, self.mh),
1599
            ('qrfinder', None, _("RFinder"), None, None, self.mh),
1600
            ('qpr', None, _("przemienniki.net"), None, None, self.mh),
1601
            ('qrbook', None, _("RepeaterBook"), None, None, self.mh),
1602
            ('export_chirp', None, _("CHIRP Native File"),
1603
             None, None, self.mh),
1604
            ('export_csv', None, _("CSV File"), None, None, self.mh),
1605
            ('stock', None, _("Import from stock config"),
1606
             None, None, self.mh),
1607
            ('channel_defaults', None, _("Channel defaults"),
1608
             None, None, self.mh),
1609
            ('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
1610
            ('help', None, _('Help'), None, None, self.mh),
1611
            ('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
1612
            ('gethelp', None, _("Get Help Online..."), None, None, self.mh),
1613
            ]
1614

    
1615
        conf = config.get()
1616
        re = not conf.get_bool("no_report")
1617
        hu = conf.get_bool("hide_unused", "memedit", default=True)
1618
        dv = conf.get_bool("developer", "state")
1619
        ci = not conf.get_bool("clone_instructions", "noconfirm")
1620
        st = not conf.get_bool("no_smart_tmode", "memedit")
1621

    
1622
        toggles = [('report', None, _("Report Statistics"),
1623
                    None, None, self.mh, re),
1624
                   ('hide_unused', None, _("Hide Unused Fields"),
1625
                    None, None, self.mh, hu),
1626
                   ('no_smart_tmode', None, _("Smart Tone Modes"),
1627
                    None, None, self.mh, st),
1628
                   ('clone_instructions', None, _("Show Instructions"),
1629
                    None, None, self.mh, ci),
1630
                   ('developer', None, _("Enable Developer Functions"),
1631
                    None, None, self.mh, dv),
1632
                   ]
1633

    
1634
        self.menu_uim = gtk.UIManager()
1635
        self.menu_ag = gtk.ActionGroup("MenuBar")
1636
        self.menu_ag.add_actions(actions)
1637
        self.menu_ag.add_toggle_actions(toggles)
1638

    
1639
        self.menu_uim.insert_action_group(self.menu_ag, 0)
1640
        self.menu_uim.add_ui_from_string(menu_xml)
1641

    
1642
        self.add_accel_group(self.menu_uim.get_accel_group())
1643

    
1644
        self.clonemenu = self.menu_uim.get_widget("/MenuBar/help/clone_instructions")
1645

    
1646
        # Initialize
1647
        self.do_toggle_developer(self.menu_ag.get_action("developer"))
1648

    
1649
        return self.menu_uim.get_widget("/MenuBar")
1650

    
1651
    def make_tabs(self):
1652
        self.tabs = gtk.Notebook()
1653
        self.tabs.set_scrollable(True)
1654

    
1655
        return self.tabs
1656

    
1657
    def close_out(self):
1658
        num = self.tabs.get_n_pages()
1659
        while num > 0:
1660
            num -= 1
1661
            LOG.debug("Closing %i" % num)
1662
            try:
1663
                self.do_close(self.tabs.get_nth_page(num))
1664
            except ModifiedError:
1665
                return False
1666

    
1667
        gtk.main_quit()
1668

    
1669
        return True
1670

    
1671
    def make_status_bar(self):
1672
        box = gtk.HBox(False, 2)
1673

    
1674
        self.sb_general = gtk.Statusbar()
1675
        self.sb_general.set_has_resize_grip(False)
1676
        self.sb_general.show()
1677
        box.pack_start(self.sb_general, 1, 1, 1)
1678

    
1679
        self.sb_radio = gtk.Statusbar()
1680
        self.sb_radio.set_has_resize_grip(True)
1681
        self.sb_radio.show()
1682
        box.pack_start(self.sb_radio, 1, 1, 1)
1683

    
1684
        box.show()
1685
        return box
1686

    
1687
    def ev_delete(self, window, event):
1688
        if not self.close_out():
1689
            return True  # Don't exit
1690

    
1691
    def ev_destroy(self, window):
1692
        if not self.close_out():
1693
            return True  # Don't exit
1694

    
1695
    def setup_extra_hotkeys(self):
1696
        accelg = self.menu_uim.get_accel_group()
1697

    
1698
        def memedit(a):
1699
            self.get_current_editorset().editors["memedit"].hotkey(a)
1700

    
1701
        actions = [
1702
            # ("action_name", "key", function)
1703
            ]
1704

    
1705
        for name, key, fn in actions:
1706
            a = gtk.Action(name, name, name, "")
1707
            a.connect("activate", fn)
1708
            self.menu_ag.add_action_with_accel(a, key)
1709
            a.set_accel_group(accelg)
1710
            a.connect_accelerator()
1711

    
1712
    def _set_icon(self):
1713
        execpath = platform.get_platform().executable_path()
1714
        path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
1715
        if not os.path.exists(path):
1716
            path = "/usr/share/pixmaps/chirp.png"
1717

    
1718
        if os.path.exists(path):
1719
            self.set_icon_from_file(path)
1720
        else:
1721
            LOG.warn("Icon %s not found" % path)
1722

    
1723
    def _updates(self, version):
1724
        if not version:
1725
            return
1726

    
1727
        if version == CHIRP_VERSION:
1728
            return
1729

    
1730
        LOG.info("Server reports version %s is available" % version)
1731

    
1732
        # Report new updates every seven days
1733
        intv = 3600 * 24 * 7
1734

    
1735
        if CONF.is_defined("last_update_check", "state") and \
1736
           (time.time() - CONF.get_int("last_update_check", "state")) < intv:
1737
            return
1738

    
1739
        CONF.set_int("last_update_check", int(time.time()), "state")
1740
        d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
1741
                              type=gtk.MESSAGE_INFO)
1742
        d.set_property("text",
1743
                       _("A new version of CHIRP is available: " +
1744
                         "{ver}. ".format(ver=version) +
1745
                         "It is recommended that you upgrade, so " +
1746
                         "go to http://chirp.danplanet.com soon!"))
1747
        d.run()
1748
        d.destroy()
1749

    
1750
    def _init_macos(self, menu_bar):
1751
        try:
1752
            import gtk_osxapplication
1753
            macapp = gtk_osxapplication.OSXApplication()
1754
        except ImportError, e:
1755
            LOG.error("No MacOS support: %s" % e)
1756
            return
1757

    
1758
        menu_bar.hide()
1759
        macapp.set_menu_bar(menu_bar)
1760

    
1761
        quititem = self.menu_uim.get_widget("/MenuBar/file/quit")
1762
        quititem.hide()
1763

    
1764
        aboutitem = self.menu_uim.get_widget("/MenuBar/help/about")
1765
        macapp.insert_app_menu_item(aboutitem, 0)
1766

    
1767
        documentationitem = self.menu_uim.get_widget("/MenuBar/help/gethelp")
1768
        macapp.insert_app_menu_item(documentationitem, 0)
1769

    
1770
        macapp.set_use_quartz_accelerators(False)
1771
        macapp.ready()
1772

    
1773
        LOG.debug("Initialized MacOS support")
1774

    
1775
    def __init__(self, *args, **kwargs):
1776
        gtk.Window.__init__(self, *args, **kwargs)
1777

    
1778
        def expose(window, event):
1779
            allocation = window.get_allocation()
1780
            CONF.set_int("window_w", allocation.width, "state")
1781
            CONF.set_int("window_h", allocation.height, "state")
1782
        self.connect("expose_event", expose)
1783

    
1784
        def state_change(window, event):
1785
            CONF.set_bool(
1786
                "window_maximized",
1787
                event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED,
1788
                "state")
1789
        self.connect("window-state-event", state_change)
1790

    
1791
        d = CONF.get("last_dir", "state")
1792
        if d and os.path.isdir(d):
1793
            platform.get_platform().set_last_dir(d)
1794

    
1795
        vbox = gtk.VBox(False, 2)
1796

    
1797
        self._recent = []
1798

    
1799
        self.menu_ag = None
1800
        mbar = self.make_menubar()
1801

    
1802
        if os.name != "nt":
1803
            self._set_icon()  # Windows gets the icon from the exe
1804
            if os.uname()[0] == "Darwin":
1805
                self._init_macos(mbar)
1806

    
1807
        vbox.pack_start(mbar, 0, 0, 0)
1808

    
1809
        self.tabs = None
1810
        tabs = self.make_tabs()
1811
        tabs.connect("switch-page", lambda n, _, p: self.ev_tab_switched(p))
1812
        tabs.connect("page-removed", lambda *a: self.ev_tab_switched())
1813
        tabs.show()
1814
        self.ev_tab_switched()
1815
        vbox.pack_start(tabs, 1, 1, 1)
1816

    
1817
        vbox.pack_start(self.make_status_bar(), 0, 0, 0)
1818

    
1819
        vbox.show()
1820

    
1821
        self.add(vbox)
1822

    
1823
        try:
1824
            width = CONF.get_int("window_w", "state")
1825
            height = CONF.get_int("window_h", "state")
1826
        except Exception:
1827
            width = 800
1828
            height = 600
1829

    
1830
        self.set_default_size(width, height)
1831
        if CONF.get_bool("window_maximized", "state"):
1832
            self.maximize()
1833
        self.set_title("CHIRP")
1834

    
1835
        self.connect("delete_event", self.ev_delete)
1836
        self.connect("destroy", self.ev_destroy)
1837

    
1838
        if not CONF.get_bool("warned_about_reporting") and \
1839
                not CONF.get_bool("no_report"):
1840
            d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self)
1841
            d.set_markup("<b><big>" +
1842
                         _("Error reporting is enabled") +
1843
                         "</big></b>")
1844
            d.format_secondary_markup(
1845
                _("If you wish to disable this feature you may do so in "
1846
                  "the <u>Help</u> menu"))
1847
            d.run()
1848
            d.destroy()
1849
        CONF.set_bool("warned_about_reporting", True)
1850

    
1851
        self.update_recent_files()
1852
        self.update_stock_configs()
1853
        self.setup_extra_hotkeys()
1854

    
1855
        def updates_callback(ver):
1856
            gobject.idle_add(self._updates, ver)
1857

    
1858
        if not CONF.get_bool("skip_update_check", "state"):
1859
            reporting.check_for_updates(updates_callback)
    (1-1/1)