# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1364673308 25200 # Node ID 05db705f36a2786c3265c16f16d50a1851cbb782 # Parent f081a4909064529f7bd41af6bd4611fe410c1f41 [RFC] Improve sub-device treatment in the UI
This changes how CHIRP exposes devices with sub-devices to the user, moving from an strange per-sub-device top-level tab to minor tabs along the left. This means replicating the memories, bank, and bank names tabs once per sub-device. In order to prevent two radiothreads from trying to talk to the same live device at the same time, RadioThread is extended to take a parent thread, which it shares the same runlock with for exclusion.
This is a big mess of a patch, but I'm not sure how else to break it up, since there is so much interaction between the affected elements. I have done minimal testing with a few models, but would really appreciate folks playing with this a bit (both with non-sub-device models, and especially live models, which I've not yet tested) in order to verify that this does not break anything.
Feedback please!
diff -r f081a4909064 -r 05db705f36a2 chirpui/bankedit.py --- a/chirpui/bankedit.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/bankedit.py Sat Mar 30 12:55:08 2013 -0700 @@ -79,8 +79,7 @@ return True
def __init__(self, rthread): - common.Editor.__init__(self) - self.rthread = rthread + super(BankNameEditor, self).__init__(rthread) self._bm = rthread.radio.get_bank_model()
types = [(gobject.TYPE_STRING, "key"), @@ -249,8 +248,8 @@ self.rthread.submit(job)
def __init__(self, rthread, editorset): - common.Editor.__init__(self) - self.rthread = rthread + super(BankMembershipEditor, self).__init__(rthread) + self.editorset = editorset self._rf = rthread.radio.get_features() self._bm = rthread.radio.get_bank_model() diff -r f081a4909064 -r 05db705f36a2 chirpui/common.py --- a/chirpui/common.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/common.py Sat Mar 30 12:55:08 2013 -0700 @@ -34,9 +34,11 @@
root = None
- def __init__(self): + 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 @@ -56,6 +58,15 @@ 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 + gobject.type_register(Editor)
def DBG(*args): @@ -128,16 +139,26 @@ (gobject.TYPE_STRING,)), }
- def __init__(self, radio): + 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.__lock = threading.Lock() - self.__runlock = threading.Lock() self.radio = radio
+ def _get_run_lock(self): + return self.__runlock + def _qlock(self): self.__lock.acquire()
@@ -196,7 +217,7 @@ self.__counter.release() self.__enabled = False
- def status(self, msg): + def _status(self, msg): jobs = 0 for i in dict(self.__queue): jobs += len(self.__queue[i]) @@ -224,14 +245,14 @@ 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() - + print "RadioThread exiting"
def log_exception(): diff -r f081a4909064 -r 05db705f36a2 chirpui/dstaredit.py --- a/chirpui/dstaredit.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/dstaredit.py Sat Mar 30 12:55:08 2013 -0700 @@ -179,8 +179,7 @@ self.rthread.submit(job)
def __init__(self, rthread): - common.Editor.__init__(self) - self.rthread = rthread + super(DStarEditor, self).__init__(rthread)
self.loaded = False
diff -r f081a4909064 -r 05db705f36a2 chirpui/editorset.py --- a/chirpui/editorset.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/editorset.py Sat Mar 30 12:55:08 2013 -0700 @@ -35,6 +35,50 @@ (gobject.TYPE_STRING,)), }
+ def _make_device_editors(self, device, devrthread, index): + key = "memedit%i" % index + if isinstance(device, chirp_common.IcomDstarSupport): + self.editors[key] = memedit.DstarMemoryEditor(devrthread) + else: + self.editors[key] = memedit.MemoryEditor(devrthread) + + self.editors[key].connect("usermsg", + lambda e, m: self.emit("usermsg", m)) + self.editors[key].connect("changed", self.editor_changed) + + if self.rf.has_sub_devices: + label = (_("Memories (%(variant)s)") % + dict(variant=device.VARIANT)) + else: + label = _("Memories") + lab = gtk.Label(label) + memedit_tab = self.tabs.append_page(self.editors[key].root, lab) + self.editors[key].root.show() + + if self.rf.has_bank: + key = "bank_members%i" % index + self.editors[key] = bankedit.BankMembershipEditor(devrthread, self) + if self.rf.has_sub_devices: + label = _("Banks (%(variant)s)") % dict(variant=device.VARIANT) + else: + label = _("Banks") + lab = gtk.Label(label) + self.tabs.append_page(self.editors[key].root, lab) + self.editors[key].root.show() + self.editors[key].connect("changed", self.banks_changed) + + if self.rf.has_bank_names: + self.editors["bank_names"] = bankedit.BankNameEditor(rthread) + if self.rf.has_sub_devices: + label = (_("Bank Names (%(variant)s)") % + dict(variant=device.VARIANT)) + else: + label = _("Bank Names") + lab = gtk.Label(label) + self.tabs.append_page(self.editors["bank_names"].root, lab) + self.editors["bank_names"].root.show() + self.editors["bank_names"].connect("changed", self.banks_changed) + def __init__(self, source, parent_window=None, filename=None, tempname=None): gtk.VBox.__init__(self, True, 0)
@@ -49,48 +93,41 @@ else: raise Exception("Unknown source type")
- self.rthread = common.RadioThread(self.radio) - self.rthread.setDaemon(True) - self.rthread.start() + rthread = common.RadioThread(self.radio) + rthread.setDaemon(True) + rthread.start()
- self.rthread.connect("status", lambda e, m: self.emit("status", m)) + 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 = { - "memedit" : None, "dstar" : None, - "bank_names" : None, - "bank_members" : None, "settings" : None, }
+ 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 isinstance(self.radio, chirp_common.IcomDstarSupport): - self.editors["memedit"] = memedit.DstarMemoryEditor(self.rthread) - self.editors["dstar"] = dstaredit.DStarEditor(self.rthread) - else: - self.editors["memedit"] = memedit.MemoryEditor(self.rthread) + self.editors["dstar"] = dstaredit.DStarEditor(rthread)
- self.editors["memedit"].connect("usermsg", - lambda e, m: self.emit("usermsg", m)) + if self.rf.has_settings: + self.editors["settings"] = settingsedit.SettingsEditor(rthread)
- rf = self.radio.get_features() - - if rf.has_bank: - self.editors["bank_members"] = \ - bankedit.BankMembershipEditor(self.rthread, self) - - if rf.has_bank_names: - self.editors["bank_names"] = bankedit.BankNameEditor(self.rthread) - - if rf.has_settings: - self.editors["settings"] = settingsedit.SettingsEditor(self.rthread) - - lab = gtk.Label(_("Memories")) - self.tabs.append_page(self.editors["memedit"].root, lab) - self.editors["memedit"].root.show()
if self.editors["dstar"]: lab = gtk.Label(_("D-STAR")) @@ -98,18 +135,6 @@ self.editors["dstar"].root.show() self.editors["dstar"].connect("changed", self.dstar_changed)
- if self.editors["bank_names"]: - lab = gtk.Label(_("Bank Names")) - self.tabs.append_page(self.editors["bank_names"].root, lab) - self.editors["bank_names"].root.show() - self.editors["bank_names"].connect("changed", self.banks_changed) - - if self.editors["bank_members"]: - lab = gtk.Label(_("Banks")) - self.tabs.append_page(self.editors["bank_members"].root, lab) - self.editors["bank_members"].root.show() - self.editors["bank_members"].connect("changed", self.banks_changed) - if self.editors["settings"]: lab = gtk.Label(_("Settings")) self.tabs.append_page(self.editors["settings"].root, lab) @@ -118,9 +143,6 @@ self.pack_start(self.tabs) self.tabs.show()
- # pylint: disable-msg=E1101 - self.editors["memedit"].connect("changed", self.editor_changed) - self.label = self.text_label = None self.make_label() self.modified = (tempname is not None) @@ -173,19 +195,21 @@
def dstar_changed(self, *args): print "D-STAR editor changed" - memedit = self.editors["memedit"] dstared = self.editors["dstar"] - memedit.set_urcall_list(dstared.editor_ucall.get_callsigns()) - memedit.set_repeater_list(dstared.editor_rcall.get_callsigns()) - memedit.prefill() + for editor in self.editors.values(): + if isinstance(editor, memedit.MemoryEditor): + editor.set_urcall_list(dstared.editor_ucall.get_callsigns()) + editor.set_repeater_list(dstared.editor_rcall.get_callsigns()) + editor.prefill() if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab()
def banks_changed(self, *args): print "Banks changed" - if self.editors["bank_members"]: - self.editors["bank_members"].banks_changed() + for editor in self.editors.values(): + if isinstance(editor, bankedit.BankMembershipEditor): + editor.banks_changed() if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() @@ -194,8 +218,9 @@ if not isinstance(self.radio, chirp_common.LiveRadio): self.modified = True self.update_tab() - if self.editors["bank_members"]: - self.editors["bank_members"].memories_changed() + for editor in self.editors.values(): + if isinstance(editor, bankedit.BankMembershipEditor): + editor.memories_changed()
def get_tab_label(self): return self.label @@ -226,7 +251,8 @@
if count > 0: self.editor_changed() - gobject.idle_add(self.editors["memedit"].prefill) + current_editor = self.get_current_editor() + gobject.idle_add(current_editor.prefill)
return count
@@ -251,6 +277,13 @@ 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, e: @@ -338,11 +371,13 @@ self)
def prime(self): + # NOTE: this is only called to prime new CSV files, so assume + # only one memory editor for now mem = chirp_common.Memory() mem.freq = 146010000
def cb(*args): - gobject.idle_add(self.editors["memedit"].prefill) + gobject.idle_add(self.editors["memedit0"].prefill)
job = common.RadioJob(cb, "set_memory", mem) job.set_desc(_("Priming memory")) @@ -358,16 +393,25 @@ v.unfocus()
def set_read_only(self, read_only=True): - self.editors["memedit"].set_read_only(read_only) - + for editor in self.editors.values(): + editor and editor.set_read_only(read_only) + def get_read_only(self): - return self.editors["memedit"].get_read_only() + return self.editors["memedit0"].get_read_only()
def prepare_close(self): - self.editors["memedit"].prepare_close() + for editor in self.editors.values(): + editor and editor.prepare_close()
def get_current_editor(self): - for e in self.editors.values(): + for lab, e in self.editors.items(): if e and self.tabs.page_num(e.root) == self.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 -r f081a4909064 -r 05db705f36a2 chirpui/mainapp.py --- a/chirpui/mainapp.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/mainapp.py Sat Mar 30 12:55:08 2013 -0700 @@ -300,8 +300,6 @@ # We have to actually instantiate the IC9xICFRadio to get its # sub-devices radio = ic9x_icf.IC9xICFRadio(fname) - devices = radio.get_sub_devices() - del radio else: try: radio = directory.get_radio_by_image(fname) @@ -315,39 +313,30 @@ common.show_error(os.path.basename(fname) + ": " + str(e)) return
- if radio.get_features().has_sub_devices: - devices = radio.get_sub_devices() - else: - devices = [radio] + first_tab = False + try: + eset = editorset.EditorSet(radio, self, + filename=fname, + tempname=tempname) + except Exception, e: + common.log_exception() + common.show_error( + _("There was an error opening {fname}: {error}").format( + fname=fname, + error=e)) + return
- prio = len(devices) - first_tab = False - for device in devices: - try: - eset = editorset.EditorSet(device, self, - filename=fname, - tempname=tempname) - except Exception, e: - common.log_exception() - common.show_error( - _("There was an error opening {fname}: {error}").format( - fname=fname, - error=error)) - return - - eset.set_read_only(read_only) - self._connect_editorset(eset) - eset.show() - tab = self.tabs.append_page(eset, eset.get_tab_label()) - if first_tab: - self.tabs.set_current_page(tab) - first_tab = False + eset.set_read_only(read_only) + self._connect_editorset(eset) + eset.show() + self.tabs.append_page(eset, eset.get_tab_label())
- if hasattr(eset.rthread.radio, "errors") and \ - eset.rthread.radio.errors: - msg = _("{num} errors during open:").format(num=len(eset.rthread.radio.errors)) - common.show_error_text(msg, - "\r\n".join(eset.rthread.radio.errors)) + if hasattr(eset.rthread.radio, "errors") and \ + eset.rthread.radio.errors: + msg = _("{num} errors during open:").format( + num=len(eset.rthread.radio.errors)) + common.show_error_text(msg, + "\r\n".join(eset.rthread.radio.errors))
def do_live_warning(self, radio): d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK) @@ -369,23 +358,12 @@ d.destroy()
def do_open_live(self, radio, tempname=None, read_only=False): - if radio.get_features().has_sub_devices: - devices = radio.get_sub_devices() - else: - devices = [radio] - - first_tab = True - for device in devices: - eset = editorset.EditorSet(device, self, tempname=tempname) - eset.connect("want-close", self.do_close) - eset.connect("status", self.ev_status) - eset.set_read_only(read_only) - eset.show() - - tab = self.tabs.append_page(eset, eset.get_tab_label()) - if first_tab: - self.tabs.set_current_page(tab) - first_tab = 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) diff -r f081a4909064 -r 05db705f36a2 chirpui/memedit.py --- a/chirpui/memedit.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/memedit.py Sat Mar 30 12:55:08 2013 -0700 @@ -1162,12 +1162,6 @@ self.prefill() self._config.set_bool("hide_empty", not show)
- def set_read_only(self, read_only): - self.read_only = read_only - - def get_read_only(self): - return self.read_only - def set_hide_unused(self, hide_unused): self.hide_unused = hide_unused self.prefill() @@ -1227,8 +1221,7 @@ return user_visible
def __init__(self, rthread): - common.Editor.__init__(self) - self.rthread = rthread + super(MemoryEditor, self).__init__(rthread)
self.defaults = dict(self.defaults)
diff -r f081a4909064 -r 05db705f36a2 chirpui/settingsedit.py --- a/chirpui/settingsedit.py Thu Mar 28 16:34:23 2013 -0700 +++ b/chirpui/settingsedit.py Sat Mar 30 12:55:08 2013 -0700 @@ -26,8 +26,7 @@
class SettingsEditor(common.Editor): def __init__(self, rthread): - common.Editor.__init__(self) - self._rthread = rthread + super(SettingsEditor, self).__init__(rthread)
self.root = gtk.HBox(False, 10) self._store = gtk.TreeStore(gobject.TYPE_STRING, @@ -57,7 +56,7 @@
job = common.RadioJob(self._build_ui, "get_settings") job.set_desc("Getting radio settings") - self._rthread.submit(job) + self.rthread.submit(job)
def _save_settings(self): if self._top_setting_group is None: @@ -70,7 +69,7 @@ job = common.RadioJob(setting_cb, "set_settings", self._top_setting_group) job.set_desc("Setting radio settings") - self._rthread.submit(job) + self.rthread.submit(job)
def _load_setting(self, value, widget): if isinstance(value, settings.RadioSettingValueInteger):