[chirp_devel] [PATCH] [RFC] Improve sub-device treatment in the UI

Dan Smith
Sat Mar 30 12:57:21 PDT 2013


# HG changeset patch
# User Dan Smith <dsmith at 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):



More information about the chirp_devel mailing list