[chirp_devel] [PATCH 0 of 7] Prototype of advanced bank model
Hi all,
For a long time, CHIRP has had a very naive internal model for dealing with banks of memories. This was developed at a time when CHIRP only handled Icom radios, and thus it assumed each memory could only be in one bank at a time. Yaesu radios have a one-to-many bank system, where memories can be in multiple banks at a time.
This patch set:
1. Creates a new bank model system 2. Demonstrates support with two icom radios (with slightly different bank mechanisms) and one yaesu radio 3. Adds UI elements to allow setting bank memberships
This only works for the three radios included right now, but I will finish the support for the rest of the radios that currently have bank support soon. Following that, I will add bank support to the radios that Jon's patches includes.
I'm sending these to the list in case anyone is interested or has comments about how this may or may not enable bank support for hopefully any type of radio out there.
Thanks!
-Dan KK7DS
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328671740 28800 # Node ID fa6b1f430f74f991f945a62dfb59e56d3ac6e4e7 # Parent 3db9dc931cbc2d39111c4173c4d51eeac3a362bc Bank Rebase: basic support
This change introduces a BankModel object, as well as refined Bank and NamedBank objects. A radio driver should implement BankModel and return it from Radio.get_banks(). This encapsulates all bank-related transactions. Radios with user-nameable banks should also implement NamedBank and plumb name changes accordingly.
diff -r 3db9dc931cbc -r fa6b1f430f74 chirp/chirp_common.py --- a/chirp/chirp_common.py Tue Feb 07 15:26:23 2012 -0800 +++ b/chirp/chirp_common.py Tue Feb 07 19:29:00 2012 -0800 @@ -527,18 +527,83 @@ self.dv_code = 0
class Bank: - def __init__(self, name): - self.__dict__["name"] = name + def __init__(self, model, index, name): + self._model = model + self._index = index + self._name = name
def __str__(self): - return self.name + return self._name
-class ImmutableBank(Bank): - def __setattr__(self, name, val): - if not hasattr(self, name): - raise ValueError("No such attribute `%s'" % name) - else: - raise ValueError("Property is immutable") + def get_name(self): + """Returns the static or user-adjustable bank name""" + return self._name + + def get_index(self): + """Returns the immutable bank index (string or int)""" + return self._index + + def __eq__(self, other): + return self.get_index() == other.get_index() + +class NamedBank(Bank): + def set_name(self, name): + """Changes the user-adjustable bank name""" + self._name = name + +class BankModel: + """A bank model where one memory is in zero or one banks at any point""" + def __init__(self, radio): + self._radio = radio + + def get_num_banks(self): + """Returns the number of banks (should be callable without + consulting the radio""" + raise Exception("Not implemented") + + def get_banks(self): + """Return a list of banks""" + raise Exception("Not implemented") + + def add_memory_to_bank(self, memory, bank): + """Add @memory to @bank.""" + raise Exception("Not implemented") + + def remove_memory_from_bank(self, memory, bank): + """Remove @memory from @bank. + Shall raise exception if @memory is not in @bank.""" + raise Exception("Not implemented") + + def get_bank_memories(self, bank): + """Return a list of memories in @bank""" + raise Exception("Not implemented") + + def get_memory_banks(self, memory): + """Returns a list of the banks that @memory is in""" + raise Exception("Not implemented") + +class BankIndexInterface: + def get_index_bounds(self): + """Returns a tuple (lo,hi) of the minimum and maximum bank indices""" + raise Exception("Not implemented") + + def get_memory_index(self, memory, bank): + """Returns the index of @memory in @bank""" + raise Exception("Not implemented") + + def set_memory_index(self, memory, bank, index): + """Sets the index of @memory in @bank to @index""" + raise Exception("Not implemented") + + def get_next_bank_index(self, bank): + """Returns the next available bank index in @bank, or raises + Exception if full""" + raise Exception("Not implemented") + + +class MTOBankModel(BankModel): + """A bank model where one memory can be in multiple banks at once """ + pass
def console_status(status): import sys @@ -562,6 +627,7 @@ "has_offset" : BOOLEAN, "has_name" : BOOLEAN, "has_bank" : BOOLEAN, + "has_bank_names" : BOOLEAN, "has_tuning_step" : BOOLEAN, "has_name" : BOOLEAN, "has_ctone" : BOOLEAN, @@ -645,6 +711,8 @@ "Indicates that an alphanumeric memory name is supported") self.init("has_bank", True, "Indicates that memories may be placed into banks") + self.init("has_bank_names", False, + "Indicates that banks may be named") self.init("has_tuning_step", True, "Indicates that memories store their tuning step") self.init("has_ctone", True, @@ -758,11 +826,9 @@ def set_memories(self, memories): pass
- def get_banks(self): - return [] - - def set_banks(self, banks): - raise errors.InvalidDataError("This model does not support bank naming") + def get_bank_model(self): + """Returns either a BankModel or None if not supported""" + return None
def get_raw_memory(self, number): pass
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328673680 28800 # Node ID 74c27f2df96137b1b07289a7bcdd31cf424e1dfd # Parent fa6b1f430f74f991f945a62dfb59e56d3ac6e4e7 [ic2820] New banks support
diff -r fa6b1f430f74 -r 74c27f2df961 chirp/ic2820.py --- a/chirp/ic2820.py Tue Feb 07 19:29:00 2012 -0800 +++ b/chirp/ic2820.py Tue Feb 07 20:01:20 2012 -0800 @@ -84,6 +84,112 @@
MEM_LOC_SIZE = 48
+class IC2820Bank(chirp_common.NamedBank): + def set_name(self, name): + _banks = self._model._radio._memobj.bank_names + _banks[self.index].name = str(name).ljust(8)[:8] + +class IC2820BankModel(chirp_common.BankModel, + chirp_common.BankIndexInterface): + def get_num_banks(self): + return 26 # A-Z + + def get_banks(self): + banks = [] + _banks = self._radio._memobj.bank_names + + for i in range(0, 26): + bank = IC2820Bank(self, + chr(ord("A") + i), + str(_banks[i].name).rstrip()) + bank.index = i + banks.append(bank) + + return banks + + def add_memory_to_bank(self, memory, bank): + if memory.number >= 500: + raise Exception("Special memories cannot be in banks") + + _bank = self._radio._memobj.bank_info[memory.number] + if bank: + _bank.bank = bank.index + _bank.index = self.get_next_bank_index(bank) + else: + self._remove_memory_from_bank(memory, None) + + def remove_memory_from_bank(self, memory, bank): + if memory.number >= 500: + raise Exception("Special memories cannot be in banks") + + _bank = self._radio._memobj.bank_info[memory.number] + if bank and _bank.bank != bank.index: + raise Exception("Memory %i is not in bank %s. Cannot remove" % \ + (memory.number, bank)) + + _bank.bank = 0xFF + _bank.index = 0xFF + + def get_bank_memories(self, bank): + memories = [] + for i in range(0, 500): + _bank = self._radio._memobj.bank_info[i] + if _bank.bank == bank.index: + memories.append(self._radio.get_memory(i)) + return memories + + def get_memory_banks(self, memory): + if memory.number >= 500: + raise Exception("Special memories cannoy be in banks") + + _bank = self._radio._memobj.bank_info[memory.number] + if _bank.bank == 0xFF: + return [] + else: + return [self.get_banks()[_bank.bank]] + + def get_index_bounds(self): + return (0, 99) + + def get_memory_index(self, memory, bank): + if memory.number >= 500: + raise Exception("Special memories cannot be in banks") + + _bank = self._radio._memobj.bank_info[memory.number] + if _bank.bank != bank.index: + raise Exception("Memory %i is not in bank %s" % (memory.number, + bank)) + + return _bank.index + + def set_memory_index(self, memory, bank, index): + if memory.number >= 500: + raise Exception("Special memories cannoy be in banks") + + if index not in range(*self.get_index_bounds()): + raise Exception("Invalid index") + + _bank = self._radio._memobj.bank_info[memory.number] + if _bank.bank != bank.index: + raise Exception("Memory %i is not in bank %s" % (memory.number, + bank)) + + _bank.index = index + + def get_next_bank_index(self, bank): + indexes = [] + for i in range(0, 500): + _bank = self._radio._memobj.bank_info[i] + if _bank.bank == bank.index and _bank.index >= 0: + indexes.append(_bank.index) + + for i in range(0, 256): + if i not in indexes: + return i + + raise errors.RadioError("Out of slots in this bank") + + class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): VENDOR = "Icom" MODEL = "IC-2820H" @@ -107,6 +213,7 @@ def get_features(self): rf = chirp_common.RadioFeatures() rf.has_bank_index = True + rf.has_bank_names = True rf.requires_call_lists = False rf.memory_bounds = (0, 499) rf.valid_modes = list(MODES) @@ -119,18 +226,6 @@ rf.valid_name_length = 8 return rf
- def get_available_bank_index(self, bank): - indexes = [] - for mem in self._memories.values(): - if mem.bank == bank and mem.bank_index >= 0: - indexes.append(mem.bank_index) - - for i in range(0, 256): - if i not in indexes: - return i - - raise errors.RadioError("Out of slots in this bank") - def _get_special(self): special = {"C0" : 500 + 20, "C1" : 500 + 21} @@ -149,9 +244,14 @@ def process_mmap(self): self._memobj = bitwise.parse(mem_format, self._mmap)
+ def _resolve_memory_number(self, number): + if isinstance(number, str): + return self._get_special()[number] + else: + return number + def get_memory(self, number): - if isinstance(number, str): - number = self._get_special()[number] + number = self._resolve_memory_number(number)
bitpos = (1 << (number % 8)) bytepos = number / 8 @@ -171,13 +271,6 @@
mem.number = number if number < 500: - _bank = self._memobj.bank_info[number] - mem.bank = _bank.bank - mem.bank_index = _bank.index - if mem.bank == 0xFF: - mem.bank = None - mem.bank_index = -1 - _skip = self._memobj.skip_flags[bytepos] _pskip = self._memobj.pskip_flags[bytepos] if _skip & bitpos: @@ -223,14 +316,6 @@ was_empty = _used & bitpos
if mem.number < 500: - _bank = self._memobj.bank_info[mem.number] - if mem.bank: - _bank.bank = mem.bank - _bank.index = mem.bank_index - else: - _bank.bank = 0xFF - _bank.index = 0xFF - skip = self._memobj.skip_flags[bytepos] pskip = self._memobj.pskip_flags[bytepos] if mem.skip == "S": @@ -271,19 +356,8 @@ def get_raw_memory(self, number): return repr(self._memobj.memory[number])
- def get_banks(self): - _banks = self._memobj.bank_names - - banks = [] - for i in range(0, 26): - banks.append(str(_banks[i].name).rstrip()) - - return banks - - def set_banks(self, banks): - _banks = self._memobj.bank_names - for i in range(0, 26): - _banks[i].name = str(banks[i]).ljust(8)[:8] + def get_bank_model(self): + return IC2820BankModel(self)
def get_urcall_list(self): _calls = self._memobj.urcall
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328673682 28800 # Node ID d447fde6f900f53d8a0ec5e27e87c13a18a276c2 # Parent 74c27f2df96137b1b07289a7bcdd31cf424e1dfd [ic2200] New bank model support
diff -r 74c27f2df961 -r d447fde6f900 chirp/ic2200.py --- a/chirp/ic2200.py Tue Feb 07 20:01:20 2012 -0800 +++ b/chirp/ic2200.py Tue Feb 07 20:01:22 2012 -0800 @@ -83,6 +83,45 @@ chirp_common.PowerLevel("MidLow", watts=10), chirp_common.PowerLevel("Low", watts=5)]
+class IC2200BankModel(chirp_common.BankModel): + def get_num_banks(self): + return 10 # A-J + + def get_banks(self): + banks = [] + for i in range(0, 10): + index = chr(ord("A") + i) + bank = chirp_common.Bank(self, index, "BANK-%s" % index) + bank.index = i + banks.append(bank) + return banks + + def add_memory_to_bank(self, memory, bank): + _flag = self._radio._memobj.flags[memory.number] + _flag.bank = bank and bank.index or 0x0A + + def remove_memory_from_bank(self, memory, bank): + _flag = self._radio._memobj.flags[memory.number] + if bank and _flag.bank != bank.index: + raise Exception("Memory %i not in bank %s. Cannot remove." % \ + (memory.number, bank)) + _flag.bank = 0x0A + + def get_bank_memories(self, bank): + memories = [] + for i in range(0, 199): + _flag = self._radio._memobj.flags[i] + if _flag.bank == bank.index: + memories.append(self._radio.get_memory(i)) + return memories + + def get_memory_banks(self, memory): + _flag = self._radio._memobj.flags[memory.number] + if _flag.bank == 0x0A: + return [] + else: + return [self.get_banks()[_flag.bank]] + class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport): VENDOR = "Icom" MODEL = "IC-2200H" @@ -258,17 +297,8 @@ def get_raw_memory(self, number): return repr(self._memobj.memory[number])
- def get_banks(self): - banks = [] - - for i in range(0, 10): - bank = chirp_common.ImmutableBank("BANK-%s" % (chr(ord("A")+i))) - banks.append(bank) - - return banks - - def set_banks(self, banks): - raise errors.InvalidDataError("Bank naming not supported on this model") + def get_bank_model(self): + return IC2200BankModel(self)
def get_urcall_list(self): return [str(x.call).rstrip() for x in self._memobj.urcalls]
# HG changeset patch # User Jon K Hellan hellan@acm.org # Date 1306516175 -7200 # Node ID 4688777e2c3a5a27df9240962fe26de3355df4f9 # Parent d447fde6f900f53d8a0ec5e27e87c13a18a276c2 Original Yaesu banks patch from Jon
diff -r d447fde6f900 -r 4688777e2c3a chirp/ft7800.py --- a/chirp/ft7800.py Tue Feb 07 20:01:22 2012 -0800 +++ b/chirp/ft7800.py Fri May 27 19:09:35 2011 +0200 @@ -51,6 +51,11 @@ unknown2:7; } names[1000];
+#seekto 0x6c48; +struct { + u32 bitmap[32]; +} bank_channels[20]; + #seekto 0x7648; struct { u8 skip0:2, @@ -323,6 +328,17 @@
self._set_mem_skip(mem, _mem)
+ # Return channels for a bank. Bank given as number + def get_bank_channels(self, bank): + channels = [] + for i in range(1000): + _bitmap = self._memobj.bank_channels[bank].bitmap[i/32] + ishft = 31 - (i % 32) + if (_bitmap >> ishft) & 1 == 1: + channels.append(i) + + return channels +
class FT7900Radio(FT7800Radio): MODEL = "FT-7900" diff -r d447fde6f900 -r 4688777e2c3a chirp/vx3.py --- a/chirp/vx3.py Tue Feb 07 20:01:22 2012 -0800 +++ b/chirp/vx3.py Fri May 27 19:09:35 2011 +0200 @@ -27,6 +27,11 @@ #seekto 0x7F4A; u8 checksum;
+#seekto 0x0B7A; +struct { + u8 name[6]; +} bank_names[24]; + #seekto 0x20CA; struct { u8 even_pskip:1, @@ -211,7 +216,16 @@ if mem.name.strip(): _mem.name[0] |= 0x80
def get_banks(self): - return [] + _banks = self._memobj.bank_names + + banks = [] + for _bank in _banks: + name = "" + for i in _bank.name: + name += CHARSET[i & 0x7F] + banks.append(name.rstrip()) + + return banks
def validate_memory(self, mem): msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem) diff -r d447fde6f900 -r 4688777e2c3a chirp/vx6.py --- a/chirp/vx6.py Tue Feb 07 20:01:22 2012 -0800 +++ b/chirp/vx6.py Fri May 27 19:09:35 2011 +0200 @@ -35,6 +35,19 @@ # D Tone: Encodes DCS code, decodes tone # } mem_format = """ +#seekto 0x018A; +u16 bank_sizes[24]; + +#seekto 0x097A; +struct { + u8 name[6]; +} bank_names[24]; + +#seekto 0x0C0A; +struct { + u16 channel[100]; +} bank_channels[24]; + #seekto 0x1ECA; struct { u8 even_pskip:1, @@ -210,3 +223,29 @@
if mem.name.strip(): _mem.name[0] |= 0x80 + + def get_banks(self): + _banks = self._memobj.bank_names + + banks = [] + for bank in _banks: + name = "" + for i in bank.name: + name += CHARSET[i & 0x7F] + banks.append(name.rstrip()) + + return banks + + # Return channels for a bank. Bank given as number + def get_bank_channels(self, bank): + nchannels = 0 + size = self._memobj.bank_sizes[bank] + if size <= 198: + nchannels = 1 + size/2 + _channels = self._memobj.bank_channels[bank] + channels = [] + for i in range(0, nchannels): + channels.append(int(_channels.channel[i])) + + return channels + diff -r d447fde6f900 -r 4688777e2c3a chirp/vx8.py --- a/chirp/vx8.py Tue Feb 07 20:01:22 2012 -0800 +++ b/chirp/vx8.py Fri May 27 19:09:35 2011 +0200 @@ -17,6 +17,22 @@ from chirp import bitwise
mem_format = """ +#seekto 0x54a; +struct { + u16 in_use; +} bank_used[24]; + +#seekto 0x135A; +struct { + u8 unknown[2]; + u8 name[16]; +} bank_info[24]; + +#seekto 0x198a; +struct { + u16 channel[100]; +} bank_members[24]; + #seekto 0x2C4A; struct { u8 flag; @@ -176,7 +192,29 @@ flag.flag |= skipbits
def get_banks(self): - return [] + _banks = self._memobj.bank_info + + banks = [] + for _bank in _banks: + name = "" + for i in _bank.name: + if i == 0xFF: + break + name += CHARSET[i & 0x7F] + banks.append(name.rstrip()) + + return banks + + # Return channels for a bank. Bank given as number + def get_bank_channels(self, bank): + _members = self._memobj.bank_members[bank] + channels = [] + for channel in _members.channel: + if channel == 0xffff: + break + channels.append(int(channel)) + + return channels
class VX8DRadio(VX8Radio): _model = "AH29D"
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328673811 28800 # Node ID 5f81b0536807cb84da7685d1014dd7f44da9f3fb # Parent 4688777e2c3a5a27df9240962fe26de3355df4f9 [vx8] New bank model support
diff -r 4688777e2c3a -r 5f81b0536807 chirp/vx8.py --- a/chirp/vx8.py Fri May 27 19:09:35 2011 +0200 +++ b/chirp/vx8.py Tue Feb 07 20:03:31 2012 -0800 @@ -82,6 +82,74 @@ chirp_common.PowerLevel("L2", watts=1.00), chirp_common.PowerLevel("L1", watts=0.05)]
+class VX8Bank(chirp_common.NamedBank): + def set_name(self, name): + _bank = self._model._radio._memobj.bank_info[self.index] + _bank.name = [CHARSET.index(x) for x in name.ljust(16)[:16]] + +class VX8BankModel(chirp_common.BankModel): + def get_num_banks(self): + return 24 + + def get_banks(self): + banks = [] + _banks = self._radio._memobj.bank_info + + index = 0 + for _bank in _banks: + name = "" + for i in _bank.name: + if i == 0xFF: + break + name += CHARSET[i & 0x7F] + bank = VX8Bank(self, "%i" % index, name.rstrip()) + bank.index = index + banks.append(bank) + index += 1 + + return banks + + def add_memory_to_bank(self, memory, bank): + _members = self._radio._memobj.bank_members[bank.index] + for i in range(0, 100): + if _members.channel[i] == 0xFFFF: + _members.channel[i] = memory.number + break + + def remove_memory_from_bank(self, memory, bank): + _members = self._radio._memobj.bank_members[bank.index] + old_members = [x.number for x in self.get_bank_memories(bank)] + if not memory.number in old_members: + raise Exception("Memory %i is not in bank %s. Cannot remove" % \ + (memory.number, bank)) + old_members.remove(memory.number) + idx = 0 + for number in old_members: + _members.channel[idx] = number + idx += 1 + if idx < 100: + _members.channel[idx] = 0xFFFF + + def get_bank_memories(self, bank): + memories = [] + _members = self._radio._memobj.bank_members[bank.index] + + for channel in _members.channel: + if channel == 0xffff: + break + memories.append(self._radio.get_memory(int(channel))) + + return memories + + def get_memory_banks(self, memory): + banks = [] + for bank in self.get_banks(): + if memory.number in \ + [x.number for x in self.get_bank_memories(bank)]: + banks.append(bank) + + return banks + class VX8Radio(yaesu_clone.YaesuCloneModeRadio): BAUD_RATE = 38400 VENDOR = "Yaesu" @@ -98,7 +166,6 @@
def get_features(self): rf = chirp_common.RadioFeatures() - rf.has_bank = False rf.has_dtcs_polarity = False rf.valid_modes = list(MODES) rf.valid_tmodes = list(TMODES) @@ -112,6 +179,7 @@ rf.memory_bounds = (1, 900) rf.can_odd_split = True rf.has_ctone = False + rf.has_bank_names = True return rf
def get_raw_memory(self, number): @@ -191,30 +259,8 @@ skipbits = SKIPS.index(mem.skip) << 2 flag.flag |= skipbits
- def get_banks(self): - _banks = self._memobj.bank_info - - banks = [] - for _bank in _banks: - name = "" - for i in _bank.name: - if i == 0xFF: - break - name += CHARSET[i & 0x7F] - banks.append(name.rstrip()) - - return banks - - # Return channels for a bank. Bank given as number - def get_bank_channels(self, bank): - _members = self._memobj.bank_members[bank] - channels = [] - for channel in _members.channel: - if channel == 0xffff: - break - channels.append(int(channel)) - - return channels + def get_bank_model(self): + return VX8BankModel(self)
class VX8DRadio(VX8Radio): _model = "AH29D"
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328673811 28800 # Node ID 4023f8be12127e2931858891f8f950e950554117 # Parent 5f81b0536807cb84da7685d1014dd7f44da9f3fb Allow setting a target for RadioJob other than the radio itself
This will facilitate synchronized jobs to the BankModel, Bank, etc objects
diff -r 5f81b0536807 -r 4023f8be1212 chirpui/common.py --- a/chirpui/common.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/common.py Tue Feb 07 20:03:31 2012 -0800 @@ -53,6 +53,7 @@ self.args = args self.kwargs = kwargs self.desc = "Working" + self.target = None
def __str__(self): return "RadioJob(%s,%s,%s)" % (self.func, self.args, self.kwargs) @@ -63,13 +64,10 @@ def set_cb_args(self, *args): self.cb_args = args
- def execute(self, radio): - try: - func = getattr(radio, self.func) - except AttributeError, e: - print "No such radio function `%s'" % self.func - return + def set_target(self, target): + self.target = target
+ def _execute(self, target, func): try: DBG("Running %s (%s %s)" % (self.func, str(self.args), @@ -89,6 +87,18 @@ 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, e: + print "No such radio function `%s'" % self.func + return + + self._execute(self.target, func) + class RadioThread(threading.Thread, gobject.GObject): __gsignals__ = { "status" : (gobject.SIGNAL_RUN_LAST,
# HG changeset patch # User Dan Smith dsmith@danplanet.com # Date 1328673811 28800 # Node ID 8aeaa94466783d5a68c0a74cdb0cc8f39a526bf2 # Parent 4023f8be12127e2931858891f8f950e950554117 UI support for the new banks model
diff -r 4023f8be1212 -r 8aeaa9446678 chirpui/bankedit.py --- a/chirpui/bankedit.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/bankedit.py Tue Feb 07 20:03:31 2012 -0800 @@ -16,20 +16,40 @@ import gtk import gobject
+from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN + +from chirp import chirp_common from chirpui import common, miscwidgets
-class BankEditor(common.Editor): +class BankNamesJob(common.RadioJob): + def __init__(self, editor, cb): + common.RadioJob.__init__(self, cb, None) + self.__editor = editor + + def execute(self, radio): + self.__editor.banks = [] + + bm = radio.get_bank_model() + banks = bm.get_banks() + for bank in banks: + self.__editor.banks.append((bank, bank.get_name())) + + gobject.idle_add(self.cb, *self.cb_args) + +class BankNameEditor(common.Editor): def refresh(self): - def set_banks(banks): - i = ord("A") - for bank in banks: - self.listw.set_item(chr(i), chr(i), str(bank)) - i += 1 + def got_banks(): + self._keys = [] + for bank, name in self.banks: + self._keys.append(bank.get_index()) + self.listw.set_item(bank.get_index(), + bank.get_index(), + name)
self.listw.connect("item-set", self.bank_changed)
- job = common.RadioJob(set_banks, "get_banks") - job.set_desc(_("Retrieving bank list")) + job = BankNamesJob(self, got_banks) + job.set_desc(_("Retrieving bank information")) self.rthread.submit(job)
def get_bank_list(self): @@ -44,8 +64,15 @@ def cb(*args): self.emit("changed")
- job = common.RadioJob(cb, "set_banks", self.get_bank_list()) - job.set_desc(_("Setting bank list")) + name = self.listw.get_item(key)[2] + bank, oldname = self.banks[self._keys.index(key)] + + def trigger_changed(*args): + self.emit("changed") + + job = common.RadioJob(trigger_changed, "set_name", name) + job.set_target(bank) + job.set_desc(_("Setting name on bank")) self.rthread.submit(job)
return True @@ -64,10 +91,203 @@ self.listw.set_sort_column(1, -1) self.listw.show()
+ self.banks = [] + 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 + +class MemoryBanksJob(common.RadioJob): + def __init__(self, cb, number): + common.RadioJob.__init__(self, cb, None) + self.__number = number + + def execute(self, radio): + mem = radio.get_memory(self.__number) + if mem.empty: + banks = [] + indexes = [] + else: + bm = radio.get_bank_model() + banks = bm.get_memory_banks(mem) + indexes = [] + if isinstance(bm, chirp_common.BankIndexInterface): + for bank in banks: + indexes.append(bm.get_memory_index(mem, bank)) + self.cb(mem, banks, indexes, *self.cb_args) + +class BankMembershipEditor(common.Editor): + def _number_to_path(self, number): + return (number - self._rf.memory_bounds[0],) + + def _toggled_cb(self, rend, path, colnum): + # The bank index is the column number, minus the 3 label columns + bank, name = self.banks[colnum - len(self._cols)] + loc, = self._store.get(self._store.get_iter(path), 0) + + if rend.get_active(): + # Changing from True to False + fn = "remove_memory_from_bank" + else: + # Changing from False to True + fn = "add_memory_to_bank" + + def do_refresh_memory(*args): + # Step 2: Update our notion of the memory's bank information + self.refresh_memory(loc) + + def do_bank_adjustment(memory): + # Step 1: Do the bank add/remove + job = common.RadioJob(do_refresh_memory, fn, memory, bank) + job.set_target(self.rthread.radio.get_bank_model()) + job.set_desc(_("Updating bank information " + "for memory {num}").format(num=memory.number)) + self.rthread.submit(job) + + # Step 0: Fetch the memory + job = common.RadioJob(do_bank_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), 0) + + def refresh_memory(*args): + self.refresh_memory(loc) + + def set_index(banks, memory): + # Step 2: Set the index + job = common.RadioJob(refresh_memory, "set_memory_index", + memory, banks[0], int(new)) + job.set_target(self.rthread.radio.get_bank_model()) + job.set_desc(_("Setting index " + "for memory {num}").format(num=memory.number)) + self.rthread.submit(job) + + def get_bank(memory): + # Step 1: Get the first/only bank + job = common.RadioJob(set_index, "get_memory_banks", memory) + job.set_cb_args(memory) + job.set_target(self.rthread.radio.get_bank_model()) + job.set_desc(_("Getting bank for " + "memory {num}").format(num=memory.number)) + self.rthread.submit(job) + + # Step 0: Get the memory + job = common.RadioJob(get_bank, "get_memory", loc) + job.set_desc(_("Getting memory {num}").format(num=loc)) + self.rthread.submit(job) + + def __init__(self, rthread): + common.Editor.__init__(self) + self.rthread = rthread + self._rf = rthread.radio.get_features() + + self._cols = [ + (_("Loc"), TYPE_INT, gtk.CellRendererText, ), + (_("Frequency"), TYPE_STRING, gtk.CellRendererText, ), + (_("Name"), TYPE_STRING, gtk.CellRendererText, ), + (_("Index"), TYPE_INT, gtk.CellRendererText, ), + ] + + cols = list(self._cols) + + for i in range(0, self.rthread.radio.get_bank_model().get_num_banks()): + label = "Bank %i" % (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) + + colnum = 0 + for label, dtype, rtype in cols: + 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) + else: + col = gtk.TreeViewColumn(label, rend, text=colnum) + + self._view.append_column(col) + if colnum == 3: + rend.set_property("editable", True) + rend.connect("edited", self._index_edited_cb) + col.set_visible(self._rf.has_bank_index) + colnum += 1 + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.add(self._view) + self._view.show() + + for i in range(*self._rf.memory_bounds): + iter = self._store.append() + self._store.set(iter, 0, i, 1, 0, 2, "", 3, 0) + + self.root = sw + self._loaded = False + + def refresh_memory(self, number): + def got_mem(memory, banks, indexes): + iter = self._store.get_iter(self._number_to_path(memory.number)) + row = [0, memory.number, + 1, chirp_common.format_freq(memory.freq), + 2, memory.name, + # Hack for only one index right now + 3, indexes and indexes[0] or 0, + ] + for i in range(0, len(self.banks)): + row.append(i + len(self._cols)) + row.append(self.banks[i][0] in banks) + + self._store.set(iter, *tuple(row)) + + job = MemoryBanksJob(got_mem, number) + job.set_desc(_("Getting bank information " + "for memory {num}").format(num=number)) + self.rthread.submit(job) + + def refresh_all_memories(self): + for i in range(*self._rf.memory_bounds): + self.refresh_memory(i) + + def refresh_banks(self, and_memories=False): + def got_banks(): + for i in range(0, len(self.banks)): + col = self._view.get_column(i + len(self._cols)) + bank, name = self.banks[i] + if name: + col.set_title(name) + else: + col.set_title("(%s)" % i) + if and_memories: + self.refresh_all_memories() + + job = BankNamesJob(self, got_banks) + job.set_desc(_("Getting bank information")) + self.rthread.submit(job) + + def focus(self): + if self._loaded: + return + + self.refresh_banks(True) + + self._loaded = True + + def memories_changed(self): + self._loaded = False + + def banks_changed(self): + self.refresh_banks() diff -r 4023f8be1212 -r 8aeaa9446678 chirpui/common.py --- a/chirpui/common.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/common.py Tue Feb 07 20:03:31 2012 -0800 @@ -94,7 +94,8 @@ try: func = getattr(self.target, self.func) except AttributeError, e: - print "No such radio function `%s'" % self.func + print "No such radio function `%s' in %s" % (self.func, + self.target) return
self._execute(self.target, func) diff -r 4023f8be1212 -r 8aeaa9446678 chirpui/editorset.py --- a/chirpui/editorset.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/editorset.py Tue Feb 07 20:03:31 2012 -0800 @@ -59,43 +59,57 @@ 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, + } + if isinstance(self.radio, chirp_common.IcomDstarSupport): - self.memedit = memedit.DstarMemoryEditor(self.rthread) - self.dstared = dstaredit.DStarEditor(self.rthread) + self.editors["memedit"] = memedit.DstarMemoryEditor(self.rthread) + self.editors["dstar"] = dstaredit.DStarEditor(self.rthread) else: - print "Starting memedit" - self.memedit = memedit.MemoryEditor(self.rthread) - print "Started" - self.dstared = None + self.editors["memedit"] = memedit.MemoryEditor(self.rthread)
- self.memedit.connect("usermsg", lambda e, m: self.emit("usermsg", m)) + self.editors["memedit"].connect("usermsg", + lambda e, m: self.emit("usermsg", m))
- if self.radio.get_features().has_bank_index: - self.banked = bankedit.BankEditor(self.rthread) - else: - self.banked = None + rf = self.radio.get_features() + + if rf.has_bank: + self.editors["bank_members"] = \ + bankedit.BankMembershipEditor(self.rthread) + + if rf.has_bank_names: + self.editors["bank_names"] = bankedit.BankNameEditor(self.rthread)
lab = gtk.Label(_("Memories")) - self.tabs.append_page(self.memedit.root, lab) - self.memedit.root.show() + self.tabs.append_page(self.editors["memedit"].root, lab) + self.editors["memedit"].root.show()
- if self.dstared: + if self.editors["dstar"]: lab = gtk.Label(_("D-STAR")) - self.tabs.append_page(self.dstared.root, lab) - self.dstared.root.show() - self.dstared.connect("changed", self.dstar_changed) + self.tabs.append_page(self.editors["dstar"].root, lab) + self.editors["dstar"].root.show() + self.editors["dstar"].connect("changed", self.dstar_changed)
- if self.banked: + 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.banked.root, lab) - self.banked.root.show() - self.banked.connect("changed", self.banks_changed) + self.tabs.append_page(self.editors["bank_members"].root, lab) + self.editors["bank_members"].root.show()
self.pack_start(self.tabs) self.tabs.show()
# pylint: disable-msg=E1101 - self.memedit.connect("changed", self.editor_changed) + self.editors["memedit"].connect("changed", self.editor_changed)
self.label = self.text_label = None self.make_label() @@ -145,16 +159,17 @@
def dstar_changed(self, *args): print "D-STAR editor changed" - self.memedit.set_urcall_list(self.dstared.editor_ucall.get_callsigns()) - self.memedit.set_repeater_list(self.dstared.editor_rcall.get_callsigns()) - self.memedit.prefill() + 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() self.modified = True self.update_tab()
def banks_changed(self, *args): print "Banks changed" - self.memedit.set_bank_list(self.banked.get_bank_list()) - self.memedit.prefill() + self.editors["bank_members"].banks_changed() self.modified = True self.update_tab()
@@ -162,6 +177,8 @@ 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()
def get_tab_label(self): return self.label @@ -190,7 +207,7 @@ print "Imported %i" % count if count > 0: self.editor_changed() - gobject.idle_add(self.memedit.prefill) + gobject.idle_add(self.editors["memedit"].prefill)
dst_rthread._qunlock()
@@ -289,23 +306,22 @@ mem.freq = 146010000
def cb(*args): - gobject.idle_add(self.memedit.prefill) + gobject.idle_add(self.editors["memedit"].prefill)
job = common.RadioJob(cb, "set_memory", mem) job.set_desc(_("Priming memory")) self.rthread.submit(job)
def tab_selected(self, notebook, foo, pagenum): - pages = ["memory", "dstar", "banks"] - - # Quick hack for D-STAR editor - if pagenum == 1: - self.dstared.focus() - - self.emit("editor-selected", pages[pagenum]) + widget = notebook.get_nth_page(pagenum) + for k,v in self.editors.items(): + if v and v.root == widget: + v.focus() + self.emit("editor-selected", k) + break
def set_read_only(self, read_only=True): - self.memedit.set_read_only(read_only) + self.editors["memedit"].set_read_only(read_only)
def prepare_close(self): - self.memedit.prepare_close() + self.editors["memedit"].prepare_close() diff -r 4023f8be1212 -r 8aeaa9446678 chirpui/mainapp.py --- a/chirpui/mainapp.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/mainapp.py Tue Feb 07 20:03:31 2012 -0800 @@ -162,7 +162,7 @@
def ev_editor_selected(self, editorset, editortype): mappings = { - "memory" : ["view", "edit"], + "memedit" : ["view", "edit"], }
for _editortype, actions in mappings.items(): diff -r 4023f8be1212 -r 8aeaa9446678 chirpui/memedit.py --- a/chirpui/memedit.py Tue Feb 07 20:03:31 2012 -0800 +++ b/chirpui/memedit.py Tue Feb 07 20:03:31 2012 -0800 @@ -114,8 +114,6 @@ (_("Power") , TYPE_STRING, gtk.CellRendererCombo, ), (_("Tune Step") , TYPE_FLOAT, gtk.CellRendererCombo, ), (_("Skip") , TYPE_STRING, gtk.CellRendererCombo, ), - (_("Bank") , TYPE_STRING, gtk.CellRendererCombo, ), - (_("Bank Index"), TYPE_INT, gtk.CellRendererText, ), ("_filled" , TYPE_BOOLEAN, None, ), ("_hide_cols" , TYPE_PYOBJECT,None, ), ("_extd" , TYPE_STRING, None, ), @@ -136,8 +134,6 @@ _("Tune Step") : 5.0, _("Tone Mode") : "", _("Skip") : "", - _("Bank") : "", - _("Bank Index"): 0, }
choices = { @@ -241,10 +237,9 @@ return new
def _get_cols_to_hide(self, iter): - tmode, duplex, bank = self.store.get(iter, - self.col(_("Tone Mode")), - self.col(_("Duplex")), - self.col(_("Bank"))) + tmode, duplex = self.store.get(iter, + self.col(_("Tone Mode")), + self.col(_("Duplex")))
hide = []
@@ -267,9 +262,6 @@ if duplex == "" or duplex == "(None)": hide += [self.col(_("Offset"))]
- if bank == "": - hide += [self.col(_("Bank Index"))] - return hide
def maybe_hide_cols(self, iter): @@ -923,18 +915,6 @@ self.rthread.submit(job, 2)
def _set_memory(self, iter, memory): - try: - if memory.bank is None: - bank = "" - else: - pathstr = "%i" % (memory.bank + 1) - bi = self.choices[_("Bank")].get_iter_from_string(pathstr) - bank, = self.choices[_("Bank")].get(bi, 1) - except Exception, e: - common.log_exception() - print "Unable to get bank: %s" % e - bank = "" - self.store.set(iter, self.col("_filled"), not memory.empty, self.col(_("Loc")), memory.number, @@ -952,9 +932,7 @@ self.col(_("Mode")), memory.mode, self.col(_("Power")), memory.power or "", self.col(_("Tune Step")), memory.tuning_step, - self.col(_("Skip")), memory.skip, - self.col(_("Bank")), bank, - self.col(_("Bank Index")), memory.bank_index) + self.col(_("Skip")), memory.skip)
hide = self._get_cols_to_hide(iter) self.store.set(iter, self.col("_hide_cols"), hide) @@ -987,34 +965,6 @@ iter = self.store.iter_next(iter)
def _set_mem_vals(self, mem, vals, iter): - def get_bank_index(name): - bidx = 0 - banks = self.choices[_("Bank")] - iter = banks.get_iter_first() - iter = banks.iter_next(iter) - while iter: - _bank, = banks.get(iter, 1) - if name == _bank: - break - iter = banks.iter_next(iter) - bidx += 1 - - return bidx - - bank = vals[self.col(_("Bank"))] - if bank is "": - bidx = None - bank_index = vals[self.col(_("Bank Index"))] - else: - bidx = get_bank_index(bank) - if vals[self.col(_("Bank Index"))] == -1 and \ - self._features.has_bank_index: - bank_index = self.rthread.radio.get_available_bank_index(bidx) - print "Chose %i index for bank %s" % (bank_index, bank) - self.store.set(iter, self.col(_("Bank Index")), bank_index) - else: - bank_index = vals[self.col(_("Bank Index"))] - power_levels = {"" : None} for i in self._features.valid_power_levels: power_levels[str(i)] = i @@ -1036,8 +986,6 @@ mem.power = power_levels[vals[self.col(_("Power"))]] mem.tuning_step = vals[self.col(_("Tune Step"))] mem.skip = vals[self.col(_("Skip"))] - mem.bank = bidx - mem.bank_index = bank_index mem.empty = not vals[self.col("_filled")]
def _get_memory(self, iter): @@ -1127,16 +1075,6 @@
return hbox
- def set_bank_list(self, banks): - self.choices[_("Bank")].clear() - self.choices[_("Bank")].append(("", "(None)")) - - i = ord("A") - for bank in banks: - self.choices[_("Bank")].append((str(bank), - ("%s-%s" % (chr(i), str(bank))))) - i += 1 - def set_show_special(self, show): self.show_special = show self.prefill() @@ -1166,8 +1104,6 @@
def get_unsupported_columns(self): maybe_hide = [ - ("has_bank_index", _("Bank Index")), - ("has_bank", _("Bank")), ("has_dtcs", _("DTCS Code")), ("has_dtcs_polarity", _("DTCS Pol")), ("has_mode", _("Mode")), @@ -1240,7 +1176,6 @@
(min, max) = self._features.memory_bounds
- self.choices[_("Bank")] = gtk.ListStore(TYPE_STRING, TYPE_STRING) self.choices[_("Mode")] = self._features["valid_modes"] self.choices[_("Tone Mode")] = self._features["valid_tmodes"] self.choices[_("Cross Mode")] = self._features["valid_cross_modes"] @@ -1251,10 +1186,6 @@ if self._features["valid_power_levels"]: self.defaults[_("Power")] = self._features["valid_power_levels"][0]
- job = common.RadioJob(self.set_bank_list, "get_banks") - job.set_desc(_("Getting bank list")) - rthread.submit(job) - if not self._features["can_odd_split"]: # We need a new list, so .remove() won't work for us self.choices[_("Duplex")] = [x for x in self.choices[_("Duplex")] @@ -1362,9 +1293,6 @@ continue
mem.name = self.rthread.radio.filter_name(mem.name) - if not self._features.has_bank: - mem.bank = None - mem.bank_index = -1
src_number = mem.number mem.number = loc
- Creates a new bank model system
- Demonstrates support with two icom radios (with slightly different
bank mechanisms) and one yaesu radio 3. Adds UI elements to allow setting bank memberships
This only works for the three radios included right now, but I will finish the support for the rest of the radios that currently have bank support soon. Following that, I will add bank support to the radios that Jon's patches includes.
I'm sending these to the list in case anyone is interested or has comments about how this may or may not enable bank support for hopefully any type of radio out there.
Is this series in current nightlies or needs to be applied on top still?
Grant.
participants (2)
-
Dan Smith
-
Grant Diffey