diff --git a/chirp/ui/__init__.py b/chirp/ui/__init__.py deleted file mode 100644 index a2a6f352..00000000 --- a/chirp/ui/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . diff --git a/chirp/ui/__main__.py b/chirp/ui/__main__.py deleted file mode 100755 index c9279f04..00000000 --- a/chirp/ui/__main__.py +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2014 Dan Smith -# -# 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 . - -def main(): - import glob - import os - import six - - import sys - import os - import locale - import gettext - import argparse - import logging - import urllib - - if six.PY3: - from gi import pygtkcompat - pygtkcompat.enable() - pygtkcompat.enable_gtk(version='3.0') - - from chirp import chirp_common - from chirp import directory - from chirp import logger - from chirp import elib_intl - from chirp import platform - from chirp.ui import config - - directory.import_drivers() - - LOG = logging.getLogger("chirpw") - - # Does not work with python3 chirpw - # urllib.URLopener.version = chirp_common.http_user_agent() - - localepath = platform.get_platform().find_resource("locale") - - conf = config.get() - manual_language = conf.get("language", "state") - langs = [] - if manual_language and manual_language != "Auto": - lang_codes = {"English": "en_US", - "Polish": "pl", - "Italian": "it", - "Dutch": "nl", - "German": "de", - "Hungarian": "hu", - "Russian": "ru", - "Portuguese (BR)": "pt_BR", - "French": "fr", - "Spanish": "es_ES", - "Chinese (Simplified)": "zh_CN", - 'Ukrainian': "uk_UA", - 'Turkish': "tr_TR", - } - try: - LOG.info("Language: %s", lang_codes[manual_language]) - langs = [lang_codes[manual_language]] - except KeyError: - LOG.error("Unsupported language `%s'" % manual_language) - else: - lc, encoding = locale.getdefaultlocale() - if (lc): - langs = [lc] - try: - langs += os.getenv("LANG").split(":") - except: - pass - - try: - if os.name == "nt": - elib_intl._putenv("LANG", langs[0]) - else: - os.putenv("LANG", langs[0]) - except IndexError: - pass - path = "locale" - gettext.bindtextdomain("CHIRP", localepath) - gettext.textdomain("CHIRP") - lang = gettext.translation("CHIRP", localepath, languages=langs, - fallback=True) - - - # Python <2.6 does not have str.format(), which chirp uses to make translation - # strings nicer. So, instead of installing the gettext standard "_()" function, - # we can install our own, which returns a string of the following class, - # which emulates the new behavior, thus allowing us to run on older Python - # versions. - class CompatStr(str): - def format(self, **kwargs): - base = lang.gettext(self) - for k, v in kwargs.items(): - base = base.replace("{%s}" % k, str(v)) - return base - - pyver = sys.version.split()[0] - - try: - vmaj, vmin, vrel = pyver.split(".", 3) - except: - vmaj, vmin = pyver.split(".", 2) - vrel = 0 - - if int(vmaj) == 2 and int(vmin) < 6: - # Python <2.6, emulate str.format() - import __builtin__ - - def lang_with_format(string): - return CompatStr(string) - __builtin__._ = lang_with_format - else: - # Python >=2.6, use normal gettext behavior - lang.install() - - parser = argparse.ArgumentParser() - parser.add_argument("files", metavar="file", nargs='*', help="File to open") - parser.add_argument("--module", metavar="module", - help="Load module on startup") - logger.add_version_argument(parser) - parser.add_argument("--profile", action="store_true", - help="Enable profiling") - logger.add_arguments(parser) - args = parser.parse_args() - - logger.handle_options(args) - - a = None - if True: - from chirp.ui import mainapp - a = mainapp.ChirpMain() - - # Be sure to load module before opening files - if args.module: - a.load_module(args.module) - - for i in args.files: - LOG.info("Opening %s", i) - a.do_open(i) - - a.show() - - if args.profile: - import cProfile - import pstats - import gtk - cProfile.run("gtk.main()", "chirpw.stats") - p = pstats.Stats("chirpw.stats") - p.sort_stats("cumulative").print_stats(10) - else: - import gtk - gtk.main() - - if config._CONFIG: - config._CONFIG.set("last_dir", - platform.get_platform().get_last_dir(), - "state") - config._CONFIG.save() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/chirp/ui/bandplans.py b/chirp/ui/bandplans.py deleted file mode 100644 index f5d204c1..00000000 --- a/chirp/ui/bandplans.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2013 Sean Burford -# -# 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 . - -import gtk -import logging -from chirp import bandplan, bandplan_na, bandplan_au -from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3 -from chirp.ui import inputdialog - -LOG = logging.getLogger(__name__) - - -def select_bandplan(bandplans, parent_window): - plans = ["None"] - for shortname, details in bandplans.plans.iteritems(): - if bandplans._config.get_bool(shortname, "bandplan"): - plans.insert(0, details[0]) - else: - plans.append(details[0]) - - d = inputdialog.ChoiceDialog(plans, parent=parent_window, - title="Choose Defaults") - d.label.set_text(_("Band plans define default channel settings for " - "frequencies in a region. Choose a band plan " - "or None for completely manual channel " - "settings.")) - d.label.set_line_wrap(True) - r = d.run() - - if r == gtk.RESPONSE_OK: - selection = d.choice.get_active_text() - for shortname, details in bandplans.plans.iteritems(): - bandplans._config.set_bool(shortname, selection == details[0], - "bandplan") - if selection == details[0]: - LOG.info("Selected band plan %s: %s" % - (shortname, selection)) - - d.destroy() diff --git a/chirp/ui/bankedit.py b/chirp/ui/bankedit.py deleted file mode 100644 index 339a42cc..00000000 --- a/chirp/ui/bankedit.py +++ /dev/null @@ -1,419 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import gobject -import time -import logging - -from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN - -from chirp import chirp_common -from chirp.ui import common, miscwidgets - -LOG = logging.getLogger(__name__) - - -class MappingNamesJob(common.RadioJob): - def __init__(self, model, editor, cb): - common.RadioJob.__init__(self, cb, None) - self.__model = model - self.__editor = editor - - def execute(self, radio): - self.__editor.mappings = [] - - mappings = self.__model.get_mappings() - for mapping in mappings: - self.__editor.mappings.append((mapping, mapping.get_name())) - - gobject.idle_add(self.cb, *self.cb_args) - - -class MappingNameEditor(common.Editor): - def refresh(self): - def got_mappings(): - self._keys = [] - for mapping, name in self.mappings: - self._keys.append(mapping.get_index()) - self.listw.set_item(mapping.get_index(), - mapping.get_index(), - name) - - self.listw.connect("item-set", self.mapping_changed) - - job = MappingNamesJob(self._model, self, got_mappings) - job.set_desc(_("Retrieving %s information") % self._type) - self.rthread.submit(job) - - def get_mapping_list(self): - mappings = [] - keys = self.listw.get_keys() - for key in keys: - mappings.append(self.listw.get_item(key)[2]) - - return mappings - - def mapping_changed(self, listw, key): - def cb(*args): - self.emit("changed") - - name = self.listw.get_item(key)[2] - mapping, oldname = self.mappings[self._keys.index(key)] - - def trigger_changed(*args): - self.emit("changed") - - job = common.RadioJob(trigger_changed, "set_name", name) - job.set_target(mapping) - job.set_desc(_("Setting name on %s") % self._type.lower()) - self.rthread.submit(job) - - return True - - def __init__(self, rthread, model): - super(MappingNameEditor, self).__init__(rthread) - self._model = model - self._type = common.unpluralize(model.get_name()) - - types = [(gobject.TYPE_STRING, "key"), - (gobject.TYPE_STRING, self._type), - (gobject.TYPE_STRING, _("Name"))] - - self.listw = miscwidgets.KeyedListWidget(types) - self.listw.set_editable(1, True) - self.listw.set_sort_column(0, 1) - self.listw.set_sort_column(1, -1) - self.listw.show() - - self.mappings = [] - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add_with_viewport(self.listw) - - self.root = sw - self._loaded = False - - def focus(self): - if self._loaded: - return - - self.refresh() - self._loaded = True - - def other_editor_changed(self, target_editor): - self._loaded = False - if self.is_focused(): - self.refresh_all_memories() - - def mappings_changed(self): - pass - - -class MemoryMappingsJob(common.RadioJob): - def __init__(self, model, cb, number): - common.RadioJob.__init__(self, cb, None) - self.__model = model - self.__number = number - - def execute(self, radio): - mem = radio.get_memory(self.__number) - if mem.empty: - mappings = [] - indexes = [] - else: - mappings = self.__model.get_memory_mappings(mem) - indexes = [] - if isinstance(self.__model, - chirp_common.MappingModelIndexInterface): - for mapping in mappings: - indexes.append(self.__model.get_memory_index(mem, mapping)) - self.cb(mem, mappings, indexes, *self.cb_args) - - -class MappingMembershipEditor(common.Editor): - def _number_to_path(self, number): - return (number - self._rf.memory_bounds[0],) - - def _get_next_mapping_index(self, mapping): - # NB: Only works for one-to-one models right now! - iter = self._store.get_iter_first() - indexes = [] - ncols = len(self._cols) + len(self.mappings) - while iter: - vals = self._store.get(iter, *tuple([n for n in range(0, ncols)])) - loc = vals[self.C_LOC] - index = vals[self.C_INDEX] - mappings = vals[self.C_MAPPINGS:] - if True in mappings and mappings.index(True) == mapping: - indexes.append(index) - iter = self._store.iter_next(iter) - - index_bounds = self._model.get_index_bounds() - num_indexes = index_bounds[1] - index_bounds[0] - indexes.sort() - for i in range(0, num_indexes): - if i not in indexes: - return i + index_bounds[0] # In case not zero-origin index - - return 0 # If the mapping is full, just wrap around! - - def _toggled_cb(self, rend, path, colnum): - try: - if not rend.get_sensitive(): - return - except AttributeError: - # support PyGTK < 2.22 - iter = self._store.get_iter(path) - if not self._store.get(iter, self.C_FILLED)[0]: - return - - # The mapping index is the column number, minus the 3 label columns - mapping, name = self.mappings[colnum - len(self._cols)] - loc, = self._store.get(self._store.get_iter(path), self.C_LOC) - - is_indexed = isinstance(self._model, - chirp_common.MappingModelIndexInterface) - - if rend.get_active(): - # Changing from True to False - fn = "remove_memory_from_mapping" - index = None - else: - # Changing from False to True - fn = "add_memory_to_mapping" - if is_indexed: - index = self._get_next_mapping_index(colnum - len(self._cols)) - else: - index = None - - def do_refresh_memory(*args): - # Step 2: Update our notion of the memory's mapping information - self.refresh_memory(loc) - - def do_mapping_index(result, memory): - if isinstance(result, Exception): - common.show_error("Failed to add {mem} to mapping: {err}" - .format(mem=memory.number, - err=str(result)), - parent=self.editorset.parent_window) - return - self.emit("changed") - # Step 3: Set the memory's mapping index (maybe) - if not is_indexed or index is None: - return do_refresh_memory() - - job = common.RadioJob(do_refresh_memory, - "set_memory_index", memory, mapping, index) - job.set_target(self._model) - job.set_desc(_("Updating {type} index " - "for memory {num}").format(type=self._type, - num=memory.number)) - self.rthread.submit(job) - - def do_mapping_adjustment(memory): - # Step 1: Do the mapping add/remove - job = common.RadioJob(do_mapping_index, fn, memory, mapping) - job.set_target(self._model) - job.set_cb_args(memory) - job.set_desc(_("Updating mapping information " - "for memory {num}").format(num=memory.number)) - self.rthread.submit(job) - - # Step 0: Fetch the memory - job = common.RadioJob(do_mapping_adjustment, "get_memory", loc) - job.set_desc(_("Getting memory {num}").format(num=loc)) - self.rthread.submit(job) - - def _index_edited_cb(self, rend, path, new): - loc, = self._store.get(self._store.get_iter(path), self.C_LOC) - - def refresh_memory(*args): - self.refresh_memory(loc) - - def set_index(mappings, memory): - self.emit("changed") - # Step 2: Set the index - job = common.RadioJob(refresh_memory, "set_memory_index", - memory, mappings[0], int(new)) - job.set_target(self._model) - job.set_desc(_("Setting index " - "for memory {num}").format(num=memory.number)) - self.rthread.submit(job) - - def get_mapping(memory): - # Step 1: Get the first/only mapping - job = common.RadioJob(set_index, "get_memory_mappings", memory) - job.set_cb_args(memory) - job.set_target(self._model) - job.set_desc(_("Getting {type} for " - "memory {num}").format(type=self._type, - num=memory.number)) - self.rthread.submit(job) - - # Step 0: Get the memory - job = common.RadioJob(get_mapping, "get_memory", loc) - job.set_desc(_("Getting memory {num}").format(num=loc)) - self.rthread.submit(job) - - def __init__(self, rthread, editorset, model): - super(MappingMembershipEditor, self).__init__(rthread) - - self.editorset = editorset - self._rf = rthread.radio.get_features() - self._model = model - self._type = common.unpluralize(model.get_name()) - - self._view_cols = [ - (_("Loc"), TYPE_INT, gtk.CellRendererText, ), - (_("Frequency"), TYPE_STRING, gtk.CellRendererText, ), - (_("Name"), TYPE_STRING, gtk.CellRendererText, ), - (_("Index"), TYPE_INT, gtk.CellRendererText, ), - ] - - self._cols = [ - ("_filled", TYPE_BOOLEAN, None, ), - ] + self._view_cols - - self.C_FILLED = 0 - self.C_LOC = 1 - self.C_FREQ = 2 - self.C_NAME = 3 - self.C_INDEX = 4 - self.C_MAPPINGS = 5 # and beyond - - cols = list(self._cols) - - self._index_cache = [] - - for i in range(0, self._model.get_num_mappings()): - label = "%s %i" % (self._type, (i+1)) - cols.append((label, TYPE_BOOLEAN, gtk.CellRendererToggle)) - - self._store = gtk.ListStore(*tuple([y for x, y, z in cols])) - self._view = gtk.TreeView(self._store) - - is_indexed = isinstance(self._model, - chirp_common.MappingModelIndexInterface) - - colnum = 0 - for label, dtype, rtype in cols: - if not rtype: - colnum += 1 - continue - rend = rtype() - if dtype == TYPE_BOOLEAN: - rend.set_property("activatable", True) - rend.connect("toggled", self._toggled_cb, colnum) - col = gtk.TreeViewColumn(label, rend, active=colnum, - sensitive=self.C_FILLED) - else: - col = gtk.TreeViewColumn(label, rend, text=colnum, - sensitive=self.C_FILLED) - - self._view.append_column(col) - col.set_resizable(True) - if colnum == self.C_NAME: - col.set_visible(self._rf.has_name) - elif colnum == self.C_INDEX: - rend.set_property("editable", True) - rend.connect("edited", self._index_edited_cb) - col.set_visible(is_indexed) - colnum += 1 - - # A non-rendered column to absorb extra space in the row - self._view.append_column(gtk.TreeViewColumn()) - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(self._view) - self._view.show() - - (min, max) = self._rf.memory_bounds - for i in range(min, max+1): - iter = self._store.append() - self._store.set(iter, - self.C_FILLED, False, - self.C_LOC, i, - self.C_FREQ, "", - self.C_NAME, "", - self.C_INDEX, 0) - - self.root = sw - self._loaded = False - - def refresh_memory(self, number): - def got_mem(memory, mappings, indexes): - iter = self._store.get_iter(self._number_to_path(memory.number)) - row = [self.C_FILLED, not memory.empty, - self.C_LOC, memory.number, - self.C_FREQ, chirp_common.format_freq(memory.freq), - self.C_NAME, memory.name, - # Hack for only one index right now - self.C_INDEX, indexes and indexes[0] or 0, - ] - for i in range(0, len(self.mappings)): - row.append(i + len(self._cols)) - row.append(self.mappings[i][0] in mappings) - - self._store.set(iter, *tuple(row)) - - job = MemoryMappingsJob(self._model, got_mem, number) - job.set_desc(_("Getting {type} information " - "for memory {num}").format(type=self._type, num=number)) - self.rthread.submit(job) - - def refresh_all_memories(self): - start = time.time() - (min, max) = self._rf.memory_bounds - for i in range(min, max+1): - self.refresh_memory(i) - LOG.debug("Got all %s info in %s" % - (self._type, (time.time() - start))) - - def refresh_mappings(self, and_memories=False): - def got_mappings(): - for i in range(len(self._cols) - len(self._view_cols) - 1, - len(self.mappings)): - col = self._view.get_column(i + len(self._view_cols)) - mapping, name = self.mappings[i] - if name: - col.set_title(name) - else: - col.set_title("(%s)" % i) - if and_memories: - self.refresh_all_memories() - - job = MappingNamesJob(self._model, self, got_mappings) - job.set_desc(_("Getting %s information") % self._type) - self.rthread.submit(job) - - def focus(self): - common.Editor.focus(self) - if self._loaded: - return - - self.refresh_mappings(True) - - self._loaded = True - - def other_editor_changed(self, target_editor): - self._loaded = False - if self.is_focused(): - self.refresh_all_memories() - - def mappings_changed(self): - self.refresh_mappings() diff --git a/chirp/ui/clone.py b/chirp/ui/clone.py deleted file mode 100644 index aea00c28..00000000 --- a/chirp/ui/clone.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import collections -import threading -import logging -import os - -import gtk -import gobject - -from chirp import platform, directory, detect, chirp_common -from chirp.ui import miscwidgets, cloneprog, inputdialog, common, config - -LOG = logging.getLogger(__name__) - -AUTO_DETECT_STRING = "Auto Detect (Icom Only)" - - -class CloneSettings: - def __init__(self): - self.port = None - self.radio_class = None - - def __str__(self): - s = "" - if self.radio_class: - return _("{vendor} {model} on {port}").format( - vendor=self.radio_class.VENDOR, - model=self.radio_class.MODEL, - port=self.port) - - -class CloneSettingsDialog(gtk.Dialog): - def __make_field(self, label, widget): - l = gtk.Label(label + ":") - l.set_alignment(1.0, 0.5) - self.__table.attach(l, 0, 1, self.__row, self.__row+1) - self.__table.attach(widget, 1, 2, self.__row, self.__row+1) - self.__row += 1 - - l.show() - widget.show() - - def __make_port(self, port): - conf = config.get("state") - - ports = platform.get_platform().list_serial_ports() - if not port: - if conf.get("last_port"): - port = conf.get("last_port") - elif ports: - port = ports[0] - else: - port = "" - if port not in ports: - ports.insert(0, port) - - return miscwidgets.make_choice(ports, True, port) - - def __make_model(self): - return miscwidgets.make_choice([], False) - - def __make_vendor(self, modelbox): - vendors = collections.defaultdict(list) - for name, rclass in sorted(directory.DRV_TO_RADIO.items()): - if not issubclass(rclass, chirp_common.CloneModeRadio) and \ - not issubclass(rclass, chirp_common.LiveRadio): - continue - - vendors[rclass.VENDOR].append(rclass) - for alias in rclass.ALIASES: - vendors[alias.VENDOR].append(alias) - - self.__vendors = vendors - - conf = config.get("state") - if not conf.get("last_vendor"): - conf.set("last_vendor", sorted(vendors.keys())[0]) - - last_vendor = conf.get("last_vendor") - if last_vendor not in list(vendors.keys()): - last_vendor = list(vendors.keys())[0] - - v = miscwidgets.make_choice(sorted(vendors.keys()), False, last_vendor) - - def _changed(box, vendors, boxes): - (vendorbox, modelbox) = boxes - models = vendors[vendorbox.value] - - added_models = [] - - model_store = modelbox.get_model() - model_store.clear() - for rclass in sorted(models, key=lambda c: c.MODEL): - if rclass.MODEL not in added_models: - model_store.append([rclass.MODEL]) - added_models.append(rclass.MODEL) - - if vendorbox.value in detect.DETECT_FUNCTIONS: - model_store.append([_("Detect")]) - added_models.insert(0, _("Detect")) - - model_names = [x.MODEL for x in models] - if conf.get("last_model") in model_names: - modelbox.value = conf.get("last_model") - elif added_models: - modelbox.value = added_models[0] - - v.widget.connect("changed", _changed, vendors, (v, modelbox)) - _changed(v, vendors, (v, modelbox)) - - return v - - def __make_ui(self, settings): - self.__table = gtk.Table(3, 2) - self.__table.set_row_spacings(3) - self.__table.set_col_spacings(10) - self.__row = 0 - - self.__port = self.__make_port(settings and settings.port or None) - self.__modl = self.__make_model() - self.__vend = self.__make_vendor(self.__modl) - - self.__make_field(_("Port"), self.__port.widget) - self.__make_field(_("Vendor"), self.__vend.widget) - self.__make_field(_("Model"), self.__modl.widget) - - if settings and settings.radio_class: - self.__vend.value = settings.radio_class.VENDOR - self.__modl.value = settings.radio_class.MODEL - self.__vend.widget.set_sensitive(False) - self.__modl.widget.set_sensitive(False) - - self.__table.show() - self.vbox.pack_start(self.__table, 1, 1, 1) - - def __init__(self, settings=None, parent=None, title=_("Radio")): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, title, - parent=parent, - buttons=buttons, - flags=gtk.DIALOG_MODAL) - self.set_default_response(gtk.RESPONSE_OK) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - self.__make_ui(settings) - - def run(self): - r = gtk.Dialog.run(self) - if r != gtk.RESPONSE_OK: - return None - - vendor = self.__vend.value - model = self.__modl.value - - cs = CloneSettings() - cs.port = self.__port.value - if model == _("Detect"): - try: - cs.radio_class = detect.DETECT_FUNCTIONS[vendor](cs.port) - if not cs.radio_class: - raise Exception( - _("Unable to detect radio on {port}").format( - port=cs.port)) - except Exception as e: - d = inputdialog.ExceptionDialog(e) - d.run() - d.destroy() - return None - else: - for rclass in list(directory.DRV_TO_RADIO.values()): - if rclass.MODEL == model: - cs.radio_class = rclass - break - alias_match = None - for alias in rclass.ALIASES: - if alias.MODEL == model: - alias_match = rclass - alias_class = alias - break - if alias_match: - - class DynamicRadioAlias(rclass): - VENDOR = alias.VENDOR - MODEL = alias.MODEL - VARIANT = alias.VARIANT - - cs.radio_class = DynamicRadioAlias - LOG.debug( - 'Chose %s alias for %s because model %s selected' % ( - alias_match, cs.radio_class, model)) - break - if not cs.radio_class: - common.show_error( - _("Internal error: Unable to upload to {model}").format( - model=model)) - LOG.info(self.__vendors) - return None - - conf = config.get("state") - conf.set("last_port", cs.port) - conf.set("last_vendor", cs.radio_class.VENDOR) - conf.set("last_model", model) - - return cs - - -class CloneCancelledException(Exception): - pass - - -class CloneThread(threading.Thread): - def __status(self, status): - gobject.idle_add(self.__progw.status, status) - - def __init__(self, radio, direction, cb=None, parent=None): - threading.Thread.__init__(self) - - self.__radio = radio - self.__out = direction == "out" - self.__cback = cb - self.__cancelled = False - - self.__progw = cloneprog.CloneProg(parent=parent, cancel=self.cancel) - - def cancel(self): - self.__radio.pipe.close() - self.__cancelled = True - - def run(self): - LOG.debug("Clone thread started") - - gobject.idle_add(self.__progw.show) - - self.__radio.status_fn = self.__status - - try: - if self.__out: - self.__radio.sync_out() - else: - self.__radio.sync_in() - - emsg = None - except Exception as e: - common.log_exception() - LOG.error("Clone failed: {error}".format(error=e)) - emsg = e - - gobject.idle_add(self.__progw.hide) - - # NB: Compulsory close of the radio's serial connection - self.__radio.pipe.close() - - LOG.debug("Clone thread ended") - - if self.__cback and not self.__cancelled: - gobject.idle_add(self.__cback, self.__radio, emsg) - - -if __name__ == "__main__": - d = CloneSettingsDialog("/dev/ttyUSB0") - r = d.run() - print(r) diff --git a/chirp/ui/cloneprog.py b/chirp/ui/cloneprog.py deleted file mode 100644 index 99beea95..00000000 --- a/chirp/ui/cloneprog.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk - - -class CloneProg(gtk.Window): - def __init__(self, **args): - if "parent" in args: - parent = args["parent"] - del args["parent"] - else: - parent = None - - if "cancel" in args: - cancel = args["cancel"] - del args["cancel"] - else: - cancel = None - - gtk.Window.__init__(self, **args) - - self.set_transient_for(parent) - self.set_modal(True) - self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - self.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - - vbox = gtk.VBox(False, 2) - vbox.show() - self.add(vbox) - - self.set_title(_("Clone Progress")) - self.set_resizable(False) - - self.infolabel = gtk.Label(_("Cloning")) - self.infolabel.show() - vbox.pack_start(self.infolabel, 1, 1, 1) - - self.progbar = gtk.ProgressBar() - self.progbar.set_fraction(0.0) - self.progbar.show() - vbox.pack_start(self.progbar, 0, 0, 0) - - cancel_b = gtk.Button(_("Cancel")) - cancel_b.connect("clicked", lambda b: cancel()) - cancel_b.show() - vbox.pack_start(cancel_b, 0, 0, 0) - - def status(self, _status): - self.infolabel.set_text(_status.msg) - - if _status.cur > _status.max: - _status.cur = _status.max - self.progbar.set_fraction(_status.cur / float(_status.max)) diff --git a/chirp/ui/common.py b/chirp/ui/common.py deleted file mode 100644 index a6b90ee9..00000000 --- a/chirp/ui/common.py +++ /dev/null @@ -1,451 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import gobject -import pango - -import threading -import time -import os -import traceback -import logging - -from chirp import errors -from chirp.ui import reporting, config - -LOG = logging.getLogger(__name__) - -CONF = config.get() - - -class Editor(gobject.GObject): - __gsignals__ = { - 'changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'usermsg': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - } - - root = None - - def __init__(self, rthread): - gobject.GObject.__init__(self) - self.read_only = False - self._focused = False - self.rthread = rthread - - def is_focused(self): - return self._focused - - def focus(self): - self._focused = True - - def unfocus(self): - self._focused = False - - def copy_selection(self, cut=False): - pass - - def paste_selection(self): - pass - - def hotkey(self, action): - pass - - def set_read_only(self, read_only): - self.read_only = read_only - - def get_read_only(self): - return self.read_only - - def prepare_close(self): - pass - - def other_editor_changed(self, editor): - pass - -gobject.type_register(Editor) - - -def DBG(*args): - if False: - LOG.debug(" ".join(args)) - - -class RadioJob: - def __init__(self, cb, func, *args, **kwargs): - self.cb = cb - self.cb_args = () - self.func = func - self.args = args - self.kwargs = kwargs - self.desc = "Working" - self.target = None - self.tb = traceback.format_stack() - - def __str__(self): - return "RadioJob(%s,%s,%s)" % (self.func, self.args, self.kwargs) - - def set_desc(self, desc): - self.desc = desc - - def set_cb_args(self, *args): - self.cb_args = args - - def set_target(self, target): - self.target = target - - def _execute(self, target, func): - try: - DBG("Running %s (%s %s)" % (self.func, - str(self.args), - str(self.kwargs))) - DBG(self.desc) - result = func(*self.args, **self.kwargs) - except errors.InvalidMemoryLocation as e: - result = e - except Exception as e: - LOG.error("Exception running RadioJob: %s" % e) - log_exception() - LOG.error("Job Args: %s" % str(self.args)) - LOG.error("Job KWArgs: %s" % str(self.kwargs)) - LOG.error("Job Called from:%s%s" % - (os.linesep, "".join(self.tb[:-1]))) - result = e - - if self.cb: - gobject.idle_add(self.cb, result, *self.cb_args) - - def execute(self, radio): - if not self.target: - self.target = radio - - try: - func = getattr(self.target, self.func) - except AttributeError as e: - LOG.error("No such radio function `%s' in %s" % - (self.func, self.target)) - return - - self._execute(self.target, func) - - -class RadioThread(threading.Thread, gobject.GObject): - __gsignals__ = { - "status": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - } - - def __init__(self, radio, parent=None): - threading.Thread.__init__(self) - gobject.GObject.__init__(self) - self.__queue = {} - if parent: - self.__runlock = parent._get_run_lock() - self.status = lambda msg: parent.status(msg) - else: - self.__runlock = threading.Lock() - self.status = self._status - - self.__counter = threading.Semaphore(0) - self.__lock = threading.Lock() - - self.__enabled = True - self.radio = radio - - def _get_run_lock(self): - return self.__runlock - - def _qlock(self): - self.__lock.acquire() - - def _qunlock(self): - self.__lock.release() - - def _qsubmit(self, job, priority): - if priority not in self.__queue: - self.__queue[priority] = [] - - self.__queue[priority].append(job) - self.__counter.release() - - def _queue_clear_below(self, priority): - for i in range(0, priority): - if i in self.__queue and len(self.__queue[i]) != 0: - return False - - return True - - def _qlock_when_idle(self, priority=10): - while True: - DBG("Attempting queue lock (%i)" % len(self.__queue)) - self._qlock() - if self._queue_clear_below(priority): - return - self._qunlock() - time.sleep(0.1) - - # This is the external lock, which stops any threads from running - # so that the radio can be operated synchronously - def lock(self): - self.__runlock.acquire() - - def unlock(self): - self.__runlock.release() - - def submit(self, job, priority=0): - self._qlock() - self._qsubmit(job, priority) - self._qunlock() - - def flush(self, priority=None): - self._qlock() - - if priority is None: - for i in self.__queue.keys(): - self.__queue[i] = [] - else: - self.__queue[priority] = [] - - self._qunlock() - - def stop(self): - self.flush() - self.__counter.release() - self.__enabled = False - - def _status(self, msg): - jobs = 0 - for i in dict(self.__queue): - jobs += len(self.__queue[i]) - gobject.idle_add(self.emit, "status", "[%i] %s" % (jobs, msg)) - - def _queue_pop(self, priority): - try: - return self.__queue[priority].pop(0) - except IndexError: - return None - - def run(self): - last_job_desc = "idle" - while self.__enabled: - DBG("Waiting for a job") - if last_job_desc: - self.status(_("Completed") + " " + last_job_desc + - " (" + _("idle") + ")") - self.__counter.acquire() - - self._qlock() - for i in sorted(self.__queue.keys()): - job = self._queue_pop(i) - if job: - DBG("Running job at priority %i" % i) - break - self._qunlock() - - if job: - self.lock() - self.status(job.desc) - job.execute(self.radio) - last_job_desc = job.desc - self.unlock() - - LOG.debug("RadioThread exiting") - - -def log_exception(): - import traceback - import sys - - reporting.report_exception(traceback.format_exc(limit=30)) - - LOG.error("-- Exception: --") - LOG.error(traceback.format_exc(limit=30)) - LOG.error("----------------") - - -def show_error(msg, parent=None): - d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent, - type=gtk.MESSAGE_ERROR) - d.set_property("text", msg) - - if not parent: - d.set_position(gtk.WIN_POS_CENTER_ALWAYS) - - d.run() - d.destroy() - - -def ask_yesno_question(msg, parent=None): - d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=parent, - type=gtk.MESSAGE_QUESTION) - d.set_property("text", msg) - - if not parent: - d.set_position(gtk.WIN_POS_CENTER_ALWAYS) - - r = d.run() - d.destroy() - - return r == gtk.RESPONSE_YES - - -def combo_select(box, value): - store = box.get_model() - iter = store.get_iter_first() - while iter: - if store.get(iter, 0)[0] == value: - box.set_active_iter(iter) - return True - iter = store.iter_next(iter) - - return False - - -def _add_text(d, text): - v = gtk.TextView() - v.get_buffer().set_text(text) - v.set_editable(False) - v.set_cursor_visible(False) - v.show() - sw = gtk.ScrolledWindow() - sw.add(v) - sw.show() - d.vbox.pack_start(sw, 1, 1, 1) - return v - - -def show_error_text(msg, text, parent=None): - d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent, - type=gtk.MESSAGE_ERROR) - d.set_property("text", msg) - - _add_text(d, text) - if not parent: - d.set_position(gtk.WIN_POS_CENTER_ALWAYS) - - d.set_size_request(600, 400) - d.run() - d.destroy() - - -def show_warning(msg, text, - parent=None, buttons=None, title="Warning", - can_squelch=False): - if buttons is None: - buttons = gtk.BUTTONS_OK - d = gtk.MessageDialog(buttons=buttons, - parent=parent, - type=gtk.MESSAGE_WARNING) - d.set_title(title) - d.set_property("text", msg) - l = gtk.Label(_("Details") + ":") - l.show() - d.vbox.pack_start(l, 0, 0, 0) - l = gtk.Label(_("Proceed?")) - l.show() - d.get_action_area().pack_start(l, 0, 0, 0) - d.get_action_area().reorder_child(l, 0) - textview = _add_text(d, text) - textview.set_wrap_mode(gtk.WRAP_WORD) - if not parent: - d.set_position(gtk.WIN_POS_CENTER_ALWAYS) - if can_squelch: - cb = gtk.CheckButton(_("Do not show this next time")) - cb.show() - d.vbox.pack_start(cb, 0, 0, 0) - - d.set_size_request(600, 400) - r = d.run() - d.destroy() - if can_squelch: - return r, cb.get_active() - return r - - -def simple_diff(a, b, diffsonly=False): - lines_a = a.split(os.linesep) - lines_b = b.split(os.linesep) - blankprinted = True - - diff = "" - for i in range(0, len(lines_a)): - if lines_a[i] != lines_b[i]: - diff += "-%s%s" % (lines_a[i], os.linesep) - diff += "+%s%s" % (lines_b[i], os.linesep) - blankprinted = False - elif diffsonly is True: - if blankprinted: - continue - diff += os.linesep - blankprinted = True - else: - diff += " %s%s" % (lines_a[i], os.linesep) - return diff - - -# A quick hacked up tool to show a blob of text in a dialog window -# using fixed-width fonts. It also highlights lines that start with -# a '-' in red bold font and '+' with blue bold font. -def show_diff_blob(title, result): - d = gtk.Dialog(title=title, - buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK)) - b = gtk.TextBuffer() - - tags = b.get_tag_table() - for color in ["red", "blue", "green", "grey"]: - tag = gtk.TextTag(color) - tag.set_property("foreground", color) - tags.add(tag) - tag = gtk.TextTag("bold") - tag.set_property("weight", pango.WEIGHT_BOLD) - tags.add(tag) - - try: - fontsize = CONF.get_int("diff_fontsize", "developer") - except Exception: - fontsize = 11 - if fontsize < 4 or fontsize > 144: - LOG.info("Unsupported diff_fontsize %i. Using 11." % fontsize) - fontsize = 11 - - lines = result.split(os.linesep) - for line in lines: - if line.startswith("-"): - tags = ("red", "bold") - elif line.startswith("+"): - tags = ("blue", "bold") - else: - tags = () - b.insert_with_tags_by_name(b.get_end_iter(), line + os.linesep, *tags) - v = gtk.TextView(b) - fontdesc = pango.FontDescription("Courier %i" % fontsize) - v.modify_font(fontdesc) - v.set_editable(False) - v.show() - s = gtk.ScrolledWindow() - s.add(v) - s.show() - d.vbox.pack_start(s, 1, 1, 1) - d.set_size_request(600, 400) - d.run() - d.destroy() - - -def unpluralize(string): - if string.endswith("s"): - return string[:-1] - else: - return string diff --git a/chirp/ui/compat.py b/chirp/ui/compat.py deleted file mode 100644 index b4ac7d2c..00000000 --- a/chirp/ui/compat.py +++ /dev/null @@ -1,82 +0,0 @@ -import contextlib -import logging -import serial as base_serial -import six - -try: - import gtk -except ImportError: - # FIXME - # For chirpc - gtk = None - -from chirp import bitwise - -LOG = logging.getLogger('uicompat') - - -@contextlib.contextmanager -def py3safe(quiet=False): - try: - yield - except Exception as e: - if not quiet: - LOG.exception('FIXMEPY3: %s' % e) - - -def SpinButton(adj): - try: - return gtk.SpinButton(adj) - except TypeError: - sb = gtk.SpinButton() - sb.configure(adj, 1.0, 0) - return sb - -def Frame(label): - try: - return gtk.Frame(label) - except TypeError: - f = gtk.Frame() - f.set_label(label) - return f - - -class CompatSerial(base_serial.Serial): - """A PY2-compatible Serial class - - This wraps serial.Serial to provide translation between - hex-char-having unicode strings in radios and the bytes-wanting - serial channel. See bitwise.string_straigth_encode() and - bitwise.string_straight_decode() for more details. - - This should only be used as a bridge for older drivers until - they can be rewritten. - """ - def write(self, data): - data = bitwise.string_straight_encode(data) - return super(CompatSerial, self).write(data) - - def read(self, count): - data = super(CompatSerial, self).read(count) - return bitwise.string_straight_decode(data) - - @classmethod - def get(cls, needs_compat, *args, **kwargs): - if six.PY3 and needs_compat: - return cls(*args, **kwargs) - else: - return base_serial.Serial(*args, **kwargs) - - -class CompatTooltips(object): - def __init__(self): - try: - self.tips = gtk.Tooltips() - except AttributeError: - self.tips = None - - def set_tip(self, widget, tip): - if self.tips: - self.tips.set_tip(widget, tip) - else: - widget.set_tooltip_text(tip) diff --git a/chirp/ui/dstaredit.py b/chirp/ui/dstaredit.py deleted file mode 100644 index 4d122f23..00000000 --- a/chirp/ui/dstaredit.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import gobject -import logging - -from chirp.ui import common, miscwidgets -from chirp.ui import compat - -LOG = logging.getLogger(__name__) - -WIDGETW = 80 -WIDGETH = 30 - - -class CallsignEditor(gtk.HBox): - __gsignals__ = { - "changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - } - - def _cs_changed(self, listw, callid): - if callid == 0 and self.first_fixed: - return False - - self.emit("changed") - - return True - - def make_list(self, width): - cols = [(gobject.TYPE_INT, ""), - (gobject.TYPE_INT, ""), - (gobject.TYPE_STRING, _("Callsign")), - ] - - self.listw = miscwidgets.KeyedListWidget(cols) - self.listw.show() - - self.listw.set_editable(1, True) - self.listw.connect("item-set", self._cs_changed) - - with compat.py3safe(): - rend = self.listw.get_renderer(1) - rend.set_property("family", "Monospace") - rend.set_property("width-chars", width) - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add_with_viewport(self.listw) - sw.show() - - return sw - - def __init__(self, first_fixed=False, width=8): - gtk.HBox.__init__(self, False, 2) - - self.first_fixed = first_fixed - - self.listw = None - - self.pack_start(self.make_list(width), 1, 1, 1) - - def set_callsigns(self, calls): - if self.first_fixed: - st = 1 - else: - st = 0 - - values = [] - i = 1 - for call in calls[st:]: - self.listw.set_item(i, i, call) - i += 1 - - def get_callsigns(self): - calls = [] - keys = self.listw.get_keys() - for key in keys: - id, idx, call = self.listw.get_item(key) - calls.append(call) - - if self.first_fixed: - calls.insert(0, "") - - return calls - - -class DStarEditor(common.Editor): - def __cs_changed(self, cse): - job = None - - LOG.debug("Callsigns: %s" % cse.get_callsigns()) - if cse == self.editor_ucall: - job = common.RadioJob(None, - "set_urcall_list", - cse.get_callsigns()) - LOG.debug("Set urcall") - elif cse == self.editor_rcall: - job = common.RadioJob(None, - "set_repeater_call_list", - cse.get_callsigns()) - LOG.debug("Set rcall") - elif cse == self.editor_mcall: - job = common.RadioJob(None, - "set_mycall_list", - cse.get_callsigns()) - - if job: - LOG.debug("Submitting job to update call lists") - self.rthread.submit(job) - - self.emit("changed") - - def make_callsigns(self): - box = gtk.HBox(True, 2) - - fixed = self.rthread.radio.get_features().has_implicit_calls - - frame = compat.Frame(_("Your callsign")) - self.editor_ucall = CallsignEditor(first_fixed=fixed) - self.editor_ucall.set_size_request(-1, 200) - self.editor_ucall.show() - frame.add(self.editor_ucall) - frame.show() - box.pack_start(frame, 1, 1, 0) - - frame = compat.Frame(_("Repeater callsign")) - self.editor_rcall = CallsignEditor(first_fixed=fixed) - self.editor_rcall.set_size_request(-1, 200) - self.editor_rcall.show() - frame.add(self.editor_rcall) - frame.show() - box.pack_start(frame, 1, 1, 0) - - frame = compat.Frame(_("My callsign")) - self.editor_mcall = CallsignEditor() - self.editor_mcall.set_size_request(-1, 200) - self.editor_mcall.show() - frame.add(self.editor_mcall) - frame.show() - box.pack_start(frame, 1, 1, 0) - - box.show() - return box - - def focus(self): - if self.loaded: - return - self.loaded = True - LOG.debug("Loading callsigns...") - - def set_ucall(calls): - self.editor_ucall.set_callsigns(calls) - self.editor_ucall.connect("changed", self.__cs_changed) - - def set_rcall(calls): - self.editor_rcall.set_callsigns(calls) - self.editor_rcall.connect("changed", self.__cs_changed) - - def set_mcall(calls): - self.editor_mcall.set_callsigns(calls) - self.editor_mcall.connect("changed", self.__cs_changed) - - job = common.RadioJob(set_ucall, "get_urcall_list") - job.set_desc(_("Downloading URCALL list")) - self.rthread.submit(job) - - job = common.RadioJob(set_rcall, "get_repeater_call_list") - job.set_desc(_("Downloading RPTCALL list")) - self.rthread.submit(job) - - job = common.RadioJob(set_mcall, "get_mycall_list") - job.set_desc(_("Downloading MYCALL list")) - self.rthread.submit(job) - - def __init__(self, rthread): - super(DStarEditor, self).__init__(rthread) - - self.loaded = False - - self.editor_ucall = self.editor_rcall = None - - vbox = gtk.VBox(False, 2) - vbox.pack_start(self.make_callsigns(), 0, 0, 0) - - tmp = gtk.Label("") - tmp.show() - vbox.pack_start(tmp, 1, 1, 1) - - self.root = vbox diff --git a/chirp/ui/editorset.py b/chirp/ui/editorset.py deleted file mode 100644 index c030595f..00000000 --- a/chirp/ui/editorset.py +++ /dev/null @@ -1,419 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import os -import gtk -import gobject -import logging - -from chirp import chirp_common, directory -from chirp.drivers import generic_csv -from chirp.ui import memedit, dstaredit, bankedit, common, importdialog -from chirp.ui import inputdialog, reporting, settingsedit, radiobrowser, config - -LOG = logging.getLogger(__name__) - - -class EditorSet(gtk.VBox): - __gsignals__ = { - "want-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - "status": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - "usermsg": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - "editor-selected": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - } - - def _make_device_mapping_editors(self, device, devrthread, index): - sub_index = 0 - memory_editor = self.editors["memedit%i" % index] - mappings = device.get_mapping_models() - for mapping_model in mappings: - members = bankedit.MappingMembershipEditor(devrthread, self, - mapping_model) - label = mapping_model.get_name() - if self.rf.has_sub_devices: - label += "(%s)" % device.VARIANT - lab = gtk.Label(label) - self.tabs.append_page(members.root, lab) - self.editors["mapping_members%i%i" % (index, sub_index)] = members - - basename = common.unpluralize(mapping_model.get_name()) - names = bankedit.MappingNameEditor(devrthread, mapping_model) - label = "%s Names" % basename - if self.rf.has_sub_devices: - label += " (%s)" % device.VARIANT - lab = gtk.Label(label) - self.tabs.append_page(names.root, lab) - self.editors["mapping_names%i%i" % (index, sub_index)] = names - - members.root.show() - members.connect("changed", self.editor_changed) - if hasattr(mapping_model.get_mappings()[0], "set_name"): - names.root.show() - members.connect("changed", lambda x: names.mappings_changed()) - names.connect("changed", lambda x: members.mappings_changed()) - names.connect("changed", self.editor_changed) - sub_index += 1 - - def _make_device_editors(self, device, devrthread, index): - if isinstance(device, chirp_common.IcomDstarSupport): - memories = memedit.DstarMemoryEditor(devrthread) - else: - memories = memedit.MemoryEditor(devrthread) - - memories.connect("usermsg", lambda e, m: self.emit("usermsg", m)) - memories.connect("changed", self.editor_changed) - - if self.rf.has_sub_devices: - label = (_("Memories (%(variant)s)") % - dict(variant=device.VARIANT)) - rf = device.get_features() - else: - label = _("Memories") - rf = self.rf - lab = gtk.Label(label) - self.tabs.append_page(memories.root, lab) - memories.root.show() - self.editors["memedit%i" % index] = memories - - self._make_device_mapping_editors(device, devrthread, index) - - if isinstance(device, chirp_common.IcomDstarSupport): - editor = dstaredit.DStarEditor(devrthread) - self.tabs.append_page(editor.root, gtk.Label(_("D-STAR"))) - editor.root.show() - editor.connect("changed", self.dstar_changed, memories) - editor.connect("changed", self.editor_changed) - self.editors["dstar"] = editor - - def __init__(self, source, parent_window=None, - filename=None, tempname=None): - gtk.VBox.__init__(self, True, 0) - - self.parent_window = parent_window - - if isinstance(source, str): - self.filename = source - self.radio = directory.get_radio_by_image(self.filename) - elif isinstance(source, chirp_common.Radio): - self.radio = source - self.filename = filename or tempname or source.VARIANT - else: - raise Exception("Unknown source type") - - rthread = common.RadioThread(self.radio) - rthread.setDaemon(True) - rthread.start() - - rthread.connect("status", lambda e, m: self.emit("status", m)) - - self.tabs = gtk.Notebook() - self.tabs.connect("switch-page", self.tab_selected) - self.tabs.set_tab_pos(gtk.POS_LEFT) - - self.editors = {} - - self.rf = self.radio.get_features() - if self.rf.has_sub_devices: - devices = self.radio.get_sub_devices() - else: - devices = [self.radio] - - index = 0 - for device in devices: - devrthread = common.RadioThread(device, rthread) - devrthread.setDaemon(True) - devrthread.start() - self._make_device_editors(device, devrthread, index) - index += 1 - - if self.rf.has_settings: - editor = settingsedit.SettingsEditor(rthread) - self.tabs.append_page(editor.root, gtk.Label(_("Settings"))) - editor.root.show() - editor.connect("changed", self.editor_changed) - self.editors["settings"] = editor - - conf = config.get() - if (hasattr(self.rthread.radio, '_memobj') and - conf.get_bool("developer", "state")): - editor = radiobrowser.RadioBrowser(self.rthread) - lab = gtk.Label(_("Browser")) - self.tabs.append_page(editor.root, lab) - editor.connect("changed", self.editor_changed) - self.editors["browser"] = editor - - self.pack_start(self.tabs) - self.tabs.show() - - self.label = self.text_label = None - self.make_label() - self.modified = (tempname is not None) - if tempname: - self.filename = tempname - self.tooltip_filename = None - self.update_tab() - - def make_label(self): - self.label = gtk.HBox(False, 0) - - self.text_label = gtk.Label("") - self.text_label.show() - self.label.pack_start(self.text_label, 1, 1, 1) - - button = gtk.Button() - button.set_relief(gtk.RELIEF_NONE) - button.set_focus_on_click(False) - - icon = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) - icon.show() - button.add(icon) - - button.connect("clicked", lambda x: self.emit("want-close")) - button.show() - self.label.pack_start(button, 0, 0, 0) - - self.label.show() - - def update_tab(self): - fn = os.path.basename(self.filename) - if self.modified: - text = "%s*" % fn - else: - text = fn - - self.text_label.set_text(self.radio.get_name() + ": " + text) - if self.filename != self.tooltip_filename: - self.text_label.set_tooltip_text(self.filename) - self.tooltip_filename = self.filename - - def save(self, fname=None): - if not fname: - fname = self.filename - if not os.path.exists(self.filename): - return # Probably before the first "Save as" - else: - self.filename = fname - - self.rthread.lock() - try: - self.radio.save(fname) - except: - self.rthread.unlock() - raise - self.rthread.unlock() - - self.modified = False - self.update_tab() - - def dstar_changed(self, dstared, memedit): - memedit.set_urcall_list(dstared.editor_ucall.get_callsigns()) - memedit.set_repeater_list(dstared.editor_rcall.get_callsigns()) - memedit.prefill() - - def editor_changed(self, target_editor=None): - LOG.debug("%s changed" % target_editor) - if not isinstance(self.radio, chirp_common.LiveRadio): - self.modified = True - self.update_tab() - for editor in list(self.editors.values()): - if editor != target_editor: - editor.other_editor_changed(target_editor) - - def get_tab_label(self): - return self.label - - def is_modified(self): - return self.modified - - def _do_import_locked(self, dlgclass, src_radio, dst_rthread): - - # An import/export action needs to be done in the absence of any - # other queued changes. So, we make sure that nothing else is - # staged for the thread and lock it up. Then we use the hidden - # interface to queue our own changes before opening it up to the - # rest of the world. - - dst_rthread._qlock_when_idle(5) # Suspend job submission when idle - - dialog = dlgclass(src_radio, dst_rthread.radio, self.parent_window) - r = dialog.run() - dialog.hide() - if r != gtk.RESPONSE_OK: - dst_rthread._qunlock() - return - - count = dialog.do_import(dst_rthread) - LOG.debug("Imported %i" % count) - dst_rthread._qunlock() - - if count > 0: - self.editor_changed() - current_editor = self.get_current_editor() - gobject.idle_add(current_editor.prefill) - - return count - - def choose_sub_device(self, radio): - devices = radio.get_sub_devices() - choices = [x.VARIANT for x in devices] - - d = inputdialog.ChoiceDialog(choices) - text = _("The {vendor} {model} has multiple independent sub-devices") - d.label.set_text(text.format(vendor=radio.VENDOR, model=radio.MODEL) + - os.linesep + _("Choose one to import from:")) - r = d.run() - chosen = d.choice.get_active_text() - d.destroy() - if r == gtk.RESPONSE_CANCEL: - raise Exception(_("Cancelled")) - for d in devices: - if d.VARIANT == chosen: - return d - - raise Exception(_("Internal Error")) - - def do_import(self, filen): - current_editor = self.get_current_editor() - if not isinstance(current_editor, memedit.MemoryEditor): - # FIXME: We need a nice message to let the user know that they - # need to select the appropriate memory editor tab before doing - # and import so that we know which thread and editor to import - # into and refresh. This will do for the moment. - common.show_error("Memory editor must be selected before import") - try: - src_radio = directory.get_radio_by_image(filen) - except Exception as e: - common.show_error(e) - return - - if isinstance(src_radio, chirp_common.NetworkSourceRadio): - ww = importdialog.WaitWindow("Querying...", self.parent_window) - ww.show() - - def status(status): - ww.set(float(status.cur) / float(status.max)) - - try: - src_radio.status_fn = status - src_radio.do_fetch() - except Exception as e: - common.show_error(e) - ww.hide() - return - ww.hide() - - try: - if src_radio.get_features().has_sub_devices: - src_radio = self.choose_sub_device(src_radio) - except Exception as e: - common.show_error(e) - return - - if len(src_radio.errors) > 0: - _filen = os.path.basename(filen) - common.show_error_text(_("There were errors while opening {file}. " - "The affected memories will not " - "be importable!").format(file=_filen), - "\r\n".join(src_radio.errors)) - - try: - count = self._do_import_locked(importdialog.ImportDialog, - src_radio, - self.rthread) - reporting.report_model_usage(src_radio, "importsrc", True) - except Exception as e: - common.log_exception() - common.show_error(_("There was an error during " - "import: {error}").format(error=e)) - - def do_export(self, filen): - try: - if filen.lower().endswith(".csv"): - dst_radio = generic_csv.CSVRadio(filen) - else: - raise Exception(_("Unsupported file type")) - except Exception as e: - common.log_exception() - common.show_error(e) - return - - dst_rthread = common.RadioThread(dst_radio) - dst_rthread.setDaemon(True) - dst_rthread.start() - - try: - count = self._do_import_locked(importdialog.ExportDialog, - self.rthread.radio, - dst_rthread) - except Exception as e: - common.log_exception() - common.show_error(_("There was an error during " - "export: {error}").format(error=e), - self.parent_window) - return - - if count <= 0: - return - - # Wait for thread queue to complete - dst_rthread._qlock_when_idle() - - try: - dst_radio.save(filename=filen) - except Exception as e: - common.log_exception() - common.show_error(_("There was an error during " - "export: {error}").format(error=e), - self) - - def tab_selected(self, notebook, foo, pagenum): - widget = notebook.get_nth_page(pagenum) - for k, v in list(self.editors.items()): - if v and v.root == widget: - v.focus() - self.emit("editor-selected", k) - elif v: - v.unfocus() - - def set_read_only(self, read_only=True): - for editor in list(self.editors.values()): - editor and editor.set_read_only(read_only) - - def get_read_only(self): - return self.editors["memedit0"].get_read_only() - - def prepare_close(self): - for editor in list(self.editors.values()): - editor and editor.prepare_close() - - def get_current_editor(self): - tabs = self.tabs - for lab, e in list(self.editors.items()): - if e and tabs.page_num(e.root) == tabs.get_current_page(): - return e - raise Exception("No editor selected?") - - @property - def rthread(self): - """Magic rthread property to return the rthread of the currently- - selected editor""" - e = self.get_current_editor() - return e.rthread diff --git a/chirp/ui/importdialog.py b/chirp/ui/importdialog.py deleted file mode 100644 index 548b4e21..00000000 --- a/chirp/ui/importdialog.py +++ /dev/null @@ -1,655 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import gobject -import pango -import logging - -from chirp import errors, chirp_common, import_logic -from chirp.ui import common - -LOG = logging.getLogger(__name__) - - -class WaitWindow(gtk.Window): - def __init__(self, msg, parent=None): - gtk.Window.__init__(self) - self.set_title("Please Wait") - self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - if parent: - self.set_transient_for(parent) - self.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - else: - self.set_position(gtk.WIN_POS_CENTER) - - vbox = gtk.VBox(False, 2) - - l = gtk.Label(msg) - l.show() - vbox.pack_start(l) - - self.prog = gtk.ProgressBar() - self.prog.show() - vbox.pack_start(self.prog) - - vbox.show() - self.add(vbox) - - def grind(self): - while gtk.events_pending(): - gtk.main_iteration(False) - - self.prog.pulse() - - def set(self, fraction): - while gtk.events_pending(): - gtk.main_iteration(False) - - self.prog.set_fraction(fraction) - - -class ImportMemoryBankJob(common.RadioJob): - def __init__(self, cb, dst_mem, src_radio, src_mem): - common.RadioJob.__init__(self, cb, None) - self.__dst_mem = dst_mem - self.__src_radio = src_radio - self.__src_mem = src_mem - - def execute(self, radio): - import_logic.import_bank(radio, self.__src_radio, - self.__dst_mem, self.__src_mem) - if self.cb: - gobject.idle_add(self.cb, *self.cb_args) - - -class ImportDialog(gtk.Dialog): - - def _check_for_dupe(self, location): - iter = self.__store.get_iter_first() - while iter: - imp, loc = self.__store.get(iter, self.col_import, self.col_nloc) - if imp and loc == location: - return True - iter = self.__store.iter_next(iter) - - return False - - def _toggle(self, rend, path, col): - iter = self.__store.get_iter(path) - imp, nloc = self.__store.get(iter, self.col_import, self.col_nloc) - if not imp and self._check_for_dupe(nloc): - d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) - d.set_property("text", - _("Location {number} is already being imported. " - "Choose another value for 'New Location' " - "before selection 'Import'").format(number=nloc)) - d.run() - d.destroy() - else: - self.__store[path][col] = not imp - - def _render(self, _, rend, model, iter, colnum): - newloc, imp = model.get(iter, self.col_nloc, self.col_import) - lo, hi = self.dst_radio.get_features().memory_bounds - - rend.set_property("text", "%i" % newloc) - if newloc in self.used_list and imp: - rend.set_property("foreground", "goldenrod") - rend.set_property("weight", pango.WEIGHT_BOLD) - elif newloc < lo or newloc > hi: - rend.set_property("foreground", "red") - rend.set_property("weight", pango.WEIGHT_BOLD) - else: - rend.set_property("foreground", "black") - rend.set_property("weight", pango.WEIGHT_NORMAL) - - def _edited(self, rend, path, new, col): - iter = self.__store.get_iter(path) - - if col == self.col_nloc: - nloc, = self.__store.get(iter, self.col_nloc) - - try: - val = int(new) - except ValueError: - common.show_error(_("Invalid value. Must be an integer.")) - return - - if val == nloc: - return - - if self._check_for_dupe(val): - d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) - d.set_property("text", - _("Location {number} is already being " - "imported").format(number=val)) - d.run() - d.destroy() - return - - self.record_use_of(val) - - elif col == self.col_name or col == self.col_comm: - val = str(new) - - else: - return - - self.__store.set(iter, col, val) - - def get_import_list(self): - import_list = [] - iter = self.__store.get_iter_first() - while iter: - old, new, name, comm, enb = \ - self.__store.get(iter, self.col_oloc, self.col_nloc, - self.col_name, self.col_comm, self.col_import) - if enb: - import_list.append((old, new, name, comm)) - iter = self.__store.iter_next(iter) - - return import_list - - def ensure_calls(self, dst_rthread, import_list): - rlist_changed = False - ulist_changed = False - - if not isinstance(self.dst_radio, chirp_common.IcomDstarSupport): - return - - ulist = self.dst_radio.get_urcall_list() - rlist = self.dst_radio.get_repeater_call_list() - - for old, new in import_list: - mem = self.src_radio.get_memory(old) - if isinstance(mem, chirp_common.DVMemory): - if mem.dv_urcall not in ulist: - LOG.debug("Adding %s to ucall list" % mem.dv_urcall) - ulist.append(mem.dv_urcall) - ulist_changed = True - if mem.dv_rpt1call not in rlist: - LOG.debug("Adding %s to rcall list" % mem.dv_rpt1call) - rlist.append(mem.dv_rpt1call) - rlist_changed = True - if mem.dv_rpt2call not in rlist: - LOG.debug("Adding %s to rcall list" % mem.dv_rpt2call) - rlist.append(mem.dv_rpt2call) - rlist_changed = True - - if ulist_changed: - job = common.RadioJob(None, "set_urcall_list", ulist) - job.set_desc(_("Updating URCALL list")) - dst_rthread._qsubmit(job, 0) - - if rlist_changed: - job = common.RadioJob(None, "set_repeater_call_list", ulist) - job.set_desc(_("Updating RPTCALL list")) - dst_rthread._qsubmit(job, 0) - - return - - def _convert_power(self, dst_levels, src_levels, mem): - if not dst_levels: - mem.power = None - return - elif not mem.power: - # Source radio does not support power levels, so choose the - # first (highest) level from the destination radio. - mem.power = dst_levels[0] - return "" - - # If both radios support power levels, we need to decide how to - # convert the source power level to a valid one for the destination - # radio. To do that, find the absolute level of the source value - # and calculate the different between it and all the levels of the - # destination, choosing the one that matches most closely. - - deltas = [abs(mem.power - power) for power in dst_levels] - mem.power = dst_levels[deltas.index(min(deltas))] - - def do_soft_conversions(self, dst_features, src_features, mem): - self._convert_power(dst_features.valid_power_levels, - src_features.valid_power_levels, - mem) - - return mem - - def do_import_banks(self): - try: - dst_banks = self.dst_radio.get_banks() - src_banks = self.src_radio.get_banks() - if not dst_banks or not src_banks: - raise Exception() - except Exception: - LOG.error("One or more of the radios doesn't support banks") - return - - if len(dst_banks) != len(src_banks): - LOG.warn("Source and destination radios have " - "a different number of banks") - else: - self.dst_radio.set_banks(src_banks) - - def do_import(self, dst_rthread): - i = 0 - error_messages = {} - import_list = self.get_import_list() - - src_features = self.src_radio.get_features() - - for old, new, name, comm in import_list: - i += 1 - LOG.debug("%sing %i -> %i" % (self.ACTION, old, new)) - - src = self.src_radio.get_memory(old) - - try: - mem = import_logic.import_mem(self.dst_radio, - src_features, - src, - {"number": new, - "name": name, - "comment": comm}) - except import_logic.ImportError as e: - LOG.error("Import error: %s", e) - error_messages[new] = str(e) - continue - - job = common.RadioJob(None, "set_memory", mem) - desc = _("Setting memory {number}").format(number=mem.number) - job.set_desc(desc) - dst_rthread._qsubmit(job, 0) - - job = ImportMemoryBankJob(None, mem, self.src_radio, src) - job.set_desc(_("Importing bank information")) - dst_rthread._qsubmit(job, 0) - - if list(error_messages.keys()): - msg = _("Error importing memories:") + "\r\n" - for num, msgs in list(error_messages.items()): - msg += "%s: %s" % (num, ",".join(msgs)) - common.show_error(msg) - - return i - - def make_view(self): - editable = [self.col_nloc, self.col_name, self.col_comm] - - self.__store = gtk.ListStore(gobject.TYPE_BOOLEAN, # Import - gobject.TYPE_INT, # Source loc - gobject.TYPE_INT, # Destination loc - gobject.TYPE_STRING, # Name - gobject.TYPE_STRING, # Frequency - gobject.TYPE_STRING, # Comment - gobject.TYPE_BOOLEAN, - gobject.TYPE_STRING) - self.__view = gtk.TreeView(self.__store) - self.__view.show() - - tips = gtk.Tooltips() - - for k in list(self.caps.keys()): - t = self.types[k] - - if t == gobject.TYPE_BOOLEAN: - rend = gtk.CellRendererToggle() - rend.connect("toggled", self._toggle, k) - column = gtk.TreeViewColumn(self.caps[k], rend, - active=k, - sensitive=self.col_okay, - activatable=self.col_okay) - else: - rend = gtk.CellRendererText() - if k in editable: - rend.set_property("editable", True) - rend.connect("edited", self._edited, k) - column = gtk.TreeViewColumn(self.caps[k], rend, - text=k, - sensitive=self.col_okay) - - if k == self.col_nloc: - column.set_cell_data_func(rend, self._render, k) - - if k in list(self.tips.keys()): - LOG.debug("Doing %s" % k) - lab = gtk.Label(self.caps[k]) - column.set_widget(lab) - tips.set_tip(lab, self.tips[k]) - lab.show() - column.set_sort_column_id(k) - self.__view.append_column(column) - - self.__view.set_tooltip_column(self.col_tmsg) - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(self.__view) - sw.show() - - return sw - - def __select_all(self, button, state): - iter = self.__store.get_iter_first() - while iter: - _state, okay, = self.__store.get(iter, - self.col_import, - self.col_okay) - if state is None: - _state = not _state and okay - else: - _state = state and okay - self.__store.set(iter, self.col_import, _state) - iter = self.__store.iter_next(iter) - - def __incrnew(self, button, delta): - iter = self.__store.get_iter_first() - while iter: - pos = self.__store.get(iter, self.col_nloc)[0] - pos += delta - if pos < 0: - pos = 0 - self.__store.set(iter, self.col_nloc, pos) - iter = self.__store.iter_next(iter) - - def __autonew(self, button): - pos = self.dst_radio.get_features().memory_bounds[0] - iter = self.__store.get_iter_first() - while iter: - selected, okay = self.__store.get(iter, - self.col_import, self.col_okay) - if selected and okay: - self.__store.set(iter, self.col_nloc, pos) - pos += 1 - iter = self.__store.iter_next(iter) - - def __revrnew(self, button): - positions = [] - iter = self.__store.get_iter_first() - while iter: - positions.append(self.__store.get(iter, self.col_nloc)[0]) - iter = self.__store.iter_next(iter) - - iter = self.__store.get_iter_first() - while iter: - self.__store.set(iter, self.col_nloc, positions.pop()) - iter = self.__store.iter_next(iter) - - def make_select(self): - hbox = gtk.HBox(True, 2) - - all = gtk.Button(_("All")) - all.connect("clicked", self.__select_all, True) - all.set_size_request(50, 25) - all.show() - hbox.pack_start(all, 0, 0, 0) - - none = gtk.Button(_("None")) - none.connect("clicked", self.__select_all, False) - none.set_size_request(50, 25) - none.show() - hbox.pack_start(none, 0, 0, 0) - - inv = gtk.Button(_("Inverse")) - inv.connect("clicked", self.__select_all, None) - inv.set_size_request(50, 25) - inv.show() - hbox.pack_start(inv, 0, 0, 0) - - frame = gtk.Frame(_("Select")) - frame.show() - frame.add(hbox) - hbox.show() - - return frame - - def make_adjust(self): - hbox = gtk.HBox(True, 2) - - incr = gtk.Button("+100") - incr.connect("clicked", self.__incrnew, 100) - incr.set_size_request(50, 25) - incr.show() - hbox.pack_start(incr, 0, 0, 0) - - incr = gtk.Button("+10") - incr.connect("clicked", self.__incrnew, 10) - incr.set_size_request(50, 25) - incr.show() - hbox.pack_start(incr, 0, 0, 0) - - incr = gtk.Button("+1") - incr.connect("clicked", self.__incrnew, 1) - incr.set_size_request(50, 25) - incr.show() - hbox.pack_start(incr, 0, 0, 0) - - decr = gtk.Button("-1") - decr.connect("clicked", self.__incrnew, -1) - decr.set_size_request(50, 25) - decr.show() - hbox.pack_start(decr, 0, 0, 0) - - decr = gtk.Button("-10") - decr.connect("clicked", self.__incrnew, -10) - decr.set_size_request(50, 25) - decr.show() - hbox.pack_start(decr, 0, 0, 0) - - decr = gtk.Button("-100") - decr.connect("clicked", self.__incrnew, -100) - decr.set_size_request(50, 25) - decr.show() - hbox.pack_start(decr, 0, 0, 0) - - auto = gtk.Button(_("Auto")) - auto.connect("clicked", self.__autonew) - auto.set_size_request(50, 25) - auto.show() - hbox.pack_start(auto, 0, 0, 0) - - revr = gtk.Button(_("Reverse")) - revr.connect("clicked", self.__revrnew) - revr.set_size_request(50, 25) - revr.show() - hbox.pack_start(revr, 0, 0, 0) - - frame = gtk.Frame(_("Adjust New Location")) - frame.show() - frame.add(hbox) - hbox.show() - - return frame - - def make_options(self): - hbox = gtk.HBox(True, 2) - - confirm = gtk.CheckButton(_("Confirm overwrites")) - confirm.connect("toggled", __set_confirm) - confirm.show() - - hbox.pack_start(confirm, 0, 0, 0) - - frame = gtk.Frame(_("Options")) - frame.add(hbox) - frame.show() - hbox.show() - - return frame - - def make_controls(self): - hbox = gtk.HBox(False, 2) - - hbox.pack_start(self.make_select(), 0, 0, 0) - hbox.pack_start(self.make_adjust(), 0, 0, 0) - # hbox.pack_start(self.make_options(), 0, 0, 0) - hbox.show() - - return hbox - - def build_ui(self): - self.vbox.pack_start(self.make_view(), 1, 1, 1) - self.vbox.pack_start(self.make_controls(), 0, 0, 0) - - def record_use_of(self, number): - lo, hi = self.dst_radio.get_features().memory_bounds - - if number < lo or number > hi: - return - - try: - mem = self.dst_radio.get_memory(number) - if mem and not mem.empty and number not in self.used_list: - self.used_list.append(number) - except errors.InvalidMemoryLocation: - LOG.error("Location %i empty or at limit of destination radio" % - number) - except errors.InvalidDataError as e: - LOG.error("Got error from radio, assuming %i beyond limits: %s" % - (number, e)) - - def populate_list(self): - start, end = self.src_radio.get_features().memory_bounds - for i in range(start, end+1): - if end > 50 and i % (end/50) == 0: - self.ww.set(float(i) / end) - try: - mem = self.src_radio.get_memory(i) - except errors.InvalidMemoryLocation as e: - continue - except Exception as e: - self.__store.append(row=(False, - i, - i, - "ERROR", - chirp_common.format_freq(0), - "", - False, - str(e), - )) - self.record_use_of(i) - continue - if mem.empty: - continue - - self.ww.set(float(i) / end) - try: - msgs = self.dst_radio.validate_memory( - import_logic.import_mem(self.dst_radio, - self.src_radio.get_features(), - mem)) - except import_logic.DestNotCompatible: - msgs = self.dst_radio.validate_memory(mem) - errs = [x for x in msgs - if isinstance(x, chirp_common.ValidationError)] - if errs: - msg = _("Cannot be imported because") + ":\r\n" - msg += ",".join(errs) - else: - errs = [] - msg = "Memory can be imported into target" - - self.__store.append(row=(not bool(msgs), - mem.number, - mem.number, - mem.name, - chirp_common.format_freq(mem.freq), - mem.comment, - not bool(errs), - msg - )) - self.record_use_of(mem.number) - - TITLE = _("Import From File") - ACTION = _("Import") - - def __init__(self, src_radio, dst_radio, parent=None): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, - buttons=buttons, - title=self.TITLE, - parent=parent) - self.set_default_response(gtk.RESPONSE_OK) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - self.col_import = 0 - self.col_nloc = 1 - self.col_oloc = 2 - self.col_name = 3 - self.col_freq = 4 - self.col_comm = 5 - self.col_okay = 6 - self.col_tmsg = 7 - - self.caps = { - self.col_import: self.ACTION, - self.col_nloc: _("To"), - self.col_oloc: _("From"), - self.col_name: _("Name"), - self.col_freq: _("Frequency"), - self.col_comm: _("Comment"), - } - - self.tips = { - self.col_nloc: _("Location memory will be imported into"), - self.col_oloc: _("Location of memory in the file being imported"), - } - - self.types = { - self.col_import: gobject.TYPE_BOOLEAN, - self.col_oloc: gobject.TYPE_INT, - self.col_nloc: gobject.TYPE_INT, - self.col_name: gobject.TYPE_STRING, - self.col_freq: gobject.TYPE_STRING, - self.col_comm: gobject.TYPE_STRING, - self.col_okay: gobject.TYPE_BOOLEAN, - self.col_tmsg: gobject.TYPE_STRING, - } - - self.src_radio = src_radio - self.dst_radio = dst_radio - - self.used_list = [] - self.not_used_list = [] - - self.build_ui() - self.set_default_size(600, 400) - - self.ww = WaitWindow(_("Preparing memory list..."), parent=parent) - self.ww.show() - self.ww.grind() - - self.populate_list() - - self.ww.hide() - - -class ExportDialog(ImportDialog): - TITLE = _("Export To File") - ACTION = _("Export") - -if __name__ == "__main__": - from chirp.ui import editorset - import sys - - f = sys.argv[1] - rc = editorset.radio_class_from_file(f) - radio = rc(f) - - d = ImportDialog(radio) - d.run() - - print(d.get_import_list()) diff --git a/chirp/ui/inputdialog.py b/chirp/ui/inputdialog.py deleted file mode 100644 index 74362dc0..00000000 --- a/chirp/ui/inputdialog.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import logging - -from chirp.ui.miscwidgets import make_choice -from chirp.ui import reporting - -LOG = logging.getLogger(__name__) - - -class TextInputDialog(gtk.Dialog): - def respond_ok(self, _): - self.response(gtk.RESPONSE_OK) - - def __init__(self, **args): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, buttons=buttons, **args) - - self.label = gtk.Label() - self.label.set_size_request(300, 100) - self.label.set_alignment(0.0, 0.5) - - # pylint: disable-msg=E1101 - self.vbox.pack_start(self.label, 1, 1, 0) - - self.text = gtk.Entry() - self.text.connect("activate", self.respond_ok, None) - # pylint: disable-msg=E1101 - self.vbox.pack_start(self.text, 1, 1, 0) - - self.label.show() - self.text.show() - - -class ChoiceDialog(gtk.Dialog): - editable = False - - def __init__(self, choices, **args): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, buttons=buttons, **args) - - self.set_default_response(gtk.RESPONSE_OK) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - - self.label = gtk.Label() - self.label.set_size_request(300, 100) - self.label.set_alignment(0.0, 0.5) - # pylint: disable-msg=E1101 - self.vbox.pack_start(self.label, 1, 1, 0) - self.label.show() - - try: - default = choices[0] - except IndexError: - default = None - - self.choice = make_choice(sorted(choices), self.editable, default) - # pylint: disable-msg=E1101 - self.vbox.pack_start(self.choice, 1, 1, 0) - self.choice.show() - - -class EditableChoiceDialog(ChoiceDialog): - editable = True - - def __init__(self, choices, **args): - ChoiceDialog.__init__(self, choices, **args) - - self.choice.child.set_activates_default(True) - - -class ExceptionDialog(gtk.MessageDialog): - def __init__(self, exception, **args): - gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK, - type=gtk.MESSAGE_ERROR, **args) - self.set_default_response(gtk.RESPONSE_OK) - self.set_property("text", _("An error has occurred")) - self.format_secondary_text(str(exception)) - - import traceback - import sys - reporting.report_exception(traceback.format_exc(limit=30)) - LOG.error("--- Exception Dialog: %s ---" % exception) - LOG.error(traceback.format_exc(limit=100)) - LOG.error("----------------------------") - - -class FieldDialog(gtk.Dialog): - def __init__(self, **args): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - - gtk.Dialog.__init__(self, buttons=buttons, **args) - - self.set_default_response(gtk.RESPONSE_OK) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - - self.__fields = {} - - def response(self, _): - LOG.debug("Blocking response") - return - - def add_field(self, label, widget, validator=None): - box = gtk.HBox(True, 2) - - lab = gtk.Label(label + ":") - lab.set_alignment(1.0, 0.5) - lab.show() - - widget.set_size_request(150, -1) - widget.show() - - box.pack_start(lab, 0, 0, 0) - box.pack_start(widget, 0, 0, 0) - box.show() - - # pylint: disable-msg=E1101 - self.vbox.pack_start(box, 0, 0, 0) - - self.__fields[label] = widget - - def get_field(self, label): - return self.__fields.get(label, None) - - -class OverwriteDialog(gtk.MessageDialog): - def __init__(self, filename): - - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, buttons=buttons) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - - self.set_property("text", _("File Exists")) - - text = \ - _("The file {name} already exists. " - "Do you want to overwrite it?").format(name=filename) - - self.format_secondary_text(text) diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py deleted file mode 100644 index 7f22461f..00000000 --- a/chirp/ui/mainapp.py +++ /dev/null @@ -1,2386 +0,0 @@ -# Copyright 2008 Dan Smith -# Copyright 2012 Tom Hayward -# -# 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 . - -from datetime import datetime -import os -import tempfile -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.ui import compat -from chirp import platform, directory, util -from chirp.drivers import generic_csv, repeaterbook -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 import bandplan -from chirp.ui import editorset, clone, miscwidgets, config, reporting, fips -from chirp.ui import bandplans - -if sys.version_info < (3, 10): - import importlib_resources -else: - import importlib.resources as importlib_resources - -gobject.threads_init() - -LOG = logging.getLogger(__name__) - -if __name__ == "__main__": - sys.path.insert(0, "..") - -try: - import serial -except ImportError as 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)) - - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - d = gtk.Dialog(title="Diff Radios", - buttons=buttons, - parent=self) - d.set_default_response(gtk.RESPONSE_OK) - d.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - - label = gtk.Label("") - label.set_markup("-1 for either Mem # does a full-file hex " + - "dump with diffs highlighted.\n" + - "-2 for first Mem # shows " + - "only 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.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("""Unable to detect model! - -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]) - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - d = gtk.Dialog(title="Detection Failed", - buttons=buttons) - d.set_default_response(gtk.RESPONSE_OK) - d.set_alternative_button_order([gtk.RESPONSE_OK, - 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 = [(_("All files") + " (*.*)", "*"), - (_("CHIRP Radio Images") + " (*.img)", "*.img"), - (_("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 - - if not os.path.exists(fname): - LOG.error("Unable to find file %s" % fname) - return - - self.record_recent_file(fname) - - 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.ImageMetadataInvalidModel as e: - version = e.metadata.get('chirp_version') - if version: - newer = chirp_common.is_version_newer(version) - LOG.error('Image is from newer CHIRP with a model we ' - 'do not support') - common.show_error( - _('Unable to open this image. It was generated ' - 'with a newer version of CHIRP and thus may ' - 'be for a radio model that is not supported ' - 'by this version. Please update to the latest ' - 'version of CHIRP and try again.')) - else: - LOG.error('Image has metadata but has no chirp_version ' - 'and we do not support the model') - common.show_error( - _('Unable to open this image: unsupported model')) - return - except errors.ImageDetectFailed: - radio = self._do_manual_select(fname) - if not radio: - return - LOG.debug("Manually selected %s" % radio) - except Exception as 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 as e: - common.log_exception() - common.show_error( - _("There was an error opening {fname}: {error}").format( - fname=fname, - error=e)) - return - - 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)) - self._show_information(radio) - - def do_live_warning(self, radio): - d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) - d.set_markup("" + _("Note:") + "") - msg = _("The {vendor} {model} operates in live mode. " - "This means that any changes you make are immediately sent " - "to the radio. Because of this, you cannot perform the " - "Save or Upload operations. If you wish to " - "edit the contents offline, please Export to a CSV " - "file, using the File menu.") - 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) - - defname_format = CONF.get("default_filename", "global") or \ - "{vendor}_{model}_{date}" - defname = defname_format.format( - vendor=eset.radio.VENDOR, - model=eset.radio.MODEL, - date=datetime.now().strftime('%Y%m%d') - ).replace('/', '_') - - 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")] - elif isinstance(eset.radio, icf.IcomCloneModeRadio): - types += [(_("ICF") + " (*.icf)", "icf")] - - while True: - fname = platform.get_platform().gui_save_file(default_name=defname, - 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 as 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, parent=self) - 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 index in range(0, KEEP_RECENT): - key = "recent%i" % index - if (index < len(recent)): - fn = recent[index] - CONF.set(key, fn, "state") - else: - CONF.remove_option(key, "state") - self.recent_files = None - - def update_recent_files(self): - if self.recent_files is not None: - return - - self.recent_files = self._get_recent_list() - for index in range(0, KEEP_RECENT): - action_name = "recent%i" % index - path = "/MenuBar/file/recent" - - old_action = self.menu_ag.get_action(action_name) - if old_action: - old_action.set_visible(False) - self.menu_ag.remove_action(old_action) - - if (index < len(self.recent_files)): - fname = self.recent_files[index] - widget_label = os.path.basename(fname).replace("_", "__") - widget_tip = _("Open recent file") + (" {name}").format( - name=fname) - widget_path = path + "/" + action_name - - action = gtk.Action(action_name, widget_label, widget_tip, "") - 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, True) - self.menu_ag.add_action(action) - - try: - widget = self.menu_uim.get_widget(widget_path) - widget.set_tooltip_text(tip) - except: - pass - - def record_recent_file(self, fname): - recent_files = self._get_recent_list() - if fname in recent_files: - recent_files.remove(fname) - if len(recent_files) == KEEP_RECENT: - recent_files.pop(0) - recent_files.append(fname) - - self._set_recent_list(recent_files) - - def clear_recent_files(self): - self._set_recent_list([]) - - 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): - for fn in ( - conf.name for conf - in importlib_resources.files('chirp.stock_configs').iterdir() - if conf.is_file() - ): - if os.path.exists(os.path.join(stock_dir, fn)): - LOG.info("Skipping existing stock config") - continue - with importlib_resources.as_file( - importlib_resources.files('chirp.stock_configs') - .joinpath(fn) - ) as path: - try: - shutil.copy(str(path), stock_dir) - LOG.debug("Copying %s -> %s" % (str(path), stock_dir)) - except Exception as e: - LOG.error( - "Unable to copy %s to %s: %s" - % (str(path), 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 as 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() - 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() - 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_information(self, radio): - message = radio.get_prompts().info - if message is None: - return - - if CONF.get_bool("clone_information", "noconfirm"): - return - - d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) - d.set_markup("" + _("{name} Information").format( - name=radio.get_name()) + "") - msg = _("{information}").format(information=message) - d.format_secondary_markup(msg) - - again = gtk.CheckButton( - _("Don't show information for any radio again")) - again.show() - again.connect("toggled", lambda action: - self.infomenu.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 _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("" + _("{name} Instructions").format( - name=radio.get_name()) + "") - 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: - if '://' in settings.port: - # NOTE: This will only work with byte-clean radios - ser = serial.serial_for_url(settings.port, do_not_open=True) - ser.timeout = 0.25 - ser.open() - else: - ser = compat.CompatSerial.get(rclass.NEEDS_COMPAT_SERIAL, - port=settings.port, - baudrate=rclass.BAUD_RATE, - rtscts=rclass.HARDWARE_FLOW, - timeout=0.25) - ser.flushInput() - except serial.SerialException as e: - d = inputdialog.ExceptionDialog(e, parent=self) - 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) - self._show_information(rclass) # show Info prompt now - - 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: - if '://' in settings.port: - ser = serial.serial_for_url(settings.port, do_not_open=True) - ser.timeout = 0.25 - ser.open() - else: - ser = compat.CompatSerial.get(radio.NEEDS_COMPAT_SERIAL, - port=settings.port, - baudrate=radio.BAUD_RATE, - rtscts=radio.HARDWARE_FLOW, - timeout=0.25) - ser.flushInput() - except serial.SerialException as 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(): - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_NO, gtk.RESPONSE_NO, - gtk.STOCK_YES, gtk.RESPONSE_YES) - dlg = miscwidgets.YesNoDialog( - title=_("Save Changes?"), parent=self, - buttons=buttons) - dlg.set_default_response(gtk.RESPONSE_YES) - 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 = [(_("All files") + " (*.*)", "*"), - (_("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_clear_recently_opened(self): - self.clear_recent_files() - - def do_dmrmarc_prompt(self): - fields = {"1City": (gtk.Entry(), lambda x: x), - "2State": (gtk.Entry(), lambda x: x), - "3Country": (gtk.Entry(), lambda x: x), - } - - d = inputdialog.FieldDialog(title=_("DMR-MARC Repeater Database Dump"), - 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:], "dmrmarc") or "") - - while d.run() == gtk.RESPONSE_OK: - for k in sorted(fields.keys()): - widget, validator = fields[k] - try: - if validator(widget.get_text()): - CONF.set(k[1:], widget.get_text(), "dmrmarc") - continue - except Exception: - pass - - d.destroy() - return True - - d.destroy() - return False - - def do_dmrmarc(self, do_import): - self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if not self.do_dmrmarc_prompt(): - self.window.set_cursor(None) - return - - city = CONF.get("city", "dmrmarc") - state = CONF.get("state", "dmrmarc") - country = CONF.get("country", "dmrmarc") - - # 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() - dmrmarcstr = "dmrmarc://%s/%s/%s" % (city, state, country) - eset.do_import(dmrmarcstr) - else: - try: - from chirp import dmrmarc - radio = dmrmarc.DMRMARCRadio(None) - radio.set_params(city, state, country) - self.do_open_live(radio, read_only=True) - except errors.RadioError as e: - common.show_error(e) - - self.window.set_cursor(None) - - def do_repeaterbook_political_prompt(self): - if not CONF.get_bool("has_seen_credit", "repeaterbook"): - d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) - d.set_markup("RepeaterBook\r\n" + - "North American Repeater Directory") - 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, state, county): - state = fips.FIPS_STATES[state.get_active_text()] - county.get_model().clear() - for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()): - county.append_text(fips_county) - county.value = list(sorted(fips.FIPS_COUNTIES[state].keys()))[0] - - state.widget.connect("changed", _changed, state, county) - - d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self) - d.add_field("State", state.widget) - d.add_field("County", county.widget) - d.add_field("Band", band.widget) - - r = d.run() - chose_state = state.get_active_text() - chose_county = county.get_active_text() - chose_band = band.get_active_text() - d.destroy() - if r != gtk.RESPONSE_OK: - return False - - code = fips.FIPS_STATES[chose_state] - county_id = fips.FIPS_COUNTIES[code][chose_county] - freq = RB_BANDS[chose_band] - CONF.set("state", str(code), "repeaterbook") - CONF.set("county", str(county_id), "repeaterbook") - CONF.set("band", str(freq), "repeaterbook") - - return True - - def do_repeaterbook_political(self, do_import): - self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if not self.do_repeaterbook_political_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://chirp.danplanet.com/query/rb/1.0/chirp" + \ - "?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 - with compat.py3safe(): - gtk.gdk.window_process_all_updates() - while gtk.events_pending(): - gtk.main_iteration(False) - - fn = tempfile.mktemp(".csv") - try: - chirp_common.urlretrieve(query, fn) - except Exception as e: - LOG.error('Failed to fetch %s: %s' % (query, e)) - if not os.path.exists(fn): - common.show_error(_("RepeaterBook query failed")) - self.window.set_cursor(None) - return - - try: - # Validate CSV - radio = repeaterbook.RBRadio(fn) - if radio.errors: - reporting.report_misc_error("repeaterbook", - ("query=%s\n" % query) + - ("\n") + - ("\n".join(radio.errors))) - except errors.InvalidDataError as e: - common.show_error(str(e)) - self.window.set_cursor(None) - return - except Exception as 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(fn) - else: - self.do_open_live(radio, read_only=True) - - def do_repeaterbook_proximity_prompt(self): - default_band = "--All--" - try: - code = int(CONF.get("band", "repeaterbook")) - for k, v in RB_BANDS.items(): - if code == v: - default_band = k - break - except: - pass - fields = {"1Location": (gtk.Entry(), lambda x: x.get_text()), - "2Distance": (gtk.Entry(), lambda x: x.get_text()), - "3Band": (miscwidgets.make_choice( - sorted(RB_BANDS.keys(), key=key_bands), - False, default_band), - lambda x: RB_BANDS[x.get_active_text()]), - } - - d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), - parent=self) - for k in sorted(fields.keys()): - widget = fields[k][0] - if isinstance(widget, miscwidgets.EditableChoiceBase): - widget = widget.widget - d.add_field(k[1:], widget) - if isinstance(fields[k][0], gtk.Entry): - fields[k][0].set_text( - CONF.get(k[1:].lower(), "repeaterbook") or "") - - while d.run() == gtk.RESPONSE_OK: - valid = True - for k, (widget, fn) in fields.items(): - try: - CONF.set(k[1:].lower(), str(fn(widget)), "repeaterbook") - continue - except: - 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_repeaterbook_proximity(self, do_import): - self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if not self.do_repeaterbook_proximity_prompt(): - self.window.set_cursor(None) - return - - loc = CONF.get("location", "repeaterbook") - - try: - dist = int(CONF.get("distance", "repeaterbook")) - except: - dist = 20 - - try: - band = int(CONF.get("band", "repeaterbook")) or '%' - band = str(band) - except: - band = '%' - - query = "http://chirp.danplanet.com/query/rb/1.0/app_direct" \ - "?loc=%s&band=%s&dist=%s" % (loc, band, dist) - print(query) - - # Do this in case the import process is going to take a while - # to make sure we process events leading up to this - with compat.py3safe(): - gtk.gdk.window_process_all_updates() - while gtk.events_pending(): - gtk.main_iteration(False) - - fn = tempfile.mktemp(".csv") - try: - chirp_common.urlretrieve(query, fn) - except Exception as e: - LOG.error('Failed to fetch %s: %s' % (query, e)) - if not os.path.exists(fn): - common.show_error(_("RepeaterBook query failed")) - self.window.set_cursor(None) - return - - try: - # Validate CSV - radio = repeaterbook.RBRadio(fn) - if radio.errors: - reporting.report_misc_error("repeaterbook", - ("query=%s\n" % query) + - ("\n") + - ("\n".join(radio.errors))) - except errors.InvalidDataError as e: - common.show_error(str(e)) - self.window.set_cursor(None) - return - except Exception as 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(fn) - 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") - try: - chirp_common.urlretrieve(url, fn) - except Exception as e: - LOG.error('Failed to fetch %s: %s' % (url, e)) - if not os.path.exists(fn): - common.show_error(_("Query failed")) - return - - class PRRadio(generic_csv.CSVRadio, - chirp_common.NetworkSourceRadio): - VENDOR = "przemienniki.net" - MODEL = "" - - try: - radio = PRRadio(fn) - except Exception as e: - common.show_error(str(e)) - return - - if do_import: - eset = self.get_current_editorset() - count = eset.do_import(fn) - 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/%s" % (zipcode, username, - passwd, 'US') - count = eset.do_import(rrstr) - else: - try: - from chirp import radioreference - radio = radioreference.RadioReferenceRadio(None) - radio.set_params(zipcode, username, passwd, 'US') - self.do_open_live(radio, read_only=True) - except errors.RadioError as e: - common.show_error(e) - - self.window.set_cursor(None) - - def do_radioreference_promptcanada(self): - fields = {"1Username": (gtk.Entry(), lambda x: x), - "2Password": (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_promptcounty(self, username, passwd): - """gotta get the fkn province list (they do change sometimes) - and the absurd arbitrary county list - """ - from chirp import radioreference - provincecounty = radioreference.RadioReferenceRadio(None) - provincecounty.set_params(None, username, passwd, None) - cancounties = provincecounty.do_getcanadacounties() - clist = cancounties[0] - provinces = cancounties[1] - default_prov = "" - default_county = "" - try: - code = int(CONF.get("province", "radioreference")) - for k, v in provinces.items(): - if code == v: - default_prov = k - break - code = int(CONF.get("county", "radioreference")) - for row in clist: - if code == row[2]: - default_county = row[3] - break - except: - pass - province = miscwidgets.make_choice(sorted(provinces.keys()), - False, default_prov) - counties = [] - for x in clist: - if x[1] == default_prov: - counties.append(x[3]) - county = miscwidgets.make_choice(counties, False, default_county) - - def _changed(box, county): - selectedprov = provinces[box.get_active_text()] - county.get_model().clear() - for list_county in clist: - if list_county[0] == selectedprov: - county.append_text(list_county[3]) - county.set_active(0) - province.connect("changed", _changed, county) - - d = inputdialog.FieldDialog( - title=_("RadioReference.com Province Query"), parent=self) - d.add_field("Province", province) - d.add_field("Region/City", county) - r = d.run() - d.destroy() - if r != gtk.RESPONSE_OK: - return False - - code = provinces[province.get_active_text()] - for row in clist: # this is absolutely not the right way to do this! - if row[3] == county.get_active_text() and \ - row[1] == province.get_active_text(): - county_id = row[2] - CONF.set("province", str(code), "radioreference") - CONF.set("county", str(county_id), "radioreference") - - return True - - def do_radioreferencecanada(self, do_import): - self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if not self.do_radioreference_promptcanada(): - self.window.set_cursor(None) - return - username = CONF.get("Username", "radioreference") - passwd = CONF.get("Password", "radioreference") - - self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) - if not self.do_radioreference_promptcounty(username, passwd): - self.window.set_cursor(None) - return - county = CONF.get("County", "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/%s" % (county, username, - passwd, 'CA') - count = eset.do_import(rrstr) - else: - try: - from chirp import radioreference - radio = radioreference.RadioReferenceRadio(None) - radio.set_params(county, username, passwd, 'CA') - self.do_open_live(radio, read_only=True) - except errors.RadioError as e: - common.show_error(e) - - self.window.set_cursor(None) - - def do_export(self): - types = [(_("CSV Files") + " (*.csv)", "csv"), - ] - - 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 - with compat.py3safe(): - 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 2019 CHIRP Software LLC") - d.set_website("http://chirp.danplanet.com") - 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" + - os.linesep + - "Chinese (Simplified): DuckSoft BH2UEP") - 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) - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - d = gtk.Dialog(title=_("Select Columns"), - parent=self, - buttons=buttons) - d.set_default_response(gtk.RESPONSE_OK) - d.set_alternative_button_order([gtk.RESPONSE_OK, - 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 = "" + _("Reporting is disabled") + "" - d.set_markup(markup) - msg = _("The reporting feature of CHIRP is designed to help " - "improve quality 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 please consider leaving " - "this feature enabled to help make CHIRP better!\n\n" - "Are you sure you want to disable this feature?") - 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_information(self, action): - CONF.set_bool("clone_information", - not action.get_active(), "noconfirm") - - 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", 'Chinese (Simplified)', 'Ukrainian', 'Turkish'] - 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, filen=None): - types = [(_("Python Modules") + " *.py", "*.py"), - (_("Modules") + " *.mod", "*.mod")] - - if filen is None: - 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() - - self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color('#ea6262')) - - try: - with file(filen) as module: - code = module.read() - 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 as 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 == "clearrecent": - self.do_clear_recently_opened() - elif action in ["qdmrmarc", "idmrmarc"]: - self.do_dmrmarc(action[0] == "i") - elif action in ["qrfinder", "irfinder"]: - self.do_rfinder(action[0] == "i") - elif action in ["qradioreference", "iradioreference"]: - self.do_radioreference(action[0] == "i") - elif action in ["qradioreferencecanada", "iradioreferencecanada"]: - self.do_radioreferencecanada(action[0] == "i") - elif action == "export": - self.do_export() - elif action in ["qrbookpolitical", "irbookpolitical"]: - self.do_repeaterbook_political(action[0] == "i") - elif action in ["qrbookproximity", "irbookproximity"]: - self.do_repeaterbook_proximity(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 = bandplan.BandPlans(CONF) - bandplans.select_bandplan(bp) - elif action == "no_smart_tmode": - self.do_toggle_no_smart_tmode(_action) - elif action == "developer": - self.do_toggle_developer(_action) - elif action == "clone_information": - self.do_toggle_clone_information(_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 = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" - ALT_KEY = "" - CTRL_KEY = "" - if sys.platform == 'darwin': - ALT_KEY = "" - CTRL_KEY = "" - 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, _("Open _Recent"), None, None, self.mh), - ('clearrecent', None, _("Clear Recently Opened"), 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"), - "%sx" % 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'), - "%sr" % CTRL_KEY, None, self.mh), - ('devdiffraw', None, _("Diff Raw Memories"), - "%sd" % CTRL_KEY, None, self.mh), - ('devdifftab', None, _("Diff Tabs"), - "%st" % 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"), "%se" % ALT_KEY, None, self.mh), - ('importsrc', None, _("Import From Data Source"), - None, None, self.mh), - ('idmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh), - ('iradioref', None, _("RadioReference"), None, None, self.mh), - ('iradioreference', None, _("RadioReference.com US"), - None, None, self.mh), - ('iradioreferencecanada', None, _("RadioReference.com Canada"), - None, None, self.mh), - ('irfinder', None, _("RFinder"), None, None, self.mh), - ('irbook', None, _("RepeaterBook"), None, None, self.mh), - ('irbookpolitical', None, _("RepeaterBook Political Query"), None, - None, self.mh), - ('irbookproximity', None, _("RepeaterBook Proximity Query"), None, - None, self.mh), - ('ipr', None, _("przemienniki.net"), None, None, self.mh), - ('querysrc', None, _("Query Data Source"), None, None, self.mh), - ('qdmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh), - ('qradioref', None, _("RadioReference"), None, None, self.mh), - ('qradioreference', None, _("RadioReference.com US"), - None, None, self.mh), - ('qradioreferencecanada', None, _("RadioReference.com Canada"), - 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), - ('qrbookpolitical', None, _("RepeaterBook Political Query"), None, - None, self.mh), - ('qrbookproximity', None, _("RepeaterBook Proximity Query"), 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") - cf = not conf.get_bool("clone_information", "noconfirm") - 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_information', None, _("Show Information"), - None, None, self.mh, cf), - ('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.recentmenu = self.menu_uim.get_widget( - "/MenuBar/file/recent") - - self.infomenu = self.menu_uim.get_widget( - "/MenuBar/help/clone_information") - - self.clonemenu = self.menu_uim.get_widget( - "/MenuBar/help/clone_instructions") - - # Initialize - self.do_toggle_developer(self.menu_ag.get_action("developer")) - self.recentmenu.connect("activate", - lambda a: self.update_recent_files()) - - 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.show() - box.pack_start(self.sb_general, 1, 1, 1) - - self.sb_radio = gtk.Statusbar() - self.sb_radio.show() - box.pack_start(self.sb_radio, 1, 1, 1) - - with compat.py3safe(quiet=True): - # Gtk2 had resize grips on the status bars, so remove them - # if we can - self.sb_general.set_has_resize_grip(False) - self.sb_radio.set_has_resize_grip(True) - - 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): - with importlib_resources.as_file( - importlib_resources.files("chirp.share") - .joinpath("chirp.png") - ) as path: - self.set_icon_from_file(str(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 three days - intv = 3600 * 24 * 3 - - 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_CANCEL, parent=self, - type=gtk.MESSAGE_INFO) - d.label.set_markup( - _('A new version of CHIRP is available: ' + - '{ver}. '.format(ver=version) + - 'It is recommended that you upgrade as soon as possible. ' - 'Please go to: \r\n\r\n' + - 'http://chirp.danplanet.com')) - response = d.run() - d.destroy() - if response == gtk.RESPONSE_OK: - webbrowser.open('http://chirp.danplanet.com/' - 'projects/chirp/wiki/Download') - - def _init_macos(self, menu_bar): - macapp = None - - # for KK7DS runtime <= R10 - try: - import gtk_osxapplication - macapp = gtk_osxapplication.OSXApplication() - except ImportError as e: - pass - - # for gtk-mac-integration >= 2.0.7 - try: - import gtkosx_application - macapp = gtkosx_application.Application() - except ImportError as e: - pass - - # for gtk-mac-integration 2.1.3 in brew - try: - from gi.repository import GtkosxApplication - macapp = GtkosxApplication.Application() - except ImportError: - pass - - if macapp is None: - LOG.error("No MacOS support") - return - - with importlib_resources.as_file( - importlib_resources.files("chirp.share") - .joinpath("chirp.png") - ) as icon: - icon_pixmap = gtk.gdk.pixbuf_new_from_file(icon) - macapp.set_dock_icon_pixbuf(icon_pixmap) - - 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=None): - allocation = window.get_allocation() - CONF.set_int("window_w", allocation.width, "state") - CONF.set_int("window_h", allocation.height, "state") - - with compat.py3safe(quiet=True): - # GTK3 does not have 'expose_event' and I am not sure which - # is a suitable replacement. We only need this to save window - # size in the config, so don't warn about it. - 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.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("" + - _("Error reporting is enabled") + - "") - d.format_secondary_markup( - _("If you wish to disable this feature you may do so in " - "the Help menu")) - d.run() - d.destroy() - CONF.set_bool("warned_about_reporting", True) - - self.recent_files = None - self.update_recent_files() - try: - self.update_stock_configs() - except UnicodeDecodeError: - LOG.exception('We hit bug #272 while working with unicode paths. ' - 'Not copying stock configs so we can continue ' - 'startup.') - 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) diff --git a/chirp/ui/memdetail.py b/chirp/ui/memdetail.py deleted file mode 100644 index abbe8c49..00000000 --- a/chirp/ui/memdetail.py +++ /dev/null @@ -1,449 +0,0 @@ -# Copyright 2012 Dan Smith -# -# 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 . - -import gtk -import os -import logging - -from chirp import chirp_common, settings -from chirp.ui import miscwidgets, common -from chirp.ui import compat - -LOG = logging.getLogger(__name__) - -POL = ["NN", "NR", "RN", "RR"] - - -class ValueEditor: - """Base class""" - def __init__(self, features, memory, errfn, name, data=None): - self._features = features - self._memory = memory - self._errfn = errfn - self._name = name - self._widget = None - self._init(data) - - def _init(self, data): - """Type-specific initialization""" - - def set_sensitive(self, sensitive): - self._widget.set_sensitive(sensitive) - - def get_widget(self): - """Returns the widget associated with this editor""" - return self._widget - - def _mem_value(self): - """Returns the raw value from the memory associated with this name""" - if self._name.startswith("extra_"): - return self._memory.extra[self._name.split("_", 1)[1]].value - else: - return getattr(self._memory, self._name) - - def _get_value(self): - """Returns the value from the widget that - should be set in the memory""" - - def update(self): - """Updates the memory object with self._getvalue()""" - - try: - newval = self._get_value() - except ValueError as e: - self._errfn(self._name, str(e)) - return str(e) - - if self._name.startswith("extra_"): - try: - extra_name = self._name.split("_", 1)[1] - LOG.debug('Setting extra %s=%r' % (extra_name, newval)) - self._memory.extra[extra_name].value = newval - except settings.InternalError as e: - self._errfn(self._name, str(e)) - return str(e) - else: - try: - setattr(self._memory, self._name, newval) - except chirp_common.ImmutableValueError as e: - if getattr(self._memory, self._name) != self._get_value(): - self._errfn(self._name, str(e)) - return str(e) - except ValueError as e: - self._errfn(self._name, str(e)) - return str(e) - - all_msgs = self._features.validate_memory(self._memory) - errs = [] - for msg in all_msgs: - if isinstance(msg, chirp_common.ValidationError): - errs.append(str(msg)) - if errs: - self._errfn(self._name, errs) - else: - self._errfn(self._name, None) - - -class StringEditor(ValueEditor): - def _init(self, data): - try: - self._widget = gtk.Entry(int(data)) - except TypeError: - self._widget = gtk.Entry() - self._widget.set_max_length(int(data)) - self._widget.set_text(str(self._mem_value())) - self._widget.connect("changed", self.changed) - - def _get_value(self): - return self._widget.get_text() - - def changed(self, _widget): - self.update() - - -class IntegerEditor(ValueEditor): - def _init(self, data): - self._widget = gtk.SpinButton() - adj = self._widget.get_adjustment() - adj.configure(data.get_value(), - data.get_min(), data.get_max(), - data.get_step(), 1, 0) - self._widget.connect('value-changed', self.changed) - - def changed(self, _widget): - self.update() - - def _get_value(self): - return int(self._widget.get_value()) - - -class ChoiceEditor(ValueEditor): - def _init(self, data): - self._choice = miscwidgets.make_choice([str(x) for x in data], - False, - str(self._mem_value())) - self._widget = self._choice.widget - - self._widget.connect("changed", self.changed) - - def _get_value(self): - return self._choice.value - - def changed(self, _widget): - self.update() - - -class PowerChoiceEditor(ChoiceEditor): - def _init(self, data): - self._choices = data - ChoiceEditor._init(self, data) - - def _get_value(self): - choice = self._widget.get_active_text() - for level in self._choices: - if str(level) == choice: - return level - raise Exception("Internal error: power level went missing") - - -class IntChoiceEditor(ChoiceEditor): - def _get_value(self): - return int(self._widget.get_active_text()) - - -class FloatChoiceEditor(ChoiceEditor): - def _get_value(self): - return float(self._widget.get_active_text()) - - -class FreqEditor(StringEditor): - def _init(self, data): - StringEditor._init(self, 0) - - def _mem_value(self): - return chirp_common.format_freq(StringEditor._mem_value(self)) - - def _get_value(self): - return chirp_common.parse_freq(self._widget.get_text()) - - -class BooleanEditor(ValueEditor): - def _init(self, data): - self._widget = gtk.CheckButton("Enabled") - self._widget.set_active(self._mem_value()) - self._widget.connect("toggled", self.toggled) - - def _get_value(self): - return self._widget.get_active() - - def toggled(self, _widget): - self.update() - - -class OffsetEditor(FreqEditor): - pass - - -class MemoryDetailEditor(gtk.Dialog): - """Detail editor for a memory""" - - def _add(self, tab, row, name, editor, text, colindex=0): - label = gtk.Label(text + ":") - label.set_alignment(1.0, 0.5) - label.show() - tab.attach(label, colindex, colindex + 1, row, row + 1, - xoptions=gtk.FILL, yoptions=0, xpadding=6, ypadding=3) - - widget = editor.get_widget() - widget.show() - tab.attach(widget, colindex + 1, colindex + 2, row, row + 1, - xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3) - - img = gtk.Image() - img.set_size_request(16, -1) - img.show() - tab.attach(img, colindex + 2, colindex + 3, row, row + 1, - xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3) - - self._editors[name] = label, editor, img - return label, editor, img - - def _set_doc(self, name, doc): - label, editor, _img = self._editors[name] - self._tips.set_tip(label, doc) - - def _make_ui(self): - - box = gtk.VBox() - box.show() - - notebook = gtk.Notebook() - notebook.set_show_border(False) - notebook.show() - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - sw.show() - - hbox = gtk.HBox() - hbox.pack_start(sw, 1, 1, 1) - hbox.show() - - tab = notebook.append_page(hbox, gtk.Label(_("General"))) - - table = gtk.Table(len(self._order), 4, False) - table.set_resize_mode(gtk.RESIZE_IMMEDIATE) - table.show() - sw.add_with_viewport(table) - - def _err(name, msg): - try: - _img = self._editors[name][2] - except KeyError: - LOG.error(self._editors.keys()) - if msg is None: - _img.clear() - self._tips.set_tip(_img, "") - else: - _img.set_from_stock(gtk.STOCK_DIALOG_WARNING, - gtk.ICON_SIZE_MENU) - self._tips.set_tip(_img, str(msg)) - self._errors[self._order.index(name)] = msg is not None - self.set_response_sensitive(gtk.RESPONSE_OK, - True not in self._errors) - - row = 0 - for name in self._order: - text, editorcls, data = self._elements[name] - editor = editorcls(self._features, self._memory, - _err, name, data) - - self._add(table, row, name, editor, text) - self._set_doc(name, text) - row += 1 - - if len(self._memory.extra): - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - sw.show() - - hbox = gtk.HBox() - hbox.pack_start(sw, 1, 1, 1) - hbox.show() - - tab = notebook.append_page(hbox, gtk.Label(_("Other"))) - - table = gtk.Table(len(self._memory.extra), 4, False) - table.set_resize_mode(gtk.RESIZE_IMMEDIATE) - table.show() - sw.add_with_viewport(table) - - for setting in self._memory.extra: - name = "extra_%s" % setting.get_name() - if isinstance(setting.value, - settings.RadioSettingValueBoolean): - editor = BooleanEditor(self._features, self._memory, - _err, name) - self._add(table, row, name, editor, - setting.get_shortname()) - self._set_doc(name, setting.__doc__) - elif isinstance(setting.value, - settings.RadioSettingValueList): - editor = ChoiceEditor(self._features, self._memory, _err, - name, setting.value.get_options()) - self._add(table, row, name, editor, - setting.get_shortname()) - self._set_doc(name, setting.__doc__) - elif isinstance(setting.value, - settings.RadioSettingValueInteger): - editor = IntegerEditor(self._features, self._memory, - _err, name, setting.value) - self._add(table, row, name, editor, - setting.get_shortname()) - row += 1 - self._order.append(name) - - self.vbox.pack_start(notebook, 1, 1, 1) - - def __init__(self, features, memory, parent=None): - self._memory = memory - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, - title="Memory Properties", - flags=gtk.DIALOG_MODAL, - parent=parent, - buttons=buttons) - self.set_default_response(gtk.RESPONSE_OK) - self.set_alternative_button_order([gtk.RESPONSE_OK, - gtk.RESPONSE_CANCEL]) - self.set_size_request(-1, 500) - self._tips = compat.CompatTooltips() - - self._features = features - - self._editors = {} - self._elements = { - "freq": (_("Frequency"), - FreqEditor, None), - "name": (_("Name"), - StringEditor, features.valid_name_length), - "tmode": (_("Tone Mode"), - ChoiceEditor, features.valid_tmodes), - "rtone": (_("Tone"), - FloatChoiceEditor, chirp_common.TONES), - "ctone": (_("ToneSql"), - FloatChoiceEditor, chirp_common.TONES), - "dtcs": (_("DTCS Code"), - IntChoiceEditor, chirp_common.DTCS_CODES), - "rx_dtcs": (_("RX DTCS Code"), - IntChoiceEditor, chirp_common.DTCS_CODES), - "dtcs_polarity": (_("DTCS Pol"), - ChoiceEditor, POL), - "cross_mode": (_("Cross mode"), - ChoiceEditor, features.valid_cross_modes), - "duplex": (_("Duplex"), - ChoiceEditor, features.valid_duplexes), - "offset": (_("Offset"), - OffsetEditor, None), - "mode": (_("Mode"), - ChoiceEditor, features.valid_modes), - "tuning_step": (_("Tune Step"), - FloatChoiceEditor, features.valid_tuning_steps), - "skip": (_("Skip"), - ChoiceEditor, features.valid_skips), - "power": (_("Power"), - PowerChoiceEditor, features.valid_power_levels), - "comment": (_("Comment"), - StringEditor, 256), - } - - self._order = [ - "freq", "name", "tmode", "rtone", "ctone", "cross_mode", - "dtcs", "rx_dtcs", "dtcs_polarity", "duplex", "offset", - "mode", "tuning_step", "skip", "power", "comment" - ] - - hide_rules = [ - ("name", features.has_name), - ("tmode", len(features.valid_tmodes) > 0), - ("ctone", features.has_ctone), - ("dtcs", features.has_dtcs), - ("rx_dtcs", features.has_rx_dtcs), - ("dtcs_polarity", features.has_dtcs_polarity), - ("cross_mode", "Cross" in features.valid_tmodes), - ("duplex", len(features.valid_duplexes) > 0), - ("offset", features.has_offset), - ("mode", len(features.valid_modes) > 0), - ("tuning_step", features.has_tuning_step), - ("skip", len(features.valid_skips) > 0), - ("power", features.valid_power_levels), - ("comment", features.has_comment), - ] - - for name, visible in hide_rules: - if not visible: - del self._elements[name] - self._order.remove(name) - - self._make_ui() - - self._errors = [False] * len(self._order) - - self.connect("response", self._validate) - - def _validate(self, _dialog, response): - if response == gtk.RESPONSE_OK: - all_msgs = self._features.validate_memory(self._memory) - errors = [] - for msg in all_msgs: - if isinstance(msg, chirp_common.ValidationError): - errors.append(msg) - if errors: - common.show_error_text(_("Memory validation failed:"), - os.linesep + - os.linesep.join(errors)) - self.emit_stop_by_name('response') - - def get_memory(self): - self._memory.empty = False - return self._memory - - -class MultiMemoryDetailEditor(MemoryDetailEditor): - - def __init__(self, features, memory, parent=None): - self._selections = dict() - super(MultiMemoryDetailEditor, self).__init__(features, memory, parent) - - def _toggle_selector(self, selector, *widgets): - for widget in widgets: - widget.set_sensitive(selector.get_active()) - - def _add(self, tab, row, name, editor, text): - - label, editor, img = super(MultiMemoryDetailEditor, self)._add( - tab, row, name, editor, text, 1) - - selector = gtk.CheckButton() - tab.attach(selector, 0, 1, row, row + 1, - xoptions=gtk.FILL, yoptions=0, xpadding=0, ypadding=3) - selector.show() - self._toggle_selector(selector, label, editor, img) - selector.connect("toggled", self._toggle_selector, label, editor, img) - self._selections[name] = selector - - def get_fields(self): - return [k for k, v in self._selections.items() if v.get_active()] diff --git a/chirp/ui/memedit.py b/chirp/ui/memedit.py deleted file mode 100644 index 1e8a4af0..00000000 --- a/chirp/ui/memedit.py +++ /dev/null @@ -1,1751 +0,0 @@ -# -# Copyright 2008 Dan Smith -# -# 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 . - -import threading - -import base64 -import gtk -import pango -from gobject import TYPE_INT, \ - TYPE_DOUBLE as TYPE_FLOAT, \ - TYPE_STRING, \ - TYPE_BOOLEAN, \ - TYPE_PYOBJECT, \ - TYPE_INT64 -import gobject -import pickle -import os -import logging - -import six - -from chirp.ui import common, shiftdialog, miscwidgets, config, memdetail -from chirp.ui import compat -from chirp import bandplan -from chirp import chirp_common, errors, directory, import_logic - -LOG = logging.getLogger(__name__) - - -if __name__ == "__main__": - import sys - sys.path.insert(0, "..") - - -def handle_toggle(_, path, store, col): - store[path][col] = not store[path][col] - - -class ValueErrorDialog(gtk.MessageDialog): - def __init__(self, exception, **args): - gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK, **args) - self.set_default_response(gtk.RESPONSE_OK) - self.set_property("text", _("Invalid value for this field")) - self.format_secondary_text(str(exception)) - - -def iter_prev(store, iter): - row = store.get_path(iter)[0] - if row == 0: - return None - return store.get_iter((row - 1,)) - - -class MemoryEditor(common.Editor): - cols = [ - (_("Loc"), TYPE_INT, gtk.CellRendererText,), - (_("Frequency"), TYPE_INT64, gtk.CellRendererText,), - (_("Name"), TYPE_STRING, gtk.CellRendererText,), - (_("Tone Mode"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Tone"), TYPE_FLOAT, gtk.CellRendererCombo,), - (_("ToneSql"), TYPE_FLOAT, gtk.CellRendererCombo,), - (_("DTCS Code"), TYPE_INT, gtk.CellRendererCombo,), - (_("DTCS Rx Code"), TYPE_INT, gtk.CellRendererCombo,), - (_("DTCS Pol"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Cross Mode"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Duplex"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Offset"), TYPE_INT64, gtk.CellRendererText,), - (_("Mode"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Power"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Tune Step"), TYPE_FLOAT, gtk.CellRendererCombo,), - (_("Skip"), TYPE_STRING, gtk.CellRendererCombo,), - (_("Comment"), TYPE_STRING, gtk.CellRendererText,), - ("_filled", TYPE_BOOLEAN, None,), - ("_hide_cols", TYPE_PYOBJECT, None,), - ("_extd", TYPE_STRING, None,), - ] - - defaults = { - _("Name"): "", - _("Frequency"): 146010000, - _("Tone"): 88.5, - _("ToneSql"): 88.5, - _("DTCS Code"): 23, - _("DTCS Rx Code"): 23, - _("DTCS Pol"): "NN", - _("Cross Mode"): "Tone->Tone", - _("Duplex"): "", - _("Offset"): 0, - _("Mode"): "FM", - _("Power"): "", - _("Tune Step"): 5.0, - _("Tone Mode"): "", - _("Skip"): "", - _("Comment"): "", - } - - choices = { - _("Tone"): chirp_common.TONES, - _("ToneSql"): chirp_common.TONES, - _("DTCS Code"): chirp_common.ALL_DTCS_CODES, - _("DTCS Rx Code"): chirp_common.ALL_DTCS_CODES, - _("DTCS Pol"): ["NN", "NR", "RN", "RR"], - _("Mode"): chirp_common.MODES, - _("Power"): [], - _("Duplex"): ["", "-", "+", "split", "off"], - _("Tune Step"): chirp_common.TUNING_STEPS, - _("Tone Mode"): ["", "Tone", "TSQL", "DTCS"], - _("Cross Mode"): chirp_common.CROSS_MODES, - } - - def ed_name(self, _, __, new, ___): - return self.rthread.radio.filter_name(new) - - def ed_offset(self, _, path, new, __): - new = chirp_common.parse_freq(new) - return abs(new) - - def ed_freq(self, _foo, path, new, colnum): - iter = self.store.get_iter(path) - was_filled, prev = self.store.get(iter, self.col("_filled"), colnum) - - def set_offset(offset): - if offset > 0: - dup = "+" - elif offset == 0: - dup = "" - else: - dup = "-" - offset *= -1 - - if dup not in self.choices[_("Duplex")]: - LOG.warn("Duplex %s not supported by this radio" % dup) - return - - if offset: - self.store.set(iter, self.col(_("Offset")), offset) - - self.store.set(iter, self.col(_("Duplex")), dup) - - def set_ts(ts): - if ts in self.choices[_("Tune Step")]: - self.store.set(iter, self.col(_("Tune Step")), ts) - else: - LOG.warn("Tune step %s not supported by this radio" % ts) - - def get_ts(path): - return self.store.get(iter, self.col(_("Tune Step")))[0] - - def set_mode(mode): - if mode in self.choices[_("Mode")]: - self.store.set(iter, self.col(_("Mode")), mode) - else: - LOG.warn("Mode %s not supported by this radio (%s)" % - (mode, self.choices[_("Mode")])) - - def set_tone(tone): - if tone in self.choices[_("Tone")]: - self.store.set(iter, self.col(_("Tone")), tone) - else: - LOG.warn("Tone %s not supported by this radio" % tone) - - try: - new = chirp_common.parse_freq(new) - except ValueError as e: - LOG.error("chirp_common.parse_freq error: %s", e) - new = None - - if not self._features.has_nostep_tuning: - set_ts(chirp_common.required_step(new)) - - is_changed = new != prev if was_filled else True - if new is not None and is_changed: - defaults = self.bandplans.get_defaults_for_frequency(new) - set_offset(defaults.offset or 0) - if defaults.step_khz: - set_ts(defaults.step_khz) - if defaults.mode: - set_mode(defaults.mode) - if defaults.tones: - set_tone(defaults.tones[0]) - - return new - - def ed_loc(self, _, path, new, __): - iter = self.store.get_iter(path) - curloc, = self.store.get(iter, self.col(_("Loc"))) - - job = common.RadioJob(None, "erase_memory", curloc) - job.set_desc(_("Erasing memory {loc}").format(loc=curloc)) - self.rthread.submit(job) - - self.need_refresh = True - - return new - - def ed_duplex(self, _foo1, path, new, _foo2): - if new == "": - return # Fast path outta here - - iter = self.store.get_iter(path) - freq, = self.store.get(iter, self.col(_("Frequency"))) - if new == "split": - # If we're going to split mode, use the current - # RX frequency as the default TX frequency - self.store.set(iter, self.col("Offset"), freq) - else: - defaults = self.bandplans.get_defaults_for_frequency(freq) - offset = defaults.offset or 0 - self.store.set(iter, self.col(_("Offset")), abs(offset)) - - return new - - def ed_tone_field(self, _foo, path, new, col): - if self._config.get_bool("no_smart_tmode"): - return new - - iter = self.store.get_iter(path) - - # Python scoping hurts us here, so store this as a list - # that we can modify, instead of helpful variables :( - modes = list(self.store.get(iter, - self.col(_("Tone Mode")), - self.col(_("Cross Mode")))) - - def _tm(*tmodes): - if modes[0] not in tmodes: - modes[0] = tmodes[0] - self.store.set(iter, self.col(_("Tone Mode")), modes[0]) - - def _cm(*cmodes): - if modes[0] == "Cross" and modes[1] not in cmodes: - modes[1] = cmodes[0] - self.store.set(iter, self.col(_("Cross Mode")), modes[1]) - - if col == self.col(_("DTCS Code")): - _tm("DTCS", "Cross") - _cm(*tuple([x for x in chirp_common.CROSS_MODES - if x.startswith("DTCS->")])) - elif col == self.col(_("DTCS Rx Code")): - _tm("Cross") - _cm(*tuple([x for x in chirp_common.CROSS_MODES - if x.endswith("->DTCS")])) - elif col == self.col(_("DTCS Pol")): - _tm("DTCS", "Cross") - _cm(*tuple([x for x in chirp_common.CROSS_MODES - if "DTCS" in x])) - elif col == self.col(_("Tone")): - _tm("Tone", "Cross") - _cm(*tuple([x for x in chirp_common.CROSS_MODES - if x.startswith("Tone->")])) - elif col == self.col(_("ToneSql")): - _tm("TSQL", "Cross") - _cm(*tuple([x for x in chirp_common.CROSS_MODES - if x.endswith("->Tone")])) - elif col == self.col(_("Cross Mode")): - _tm("Cross") - - return new - - def _get_cols_to_hide(self, iter): - tmode, duplex, cmode = self.store.get(iter, - self.col(_("Tone Mode")), - self.col(_("Duplex")), - self.col(_("Cross Mode"))) - - hide = [] - txmode, rxmode = cmode.split("->") - - if tmode == "Tone": - hide += [self.col(_("ToneSql")), - self.col(_("DTCS Code")), - self.col(_("DTCS Rx Code")), - self.col(_("DTCS Pol")), - self.col(_("Cross Mode"))] - elif tmode == "TSQL" or tmode == "TSQL-R": - if self._features.has_ctone: - hide += [self.col(_("Tone"))] - - hide += [self.col(_("DTCS Code")), - self.col(_("DTCS Rx Code")), - self.col(_("DTCS Pol")), - self.col(_("Cross Mode"))] - elif tmode == "DTCS" or tmode == "DTCS-R": - hide += [self.col(_("Tone")), - self.col(_("ToneSql")), - self.col(_("Cross Mode")), - self.col(_("DTCS Rx Code"))] - elif tmode == "" or tmode == "(None)": - hide += [self.col(_("Tone")), - self.col(_("ToneSql")), - self.col(_("DTCS Code")), - self.col(_("DTCS Rx Code")), - self.col(_("DTCS Pol")), - self.col(_("Cross Mode"))] - elif tmode == "Cross": - if txmode != "Tone": - hide += [self.col(_("Tone"))] - if txmode != "DTCS": - hide += [self.col(_("DTCS Code"))] - if rxmode != "Tone": - hide += [self.col(_("ToneSql"))] - if rxmode != "DTCS": - hide += [self.col(_("DTCS Rx Code"))] - if "DTCS" not in cmode: - hide += [self.col(_("DTCS Pol"))] - - if duplex == "" or duplex == "(None)" or duplex == "off": - hide += [self.col(_("Offset"))] - - return hide - - def maybe_hide_cols(self, iter): - hide_cols = self._get_cols_to_hide(iter) - self.store.set(iter, self.col("_hide_cols"), hide_cols) - - def edited(self, rend, path, new, cap): - if self.read_only: - common.show_error(_("Unable to make changes to this model")) - return - - iter = self.store.get_iter(path) - vals = self.store.get(iter, *range(0, len(self.cols))) - prev, = self.store.get(iter, self.col(cap)) - filled, = self.store.get(iter, self.col("_filled")) - freq, = self.store.get(iter, self.col(_("Frequency"))) - - # Take default values, if not yet edited - if not filled and freq == 0: - self.insert_new(iter) - - colnum = self.col(cap) - funcs = { - _("Loc"): self.ed_loc, - _("Name"): self.ed_name, - _("Frequency"): self.ed_freq, - _("Duplex"): self.ed_duplex, - _("Offset"): self.ed_offset, - _("Tone"): self.ed_tone_field, - _("ToneSql"): self.ed_tone_field, - _("DTCS Code"): self.ed_tone_field, - _("DTCS Rx Code"): self.ed_tone_field, - _("DTCS Pol"): self.ed_tone_field, - _("Cross Mode"): self.ed_tone_field, - } - - # Call the specific editor for the given column - if cap in funcs: - new = funcs[cap](rend, path, new, colnum) - - # Sanity check - if new is None: - LOG.error("Bad value for {col}: {val}".format(col=cap, val=new)) - return - - # Convert type based on column - if self.store.get_column_type(colnum) == TYPE_INT: - new = int(new) - elif self.store.get_column_type(colnum) == TYPE_FLOAT: - new = float(new) - elif self.store.get_column_type(colnum) == TYPE_BOOLEAN: - new = bool(new) - elif self.store.get_column_type(colnum) == TYPE_STRING: - if new == "(None)": - new = "" - - # Verify a change was made - if new == prev: - # Restore all columns to their original values, a given editor - # may modify multiple columns so we need to restore each of them - # to their previous values in order to restore the original state. - self._restore(iter, vals) - return - - # Finally, set the new value - self.store.set(iter, self.col(cap), new) - - # Have the radio validate the memory - mem = self._get_memory(iter) - msgs = self.rthread.radio.validate_memory(mem) - if msgs: - common.show_error(_("Error setting memory") + ": " + - "\r\n\r\n".join(msgs)) - # Restore to original values and exit - self._restore(iter, vals) - return - - mem.empty = False - - # Kick off job to write memory - job = common.RadioJob(self._set_memory_cb, "set_memory", mem) - job.set_desc(_("Writing memory {number}").format(number=mem.number)) - self.rthread.submit(job) - - self.store.set(iter, self.col("_filled"), True) - - self.maybe_hide_cols(iter) - - persist_defaults = [_("Power"), _("Frequency"), _("Mode")] - if cap in persist_defaults: - self.defaults[cap] = new - - def _render(self, colnum, val, iter=None, hide=[]): - if colnum in hide and self.hide_unused: - return "" - - if colnum == self.col(_("Frequency")): - val = chirp_common.format_freq(val) - elif colnum in [self.col(_("DTCS Code")), self.col(_("DTCS Rx Code"))]: - val = "%03i" % int(val) - elif colnum == self.col(_("Offset")): - val = chirp_common.format_freq(val) - elif colnum in [self.col(_("Tone")), self.col(_("ToneSql"))]: - val = "%.1f" % val - elif colnum in [self.col(_("Tone Mode")), self.col(_("Duplex"))]: - if not val: - val = "(None)" - elif colnum == self.col(_("Loc")) and iter is not None: - extd, = self.store.get(iter, self.col("_extd")) - if extd: - val = extd - - return str(val) - - def render(self, _, rend, model, iter, colnum): - val, hide, filled = model.get(iter, colnum, self.col("_hide_cols"), - self.col('_filled')) - val = self._render(colnum, val, iter, hide or []) - rend.set_property("text", "%s" % val) - rend.set_sensitive(filled) - - def insert_new(self, iter, loc=None): - line = [] - for key, val in self.defaults.items(): - line.append(self.col(key)) - line.append(val) - - if not loc: - loc, = self.store.get(iter, self.col(_("Loc"))) - - self.store.set(iter, - 0, loc, - *tuple(line)) - - return self._get_memory(iter) - - def insert_easy(self, store, _iter, delta): - if delta < 0: - iter = store.insert_before(_iter) - else: - iter = store.insert_after(_iter) - - newpos, = store.get(_iter, self.col(_("Loc"))) - newpos += delta - - LOG.debug("Insert easy: %i" % delta) - - mem = self.insert_new(iter, newpos) - job = common.RadioJob(None, "set_memory", mem) - job.set_desc(_("Writing memory {number}").format(number=mem.number)) - self.rthread.submit(job) - - def insert_hard(self, store, _iter, delta, warn=True): - if isinstance(self.rthread.radio, chirp_common.LiveRadio) and warn: - txt = _("This operation requires moving all subsequent channels " - "by one spot until an empty location is reached. This " - "can take a LONG time. Are you sure you want to do this?") - if not common.ask_yesno_question(txt): - return False # No change - - if delta <= 0: - iter = _iter - else: - iter = store.iter_next(_iter) - - pos, = store.get(iter, self.col(_("Loc"))) - - sd = shiftdialog.ShiftDialog(self.rthread) - - if delta == 0: - sd.delete(pos) - sd.destroy() - self.prefill() - else: - sd.insert(pos) - sd.destroy() - job = common.RadioJob( - lambda x: self.prefill(), "erase_memory", pos) - job.set_desc(_("Adding memory {number}").format(number=pos)) - self.rthread.submit(job) - - return True # We changed memories - - def _delete_rows(self, paths): - to_remove = [] - for path in paths: - iter = self.store.get_iter(path) - cur_pos, = self.store.get(iter, self.col(_("Loc"))) - to_remove.append(cur_pos) - self.store.set(iter, self.col("_filled"), False) - job = common.RadioJob(None, "erase_memory", cur_pos) - job.set_desc(_("Erasing memory {number}").format(number=cur_pos)) - self.rthread.submit(job) - - def handler(mem): - if not isinstance(mem, Exception): - if not mem.empty or self.show_empty: - gobject.idle_add(self.set_memory, mem) - - job = common.RadioJob(handler, "get_memory", cur_pos) - job.set_desc(_("Getting memory {number}").format(number=cur_pos)) - self.rthread.submit(job) - - if not self.show_empty: - # We need to actually remove the rows from the store - # now, but carefully! Get a list of deleted locations - # in order and proceed from the first path in the list - # until we run out of rows or we've removed all the - # desired ones. - to_remove.sort() - to_remove.reverse() - iter = self.store.get_iter(paths[0]) - while to_remove and iter: - pos, = self.store.get(iter, self.col(_("Loc"))) - if pos in to_remove: - to_remove.remove(pos) - if not self.store.remove(iter): - break # This was the last row - else: - iter = self.store.iter_next(iter) - - return True # We changed memories - - def _delete_rows_and_shift(self, paths, all=False): - iter = self.store.get_iter(paths[0]) - starting_loc, = self.store.get(iter, self.col(_("Loc"))) - for i in range(0, len(paths)): - sd = shiftdialog.ShiftDialog(self.rthread) - sd.delete(starting_loc, quiet=True, all=all) - sd.destroy() - - self.prefill() - return True # We changed memories - - def _move_up_down(self, paths, action): - if action.endswith("up"): - delta = -1 - donor_path = paths[-1] - victim_path = paths[0] - else: - delta = 1 - donor_path = paths[0] - victim_path = paths[-1] - - try: - victim_path = (victim_path[0] + delta,) - if victim_path[0] < 0: - raise ValueError() - donor_loc = self.store.get(self.store.get_iter(donor_path), - self.col(_("Loc")))[0] - victim_loc = self.store.get(self.store.get_iter(victim_path), - self.col(_("Loc")))[0] - except ValueError: - self.emit("usermsg", "No room to %s" % (action.replace("_", " "))) - return False # No change - - class Context: - pass - ctx = Context() - - ctx.victim_mem = None - ctx.donor_loc = donor_loc - ctx.done_count = 0 - ctx.path_count = len(paths) - - # Steps: - # 1. Grab the victim (the one that will need to be saved and moved - # from the front to the back or back to the front) and save it - # 2. Grab each memory along the way, storing it in the +delta - # destination location after we get it - # 3. If we're the final move, then schedule storing the victim - # in the hole we created - - def update_selection(): - sel = self.view.get_selection() - sel.unselect_all() - for path in paths: - gobject.idle_add(sel.select_path, (path[0]+delta,)) - - def save_victim(mem, ctx): - ctx.victim_mem = mem - - def store_victim(mem, dest): - old = mem.number - mem.number = dest - job = common.RadioJob(None, "set_memory", mem) - job.set_desc( - _("Moving memory from {old} to {new}").format(old=old, - new=dest)) - self.rthread.submit(job) - self._set_memory(self.store.get_iter(donor_path), mem) - update_selection() - - def move_mem(mem, delta, ctx, iter): - old = mem.number - mem.number += delta - job = common.RadioJob(None, "set_memory", mem) - job.set_desc( - _("Moving memory from {old} to {new}").format(old=old, - new=old+delta)) - self.rthread.submit(job) - self._set_memory(iter, mem) - ctx.done_count += 1 - if ctx.done_count == ctx.path_count: - store_victim(ctx.victim_mem, ctx.donor_loc) - - job = common.RadioJob(lambda m: save_victim(m, ctx), - "get_memory", victim_loc) - job.set_desc(_("Getting memory {number}").format(number=victim_loc)) - self.rthread.submit(job) - - for i in range(len(paths)): - path = paths[i] - if delta > 0: - dest = i+1 - else: - dest = i-1 - - if dest < 0 or dest >= len(paths): - dest = victim_path - else: - dest = paths[dest] - - iter = self.store.get_iter(path) - loc, = self.store.get(iter, self.col(_("Loc"))) - job = common.RadioJob(move_mem, "get_memory", loc) - job.set_cb_args(delta, ctx, self.store.get_iter(dest)) - job.set_desc("Getting memory %i" % loc) - self.rthread.submit(job) - - return True # We (scheduled some) change to the memories - - def _exchange_memories(self, paths): - if len(paths) != 2: - self.emit("usermsg", "Select two memories first") - return False - - loc_a, = self.store.get(self.store.get_iter(paths[0]), - self.col(_("Loc"))) - loc_b, = self.store.get(self.store.get_iter(paths[1]), - self.col(_("Loc"))) - - def store_mem(mem, dst): - src = mem.number - mem.number = dst - job = common.RadioJob(None, "set_memory", mem) - job.set_desc( - _("Moving memory from {old} to {new}").format( - old=src, new=dst)) - self.rthread.submit(job) - if dst == loc_a: - self.prefill() - - job = common.RadioJob(lambda m: store_mem(m, loc_b), - "get_memory", loc_a) - job.set_desc(_("Getting memory {number}").format(number=loc_a)) - self.rthread.submit(job) - - job = common.RadioJob(lambda m: store_mem(m, loc_a), - "get_memory", loc_b) - job.set_desc(_("Getting memory {number}").format(number=loc_b)) - self.rthread.submit(job) - - # We (scheduled some) change to the memories - return True - - def _show_raw(self, cur_pos): - def idle_show_raw(result): - gobject.idle_add(common.show_diff_blob, - _("Raw memory {number}").format( - number=cur_pos), result) - - job = common.RadioJob(idle_show_raw, "get_raw_memory", cur_pos) - job.set_desc(_("Getting raw memory {number}").format(number=cur_pos)) - self.rthread.submit(job) - - def _diff_raw(self, paths): - if len(paths) != 2: - common.show_error(_("You can only diff two memories!")) - return - - loc_a = self.store.get(self.store.get_iter(paths[0]), - self.col(_("Loc")))[0] - loc_b = self.store.get(self.store.get_iter(paths[1]), - self.col(_("Loc")))[0] - - raw = {} - - def diff_raw(which, result): - raw[which] = _("Memory {number}").format(number=which) + \ - os.linesep + result - - if len(raw.keys()) == 2: - diff = common.simple_diff(raw[loc_a], raw[loc_b]) - gobject.idle_add(common.show_diff_blob, - _("Diff of {a} and {b}").format(a=loc_a, - b=loc_b), - diff) - - job = common.RadioJob(lambda r: diff_raw(loc_a, r), - "get_raw_memory", loc_a) - job.set_desc(_("Getting raw memory {number}").format(number=loc_a)) - self.rthread.submit(job) - - job = common.RadioJob(lambda r: diff_raw(loc_b, r), - "get_raw_memory", loc_b) - job.set_desc(_("Getting raw memory {number}").format(number=loc_b)) - self.rthread.submit(job) - - def _copy_field(self, src_memory, dst_memory, field): - if field.startswith("extra_"): - field = field.split("_", 1)[1] - value = src_memory.extra[field].value.get_value() - dst_memory.extra[field].value = value - else: - setattr(dst_memory, field, getattr(src_memory, field)) - - def _apply_multiple(self, src_memory, fields, locations): - for location in locations: - def apply_and_set(memory): - for field in fields: - self._copy_field(src_memory, memory, field) - cb = (memory.number == locations[-1] and - self._set_memory_cb or None) - job = common.RadioJob(cb, "set_memory", memory) - job.set_desc(_("Writing memory {number}").format( - number=memory.number)) - self.rthread.submit(job) - job = common.RadioJob(apply_and_set, "get_memory", location) - job.set_desc(_("Getting original memory {number}").format( - number=location)) - self.rthread.submit(job) - - def edit_memory(self, memory, locations): - if len(locations) > 1: - dlg = memdetail.MultiMemoryDetailEditor(self._features, memory) - else: - dlg = memdetail.MemoryDetailEditor(self._features, memory) - r = dlg.run() - if r == gtk.RESPONSE_OK: - self.need_refresh = True - mem = dlg.get_memory() - if len(locations) > 1: - self._apply_multiple(memory, dlg.get_fields(), locations) - else: - if "name" not in mem.immutable: - mem.name = self.rthread.radio.filter_name(mem.name) - job = common.RadioJob(self._set_memory_cb, "set_memory", mem) - job.set_desc(_("Writing memory {number}").format( - number=mem.number)) - self.rthread.submit(job) - dlg.destroy() - - def mh(self, _action, store, paths): - action = _action.get_name() - selected = [] - for path in paths: - iter = store.get_iter(path) - loc, = store.get(iter, self.col(_("Loc"))) - selected.append(loc) - cur_pos = selected[0] - - require_contiguous = ["delete_s", "move_up", "move_dn"] - if action in require_contiguous: - last = paths[0][0] - for path in paths[1:]: - if path[0] != last+1: - self.emit("usermsg", _("Memories must be contiguous")) - return - last = path[0] - - changed = False - - if action == "insert_next": - changed = self.insert_hard(store, iter, 1) - elif action == "insert_prev": - changed = self.insert_hard(store, iter, -1) - elif action == "delete": - changed = self._delete_rows(paths) - elif action == "delete_s": - changed = self._delete_rows_and_shift(paths) - elif action == "delete_sall": - changed = self._delete_rows_and_shift(paths, all=True) - elif action in ["move_up", "move_dn"]: - changed = self._move_up_down(paths, action) - elif action == "exchange": - changed = self._exchange_memories(paths) - elif action in ["cut", "copy"]: - changed = self.copy_selection(action == "cut") - elif action == "paste": - changed = self.paste_selection() - elif action == "all": - changed = self.select_all() - elif action == "devshowraw": - self._show_raw(cur_pos) - elif action == "devdiffraw": - self._diff_raw(paths) - elif action == "properties": - job = common.RadioJob(self.edit_memory, "get_memory", cur_pos) - job.set_cb_args(selected) - self.rthread.submit(job) - - if changed: - self.emit("changed") - - def hotkey(self, action): - if self._in_editing: - # Don't forward potentially-dangerous hotkeys to the menu - # handler if we're editing a cell right now - return - - self.emit("usermsg", "") - (store, paths) = self.view.get_selection().get_selected_rows() - if len(paths) == 0: - return - self.mh(action, store, paths) - - def make_context_menu(self): - if self._config.get_bool("developer", "state"): - devmenu = """ - - - -""" - else: - devmenu = "" - - menu_xml = """ - - - - - - - - - - - - - - - - - - - - - %s - - -""" % devmenu - - (store, paths) = self.view.get_selection().get_selected_rows() - issingle = len(paths) == 1 - istwo = len(paths) == 2 - - actions = [ - ("cut", _("Cut")), - ("copy", _("Copy")), - ("paste", _("Paste")), - ("all", _("Select All")), - ("insert_prev", _("Insert row above")), - ("insert_next", _("Insert row below")), - ("deletes", _("Delete")), - ("delete", issingle and _("this memory") or _("these memories")), - ("delete_s", _("...and shift block up")), - ("delete_sall", _("...and shift all memories up")), - ("move_up", _("Move up")), - ("move_dn", _("Move down")), - ("exchange", _("Exchange memories")), - ("properties", _("P_roperties")), - ("devshowraw", _("Show Raw Memory")), - ("devdiffraw", _("Diff Raw Memories")), - ] - - no_multiple = ["insert_prev", "insert_next", "paste", "devshowraw"] - only_two = ["devdiffraw", "exchange"] - - ag = gtk.ActionGroup("Menu") - - for name, label in actions: - a = gtk.Action(name, label, "", 0) - a.connect("activate", self.mh, store, paths) - if name in no_multiple: - a.set_sensitive(issingle) - if name in only_two: - a.set_sensitive(istwo) - ag.add_action(a) - - if issingle: - iter = store.get_iter(paths[0]) - cur_pos, = store.get(iter, self.col(_("Loc"))) - if cur_pos == self._features.memory_bounds[1]: - ag.get_action("delete_s").set_sensitive(False) - - uim = gtk.UIManager() - uim.insert_action_group(ag, 0) - uim.add_ui_from_string(menu_xml) - - return uim.get_widget("/Menu") - - def double_click_empty_memory(self, treeiter, path, col): - loc = self.store.get(treeiter, self.col(_('Loc'))) - # Make this filled=True, thus sensitive=True - self.store.set(treeiter, self.col(_('_filled')), True) - # Set the cursor and stat editing - self.view.set_cursor(path, col, True) - - def click_cb(self, view, event): - self.emit("usermsg", "") - - pathinfo = view.get_path_at_pos(int(event.x), int(event.y)) - if pathinfo is None: - return - path, col, x, y = pathinfo - treeiter = self.store.get_iter(path) - - if hasattr(gtk.gdk.EventType, '_2BUTTON_PRESS'): - double_click = event.type == gtk.gdk.EventType._2BUTTON_PRESS - else: - double_click = False - - if event.button == 1 and double_click: - filled, = self.store.get(treeiter, self.col(_('_filled'))) - if not filled: - self.double_click_empty_memory(treeiter, path, col) - return True - elif event.button == 3: - view.set_cursor(path, col) - menu = self.make_context_menu() - try: - menu.popup(None, None, None, event.button, event.time) - except TypeError: - # GTK3 - menu.popup(None, None, None, None, event.button, - event.time) - return True - - def get_column_visible(self, col): - column = self.view.get_column(col) - return column.get_visible() - - def set_column_visible(self, col, visible): - column = self.view.get_column(col) - column.set_visible(visible) - - def cell_editing_started(self, rend, event, path): - self._in_editing = True - self._edit_path = self.view.get_cursor() - - def cell_editing_stopped(self, *args): - self._in_editing = False - self.view.grab_focus() - self.view.set_cursor(*self._edit_path) - - def make_editor(self): - types = tuple([x[1] for x in self.cols]) - self.store = gtk.ListStore(*types) - - self.view = gtk.TreeView(self.store) - self.view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.view.set_rules_hint(True) - - hbox = gtk.HBox() - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(self.view) - - filled = self.col("_filled") - - default_col_order = [x for x, y, z in self.cols if z] - try: - config_setting = self._config.get("column_order_%s" % - self.__class__.__name__) - if config_setting is None: - col_order = default_col_order - else: - col_order = config_setting.split(",") - if len(col_order) != len(default_col_order): - raise Exception() - for i in col_order: - if i not in default_col_order: - raise Exception() - except Exception as e: - LOG.error("column order setting: %s", e) - col_order = default_col_order - - non_editable = [_("Loc")] - - unsupported_cols = self.get_unsupported_columns() - visible_cols = self.get_columns_visible() - - self._renderers = {} - cols = {} - i = 0 - for _cap, _type, _rend in self.cols: - if not _rend: - continue - rend = _rend() - self._renderers[_cap] = rend - rend.connect('editing-started', self.cell_editing_started) - rend.connect('editing-canceled', self.cell_editing_stopped) - rend.connect('edited', self.cell_editing_stopped) - - if _type == TYPE_BOOLEAN: - # rend.set_property("activatable", True) - # rend.connect("toggled", handle_toggle, self.store, i) - col = gtk.TreeViewColumn(_cap, rend, active=i, - sensitive=filled) - elif _rend == gtk.CellRendererCombo: - if isinstance(self.choices[_cap], gtk.ListStore): - choices = self.choices[_cap] - else: - choices = gtk.ListStore(TYPE_STRING, TYPE_STRING) - for choice in self.choices[_cap]: - choices.append([str(choice), self._render(i, choice)]) - rend.set_property("model", choices) - rend.set_property("text-column", 1) - rend.set_property("editable", True) - rend.set_property("has-entry", False) - rend.connect("edited", self.edited, _cap) - col = gtk.TreeViewColumn(_cap, rend, text=i) - col.set_cell_data_func(rend, self.render, i) - else: - rend.set_property("editable", _cap not in non_editable) - rend.connect("edited", self.edited, _cap) - col = gtk.TreeViewColumn(_cap, rend, text=i) - col.set_cell_data_func(rend, self.render, i) - - col.set_reorderable(True) - col.set_sort_column_id(i) - col.set_resizable(True) - col.set_min_width(1) - col.set_visible(not _cap.startswith("_") and - _cap in visible_cols and - _cap not in unsupported_cols) - cols[_cap] = col - i += 1 - - for cap in col_order: - self.view.append_column(cols[cap]) - - self.store.set_sort_column_id(self.col(_("Loc")), gtk.SORT_ASCENDING) - - self.view.show() - sw.show() - hbox.pack_start(sw, 1, 1, 1) - - self.view.connect("button_press_event", self.click_cb) - - hbox.show() - - return hbox - - def col(self, caption): - try: - return self._cached_cols[caption] - except KeyError: - raise Exception( - _("Internal Error: Column {name} not found").format( - name=caption)) - - def rend(self, caption): - try: - return self._renderers[caption] - except KeyError: - print(self._renderers) - raise Exception( - _('Internal Error: Renderer for column %s not found') % ( - caption)) - - def _prefill(self, num): - def handler(mem, number): - if not isinstance(mem, Exception): - if not mem.empty or self.show_empty: - gobject.idle_add(self.set_memory, mem) - else: - mem = chirp_common.Memory(number, True, "Error") - gobject.idle_add(self.set_memory, mem) - - job = common.RadioJob(handler, "get_memory", num) - job.set_desc(_("Getting memory {number}").format(number=num)) - job.set_cb_args(num) - self.rthread.submit(job, 2) - - def prefill(self): - self.store.clear() - self._rows_in_store = 0 - - lo = int(self.lo_limit_adj.get_value()) - hi = int(self.hi_limit_adj.get_value()) - - for i in range(lo, hi+1): - self._prefill(i) - - if self.show_special: - for i in self._features.valid_special_chans: - self._prefill(i) - - def _restore(self, iter, vals): - for col, val in enumerate(vals): - self.store.set(iter, col, val) - - def _set_memory(self, iter, memory): - self.store.set(iter, - self.col("_filled"), not memory.empty, - self.col(_("Loc")), memory.number, - self.col("_extd"), memory.extd_number, - self.col(_("Name")), memory.name, - self.col(_("Frequency")), memory.freq, - self.col(_("Tone Mode")), memory.tmode, - self.col(_("Tone")), memory.rtone, - self.col(_("ToneSql")), memory.ctone, - self.col(_("DTCS Code")), memory.dtcs, - self.col(_("DTCS Rx Code")), memory.rx_dtcs, - self.col(_("DTCS Pol")), memory.dtcs_polarity, - self.col(_("Cross Mode")), memory.cross_mode, - self.col(_("Duplex")), memory.duplex, - self.col(_("Offset")), memory.offset, - self.col(_("Mode")), memory.mode, - self.col(_("Power")), (memory.power and - str(memory.power) or - ""), - self.col(_("Tune Step")), memory.tuning_step, - self.col(_("Skip")), memory.skip, - self.col(_("Comment")), memory.comment) - - hide = self._get_cols_to_hide(iter) - self.store.set(iter, self.col("_hide_cols"), hide) - - def set_memory(self, memory): - iter = self.store.get_iter_first() - - while iter is not None: - loc, = self.store.get(iter, self.col(_("Loc"))) - if loc == memory.number: - return self._set_memory(iter, memory) - - iter = self.store.iter_next(iter) - - iter = self.store.append() - self._rows_in_store += 1 - self._set_memory(iter, memory) - - def clear_memory(self, number): - iter = self.store.get_iter_first() - while iter: - loc, = self.store.get(iter, self.col(_("Loc"))) - if loc == number: - LOG.debug("Deleting %i" % number) - # FIXME: Make the actual remove happen on callback - self.store.remove(iter) - job = common.RadioJob(None, "erase_memory", number) - job.set_desc( - _("Erasing memory {number}").format(number=number)) - self.rthread.submit() - break - iter = self.store.iter_next(iter) - - def _set_mem_vals(self, mem, vals, iter): - power_levels = {"": None} - for i in self._features.valid_power_levels: - power_levels[str(i)] = i - - mem.freq = vals[self.col(_("Frequency"))] - mem.number = vals[self.col(_("Loc"))] - mem.extd_number = vals[self.col("_extd")] - mem.name = vals[self.col(_("Name"))] - mem.vfo = 0 - mem.rtone = vals[self.col(_("Tone"))] - mem.ctone = vals[self.col(_("ToneSql"))] - mem.dtcs = vals[self.col(_("DTCS Code"))] - mem.rx_dtcs = vals[self.col(_("DTCS Rx Code"))] - mem.tmode = vals[self.col(_("Tone Mode"))] - mem.cross_mode = vals[self.col(_("Cross Mode"))] - mem.dtcs_polarity = vals[self.col(_("DTCS Pol"))] - mem.duplex = vals[self.col(_("Duplex"))] - mem.offset = vals[self.col(_("Offset"))] - mem.mode = vals[self.col(_("Mode"))] - mem.power = power_levels[vals[self.col(_("Power"))]] - mem.tuning_step = vals[self.col(_("Tune Step"))] - mem.skip = vals[self.col(_("Skip"))] - mem.comment = vals[self.col(_("Comment"))] - mem.empty = not vals[self.col("_filled")] - - def _get_memory(self, iter): - vals = self.store.get(iter, *range(0, len(self.cols))) - mem = chirp_common.Memory() - self._set_mem_vals(mem, vals, iter) - - return mem - - def _limit_key(self, which): - if which not in ["lo", "hi"]: - raise Exception(_("Internal Error: Invalid limit {number}").format( - number=which)) - return "%s_%s" % \ - (directory.radio_class_id(self.rthread.radio.__class__), which) - - def _store_limit(self, sb, which): - self._config.set_int(self._limit_key(which), int(sb.get_value())) - - def make_controls(self, min, max): - hbox = gtk.HBox(False, 2) - - lab = gtk.Label(_("Memory Range:")) - lab.show() - hbox.pack_start(lab, 0, 0, 0) - - lokey = self._limit_key("lo") - hikey = self._limit_key("hi") - lostart = self._config.is_defined(lokey) and \ - self._config.get_int(lokey) or min - histart = self._config.is_defined(hikey) and \ - self._config.get_int(hikey) or 999 - - self.lo_limit_adj = gtk.Adjustment(lostart, min, max-1, 1, 10) - lo = compat.SpinButton(self.lo_limit_adj) - lo.set_value(lostart) - lo.connect("value-changed", self._store_limit, "lo") - lo.show() - hbox.pack_start(lo, 0, 0, 0) - - lab = gtk.Label(" - ") - lab.show() - hbox.pack_start(lab, 0, 0, 0) - - self.hi_limit_adj = gtk.Adjustment(histart, min+1, max, 1, 10) - hi = compat.SpinButton(self.hi_limit_adj) - hi.set_value(histart) - hi.connect("value-changed", self._store_limit, "hi") - hi.show() - hbox.pack_start(hi, 0, 0, 0) - - refresh = gtk.Button(_("Refresh")) - refresh.set_relief(gtk.RELIEF_NONE) - refresh.connect("clicked", lambda x: self.prefill()) - refresh.show() - hbox.pack_start(refresh, 0, 0, 0) - - def activate_go(widget): - refresh.clicked() - - def set_hi(widget, event): - loval = self.lo_limit_adj.get_value() - hival = self.hi_limit_adj.get_value() - if loval >= hival: - self.hi_limit_adj.set_value(loval + 25) - - lo.connect_after("focus-out-event", set_hi) - lo.connect_after("activate", activate_go) - hi.connect_after("activate", activate_go) - - sep = gtk.VSeparator() - sep.show() - hbox.pack_start(sep, 0, 0, 2) - - showspecial = gtk.ToggleButton(_("Special Channels")) - showspecial.set_relief(gtk.RELIEF_NONE) - showspecial.set_active(self.show_special) - showspecial.connect("toggled", - lambda x: self.set_show_special(x.get_active())) - showspecial.show() - hbox.pack_start(showspecial, 0, 0, 0) - - showempty = gtk.ToggleButton(_("Show Empty")) - showempty.set_relief(gtk.RELIEF_NONE) - showempty.set_active(self.show_empty) - showempty.connect("toggled", - lambda x: self.set_show_empty(x.get_active())) - showempty.show() - hbox.pack_start(showempty, 0, 0, 0) - - sep = gtk.VSeparator() - sep.show() - hbox.pack_start(sep, 0, 0, 2) - - props = gtk.Button(_("Properties")) - props.set_relief(gtk.RELIEF_NONE) - props.connect("clicked", - lambda x: self.hotkey( - gtk.Action("properties", "", "", 0))) - props.show() - hbox.pack_start(props, 0, 0, 0) - - hbox.show() - - return hbox - - def set_show_special(self, show): - self.show_special = show - self.prefill() - self._config.set_bool("show_special", show) - - def set_show_empty(self, show): - self.show_empty = show - self.prefill() - self._config.set_bool("hide_empty", not show) - - def set_hide_unused(self, hide_unused): - self.hide_unused = hide_unused - self.prefill() - self._config.set_bool("hide_unused", hide_unused) - - def __cache_columns(self): - # We call self.col() a lot. Caching the name->column# lookup - # makes a significant performance improvement - self._cached_cols = {} - i = 0 - for x in self.cols: - self._cached_cols[x[0]] = i - i += 1 - - def get_unsupported_columns(self): - maybe_hide = [ - ("has_dtcs", _("DTCS Code")), - ("has_rx_dtcs", _("DTCS Rx Code")), - ("has_dtcs_polarity", _("DTCS Pol")), - ("has_mode", _("Mode")), - ("has_offset", _("Offset")), - ("has_name", _("Name")), - ("has_tuning_step", _("Tune Step")), - ("has_name", _("Name")), - ("has_ctone", _("ToneSql")), - ("has_cross", _("Cross Mode")), - ("has_comment", _("Comment")), - ("valid_tmodes", _("Tone Mode")), - ("valid_tmodes", _("Tone")), - ("valid_duplexes", _("Duplex")), - ("valid_skips", _("Skip")), - ("valid_power_levels", _("Power")), - ] - - unsupported = [] - for feature, colname in maybe_hide: - if feature.startswith("has_"): - supported = self._features[feature] - LOG.info("%s supported: %s" % (colname, supported)) - elif feature.startswith("valid_"): - supported = len(self._features[feature]) != 0 - - if not supported: - unsupported.append(colname) - - return unsupported - - def get_columns_visible(self): - unsupported = self.get_unsupported_columns() - driver = directory.radio_class_id(self.rthread.radio.__class__) - user_visible = self._config.get(driver, "memedit_columns") - if user_visible: - user_visible = user_visible.split(",") - else: - # No setting for this radio, so assume all - user_visible = [x[0] for x in self.cols if x not in unsupported] - return user_visible - - def __init__(self, rthread): - super(MemoryEditor, self).__init__(rthread) - - self.defaults = dict(self.defaults) - - self._config = config.get("memedit") - - self.bandplans = bandplan.BandPlans(config.get()) - - self.allowed_bands = [144, 440] - self.count = 100 - self.show_special = self._config.get_bool("show_special") - self.show_empty = not self._config.get_bool("hide_empty") - self.hide_unused = self._config.get_bool("hide_unused", default=True) - self.read_only = False - - self.need_refresh = False - self._in_editing = False - - self.lo_limit_adj = self.hi_limit_adj = None - self.store = self.view = None - - self.__cache_columns() - - self._features = self.rthread.radio.get_features() - - (min, max) = self._features.memory_bounds - - self.choices[_("Mode")] = self._features["valid_modes"] - self.choices[_("Tone Mode")] = self._features["valid_tmodes"] - self.choices[_("Cross Mode")] = self._features["valid_cross_modes"] - self.choices[_("Skip")] = self._features["valid_skips"] - self.choices[_("Power")] = [str(x) for x in - self._features["valid_power_levels"]] - self.choices[_("DTCS Pol")] = self._features["valid_dtcs_pols"] - self.choices[_("DTCS Code")] = self._features["valid_dtcs_codes"] - self.choices[_("DTCS Rx Code")] = self._features["valid_dtcs_codes"] - - if self._features["valid_power_levels"]: - self.defaults[_("Power")] = self._features["valid_power_levels"][0] - - self.choices[_("Tune Step")] = self._features["valid_tuning_steps"] - - self.choices[_("Duplex")] = list(self._features.valid_duplexes) - - if self.defaults[_("Mode")] not in self._features.valid_modes: - self.defaults[_("Mode")] = self._features.valid_modes[0] - - vbox = gtk.VBox(False, 2) - vbox.pack_start(self.make_controls(min, max), 0, 0, 0) - vbox.pack_start(self.make_editor(), 1, 1, 1) - vbox.show() - - self.prefill() - - self.root = vbox - - # Run low priority jobs to get the rest of the memories - hi = int(self.hi_limit_adj.get_value()) - for i in range(hi+1, max+1): - job = common.RadioJob(None, "get_memory", i) - job.set_desc(_("Getting memory {number}").format(number=i)) - self.rthread.submit(job, 10) - - def _set_memory_cb(self, result): - if isinstance(result, Exception): - # FIXME: This can't be in the thread - dlg = ValueErrorDialog(result) - dlg.run() - dlg.destroy() - self.prefill() - elif self.need_refresh: - self.prefill() - self.need_refresh = False - - self.emit('changed') - - def copy_selection(self, cut=False): - (store, paths) = self.view.get_selection().get_selected_rows() - - maybe_cut = [] - selection = [] - - for path in paths: - iter = store.get_iter(path) - mem = self._get_memory(iter) - selection.append(mem.dupe()) - maybe_cut.append((iter, mem)) - - if cut: - for iter, mem in maybe_cut: - mem.empty = True - job = common.RadioJob(self._set_memory_cb, - "erase_memory", mem.number) - job.set_desc( - _("Cutting memory {number}").format(number=mem.number)) - self.rthread.submit(job) - - self._set_memory(iter, mem) - - result = base64.b64encode(pickle.dumps((self._features, - selection))).decode() - if hasattr(gtk.Clipboard, 'get'): - # GTK3 - clipboard = gtk.Clipboard.get(gtk.gdk.SELECTION_CLIPBOARD) - clipboard.set_text(result, len(result)) - else: - # GTK2 - clipboard = gtk.Clipboard(selection="CLIPBOARD") - clipboard.set_text(result) - clipboard.store() - - return cut # Only changed if we did a cut - - def _paste_selection(self, clipboard, text, data): - if not text: - return - - (store, paths) = self.view.get_selection().get_selected_rows() - if len(paths) > 1: - common.show_error("To paste, select only the starting location") - return - - iter = store.get_iter(paths[0]) - - always = False - - try: - src_features, mem_list = pickle.loads(base64.b64decode(text)) - except Exception: - LOG.error("Paste failed to unpickle") - return - - if (paths[0][0] + len(mem_list)) > self._rows_in_store: - common.show_error(_("Unable to paste {src} memories into " - "{dst} rows. Increase the memory bounds " - "or show empty memories.").format( - src=len(mem_list), - dst=(self._rows_in_store - paths[0][0]))) - return - - for mem in mem_list: - if mem.empty: - iter = self.store.iter_next(iter) - continue - loc, filled = store.get(iter, - self.col(_("Loc")), self.col("_filled")) - if filled and not always: - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_NO, gtk.RESPONSE_NO, - _("Yes to All"), gtk.RESPONSE_ACCEPT, - gtk.STOCK_YES, gtk.RESPONSE_YES) - d = miscwidgets.YesNoDialog(title=_("Overwrite?"), - parent=None, - buttons=buttons) - d.set_default_response(gtk.RESPONSE_YES) - d.set_text( - _("Overwrite location {number}?").format(number=loc)) - r = d.run() - d.destroy() - if r == gtk.RESPONSE_ACCEPT: - always = True - elif r == gtk.RESPONSE_CANCEL: - break - elif r == gtk.RESPONSE_NO: - iter = store.iter_next(iter) - continue - - mem.name = self.rthread.radio.filter_name(mem.name) - - src_number = mem.number - mem.number = loc - - try: - mem = import_logic.import_mem(self.rthread.radio, - src_features, - mem) - except import_logic.DestNotCompatible: - msgs = self.rthread.radio.validate_memory(mem) - errs = [x for x in msgs - if isinstance(x, chirp_common.ValidationError)] - if errs: - buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK) - d = miscwidgets.YesNoDialog(title=_("Incompatible Memory"), - buttons=buttons) - d.set_default_response(gtk.RESPONSE_OK) - d.set_text( - _("Pasted memory {number} is not compatible with " - "this radio because:").format(number=src_number) + - os.linesep + os.linesep.join(msgs)) - r = d.run() - d.destroy() - if r == gtk.RESPONSE_CANCEL: - break - else: - iter = store.iter_next(iter) - continue - - self._set_memory(iter, mem) - iter = store.iter_next(iter) - - job = common.RadioJob(self._set_memory_cb, "set_memory", mem) - job.set_desc( - _("Writing memory {number}").format(number=mem.number)) - self.rthread.submit(job) - - def paste_selection(self): - if hasattr(gtk.Clipboard, 'get'): - # GTK3 - clipboard = gtk.Clipboard.get(gtk.gdk.SELECTION_CLIPBOARD) - text = clipboard.wait_for_text() - self._paste_selection(clipboard, text, None) - else: - # GTK2 - clipboard = gtk.Clipboard(selection="CLIPBOARD") - clipboard.request_text(self._paste_selection) - - def select_all(self): - self.view.get_selection().select_all() - - def prepare_close(self): - cols = self.view.get_columns() - self._config.set("column_order_%s" % self.__class__.__name__, - ",".join([x.get_title() for x in cols])) - - def other_editor_changed(self, target_editor): - self.need_refresh = True - - -class DstarMemoryEditor(MemoryEditor): - def _get_cols_to_hide(self, iter): - hide = MemoryEditor._get_cols_to_hide(self, iter) - - mode, = self.store.get(iter, self.col(_("Mode"))) - if mode != "DV": - hide += [self.col("URCALL"), - self.col("RPT1CALL"), - self.col("RPT2CALL")] - - return hide - - def render(self, null, rend, model, iter, colnum): - MemoryEditor.render(self, null, rend, model, iter, colnum) - - vals = model.get(iter, *tuple(range(0, len(self.cols)))) - val = vals[colnum] - - def _enabled(sensitive): - rend.set_property("sensitive", sensitive) - - def d_unless_mode(mode): - _enabled(vals[self.col(_("Mode"))] == mode) - - _dv_columns = [_("URCALL"), _("RPT1CALL"), _("RPT2CALL"), - _("Digital Code")] - dv_columns = [self.col(x) for x in _dv_columns] - if colnum in dv_columns: - d_unless_mode("DV") - - def _get_memory(self, iter): - vals = self.store.get(iter, *range(0, len(self.cols))) - if vals[self.col(_("Mode"))] != "DV": - return MemoryEditor._get_memory(self, iter) - - mem = chirp_common.DVMemory() - - MemoryEditor._set_mem_vals(self, mem, vals, iter) - - mem.dv_urcall = vals[self.col(_("URCALL"))] - mem.dv_rpt1call = vals[self.col(_("RPT1CALL"))] - mem.dv_rpt2call = vals[self.col(_("RPT2CALL"))] - mem.dv_code = vals[self.col(_("Digital Code"))] - - return mem - - def __init__(self, rthread): - # I think self.cols is "static" or "unbound" or something else - # like that and += modifies the type, not self (how bizarre) - self.cols = list(self.cols) - new_cols = [("URCALL", TYPE_STRING, gtk.CellRendererCombo), - ("RPT1CALL", TYPE_STRING, gtk.CellRendererCombo), - ("RPT2CALL", TYPE_STRING, gtk.CellRendererCombo), - ("Digital Code", TYPE_INT, gtk.CellRendererText), - ] - for col in new_cols: - index = self.cols.index(("_filled", TYPE_BOOLEAN, None)) - self.cols.insert(index, col) - - self.choices = dict(self.choices) - self.defaults = dict(self.defaults) - - self.choices["URCALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) - self.choices["RPT1CALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) - self.choices["RPT2CALL"] = gtk.ListStore(TYPE_STRING, TYPE_STRING) - - self.defaults["URCALL"] = "" - self.defaults["RPT1CALL"] = "" - self.defaults["RPT2CALL"] = "" - self.defaults["Digital Code"] = 0 - - MemoryEditor.__init__(self, rthread) - - def ucall_cb(calls): - self.defaults["URCALL"] = calls[0] - for call in calls: - self.choices["URCALL"].append((call, call)) - - if self._features.requires_call_lists: - ujob = common.RadioJob(ucall_cb, "get_urcall_list") - ujob.set_desc(_("Downloading URCALL list")) - rthread.submit(ujob) - - def rcall_cb(calls): - self.defaults["RPT1CALL"] = calls[0] - self.defaults["RPT2CALL"] = calls[0] - for call in calls: - self.choices["RPT1CALL"].append((call, call)) - self.choices["RPT2CALL"].append((call, call)) - - if self._features.requires_call_lists: - rjob = common.RadioJob(rcall_cb, "get_repeater_call_list") - rjob.set_desc(_("Downloading RPTCALL list")) - rthread.submit(rjob) - - _dv_columns = ["URCALL", "RPT1CALL", "RPT2CALL", "Digital Code"] - - if not self._features.requires_call_lists: - for i in _dv_columns: - if i not in self.choices: - continue - rend = self.rend(i) - rend.set_property("has-entry", True) - - for i in _dv_columns: - rend = self.rend(i) - rend.set_property("family", "Monospace") - - def set_urcall_list(self, urcalls): - store = self.choices["URCALL"] - - store.clear() - for call in urcalls: - store.append((call, call)) - - def set_repeater_list(self, repeaters): - for listname in ["RPT1CALL", "RPT2CALL"]: - store = self.choices[listname] - - store.clear() - for call in repeaters: - store.append((call, call)) - - def _set_memory(self, iter, memory): - MemoryEditor._set_memory(self, iter, memory) - - if isinstance(memory, chirp_common.DVMemory): - self.store.set(iter, - self.col("URCALL"), memory.dv_urcall, - self.col("RPT1CALL"), memory.dv_rpt1call, - self.col("RPT2CALL"), memory.dv_rpt2call, - self.col("Digital Code"), memory.dv_code, - ) - else: - self.store.set(iter, - self.col("URCALL"), "", - self.col("RPT1CALL"), "", - self.col("RPT2CALL"), "", - self.col("Digital Code"), 0, - ) diff --git a/chirp/ui/miscwidgets.py b/chirp/ui/miscwidgets.py deleted file mode 100644 index a7e8b781..00000000 --- a/chirp/ui/miscwidgets.py +++ /dev/null @@ -1,883 +0,0 @@ -# Copyright 2008 Dan Smith -# -# 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 . - -import abc -import gtk -import gobject -import pango - -import os -import logging - -import six - -from chirp import platform -from chirp.ui import compat -from chirp.ui import common - -LOG = logging.getLogger(__name__) - - -class KeyedListWidget(gtk.HBox): - __gsignals__ = { - "item-selected": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - "item-toggled": (gobject.SIGNAL_ACTION, - gobject.TYPE_BOOLEAN, - (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)), - "item-set": (gobject.SIGNAL_ACTION, - gobject.TYPE_BOOLEAN, - (gobject.TYPE_STRING,)), - } - - def _toggle(self, rend, path, colnum): - if self.__toggle_connected: - self.__store[path][colnum] = not self.__store[path][colnum] - iter = self.__store.get_iter(path) - id, = self.__store.get(iter, 0) - self.emit("item-toggled", id, self.__store[path][colnum]) - - def _edited(self, rend, path, new, colnum): - iter = self.__store.get_iter(path) - key, oldval = self.__store.get(iter, 0, colnum) - self.__store.set(iter, colnum, new) - if not self.emit("item-set", key): - self.__store.set(iter, colnum, oldval) - - def _mouse(self, view, event): - x, y = event.get_coords() - path = self.__view.get_path_at_pos(int(x), int(y)) - if path: - self.__view.set_cursor_on_cell(path[0]) - - sel = self.get_selected() - if sel: - self.emit("item-selected", sel) - - def _make_view(self): - colnum = -1 - - self._renderers = [] - - for typ, cap in self.columns: - colnum += 1 - if colnum == 0: - continue # Key column - - if typ in [gobject.TYPE_STRING, gobject.TYPE_INT, - gobject.TYPE_FLOAT]: - rend = gtk.CellRendererText() - rend.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn(cap, rend, text=colnum) - elif typ in [gobject.TYPE_BOOLEAN]: - rend = gtk.CellRendererToggle() - rend.connect("toggled", self._toggle, colnum) - column = gtk.TreeViewColumn(cap, rend, active=colnum) - else: - raise Exception("Unsupported type %s" % typ) - - self._renderers.append(rend) - - column.set_sort_column_id(colnum) - self.__view.append_column(column) - - self.__view.connect("button_press_event", self._mouse) - - def set_item(self, key, *values): - iter = self.__store.get_iter_first() - while iter: - id, = self.__store.get(iter, 0) - if id == key: - self.__store.insert_after(iter, row=(id,)+values) - self.__store.remove(iter) - return - iter = self.__store.iter_next(iter) - - self.__store.append(row=(key,) + values) - - self.emit("item-set", key) - - def get_item(self, key): - iter = self.__store.get_iter_first() - while iter: - vals = self.__store.get(iter, *tuple(range(len(self.columns)))) - if vals[0] == key: - return vals - iter = self.__store.iter_next(iter) - - return None - - def del_item(self, key): - iter = self.__store.get_iter_first() - while iter: - id, = self.__store.get(iter, 0) - if id == key: - self.__store.remove(iter) - return True - - iter = self.__store.iter_next(iter) - - return False - - def has_item(self, key): - return self.get_item(key) is not None - - def get_selected(self): - try: - (store, iter) = self.__view.get_selection().get_selected() - return store.get(iter, 0)[0] - except Exception as e: - LOG.error("Unable to find selected: %s" % e) - return None - - def select_item(self, key): - if key is None: - sel = self.__view.get_selection() - sel.unselect_all() - return True - - iter = self.__store.get_iter_first() - while iter: - if self.__store.get(iter, 0)[0] == key: - selection = self.__view.get_selection() - path = self.__store.get_path(iter) - selection.select_path(path) - return True - iter = self.__store.iter_next(iter) - - return False - - def get_keys(self): - keys = [] - iter = self.__store.get_iter_first() - while iter: - key, = self.__store.get(iter, 0) - keys.append(key) - iter = self.__store.iter_next(iter) - - return keys - - def __init__(self, columns): - gtk.HBox.__init__(self, True, 0) - - self.columns = columns - - types = tuple([x for x, y in columns]) - - self.__store = gtk.ListStore(*types) - self.__view = gtk.TreeView(self.__store) - - self.pack_start(self.__view, 1, 1, 1) - - self.__toggle_connected = False - - self._make_view() - self.__view.show() - - def connect(self, signame, *args): - if signame == "item-toggled": - self.__toggle_connected = True - - gtk.HBox.connect(self, signame, *args) - - def set_editable(self, column, is_editable): - rend = self.get_renderer(column) - rend.set_property("editable", True) - rend.connect("edited", self._edited, column + 1) - - def set_sort_column(self, column, value=None): - if not value: - value = column - col = self.__view.get_column(column) - col.set_sort_column_id(value) - - def get_renderer(self, colnum): - return self._renderers[colnum] - - -class ListWidget(gtk.HBox): - __gsignals__ = { - "click-on-list": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gtk.TreeView, gtk.gdk.Event)), - "item-toggled": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - } - - store_type = gtk.ListStore - - def mouse_cb(self, view, event): - self.emit("click-on-list", view, event) - - # pylint: disable-msg=W0613 - def _toggle(self, render, path, column): - self._store[path][column] = not self._store[path][column] - iter = self._store.get_iter(path) - vals = tuple(self._store.get(iter, *tuple(range(self._ncols)))) - for cb in self.toggle_cb: - cb(*vals) - self.emit("item-toggled", vals) - - def make_view(self, columns): - self._view = gtk.TreeView(self._store) - - for _type, _col in columns: - if _col.startswith("__"): - continue - - index = columns.index((_type, _col)) - if _type == gobject.TYPE_STRING or \ - _type == gobject.TYPE_INT or \ - _type == gobject.TYPE_FLOAT: - rend = gtk.CellRendererText() - column = gtk.TreeViewColumn(_col, rend, text=index) - column.set_resizable(True) - rend.set_property("ellipsize", pango.ELLIPSIZE_END) - elif _type == gobject.TYPE_BOOLEAN: - rend = gtk.CellRendererToggle() - rend.connect("toggled", self._toggle, index) - column = gtk.TreeViewColumn(_col, rend, active=index) - else: - raise Exception("Unknown column type (%i)" % index) - - column.set_sort_column_id(index) - self._view.append_column(column) - - self._view.connect("button_press_event", self.mouse_cb) - - def __init__(self, columns, parent=True): - gtk.HBox.__init__(self) - - # pylint: disable-msg=W0612 - col_types = tuple([x for x, y in columns]) - self._ncols = len(col_types) - - self._store = self.store_type(*col_types) - self._view = None - self.make_view(columns) - - self._view.show() - if parent: - self.pack_start(self._view, 1, 1, 1) - - self.toggle_cb = [] - - def packable(self): - return self._view - - def add_item(self, *vals): - if len(vals) != self._ncols: - raise Exception("Need %i columns" % self._ncols) - - args = [] - i = 0 - for val in vals: - args.append(i) - args.append(val) - i += 1 - - args = tuple(args) - - iter = self._store.append() - self._store.set(iter, *args) - - def _remove_item(self, model, path, iter, match): - vals = model.get(iter, *tuple(range(0, self._ncols))) - if vals == match: - model.remove(iter) - - def remove_item(self, *vals): - if len(vals) != self._ncols: - raise Exception("Need %i columns" % self._ncols) - - def remove_selected(self): - try: - (lst, iter) = self._view.get_selection().get_selected() - lst.remove(iter) - except Exception as e: - LOG.error("Unable to remove selected: %s" % e) - - def get_selected(self, take_default=False): - (lst, iter) = self._view.get_selection().get_selected() - if not iter and take_default: - iter = lst.get_iter_first() - - return lst.get(iter, *tuple(range(self._ncols))) - - def move_selected(self, delta): - (lst, iter) = self._view.get_selection().get_selected() - - pos = int(lst.get_path(iter)[0]) - - try: - target = None - - if delta > 0 and pos > 0: - target = lst.get_iter(pos-1) - elif delta < 0: - target = lst.get_iter(pos+1) - except Exception as e: - return False - - if target: - return lst.swap(iter, target) - - def _get_value(self, model, path, iter, lst): - lst.append(model.get(iter, *tuple(range(0, self._ncols)))) - - def get_values(self): - lst = [] - - self._store.foreach(self._get_value, lst) - - return lst - - def set_values(self, lst): - self._store.clear() - - for i in lst: - self.add_item(*i) - - -class TreeWidget(ListWidget): - store_type = gtk.TreeStore - - # pylint: disable-msg=W0613 - def _toggle(self, render, path, column): - self._store[path][column] = not self._store[path][column] - iter = self._store.get_iter(path) - vals = tuple(self._store.get(iter, *tuple(range(self._ncols)))) - - piter = self._store.iter_parent(iter) - if piter: - parent = self._store.get(piter, self._key)[0] - else: - parent = None - - for cb in self.toggle_cb: - cb(parent, *vals) - - def __init__(self, columns, key, parent=True): - ListWidget.__init__(self, columns, parent) - - self._key = key - - def _add_item(self, piter, *vals): - args = [] - i = 0 - for val in vals: - args.append(i) - args.append(val) - i += 1 - - args = tuple(args) - - iter = self._store.append(piter) - self._store.set(iter, *args) - - def _iter_of(self, key, iter=None): - if not iter: - iter = self._store.get_iter_first() - - while iter is not None: - _id = self._store.get(iter, self._key)[0] - if _id == key: - return iter - - iter = self._store.iter_next(iter) - - return None - - def add_item(self, parent, *vals): - if len(vals) != self._ncols: - raise Exception("Need %i columns" % self._ncols) - - if not parent: - self._add_item(None, *vals) - else: - iter = self._iter_of(parent) - if iter: - self._add_item(iter, *vals) - else: - raise Exception("Parent not found: %s", parent) - - def _set_values(self, parent, vals): - if isinstance(vals, dict): - for key, val in vals.items(): - iter = self._store.append(parent) - self._store.set(iter, self._key, key) - self._set_values(iter, val) - elif isinstance(vals, list): - for i in vals: - self._set_values(parent, i) - elif isinstance(vals, tuple): - self._add_item(parent, *vals) - else: - LOG.error("Unknown type: %s" % vals) - - def set_values(self, vals): - self._store.clear() - self._set_values(self._store.get_iter_first(), vals) - - def del_item(self, parent, key): - iter = self._iter_of(key, - self._store.iter_children(self._iter_of(parent))) - if iter: - self._store.remove(iter) - else: - raise Exception("Item not found") - - def get_item(self, parent, key): - iter = self._iter_of(key, - self._store.iter_children(self._iter_of(parent))) - - if iter: - return self._store.get(iter, *(tuple(range(0, self._ncols)))) - else: - raise Exception("Item not found") - - def set_item(self, parent, *vals): - iter = self._iter_of(vals[self._key], - self._store.iter_children(self._iter_of(parent))) - - if iter: - args = [] - i = 0 - - for val in vals: - args.append(i) - args.append(val) - i += 1 - - self._store.set(iter, *(tuple(args))) - else: - raise Exception("Item not found") - - -class ProgressDialog(gtk.Window): - def __init__(self, title, parent=None): - gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) - self.set_modal(True) - self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - self.set_title(title) - if parent: - self.set_transient_for(parent) - - self.set_resizable(False) - - vbox = gtk.VBox(False, 2) - - self.label = gtk.Label("") - self.label.set_size_request(100, 50) - self.label.show() - - self.pbar = gtk.ProgressBar() - self.pbar.show() - - vbox.pack_start(self.label, 0, 0, 0) - vbox.pack_start(self.pbar, 0, 0, 0) - - vbox.show() - - self.add(vbox) - - def set_text(self, text): - self.label.set_text(text) - self.queue_draw() - - while gtk.events_pending(): - gtk.main_iteration_do(False) - - def set_fraction(self, frac): - self.pbar.set_fraction(frac) - self.queue_draw() - - while gtk.events_pending(): - gtk.main_iteration_do(False) - - -class LatLonEntry(gtk.Entry): - def __init__(self, *args): - gtk.Entry.__init__(self, *args) - - self.connect("changed", self.format) - - def format(self, entry): - string = entry.get_text() - if string is None: - return - - deg = u"\u00b0" - - while " " in string: - if "." in string: - break - elif deg not in string: - string = string.replace(" ", deg) - elif "'" not in string: - string = string.replace(" ", "'") - elif '"' not in string: - string = string.replace(" ", '"') - else: - string = string.replace(" ", "") - - entry.set_text(string) - - def parse_dd(self, string): - return float(string) - - def parse_dm(self, string): - string = string.strip() - string = string.replace(' ', ' ') - - (_degrees, _minutes) = string.split(' ', 2) - - degrees = int(_degrees) - minutes = float(_minutes) - - return degrees + (minutes / 60.0) - - def parse_dms(self, string): - string = string.replace(u"\u00b0", " ") - string = string.replace('"', ' ') - string = string.replace("'", ' ') - string = string.replace(' ', ' ') - string = string.strip() - - items = string.split(' ') - - if len(items) > 3: - raise Exception("Invalid format") - elif len(items) == 3: - deg = items[0] - mns = items[1] - sec = items[2] - elif len(items) == 2: - deg = items[0] - mns = items[1] - sec = 0 - elif len(items) == 1: - deg = items[0] - mns = 0 - sec = 0 - else: - deg = 0 - mns = 0 - sec = 0 - - degrees = int(deg) - minutes = int(mns) - seconds = float(sec) - - return degrees + (minutes / 60.0) + (seconds / 3600.0) - - def value(self): - string = self.get_text() - - try: - return self.parse_dd(string) - except: - try: - return self.parse_dm(string) - except: - try: - return self.parse_dms(string) - except Exception as e: - LOG.error("DMS: %s" % e) - - raise Exception("Invalid format") - - def validate(self): - try: - self.value() - return True - except: - return False - - -class YesNoDialog(gtk.Dialog): - def __init__(self, title="", parent=None, buttons=None): - gtk.Dialog.__init__(self, - title=title, - parent=parent, - buttons=buttons) - - self._label = gtk.Label("") - self._label.set_alignment(0.0, 0.5) - self._label.show() - - # pylint: disable-msg=E1101 - self.vbox.pack_start(self._label, 1, 1, 1) - - def set_text(self, text): - self._label.set_text(text) - - -@six.add_metaclass(abc.ABCMeta) -class EditableChoiceBase(object): - def __init__(self, options, editable, default): - pass - - @property - @abc.abstractmethod - def widget(self): - raise NotImplementedError() - - @property - @abc.abstractmethod - def value(self): - pass - - @value.setter - @abc.abstractmethod - def value(self): - pass - - @abc.abstractmethod - def get_model(self): - pass - - @abc.abstractmethod - def append_text(self, text): - """Add a choice option""" - pass - - def set_active(self, text): - """Legacy compat""" - self.value = text - - def get_active_text(self): - """Legacy compat""" - return self.value - - def set_sensitive(self, sensitive): - self.widget.set_sensitive(sensitive) - - def connect(self, signal, fn, data): - self.widget.connect(signal, lambda *a: fn(self, data)) - - -class EditableChoiceGTK3(EditableChoiceBase): - def __init__(self, options, editable, default): - self.store = gtk.ListStore(str) - for i, option in enumerate(options): - self.store.append([option]) - if editable: - self.sel = gtk.ComboBox.new_with_model_and_entry(self.store) - self.sel.set_entry_text_column(0) - else: - self.sel = gtk.ComboBox.new_with_model(self.store) - renderer_text = gtk.CellRendererText() - self.sel.pack_start(renderer_text, True) - self.sel.add_attribute(renderer_text, "text", 0) - if default: - self.value = default - - @property - def widget(self): - return self.sel - - @property - def value(self): - modeliter = self.sel.get_active_iter() - if modeliter: - return self.store[modeliter][0] - else: - LOG.warning('No active iter') - - @value.setter - def value(self, val): - modeliter = self.store.get_iter_first() - while modeliter is not None: - row = self.store[modeliter] - if row[0] == val: - self.sel.set_active_iter(modeliter) - break - modeliter = self.store.iter_next(modeliter) - - def get_model(self): - return self.store - - def append_text(self, text): - self.store.append([text]) - - -class EditableChoiceGTK2(EditableChoiceBase): - def __init__(self, options, editable, default): - if editable: - self.sel = gtk.combo_box_entry_new_text() - else: - self.sel = gtk.combo_box_new_text() - - for opt in options: - self.sel.append_text(opt) - - if default: - try: - idx = options.index(default) - self.sel.set_active(idx) - except Exception as e: - pass - - @property - def widget(self): - return self.sel - - @property - def value(self): - store = self.sel.get_model() - modeliter = self.sel.get_active_iter() - return store[modeliter][0] - - @value.setter - def value(self, val): - common.combo_select(self.sel, val) - - def get_model(self): - return self.sel.get_model() - - def append_text(self, text): - self.sel.append_text(text) - - -def make_choice(options, editable=True, default=None): - if hasattr(gtk.ComboBox, 'new_with_entry'): - return EditableChoiceGTK3(options, editable, default) - else: - return EditableChoiceGTK2(options, editable, default) - - -class FilenameBox(gtk.HBox): - __gsignals__ = { - "filename-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - } - - def do_browse(self, _, dir): - if self.filename.get_text(): - start = os.path.dirname(self.filename.get_text()) - else: - start = None - - if dir: - fn = platform.get_platform().gui_select_dir(start) - else: - fn = platform.get_platform().gui_save_file(start, types=self.types) - if fn: - self.filename.set_text(fn) - - def do_changed(self, _): - self.emit("filename_changed") - - def __init__(self, find_dir=False, types=[]): - gtk.HBox.__init__(self, False, 0) - - self.types = types - - self.filename = gtk.Entry() - self.filename.show() - self.pack_start(self.filename, 1, 1, 1) - - browse = gtk.Button("...") - browse.show() - self.pack_start(browse, 0, 0, 0) - - self.filename.connect("changed", self.do_changed) - browse.connect("clicked", self.do_browse, find_dir) - - def set_filename(self, fn): - self.filename.set_text(fn) - - def get_filename(self): - return self.filename.get_text() - - -def make_pixbuf_choice(options, default=None): - store = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING) - box = gtk.ComboBox(store) - - cell = gtk.CellRendererPixbuf() - box.pack_start(cell, True) - box.add_attribute(cell, "pixbuf", 0) - - cell = gtk.CellRendererText() - box.pack_start(cell, True) - box.add_attribute(cell, "text", 1) - - _default = None - for pic, value in options: - iter = store.append() - store.set(iter, 0, pic, 1, value) - if default == value: - _default = options.index((pic, value)) - - if _default: - box.set_active(_default) - - return box - - -def test(): - win = gtk.Window(gtk.WINDOW_TOPLEVEL) - lst = ListWidget([(gobject.TYPE_STRING, "Foo"), - (gobject.TYPE_BOOLEAN, "Bar")]) - - lst.add_item("Test1", True) - lst.set_values([("Test2", True), ("Test3", False)]) - - lst.show() - win.add(lst) - win.show() - - win1 = ProgressDialog("foo") - win1.show() - - win2 = gtk.Window(gtk.WINDOW_TOPLEVEL) - lle = LatLonEntry() - lle.show() - win2.add(lle) - win2.show() - - win3 = gtk.Window(gtk.WINDOW_TOPLEVEL) - lst = TreeWidget([(gobject.TYPE_STRING, "Id"), - (gobject.TYPE_STRING, "Value")], - 1) - lst.set_values({"Fruit": [("Apple", "Red"), ("Orange", "Orange")], - "Pizza": [("Cheese", "Simple"), ("Pepperoni", "Yummy")]}) - lst.add_item("Fruit", "Bananna", "Yellow") - lst.show() - win3.add(lst) - win3.show() - - def print_val(entry): - if entry.validate(): - print("Valid: %s" % entry.value()) - else: - print("Invalid") - lle.connect("activate", print_val) - - lle.set_text("45 13 12") - - try: - gtk.main() - except KeyboardInterrupt: - pass - - print(lst.get_values()) - - -if __name__ == "__main__": - test() diff --git a/chirp/ui/radiobrowser.py b/chirp/ui/radiobrowser.py deleted file mode 100644 index 90b2981d..00000000 --- a/chirp/ui/radiobrowser.py +++ /dev/null @@ -1,350 +0,0 @@ -import gtk -import gobject -import pango -import re -import os -import logging - -from chirp import bitwise -from chirp.ui import common, config - -LOG = logging.getLogger(__name__) - -CONF = config.get() - - -def do_insert_line_with_tags(b, line): - def i(text, *tags): - b.insert_with_tags_by_name(b.get_end_iter(), text, *tags) - - def ident(name): - if "unknown" in name: - i(name, 'grey', 'bold') - else: - i(name, 'bold') - - def nonzero(value): - i(value, 'red', 'bold') - - def foo(value): - i(value, 'blue', 'bold') - - m = re.match("^( *)([A-z0-9_]+: )(0x[A-F0-9]+) \((.*)\)$", line) - if m: - i(m.group(1)) - ident(m.group(2)) - if m.group(3) == '0x00': - i(m.group(3)) - else: - nonzero(m.group(3)) - i(' (') - for char in m.group(4): - if char == '1': - nonzero(char) - else: - i(char) - i(')') - return - - m = re.match("^( *)([A-z0-9_]+: )(.*)$", line) - if m: - i(m.group(1)) - ident(m.group(2)) - i(m.group(3)) - return - - m = re.match("^(.*} )([A-z0-9_]+)( \()([0-9]+)( bytes at )(0x[A-F0-9]+)", - line) - if m: - i(m.group(1)) - ident(m.group(2)) - i(m.group(3)) - foo(m.group(4)) - i(m.group(5)) - foo(m.group(6)) - i(")") - return - - i(line) - - -def do_insert_with_tags(buf, text): - buf.set_text('') - lines = text.split(os.linesep) - for line in lines: - do_insert_line_with_tags(buf, line) - buf.insert_with_tags_by_name(buf.get_end_iter(), os.linesep) - - -def classname(obj): - return str(obj.__class__).split('.')[-1] - - -def bitwise_type(classname): - return classname.split("DataElement")[0] - - -class FixedEntry(gtk.Entry): - def __init__(self, *args, **kwargs): - super(FixedEntry, self).__init__(*args, **kwargs) - - try: - fontsize = CONF.get_int("browser_fontsize", "developer") - except Exception: - fontsize = 10 - if fontsize < 4 or fontsize > 144: - LOG.warn("Unsupported browser_fontsize %i. Using 10." % fontsize) - fontsize = 11 - - fontdesc = pango.FontDescription("Courier bold %i" % fontsize) - self.modify_font(fontdesc) - - -class IntegerEntry(FixedEntry): - def _colorize(self, _self): - value = self.get_text() - if value.startswith("0x"): - value = value[2:] - value = value.replace("0", "") - if not value: - self.modify_text(gtk.STATE_NORMAL, None) - else: - self.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse('red')) - - def __init__(self, *args, **kwargs): - super(IntegerEntry, self).__init__(*args, **kwargs) - self.connect("changed", self._colorize) - - -class BitwiseEditor(gtk.HBox): - def __init__(self, element): - super(BitwiseEditor, self).__init__(False, 3) - self._element = element - self._build_ui() - - -class IntegerEditor(BitwiseEditor): - def _changed(self, entry, base): - if not self._update: - return - value = entry.get_text() - if value.startswith("0x"): - value = value[2:] - self._element.set_value(int(value, base)) - self._update_entries(skip=entry) - - def _update_entries(self, skip=None): - self._update = False - for ent, format_spec in self._entries: - if ent != skip: - ent.set_text(format_spec.format(int(self._element))) - self._update = True - - def _build_ui(self): - self._entries = [] - self._update = True - - hexdigits = ((self._element.size() / 4) + - (self._element.size() % 4 and 1 or 0)) - formats = [('Hex', 16, '0x{:0%iX}' % hexdigits), - ('Dec', 10, '{:d}'), - ('Bin', 2, '{:0%ib}' % self._element.size())] - for name, base, format_spec in formats: - lab = gtk.Label(name) - self.pack_start(lab, 0, 0, 0) - lab.show() - int(self._element) - ent = IntegerEntry() - self._entries.append((ent, format_spec)) - ent.connect('changed', self._changed, base) - self.pack_start(ent, 0, 0, 0) - ent.show() - self._update_entries() - - -class BCDArrayEditor(BitwiseEditor): - def _changed(self, entry, hexent): - self._element.set_value(int(entry.get_text())) - self._format_hexent(hexent) - - def _format_hexent(self, hexent): - value = "" - for i in self._element: - a, b = i.get_value() - value += "%i%i" % (a, b) - hexent.set_text(value) - - def _build_ui(self): - lab = gtk.Label("Dec") - lab.show() - self.pack_start(lab, 0, 0, 0) - ent = FixedEntry() - ent.set_text(str(int(self._element))) - ent.show() - self.pack_start(ent, 1, 1, 1) - - lab = gtk.Label("Hex") - lab.show() - self.pack_start(lab, 0, 0, 0) - - hexent = FixedEntry() - hexent.show() - self.pack_start(hexent, 1, 1, 1) - hexent.set_editable(False) - - ent.connect('changed', self._changed, hexent) - self._format_hexent(hexent) - - -class CharArrayEditor(BitwiseEditor): - def _changed(self, entry): - self._element.set_value(entry.get_text().ljust(len(self._element))) - - def _build_ui(self): - ent = FixedEntry(len(self._element)) - ent.set_text(str(self._element).rstrip("\x00")) - ent.connect('changed', self._changed) - ent.show() - self.pack_start(ent, 1, 1, 1) - - -class OtherEditor(BitwiseEditor): - def _build_ui(self): - name = classname(self._element) - name = bitwise_type(name) - if isinstance(self._element, bitwise.arrayDataElement): - name += " %s[%i]" % ( - bitwise_type(classname(self._element[0])), - len(self._element)) - - l = gtk.Label(name) - l.show() - self.pack_start(l, 1, 1, 1) - - -class RadioBrowser(common.Editor): - def _build_ui(self): - self._display = gtk.Table(20, 2) - - self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) - self._tree = gtk.TreeView(self._store) - - rend = gtk.CellRendererText() - tvc = gtk.TreeViewColumn('Element', rend, text=0) - self._tree.append_column(tvc) - self._tree.connect('button_press_event', self._tree_click) - - self.root = gtk.HPaned() - self.root.set_position(200) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(self._tree) - sw.show() - self.root.add1(sw) - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add_with_viewport(self._display) - sw.show() - self.root.add2(sw) - self._tree.show() - self._display.show() - self.root.show() - - def _fill(self, name, obj, parent=None): - iter = self._store.append(parent, (name, obj)) - - if isinstance(obj, bitwise.structDataElement): - for name, item in obj.items(): - if isinstance(item, bitwise.structDataElement): - self._fill(name, item, iter) - elif isinstance(item, bitwise.arrayDataElement): - self._fill("%s[%i]" % (name, len(item)), item, iter) - elif isinstance(obj, bitwise.arrayDataElement): - i = 0 - for item in obj: - if isinstance(obj[0], bitwise.structDataElement): - self._fill("%s[%i]" % (name, i), item, iter) - i += 1 - - def _tree_click(self, view, event): - if event.button != 1: - return - - index = [0] - - def pack(widget, pos): - self._display.attach(widget, pos, pos + 1, index[0], index[0] + 1, - xoptions=gtk.FILL, yoptions=0) - - def next_row(): - index[0] += 1 - - def abandon(child): - self._display.remove(child) - - pathinfo = view.get_path_at_pos(int(event.x), int(event.y)) - path = pathinfo[0] - iter = self._store.get_iter(path) - name, obj = self._store.get(iter, 0, 1) - - self._display.foreach(abandon) - - for name, item in obj.items(): - if item.size() % 8 == 0: - name = '%s (%s %i bytes)' % ( - name, bitwise_type(classname(item)), item.size() / 8) - else: - name = '%s (%s %i bits)' % ( - name, bitwise_type(classname(item)), item.size()) - l = gtk.Label(name + " ") - l.set_use_markup(True) - l.show() - pack(l, 0) - - if (isinstance(item, bitwise.intDataElement) or - isinstance(item, bitwise.bcdDataElement)): - e = IntegerEditor(item) - elif (isinstance(item, bitwise.arrayDataElement) and - isinstance(item[0], bitwise.bcdDataElement)): - e = BCDArrayEditor(item) - elif (isinstance(item, bitwise.arrayDataElement) and - isinstance(item[0], bitwise.charDataElement)): - e = CharArrayEditor(item) - else: - e = OtherEditor(item) - e.show() - pack(e, 1) - next_row() - - def __init__(self, rthread): - super(RadioBrowser, self).__init__(rthread) - self._radio = rthread.radio - self._focused = False - self._build_ui() - self._fill('root', self._radio._memobj) - - def focus(self): - self._focused = True - - def unfocus(self): - if self._focused: - self.emit("changed") - self._focused = False - - -if __name__ == "__main__": - from chirp.drivers import * - from chirp import directory - import sys - - r = directory.get_radio_by_image(sys.argv[1]) - - class Foo: - radio = r - - w = gtk.Window() - b = RadioBrowser(Foo) - w.set_default_size(1024, 768) - w.add(b.root) - w.show() - gtk.main() diff --git a/chirp/ui/reporting.py b/chirp/ui/reporting.py deleted file mode 100644 index ad52be99..00000000 --- a/chirp/ui/reporting.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright 2011 Dan Smith -# -# 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 . - -# README: -# -# I know that collecting data is not very popular. I don't like it -# either. However, it's hard to tell what drivers people are using -# and I think it would be helpful if I had that information. This is -# completely optional, so you can turn it off if you want. It doesn't -# report anything other than version and model usage information. The -# code below is very conservative, and will disable itself if reporting -# fails even once or takes too long to perform. It's run in a thread -# so that the user shouldn't even notice it's happening. -# - -import threading -import os -import time -import logging - -from chirp import CHIRP_VERSION, platform - -REPORT_URL = "http://chirp.danplanet.com/report/report.php?do_report" -ENABLED = True -THREAD_SEM = threading.Semaphore(10) # Maximum number of outstanding threads -LAST = 0 -LAST_TYPE = None - -LOG = logging.getLogger(__name__) - -try: - # Don't let failure to import any of these modules cause trouble - from chirp.ui import config - import xmlrpclib -except: - ENABLED = False - - -def should_report(): - if not ENABLED: - LOG.info("Not reporting due to recent failure") - return False - - conf = config.get() - if conf.get_bool("no_report"): - LOG.info("Reporting disabled") - return False - - return True - - -def _report_model_usage(model, direction, success): - global ENABLED - if direction not in ["live", "download", "upload", - "import", "export", "importsrc"]: - LOG.warn("Invalid direction `%s'" % direction) - return True # This is a bug, but not fatal - - model = "%s_%s" % (model.VENDOR, model.MODEL) - data = "%s,%s,%s" % (model, direction, success) - - LOG.debug("Reporting model usage: %s" % data) - - proxy = xmlrpclib.ServerProxy(REPORT_URL) - id = proxy.report_stats(CHIRP_VERSION, - platform.get_platform().os_version_string(), - "model_use", - data) - - # If the server returns zero, it wants us to shut up - return id != 0 - - -def _report_exception(stack): - global ENABLED - - LOG.debug("Reporting exception") - - proxy = xmlrpclib.ServerProxy(REPORT_URL) - id = proxy.report_exception(CHIRP_VERSION, - platform.get_platform().os_version_string(), - "exception", - stack) - - # If the server returns zero, it wants us to shut up - return id != 0 - - -def _report_misc_error(module, data): - global ENABLED - - LOG.debug("Reporting misc error with %s" % module) - - proxy = xmlrpclib.ServerProxy(REPORT_URL) - id = proxy.report_misc_error(CHIRP_VERSION, - platform.get_platform().os_version_string(), - module, data) - - # If the server returns zero, it wants us to shut up - return id != 0 - - -def _check_for_updates(callback): - LOG.debug("Checking for updates") - proxy = xmlrpclib.ServerProxy(REPORT_URL) - ver = proxy.check_for_updates(CHIRP_VERSION, - platform.get_platform().os_version_string()) - - LOG.debug("Server reports version %s is latest" % ver) - callback(ver) - return True - - -class ReportThread(threading.Thread): - def __init__(self, func, *args): - threading.Thread.__init__(self) - self.__func = func - self.__args = args - - def _run(self): - try: - return self.__func(*self.__args) - except Exception as e: - LOG.debug("Failed to report: %s" % e) - return False - - def run(self): - start = time.time() - result = self._run() - if not result: - # Reporting failed - ENABLED = False - elif (time.time() - start) > 15: - # Reporting took too long - LOG.debug("Time to report was %.2f sec -- Disabling" % - (time.time()-start)) - ENABLED = False - - THREAD_SEM.release() - - -def dispatch_thread(func, *args): - global LAST - global LAST_TYPE - - # If reporting is disabled or failing, bail - if not should_report(): - LOG.debug("Reporting is disabled") - return - - # If the time between now and the last report is less than 5 seconds, bail - delta = time.time() - LAST - if delta < 5 and func == LAST_TYPE: - LOG.debug("Throttling...") - return - - LAST = time.time() - LAST_TYPE = func - - # If there are already too many threads running, bail - if not THREAD_SEM.acquire(False): - LOG.debug("Too many threads already running") - return - - t = ReportThread(func, *args) - t.start() - - -def report_model_usage(model, direction, success): - dispatch_thread(_report_model_usage, model, direction, success) - - -def report_exception(stack): - dispatch_thread(_report_exception, stack) - - -def report_misc_error(module, data): - dispatch_thread(_report_misc_error, module, data) - - -# Calls callback with the latest version -def check_for_updates(callback): - dispatch_thread(_check_for_updates, callback) diff --git a/chirp/ui/settingsedit.py b/chirp/ui/settingsedit.py deleted file mode 100644 index 98d10664..00000000 --- a/chirp/ui/settingsedit.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright 2012 Dan Smith -# -# 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 2 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 . - -import gtk -import gobject -import logging - -from chirp import chirp_common -from chirp import settings -from chirp.ui import common, miscwidgets - -LOG = logging.getLogger(__name__) - - -class RadioSettingProxy(settings.RadioSetting): - - def __init__(self, setting, editor): - self._setting = setting - self._editor = editor - - -class SettingsEditor(common.Editor): - - def __init__(self, rthread): - super(SettingsEditor, self).__init__(rthread) - - # The main box - self.root = gtk.HBox(False, 0) - - # The pane - paned = gtk.HPaned() - paned.show() - self.root.pack_start(paned, 1, 1, 0) - - # The selection tree - self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT) - self._view = gtk.TreeView(self._store) - self._view.get_selection().connect("changed", self._view_changed_cb) - column = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0) - column.set_widget(gtk.Label()) - self._view.append_column(column) - self._view.show() - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled_window.add_with_viewport(self._view) - scrolled_window.set_size_request(200, -1) - scrolled_window.show() - paned.pack1(scrolled_window) - - # The settings notebook - self._notebook = gtk.Notebook() - self._notebook.set_show_tabs(False) - self._notebook.set_show_border(False) - self._notebook.show() - paned.pack2(self._notebook) - - self._changed = False - self._settings = None - - job = common.RadioJob(self._get_settings_cb, "get_settings") - job.set_desc("Getting radio settings") - self.rthread.submit(job) - - def _save_settings(self): - if self._settings is None: - return - - def setting_cb(result): - if isinstance(result, Exception): - common.show_error(_("Error in setting value: %s") % result) - elif self._changed: - self.emit("changed") - self._changed = False - - job = common.RadioJob(setting_cb, "set_settings", - self._settings) - job.set_desc("Setting radio settings") - self.rthread.submit(job) - - def _do_save_setting(self, widget, value): - if isinstance(value, settings.RadioSettingValueInteger): - value.set_value(widget.get_adjustment().get_value()) - elif isinstance(value, settings.RadioSettingValueFloat): - value.set_value(widget.get_text()) - elif isinstance(value, settings.RadioSettingValueBoolean): - value.set_value(widget.get_active()) - elif isinstance(value, settings.RadioSettingValueList): - value.set_value(widget.value) - elif isinstance(value, settings.RadioSettingValueString): - value.set_value(widget.get_text()) - else: - LOG.error("Unsupported widget type %s for %s" % - (element.value.__class__, element.get_name())) - - self._changed = True - self._save_settings() - - def _save_setting(self, widget, value): - try: - self._do_save_setting(widget, value) - except settings.InvalidValueError as e: - common.show_error(_("Invalid setting value: %s") % e) - - def _build_ui_tab(self, group): - - # The scrolled window - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.show() - - # Notebook tab - tab = self._notebook.append_page(sw, gtk.Label(_(group.get_name()))) - - # Settings table - table = gtk.Table(len(group), 2, False) - table.set_resize_mode(gtk.RESIZE_IMMEDIATE) - table.show() - sw.add_with_viewport(table) - - row = 0 - for element in group: - if not isinstance(element, settings.RadioSetting): - continue - - # Label - label = gtk.Label(element.get_shortname() + ":") - label.set_alignment(1.0, 0.5) - label.show() - - table.attach(label, 0, 1, row, row + 1, - xoptions=gtk.FILL, yoptions=0, - xpadding=6, ypadding=3) - - if isinstance(element.value, list) and \ - isinstance(element.value[0], - settings.RadioSettingValueInteger): - box = gtk.HBox(True) - else: - box = gtk.VBox(True) - - # Widget container - box.show() - table.attach(box, 1, 2, row, row + 1, - xoptions=gtk.FILL, yoptions=0, - xpadding=12, ypadding=3) - - for i in list(element.keys()): - value = element[i] - if isinstance(value, settings.RadioSettingValueInteger): - widget = gtk.SpinButton() - adj = widget.get_adjustment() - adj.configure(value.get_value(), - value.get_min(), value.get_max(), - value.get_step(), 1, 0) - widget.connect("value-changed", self._save_setting, value) - elif isinstance(value, settings.RadioSettingValueFloat): - widget = gtk.Entry() - widget.set_width_chars(16) - widget.set_text(value.format()) - widget.connect("focus-out-event", lambda w, e, v: - self._save_setting(w, v), value) - elif isinstance(value, settings.RadioSettingValueBoolean): - widget = gtk.CheckButton(_("Enabled")) - widget.set_active(value.get_value()) - widget.connect("toggled", self._save_setting, value) - elif isinstance(value, settings.RadioSettingValueList): - choice = miscwidgets.make_choice([], editable=False) - model = choice.get_model() - model.clear() - for option in value.get_options(): - choice.append_text(option) - choice.value = value.get_value() - widget = choice.widget - choice.connect("changed", self._save_setting, value) - elif isinstance(value, settings.RadioSettingValueString): - widget = gtk.Entry() - widget.set_width_chars(32) - widget.set_text(str(value).rstrip()) - widget.connect("focus-out-event", lambda w, e, v: - self._save_setting(w, v), value) - else: - LOG.error("Unsupported widget type: %s" % value.__class__) - - widget.set_sensitive(value.get_mutable()) - label.set_mnemonic_widget(widget) - widget.get_accessible().set_name(element.get_shortname()) - widget.show() - - box.pack_start(widget, 1, 1, 1) - - row += 1 - - return tab - - def _build_ui_group(self, group, parent): - tab = self._build_ui_tab(group) - - iter = self._store.append(parent) - self._store.set(iter, 0, group.get_shortname(), 1, tab) - - for element in group: - if not isinstance(element, settings.RadioSetting): - self._build_ui_group(element, iter) - - def _build_ui(self, settings): - if not isinstance(settings, list): - raise Exception("Invalid Radio Settings") - return - - self._settings = settings - for group in settings: - self._build_ui_group(group, None) - self._view.expand_all() - - def _get_settings_cb(self, settings): - gobject.idle_add(self._build_ui, settings) - - def _view_changed_cb(self, selection): - (lst, iter) = selection.get_selected() - tab, = self._store.get(iter, 1) - self._notebook.set_current_page(tab) diff --git a/chirp/ui/shiftdialog.py b/chirp/ui/shiftdialog.py deleted file mode 100644 index dee1cb47..00000000 --- a/chirp/ui/shiftdialog.py +++ /dev/null @@ -1,162 +0,0 @@ -# -# Copyright 2008 Dan Smith -# -# 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 . - -import gtk -import gobject -import threading -import logging - -from chirp import errors, chirp_common - -LOG = logging.getLogger(__name__) - - -class ShiftDialog(gtk.Dialog): - def __init__(self, rthread, parent=None): - buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_OK) - gtk.Dialog.__init__(self, - title=_("Shift"), - buttons=buttons) - self.set_default_response(gtk.RESPONSE_OK) - self.set_position(gtk.WIN_POS_CENTER_ALWAYS) - - self.rthread = rthread - - self.__prog = gtk.ProgressBar() - self.__prog.show() - - self.__labl = gtk.Label("") - self.__labl.show() - - self.vbox.pack_start(self.__prog, 1, 1, 1) - self.vbox.pack_start(self.__labl, 0, 0, 0) - - self.quiet = False - - self.thread = None - - def _status(self, msg, prog): - self.__labl.set_text(msg) - self.__prog.set_fraction(prog) - - def status(self, msg, prog): - gobject.idle_add(self._status, msg, prog) - - def _shift_memories(self, delta, memories): - count = 0.0 - for i in memories: - src = i.number - dst = src + delta - - LOG.info("Moving %i to %i" % (src, dst)) - self.status(_("Moving {src} to {dst}").format(src=src, - dst=dst), - count / len(memories)) - - i.number = dst - if i.empty: - self.rthread.radio.erase_memory(i.number) - else: - self.rthread.radio.set_memory(i) - count += 1.0 - - return int(count) - - def _get_mems_until_hole(self, start, endokay=False, all=False): - mems = [] - - llimit, ulimit = self.rthread.radio.get_features().memory_bounds - - pos = start - while pos <= ulimit: - self.status(_("Looking for a free spot ({number})").format( - number=pos), 0) - try: - mem = self.rthread.radio.get_memory(pos) - if mem.empty and not all: - break - except errors.InvalidMemoryLocation: - break - - mems.append(mem) - pos += 1 - - if pos > ulimit and not endokay: - raise errors.InvalidMemoryLocation(_("No space to insert a row")) - - LOG.debug("Found a hole: %i" % pos) - - return mems - - def _insert_hole(self, start): - mems = self._get_mems_until_hole(start) - mems.reverse() - if mems: - ret = self._shift_memories(1, mems) - if ret: - # Clear the hole we made - self.rthread.radio.erase_memory(start) - return ret - else: - LOG.warn("No memory list?") - return 0 - - def _delete_hole(self, start, all=False): - mems = self._get_mems_until_hole(start+1, endokay=True, all=all) - if mems: - count = self._shift_memories(-1, mems) - self.rthread.radio.erase_memory(count+start) - return count - else: - LOG.warn("No memory list?") - return 0 - - def finished(self): - if self.quiet: - gobject.idle_add(self.response, gtk.RESPONSE_OK) - else: - gobject.idle_add(self.set_response_sensitive, - gtk.RESPONSE_OK, True) - - def threadfn(self, newhole, func, *args): - self.status("Waiting for radio to become available", 0) - self.rthread.lock() - - try: - count = func(newhole, *args) - except errors.InvalidMemoryLocation as e: - self.status(str(e), 0) - self.finished() - return - - self.rthread.unlock() - self.status(_("Moved {count} memories").format(count=count), 1) - - self.finished() - - def insert(self, newhole, quiet=False): - self.quiet = quiet - self.thread = threading.Thread(target=self.threadfn, - args=(newhole, self._insert_hole)) - self.thread.start() - gtk.Dialog.run(self) - - def delete(self, newhole, quiet=False, all=False): - self.quiet = quiet - self.thread = threading.Thread(target=self.threadfn, - args=(newhole, self._delete_hole, all)) - self.thread.start() - gtk.Dialog.run(self) diff --git a/setup.py b/setup.py index bd6ab9cd..3d5dafdc 100644 --- a/setup.py +++ b/setup.py @@ -20,10 +20,8 @@ setup(name='chirp', ], extras_require={ 'wx': ['wxPython'], - 'gtk': ['PyGObject'] }, entry_points={ - 'console_scripts': ["chirp=chirp.wxui:chirpmain", - "legacychirp=chirp.ui.__main__:main"], + 'console_scripts': ["chirp=chirp.wxui:chirpmain"], }, ) diff --git a/tools/cpep8.manifest b/tools/cpep8.manifest index 8c0b0248..b875f703 100644 --- a/tools/cpep8.manifest +++ b/tools/cpep8.manifest @@ -144,24 +144,6 @@ ./chirp/pyPEG.py ./chirp/sources/radioreference.py ./chirp/settings.py -./chirp/ui/__init__.py -./chirp/ui/bandplans.py -./chirp/ui/bankedit.py -./chirp/ui/clone.py -./chirp/ui/cloneprog.py -./chirp/ui/common.py -./chirp/ui/dstaredit.py -./chirp/ui/editorset.py -./chirp/ui/importdialog.py -./chirp/ui/inputdialog.py -./chirp/ui/mainapp.py -./chirp/ui/memdetail.py -./chirp/ui/memedit.py -./chirp/ui/miscwidgets.py -./chirp/ui/radiobrowser.py -./chirp/ui/reporting.py -./chirp/ui/settingsedit.py -./chirp/ui/shiftdialog.py ./chirp/util.py ./chirpc ./rpttool