[chirp_devel] [PATCH 0 of 2] [RFC] Memory Detail Editor
This not-yet-finished RFC patch adds a memory detail editor to the UI.
Marco, I think this may be a better place to allow editing of TX and RX tones independently, instead of continuing to add that stuff in columns to the main editor. This will also set the stage for letting us expose radios-specific attributes of a memory object, but tacking on a RadioSettingsGroup and exposing that in the UI.
Thoughts?
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1336436519 25200 # Node ID d059f6451dc6c0e6c173b624cb8616e7e28c74c7 # Parent a7c0eabd3cc63c4acb3fe5af14290b217c8ba692 Move ValidateMemory() to RadioFeatures, which is where it really belongs, but leave linkage in Radio. #00
diff -r a7c0eabd3cc6 -r d059f6451dc6 chirp/chirp_common.py --- a/chirp/chirp_common.py Mon May 07 12:38:52 2012 -0700 +++ b/chirp/chirp_common.py Mon May 07 17:21:59 2012 -0700 @@ -773,6 +773,86 @@ def __getitem__(self, name): return self.__dict__[name]
+ def validate_memory(self, mem): + """Return a list of warnings and errors that will be encoundered + if trying to set @mem on the current radio""" + msgs = [] + rf = self.get_features() + + lo, hi = rf.memory_bounds + if not rf.has_infinite_number and \ + (mem.number < lo or mem.number > hi) and \ + mem.extd_number not in self.get_special_locations(): + msg = ValidationWarning("Location %i is out of range" % mem.number) + msgs.append(msg) + + if rf.valid_modes and mem.mode not in rf.valid_modes: + msg = ValidationError("Mode %s not supported" % mem.mode) + msgs.append(msg) + + if rf.valid_tmodes and mem.tmode not in rf.valid_tmodes: + msg = ValidationError("Tone mode %s not supported" % mem.tmode) + msgs.append(msg) + else: + if mem.tmode == "Cross": + if rf.valid_cross_modes and \ + mem.cross_mode not in rf.valid_cross_modes: + msg = ValidationError("Cross tone mode %s not supported" % \ + mem.cross_mode) + msgs.append(msg) + + if rf.has_dtcs_polarity and mem.dtcs_polarity not in rf.valid_dtcs_pols: + msg = ValidationError("DTCS Polarity %s not supported" % \ + mem.dtcs_polarity) + msgs.append(msg) + + if rf.valid_duplexes and mem.duplex not in rf.valid_duplexes: + msg = ValidationError("Duplex %s not supported" % mem.duplex) + msgs.append(msg) + + ts = mem.tuning_step + if rf.valid_tuning_steps and ts not in rf.valid_tuning_steps and \ + not rf.has_nostep_tuning: + msg = ValidationError("Tuning step %.2f not supported" % ts) + msgs.append(msg) + + if rf.valid_bands: + valid = False + for lo, hi in rf.valid_bands: + if mem.freq > lo and mem.freq < hi: + valid = True + break + if not valid: + msg = ValidationError( + ("Frequency {freq} is out " + "of supported range").format(freq=format_freq(mem.freq))) + msgs.append(msg) + + if mem.power and \ + rf.valid_power_levels and \ + mem.power not in rf.valid_power_levels: + msg = ValidationWarning("Power level %s not supported" % mem.power) + msgs.append(msg) + + if rf.valid_tuning_steps and not rf.has_nostep_tuning: + try: + step = required_step(mem.freq) + if step not in rf.valid_tuning_steps: + msg = ValidationError("Frequency requires %.2fkHz step" %\ + required_step(mem.freq)) + msgs.append(msg) + except errors.InvalidDataError, e: + msgs.append(str(e)) + + if rf.valid_characters: + for char in mem.name: + if char not in rf.valid_characters: + msgs.append(ValidationWarning(("Name character `%s'" % char) + + " not supported")) + break + + return msgs + class ValidationMessage(str): """Base class for Validation Errors and Warnings""" pass @@ -862,82 +942,8 @@ def validate_memory(self, mem): """Return a list of warnings and errors that will be encoundered if trying to set @mem on the current radio""" - msgs = [] rf = self.get_features() - - lo, hi = rf.memory_bounds - if not rf.has_infinite_number and \ - (mem.number < lo or mem.number > hi) and \ - mem.extd_number not in self.get_special_locations(): - msg = ValidationWarning("Location %i is out of range" % mem.number) - msgs.append(msg) - - if rf.valid_modes and mem.mode not in rf.valid_modes: - msg = ValidationError("Mode %s not supported" % mem.mode) - msgs.append(msg) - - if rf.valid_tmodes and mem.tmode not in rf.valid_tmodes: - msg = ValidationError("Tone mode %s not supported" % mem.tmode) - msgs.append(msg) - else: - if mem.tmode == "Cross": - if rf.valid_cross_modes and \ - mem.cross_mode not in rf.valid_cross_modes: - msg = ValidationError("Cross tone mode %s not supported" % \ - mem.cross_mode) - msgs.append(msg) - - if rf.has_dtcs_polarity and mem.dtcs_polarity not in rf.valid_dtcs_pols: - msg = ValidationError("DTCS Polarity %s not supported" % \ - mem.dtcs_polarity) - msgs.append(msg) - - if rf.valid_duplexes and mem.duplex not in rf.valid_duplexes: - msg = ValidationError("Duplex %s not supported" % mem.duplex) - msgs.append(msg) - - ts = mem.tuning_step - if rf.valid_tuning_steps and ts not in rf.valid_tuning_steps and \ - not rf.has_nostep_tuning: - msg = ValidationError("Tuning step %.2f not supported" % ts) - msgs.append(msg) - - if rf.valid_bands: - valid = False - for lo, hi in rf.valid_bands: - if mem.freq > lo and mem.freq < hi: - valid = True - break - if not valid: - msg = ValidationError( - ("Frequency {freq} is out " - "of supported range").format(freq=format_freq(mem.freq))) - msgs.append(msg) - - if mem.power and \ - rf.valid_power_levels and \ - mem.power not in rf.valid_power_levels: - msg = ValidationWarning("Power level %s not supported" % mem.power) - msgs.append(msg) - - if rf.valid_tuning_steps and not rf.has_nostep_tuning: - try: - step = required_step(mem.freq) - if step not in rf.valid_tuning_steps: - msg = ValidationError("Frequency requires %.2fkHz step" %\ - required_step(mem.freq)) - msgs.append(msg) - except errors.InvalidDataError, e: - msgs.append(str(e)) - - if rf.valid_characters: - for char in mem.name: - if char not in rf.valid_characters: - msgs.append(ValidationWarning(("Name character `%s'" % char) + - " not supported")) - break - - return msgs + return rf.validate_memory(mem)
def get_settings(self): """Returns a RadioSettingGroup containing one or more
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1336436519 25200 # Node ID 06d821868172fdd77282ebef4c785d050be09360 # Parent d059f6451dc6c0e6c173b624cb8616e7e28c74c7 Add memory detail editor
This adds an "Edit" option to the right-click context menu for a memory, which opens a dialog window allowing somewhat easier atomic editing of a memory's values. I think this will provide an easier way to expose some more complicated and radio-specific features of the memory objects, such as split tones and perhaps even D-STAR attributes.
#00
diff -r d059f6451dc6 -r 06d821868172 chirpui/memdetail.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirpui/memdetail.py Mon May 07 17:21:59 2012 -0700 @@ -0,0 +1,175 @@ +# Copyright 2012 Dan Smith dsmith@danplanet.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +import gtk + +from chirp import chirp_common +from chirpui import miscwidgets + +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 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""" + 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: + setattr(self._memory, self._name, self._get_value()) + except ValueError, e: + self._errfn(self._name, str(e)) + return str(e) + print self._memory + + # Validate! + + self._errfn(self._name, None) + +class StringEditor(ValueEditor): + def _init(self, data): + self._widget = gtk.Entry(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 ChoiceEditor(ValueEditor): + def _init(self, data): + self._widget = miscwidgets.make_choice([str(x) for x in data], + False, + str(self._mem_value())) + self._widget.connect("changed", self.changed) + + def _get_value(self): + return self._widget.get_active_text() + + def changed(self, _widget): + self.update() + +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) + self._widget.set_text(chirp_common.format_freq(self._mem_value())) + + def _get_value(self): + return chirp_common.parse_freq(self._widget.get_text()) + +class OffsetEditor(FreqEditor): + pass + +class MemoryDetailEditor(gtk.Dialog): + """Detail editor for a memory""" + + def _make_ui(self): + tab = gtk.Table(len(self._order), 3, False) + self.vbox.pack_start(tab, 1, 1, 1) + tab.show() + + row = 0 + + def err(name, msg): + _img = self._editors[name][1] + if msg is None: + _img.clear() + else: + _img.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + + for name in self._order: + labeltxt, editorcls, data = self._elements[name] + + label = gtk.Label(labeltxt) + img = gtk.Image() + + editor = editorcls(self._features, self._memory, + err, name, data) + + label.show() + tab.attach(label, 0, 1, row, row+1) + + img.set_size_request(15, -1) + img.show() + tab.attach(img, 2, 3, row, row+1) + + editor.get_widget().show() + tab.attach(editor.get_widget(), 1, 2, row, row+1) + + self._editors[name] = editor, img + row += 1 + + def __init__(self, features, memory, parent=None): + gtk.Dialog.__init__(self, + title=_("Edit Memory #{num}").format(num=memory.number), + parent=parent, + buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK, + gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) + self._features = features + self._memory = memory + + 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"), ChoiceEditor, 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), + # Power + "tuning_step" : (_("Tune Step"), FloatChoiceEditor, features.valid_tuning_steps), + "skip" : (_("Skip"), ChoiceEditor, features.valid_skips), + "comment" : (_("Comment"), StringEditor, 256), + } + self._order = ["freq", "name", "tmode", "rtone", "ctone", + "dtcs", "dtcs_polarity", "duplex", "offset", + "mode", "tuning_step", "skip", "comment"] + + self._make_ui() + self.set_default_size(400, -1) + + def get_memory(self): + return self._memory diff -r d059f6451dc6 -r 06d821868172 chirpui/memedit.py --- a/chirpui/memedit.py Mon May 07 17:21:59 2012 -0700 +++ b/chirpui/memedit.py Mon May 07 17:21:59 2012 -0700 @@ -32,7 +32,7 @@ import pickle import os
-from chirpui import common, shiftdialog, miscwidgets, config +from chirpui import common, shiftdialog, miscwidgets, config, memdetail from chirp import chirp_common, errors, directory, import_logic
def handle_toggle(_, path, store, col): @@ -612,6 +612,18 @@ job.set_desc(_("Getting raw memory {number}").format(number=loc_b)) self.rthread.submit(job)
+ def edit_memory(self, memory): + dlg = memdetail.MemoryDetailEditor(self._features, memory) + r = dlg.run() + if r == gtk.RESPONSE_OK: + self.need_refresh = True + mem = dlg.get_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.emit("changed") + dlg.destroy() + def mh(self, _action, store, paths): action = _action.get_name() iter = store.get_iter(paths[0]) @@ -648,6 +660,9 @@ self._show_raw(cur_pos) elif action == "devdiffraw": self._diff_raw(paths) + elif action == "edit": + job = common.RadioJob(self.edit_memory, "get_memory", cur_pos) + self.rthread.submit(job)
if changed: self.emit("changed") @@ -671,7 +686,8 @@
menu_xml = """ <ui> - <popup name="Menu"> + <popup name="Menu"> + <menuitem action="edit"/> <menuitem action="insert_prev"/> <menuitem action="insert_next"/> <menuitem action="delete"/> @@ -694,6 +710,7 @@ istwo = len(paths) == 2
actions = [ + ("edit", _("Edit")), ("insert_prev", _("Insert row above")), ("insert_next", _("Insert row below")), ("delete", issingle and _("Delete") or _("Delete all")),
I think the best could be to have info in column in the main editor letting the user choose which one he likes to see (as it's already supported) and a memory detail editor that show every detail supported by the single memory.
So my vote is "BOTH"
For the main editor we can then choose a light visibility default with just the main stuff.
73 de IZ3GME Marco
On 08/05/2012 02:22, Dan Smith wrote:
This not-yet-finished RFC patch adds a memory detail editor to the UI.
Marco, I think this may be a better place to allow editing of TX and RX tones independently, instead of continuing to add that stuff in columns to the main editor. This will also set the stage for letting us expose radios-specific attributes of a memory object, but tacking on a RadioSettingsGroup and exposing that in the UI.
Thoughts? _______________________________________________ chirp_devel mailing list chirp_devel@intrepid.danplanet.com http://intrepid.danplanet.com/mailman/listinfo/chirp_devel
I think the best could be to have info in column in the main editor letting the user choose which one he likes to see (as it's already supported) and a memory detail editor that show every detail supported by the single memory.
So my vote is "BOTH"
For the main editor we can then choose a light visibility default with just the main stuff.
Right, my point was not to remove anyting from the main editor as it stands (except perhaps the D-STAR stuff, which is messy) and just allow adding additional stuff (like your TX DCS code, and Tone->Tone items) in the detail editor.
Sound okay?
participants (2)
-
Dan Smith
-
IZ3GME Marco