|
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
|
|
# Copyright 2012 Tom Hayward <tom@tomh.us>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import tempfile
|
|
import urllib
|
|
import webbrowser
|
|
from glob import glob
|
|
import shutil
|
|
import time
|
|
import logging
|
|
import gtk
|
|
import gobject
|
|
import sys
|
|
|
|
from chirp.ui import inputdialog, common
|
|
from chirp import platform, directory, util
|
|
from chirp.drivers import generic_xml, generic_csv
|
|
from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6
|
|
from chirp.drivers import icf, ic9x_icf
|
|
from chirp import CHIRP_VERSION, chirp_common, detect, errors
|
|
from chirp.ui import editorset, clone, miscwidgets, config, reporting, fips
|
|
from chirp.ui import bandplans
|
|
|
|
gobject.threads_init()
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
if __name__ == "__main__":
|
|
sys.path.insert(0, "..")
|
|
|
|
try:
|
|
import serial
|
|
except ImportError, e:
|
|
common.log_exception()
|
|
common.show_error("\nThe Pyserial module is not installed!")
|
|
|
|
|
|
CONF = config.get()
|
|
|
|
KEEP_RECENT = 8
|
|
|
|
RB_BANDS = {
|
|
"--All--": 0,
|
|
"10 meters (29MHz)": 29,
|
|
"6 meters (54MHz)": 5,
|
|
"2 meters (144MHz)": 14,
|
|
"1.25 meters (220MHz)": 22,
|
|
"70 centimeters (440MHz)": 4,
|
|
"33 centimeters (900MHz)": 9,
|
|
"23 centimeters (1.2GHz)": 12,
|
|
}
|
|
|
|
|
|
def key_bands(band):
|
|
if band.startswith("-"):
|
|
return -1
|
|
|
|
amount, units, mhz = band.split(" ")
|
|
scale = units == "meters" and 100 or 1
|
|
|
|
return 100000 - (float(amount) * scale)
|
|
|
|
|
|
class ModifiedError(Exception):
|
|
pass
|
|
|
|
|
|
class ChirpMain(gtk.Window):
|
|
def get_current_editorset(self):
|
|
page = self.tabs.get_current_page()
|
|
if page is not None:
|
|
return self.tabs.get_nth_page(page)
|
|
else:
|
|
return None
|
|
|
|
def ev_tab_switched(self, pagenum=None):
|
|
def set_action_sensitive(action, sensitive):
|
|
self.menu_ag.get_action(action).set_sensitive(sensitive)
|
|
|
|
if pagenum is not None:
|
|
eset = self.tabs.get_nth_page(pagenum)
|
|
else:
|
|
eset = self.get_current_editorset()
|
|
|
|
upload_sens = bool(eset and
|
|
isinstance(eset.radio, chirp_common.CloneModeRadio))
|
|
|
|
if not eset or isinstance(eset.radio, chirp_common.LiveRadio):
|
|
save_sens = False
|
|
elif isinstance(eset.radio, chirp_common.NetworkSourceRadio):
|
|
save_sens = False
|
|
else:
|
|
save_sens = True
|
|
|
|
for i in ["import", "importsrc", "stock"]:
|
|
set_action_sensitive(i,
|
|
eset is not None and not eset.get_read_only())
|
|
|
|
for i in ["save", "saveas"]:
|
|
set_action_sensitive(i, save_sens)
|
|
|
|
for i in ["upload"]:
|
|
set_action_sensitive(i, upload_sens)
|
|
|
|
for i in ["cancelq"]:
|
|
set_action_sensitive(i, eset is not None and not save_sens)
|
|
|
|
for i in ["export", "close", "columns", "irbook", "irfinder",
|
|
"move_up", "move_dn", "exchange", "iradioreference",
|
|
"cut", "copy", "paste", "delete", "viewdeveloper",
|
|
"all", "properties"]:
|
|
set_action_sensitive(i, eset is not None)
|
|
|
|
def ev_status(self, editorset, msg):
|
|
self.sb_radio.pop(0)
|
|
self.sb_radio.push(0, msg)
|
|
|
|
def ev_usermsg(self, editorset, msg):
|
|
self.sb_general.pop(0)
|
|
self.sb_general.push(0, msg)
|
|
|
|
def ev_editor_selected(self, editorset, editortype):
|
|
mappings = {
|
|
"memedit": ["view", "edit"],
|
|
}
|
|
|
|
for _editortype, actions in mappings.items():
|
|
for _action in actions:
|
|
action = self.menu_ag.get_action(_action)
|
|
action.set_sensitive(editortype.startswith(_editortype))
|
|
|
|
def _connect_editorset(self, eset):
|
|
eset.connect("want-close", self.do_close)
|
|
eset.connect("status", self.ev_status)
|
|
eset.connect("usermsg", self.ev_usermsg)
|
|
eset.connect("editor-selected", self.ev_editor_selected)
|
|
|
|
def do_diff_radio(self):
|
|
if self.tabs.get_n_pages() < 2:
|
|
common.show_error("Diff tabs requires at least two open tabs!")
|
|
return
|
|
|
|
esets = []
|
|
for i in range(0, self.tabs.get_n_pages()):
|
|
esets.append(self.tabs.get_nth_page(i))
|
|
|
|
d = gtk.Dialog(title="Diff Radios",
|
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
|
|
parent=self)
|
|
|
|
label = gtk.Label("")
|
|
label.set_markup("<b>-1</b> for either Mem # does a full-file hex " +
|
|
"dump with diffs highlighted.\n" +
|
|
"<b>-2</b> for first Mem # shows " +
|
|
"<b>only</b> the diffs.")
|
|
d.vbox.pack_start(label, True, True, 0)
|
|
label.show()
|
|
|
|
choices = []
|
|
for eset in esets:
|
|
choices.append("%s %s (%s)" % (eset.rthread.radio.VENDOR,
|
|
eset.rthread.radio.MODEL,
|
|
eset.filename))
|
|
choice_a = miscwidgets.make_choice(choices, False, choices[0])
|
|
choice_a.show()
|
|
chan_a = gtk.SpinButton()
|
|
chan_a.get_adjustment().set_all(1, -2, 999, 1, 10, 0)
|
|
chan_a.show()
|
|
hbox = gtk.HBox(False, 3)
|
|
hbox.pack_start(choice_a, 1, 1, 1)
|
|
hbox.pack_start(chan_a, 0, 0, 0)
|
|
hbox.show()
|
|
d.vbox.pack_start(hbox, 0, 0, 0)
|
|
|
|
choice_b = miscwidgets.make_choice(choices, False, choices[1])
|
|
choice_b.show()
|
|
chan_b = gtk.SpinButton()
|
|
chan_b.get_adjustment().set_all(1, -1, 999, 1, 10, 0)
|
|
chan_b.show()
|
|
hbox = gtk.HBox(False, 3)
|
|
hbox.pack_start(choice_b, 1, 1, 1)
|
|
hbox.pack_start(chan_b, 0, 0, 0)
|
|
hbox.show()
|
|
d.vbox.pack_start(hbox, 0, 0, 0)
|
|
|
|
r = d.run()
|
|
sel_a = choice_a.get_active_text()
|
|
sel_chan_a = chan_a.get_value()
|
|
sel_b = choice_b.get_active_text()
|
|
sel_chan_b = chan_b.get_value()
|
|
d.destroy()
|
|
if r == gtk.RESPONSE_CANCEL:
|
|
return
|
|
|
|
if sel_a == sel_b:
|
|
common.show_error("Can't diff the same tab!")
|
|
return
|
|
|
|
LOG.debug("Selected %s@%i and %s@%i" %
|
|
(sel_a, sel_chan_a, sel_b, sel_chan_b))
|
|
name_a = os.path.basename(sel_a)
|
|
name_a = name_a[:name_a.rindex(")")]
|
|
name_b = os.path.basename(sel_b)
|
|
name_b = name_b[:name_b.rindex(")")]
|
|
diffwintitle = "%s@%i diff %s@%i" % (
|
|
name_a, sel_chan_a, name_b, sel_chan_b)
|
|
|
|
eset_a = esets[choices.index(sel_a)]
|
|
eset_b = esets[choices.index(sel_b)]
|
|
|
|
def _show_diff(mem_b, mem_a):
|
|
# Step 3: Show the diff
|
|
diff = common.simple_diff(mem_a, mem_b)
|
|
common.show_diff_blob(diffwintitle, diff)
|
|
|
|
def _get_mem_b(mem_a):
|
|
# Step 2: Get memory b
|
|
job = common.RadioJob(_show_diff, "get_raw_memory",
|
|
int(sel_chan_b))
|
|
job.set_cb_args(mem_a)
|
|
eset_b.rthread.submit(job)
|
|
|
|
if sel_chan_a >= 0 and sel_chan_b >= 0:
|
|
# Diff numbered memory
|
|
# Step 1: Get memory a
|
|
job = common.RadioJob(_get_mem_b, "get_raw_memory",
|
|
int(sel_chan_a))
|
|
eset_a.rthread.submit(job)
|
|
elif isinstance(eset_a.rthread.radio, chirp_common.CloneModeRadio) and\
|
|
isinstance(eset_b.rthread.radio, chirp_common.CloneModeRadio):
|
|
# Diff whole (can do this without a job, since both are clone-mode)
|
|
try:
|
|
addrfmt = CONF.get('hexdump_addrfmt', section='developer',
|
|
raw=True)
|
|
except:
|
|
pass
|
|
a = util.hexprint(eset_a.rthread.radio._mmap.get_packed(),
|
|
addrfmt=addrfmt)
|
|
b = util.hexprint(eset_b.rthread.radio._mmap.get_packed(),
|
|
addrfmt=addrfmt)
|
|
if sel_chan_a == -2:
|
|
diffsonly = True
|
|
else:
|
|
diffsonly = False
|
|
common.show_diff_blob(diffwintitle,
|
|
common.simple_diff(a, b, diffsonly))
|
|
else:
|
|
common.show_error("Cannot diff whole live-mode radios!")
|
|
|
|
def do_new(self):
|
|
eset = editorset.EditorSet(_("Untitled") + ".csv", self)
|
|
self._connect_editorset(eset)
|
|
eset.prime()
|
|
eset.show()
|
|
|
|
tab = self.tabs.append_page(eset, eset.get_tab_label())
|
|
self.tabs.set_current_page(tab)
|
|
|
|
def _do_manual_select(self, filename):
|
|
radiolist = {}
|
|
for drv, radio in directory.DRV_TO_RADIO.items():
|
|
if not issubclass(radio, chirp_common.CloneModeRadio):
|
|
continue
|
|
radiolist["%s %s" % (radio.VENDOR, radio.MODEL)] = drv
|
|
|
|
lab = gtk.Label("""<b><big>Unable to detect model!</big></b>
|
|
|
|
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.
|
|
""")
|
|
|
|
lab.set_justify(gtk.JUSTIFY_FILL)
|
|
lab.set_line_wrap(True)
|
|
lab.set_use_markup(True)
|
|
lab.show()
|
|
choice = miscwidgets.make_choice(sorted(radiolist.keys()), False,
|
|
sorted(radiolist.keys())[0])
|
|
d = gtk.Dialog(title="Detection Failed",
|
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
d.vbox.pack_start(lab, 0, 0, 0)
|
|
d.vbox.pack_start(choice, 0, 0, 0)
|
|
d.vbox.set_spacing(5)
|
|
choice.show()
|
|
d.set_default_size(400, 200)
|
|
# d.set_resizable(False)
|
|
r = d.run()
|
|
d.destroy()
|
|
if r != gtk.RESPONSE_OK:
|
|
return
|
|
try:
|
|
rc = directory.DRV_TO_RADIO[radiolist[choice.get_active_text()]]
|
|
return rc(filename)
|
|
except:
|
|
return
|
|
|
|
def do_open(self, fname=None, tempname=None):
|
|
if not fname:
|
|
types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"),
|
|
(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
|
|
(_("CSV Files") + " (*.csv)", "*.csv"),
|
|
(_("DAT Files") + " (*.dat)", "*.dat"),
|
|
(_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
|
|
(_("ICF Files") + " (*.icf)", "*.icf"),
|
|
(_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
|
|
(_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
|
|
(_("VX7 Commander Files") + " (*.vx7)", "*.vx7"),
|
|
]
|
|
fname = platform.get_platform().gui_open_file(types=types)
|
|
if not fname:
|
|
return
|
|
|
|
self.record_recent_file(fname)
|
|
|
|
if icf.is_icf_file(fname):
|
|
a = common.ask_yesno_question(
|
|
_("ICF files cannot be edited, only displayed or imported "
|
|
"into another file. Open in read-only mode?"),
|
|
self)
|
|
if not a:
|
|
return
|
|
read_only = True
|
|
else:
|
|
read_only = False
|
|
|
|
if icf.is_9x_icf(fname):
|
|
# We have to actually instantiate the IC9xICFRadio to get its
|
|
# sub-devices
|
|
radio = ic9x_icf.IC9xICFRadio(fname)
|
|
else:
|
|
try:
|
|
radio = directory.get_radio_by_image(fname)
|
|
except errors.ImageDetectFailed:
|
|
radio = self._do_manual_select(fname)
|
|
if not radio:
|
|
return
|
|
LOG.debug("Manually selected %s" % radio)
|
|
except Exception, e:
|
|
common.log_exception()
|
|
common.show_error(os.path.basename(fname) + ": " + str(e))
|
|
return
|
|
|
|
first_tab = False
|
|
try:
|
|
eset = editorset.EditorSet(radio, self,
|
|
filename=fname,
|
|
tempname=tempname)
|
|
except Exception, e:
|
|
common.log_exception()
|
|
common.show_error(
|
|
_("There was an error opening {fname}: {error}").format(
|
|
fname=fname,
|
|
error=e))
|
|
return
|
|
|
|
eset.set_read_only(read_only)
|
|
self._connect_editorset(eset)
|
|
eset.show()
|
|
self.tabs.append_page(eset, eset.get_tab_label())
|
|
|
|
if hasattr(eset.rthread.radio, "errors") and \
|
|
eset.rthread.radio.errors:
|
|
msg = _("{num} errors during open:").format(
|
|
num=len(eset.rthread.radio.errors))
|
|
common.show_error_text(msg,
|
|
"\r\n".join(eset.rthread.radio.errors))
|
|
|
|
def do_live_warning(self, radio):
|
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
|
d.set_markup("<big><b>" + _("Note:") + "</b></big>")
|
|
msg = _("The {vendor} {model} operates in <b>live mode</b>. "
|
|
"This means that any changes you make are immediately sent "
|
|
"to the radio. Because of this, you cannot perform the "
|
|
"<u>Save</u> or <u>Upload</u> operations. If you wish to "
|
|
"edit the contents offline, please <u>Export</u> to a CSV "
|
|
"file, using the <b>File menu</b>.")
|
|
msg = msg.format(vendor=radio.VENDOR, model=radio.MODEL)
|
|
d.format_secondary_markup(msg)
|
|
|
|
again = gtk.CheckButton(_("Don't show this again"))
|
|
again.show()
|
|
d.vbox.pack_start(again, 0, 0, 0)
|
|
d.run()
|
|
CONF.set_bool("live_mode", again.get_active(), "noconfirm")
|
|
d.destroy()
|
|
|
|
def do_open_live(self, radio, tempname=None, read_only=False):
|
|
eset = editorset.EditorSet(radio, self, tempname=tempname)
|
|
eset.connect("want-close", self.do_close)
|
|
eset.connect("status", self.ev_status)
|
|
eset.set_read_only(read_only)
|
|
eset.show()
|
|
self.tabs.append_page(eset, eset.get_tab_label())
|
|
|
|
if isinstance(radio, chirp_common.LiveRadio):
|
|
reporting.report_model_usage(radio, "live", True)
|
|
if not CONF.get_bool("live_mode", "noconfirm"):
|
|
self.do_live_warning(radio)
|
|
|
|
def do_save(self, eset=None):
|
|
if not eset:
|
|
eset = self.get_current_editorset()
|
|
|
|
# For usability, allow Ctrl-S to short-circuit to Save-As if
|
|
# we are working on a yet-to-be-saved image
|
|
if not os.path.exists(eset.filename):
|
|
return self.do_saveas()
|
|
|
|
eset.save()
|
|
|
|
def do_saveas(self):
|
|
eset = self.get_current_editorset()
|
|
|
|
label = _("{vendor} {model} image file").format(
|
|
vendor=eset.radio.VENDOR,
|
|
model=eset.radio.MODEL)
|
|
|
|
types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
|
|
eset.radio.FILE_EXTENSION)]
|
|
|
|
if isinstance(eset.radio, vx7.VX7Radio):
|
|
types += [(_("VX7 Commander") + " (*.vx7)", "vx7")]
|
|
elif isinstance(eset.radio, vx6.VX6Radio):
|
|
types += [(_("VX6 Commander") + " (*.vx6)", "vx6")]
|
|
elif isinstance(eset.radio, vx5.VX5Radio):
|
|
types += [(_("EVE") + " (*.eve)", "eve")]
|
|
types += [(_("VX5 Commander") + " (*.vx5)", "vx5")]
|
|
|
|
while True:
|
|
fname = platform.get_platform().gui_save_file(types=types)
|
|
if not fname:
|
|
return
|
|
|
|
if os.path.exists(fname):
|
|
dlg = inputdialog.OverwriteDialog(fname)
|
|
owrite = dlg.run()
|
|
dlg.destroy()
|
|
if owrite == gtk.RESPONSE_OK:
|
|
break
|
|
else:
|
|
break
|
|
|
|
try:
|
|
eset.save(fname)
|
|
except Exception, e:
|
|
d = inputdialog.ExceptionDialog(e)
|
|
d.run()
|
|
d.destroy()
|
|
|
|
def cb_clonein(self, radio, emsg=None):
|
|
radio.pipe.close()
|
|
reporting.report_model_usage(radio, "download", bool(emsg))
|
|
if not emsg:
|
|
self.do_open_live(radio, tempname="(" + _("Untitled") + ")")
|
|
else:
|
|
d = inputdialog.ExceptionDialog(emsg)
|
|
d.run()
|
|
d.destroy()
|
|
|
|
def cb_cloneout(self, radio, emsg=None):
|
|
radio.pipe.close()
|
|
reporting.report_model_usage(radio, "upload", True)
|
|
if emsg:
|
|
d = inputdialog.ExceptionDialog(emsg)
|
|
d.run()
|
|
d.destroy()
|
|
|
|
def _get_recent_list(self):
|
|
recent = []
|
|
for i in range(0, KEEP_RECENT):
|
|
fn = CONF.get("recent%i" % i, "state")
|
|
if fn:
|
|
recent.append(fn)
|
|
return recent
|
|
|
|
def _set_recent_list(self, recent):
|
|
for fn in recent:
|
|
CONF.set("recent%i" % recent.index(fn), fn, "state")
|
|
|
|
def update_recent_files(self):
|
|
i = 0
|
|
for fname in self._get_recent_list():
|
|
action_name = "recent%i" % i
|
|
path = "/MenuBar/file/recent"
|
|
|
|
old_action = self.menu_ag.get_action(action_name)
|
|
if old_action:
|
|
self.menu_ag.remove_action(old_action)
|
|
|
|
file_basename = os.path.basename(fname).replace("_", "__")
|
|
action = gtk.Action(
|
|
action_name, "_%i. %s" % (i+1, file_basename),
|
|
_("Open recent file {name}").format(name=fname), "")
|
|
action.connect("activate", lambda a, f: self.do_open(f), fname)
|
|
mid = self.menu_uim.new_merge_id()
|
|
self.menu_uim.add_ui(mid, path,
|
|
action_name, action_name,
|
|
gtk.UI_MANAGER_MENUITEM, False)
|
|
self.menu_ag.add_action(action)
|
|
i += 1
|
|
|
|
def record_recent_file(self, filename):
|
|
|
|
recent_files = self._get_recent_list()
|
|
if filename not in recent_files:
|
|
if len(recent_files) == KEEP_RECENT:
|
|
del recent_files[-1]
|
|
recent_files.insert(0, filename)
|
|
self._set_recent_list(recent_files)
|
|
|
|
self.update_recent_files()
|
|
|
|
def import_stock_config(self, action, config):
|
|
eset = self.get_current_editorset()
|
|
count = eset.do_import(config)
|
|
|
|
def copy_shipped_stock_configs(self, stock_dir):
|
|
execpath = platform.get_platform().executable_path()
|
|
basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
|
|
if not os.path.exists(basepath):
|
|
basepath = "/usr/share/chirp/stock_configs"
|
|
|
|
files = glob(os.path.join(basepath, "*.csv"))
|
|
for fn in files:
|
|
if os.path.exists(os.path.join(stock_dir, os.path.basename(fn))):
|
|
LOG.info("Skipping existing stock config")
|
|
continue
|
|
try:
|
|
shutil.copy(fn, stock_dir)
|
|
LOG.debug("Copying %s -> %s" % (fn, stock_dir))
|
|
except Exception, e:
|
|
LOG.error("Unable to copy %s to %s: %s" % (fn, stock_dir, e))
|
|
return False
|
|
return True
|
|
|
|
def update_stock_configs(self):
|
|
stock_dir = platform.get_platform().config_file("stock_configs")
|
|
if not os.path.isdir(stock_dir):
|
|
try:
|
|
os.mkdir(stock_dir)
|
|
except Exception, e:
|
|
LOG.error("Unable to create directory: %s" % stock_dir)
|
|
return
|
|
if not self.copy_shipped_stock_configs(stock_dir):
|
|
return
|
|
|
|
def _do_import_action(config):
|
|
name = os.path.splitext(os.path.basename(config))[0]
|
|
action_name = "stock-%i" % configs.index(config)
|
|
path = "/MenuBar/radio/stock"
|
|
action = gtk.Action(action_name,
|
|
name,
|
|
_("Import stock "
|
|
"configuration {name}").format(name=name),
|
|
"")
|
|
action.connect("activate", self.import_stock_config, config)
|
|
mid = self.menu_uim.new_merge_id()
|
|
mid = self.menu_uim.add_ui(mid, path,
|
|
action_name, action_name,
|
|
gtk.UI_MANAGER_MENUITEM, False)
|
|
self.menu_ag.add_action(action)
|
|
|
|
def _do_open_action(config):
|
|
name = os.path.splitext(os.path.basename(config))[0]
|
|
action_name = "openstock-%i" % configs.index(config)
|
|
path = "/MenuBar/file/openstock"
|
|
action = gtk.Action(action_name,
|
|
name,
|
|
_("Open stock "
|
|
"configuration {name}").format(name=name),
|
|
"")
|
|
action.connect("activate", lambda a, c: self.do_open(c), config)
|
|
mid = self.menu_uim.new_merge_id()
|
|
mid = self.menu_uim.add_ui(mid, path,
|
|
action_name, action_name,
|
|
gtk.UI_MANAGER_MENUITEM, False)
|
|
self.menu_ag.add_action(action)
|
|
|
|
configs = glob(os.path.join(stock_dir, "*.csv"))
|
|
for config in configs:
|
|
_do_import_action(config)
|
|
_do_open_action(config)
|
|
|
|
def _confirm_experimental(self, rclass):
|
|
sql_key = "warn_experimental_%s" % directory.radio_class_id(rclass)
|
|
if CONF.is_defined(sql_key, "state") and \
|
|
not CONF.get_bool(sql_key, "state"):
|
|
return True
|
|
|
|
title = _("Proceed with experimental driver?")
|
|
text = rclass.get_prompts().experimental
|
|
msg = _("This radio's driver is experimental. "
|
|
"Do you want to proceed?")
|
|
resp, squelch = common.show_warning(msg, text,
|
|
title=title,
|
|
buttons=gtk.BUTTONS_YES_NO,
|
|
can_squelch=True)
|
|
if resp == gtk.RESPONSE_YES:
|
|
CONF.set_bool(sql_key, not squelch, "state")
|
|
return resp == gtk.RESPONSE_YES
|
|
|
|
def _show_instructions(self, radio, message):
|
|
if message is None:
|
|
return
|
|
|
|
if CONF.get_bool("clone_instructions", "noconfirm"):
|
|
return
|
|
|
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
|
d.set_markup("<big><b>" + _("{name} Instructions").format(
|
|
name=radio.get_name()) + "</b></big>")
|
|
msg = _("{instructions}").format(instructions=message)
|
|
d.format_secondary_markup(msg)
|
|
|
|
again = gtk.CheckButton(
|
|
_("Don't show instructions for any radio again"))
|
|
again.show()
|
|
again.connect("toggled", lambda action:
|
|
self.clonemenu.set_active(not action.get_active()))
|
|
d.vbox.pack_start(again, 0, 0, 0)
|
|
h_button_box = d.vbox.get_children()[2]
|
|
try:
|
|
ok_button = h_button_box.get_children()[0]
|
|
ok_button.grab_default()
|
|
ok_button.grab_focus()
|
|
except AttributeError:
|
|
# don't grab focus on GTK+ 2.0
|
|
pass
|
|
d.run()
|
|
d.destroy()
|
|
|
|
|
|
def do_download(self, port=None, rtype=None):
|
|
d = clone.CloneSettingsDialog(parent=self)
|
|
settings = d.run()
|
|
d.destroy()
|
|
if not settings:
|
|
return
|
|
|
|
rclass = settings.radio_class
|
|
if issubclass(rclass, chirp_common.ExperimentalRadio) and \
|
|
not self._confirm_experimental(rclass):
|
|
# User does not want to proceed with experimental driver
|
|
return
|
|
|
|
self._show_instructions(rclass, rclass.get_prompts().pre_download)
|
|
|
|
LOG.debug("User selected %s %s on port %s" %
|
|
(rclass.VENDOR, rclass.MODEL, settings.port))
|
|
|
|
try:
|
|
ser = serial.Serial(port=settings.port,
|
|
baudrate=rclass.BAUD_RATE,
|
|
rtscts=rclass.HARDWARE_FLOW,
|
|
timeout=0.25)
|
|
ser.flushInput()
|
|
except serial.SerialException, e:
|
|
d = inputdialog.ExceptionDialog(e)
|
|
d.run()
|
|
d.destroy()
|
|
return
|
|
|
|
radio = settings.radio_class(ser)
|
|
|
|
fn = tempfile.mktemp()
|
|
if isinstance(radio, chirp_common.CloneModeRadio):
|
|
ct = clone.CloneThread(radio, "in", cb=self.cb_clonein,
|
|
parent=self)
|
|
ct.start()
|
|
else:
|
|
self.do_open_live(radio)
|
|
|
|
def do_upload(self, port=None, rtype=None):
|
|
eset = self.get_current_editorset()
|
|
radio = eset.radio
|
|
|
|
settings = clone.CloneSettings()
|
|
settings.radio_class = radio.__class__
|
|
|
|
d = clone.CloneSettingsDialog(settings, parent=self)
|
|
settings = d.run()
|
|
d.destroy()
|
|
if not settings:
|
|
return
|
|
prompts = radio.get_prompts()
|
|
|
|
if prompts.display_pre_upload_prompt_before_opening_port is True:
|
|
LOG.debug("Opening port after pre_upload prompt.")
|
|
self._show_instructions(radio, prompts.pre_upload)
|
|
|
|
if isinstance(radio, chirp_common.ExperimentalRadio) and \
|
|
not self._confirm_experimental(radio.__class__):
|
|
# User does not want to proceed with experimental driver
|
|
return
|
|
|
|
try:
|
|
ser = serial.Serial(port=settings.port,
|
|
baudrate=radio.BAUD_RATE,
|
|
rtscts=radio.HARDWARE_FLOW,
|
|
timeout=0.25)
|
|
ser.flushInput()
|
|
except serial.SerialException, e:
|
|
d = inputdialog.ExceptionDialog(e)
|
|
d.run()
|
|
d.destroy()
|
|
return
|
|
|
|
if prompts.display_pre_upload_prompt_before_opening_port is False:
|
|
LOG.debug("Opening port before pre_upload prompt.")
|
|
self._show_instructions(radio, prompts.pre_upload)
|
|
|
|
radio.set_pipe(ser)
|
|
|
|
ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self)
|
|
ct.start()
|
|
|
|
def do_close(self, tab_child=None):
|
|
if tab_child:
|
|
eset = tab_child
|
|
else:
|
|
eset = self.get_current_editorset()
|
|
|
|
if not eset:
|
|
return False
|
|
|
|
if eset.is_modified():
|
|
dlg = miscwidgets.YesNoDialog(
|
|
title=_("Save Changes?"), parent=self,
|
|
buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES,
|
|
gtk.STOCK_NO, gtk.RESPONSE_NO,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
dlg.set_text(_("File is modified, save changes before closing?"))
|
|
res = dlg.run()
|
|
dlg.destroy()
|
|
|
|
if res == gtk.RESPONSE_YES:
|
|
self.do_save(eset)
|
|
elif res != gtk.RESPONSE_NO:
|
|
raise ModifiedError()
|
|
|
|
eset.rthread.stop()
|
|
eset.rthread.join()
|
|
|
|
eset.prepare_close()
|
|
|
|
if eset.radio.pipe:
|
|
eset.radio.pipe.close()
|
|
|
|
if isinstance(eset.radio, chirp_common.LiveRadio):
|
|
action = self.menu_ag.get_action("openlive")
|
|
if action:
|
|
action.set_sensitive(True)
|
|
|
|
page = self.tabs.page_num(eset)
|
|
if page is not None:
|
|
self.tabs.remove_page(page)
|
|
|
|
return True
|
|
|
|
def do_import(self):
|
|
types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
|
|
(_("CHIRP Radio Images") + " (*.img)", "*.img"),
|
|
(_("CSV Files") + " (*.csv)", "*.csv"),
|
|
(_("DAT Files") + " (*.dat)", "*.dat"),
|
|
(_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
|
|
(_("ICF Files") + " (*.icf)", "*.icf"),
|
|
(_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
|
|
(_("Kenwood ITM Files") + " (*.itm)", "*.itm"),
|
|
(_("Travel Plus Files") + " (*.tpe)", "*.tpe"),
|
|
(_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
|
|
(_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
|
|
(_("VX7 Commander Files") + " (*.vx7)", "*.vx7")]
|
|
filen = platform.get_platform().gui_open_file(types=types)
|
|
if not filen:
|
|
return
|
|
|
|
eset = self.get_current_editorset()
|
|
count = eset.do_import(filen)
|
|
reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
|
|
|
|
def do_repeaterbook_prompt(self):
|
|
if not CONF.get_bool("has_seen_credit", "repeaterbook"):
|
|
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
|
|
d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" +
|
|
"<i>North American Repeater Directory</i></big>")
|
|
d.format_secondary_markup("For more information about this " +
|
|
"free service, please go to\r\n" +
|
|
"http://www.repeaterbook.com")
|
|
d.run()
|
|
d.destroy()
|
|
CONF.set_bool("has_seen_credit", True, "repeaterbook")
|
|
|
|
default_state = "Oregon"
|
|
default_county = "--All--"
|
|
default_band = "--All--"
|
|
try:
|
|
try:
|
|
code = int(CONF.get("state", "repeaterbook"))
|
|
except:
|
|
code = CONF.get("state", "repeaterbook")
|
|
for k, v in fips.FIPS_STATES.items():
|
|
if code == v:
|
|
default_state = k
|
|
break
|
|
|
|
code = CONF.get("county", "repeaterbook")
|
|
items = fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items()
|
|
for k, v in items:
|
|
if code == v:
|
|
default_county = k
|
|
break
|
|
|
|
code = int(CONF.get("band", "repeaterbook"))
|
|
for k, v in RB_BANDS.items():
|
|
if code == v:
|
|
default_band = k
|
|
break
|
|
except:
|
|
pass
|
|
|
|
state = miscwidgets.make_choice(sorted(fips.FIPS_STATES.keys()),
|
|
False, default_state)
|
|
county = miscwidgets.make_choice(
|
|
sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()),
|
|
False, default_county)
|
|
band = miscwidgets.make_choice(sorted(RB_BANDS.keys(), key=key_bands),
|
|
False, default_band)
|
|
|
|
def _changed(box, county):
|
|
state = fips.FIPS_STATES[box.get_active_text()]
|
|
county.get_model().clear()
|
|
for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()):
|
|
county.append_text(fips_county)
|
|
county.set_active(0)
|
|
|
|
state.connect("changed", _changed, county)
|
|
|
|
d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
|
|
d.add_field("State", state)
|
|
d.add_field("County", county)
|
|
d.add_field("Band", band)
|
|
|
|
r = d.run()
|
|
d.destroy()
|
|
if r != gtk.RESPONSE_OK:
|
|
return False
|
|
|
|
code = fips.FIPS_STATES[state.get_active_text()]
|
|
county_id = fips.FIPS_COUNTIES[code][county.get_active_text()]
|
|
freq = RB_BANDS[band.get_active_text()]
|
|
CONF.set("state", str(code), "repeaterbook")
|
|
CONF.set("county", str(county_id), "repeaterbook")
|
|
CONF.set("band", str(freq), "repeaterbook")
|
|
|
|
return True
|
|
|
|
def do_repeaterbook(self, do_import):
|
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
|
if not self.do_repeaterbook_prompt():
|
|
self.window.set_cursor(None)
|
|
return
|
|
|
|
try:
|
|
code = "%02i" % int(CONF.get("state", "repeaterbook"))
|
|
except:
|
|
try:
|
|
code = CONF.get("state", "repeaterbook")
|
|
except:
|
|
code = '41' # Oregon default
|
|
|
|
try:
|
|
county = CONF.get("county", "repeaterbook")
|
|
except:
|
|
county = '%' # --All-- default
|
|
|
|
try:
|
|
band = int(CONF.get("band", "repeaterbook"))
|
|
except:
|
|
band = 14 # 2m default
|
|
|
|
query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php" + \
|
|
"?func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \
|
|
"&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%"
|
|
query = query % (code,
|
|
band and band or "%%",
|
|
county and county or "%%")
|
|
|
|
# Do this in case the import process is going to take a while
|
|
# to make sure we process events leading up to this
|
|
gtk.gdk.window_process_all_updates()
|
|
while gtk.events_pending():
|
|
gtk.main_iteration(False)
|
|
|
|
fn = tempfile.mktemp(".csv")
|
|
filename, headers = urllib.urlretrieve(query, fn)
|
|
if not os.path.exists(filename):
|
|
LOG.error("Failed, headers were: %s", headers)
|
|
common.show_error(_("RepeaterBook query failed"))
|
|
self.window.set_cursor(None)
|
|
return
|
|
|
|
class RBRadio(generic_csv.CSVRadio,
|
|
chirp_common.NetworkSourceRadio):
|
|
VENDOR = "RepeaterBook"
|
|
MODEL = ""
|
|
|
|
try:
|
|
# Validate CSV
|
|
radio = RBRadio(filename)
|
|
if radio.errors:
|
|
reporting.report_misc_error("repeaterbook",
|
|
("query=%s\n" % query) +
|
|
("\n") +
|
|
("\n".join(radio.errors)))
|
|
except errors.InvalidDataError, e:
|
|
common.show_error(str(e))
|
|
self.window.set_cursor(None)
|
|
return
|
|
except Exception, e:
|
|
common.log_exception()
|
|
|
|
reporting.report_model_usage(radio, "import", True)
|
|
|
|
self.window.set_cursor(None)
|
|
if do_import:
|
|
eset = self.get_current_editorset()
|
|
count = eset.do_import(filename)
|
|
else:
|
|
self.do_open_live(radio, read_only=True)
|
|
|
|
def do_przemienniki_prompt(self):
|
|
d = inputdialog.FieldDialog(title='przemienniki.net query',
|
|
parent=self)
|
|
fields = {
|
|
"Country":
|
|
(miscwidgets.make_choice(
|
|
['at', 'bg', 'by', 'ch', 'cz', 'de', 'dk', 'es', 'fi',
|
|
'fr', 'hu', 'it', 'lt', 'lv', 'no', 'pl', 'ro', 'se',
|
|
'sk', 'ua', 'uk'], False),
|
|
lambda x: str(x.get_active_text())),
|
|
"Band":
|
|
(miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
|
|
'23cm', '13cm', '3cm'], False, '2m'),
|
|
lambda x: str(x.get_active_text())),
|
|
"Mode":
|
|
(miscwidgets.make_choice(['fm', 'dv'], False),
|
|
lambda x: str(x.get_active_text())),
|
|
"Only Working":
|
|
(miscwidgets.make_choice(['', 'yes'], False),
|
|
lambda x: str(x.get_active_text())),
|
|
"Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
|
|
"Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
|
|
"Range": (gtk.Entry(), lambda x: int(x.get_text())),
|
|
}
|
|
for name in sorted(fields.keys()):
|
|
value, fn = fields[name]
|
|
d.add_field(name, value)
|
|
while d.run() == gtk.RESPONSE_OK:
|
|
query = "http://przemienniki.net/export/chirp.csv?"
|
|
args = []
|
|
for name, (value, fn) in fields.items():
|
|
if isinstance(value, gtk.Entry):
|
|
contents = value.get_text()
|
|
else:
|
|
contents = value.get_active_text()
|
|
if contents:
|
|
try:
|
|
_value = fn(value)
|
|
except ValueError:
|
|
common.show_error(_("Invalid value for %s") % name)
|
|
query = None
|
|
continue
|
|
|
|
args.append("=".join((name.replace(" ", "").lower(),
|
|
contents)))
|
|
query += "&".join(args)
|
|
LOG.debug(query)
|
|
d.destroy()
|
|
return query
|
|
|
|
d.destroy()
|
|
return query
|
|
|
|
def do_przemienniki(self, do_import):
|
|
url = self.do_przemienniki_prompt()
|
|
if not url:
|
|
return
|
|
|
|
fn = tempfile.mktemp(".csv")
|
|
filename, headers = urllib.urlretrieve(url, fn)
|
|
if not os.path.exists(filename):
|
|
LOG.error("Failed, headers were: %s", str(headers))
|
|
common.show_error(_("Query failed"))
|
|
return
|
|
|
|
class PRRadio(generic_csv.CSVRadio,
|
|
chirp_common.NetworkSourceRadio):
|
|
VENDOR = "przemienniki.net"
|
|
MODEL = ""
|
|
|
|
try:
|
|
radio = PRRadio(filename)
|
|
except Exception, e:
|
|
common.show_error(str(e))
|
|
return
|
|
|
|
if do_import:
|
|
eset = self.get_current_editorset()
|
|
count = eset.do_import(filename)
|
|
else:
|
|
self.do_open_live(radio, read_only=True)
|
|
|
|
def do_rfinder_prompt(self):
|
|
fields = {"1Email": (gtk.Entry(), lambda x: "@" in x),
|
|
"2Password": (gtk.Entry(), lambda x: x),
|
|
"3Latitude": (gtk.Entry(),
|
|
lambda x: float(x) < 90 and float(x) > -90),
|
|
"4Longitude": (gtk.Entry(),
|
|
lambda x: float(x) < 180 and float(x) > -180),
|
|
"5Range_in_Miles": (gtk.Entry(),
|
|
lambda x: int(x) > 0 and int(x) < 5000),
|
|
}
|
|
|
|
d = inputdialog.FieldDialog(title="RFinder Login", parent=self)
|
|
for k in sorted(fields.keys()):
|
|
d.add_field(k[1:].replace("_", " "), fields[k][0])
|
|
fields[k][0].set_text(CONF.get(k[1:], "rfinder") or "")
|
|
fields[k][0].set_visibility(k != "2Password")
|
|
|
|
while d.run() == gtk.RESPONSE_OK:
|
|
valid = True
|
|
for k in sorted(fields.keys()):
|
|
widget, validator = fields[k]
|
|
try:
|
|
if validator(widget.get_text()):
|
|
CONF.set(k[1:], widget.get_text(), "rfinder")
|
|
continue
|
|
except Exception:
|
|
pass
|
|
common.show_error("Invalid value for %s" % k[1:])
|
|
valid = False
|
|
break
|
|
|
|
if valid:
|
|
d.destroy()
|
|
return True
|
|
|
|
d.destroy()
|
|
return False
|
|
|
|
def do_rfinder(self, do_import):
|
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
|
if not self.do_rfinder_prompt():
|
|
self.window.set_cursor(None)
|
|
return
|
|
|
|
lat = CONF.get_float("Latitude", "rfinder")
|
|
lon = CONF.get_float("Longitude", "rfinder")
|
|
passwd = CONF.get("Password", "rfinder")
|
|
email = CONF.get("Email", "rfinder")
|
|
miles = CONF.get_int("Range_in_Miles", "rfinder")
|
|
|
|
# Do this in case the import process is going to take a while
|
|
# to make sure we process events leading up to this
|
|
gtk.gdk.window_process_all_updates()
|
|
while gtk.events_pending():
|
|
gtk.main_iteration(False)
|
|
|
|
if do_import:
|
|
eset = self.get_current_editorset()
|
|
rfstr = "rfinder://%s/%s/%f/%f/%i" % \
|
|
(email, passwd, lat, lon, miles)
|
|
count = eset.do_import(rfstr)
|
|
else:
|
|
from chirp.drivers import rfinder
|
|
radio = rfinder.RFinderRadio(None)
|
|
radio.set_params((lat, lon), miles, email, passwd)
|
|
self.do_open_live(radio, read_only=True)
|
|
|
|
self.window.set_cursor(None)
|
|
|
|
def do_radioreference_prompt(self):
|
|
fields = {"1Username": (gtk.Entry(), lambda x: x),
|
|
"2Password": (gtk.Entry(), lambda x: x),
|
|
"3Zipcode": (gtk.Entry(), lambda x: x),
|
|
}
|
|
|
|
d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
|
|
parent=self)
|
|
for k in sorted(fields.keys()):
|
|
d.add_field(k[1:], fields[k][0])
|
|
fields[k][0].set_text(CONF.get(k[1:], "radioreference") or "")
|
|
fields[k][0].set_visibility(k != "2Password")
|
|
|
|
while d.run() == gtk.RESPONSE_OK:
|
|
valid = True
|
|
for k in sorted(fields.keys()):
|
|
widget, validator = fields[k]
|
|
try:
|
|
if validator(widget.get_text()):
|
|
CONF.set(k[1:], widget.get_text(), "radioreference")
|
|
continue
|
|
except Exception:
|
|
pass
|
|
common.show_error("Invalid value for %s" % k[1:])
|
|
valid = False
|
|
break
|
|
|
|
if valid:
|
|
d.destroy()
|
|
return True
|
|
|
|
d.destroy()
|
|
return False
|
|
|
|
def do_radioreference(self, do_import):
|
|
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
|
|
if not self.do_radioreference_prompt():
|
|
self.window.set_cursor(None)
|
|
return
|
|
|
|
username = CONF.get("Username", "radioreference")
|
|
passwd = CONF.get("Password", "radioreference")
|
|
zipcode = CONF.get("Zipcode", "radioreference")
|
|
|
|
# Do this in case the import process is going to take a while
|
|
# to make sure we process events leading up to this
|
|
gtk.gdk.window_process_all_updates()
|
|
while gtk.events_pending():
|
|
gtk.main_iteration(False)
|
|
|
|
if do_import:
|
|
eset = self.get_current_editorset()
|
|
rrstr = "radioreference://%s/%s/%s" % (zipcode, username, passwd)
|
|
count = eset.do_import(rrstr)
|
|
else:
|
|
try:
|
|
from chirp import radioreference
|
|
radio = radioreference.RadioReferenceRadio(None)
|
|
radio.set_params(zipcode, username, passwd)
|
|
self.do_open_live(radio, read_only=True)
|
|
except errors.RadioError, e:
|
|
common.show_error(e)
|
|
|
|
self.window.set_cursor(None)
|
|
|
|
def do_export(self):
|
|
types = [(_("CSV Files") + " (*.csv)", "csv"),
|
|
(_("CHIRP Files") + " (*.chirp)", "chirp"),
|
|
]
|
|
|
|
eset = self.get_current_editorset()
|
|
|
|
if os.path.exists(eset.filename):
|
|
base = os.path.basename(eset.filename)
|
|
if "." in base:
|
|
base = base[:base.rindex(".")]
|
|
defname = base
|
|
else:
|
|
defname = "radio"
|
|
|
|
filen = platform.get_platform().gui_save_file(default_name=defname,
|
|
types=types)
|
|
if not filen:
|
|
return
|
|
|
|
if os.path.exists(filen):
|
|
dlg = inputdialog.OverwriteDialog(filen)
|
|
owrite = dlg.run()
|
|
dlg.destroy()
|
|
if owrite != gtk.RESPONSE_OK:
|
|
return
|
|
os.remove(filen)
|
|
|
|
count = eset.do_export(filen)
|
|
reporting.report_model_usage(eset.rthread.radio, "export", count > 0)
|
|
|
|
def do_about(self):
|
|
d = gtk.AboutDialog()
|
|
d.set_transient_for(self)
|
|
import sys
|
|
verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % (
|
|
".".join([str(x) for x in gtk.gtk_version]),
|
|
".".join([str(x) for x in gtk.pygtk_version]),
|
|
sys.version.split()[0])
|
|
|
|
# Set url hook to handle user activating a URL link in the about dialog
|
|
gtk.about_dialog_set_url_hook(lambda dlg, url: webbrowser.open(url))
|
|
|
|
d.set_name("CHIRP")
|
|
d.set_version(CHIRP_VERSION)
|
|
d.set_copyright("Copyright 2015 Dan Smith (KK7DS)")
|
|
d.set_website("http://chirp.danplanet.com")
|
|
d.set_authors(("Dan Smith KK7DS <dsmith@danplanet.com>",
|
|
_("With significant contributions from:"),
|
|
"Tom KD7LXL",
|
|
"Marco IZ3GME",
|
|
"Jim KC9HI"
|
|
))
|
|
d.set_translator_credits("Polish: Grzegorz SQ2RBY" +
|
|
os.linesep +
|
|
"Italian: Fabio IZ2QDH" +
|
|
os.linesep +
|
|
"Dutch: Michael PD4MT" +
|
|
os.linesep +
|
|
"German: Benjamin HB9EUK" +
|
|
os.linesep +
|
|
"Hungarian: Attila HA5JA" +
|
|
os.linesep +
|
|
"Russian: Dmitry Slukin" +
|
|
os.linesep +
|
|
"Portuguese (BR): Crezivando PP7CJ")
|
|
d.set_comments(verinfo)
|
|
|
|
d.run()
|
|
d.destroy()
|
|
|
|
def do_gethelp(self):
|
|
webbrowser.open("http://chirp.danplanet.com")
|
|
|
|
def do_columns(self):
|
|
eset = self.get_current_editorset()
|
|
driver = directory.get_driver(eset.rthread.radio.__class__)
|
|
radio_name = "%s %s %s" % (eset.rthread.radio.VENDOR,
|
|
eset.rthread.radio.MODEL,
|
|
eset.rthread.radio.VARIANT)
|
|
d = gtk.Dialog(title=_("Select Columns"),
|
|
parent=self,
|
|
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
|
|
|
vbox = gtk.VBox()
|
|
vbox.show()
|
|
sw = gtk.ScrolledWindow()
|
|
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
|
sw.add_with_viewport(vbox)
|
|
sw.show()
|
|
d.vbox.pack_start(sw, 1, 1, 1)
|
|
d.set_size_request(-1, 300)
|
|
d.set_resizable(False)
|
|
|
|
labelstr = _("Visible columns for {radio}").format(radio=radio_name)
|
|
label = gtk.Label(labelstr)
|
|
label.show()
|
|
vbox.pack_start(label)
|
|
|
|
fields = []
|
|
memedit = eset.get_current_editor() # .editors["memedit"]
|
|
unsupported = memedit.get_unsupported_columns()
|
|
for colspec in memedit.cols:
|
|
if colspec[0].startswith("_"):
|
|
continue
|
|
elif colspec[0] in unsupported:
|
|
continue
|
|
label = colspec[0]
|
|
visible = memedit.get_column_visible(memedit.col(label))
|
|
widget = gtk.CheckButton(label)
|
|
widget.set_active(visible)
|
|
fields.append(widget)
|
|
vbox.pack_start(widget, 1, 1, 1)
|
|
widget.show()
|
|
|
|
res = d.run()
|
|
selected_columns = []
|
|
if res == gtk.RESPONSE_OK:
|
|
for widget in fields:
|
|
colnum = memedit.col(widget.get_label())
|
|
memedit.set_column_visible(colnum, widget.get_active())
|
|
if widget.get_active():
|
|
selected_columns.append(widget.get_label())
|
|
|
|
d.destroy()
|
|
|
|
CONF.set(driver, ",".join(selected_columns), "memedit_columns")
|
|
|
|
def do_hide_unused(self, action):
|
|
eset = self.get_current_editorset()
|
|
if eset is None:
|
|
conf = config.get("memedit")
|
|
conf.set_bool("hide_unused", action.get_active())
|
|
else:
|
|
for editortype, editor in eset.editors.iteritems():
|
|
if "memedit" in editortype:
|
|
editor.set_hide_unused(action.get_active())
|
|
|
|
def do_clearq(self):
|
|
eset = self.get_current_editorset()
|
|
eset.rthread.flush()
|
|
|
|
def do_copy(self, cut):
|
|
eset = self.get_current_editorset()
|
|
eset.get_current_editor().copy_selection(cut)
|
|
|
|
def do_paste(self):
|
|
eset = self.get_current_editorset()
|
|
eset.get_current_editor().paste_selection()
|
|
|
|
def do_delete(self):
|
|
eset = self.get_current_editorset()
|
|
eset.get_current_editor().copy_selection(True)
|
|
|
|
def do_toggle_report(self, action):
|
|
if not action.get_active():
|
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=self)
|
|
markup = "<b><big>" + _("Reporting is disabled") + "</big></b>"
|
|
d.set_markup(markup)
|
|
msg = _("The reporting feature of CHIRP is designed to help "
|
|
"<u>improve quality</u> by allowing the authors to focus "
|
|
"on the radio drivers used most often and errors "
|
|
"experienced by the users. The reports contain no "
|
|
"identifying information and are used only for "
|
|
"statistical purposes by the authors. Your privacy is "
|
|
"extremely important, but <u>please consider leaving "
|
|
"this feature enabled to help make CHIRP better!</u>\n\n"
|
|
"<b>Are you sure you want to disable this feature?</b>")
|
|
d.format_secondary_markup(msg.replace("\n", "\r\n"))
|
|
r = d.run()
|
|
d.destroy()
|
|
if r == gtk.RESPONSE_NO:
|
|
action.set_active(not action.get_active())
|
|
|
|
conf = config.get()
|
|
conf.set_bool("no_report", not action.get_active())
|
|
|
|
def do_toggle_no_smart_tmode(self, action):
|
|
CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit")
|
|
|
|
def do_toggle_developer(self, action):
|
|
conf = config.get()
|
|
conf.set_bool("developer", action.get_active(), "state")
|
|
|
|
for name in ["viewdeveloper", "loadmod"]:
|
|
devaction = self.menu_ag.get_action(name)
|
|
devaction.set_visible(action.get_active())
|
|
|
|
def do_toggle_clone_instructions(self, action):
|
|
CONF.set_bool("clone_instructions", not action.get_active(), "noconfirm")
|
|
|
|
def do_change_language(self):
|
|
langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
|
|
"Hungarian", "Russian", "Portuguese (BR)", "French", "Spanish"]
|
|
d = inputdialog.ChoiceDialog(langs, parent=self,
|
|
title="Choose Language")
|
|
d.label.set_text(_("Choose a language or Auto to use the "
|
|
"operating system default. You will need to "
|
|
"restart the application before the change "
|
|
"will take effect"))
|
|
d.label.set_line_wrap(True)
|
|
r = d.run()
|
|
if r == gtk.RESPONSE_OK:
|
|
LOG.debug("Chose language %s" % d.choice.get_active_text())
|
|
conf = config.get()
|
|
conf.set("language", d.choice.get_active_text(), "state")
|
|
d.destroy()
|
|
|
|
def load_module(self):
|
|
types = [(_("Python Modules") + "*.py", "*.py")]
|
|
filen = platform.get_platform().gui_open_file(types=types)
|
|
if not filen:
|
|
return
|
|
|
|
# We're in development mode, so we need to tell the directory to
|
|
# allow a loaded module to override an existing driver, against
|
|
# its normal better judgement
|
|
directory.enable_reregistrations()
|
|
|
|
try:
|
|
module = file(filen)
|
|
code = module.read()
|
|
module.close()
|
|
pyc = compile(code, filen, 'exec')
|
|
# See this for why:
|
|
# http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec
|
|
exec(pyc, globals(), globals())
|
|
except Exception, e:
|
|
common.log_exception()
|
|
common.show_error("Unable to load module: %s" % e)
|
|
|
|
def mh(self, _action, *args):
|
|
action = _action.get_name()
|
|
|
|
if action == "quit":
|
|
gtk.main_quit()
|
|
elif action == "new":
|
|
self.do_new()
|
|
elif action == "open":
|
|
self.do_open()
|
|
elif action == "save":
|
|
self.do_save()
|
|
elif action == "saveas":
|
|
self.do_saveas()
|
|
elif action.startswith("download"):
|
|
self.do_download(*args)
|
|
elif action.startswith("upload"):
|
|
self.do_upload(*args)
|
|
elif action == "close":
|
|
self.do_close()
|
|
elif action == "import":
|
|
self.do_import()
|
|
elif action in ["qrfinder", "irfinder"]:
|
|
self.do_rfinder(action[0] == "i")
|
|
elif action in ["qradioreference", "iradioreference"]:
|
|
self.do_radioreference(action[0] == "i")
|
|
elif action == "export":
|
|
self.do_export()
|
|
elif action in ["qrbook", "irbook"]:
|
|
self.do_repeaterbook(action[0] == "i")
|
|
elif action in ["qpr", "ipr"]:
|
|
self.do_przemienniki(action[0] == "i")
|
|
elif action == "about":
|
|
self.do_about()
|
|
elif action == "gethelp":
|
|
self.do_gethelp()
|
|
elif action == "columns":
|
|
self.do_columns()
|
|
elif action == "hide_unused":
|
|
self.do_hide_unused(_action)
|
|
elif action == "cancelq":
|
|
self.do_clearq()
|
|
elif action == "report":
|
|
self.do_toggle_report(_action)
|
|
elif action == "channel_defaults":
|
|
# The memedit thread also has an instance of bandplans.
|
|
bp = bandplans.BandPlans(CONF)
|
|
bp.select_bandplan(self)
|
|
elif action == "no_smart_tmode":
|
|
self.do_toggle_no_smart_tmode(_action)
|
|
elif action == "developer":
|
|
self.do_toggle_developer(_action)
|
|
elif action == "clone_instructions":
|
|
self.do_toggle_clone_instructions(_action)
|
|
elif action in ["cut", "copy", "paste", "delete",
|
|
"move_up", "move_dn", "exchange", "all",
|
|
"devshowraw", "devdiffraw", "properties"]:
|
|
self.get_current_editorset().get_current_editor().hotkey(_action)
|
|
elif action == "devdifftab":
|
|
self.do_diff_radio()
|
|
elif action == "language":
|
|
self.do_change_language()
|
|
elif action == "loadmod":
|
|
self.load_module()
|
|
else:
|
|
return
|
|
|
|
self.ev_tab_switched()
|
|
|
|
def make_menubar(self):
|
|
menu_xml = """
|
|
<ui>
|
|
<menubar name="MenuBar">
|
|
<menu action="file">
|
|
<menuitem action="new"/>
|
|
<menuitem action="open"/>
|
|
<menu action="openstock" name="openstock"/>
|
|
<menu action="recent" name="recent"/>
|
|
<menuitem action="save"/>
|
|
<menuitem action="saveas"/>
|
|
<menuitem action="loadmod"/>
|
|
<separator/>
|
|
<menuitem action="import"/>
|
|
<menuitem action="export"/>
|
|
<separator/>
|
|
<menuitem action="close"/>
|
|
<menuitem action="quit"/>
|
|
</menu>
|
|
<menu action="edit">
|
|
<menuitem action="cut"/>
|
|
<menuitem action="copy"/>
|
|
<menuitem action="paste"/>
|
|
<menuitem action="delete"/>
|
|
<separator/>
|
|
<menuitem action="all"/>
|
|
<separator/>
|
|
<menuitem action="move_up"/>
|
|
<menuitem action="move_dn"/>
|
|
<menuitem action="exchange"/>
|
|
<separator/>
|
|
<menuitem action="properties"/>
|
|
</menu>
|
|
<menu action="view">
|
|
<menuitem action="columns"/>
|
|
<menuitem action="hide_unused"/>
|
|
<menuitem action="no_smart_tmode"/>
|
|
<menu action="viewdeveloper">
|
|
<menuitem action="devshowraw"/>
|
|
<menuitem action="devdiffraw"/>
|
|
<menuitem action="devdifftab"/>
|
|
</menu>
|
|
<menuitem action="language"/>
|
|
</menu>
|
|
<menu action="radio" name="radio">
|
|
<menuitem action="download"/>
|
|
<menuitem action="upload"/>
|
|
<menu action="importsrc" name="importsrc">
|
|
<menuitem action="iradioreference"/>
|
|
<menuitem action="irbook"/>
|
|
<menuitem action="ipr"/>
|
|
<menuitem action="irfinder"/>
|
|
</menu>
|
|
<menu action="querysrc" name="querysrc">
|
|
<menuitem action="qradioreference"/>
|
|
<menuitem action="qrbook"/>
|
|
<menuitem action="qpr"/>
|
|
<menuitem action="qrfinder"/>
|
|
</menu>
|
|
<menu action="stock" name="stock"/>
|
|
<separator/>
|
|
<menuitem action="channel_defaults"/>
|
|
<separator/>
|
|
<menuitem action="cancelq"/>
|
|
</menu>
|
|
<menu action="help">
|
|
<menuitem action="gethelp"/>
|
|
<separator/>
|
|
<menuitem action="report"/>
|
|
<menuitem action="clone_instructions"/>
|
|
<menuitem action="developer"/>
|
|
<separator/>
|
|
<menuitem action="about"/>
|
|
</menu>
|
|
</menubar>
|
|
</ui>
|
|
"""
|
|
ALT_KEY = "<Alt>"
|
|
CTRL_KEY = "<Ctrl>"
|
|
if sys.platform == 'darwin':
|
|
ALT_KEY = "<Meta>"
|
|
CTRL_KEY = "<Meta>"
|
|
actions = [
|
|
('file', None, _("_File"), None, None, self.mh),
|
|
('new', gtk.STOCK_NEW, None, None, None, self.mh),
|
|
('open', gtk.STOCK_OPEN, None, None, None, self.mh),
|
|
('openstock', None, _("Open stock config"), None, None, self.mh),
|
|
('recent', None, _("_Recent"), None, None, self.mh),
|
|
('save', gtk.STOCK_SAVE, None, None, None, self.mh),
|
|
('saveas', gtk.STOCK_SAVE_AS, None, None, None, self.mh),
|
|
('loadmod', None, _("Load Module"), None, None, self.mh),
|
|
('close', gtk.STOCK_CLOSE, None, None, None, self.mh),
|
|
('quit', gtk.STOCK_QUIT, None, None, None, self.mh),
|
|
('edit', None, _("_Edit"), None, None, self.mh),
|
|
('cut', None, _("_Cut"), "%sx" % CTRL_KEY, None, self.mh),
|
|
('copy', None, _("_Copy"), "%sc" % CTRL_KEY, None, self.mh),
|
|
('paste', None, _("_Paste"),
|
|
"%sv" % CTRL_KEY, None, self.mh),
|
|
('delete', None, _("_Delete"), "Delete", None, self.mh),
|
|
('all', None, _("Select _All"), None, None, self.mh),
|
|
('move_up', None, _("Move _Up"),
|
|
"%sUp" % CTRL_KEY, None, self.mh),
|
|
('move_dn', None, _("Move Dow_n"),
|
|
"%sDown" % CTRL_KEY, None, self.mh),
|
|
('exchange', None, _("E_xchange"),
|
|
"%s<Shift>x" % CTRL_KEY, None, self.mh),
|
|
('properties', None, _("P_roperties"), None, None, self.mh),
|
|
('view', None, _("_View"), None, None, self.mh),
|
|
('columns', None, _("Columns"), None, None, self.mh),
|
|
('viewdeveloper', None, _("Developer"), None, None, self.mh),
|
|
('devshowraw', None, _('Show raw memory'),
|
|
"%s<Shift>r" % CTRL_KEY, None, self.mh),
|
|
('devdiffraw', None, _("Diff raw memories"),
|
|
"%s<Shift>d" % CTRL_KEY, None, self.mh),
|
|
('devdifftab', None, _("Diff tabs"),
|
|
"%s<Shift>t" % CTRL_KEY, None, self.mh),
|
|
('language', None, _("Change language"), None, None, self.mh),
|
|
('radio', None, _("_Radio"), None, None, self.mh),
|
|
('download', None, _("Download From Radio"),
|
|
"%sd" % ALT_KEY, None, self.mh),
|
|
('upload', None, _("Upload To Radio"),
|
|
"%su" % ALT_KEY, None, self.mh),
|
|
('import', None, _("Import"), "%si" % ALT_KEY, None, self.mh),
|
|
('export', None, _("Export"), "%sx" % ALT_KEY, None, self.mh),
|
|
('importsrc', None, _("Import from data source"),
|
|
None, None, self.mh),
|
|
('iradioreference', None, _("RadioReference.com"),
|
|
None, None, self.mh),
|
|
('irfinder', None, _("RFinder"), None, None, self.mh),
|
|
('irbook', None, _("RepeaterBook"), None, None, self.mh),
|
|
('ipr', None, _("przemienniki.net"), None, None, self.mh),
|
|
('querysrc', None, _("Query data source"), None, None, self.mh),
|
|
('qradioreference', None, _("RadioReference.com"),
|
|
None, None, self.mh),
|
|
('qrfinder', None, _("RFinder"), None, None, self.mh),
|
|
('qpr', None, _("przemienniki.net"), None, None, self.mh),
|
|
('qrbook', None, _("RepeaterBook"), None, None, self.mh),
|
|
('export_chirp', None, _("CHIRP Native File"),
|
|
None, None, self.mh),
|
|
('export_csv', None, _("CSV File"), None, None, self.mh),
|
|
('stock', None, _("Import from stock config"),
|
|
None, None, self.mh),
|
|
('channel_defaults', None, _("Channel defaults"),
|
|
None, None, self.mh),
|
|
('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
|
|
('help', None, _('Help'), None, None, self.mh),
|
|
('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
|
|
('gethelp', None, _("Get Help Online..."), None, None, self.mh),
|
|
]
|
|
|
|
conf = config.get()
|
|
re = not conf.get_bool("no_report")
|
|
hu = conf.get_bool("hide_unused", "memedit", default=True)
|
|
dv = conf.get_bool("developer", "state")
|
|
ci = not conf.get_bool("clone_instructions", "noconfirm")
|
|
st = not conf.get_bool("no_smart_tmode", "memedit")
|
|
|
|
toggles = [('report', None, _("Report Statistics"),
|
|
None, None, self.mh, re),
|
|
('hide_unused', None, _("Hide Unused Fields"),
|
|
None, None, self.mh, hu),
|
|
('no_smart_tmode', None, _("Smart Tone Modes"),
|
|
None, None, self.mh, st),
|
|
('clone_instructions', None, _("Show Instructions"),
|
|
None, None, self.mh, ci),
|
|
('developer', None, _("Enable Developer Functions"),
|
|
None, None, self.mh, dv),
|
|
]
|
|
|
|
self.menu_uim = gtk.UIManager()
|
|
self.menu_ag = gtk.ActionGroup("MenuBar")
|
|
self.menu_ag.add_actions(actions)
|
|
self.menu_ag.add_toggle_actions(toggles)
|
|
|
|
self.menu_uim.insert_action_group(self.menu_ag, 0)
|
|
self.menu_uim.add_ui_from_string(menu_xml)
|
|
|
|
self.add_accel_group(self.menu_uim.get_accel_group())
|
|
|
|
self.clonemenu = self.menu_uim.get_widget("/MenuBar/help/clone_instructions")
|
|
|
|
# Initialize
|
|
self.do_toggle_developer(self.menu_ag.get_action("developer"))
|
|
|
|
return self.menu_uim.get_widget("/MenuBar")
|
|
|
|
def make_tabs(self):
|
|
self.tabs = gtk.Notebook()
|
|
self.tabs.set_scrollable(True)
|
|
|
|
return self.tabs
|
|
|
|
def close_out(self):
|
|
num = self.tabs.get_n_pages()
|
|
while num > 0:
|
|
num -= 1
|
|
LOG.debug("Closing %i" % num)
|
|
try:
|
|
self.do_close(self.tabs.get_nth_page(num))
|
|
except ModifiedError:
|
|
return False
|
|
|
|
gtk.main_quit()
|
|
|
|
return True
|
|
|
|
def make_status_bar(self):
|
|
box = gtk.HBox(False, 2)
|
|
|
|
self.sb_general = gtk.Statusbar()
|
|
self.sb_general.set_has_resize_grip(False)
|
|
self.sb_general.show()
|
|
box.pack_start(self.sb_general, 1, 1, 1)
|
|
|
|
self.sb_radio = gtk.Statusbar()
|
|
self.sb_radio.set_has_resize_grip(True)
|
|
self.sb_radio.show()
|
|
box.pack_start(self.sb_radio, 1, 1, 1)
|
|
|
|
box.show()
|
|
return box
|
|
|
|
def ev_delete(self, window, event):
|
|
if not self.close_out():
|
|
return True # Don't exit
|
|
|
|
def ev_destroy(self, window):
|
|
if not self.close_out():
|
|
return True # Don't exit
|
|
|
|
def setup_extra_hotkeys(self):
|
|
accelg = self.menu_uim.get_accel_group()
|
|
|
|
def memedit(a):
|
|
self.get_current_editorset().editors["memedit"].hotkey(a)
|
|
|
|
actions = [
|
|
# ("action_name", "key", function)
|
|
]
|
|
|
|
for name, key, fn in actions:
|
|
a = gtk.Action(name, name, name, "")
|
|
a.connect("activate", fn)
|
|
self.menu_ag.add_action_with_accel(a, key)
|
|
a.set_accel_group(accelg)
|
|
a.connect_accelerator()
|
|
|
|
def _set_icon(self):
|
|
execpath = platform.get_platform().executable_path()
|
|
path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
|
|
if not os.path.exists(path):
|
|
path = "/usr/share/pixmaps/chirp.png"
|
|
|
|
if os.path.exists(path):
|
|
self.set_icon_from_file(path)
|
|
else:
|
|
LOG.warn("Icon %s not found" % path)
|
|
|
|
def _updates(self, version):
|
|
if not version:
|
|
return
|
|
|
|
if version == CHIRP_VERSION:
|
|
return
|
|
|
|
LOG.info("Server reports version %s is available" % version)
|
|
|
|
# Report new updates every seven days
|
|
intv = 3600 * 24 * 7
|
|
|
|
if CONF.is_defined("last_update_check", "state") and \
|
|
(time.time() - CONF.get_int("last_update_check", "state")) < intv:
|
|
return
|
|
|
|
CONF.set_int("last_update_check", int(time.time()), "state")
|
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
|
|
type=gtk.MESSAGE_INFO)
|
|
d.set_property("text",
|
|
_("A new version of CHIRP is available: " +
|
|
"{ver}. ".format(ver=version) +
|
|
"It is recommended that you upgrade, so " +
|
|
"go to http://chirp.danplanet.com soon!"))
|
|
d.run()
|
|
d.destroy()
|
|
|
|
def _init_macos(self, menu_bar):
|
|
try:
|
|
import gtk_osxapplication
|
|
macapp = gtk_osxapplication.OSXApplication()
|
|
except ImportError, e:
|
|
LOG.error("No MacOS support: %s" % e)
|
|
return
|
|
|
|
menu_bar.hide()
|
|
macapp.set_menu_bar(menu_bar)
|
|
|
|
quititem = self.menu_uim.get_widget("/MenuBar/file/quit")
|
|
quititem.hide()
|
|
|
|
aboutitem = self.menu_uim.get_widget("/MenuBar/help/about")
|
|
macapp.insert_app_menu_item(aboutitem, 0)
|
|
|
|
documentationitem = self.menu_uim.get_widget("/MenuBar/help/gethelp")
|
|
macapp.insert_app_menu_item(documentationitem, 0)
|
|
|
|
macapp.set_use_quartz_accelerators(False)
|
|
macapp.ready()
|
|
|
|
LOG.debug("Initialized MacOS support")
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
gtk.Window.__init__(self, *args, **kwargs)
|
|
|
|
def expose(window, event):
|
|
allocation = window.get_allocation()
|
|
CONF.set_int("window_w", allocation.width, "state")
|
|
CONF.set_int("window_h", allocation.height, "state")
|
|
self.connect("expose_event", expose)
|
|
|
|
def state_change(window, event):
|
|
CONF.set_bool(
|
|
"window_maximized",
|
|
event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED,
|
|
"state")
|
|
self.connect("window-state-event", state_change)
|
|
|
|
d = CONF.get("last_dir", "state")
|
|
if d and os.path.isdir(d):
|
|
platform.get_platform().set_last_dir(d)
|
|
|
|
vbox = gtk.VBox(False, 2)
|
|
|
|
self._recent = []
|
|
|
|
self.menu_ag = None
|
|
mbar = self.make_menubar()
|
|
|
|
if os.name != "nt":
|
|
self._set_icon() # Windows gets the icon from the exe
|
|
if os.uname()[0] == "Darwin":
|
|
self._init_macos(mbar)
|
|
|
|
vbox.pack_start(mbar, 0, 0, 0)
|
|
|
|
self.tabs = None
|
|
tabs = self.make_tabs()
|
|
tabs.connect("switch-page", lambda n, _, p: self.ev_tab_switched(p))
|
|
tabs.connect("page-removed", lambda *a: self.ev_tab_switched())
|
|
tabs.show()
|
|
self.ev_tab_switched()
|
|
vbox.pack_start(tabs, 1, 1, 1)
|
|
|
|
vbox.pack_start(self.make_status_bar(), 0, 0, 0)
|
|
|
|
vbox.show()
|
|
|
|
self.add(vbox)
|
|
|
|
try:
|
|
width = CONF.get_int("window_w", "state")
|
|
height = CONF.get_int("window_h", "state")
|
|
except Exception:
|
|
width = 800
|
|
height = 600
|
|
|
|
self.set_default_size(width, height)
|
|
if CONF.get_bool("window_maximized", "state"):
|
|
self.maximize()
|
|
self.set_title("CHIRP")
|
|
|
|
self.connect("delete_event", self.ev_delete)
|
|
self.connect("destroy", self.ev_destroy)
|
|
|
|
if not CONF.get_bool("warned_about_reporting") and \
|
|
not CONF.get_bool("no_report"):
|
|
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self)
|
|
d.set_markup("<b><big>" +
|
|
_("Error reporting is enabled") +
|
|
"</big></b>")
|
|
d.format_secondary_markup(
|
|
_("If you wish to disable this feature you may do so in "
|
|
"the <u>Help</u> menu"))
|
|
d.run()
|
|
d.destroy()
|
|
CONF.set_bool("warned_about_reporting", True)
|
|
|
|
self.update_recent_files()
|
|
self.update_stock_configs()
|
|
self.setup_extra_hotkeys()
|
|
|
|
def updates_callback(ver):
|
|
gobject.idle_add(self._updates, ver)
|
|
|
|
if not CONF.get_bool("skip_update_check", "state"):
|
|
reporting.check_for_updates(updates_callback)
|