Delete chirp.ui module

This removes the legacy GTK GUI from the tree. This is not being
maintained, doesn't work very well, and I'm tired of accidentally
editing files here when I mean to edit wxui/*. When we announce the
chirp-next stuff to the users, I don't want packagers to be confused
about which version to use, what the required dependencies are, etc.
This commit is contained in:
Dan Smith 2022-12-25 09:01:32 -08:00 committed by Dan Smith
parent 6964f1a0cc
commit b9054684e7
22 changed files with 1 additions and 9401 deletions

View file

@ -1,14 +0,0 @@
# Copyright 2008 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/>.

View file

@ -1,175 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2014 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/>.
def main():
import glob
import os
import six
import sys
import os
import locale
import gettext
import argparse
import logging
import urllib
if six.PY3:
from gi import pygtkcompat
pygtkcompat.enable()
pygtkcompat.enable_gtk(version='3.0')
from chirp import chirp_common
from chirp import directory
from chirp import logger
from chirp import elib_intl
from chirp import platform
from chirp.ui import config
directory.import_drivers()
LOG = logging.getLogger("chirpw")
# Does not work with python3 chirpw
# urllib.URLopener.version = chirp_common.http_user_agent()
localepath = platform.get_platform().find_resource("locale")
conf = config.get()
manual_language = conf.get("language", "state")
langs = []
if manual_language and manual_language != "Auto":
lang_codes = {"English": "en_US",
"Polish": "pl",
"Italian": "it",
"Dutch": "nl",
"German": "de",
"Hungarian": "hu",
"Russian": "ru",
"Portuguese (BR)": "pt_BR",
"French": "fr",
"Spanish": "es_ES",
"Chinese (Simplified)": "zh_CN",
'Ukrainian': "uk_UA",
'Turkish': "tr_TR",
}
try:
LOG.info("Language: %s", lang_codes[manual_language])
langs = [lang_codes[manual_language]]
except KeyError:
LOG.error("Unsupported language `%s'" % manual_language)
else:
lc, encoding = locale.getdefaultlocale()
if (lc):
langs = [lc]
try:
langs += os.getenv("LANG").split(":")
except:
pass
try:
if os.name == "nt":
elib_intl._putenv("LANG", langs[0])
else:
os.putenv("LANG", langs[0])
except IndexError:
pass
path = "locale"
gettext.bindtextdomain("CHIRP", localepath)
gettext.textdomain("CHIRP")
lang = gettext.translation("CHIRP", localepath, languages=langs,
fallback=True)
# Python <2.6 does not have str.format(), which chirp uses to make translation
# strings nicer. So, instead of installing the gettext standard "_()" function,
# we can install our own, which returns a string of the following class,
# which emulates the new behavior, thus allowing us to run on older Python
# versions.
class CompatStr(str):
def format(self, **kwargs):
base = lang.gettext(self)
for k, v in kwargs.items():
base = base.replace("{%s}" % k, str(v))
return base
pyver = sys.version.split()[0]
try:
vmaj, vmin, vrel = pyver.split(".", 3)
except:
vmaj, vmin = pyver.split(".", 2)
vrel = 0
if int(vmaj) == 2 and int(vmin) < 6:
# Python <2.6, emulate str.format()
import __builtin__
def lang_with_format(string):
return CompatStr(string)
__builtin__._ = lang_with_format
else:
# Python >=2.6, use normal gettext behavior
lang.install()
parser = argparse.ArgumentParser()
parser.add_argument("files", metavar="file", nargs='*', help="File to open")
parser.add_argument("--module", metavar="module",
help="Load module on startup")
logger.add_version_argument(parser)
parser.add_argument("--profile", action="store_true",
help="Enable profiling")
logger.add_arguments(parser)
args = parser.parse_args()
logger.handle_options(args)
a = None
if True:
from chirp.ui import mainapp
a = mainapp.ChirpMain()
# Be sure to load module before opening files
if args.module:
a.load_module(args.module)
for i in args.files:
LOG.info("Opening %s", i)
a.do_open(i)
a.show()
if args.profile:
import cProfile
import pstats
import gtk
cProfile.run("gtk.main()", "chirpw.stats")
p = pstats.Stats("chirpw.stats")
p.sort_stats("cumulative").print_stats(10)
else:
import gtk
gtk.main()
if config._CONFIG:
config._CONFIG.set("last_dir",
platform.get_platform().get_last_dir(),
"state")
config._CONFIG.save()
if __name__ == '__main__':
main()

View file

@ -1,51 +0,0 @@
# Copyright 2013 Sean Burford <sburford@google.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
import logging
from chirp import bandplan, bandplan_na, bandplan_au
from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3
from chirp.ui import inputdialog
LOG = logging.getLogger(__name__)
def select_bandplan(bandplans, parent_window):
plans = ["None"]
for shortname, details in bandplans.plans.iteritems():
if bandplans._config.get_bool(shortname, "bandplan"):
plans.insert(0, details[0])
else:
plans.append(details[0])
d = inputdialog.ChoiceDialog(plans, parent=parent_window,
title="Choose Defaults")
d.label.set_text(_("Band plans define default channel settings for "
"frequencies in a region. Choose a band plan "
"or None for completely manual channel "
"settings."))
d.label.set_line_wrap(True)
r = d.run()
if r == gtk.RESPONSE_OK:
selection = d.choice.get_active_text()
for shortname, details in bandplans.plans.iteritems():
bandplans._config.set_bool(shortname, selection == details[0],
"bandplan")
if selection == details[0]:
LOG.info("Selected band plan %s: %s" %
(shortname, selection))
d.destroy()

View file

@ -1,419 +0,0 @@
# Copyright 2008 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
import gobject
import time
import logging
from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN
from chirp import chirp_common
from chirp.ui import common, miscwidgets
LOG = logging.getLogger(__name__)
class MappingNamesJob(common.RadioJob):
def __init__(self, model, editor, cb):
common.RadioJob.__init__(self, cb, None)
self.__model = model
self.__editor = editor
def execute(self, radio):
self.__editor.mappings = []
mappings = self.__model.get_mappings()
for mapping in mappings:
self.__editor.mappings.append((mapping, mapping.get_name()))
gobject.idle_add(self.cb, *self.cb_args)
class MappingNameEditor(common.Editor):
def refresh(self):
def got_mappings():
self._keys = []
for mapping, name in self.mappings:
self._keys.append(mapping.get_index())
self.listw.set_item(mapping.get_index(),
mapping.get_index(),
name)
self.listw.connect("item-set", self.mapping_changed)
job = MappingNamesJob(self._model, self, got_mappings)
job.set_desc(_("Retrieving %s information") % self._type)
self.rthread.submit(job)
def get_mapping_list(self):
mappings = []
keys = self.listw.get_keys()
for key in keys:
mappings.append(self.listw.get_item(key)[2])
return mappings
def mapping_changed(self, listw, key):
def cb(*args):
self.emit("changed")
name = self.listw.get_item(key)[2]
mapping, oldname = self.mappings[self._keys.index(key)]
def trigger_changed(*args):
self.emit("changed")
job = common.RadioJob(trigger_changed, "set_name", name)
job.set_target(mapping)
job.set_desc(_("Setting name on %s") % self._type.lower())
self.rthread.submit(job)
return True
def __init__(self, rthread, model):
super(MappingNameEditor, self).__init__(rthread)
self._model = model
self._type = common.unpluralize(model.get_name())
types = [(gobject.TYPE_STRING, "key"),
(gobject.TYPE_STRING, self._type),
(gobject.TYPE_STRING, _("Name"))]
self.listw = miscwidgets.KeyedListWidget(types)
self.listw.set_editable(1, True)
self.listw.set_sort_column(0, 1)
self.listw.set_sort_column(1, -1)
self.listw.show()
self.mappings = []
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add_with_viewport(self.listw)
self.root = sw
self._loaded = False
def focus(self):
if self._loaded:
return
self.refresh()
self._loaded = True
def other_editor_changed(self, target_editor):
self._loaded = False
if self.is_focused():
self.refresh_all_memories()
def mappings_changed(self):
pass
class MemoryMappingsJob(common.RadioJob):
def __init__(self, model, cb, number):
common.RadioJob.__init__(self, cb, None)
self.__model = model
self.__number = number
def execute(self, radio):
mem = radio.get_memory(self.__number)
if mem.empty:
mappings = []
indexes = []
else:
mappings = self.__model.get_memory_mappings(mem)
indexes = []
if isinstance(self.__model,
chirp_common.MappingModelIndexInterface):
for mapping in mappings:
indexes.append(self.__model.get_memory_index(mem, mapping))
self.cb(mem, mappings, indexes, *self.cb_args)
class MappingMembershipEditor(common.Editor):
def _number_to_path(self, number):
return (number - self._rf.memory_bounds[0],)
def _get_next_mapping_index(self, mapping):
# NB: Only works for one-to-one models right now!
iter = self._store.get_iter_first()
indexes = []
ncols = len(self._cols) + len(self.mappings)
while iter:
vals = self._store.get(iter, *tuple([n for n in range(0, ncols)]))
loc = vals[self.C_LOC]
index = vals[self.C_INDEX]
mappings = vals[self.C_MAPPINGS:]
if True in mappings and mappings.index(True) == mapping:
indexes.append(index)
iter = self._store.iter_next(iter)
index_bounds = self._model.get_index_bounds()
num_indexes = index_bounds[1] - index_bounds[0]
indexes.sort()
for i in range(0, num_indexes):
if i not in indexes:
return i + index_bounds[0] # In case not zero-origin index
return 0 # If the mapping is full, just wrap around!
def _toggled_cb(self, rend, path, colnum):
try:
if not rend.get_sensitive():
return
except AttributeError:
# support PyGTK < 2.22
iter = self._store.get_iter(path)
if not self._store.get(iter, self.C_FILLED)[0]:
return
# The mapping index is the column number, minus the 3 label columns
mapping, name = self.mappings[colnum - len(self._cols)]
loc, = self._store.get(self._store.get_iter(path), self.C_LOC)
is_indexed = isinstance(self._model,
chirp_common.MappingModelIndexInterface)
if rend.get_active():
# Changing from True to False
fn = "remove_memory_from_mapping"
index = None
else:
# Changing from False to True
fn = "add_memory_to_mapping"
if is_indexed:
index = self._get_next_mapping_index(colnum - len(self._cols))
else:
index = None
def do_refresh_memory(*args):
# Step 2: Update our notion of the memory's mapping information
self.refresh_memory(loc)
def do_mapping_index(result, memory):
if isinstance(result, Exception):
common.show_error("Failed to add {mem} to mapping: {err}"
.format(mem=memory.number,
err=str(result)),
parent=self.editorset.parent_window)
return
self.emit("changed")
# Step 3: Set the memory's mapping index (maybe)
if not is_indexed or index is None:
return do_refresh_memory()
job = common.RadioJob(do_refresh_memory,
"set_memory_index", memory, mapping, index)
job.set_target(self._model)
job.set_desc(_("Updating {type} index "
"for memory {num}").format(type=self._type,
num=memory.number))
self.rthread.submit(job)
def do_mapping_adjustment(memory):
# Step 1: Do the mapping add/remove
job = common.RadioJob(do_mapping_index, fn, memory, mapping)
job.set_target(self._model)
job.set_cb_args(memory)
job.set_desc(_("Updating mapping information "
"for memory {num}").format(num=memory.number))
self.rthread.submit(job)
# Step 0: Fetch the memory
job = common.RadioJob(do_mapping_adjustment, "get_memory", loc)
job.set_desc(_("Getting memory {num}").format(num=loc))
self.rthread.submit(job)
def _index_edited_cb(self, rend, path, new):
loc, = self._store.get(self._store.get_iter(path), self.C_LOC)
def refresh_memory(*args):
self.refresh_memory(loc)
def set_index(mappings, memory):
self.emit("changed")
# Step 2: Set the index
job = common.RadioJob(refresh_memory, "set_memory_index",
memory, mappings[0], int(new))
job.set_target(self._model)
job.set_desc(_("Setting index "
"for memory {num}").format(num=memory.number))
self.rthread.submit(job)
def get_mapping(memory):
# Step 1: Get the first/only mapping
job = common.RadioJob(set_index, "get_memory_mappings", memory)
job.set_cb_args(memory)
job.set_target(self._model)
job.set_desc(_("Getting {type} for "
"memory {num}").format(type=self._type,
num=memory.number))
self.rthread.submit(job)
# Step 0: Get the memory
job = common.RadioJob(get_mapping, "get_memory", loc)
job.set_desc(_("Getting memory {num}").format(num=loc))
self.rthread.submit(job)
def __init__(self, rthread, editorset, model):
super(MappingMembershipEditor, self).__init__(rthread)
self.editorset = editorset
self._rf = rthread.radio.get_features()
self._model = model
self._type = common.unpluralize(model.get_name())
self._view_cols = [
(_("Loc"), TYPE_INT, gtk.CellRendererText, ),
(_("Frequency"), TYPE_STRING, gtk.CellRendererText, ),
(_("Name"), TYPE_STRING, gtk.CellRendererText, ),
(_("Index"), TYPE_INT, gtk.CellRendererText, ),
]
self._cols = [
("_filled", TYPE_BOOLEAN, None, ),
] + self._view_cols
self.C_FILLED = 0
self.C_LOC = 1
self.C_FREQ = 2
self.C_NAME = 3
self.C_INDEX = 4
self.C_MAPPINGS = 5 # and beyond
cols = list(self._cols)
self._index_cache = []
for i in range(0, self._model.get_num_mappings()):
label = "%s %i" % (self._type, (i+1))
cols.append((label, TYPE_BOOLEAN, gtk.CellRendererToggle))
self._store = gtk.ListStore(*tuple([y for x, y, z in cols]))
self._view = gtk.TreeView(self._store)
is_indexed = isinstance(self._model,
chirp_common.MappingModelIndexInterface)
colnum = 0
for label, dtype, rtype in cols:
if not rtype:
colnum += 1
continue
rend = rtype()
if dtype == TYPE_BOOLEAN:
rend.set_property("activatable", True)
rend.connect("toggled", self._toggled_cb, colnum)
col = gtk.TreeViewColumn(label, rend, active=colnum,
sensitive=self.C_FILLED)
else:
col = gtk.TreeViewColumn(label, rend, text=colnum,
sensitive=self.C_FILLED)
self._view.append_column(col)
col.set_resizable(True)
if colnum == self.C_NAME:
col.set_visible(self._rf.has_name)
elif colnum == self.C_INDEX:
rend.set_property("editable", True)
rend.connect("edited", self._index_edited_cb)
col.set_visible(is_indexed)
colnum += 1
# A non-rendered column to absorb extra space in the row
self._view.append_column(gtk.TreeViewColumn())
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(self._view)
self._view.show()
(min, max) = self._rf.memory_bounds
for i in range(min, max+1):
iter = self._store.append()
self._store.set(iter,
self.C_FILLED, False,
self.C_LOC, i,
self.C_FREQ, "",
self.C_NAME, "",
self.C_INDEX, 0)
self.root = sw
self._loaded = False
def refresh_memory(self, number):
def got_mem(memory, mappings, indexes):
iter = self._store.get_iter(self._number_to_path(memory.number))
row = [self.C_FILLED, not memory.empty,
self.C_LOC, memory.number,
self.C_FREQ, chirp_common.format_freq(memory.freq),
self.C_NAME, memory.name,
# Hack for only one index right now
self.C_INDEX, indexes and indexes[0] or 0,
]
for i in range(0, len(self.mappings)):
row.append(i + len(self._cols))
row.append(self.mappings[i][0] in mappings)
self._store.set(iter, *tuple(row))
job = MemoryMappingsJob(self._model, got_mem, number)
job.set_desc(_("Getting {type} information "
"for memory {num}").format(type=self._type, num=number))
self.rthread.submit(job)
def refresh_all_memories(self):
start = time.time()
(min, max) = self._rf.memory_bounds
for i in range(min, max+1):
self.refresh_memory(i)
LOG.debug("Got all %s info in %s" %
(self._type, (time.time() - start)))
def refresh_mappings(self, and_memories=False):
def got_mappings():
for i in range(len(self._cols) - len(self._view_cols) - 1,
len(self.mappings)):
col = self._view.get_column(i + len(self._view_cols))
mapping, name = self.mappings[i]
if name:
col.set_title(name)
else:
col.set_title("(%s)" % i)
if and_memories:
self.refresh_all_memories()
job = MappingNamesJob(self._model, self, got_mappings)
job.set_desc(_("Getting %s information") % self._type)
self.rthread.submit(job)
def focus(self):
common.Editor.focus(self)
if self._loaded:
return
self.refresh_mappings(True)
self._loaded = True
def other_editor_changed(self, target_editor):
self._loaded = False
if self.is_focused():
self.refresh_all_memories()
def mappings_changed(self):
self.refresh_mappings()

View file

@ -1,277 +0,0 @@
# Copyright 2008 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 collections
import threading
import logging
import os
import gtk
import gobject
from chirp import platform, directory, detect, chirp_common
from chirp.ui import miscwidgets, cloneprog, inputdialog, common, config
LOG = logging.getLogger(__name__)
AUTO_DETECT_STRING = "Auto Detect (Icom Only)"
class CloneSettings:
def __init__(self):
self.port = None
self.radio_class = None
def __str__(self):
s = ""
if self.radio_class:
return _("{vendor} {model} on {port}").format(
vendor=self.radio_class.VENDOR,
model=self.radio_class.MODEL,
port=self.port)
class CloneSettingsDialog(gtk.Dialog):
def __make_field(self, label, widget):
l = gtk.Label(label + ":")
l.set_alignment(1.0, 0.5)
self.__table.attach(l, 0, 1, self.__row, self.__row+1)
self.__table.attach(widget, 1, 2, self.__row, self.__row+1)
self.__row += 1
l.show()
widget.show()
def __make_port(self, port):
conf = config.get("state")
ports = platform.get_platform().list_serial_ports()
if not port:
if conf.get("last_port"):
port = conf.get("last_port")
elif ports:
port = ports[0]
else:
port = ""
if port not in ports:
ports.insert(0, port)
return miscwidgets.make_choice(ports, True, port)
def __make_model(self):
return miscwidgets.make_choice([], False)
def __make_vendor(self, modelbox):
vendors = collections.defaultdict(list)
for name, rclass in sorted(directory.DRV_TO_RADIO.items()):
if not issubclass(rclass, chirp_common.CloneModeRadio) and \
not issubclass(rclass, chirp_common.LiveRadio):
continue
vendors[rclass.VENDOR].append(rclass)
for alias in rclass.ALIASES:
vendors[alias.VENDOR].append(alias)
self.__vendors = vendors
conf = config.get("state")
if not conf.get("last_vendor"):
conf.set("last_vendor", sorted(vendors.keys())[0])
last_vendor = conf.get("last_vendor")
if last_vendor not in list(vendors.keys()):
last_vendor = list(vendors.keys())[0]
v = miscwidgets.make_choice(sorted(vendors.keys()), False, last_vendor)
def _changed(box, vendors, boxes):
(vendorbox, modelbox) = boxes
models = vendors[vendorbox.value]
added_models = []
model_store = modelbox.get_model()
model_store.clear()
for rclass in sorted(models, key=lambda c: c.MODEL):
if rclass.MODEL not in added_models:
model_store.append([rclass.MODEL])
added_models.append(rclass.MODEL)
if vendorbox.value in detect.DETECT_FUNCTIONS:
model_store.append([_("Detect")])
added_models.insert(0, _("Detect"))
model_names = [x.MODEL for x in models]
if conf.get("last_model") in model_names:
modelbox.value = conf.get("last_model")
elif added_models:
modelbox.value = added_models[0]
v.widget.connect("changed", _changed, vendors, (v, modelbox))
_changed(v, vendors, (v, modelbox))
return v
def __make_ui(self, settings):
self.__table = gtk.Table(3, 2)
self.__table.set_row_spacings(3)
self.__table.set_col_spacings(10)
self.__row = 0
self.__port = self.__make_port(settings and settings.port or None)
self.__modl = self.__make_model()
self.__vend = self.__make_vendor(self.__modl)
self.__make_field(_("Port"), self.__port.widget)
self.__make_field(_("Vendor"), self.__vend.widget)
self.__make_field(_("Model"), self.__modl.widget)
if settings and settings.radio_class:
self.__vend.value = settings.radio_class.VENDOR
self.__modl.value = settings.radio_class.MODEL
self.__vend.widget.set_sensitive(False)
self.__modl.widget.set_sensitive(False)
self.__table.show()
self.vbox.pack_start(self.__table, 1, 1, 1)
def __init__(self, settings=None, parent=None, title=_("Radio")):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self, title,
parent=parent,
buttons=buttons,
flags=gtk.DIALOG_MODAL)
self.set_default_response(gtk.RESPONSE_OK)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.__make_ui(settings)
def run(self):
r = gtk.Dialog.run(self)
if r != gtk.RESPONSE_OK:
return None
vendor = self.__vend.value
model = self.__modl.value
cs = CloneSettings()
cs.port = self.__port.value
if model == _("Detect"):
try:
cs.radio_class = detect.DETECT_FUNCTIONS[vendor](cs.port)
if not cs.radio_class:
raise Exception(
_("Unable to detect radio on {port}").format(
port=cs.port))
except Exception as e:
d = inputdialog.ExceptionDialog(e)
d.run()
d.destroy()
return None
else:
for rclass in list(directory.DRV_TO_RADIO.values()):
if rclass.MODEL == model:
cs.radio_class = rclass
break
alias_match = None
for alias in rclass.ALIASES:
if alias.MODEL == model:
alias_match = rclass
alias_class = alias
break
if alias_match:
class DynamicRadioAlias(rclass):
VENDOR = alias.VENDOR
MODEL = alias.MODEL
VARIANT = alias.VARIANT
cs.radio_class = DynamicRadioAlias
LOG.debug(
'Chose %s alias for %s because model %s selected' % (
alias_match, cs.radio_class, model))
break
if not cs.radio_class:
common.show_error(
_("Internal error: Unable to upload to {model}").format(
model=model))
LOG.info(self.__vendors)
return None
conf = config.get("state")
conf.set("last_port", cs.port)
conf.set("last_vendor", cs.radio_class.VENDOR)
conf.set("last_model", model)
return cs
class CloneCancelledException(Exception):
pass
class CloneThread(threading.Thread):
def __status(self, status):
gobject.idle_add(self.__progw.status, status)
def __init__(self, radio, direction, cb=None, parent=None):
threading.Thread.__init__(self)
self.__radio = radio
self.__out = direction == "out"
self.__cback = cb
self.__cancelled = False
self.__progw = cloneprog.CloneProg(parent=parent, cancel=self.cancel)
def cancel(self):
self.__radio.pipe.close()
self.__cancelled = True
def run(self):
LOG.debug("Clone thread started")
gobject.idle_add(self.__progw.show)
self.__radio.status_fn = self.__status
try:
if self.__out:
self.__radio.sync_out()
else:
self.__radio.sync_in()
emsg = None
except Exception as e:
common.log_exception()
LOG.error("Clone failed: {error}".format(error=e))
emsg = e
gobject.idle_add(self.__progw.hide)
# NB: Compulsory close of the radio's serial connection
self.__radio.pipe.close()
LOG.debug("Clone thread ended")
if self.__cback and not self.__cancelled:
gobject.idle_add(self.__cback, self.__radio, emsg)
if __name__ == "__main__":
d = CloneSettingsDialog("/dev/ttyUSB0")
r = d.run()
print(r)

View file

@ -1,66 +0,0 @@
# Copyright 2008 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
class CloneProg(gtk.Window):
def __init__(self, **args):
if "parent" in args:
parent = args["parent"]
del args["parent"]
else:
parent = None
if "cancel" in args:
cancel = args["cancel"]
del args["cancel"]
else:
cancel = None
gtk.Window.__init__(self, **args)
self.set_transient_for(parent)
self.set_modal(True)
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
vbox = gtk.VBox(False, 2)
vbox.show()
self.add(vbox)
self.set_title(_("Clone Progress"))
self.set_resizable(False)
self.infolabel = gtk.Label(_("Cloning"))
self.infolabel.show()
vbox.pack_start(self.infolabel, 1, 1, 1)
self.progbar = gtk.ProgressBar()
self.progbar.set_fraction(0.0)
self.progbar.show()
vbox.pack_start(self.progbar, 0, 0, 0)
cancel_b = gtk.Button(_("Cancel"))
cancel_b.connect("clicked", lambda b: cancel())
cancel_b.show()
vbox.pack_start(cancel_b, 0, 0, 0)
def status(self, _status):
self.infolabel.set_text(_status.msg)
if _status.cur > _status.max:
_status.cur = _status.max
self.progbar.set_fraction(_status.cur / float(_status.max))

View file

@ -1,451 +0,0 @@
# Copyright 2008 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
import gobject
import pango
import threading
import time
import os
import traceback
import logging
from chirp import errors
from chirp.ui import reporting, config
LOG = logging.getLogger(__name__)
CONF = config.get()
class Editor(gobject.GObject):
__gsignals__ = {
'changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
'usermsg': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
}
root = None
def __init__(self, rthread):
gobject.GObject.__init__(self)
self.read_only = False
self._focused = False
self.rthread = rthread
def is_focused(self):
return self._focused
def focus(self):
self._focused = True
def unfocus(self):
self._focused = False
def copy_selection(self, cut=False):
pass
def paste_selection(self):
pass
def hotkey(self, action):
pass
def set_read_only(self, read_only):
self.read_only = read_only
def get_read_only(self):
return self.read_only
def prepare_close(self):
pass
def other_editor_changed(self, editor):
pass
gobject.type_register(Editor)
def DBG(*args):
if False:
LOG.debug(" ".join(args))
class RadioJob:
def __init__(self, cb, func, *args, **kwargs):
self.cb = cb
self.cb_args = ()
self.func = func
self.args = args
self.kwargs = kwargs
self.desc = "Working"
self.target = None
self.tb = traceback.format_stack()
def __str__(self):
return "RadioJob(%s,%s,%s)" % (self.func, self.args, self.kwargs)
def set_desc(self, desc):
self.desc = desc
def set_cb_args(self, *args):
self.cb_args = args
def set_target(self, target):
self.target = target
def _execute(self, target, func):
try:
DBG("Running %s (%s %s)" % (self.func,
str(self.args),
str(self.kwargs)))
DBG(self.desc)
result = func(*self.args, **self.kwargs)
except errors.InvalidMemoryLocation as e:
result = e
except Exception as e:
LOG.error("Exception running RadioJob: %s" % e)
log_exception()
LOG.error("Job Args: %s" % str(self.args))
LOG.error("Job KWArgs: %s" % str(self.kwargs))
LOG.error("Job Called from:%s%s" %
(os.linesep, "".join(self.tb[:-1])))
result = e
if self.cb:
gobject.idle_add(self.cb, result, *self.cb_args)
def execute(self, radio):
if not self.target:
self.target = radio
try:
func = getattr(self.target, self.func)
except AttributeError as e:
LOG.error("No such radio function `%s' in %s" %
(self.func, self.target))
return
self._execute(self.target, func)
class RadioThread(threading.Thread, gobject.GObject):
__gsignals__ = {
"status": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
}
def __init__(self, radio, parent=None):
threading.Thread.__init__(self)
gobject.GObject.__init__(self)
self.__queue = {}
if parent:
self.__runlock = parent._get_run_lock()
self.status = lambda msg: parent.status(msg)
else:
self.__runlock = threading.Lock()
self.status = self._status
self.__counter = threading.Semaphore(0)
self.__lock = threading.Lock()
self.__enabled = True
self.radio = radio
def _get_run_lock(self):
return self.__runlock
def _qlock(self):
self.__lock.acquire()
def _qunlock(self):
self.__lock.release()
def _qsubmit(self, job, priority):
if priority not in self.__queue:
self.__queue[priority] = []
self.__queue[priority].append(job)
self.__counter.release()
def _queue_clear_below(self, priority):
for i in range(0, priority):
if i in self.__queue and len(self.__queue[i]) != 0:
return False
return True
def _qlock_when_idle(self, priority=10):
while True:
DBG("Attempting queue lock (%i)" % len(self.__queue))
self._qlock()
if self._queue_clear_below(priority):
return
self._qunlock()
time.sleep(0.1)
# This is the external lock, which stops any threads from running
# so that the radio can be operated synchronously
def lock(self):
self.__runlock.acquire()
def unlock(self):
self.__runlock.release()
def submit(self, job, priority=0):
self._qlock()
self._qsubmit(job, priority)
self._qunlock()
def flush(self, priority=None):
self._qlock()
if priority is None:
for i in self.__queue.keys():
self.__queue[i] = []
else:
self.__queue[priority] = []
self._qunlock()
def stop(self):
self.flush()
self.__counter.release()
self.__enabled = False
def _status(self, msg):
jobs = 0
for i in dict(self.__queue):
jobs += len(self.__queue[i])
gobject.idle_add(self.emit, "status", "[%i] %s" % (jobs, msg))
def _queue_pop(self, priority):
try:
return self.__queue[priority].pop(0)
except IndexError:
return None
def run(self):
last_job_desc = "idle"
while self.__enabled:
DBG("Waiting for a job")
if last_job_desc:
self.status(_("Completed") + " " + last_job_desc +
" (" + _("idle") + ")")
self.__counter.acquire()
self._qlock()
for i in sorted(self.__queue.keys()):
job = self._queue_pop(i)
if job:
DBG("Running job at priority %i" % i)
break
self._qunlock()
if job:
self.lock()
self.status(job.desc)
job.execute(self.radio)
last_job_desc = job.desc
self.unlock()
LOG.debug("RadioThread exiting")
def log_exception():
import traceback
import sys
reporting.report_exception(traceback.format_exc(limit=30))
LOG.error("-- Exception: --")
LOG.error(traceback.format_exc(limit=30))
LOG.error("----------------")
def show_error(msg, parent=None):
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent,
type=gtk.MESSAGE_ERROR)
d.set_property("text", msg)
if not parent:
d.set_position(gtk.WIN_POS_CENTER_ALWAYS)
d.run()
d.destroy()
def ask_yesno_question(msg, parent=None):
d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=parent,
type=gtk.MESSAGE_QUESTION)
d.set_property("text", msg)
if not parent:
d.set_position(gtk.WIN_POS_CENTER_ALWAYS)
r = d.run()
d.destroy()
return r == gtk.RESPONSE_YES
def combo_select(box, value):
store = box.get_model()
iter = store.get_iter_first()
while iter:
if store.get(iter, 0)[0] == value:
box.set_active_iter(iter)
return True
iter = store.iter_next(iter)
return False
def _add_text(d, text):
v = gtk.TextView()
v.get_buffer().set_text(text)
v.set_editable(False)
v.set_cursor_visible(False)
v.show()
sw = gtk.ScrolledWindow()
sw.add(v)
sw.show()
d.vbox.pack_start(sw, 1, 1, 1)
return v
def show_error_text(msg, text, parent=None):
d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent,
type=gtk.MESSAGE_ERROR)
d.set_property("text", msg)
_add_text(d, text)
if not parent:
d.set_position(gtk.WIN_POS_CENTER_ALWAYS)
d.set_size_request(600, 400)
d.run()
d.destroy()
def show_warning(msg, text,
parent=None, buttons=None, title="Warning",
can_squelch=False):
if buttons is None:
buttons = gtk.BUTTONS_OK
d = gtk.MessageDialog(buttons=buttons,
parent=parent,
type=gtk.MESSAGE_WARNING)
d.set_title(title)
d.set_property("text", msg)
l = gtk.Label(_("Details") + ":")
l.show()
d.vbox.pack_start(l, 0, 0, 0)
l = gtk.Label(_("Proceed?"))
l.show()
d.get_action_area().pack_start(l, 0, 0, 0)
d.get_action_area().reorder_child(l, 0)
textview = _add_text(d, text)
textview.set_wrap_mode(gtk.WRAP_WORD)
if not parent:
d.set_position(gtk.WIN_POS_CENTER_ALWAYS)
if can_squelch:
cb = gtk.CheckButton(_("Do not show this next time"))
cb.show()
d.vbox.pack_start(cb, 0, 0, 0)
d.set_size_request(600, 400)
r = d.run()
d.destroy()
if can_squelch:
return r, cb.get_active()
return r
def simple_diff(a, b, diffsonly=False):
lines_a = a.split(os.linesep)
lines_b = b.split(os.linesep)
blankprinted = True
diff = ""
for i in range(0, len(lines_a)):
if lines_a[i] != lines_b[i]:
diff += "-%s%s" % (lines_a[i], os.linesep)
diff += "+%s%s" % (lines_b[i], os.linesep)
blankprinted = False
elif diffsonly is True:
if blankprinted:
continue
diff += os.linesep
blankprinted = True
else:
diff += " %s%s" % (lines_a[i], os.linesep)
return diff
# A quick hacked up tool to show a blob of text in a dialog window
# using fixed-width fonts. It also highlights lines that start with
# a '-' in red bold font and '+' with blue bold font.
def show_diff_blob(title, result):
d = gtk.Dialog(title=title,
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
b = gtk.TextBuffer()
tags = b.get_tag_table()
for color in ["red", "blue", "green", "grey"]:
tag = gtk.TextTag(color)
tag.set_property("foreground", color)
tags.add(tag)
tag = gtk.TextTag("bold")
tag.set_property("weight", pango.WEIGHT_BOLD)
tags.add(tag)
try:
fontsize = CONF.get_int("diff_fontsize", "developer")
except Exception:
fontsize = 11
if fontsize < 4 or fontsize > 144:
LOG.info("Unsupported diff_fontsize %i. Using 11." % fontsize)
fontsize = 11
lines = result.split(os.linesep)
for line in lines:
if line.startswith("-"):
tags = ("red", "bold")
elif line.startswith("+"):
tags = ("blue", "bold")
else:
tags = ()
b.insert_with_tags_by_name(b.get_end_iter(), line + os.linesep, *tags)
v = gtk.TextView(b)
fontdesc = pango.FontDescription("Courier %i" % fontsize)
v.modify_font(fontdesc)
v.set_editable(False)
v.show()
s = gtk.ScrolledWindow()
s.add(v)
s.show()
d.vbox.pack_start(s, 1, 1, 1)
d.set_size_request(600, 400)
d.run()
d.destroy()
def unpluralize(string):
if string.endswith("s"):
return string[:-1]
else:
return string

View file

@ -1,82 +0,0 @@
import contextlib
import logging
import serial as base_serial
import six
try:
import gtk
except ImportError:
# FIXME
# For chirpc
gtk = None
from chirp import bitwise
LOG = logging.getLogger('uicompat')
@contextlib.contextmanager
def py3safe(quiet=False):
try:
yield
except Exception as e:
if not quiet:
LOG.exception('FIXMEPY3: %s' % e)
def SpinButton(adj):
try:
return gtk.SpinButton(adj)
except TypeError:
sb = gtk.SpinButton()
sb.configure(adj, 1.0, 0)
return sb
def Frame(label):
try:
return gtk.Frame(label)
except TypeError:
f = gtk.Frame()
f.set_label(label)
return f
class CompatSerial(base_serial.Serial):
"""A PY2-compatible Serial class
This wraps serial.Serial to provide translation between
hex-char-having unicode strings in radios and the bytes-wanting
serial channel. See bitwise.string_straigth_encode() and
bitwise.string_straight_decode() for more details.
This should only be used as a bridge for older drivers until
they can be rewritten.
"""
def write(self, data):
data = bitwise.string_straight_encode(data)
return super(CompatSerial, self).write(data)
def read(self, count):
data = super(CompatSerial, self).read(count)
return bitwise.string_straight_decode(data)
@classmethod
def get(cls, needs_compat, *args, **kwargs):
if six.PY3 and needs_compat:
return cls(*args, **kwargs)
else:
return base_serial.Serial(*args, **kwargs)
class CompatTooltips(object):
def __init__(self):
try:
self.tips = gtk.Tooltips()
except AttributeError:
self.tips = None
def set_tip(self, widget, tip):
if self.tips:
self.tips.set_tip(widget, tip)
else:
widget.set_tooltip_text(tip)

View file

@ -1,202 +0,0 @@
# Copyright 2008 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
import gobject
import logging
from chirp.ui import common, miscwidgets
from chirp.ui import compat
LOG = logging.getLogger(__name__)
WIDGETW = 80
WIDGETH = 30
class CallsignEditor(gtk.HBox):
__gsignals__ = {
"changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def _cs_changed(self, listw, callid):
if callid == 0 and self.first_fixed:
return False
self.emit("changed")
return True
def make_list(self, width):
cols = [(gobject.TYPE_INT, ""),
(gobject.TYPE_INT, ""),
(gobject.TYPE_STRING, _("Callsign")),
]
self.listw = miscwidgets.KeyedListWidget(cols)
self.listw.show()
self.listw.set_editable(1, True)
self.listw.connect("item-set", self._cs_changed)
with compat.py3safe():
rend = self.listw.get_renderer(1)
rend.set_property("family", "Monospace")
rend.set_property("width-chars", width)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add_with_viewport(self.listw)
sw.show()
return sw
def __init__(self, first_fixed=False, width=8):
gtk.HBox.__init__(self, False, 2)
self.first_fixed = first_fixed
self.listw = None
self.pack_start(self.make_list(width), 1, 1, 1)
def set_callsigns(self, calls):
if self.first_fixed:
st = 1
else:
st = 0
values = []
i = 1
for call in calls[st:]:
self.listw.set_item(i, i, call)
i += 1
def get_callsigns(self):
calls = []
keys = self.listw.get_keys()
for key in keys:
id, idx, call = self.listw.get_item(key)
calls.append(call)
if self.first_fixed:
calls.insert(0, "")
return calls
class DStarEditor(common.Editor):
def __cs_changed(self, cse):
job = None
LOG.debug("Callsigns: %s" % cse.get_callsigns())
if cse == self.editor_ucall:
job = common.RadioJob(None,
"set_urcall_list",
cse.get_callsigns())
LOG.debug("Set urcall")
elif cse == self.editor_rcall:
job = common.RadioJob(None,
"set_repeater_call_list",
cse.get_callsigns())
LOG.debug("Set rcall")
elif cse == self.editor_mcall:
job = common.RadioJob(None,
"set_mycall_list",
cse.get_callsigns())
if job:
LOG.debug("Submitting job to update call lists")
self.rthread.submit(job)
self.emit("changed")
def make_callsigns(self):
box = gtk.HBox(True, 2)
fixed = self.rthread.radio.get_features().has_implicit_calls
frame = compat.Frame(_("Your callsign"))
self.editor_ucall = CallsignEditor(first_fixed=fixed)
self.editor_ucall.set_size_request(-1, 200)
self.editor_ucall.show()
frame.add(self.editor_ucall)
frame.show()
box.pack_start(frame, 1, 1, 0)
frame = compat.Frame(_("Repeater callsign"))
self.editor_rcall = CallsignEditor(first_fixed=fixed)
self.editor_rcall.set_size_request(-1, 200)
self.editor_rcall.show()
frame.add(self.editor_rcall)
frame.show()
box.pack_start(frame, 1, 1, 0)
frame = compat.Frame(_("My callsign"))
self.editor_mcall = CallsignEditor()
self.editor_mcall.set_size_request(-1, 200)
self.editor_mcall.show()
frame.add(self.editor_mcall)
frame.show()
box.pack_start(frame, 1, 1, 0)
box.show()
return box
def focus(self):
if self.loaded:
return
self.loaded = True
LOG.debug("Loading callsigns...")
def set_ucall(calls):
self.editor_ucall.set_callsigns(calls)
self.editor_ucall.connect("changed", self.__cs_changed)
def set_rcall(calls):
self.editor_rcall.set_callsigns(calls)
self.editor_rcall.connect("changed", self.__cs_changed)
def set_mcall(calls):
self.editor_mcall.set_callsigns(calls)
self.editor_mcall.connect("changed", self.__cs_changed)
job = common.RadioJob(set_ucall, "get_urcall_list")
job.set_desc(_("Downloading URCALL list"))
self.rthread.submit(job)
job = common.RadioJob(set_rcall, "get_repeater_call_list")
job.set_desc(_("Downloading RPTCALL list"))
self.rthread.submit(job)
job = common.RadioJob(set_mcall, "get_mycall_list")
job.set_desc(_("Downloading MYCALL list"))
self.rthread.submit(job)
def __init__(self, rthread):
super(DStarEditor, self).__init__(rthread)
self.loaded = False
self.editor_ucall = self.editor_rcall = None
vbox = gtk.VBox(False, 2)
vbox.pack_start(self.make_callsigns(), 0, 0, 0)
tmp = gtk.Label("")
tmp.show()
vbox.pack_start(tmp, 1, 1, 1)
self.root = vbox

View file

@ -1,419 +0,0 @@
# Copyright 2008 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 os
import gtk
import gobject
import logging
from chirp import chirp_common, directory
from chirp.drivers import generic_csv
from chirp.ui import memedit, dstaredit, bankedit, common, importdialog
from chirp.ui import inputdialog, reporting, settingsedit, radiobrowser, config
LOG = logging.getLogger(__name__)
class EditorSet(gtk.VBox):
__gsignals__ = {
"want-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
"status": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
"usermsg": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
"editor-selected": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
}
def _make_device_mapping_editors(self, device, devrthread, index):
sub_index = 0
memory_editor = self.editors["memedit%i" % index]
mappings = device.get_mapping_models()
for mapping_model in mappings:
members = bankedit.MappingMembershipEditor(devrthread, self,
mapping_model)
label = mapping_model.get_name()
if self.rf.has_sub_devices:
label += "(%s)" % device.VARIANT
lab = gtk.Label(label)
self.tabs.append_page(members.root, lab)
self.editors["mapping_members%i%i" % (index, sub_index)] = members
basename = common.unpluralize(mapping_model.get_name())
names = bankedit.MappingNameEditor(devrthread, mapping_model)
label = "%s Names" % basename
if self.rf.has_sub_devices:
label += " (%s)" % device.VARIANT
lab = gtk.Label(label)
self.tabs.append_page(names.root, lab)
self.editors["mapping_names%i%i" % (index, sub_index)] = names
members.root.show()
members.connect("changed", self.editor_changed)
if hasattr(mapping_model.get_mappings()[0], "set_name"):
names.root.show()
members.connect("changed", lambda x: names.mappings_changed())
names.connect("changed", lambda x: members.mappings_changed())
names.connect("changed", self.editor_changed)
sub_index += 1
def _make_device_editors(self, device, devrthread, index):
if isinstance(device, chirp_common.IcomDstarSupport):
memories = memedit.DstarMemoryEditor(devrthread)
else:
memories = memedit.MemoryEditor(devrthread)
memories.connect("usermsg", lambda e, m: self.emit("usermsg", m))
memories.connect("changed", self.editor_changed)
if self.rf.has_sub_devices:
label = (_("Memories (%(variant)s)") %
dict(variant=device.VARIANT))
rf = device.get_features()
else:
label = _("Memories")
rf = self.rf
lab = gtk.Label(label)
self.tabs.append_page(memories.root, lab)
memories.root.show()
self.editors["memedit%i" % index] = memories
self._make_device_mapping_editors(device, devrthread, index)
if isinstance(device, chirp_common.IcomDstarSupport):
editor = dstaredit.DStarEditor(devrthread)
self.tabs.append_page(editor.root, gtk.Label(_("D-STAR")))
editor.root.show()
editor.connect("changed", self.dstar_changed, memories)
editor.connect("changed", self.editor_changed)
self.editors["dstar"] = editor
def __init__(self, source, parent_window=None,
filename=None, tempname=None):
gtk.VBox.__init__(self, True, 0)
self.parent_window = parent_window
if isinstance(source, str):
self.filename = source
self.radio = directory.get_radio_by_image(self.filename)
elif isinstance(source, chirp_common.Radio):
self.radio = source
self.filename = filename or tempname or source.VARIANT
else:
raise Exception("Unknown source type")
rthread = common.RadioThread(self.radio)
rthread.setDaemon(True)
rthread.start()
rthread.connect("status", lambda e, m: self.emit("status", m))
self.tabs = gtk.Notebook()
self.tabs.connect("switch-page", self.tab_selected)
self.tabs.set_tab_pos(gtk.POS_LEFT)
self.editors = {}
self.rf = self.radio.get_features()
if self.rf.has_sub_devices:
devices = self.radio.get_sub_devices()
else:
devices = [self.radio]
index = 0
for device in devices:
devrthread = common.RadioThread(device, rthread)
devrthread.setDaemon(True)
devrthread.start()
self._make_device_editors(device, devrthread, index)
index += 1
if self.rf.has_settings:
editor = settingsedit.SettingsEditor(rthread)
self.tabs.append_page(editor.root, gtk.Label(_("Settings")))
editor.root.show()
editor.connect("changed", self.editor_changed)
self.editors["settings"] = editor
conf = config.get()
if (hasattr(self.rthread.radio, '_memobj') and
conf.get_bool("developer", "state")):
editor = radiobrowser.RadioBrowser(self.rthread)
lab = gtk.Label(_("Browser"))
self.tabs.append_page(editor.root, lab)
editor.connect("changed", self.editor_changed)
self.editors["browser"] = editor
self.pack_start(self.tabs)
self.tabs.show()
self.label = self.text_label = None
self.make_label()
self.modified = (tempname is not None)
if tempname:
self.filename = tempname
self.tooltip_filename = None
self.update_tab()
def make_label(self):
self.label = gtk.HBox(False, 0)
self.text_label = gtk.Label("")
self.text_label.show()
self.label.pack_start(self.text_label, 1, 1, 1)
button = gtk.Button()
button.set_relief(gtk.RELIEF_NONE)
button.set_focus_on_click(False)
icon = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
icon.show()
button.add(icon)
button.connect("clicked", lambda x: self.emit("want-close"))
button.show()
self.label.pack_start(button, 0, 0, 0)
self.label.show()
def update_tab(self):
fn = os.path.basename(self.filename)
if self.modified:
text = "%s*" % fn
else:
text = fn
self.text_label.set_text(self.radio.get_name() + ": " + text)
if self.filename != self.tooltip_filename:
self.text_label.set_tooltip_text(self.filename)
self.tooltip_filename = self.filename
def save(self, fname=None):
if not fname:
fname = self.filename
if not os.path.exists(self.filename):
return # Probably before the first "Save as"
else:
self.filename = fname
self.rthread.lock()
try:
self.radio.save(fname)
except:
self.rthread.unlock()
raise
self.rthread.unlock()
self.modified = False
self.update_tab()
def dstar_changed(self, dstared, memedit):
memedit.set_urcall_list(dstared.editor_ucall.get_callsigns())
memedit.set_repeater_list(dstared.editor_rcall.get_callsigns())
memedit.prefill()
def editor_changed(self, target_editor=None):
LOG.debug("%s changed" % target_editor)
if not isinstance(self.radio, chirp_common.LiveRadio):
self.modified = True
self.update_tab()
for editor in list(self.editors.values()):
if editor != target_editor:
editor.other_editor_changed(target_editor)
def get_tab_label(self):
return self.label
def is_modified(self):
return self.modified
def _do_import_locked(self, dlgclass, src_radio, dst_rthread):
# An import/export action needs to be done in the absence of any
# other queued changes. So, we make sure that nothing else is
# staged for the thread and lock it up. Then we use the hidden
# interface to queue our own changes before opening it up to the
# rest of the world.
dst_rthread._qlock_when_idle(5) # Suspend job submission when idle
dialog = dlgclass(src_radio, dst_rthread.radio, self.parent_window)
r = dialog.run()
dialog.hide()
if r != gtk.RESPONSE_OK:
dst_rthread._qunlock()
return
count = dialog.do_import(dst_rthread)
LOG.debug("Imported %i" % count)
dst_rthread._qunlock()
if count > 0:
self.editor_changed()
current_editor = self.get_current_editor()
gobject.idle_add(current_editor.prefill)
return count
def choose_sub_device(self, radio):
devices = radio.get_sub_devices()
choices = [x.VARIANT for x in devices]
d = inputdialog.ChoiceDialog(choices)
text = _("The {vendor} {model} has multiple independent sub-devices")
d.label.set_text(text.format(vendor=radio.VENDOR, model=radio.MODEL) +
os.linesep + _("Choose one to import from:"))
r = d.run()
chosen = d.choice.get_active_text()
d.destroy()
if r == gtk.RESPONSE_CANCEL:
raise Exception(_("Cancelled"))
for d in devices:
if d.VARIANT == chosen:
return d
raise Exception(_("Internal Error"))
def do_import(self, filen):
current_editor = self.get_current_editor()
if not isinstance(current_editor, memedit.MemoryEditor):
# FIXME: We need a nice message to let the user know that they
# need to select the appropriate memory editor tab before doing
# and import so that we know which thread and editor to import
# into and refresh. This will do for the moment.
common.show_error("Memory editor must be selected before import")
try:
src_radio = directory.get_radio_by_image(filen)
except Exception as e:
common.show_error(e)
return
if isinstance(src_radio, chirp_common.NetworkSourceRadio):
ww = importdialog.WaitWindow("Querying...", self.parent_window)
ww.show()
def status(status):
ww.set(float(status.cur) / float(status.max))
try:
src_radio.status_fn = status
src_radio.do_fetch()
except Exception as e:
common.show_error(e)
ww.hide()
return
ww.hide()
try:
if src_radio.get_features().has_sub_devices:
src_radio = self.choose_sub_device(src_radio)
except Exception as e:
common.show_error(e)
return
if len(src_radio.errors) > 0:
_filen = os.path.basename(filen)
common.show_error_text(_("There were errors while opening {file}. "
"The affected memories will not "
"be importable!").format(file=_filen),
"\r\n".join(src_radio.errors))
try:
count = self._do_import_locked(importdialog.ImportDialog,
src_radio,
self.rthread)
reporting.report_model_usage(src_radio, "importsrc", True)
except Exception as e:
common.log_exception()
common.show_error(_("There was an error during "
"import: {error}").format(error=e))
def do_export(self, filen):
try:
if filen.lower().endswith(".csv"):
dst_radio = generic_csv.CSVRadio(filen)
else:
raise Exception(_("Unsupported file type"))
except Exception as e:
common.log_exception()
common.show_error(e)
return
dst_rthread = common.RadioThread(dst_radio)
dst_rthread.setDaemon(True)
dst_rthread.start()
try:
count = self._do_import_locked(importdialog.ExportDialog,
self.rthread.radio,
dst_rthread)
except Exception as e:
common.log_exception()
common.show_error(_("There was an error during "
"export: {error}").format(error=e),
self.parent_window)
return
if count <= 0:
return
# Wait for thread queue to complete
dst_rthread._qlock_when_idle()
try:
dst_radio.save(filename=filen)
except Exception as e:
common.log_exception()
common.show_error(_("There was an error during "
"export: {error}").format(error=e),
self)
def tab_selected(self, notebook, foo, pagenum):
widget = notebook.get_nth_page(pagenum)
for k, v in list(self.editors.items()):
if v and v.root == widget:
v.focus()
self.emit("editor-selected", k)
elif v:
v.unfocus()
def set_read_only(self, read_only=True):
for editor in list(self.editors.values()):
editor and editor.set_read_only(read_only)
def get_read_only(self):
return self.editors["memedit0"].get_read_only()
def prepare_close(self):
for editor in list(self.editors.values()):
editor and editor.prepare_close()
def get_current_editor(self):
tabs = self.tabs
for lab, e in list(self.editors.items()):
if e and tabs.page_num(e.root) == tabs.get_current_page():
return e
raise Exception("No editor selected?")
@property
def rthread(self):
"""Magic rthread property to return the rthread of the currently-
selected editor"""
e = self.get_current_editor()
return e.rthread

View file

@ -1,655 +0,0 @@
# Copyright 2008 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
import gobject
import pango
import logging
from chirp import errors, chirp_common, import_logic
from chirp.ui import common
LOG = logging.getLogger(__name__)
class WaitWindow(gtk.Window):
def __init__(self, msg, parent=None):
gtk.Window.__init__(self)
self.set_title("Please Wait")
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
if parent:
self.set_transient_for(parent)
self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
else:
self.set_position(gtk.WIN_POS_CENTER)
vbox = gtk.VBox(False, 2)
l = gtk.Label(msg)
l.show()
vbox.pack_start(l)
self.prog = gtk.ProgressBar()
self.prog.show()
vbox.pack_start(self.prog)
vbox.show()
self.add(vbox)
def grind(self):
while gtk.events_pending():
gtk.main_iteration(False)
self.prog.pulse()
def set(self, fraction):
while gtk.events_pending():
gtk.main_iteration(False)
self.prog.set_fraction(fraction)
class ImportMemoryBankJob(common.RadioJob):
def __init__(self, cb, dst_mem, src_radio, src_mem):
common.RadioJob.__init__(self, cb, None)
self.__dst_mem = dst_mem
self.__src_radio = src_radio
self.__src_mem = src_mem
def execute(self, radio):
import_logic.import_bank(radio, self.__src_radio,
self.__dst_mem, self.__src_mem)
if self.cb:
gobject.idle_add(self.cb, *self.cb_args)
class ImportDialog(gtk.Dialog):
def _check_for_dupe(self, location):
iter = self.__store.get_iter_first()
while iter:
imp, loc = self.__store.get(iter, self.col_import, self.col_nloc)
if imp and loc == location:
return True
iter = self.__store.iter_next(iter)
return False
def _toggle(self, rend, path, col):
iter = self.__store.get_iter(path)
imp, nloc = self.__store.get(iter, self.col_import, self.col_nloc)
if not imp and self._check_for_dupe(nloc):
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
d.set_property("text",
_("Location {number} is already being imported. "
"Choose another value for 'New Location' "
"before selection 'Import'").format(number=nloc))
d.run()
d.destroy()
else:
self.__store[path][col] = not imp
def _render(self, _, rend, model, iter, colnum):
newloc, imp = model.get(iter, self.col_nloc, self.col_import)
lo, hi = self.dst_radio.get_features().memory_bounds
rend.set_property("text", "%i" % newloc)
if newloc in self.used_list and imp:
rend.set_property("foreground", "goldenrod")
rend.set_property("weight", pango.WEIGHT_BOLD)
elif newloc < lo or newloc > hi:
rend.set_property("foreground", "red")
rend.set_property("weight", pango.WEIGHT_BOLD)
else:
rend.set_property("foreground", "black")
rend.set_property("weight", pango.WEIGHT_NORMAL)
def _edited(self, rend, path, new, col):
iter = self.__store.get_iter(path)
if col == self.col_nloc:
nloc, = self.__store.get(iter, self.col_nloc)
try:
val = int(new)
except ValueError:
common.show_error(_("Invalid value. Must be an integer."))
return
if val == nloc:
return
if self._check_for_dupe(val):
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
d.set_property("text",
_("Location {number} is already being "
"imported").format(number=val))
d.run()
d.destroy()
return
self.record_use_of(val)
elif col == self.col_name or col == self.col_comm:
val = str(new)
else:
return
self.__store.set(iter, col, val)
def get_import_list(self):
import_list = []
iter = self.__store.get_iter_first()
while iter:
old, new, name, comm, enb = \
self.__store.get(iter, self.col_oloc, self.col_nloc,
self.col_name, self.col_comm, self.col_import)
if enb:
import_list.append((old, new, name, comm))
iter = self.__store.iter_next(iter)
return import_list
def ensure_calls(self, dst_rthread, import_list):
rlist_changed = False
ulist_changed = False
if not isinstance(self.dst_radio, chirp_common.IcomDstarSupport):
return
ulist = self.dst_radio.get_urcall_list()
rlist = self.dst_radio.get_repeater_call_list()
for old, new in import_list:
mem = self.src_radio.get_memory(old)
if isinstance(mem, chirp_common.DVMemory):
if mem.dv_urcall not in ulist:
LOG.debug("Adding %s to ucall list" % mem.dv_urcall)
ulist.append(mem.dv_urcall)
ulist_changed = True
if mem.dv_rpt1call not in rlist:
LOG.debug("Adding %s to rcall list" % mem.dv_rpt1call)
rlist.append(mem.dv_rpt1call)
rlist_changed = True
if mem.dv_rpt2call not in rlist:
LOG.debug("Adding %s to rcall list" % mem.dv_rpt2call)
rlist.append(mem.dv_rpt2call)
rlist_changed = True
if ulist_changed:
job = common.RadioJob(None, "set_urcall_list", ulist)
job.set_desc(_("Updating URCALL list"))
dst_rthread._qsubmit(job, 0)
if rlist_changed:
job = common.RadioJob(None, "set_repeater_call_list", ulist)
job.set_desc(_("Updating RPTCALL list"))
dst_rthread._qsubmit(job, 0)
return
def _convert_power(self, dst_levels, src_levels, mem):
if not dst_levels:
mem.power = None
return
elif not mem.power:
# Source radio does not support power levels, so choose the
# first (highest) level from the destination radio.
mem.power = dst_levels[0]
return ""
# If both radios support power levels, we need to decide how to
# convert the source power level to a valid one for the destination
# radio. To do that, find the absolute level of the source value
# and calculate the different between it and all the levels of the
# destination, choosing the one that matches most closely.
deltas = [abs(mem.power - power) for power in dst_levels]
mem.power = dst_levels[deltas.index(min(deltas))]
def do_soft_conversions(self, dst_features, src_features, mem):
self._convert_power(dst_features.valid_power_levels,
src_features.valid_power_levels,
mem)
return mem
def do_import_banks(self):
try:
dst_banks = self.dst_radio.get_banks()
src_banks = self.src_radio.get_banks()
if not dst_banks or not src_banks:
raise Exception()
except Exception:
LOG.error("One or more of the radios doesn't support banks")
return
if len(dst_banks) != len(src_banks):
LOG.warn("Source and destination radios have "
"a different number of banks")
else:
self.dst_radio.set_banks(src_banks)
def do_import(self, dst_rthread):
i = 0
error_messages = {}
import_list = self.get_import_list()
src_features = self.src_radio.get_features()
for old, new, name, comm in import_list:
i += 1
LOG.debug("%sing %i -> %i" % (self.ACTION, old, new))
src = self.src_radio.get_memory(old)
try:
mem = import_logic.import_mem(self.dst_radio,
src_features,
src,
{"number": new,
"name": name,
"comment": comm})
except import_logic.ImportError as e:
LOG.error("Import error: %s", e)
error_messages[new] = str(e)
continue
job = common.RadioJob(None, "set_memory", mem)
desc = _("Setting memory {number}").format(number=mem.number)
job.set_desc(desc)
dst_rthread._qsubmit(job, 0)
job = ImportMemoryBankJob(None, mem, self.src_radio, src)
job.set_desc(_("Importing bank information"))
dst_rthread._qsubmit(job, 0)
if list(error_messages.keys()):
msg = _("Error importing memories:") + "\r\n"
for num, msgs in list(error_messages.items()):
msg += "%s: %s" % (num, ",".join(msgs))
common.show_error(msg)
return i
def make_view(self):
editable = [self.col_nloc, self.col_name, self.col_comm]
self.__store = gtk.ListStore(gobject.TYPE_BOOLEAN, # Import
gobject.TYPE_INT, # Source loc
gobject.TYPE_INT, # Destination loc
gobject.TYPE_STRING, # Name
gobject.TYPE_STRING, # Frequency
gobject.TYPE_STRING, # Comment
gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING)
self.__view = gtk.TreeView(self.__store)
self.__view.show()
tips = gtk.Tooltips()
for k in list(self.caps.keys()):
t = self.types[k]
if t == gobject.TYPE_BOOLEAN:
rend = gtk.CellRendererToggle()
rend.connect("toggled", self._toggle, k)
column = gtk.TreeViewColumn(self.caps[k], rend,
active=k,
sensitive=self.col_okay,
activatable=self.col_okay)
else:
rend = gtk.CellRendererText()
if k in editable:
rend.set_property("editable", True)
rend.connect("edited", self._edited, k)
column = gtk.TreeViewColumn(self.caps[k], rend,
text=k,
sensitive=self.col_okay)
if k == self.col_nloc:
column.set_cell_data_func(rend, self._render, k)
if k in list(self.tips.keys()):
LOG.debug("Doing %s" % k)
lab = gtk.Label(self.caps[k])
column.set_widget(lab)
tips.set_tip(lab, self.tips[k])
lab.show()
column.set_sort_column_id(k)
self.__view.append_column(column)
self.__view.set_tooltip_column(self.col_tmsg)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(self.__view)
sw.show()
return sw
def __select_all(self, button, state):
iter = self.__store.get_iter_first()
while iter:
_state, okay, = self.__store.get(iter,
self.col_import,
self.col_okay)
if state is None:
_state = not _state and okay
else:
_state = state and okay
self.__store.set(iter, self.col_import, _state)
iter = self.__store.iter_next(iter)
def __incrnew(self, button, delta):
iter = self.__store.get_iter_first()
while iter:
pos = self.__store.get(iter, self.col_nloc)[0]
pos += delta
if pos < 0:
pos = 0
self.__store.set(iter, self.col_nloc, pos)
iter = self.__store.iter_next(iter)
def __autonew(self, button):
pos = self.dst_radio.get_features().memory_bounds[0]
iter = self.__store.get_iter_first()
while iter:
selected, okay = self.__store.get(iter,
self.col_import, self.col_okay)
if selected and okay:
self.__store.set(iter, self.col_nloc, pos)
pos += 1
iter = self.__store.iter_next(iter)
def __revrnew(self, button):
positions = []
iter = self.__store.get_iter_first()
while iter:
positions.append(self.__store.get(iter, self.col_nloc)[0])
iter = self.__store.iter_next(iter)
iter = self.__store.get_iter_first()
while iter:
self.__store.set(iter, self.col_nloc, positions.pop())
iter = self.__store.iter_next(iter)
def make_select(self):
hbox = gtk.HBox(True, 2)
all = gtk.Button(_("All"))
all.connect("clicked", self.__select_all, True)
all.set_size_request(50, 25)
all.show()
hbox.pack_start(all, 0, 0, 0)
none = gtk.Button(_("None"))
none.connect("clicked", self.__select_all, False)
none.set_size_request(50, 25)
none.show()
hbox.pack_start(none, 0, 0, 0)
inv = gtk.Button(_("Inverse"))
inv.connect("clicked", self.__select_all, None)
inv.set_size_request(50, 25)
inv.show()
hbox.pack_start(inv, 0, 0, 0)
frame = gtk.Frame(_("Select"))
frame.show()
frame.add(hbox)
hbox.show()
return frame
def make_adjust(self):
hbox = gtk.HBox(True, 2)
incr = gtk.Button("+100")
incr.connect("clicked", self.__incrnew, 100)
incr.set_size_request(50, 25)
incr.show()
hbox.pack_start(incr, 0, 0, 0)
incr = gtk.Button("+10")
incr.connect("clicked", self.__incrnew, 10)
incr.set_size_request(50, 25)
incr.show()
hbox.pack_start(incr, 0, 0, 0)
incr = gtk.Button("+1")
incr.connect("clicked", self.__incrnew, 1)
incr.set_size_request(50, 25)
incr.show()
hbox.pack_start(incr, 0, 0, 0)
decr = gtk.Button("-1")
decr.connect("clicked", self.__incrnew, -1)
decr.set_size_request(50, 25)
decr.show()
hbox.pack_start(decr, 0, 0, 0)
decr = gtk.Button("-10")
decr.connect("clicked", self.__incrnew, -10)
decr.set_size_request(50, 25)
decr.show()
hbox.pack_start(decr, 0, 0, 0)
decr = gtk.Button("-100")
decr.connect("clicked", self.__incrnew, -100)
decr.set_size_request(50, 25)
decr.show()
hbox.pack_start(decr, 0, 0, 0)
auto = gtk.Button(_("Auto"))
auto.connect("clicked", self.__autonew)
auto.set_size_request(50, 25)
auto.show()
hbox.pack_start(auto, 0, 0, 0)
revr = gtk.Button(_("Reverse"))
revr.connect("clicked", self.__revrnew)
revr.set_size_request(50, 25)
revr.show()
hbox.pack_start(revr, 0, 0, 0)
frame = gtk.Frame(_("Adjust New Location"))
frame.show()
frame.add(hbox)
hbox.show()
return frame
def make_options(self):
hbox = gtk.HBox(True, 2)
confirm = gtk.CheckButton(_("Confirm overwrites"))
confirm.connect("toggled", __set_confirm)
confirm.show()
hbox.pack_start(confirm, 0, 0, 0)
frame = gtk.Frame(_("Options"))
frame.add(hbox)
frame.show()
hbox.show()
return frame
def make_controls(self):
hbox = gtk.HBox(False, 2)
hbox.pack_start(self.make_select(), 0, 0, 0)
hbox.pack_start(self.make_adjust(), 0, 0, 0)
# hbox.pack_start(self.make_options(), 0, 0, 0)
hbox.show()
return hbox
def build_ui(self):
self.vbox.pack_start(self.make_view(), 1, 1, 1)
self.vbox.pack_start(self.make_controls(), 0, 0, 0)
def record_use_of(self, number):
lo, hi = self.dst_radio.get_features().memory_bounds
if number < lo or number > hi:
return
try:
mem = self.dst_radio.get_memory(number)
if mem and not mem.empty and number not in self.used_list:
self.used_list.append(number)
except errors.InvalidMemoryLocation:
LOG.error("Location %i empty or at limit of destination radio" %
number)
except errors.InvalidDataError as e:
LOG.error("Got error from radio, assuming %i beyond limits: %s" %
(number, e))
def populate_list(self):
start, end = self.src_radio.get_features().memory_bounds
for i in range(start, end+1):
if end > 50 and i % (end/50) == 0:
self.ww.set(float(i) / end)
try:
mem = self.src_radio.get_memory(i)
except errors.InvalidMemoryLocation as e:
continue
except Exception as e:
self.__store.append(row=(False,
i,
i,
"ERROR",
chirp_common.format_freq(0),
"",
False,
str(e),
))
self.record_use_of(i)
continue
if mem.empty:
continue
self.ww.set(float(i) / end)
try:
msgs = self.dst_radio.validate_memory(
import_logic.import_mem(self.dst_radio,
self.src_radio.get_features(),
mem))
except import_logic.DestNotCompatible:
msgs = self.dst_radio.validate_memory(mem)
errs = [x for x in msgs
if isinstance(x, chirp_common.ValidationError)]
if errs:
msg = _("Cannot be imported because") + ":\r\n"
msg += ",".join(errs)
else:
errs = []
msg = "Memory can be imported into target"
self.__store.append(row=(not bool(msgs),
mem.number,
mem.number,
mem.name,
chirp_common.format_freq(mem.freq),
mem.comment,
not bool(errs),
msg
))
self.record_use_of(mem.number)
TITLE = _("Import From File")
ACTION = _("Import")
def __init__(self, src_radio, dst_radio, parent=None):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self,
buttons=buttons,
title=self.TITLE,
parent=parent)
self.set_default_response(gtk.RESPONSE_OK)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.col_import = 0
self.col_nloc = 1
self.col_oloc = 2
self.col_name = 3
self.col_freq = 4
self.col_comm = 5
self.col_okay = 6
self.col_tmsg = 7
self.caps = {
self.col_import: self.ACTION,
self.col_nloc: _("To"),
self.col_oloc: _("From"),
self.col_name: _("Name"),
self.col_freq: _("Frequency"),
self.col_comm: _("Comment"),
}
self.tips = {
self.col_nloc: _("Location memory will be imported into"),
self.col_oloc: _("Location of memory in the file being imported"),
}
self.types = {
self.col_import: gobject.TYPE_BOOLEAN,
self.col_oloc: gobject.TYPE_INT,
self.col_nloc: gobject.TYPE_INT,
self.col_name: gobject.TYPE_STRING,
self.col_freq: gobject.TYPE_STRING,
self.col_comm: gobject.TYPE_STRING,
self.col_okay: gobject.TYPE_BOOLEAN,
self.col_tmsg: gobject.TYPE_STRING,
}
self.src_radio = src_radio
self.dst_radio = dst_radio
self.used_list = []
self.not_used_list = []
self.build_ui()
self.set_default_size(600, 400)
self.ww = WaitWindow(_("Preparing memory list..."), parent=parent)
self.ww.show()
self.ww.grind()
self.populate_list()
self.ww.hide()
class ExportDialog(ImportDialog):
TITLE = _("Export To File")
ACTION = _("Export")
if __name__ == "__main__":
from chirp.ui import editorset
import sys
f = sys.argv[1]
rc = editorset.radio_class_from_file(f)
radio = rc(f)
d = ImportDialog(radio)
d.run()
print(d.get_import_list())

View file

@ -1,160 +0,0 @@
# Copyright 2008 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
import logging
from chirp.ui.miscwidgets import make_choice
from chirp.ui import reporting
LOG = logging.getLogger(__name__)
class TextInputDialog(gtk.Dialog):
def respond_ok(self, _):
self.response(gtk.RESPONSE_OK)
def __init__(self, **args):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self, buttons=buttons, **args)
self.label = gtk.Label()
self.label.set_size_request(300, 100)
self.label.set_alignment(0.0, 0.5)
# pylint: disable-msg=E1101
self.vbox.pack_start(self.label, 1, 1, 0)
self.text = gtk.Entry()
self.text.connect("activate", self.respond_ok, None)
# pylint: disable-msg=E1101
self.vbox.pack_start(self.text, 1, 1, 0)
self.label.show()
self.text.show()
class ChoiceDialog(gtk.Dialog):
editable = False
def __init__(self, choices, **args):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self, buttons=buttons, **args)
self.set_default_response(gtk.RESPONSE_OK)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.label = gtk.Label()
self.label.set_size_request(300, 100)
self.label.set_alignment(0.0, 0.5)
# pylint: disable-msg=E1101
self.vbox.pack_start(self.label, 1, 1, 0)
self.label.show()
try:
default = choices[0]
except IndexError:
default = None
self.choice = make_choice(sorted(choices), self.editable, default)
# pylint: disable-msg=E1101
self.vbox.pack_start(self.choice, 1, 1, 0)
self.choice.show()
class EditableChoiceDialog(ChoiceDialog):
editable = True
def __init__(self, choices, **args):
ChoiceDialog.__init__(self, choices, **args)
self.choice.child.set_activates_default(True)
class ExceptionDialog(gtk.MessageDialog):
def __init__(self, exception, **args):
gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK,
type=gtk.MESSAGE_ERROR, **args)
self.set_default_response(gtk.RESPONSE_OK)
self.set_property("text", _("An error has occurred"))
self.format_secondary_text(str(exception))
import traceback
import sys
reporting.report_exception(traceback.format_exc(limit=30))
LOG.error("--- Exception Dialog: %s ---" % exception)
LOG.error(traceback.format_exc(limit=100))
LOG.error("----------------------------")
class FieldDialog(gtk.Dialog):
def __init__(self, **args):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self, buttons=buttons, **args)
self.set_default_response(gtk.RESPONSE_OK)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.__fields = {}
def response(self, _):
LOG.debug("Blocking response")
return
def add_field(self, label, widget, validator=None):
box = gtk.HBox(True, 2)
lab = gtk.Label(label + ":")
lab.set_alignment(1.0, 0.5)
lab.show()
widget.set_size_request(150, -1)
widget.show()
box.pack_start(lab, 0, 0, 0)
box.pack_start(widget, 0, 0, 0)
box.show()
# pylint: disable-msg=E1101
self.vbox.pack_start(box, 0, 0, 0)
self.__fields[label] = widget
def get_field(self, label):
return self.__fields.get(label, None)
class OverwriteDialog(gtk.MessageDialog):
def __init__(self, filename):
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self, buttons=buttons)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.set_property("text", _("File Exists"))
text = \
_("The file {name} already exists. "
"Do you want to overwrite it?").format(name=filename)
self.format_secondary_text(text)

File diff suppressed because it is too large Load diff

View file

@ -1,449 +0,0 @@
# 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
import os
import logging
from chirp import chirp_common, settings
from chirp.ui import miscwidgets, common
from chirp.ui import compat
LOG = logging.getLogger(__name__)
POL = ["NN", "NR", "RN", "RR"]
class ValueEditor:
"""Base class"""
def __init__(self, features, memory, errfn, name, data=None):
self._features = features
self._memory = memory
self._errfn = errfn
self._name = name
self._widget = None
self._init(data)
def _init(self, data):
"""Type-specific initialization"""
def set_sensitive(self, sensitive):
self._widget.set_sensitive(sensitive)
def get_widget(self):
"""Returns the widget associated with this editor"""
return self._widget
def _mem_value(self):
"""Returns the raw value from the memory associated with this name"""
if self._name.startswith("extra_"):
return self._memory.extra[self._name.split("_", 1)[1]].value
else:
return getattr(self._memory, self._name)
def _get_value(self):
"""Returns the value from the widget that
should be set in the memory"""
def update(self):
"""Updates the memory object with self._getvalue()"""
try:
newval = self._get_value()
except ValueError as e:
self._errfn(self._name, str(e))
return str(e)
if self._name.startswith("extra_"):
try:
extra_name = self._name.split("_", 1)[1]
LOG.debug('Setting extra %s=%r' % (extra_name, newval))
self._memory.extra[extra_name].value = newval
except settings.InternalError as e:
self._errfn(self._name, str(e))
return str(e)
else:
try:
setattr(self._memory, self._name, newval)
except chirp_common.ImmutableValueError as e:
if getattr(self._memory, self._name) != self._get_value():
self._errfn(self._name, str(e))
return str(e)
except ValueError as e:
self._errfn(self._name, str(e))
return str(e)
all_msgs = self._features.validate_memory(self._memory)
errs = []
for msg in all_msgs:
if isinstance(msg, chirp_common.ValidationError):
errs.append(str(msg))
if errs:
self._errfn(self._name, errs)
else:
self._errfn(self._name, None)
class StringEditor(ValueEditor):
def _init(self, data):
try:
self._widget = gtk.Entry(int(data))
except TypeError:
self._widget = gtk.Entry()
self._widget.set_max_length(int(data))
self._widget.set_text(str(self._mem_value()))
self._widget.connect("changed", self.changed)
def _get_value(self):
return self._widget.get_text()
def changed(self, _widget):
self.update()
class IntegerEditor(ValueEditor):
def _init(self, data):
self._widget = gtk.SpinButton()
adj = self._widget.get_adjustment()
adj.configure(data.get_value(),
data.get_min(), data.get_max(),
data.get_step(), 1, 0)
self._widget.connect('value-changed', self.changed)
def changed(self, _widget):
self.update()
def _get_value(self):
return int(self._widget.get_value())
class ChoiceEditor(ValueEditor):
def _init(self, data):
self._choice = miscwidgets.make_choice([str(x) for x in data],
False,
str(self._mem_value()))
self._widget = self._choice.widget
self._widget.connect("changed", self.changed)
def _get_value(self):
return self._choice.value
def changed(self, _widget):
self.update()
class PowerChoiceEditor(ChoiceEditor):
def _init(self, data):
self._choices = data
ChoiceEditor._init(self, data)
def _get_value(self):
choice = self._widget.get_active_text()
for level in self._choices:
if str(level) == choice:
return level
raise Exception("Internal error: power level went missing")
class IntChoiceEditor(ChoiceEditor):
def _get_value(self):
return int(self._widget.get_active_text())
class FloatChoiceEditor(ChoiceEditor):
def _get_value(self):
return float(self._widget.get_active_text())
class FreqEditor(StringEditor):
def _init(self, data):
StringEditor._init(self, 0)
def _mem_value(self):
return chirp_common.format_freq(StringEditor._mem_value(self))
def _get_value(self):
return chirp_common.parse_freq(self._widget.get_text())
class BooleanEditor(ValueEditor):
def _init(self, data):
self._widget = gtk.CheckButton("Enabled")
self._widget.set_active(self._mem_value())
self._widget.connect("toggled", self.toggled)
def _get_value(self):
return self._widget.get_active()
def toggled(self, _widget):
self.update()
class OffsetEditor(FreqEditor):
pass
class MemoryDetailEditor(gtk.Dialog):
"""Detail editor for a memory"""
def _add(self, tab, row, name, editor, text, colindex=0):
label = gtk.Label(text + ":")
label.set_alignment(1.0, 0.5)
label.show()
tab.attach(label, colindex, colindex + 1, row, row + 1,
xoptions=gtk.FILL, yoptions=0, xpadding=6, ypadding=3)
widget = editor.get_widget()
widget.show()
tab.attach(widget, colindex + 1, colindex + 2, row, row + 1,
xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3)
img = gtk.Image()
img.set_size_request(16, -1)
img.show()
tab.attach(img, colindex + 2, colindex + 3, row, row + 1,
xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3)
self._editors[name] = label, editor, img
return label, editor, img
def _set_doc(self, name, doc):
label, editor, _img = self._editors[name]
self._tips.set_tip(label, doc)
def _make_ui(self):
box = gtk.VBox()
box.show()
notebook = gtk.Notebook()
notebook.set_show_border(False)
notebook.show()
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.show()
hbox = gtk.HBox()
hbox.pack_start(sw, 1, 1, 1)
hbox.show()
tab = notebook.append_page(hbox, gtk.Label(_("General")))
table = gtk.Table(len(self._order), 4, False)
table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
table.show()
sw.add_with_viewport(table)
def _err(name, msg):
try:
_img = self._editors[name][2]
except KeyError:
LOG.error(self._editors.keys())
if msg is None:
_img.clear()
self._tips.set_tip(_img, "")
else:
_img.set_from_stock(gtk.STOCK_DIALOG_WARNING,
gtk.ICON_SIZE_MENU)
self._tips.set_tip(_img, str(msg))
self._errors[self._order.index(name)] = msg is not None
self.set_response_sensitive(gtk.RESPONSE_OK,
True not in self._errors)
row = 0
for name in self._order:
text, editorcls, data = self._elements[name]
editor = editorcls(self._features, self._memory,
_err, name, data)
self._add(table, row, name, editor, text)
self._set_doc(name, text)
row += 1
if len(self._memory.extra):
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.show()
hbox = gtk.HBox()
hbox.pack_start(sw, 1, 1, 1)
hbox.show()
tab = notebook.append_page(hbox, gtk.Label(_("Other")))
table = gtk.Table(len(self._memory.extra), 4, False)
table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
table.show()
sw.add_with_viewport(table)
for setting in self._memory.extra:
name = "extra_%s" % setting.get_name()
if isinstance(setting.value,
settings.RadioSettingValueBoolean):
editor = BooleanEditor(self._features, self._memory,
_err, name)
self._add(table, row, name, editor,
setting.get_shortname())
self._set_doc(name, setting.__doc__)
elif isinstance(setting.value,
settings.RadioSettingValueList):
editor = ChoiceEditor(self._features, self._memory, _err,
name, setting.value.get_options())
self._add(table, row, name, editor,
setting.get_shortname())
self._set_doc(name, setting.__doc__)
elif isinstance(setting.value,
settings.RadioSettingValueInteger):
editor = IntegerEditor(self._features, self._memory,
_err, name, setting.value)
self._add(table, row, name, editor,
setting.get_shortname())
row += 1
self._order.append(name)
self.vbox.pack_start(notebook, 1, 1, 1)
def __init__(self, features, memory, parent=None):
self._memory = memory
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self,
title="Memory Properties",
flags=gtk.DIALOG_MODAL,
parent=parent,
buttons=buttons)
self.set_default_response(gtk.RESPONSE_OK)
self.set_alternative_button_order([gtk.RESPONSE_OK,
gtk.RESPONSE_CANCEL])
self.set_size_request(-1, 500)
self._tips = compat.CompatTooltips()
self._features = features
self._editors = {}
self._elements = {
"freq": (_("Frequency"),
FreqEditor, None),
"name": (_("Name"),
StringEditor, features.valid_name_length),
"tmode": (_("Tone Mode"),
ChoiceEditor, features.valid_tmodes),
"rtone": (_("Tone"),
FloatChoiceEditor, chirp_common.TONES),
"ctone": (_("ToneSql"),
FloatChoiceEditor, chirp_common.TONES),
"dtcs": (_("DTCS Code"),
IntChoiceEditor, chirp_common.DTCS_CODES),
"rx_dtcs": (_("RX DTCS Code"),
IntChoiceEditor, chirp_common.DTCS_CODES),
"dtcs_polarity": (_("DTCS Pol"),
ChoiceEditor, POL),
"cross_mode": (_("Cross mode"),
ChoiceEditor, features.valid_cross_modes),
"duplex": (_("Duplex"),
ChoiceEditor, features.valid_duplexes),
"offset": (_("Offset"),
OffsetEditor, None),
"mode": (_("Mode"),
ChoiceEditor, features.valid_modes),
"tuning_step": (_("Tune Step"),
FloatChoiceEditor, features.valid_tuning_steps),
"skip": (_("Skip"),
ChoiceEditor, features.valid_skips),
"power": (_("Power"),
PowerChoiceEditor, features.valid_power_levels),
"comment": (_("Comment"),
StringEditor, 256),
}
self._order = [
"freq", "name", "tmode", "rtone", "ctone", "cross_mode",
"dtcs", "rx_dtcs", "dtcs_polarity", "duplex", "offset",
"mode", "tuning_step", "skip", "power", "comment"
]
hide_rules = [
("name", features.has_name),
("tmode", len(features.valid_tmodes) > 0),
("ctone", features.has_ctone),
("dtcs", features.has_dtcs),
("rx_dtcs", features.has_rx_dtcs),
("dtcs_polarity", features.has_dtcs_polarity),
("cross_mode", "Cross" in features.valid_tmodes),
("duplex", len(features.valid_duplexes) > 0),
("offset", features.has_offset),
("mode", len(features.valid_modes) > 0),
("tuning_step", features.has_tuning_step),
("skip", len(features.valid_skips) > 0),
("power", features.valid_power_levels),
("comment", features.has_comment),
]
for name, visible in hide_rules:
if not visible:
del self._elements[name]
self._order.remove(name)
self._make_ui()
self._errors = [False] * len(self._order)
self.connect("response", self._validate)
def _validate(self, _dialog, response):
if response == gtk.RESPONSE_OK:
all_msgs = self._features.validate_memory(self._memory)
errors = []
for msg in all_msgs:
if isinstance(msg, chirp_common.ValidationError):
errors.append(msg)
if errors:
common.show_error_text(_("Memory validation failed:"),
os.linesep +
os.linesep.join(errors))
self.emit_stop_by_name('response')
def get_memory(self):
self._memory.empty = False
return self._memory
class MultiMemoryDetailEditor(MemoryDetailEditor):
def __init__(self, features, memory, parent=None):
self._selections = dict()
super(MultiMemoryDetailEditor, self).__init__(features, memory, parent)
def _toggle_selector(self, selector, *widgets):
for widget in widgets:
widget.set_sensitive(selector.get_active())
def _add(self, tab, row, name, editor, text):
label, editor, img = super(MultiMemoryDetailEditor, self)._add(
tab, row, name, editor, text, 1)
selector = gtk.CheckButton()
tab.attach(selector, 0, 1, row, row + 1,
xoptions=gtk.FILL, yoptions=0, xpadding=0, ypadding=3)
selector.show()
self._toggle_selector(selector, label, editor, img)
selector.connect("toggled", self._toggle_selector, label, editor, img)
self._selections[name] = selector
def get_fields(self):
return [k for k, v in self._selections.items() if v.get_active()]

File diff suppressed because it is too large Load diff

View file

@ -1,883 +0,0 @@
# Copyright 2008 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 abc
import gtk
import gobject
import pango
import os
import logging
import six
from chirp import platform
from chirp.ui import compat
from chirp.ui import common
LOG = logging.getLogger(__name__)
class KeyedListWidget(gtk.HBox):
__gsignals__ = {
"item-selected": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
"item-toggled": (gobject.SIGNAL_ACTION,
gobject.TYPE_BOOLEAN,
(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)),
"item-set": (gobject.SIGNAL_ACTION,
gobject.TYPE_BOOLEAN,
(gobject.TYPE_STRING,)),
}
def _toggle(self, rend, path, colnum):
if self.__toggle_connected:
self.__store[path][colnum] = not self.__store[path][colnum]
iter = self.__store.get_iter(path)
id, = self.__store.get(iter, 0)
self.emit("item-toggled", id, self.__store[path][colnum])
def _edited(self, rend, path, new, colnum):
iter = self.__store.get_iter(path)
key, oldval = self.__store.get(iter, 0, colnum)
self.__store.set(iter, colnum, new)
if not self.emit("item-set", key):
self.__store.set(iter, colnum, oldval)
def _mouse(self, view, event):
x, y = event.get_coords()
path = self.__view.get_path_at_pos(int(x), int(y))
if path:
self.__view.set_cursor_on_cell(path[0])
sel = self.get_selected()
if sel:
self.emit("item-selected", sel)
def _make_view(self):
colnum = -1
self._renderers = []
for typ, cap in self.columns:
colnum += 1
if colnum == 0:
continue # Key column
if typ in [gobject.TYPE_STRING, gobject.TYPE_INT,
gobject.TYPE_FLOAT]:
rend = gtk.CellRendererText()
rend.set_property("ellipsize", pango.ELLIPSIZE_END)
column = gtk.TreeViewColumn(cap, rend, text=colnum)
elif typ in [gobject.TYPE_BOOLEAN]:
rend = gtk.CellRendererToggle()
rend.connect("toggled", self._toggle, colnum)
column = gtk.TreeViewColumn(cap, rend, active=colnum)
else:
raise Exception("Unsupported type %s" % typ)
self._renderers.append(rend)
column.set_sort_column_id(colnum)
self.__view.append_column(column)
self.__view.connect("button_press_event", self._mouse)
def set_item(self, key, *values):
iter = self.__store.get_iter_first()
while iter:
id, = self.__store.get(iter, 0)
if id == key:
self.__store.insert_after(iter, row=(id,)+values)
self.__store.remove(iter)
return
iter = self.__store.iter_next(iter)
self.__store.append(row=(key,) + values)
self.emit("item-set", key)
def get_item(self, key):
iter = self.__store.get_iter_first()
while iter:
vals = self.__store.get(iter, *tuple(range(len(self.columns))))
if vals[0] == key:
return vals
iter = self.__store.iter_next(iter)
return None
def del_item(self, key):
iter = self.__store.get_iter_first()
while iter:
id, = self.__store.get(iter, 0)
if id == key:
self.__store.remove(iter)
return True
iter = self.__store.iter_next(iter)
return False
def has_item(self, key):
return self.get_item(key) is not None
def get_selected(self):
try:
(store, iter) = self.__view.get_selection().get_selected()
return store.get(iter, 0)[0]
except Exception as e:
LOG.error("Unable to find selected: %s" % e)
return None
def select_item(self, key):
if key is None:
sel = self.__view.get_selection()
sel.unselect_all()
return True
iter = self.__store.get_iter_first()
while iter:
if self.__store.get(iter, 0)[0] == key:
selection = self.__view.get_selection()
path = self.__store.get_path(iter)
selection.select_path(path)
return True
iter = self.__store.iter_next(iter)
return False
def get_keys(self):
keys = []
iter = self.__store.get_iter_first()
while iter:
key, = self.__store.get(iter, 0)
keys.append(key)
iter = self.__store.iter_next(iter)
return keys
def __init__(self, columns):
gtk.HBox.__init__(self, True, 0)
self.columns = columns
types = tuple([x for x, y in columns])
self.__store = gtk.ListStore(*types)
self.__view = gtk.TreeView(self.__store)
self.pack_start(self.__view, 1, 1, 1)
self.__toggle_connected = False
self._make_view()
self.__view.show()
def connect(self, signame, *args):
if signame == "item-toggled":
self.__toggle_connected = True
gtk.HBox.connect(self, signame, *args)
def set_editable(self, column, is_editable):
rend = self.get_renderer(column)
rend.set_property("editable", True)
rend.connect("edited", self._edited, column + 1)
def set_sort_column(self, column, value=None):
if not value:
value = column
col = self.__view.get_column(column)
col.set_sort_column_id(value)
def get_renderer(self, colnum):
return self._renderers[colnum]
class ListWidget(gtk.HBox):
__gsignals__ = {
"click-on-list": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gtk.TreeView, gtk.gdk.Event)),
"item-toggled": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,)),
}
store_type = gtk.ListStore
def mouse_cb(self, view, event):
self.emit("click-on-list", view, event)
# pylint: disable-msg=W0613
def _toggle(self, render, path, column):
self._store[path][column] = not self._store[path][column]
iter = self._store.get_iter(path)
vals = tuple(self._store.get(iter, *tuple(range(self._ncols))))
for cb in self.toggle_cb:
cb(*vals)
self.emit("item-toggled", vals)
def make_view(self, columns):
self._view = gtk.TreeView(self._store)
for _type, _col in columns:
if _col.startswith("__"):
continue
index = columns.index((_type, _col))
if _type == gobject.TYPE_STRING or \
_type == gobject.TYPE_INT or \
_type == gobject.TYPE_FLOAT:
rend = gtk.CellRendererText()
column = gtk.TreeViewColumn(_col, rend, text=index)
column.set_resizable(True)
rend.set_property("ellipsize", pango.ELLIPSIZE_END)
elif _type == gobject.TYPE_BOOLEAN:
rend = gtk.CellRendererToggle()
rend.connect("toggled", self._toggle, index)
column = gtk.TreeViewColumn(_col, rend, active=index)
else:
raise Exception("Unknown column type (%i)" % index)
column.set_sort_column_id(index)
self._view.append_column(column)
self._view.connect("button_press_event", self.mouse_cb)
def __init__(self, columns, parent=True):
gtk.HBox.__init__(self)
# pylint: disable-msg=W0612
col_types = tuple([x for x, y in columns])
self._ncols = len(col_types)
self._store = self.store_type(*col_types)
self._view = None
self.make_view(columns)
self._view.show()
if parent:
self.pack_start(self._view, 1, 1, 1)
self.toggle_cb = []
def packable(self):
return self._view
def add_item(self, *vals):
if len(vals) != self._ncols:
raise Exception("Need %i columns" % self._ncols)
args = []
i = 0
for val in vals:
args.append(i)
args.append(val)
i += 1
args = tuple(args)
iter = self._store.append()
self._store.set(iter, *args)
def _remove_item(self, model, path, iter, match):
vals = model.get(iter, *tuple(range(0, self._ncols)))
if vals == match:
model.remove(iter)
def remove_item(self, *vals):
if len(vals) != self._ncols:
raise Exception("Need %i columns" % self._ncols)
def remove_selected(self):
try:
(lst, iter) = self._view.get_selection().get_selected()
lst.remove(iter)
except Exception as e:
LOG.error("Unable to remove selected: %s" % e)
def get_selected(self, take_default=False):
(lst, iter) = self._view.get_selection().get_selected()
if not iter and take_default:
iter = lst.get_iter_first()
return lst.get(iter, *tuple(range(self._ncols)))
def move_selected(self, delta):
(lst, iter) = self._view.get_selection().get_selected()
pos = int(lst.get_path(iter)[0])
try:
target = None
if delta > 0 and pos > 0:
target = lst.get_iter(pos-1)
elif delta < 0:
target = lst.get_iter(pos+1)
except Exception as e:
return False
if target:
return lst.swap(iter, target)
def _get_value(self, model, path, iter, lst):
lst.append(model.get(iter, *tuple(range(0, self._ncols))))
def get_values(self):
lst = []
self._store.foreach(self._get_value, lst)
return lst
def set_values(self, lst):
self._store.clear()
for i in lst:
self.add_item(*i)
class TreeWidget(ListWidget):
store_type = gtk.TreeStore
# pylint: disable-msg=W0613
def _toggle(self, render, path, column):
self._store[path][column] = not self._store[path][column]
iter = self._store.get_iter(path)
vals = tuple(self._store.get(iter, *tuple(range(self._ncols))))
piter = self._store.iter_parent(iter)
if piter:
parent = self._store.get(piter, self._key)[0]
else:
parent = None
for cb in self.toggle_cb:
cb(parent, *vals)
def __init__(self, columns, key, parent=True):
ListWidget.__init__(self, columns, parent)
self._key = key
def _add_item(self, piter, *vals):
args = []
i = 0
for val in vals:
args.append(i)
args.append(val)
i += 1
args = tuple(args)
iter = self._store.append(piter)
self._store.set(iter, *args)
def _iter_of(self, key, iter=None):
if not iter:
iter = self._store.get_iter_first()
while iter is not None:
_id = self._store.get(iter, self._key)[0]
if _id == key:
return iter
iter = self._store.iter_next(iter)
return None
def add_item(self, parent, *vals):
if len(vals) != self._ncols:
raise Exception("Need %i columns" % self._ncols)
if not parent:
self._add_item(None, *vals)
else:
iter = self._iter_of(parent)
if iter:
self._add_item(iter, *vals)
else:
raise Exception("Parent not found: %s", parent)
def _set_values(self, parent, vals):
if isinstance(vals, dict):
for key, val in vals.items():
iter = self._store.append(parent)
self._store.set(iter, self._key, key)
self._set_values(iter, val)
elif isinstance(vals, list):
for i in vals:
self._set_values(parent, i)
elif isinstance(vals, tuple):
self._add_item(parent, *vals)
else:
LOG.error("Unknown type: %s" % vals)
def set_values(self, vals):
self._store.clear()
self._set_values(self._store.get_iter_first(), vals)
def del_item(self, parent, key):
iter = self._iter_of(key,
self._store.iter_children(self._iter_of(parent)))
if iter:
self._store.remove(iter)
else:
raise Exception("Item not found")
def get_item(self, parent, key):
iter = self._iter_of(key,
self._store.iter_children(self._iter_of(parent)))
if iter:
return self._store.get(iter, *(tuple(range(0, self._ncols))))
else:
raise Exception("Item not found")
def set_item(self, parent, *vals):
iter = self._iter_of(vals[self._key],
self._store.iter_children(self._iter_of(parent)))
if iter:
args = []
i = 0
for val in vals:
args.append(i)
args.append(val)
i += 1
self._store.set(iter, *(tuple(args)))
else:
raise Exception("Item not found")
class ProgressDialog(gtk.Window):
def __init__(self, title, parent=None):
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
self.set_modal(True)
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.set_title(title)
if parent:
self.set_transient_for(parent)
self.set_resizable(False)
vbox = gtk.VBox(False, 2)
self.label = gtk.Label("")
self.label.set_size_request(100, 50)
self.label.show()
self.pbar = gtk.ProgressBar()
self.pbar.show()
vbox.pack_start(self.label, 0, 0, 0)
vbox.pack_start(self.pbar, 0, 0, 0)
vbox.show()
self.add(vbox)
def set_text(self, text):
self.label.set_text(text)
self.queue_draw()
while gtk.events_pending():
gtk.main_iteration_do(False)
def set_fraction(self, frac):
self.pbar.set_fraction(frac)
self.queue_draw()
while gtk.events_pending():
gtk.main_iteration_do(False)
class LatLonEntry(gtk.Entry):
def __init__(self, *args):
gtk.Entry.__init__(self, *args)
self.connect("changed", self.format)
def format(self, entry):
string = entry.get_text()
if string is None:
return
deg = u"\u00b0"
while " " in string:
if "." in string:
break
elif deg not in string:
string = string.replace(" ", deg)
elif "'" not in string:
string = string.replace(" ", "'")
elif '"' not in string:
string = string.replace(" ", '"')
else:
string = string.replace(" ", "")
entry.set_text(string)
def parse_dd(self, string):
return float(string)
def parse_dm(self, string):
string = string.strip()
string = string.replace(' ', ' ')
(_degrees, _minutes) = string.split(' ', 2)
degrees = int(_degrees)
minutes = float(_minutes)
return degrees + (minutes / 60.0)
def parse_dms(self, string):
string = string.replace(u"\u00b0", " ")
string = string.replace('"', ' ')
string = string.replace("'", ' ')
string = string.replace(' ', ' ')
string = string.strip()
items = string.split(' ')
if len(items) > 3:
raise Exception("Invalid format")
elif len(items) == 3:
deg = items[0]
mns = items[1]
sec = items[2]
elif len(items) == 2:
deg = items[0]
mns = items[1]
sec = 0
elif len(items) == 1:
deg = items[0]
mns = 0
sec = 0
else:
deg = 0
mns = 0
sec = 0
degrees = int(deg)
minutes = int(mns)
seconds = float(sec)
return degrees + (minutes / 60.0) + (seconds / 3600.0)
def value(self):
string = self.get_text()
try:
return self.parse_dd(string)
except:
try:
return self.parse_dm(string)
except:
try:
return self.parse_dms(string)
except Exception as e:
LOG.error("DMS: %s" % e)
raise Exception("Invalid format")
def validate(self):
try:
self.value()
return True
except:
return False
class YesNoDialog(gtk.Dialog):
def __init__(self, title="", parent=None, buttons=None):
gtk.Dialog.__init__(self,
title=title,
parent=parent,
buttons=buttons)
self._label = gtk.Label("")
self._label.set_alignment(0.0, 0.5)
self._label.show()
# pylint: disable-msg=E1101
self.vbox.pack_start(self._label, 1, 1, 1)
def set_text(self, text):
self._label.set_text(text)
@six.add_metaclass(abc.ABCMeta)
class EditableChoiceBase(object):
def __init__(self, options, editable, default):
pass
@property
@abc.abstractmethod
def widget(self):
raise NotImplementedError()
@property
@abc.abstractmethod
def value(self):
pass
@value.setter
@abc.abstractmethod
def value(self):
pass
@abc.abstractmethod
def get_model(self):
pass
@abc.abstractmethod
def append_text(self, text):
"""Add a choice option"""
pass
def set_active(self, text):
"""Legacy compat"""
self.value = text
def get_active_text(self):
"""Legacy compat"""
return self.value
def set_sensitive(self, sensitive):
self.widget.set_sensitive(sensitive)
def connect(self, signal, fn, data):
self.widget.connect(signal, lambda *a: fn(self, data))
class EditableChoiceGTK3(EditableChoiceBase):
def __init__(self, options, editable, default):
self.store = gtk.ListStore(str)
for i, option in enumerate(options):
self.store.append([option])
if editable:
self.sel = gtk.ComboBox.new_with_model_and_entry(self.store)
self.sel.set_entry_text_column(0)
else:
self.sel = gtk.ComboBox.new_with_model(self.store)
renderer_text = gtk.CellRendererText()
self.sel.pack_start(renderer_text, True)
self.sel.add_attribute(renderer_text, "text", 0)
if default:
self.value = default
@property
def widget(self):
return self.sel
@property
def value(self):
modeliter = self.sel.get_active_iter()
if modeliter:
return self.store[modeliter][0]
else:
LOG.warning('No active iter')
@value.setter
def value(self, val):
modeliter = self.store.get_iter_first()
while modeliter is not None:
row = self.store[modeliter]
if row[0] == val:
self.sel.set_active_iter(modeliter)
break
modeliter = self.store.iter_next(modeliter)
def get_model(self):
return self.store
def append_text(self, text):
self.store.append([text])
class EditableChoiceGTK2(EditableChoiceBase):
def __init__(self, options, editable, default):
if editable:
self.sel = gtk.combo_box_entry_new_text()
else:
self.sel = gtk.combo_box_new_text()
for opt in options:
self.sel.append_text(opt)
if default:
try:
idx = options.index(default)
self.sel.set_active(idx)
except Exception as e:
pass
@property
def widget(self):
return self.sel
@property
def value(self):
store = self.sel.get_model()
modeliter = self.sel.get_active_iter()
return store[modeliter][0]
@value.setter
def value(self, val):
common.combo_select(self.sel, val)
def get_model(self):
return self.sel.get_model()
def append_text(self, text):
self.sel.append_text(text)
def make_choice(options, editable=True, default=None):
if hasattr(gtk.ComboBox, 'new_with_entry'):
return EditableChoiceGTK3(options, editable, default)
else:
return EditableChoiceGTK2(options, editable, default)
class FilenameBox(gtk.HBox):
__gsignals__ = {
"filename-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def do_browse(self, _, dir):
if self.filename.get_text():
start = os.path.dirname(self.filename.get_text())
else:
start = None
if dir:
fn = platform.get_platform().gui_select_dir(start)
else:
fn = platform.get_platform().gui_save_file(start, types=self.types)
if fn:
self.filename.set_text(fn)
def do_changed(self, _):
self.emit("filename_changed")
def __init__(self, find_dir=False, types=[]):
gtk.HBox.__init__(self, False, 0)
self.types = types
self.filename = gtk.Entry()
self.filename.show()
self.pack_start(self.filename, 1, 1, 1)
browse = gtk.Button("...")
browse.show()
self.pack_start(browse, 0, 0, 0)
self.filename.connect("changed", self.do_changed)
browse.connect("clicked", self.do_browse, find_dir)
def set_filename(self, fn):
self.filename.set_text(fn)
def get_filename(self):
return self.filename.get_text()
def make_pixbuf_choice(options, default=None):
store = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING)
box = gtk.ComboBox(store)
cell = gtk.CellRendererPixbuf()
box.pack_start(cell, True)
box.add_attribute(cell, "pixbuf", 0)
cell = gtk.CellRendererText()
box.pack_start(cell, True)
box.add_attribute(cell, "text", 1)
_default = None
for pic, value in options:
iter = store.append()
store.set(iter, 0, pic, 1, value)
if default == value:
_default = options.index((pic, value))
if _default:
box.set_active(_default)
return box
def test():
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
lst = ListWidget([(gobject.TYPE_STRING, "Foo"),
(gobject.TYPE_BOOLEAN, "Bar")])
lst.add_item("Test1", True)
lst.set_values([("Test2", True), ("Test3", False)])
lst.show()
win.add(lst)
win.show()
win1 = ProgressDialog("foo")
win1.show()
win2 = gtk.Window(gtk.WINDOW_TOPLEVEL)
lle = LatLonEntry()
lle.show()
win2.add(lle)
win2.show()
win3 = gtk.Window(gtk.WINDOW_TOPLEVEL)
lst = TreeWidget([(gobject.TYPE_STRING, "Id"),
(gobject.TYPE_STRING, "Value")],
1)
lst.set_values({"Fruit": [("Apple", "Red"), ("Orange", "Orange")],
"Pizza": [("Cheese", "Simple"), ("Pepperoni", "Yummy")]})
lst.add_item("Fruit", "Bananna", "Yellow")
lst.show()
win3.add(lst)
win3.show()
def print_val(entry):
if entry.validate():
print("Valid: %s" % entry.value())
else:
print("Invalid")
lle.connect("activate", print_val)
lle.set_text("45 13 12")
try:
gtk.main()
except KeyboardInterrupt:
pass
print(lst.get_values())
if __name__ == "__main__":
test()

View file

@ -1,350 +0,0 @@
import gtk
import gobject
import pango
import re
import os
import logging
from chirp import bitwise
from chirp.ui import common, config
LOG = logging.getLogger(__name__)
CONF = config.get()
def do_insert_line_with_tags(b, line):
def i(text, *tags):
b.insert_with_tags_by_name(b.get_end_iter(), text, *tags)
def ident(name):
if "unknown" in name:
i(name, 'grey', 'bold')
else:
i(name, 'bold')
def nonzero(value):
i(value, 'red', 'bold')
def foo(value):
i(value, 'blue', 'bold')
m = re.match("^( *)([A-z0-9_]+: )(0x[A-F0-9]+) \((.*)\)$", line)
if m:
i(m.group(1))
ident(m.group(2))
if m.group(3) == '0x00':
i(m.group(3))
else:
nonzero(m.group(3))
i(' (')
for char in m.group(4):
if char == '1':
nonzero(char)
else:
i(char)
i(')')
return
m = re.match("^( *)([A-z0-9_]+: )(.*)$", line)
if m:
i(m.group(1))
ident(m.group(2))
i(m.group(3))
return
m = re.match("^(.*} )([A-z0-9_]+)( \()([0-9]+)( bytes at )(0x[A-F0-9]+)",
line)
if m:
i(m.group(1))
ident(m.group(2))
i(m.group(3))
foo(m.group(4))
i(m.group(5))
foo(m.group(6))
i(")")
return
i(line)
def do_insert_with_tags(buf, text):
buf.set_text('')
lines = text.split(os.linesep)
for line in lines:
do_insert_line_with_tags(buf, line)
buf.insert_with_tags_by_name(buf.get_end_iter(), os.linesep)
def classname(obj):
return str(obj.__class__).split('.')[-1]
def bitwise_type(classname):
return classname.split("DataElement")[0]
class FixedEntry(gtk.Entry):
def __init__(self, *args, **kwargs):
super(FixedEntry, self).__init__(*args, **kwargs)
try:
fontsize = CONF.get_int("browser_fontsize", "developer")
except Exception:
fontsize = 10
if fontsize < 4 or fontsize > 144:
LOG.warn("Unsupported browser_fontsize %i. Using 10." % fontsize)
fontsize = 11
fontdesc = pango.FontDescription("Courier bold %i" % fontsize)
self.modify_font(fontdesc)
class IntegerEntry(FixedEntry):
def _colorize(self, _self):
value = self.get_text()
if value.startswith("0x"):
value = value[2:]
value = value.replace("0", "")
if not value:
self.modify_text(gtk.STATE_NORMAL, None)
else:
self.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse('red'))
def __init__(self, *args, **kwargs):
super(IntegerEntry, self).__init__(*args, **kwargs)
self.connect("changed", self._colorize)
class BitwiseEditor(gtk.HBox):
def __init__(self, element):
super(BitwiseEditor, self).__init__(False, 3)
self._element = element
self._build_ui()
class IntegerEditor(BitwiseEditor):
def _changed(self, entry, base):
if not self._update:
return
value = entry.get_text()
if value.startswith("0x"):
value = value[2:]
self._element.set_value(int(value, base))
self._update_entries(skip=entry)
def _update_entries(self, skip=None):
self._update = False
for ent, format_spec in self._entries:
if ent != skip:
ent.set_text(format_spec.format(int(self._element)))
self._update = True
def _build_ui(self):
self._entries = []
self._update = True
hexdigits = ((self._element.size() / 4) +
(self._element.size() % 4 and 1 or 0))
formats = [('Hex', 16, '0x{:0%iX}' % hexdigits),
('Dec', 10, '{:d}'),
('Bin', 2, '{:0%ib}' % self._element.size())]
for name, base, format_spec in formats:
lab = gtk.Label(name)
self.pack_start(lab, 0, 0, 0)
lab.show()
int(self._element)
ent = IntegerEntry()
self._entries.append((ent, format_spec))
ent.connect('changed', self._changed, base)
self.pack_start(ent, 0, 0, 0)
ent.show()
self._update_entries()
class BCDArrayEditor(BitwiseEditor):
def _changed(self, entry, hexent):
self._element.set_value(int(entry.get_text()))
self._format_hexent(hexent)
def _format_hexent(self, hexent):
value = ""
for i in self._element:
a, b = i.get_value()
value += "%i%i" % (a, b)
hexent.set_text(value)
def _build_ui(self):
lab = gtk.Label("Dec")
lab.show()
self.pack_start(lab, 0, 0, 0)
ent = FixedEntry()
ent.set_text(str(int(self._element)))
ent.show()
self.pack_start(ent, 1, 1, 1)
lab = gtk.Label("Hex")
lab.show()
self.pack_start(lab, 0, 0, 0)
hexent = FixedEntry()
hexent.show()
self.pack_start(hexent, 1, 1, 1)
hexent.set_editable(False)
ent.connect('changed', self._changed, hexent)
self._format_hexent(hexent)
class CharArrayEditor(BitwiseEditor):
def _changed(self, entry):
self._element.set_value(entry.get_text().ljust(len(self._element)))
def _build_ui(self):
ent = FixedEntry(len(self._element))
ent.set_text(str(self._element).rstrip("\x00"))
ent.connect('changed', self._changed)
ent.show()
self.pack_start(ent, 1, 1, 1)
class OtherEditor(BitwiseEditor):
def _build_ui(self):
name = classname(self._element)
name = bitwise_type(name)
if isinstance(self._element, bitwise.arrayDataElement):
name += " %s[%i]" % (
bitwise_type(classname(self._element[0])),
len(self._element))
l = gtk.Label(name)
l.show()
self.pack_start(l, 1, 1, 1)
class RadioBrowser(common.Editor):
def _build_ui(self):
self._display = gtk.Table(20, 2)
self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
self._tree = gtk.TreeView(self._store)
rend = gtk.CellRendererText()
tvc = gtk.TreeViewColumn('Element', rend, text=0)
self._tree.append_column(tvc)
self._tree.connect('button_press_event', self._tree_click)
self.root = gtk.HPaned()
self.root.set_position(200)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(self._tree)
sw.show()
self.root.add1(sw)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add_with_viewport(self._display)
sw.show()
self.root.add2(sw)
self._tree.show()
self._display.show()
self.root.show()
def _fill(self, name, obj, parent=None):
iter = self._store.append(parent, (name, obj))
if isinstance(obj, bitwise.structDataElement):
for name, item in obj.items():
if isinstance(item, bitwise.structDataElement):
self._fill(name, item, iter)
elif isinstance(item, bitwise.arrayDataElement):
self._fill("%s[%i]" % (name, len(item)), item, iter)
elif isinstance(obj, bitwise.arrayDataElement):
i = 0
for item in obj:
if isinstance(obj[0], bitwise.structDataElement):
self._fill("%s[%i]" % (name, i), item, iter)
i += 1
def _tree_click(self, view, event):
if event.button != 1:
return
index = [0]
def pack(widget, pos):
self._display.attach(widget, pos, pos + 1, index[0], index[0] + 1,
xoptions=gtk.FILL, yoptions=0)
def next_row():
index[0] += 1
def abandon(child):
self._display.remove(child)
pathinfo = view.get_path_at_pos(int(event.x), int(event.y))
path = pathinfo[0]
iter = self._store.get_iter(path)
name, obj = self._store.get(iter, 0, 1)
self._display.foreach(abandon)
for name, item in obj.items():
if item.size() % 8 == 0:
name = '<b>%s</b> <small>(%s %i bytes)</small>' % (
name, bitwise_type(classname(item)), item.size() / 8)
else:
name = '<b>%s</b> <small>(%s %i bits)</small>' % (
name, bitwise_type(classname(item)), item.size())
l = gtk.Label(name + " ")
l.set_use_markup(True)
l.show()
pack(l, 0)
if (isinstance(item, bitwise.intDataElement) or
isinstance(item, bitwise.bcdDataElement)):
e = IntegerEditor(item)
elif (isinstance(item, bitwise.arrayDataElement) and
isinstance(item[0], bitwise.bcdDataElement)):
e = BCDArrayEditor(item)
elif (isinstance(item, bitwise.arrayDataElement) and
isinstance(item[0], bitwise.charDataElement)):
e = CharArrayEditor(item)
else:
e = OtherEditor(item)
e.show()
pack(e, 1)
next_row()
def __init__(self, rthread):
super(RadioBrowser, self).__init__(rthread)
self._radio = rthread.radio
self._focused = False
self._build_ui()
self._fill('root', self._radio._memobj)
def focus(self):
self._focused = True
def unfocus(self):
if self._focused:
self.emit("changed")
self._focused = False
if __name__ == "__main__":
from chirp.drivers import *
from chirp import directory
import sys
r = directory.get_radio_by_image(sys.argv[1])
class Foo:
radio = r
w = gtk.Window()
b = RadioBrowser(Foo)
w.set_default_size(1024, 768)
w.add(b.root)
w.show()
gtk.main()

View file

@ -1,195 +0,0 @@
# Copyright 2011 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/>.
# README:
#
# I know that collecting data is not very popular. I don't like it
# either. However, it's hard to tell what drivers people are using
# and I think it would be helpful if I had that information. This is
# completely optional, so you can turn it off if you want. It doesn't
# report anything other than version and model usage information. The
# code below is very conservative, and will disable itself if reporting
# fails even once or takes too long to perform. It's run in a thread
# so that the user shouldn't even notice it's happening.
#
import threading
import os
import time
import logging
from chirp import CHIRP_VERSION, platform
REPORT_URL = "http://chirp.danplanet.com/report/report.php?do_report"
ENABLED = True
THREAD_SEM = threading.Semaphore(10) # Maximum number of outstanding threads
LAST = 0
LAST_TYPE = None
LOG = logging.getLogger(__name__)
try:
# Don't let failure to import any of these modules cause trouble
from chirp.ui import config
import xmlrpclib
except:
ENABLED = False
def should_report():
if not ENABLED:
LOG.info("Not reporting due to recent failure")
return False
conf = config.get()
if conf.get_bool("no_report"):
LOG.info("Reporting disabled")
return False
return True
def _report_model_usage(model, direction, success):
global ENABLED
if direction not in ["live", "download", "upload",
"import", "export", "importsrc"]:
LOG.warn("Invalid direction `%s'" % direction)
return True # This is a bug, but not fatal
model = "%s_%s" % (model.VENDOR, model.MODEL)
data = "%s,%s,%s" % (model, direction, success)
LOG.debug("Reporting model usage: %s" % data)
proxy = xmlrpclib.ServerProxy(REPORT_URL)
id = proxy.report_stats(CHIRP_VERSION,
platform.get_platform().os_version_string(),
"model_use",
data)
# If the server returns zero, it wants us to shut up
return id != 0
def _report_exception(stack):
global ENABLED
LOG.debug("Reporting exception")
proxy = xmlrpclib.ServerProxy(REPORT_URL)
id = proxy.report_exception(CHIRP_VERSION,
platform.get_platform().os_version_string(),
"exception",
stack)
# If the server returns zero, it wants us to shut up
return id != 0
def _report_misc_error(module, data):
global ENABLED
LOG.debug("Reporting misc error with %s" % module)
proxy = xmlrpclib.ServerProxy(REPORT_URL)
id = proxy.report_misc_error(CHIRP_VERSION,
platform.get_platform().os_version_string(),
module, data)
# If the server returns zero, it wants us to shut up
return id != 0
def _check_for_updates(callback):
LOG.debug("Checking for updates")
proxy = xmlrpclib.ServerProxy(REPORT_URL)
ver = proxy.check_for_updates(CHIRP_VERSION,
platform.get_platform().os_version_string())
LOG.debug("Server reports version %s is latest" % ver)
callback(ver)
return True
class ReportThread(threading.Thread):
def __init__(self, func, *args):
threading.Thread.__init__(self)
self.__func = func
self.__args = args
def _run(self):
try:
return self.__func(*self.__args)
except Exception as e:
LOG.debug("Failed to report: %s" % e)
return False
def run(self):
start = time.time()
result = self._run()
if not result:
# Reporting failed
ENABLED = False
elif (time.time() - start) > 15:
# Reporting took too long
LOG.debug("Time to report was %.2f sec -- Disabling" %
(time.time()-start))
ENABLED = False
THREAD_SEM.release()
def dispatch_thread(func, *args):
global LAST
global LAST_TYPE
# If reporting is disabled or failing, bail
if not should_report():
LOG.debug("Reporting is disabled")
return
# If the time between now and the last report is less than 5 seconds, bail
delta = time.time() - LAST
if delta < 5 and func == LAST_TYPE:
LOG.debug("Throttling...")
return
LAST = time.time()
LAST_TYPE = func
# If there are already too many threads running, bail
if not THREAD_SEM.acquire(False):
LOG.debug("Too many threads already running")
return
t = ReportThread(func, *args)
t.start()
def report_model_usage(model, direction, success):
dispatch_thread(_report_model_usage, model, direction, success)
def report_exception(stack):
dispatch_thread(_report_exception, stack)
def report_misc_error(module, data):
dispatch_thread(_report_misc_error, module, data)
# Calls callback with the latest version
def check_for_updates(callback):
dispatch_thread(_check_for_updates, callback)

View file

@ -1,233 +0,0 @@
# 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 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gtk
import gobject
import logging
from chirp import chirp_common
from chirp import settings
from chirp.ui import common, miscwidgets
LOG = logging.getLogger(__name__)
class RadioSettingProxy(settings.RadioSetting):
def __init__(self, setting, editor):
self._setting = setting
self._editor = editor
class SettingsEditor(common.Editor):
def __init__(self, rthread):
super(SettingsEditor, self).__init__(rthread)
# The main box
self.root = gtk.HBox(False, 0)
# The pane
paned = gtk.HPaned()
paned.show()
self.root.pack_start(paned, 1, 1, 0)
# The selection tree
self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT)
self._view = gtk.TreeView(self._store)
self._view.get_selection().connect("changed", self._view_changed_cb)
column = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
column.set_widget(gtk.Label())
self._view.append_column(column)
self._view.show()
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled_window.add_with_viewport(self._view)
scrolled_window.set_size_request(200, -1)
scrolled_window.show()
paned.pack1(scrolled_window)
# The settings notebook
self._notebook = gtk.Notebook()
self._notebook.set_show_tabs(False)
self._notebook.set_show_border(False)
self._notebook.show()
paned.pack2(self._notebook)
self._changed = False
self._settings = None
job = common.RadioJob(self._get_settings_cb, "get_settings")
job.set_desc("Getting radio settings")
self.rthread.submit(job)
def _save_settings(self):
if self._settings is None:
return
def setting_cb(result):
if isinstance(result, Exception):
common.show_error(_("Error in setting value: %s") % result)
elif self._changed:
self.emit("changed")
self._changed = False
job = common.RadioJob(setting_cb, "set_settings",
self._settings)
job.set_desc("Setting radio settings")
self.rthread.submit(job)
def _do_save_setting(self, widget, value):
if isinstance(value, settings.RadioSettingValueInteger):
value.set_value(widget.get_adjustment().get_value())
elif isinstance(value, settings.RadioSettingValueFloat):
value.set_value(widget.get_text())
elif isinstance(value, settings.RadioSettingValueBoolean):
value.set_value(widget.get_active())
elif isinstance(value, settings.RadioSettingValueList):
value.set_value(widget.value)
elif isinstance(value, settings.RadioSettingValueString):
value.set_value(widget.get_text())
else:
LOG.error("Unsupported widget type %s for %s" %
(element.value.__class__, element.get_name()))
self._changed = True
self._save_settings()
def _save_setting(self, widget, value):
try:
self._do_save_setting(widget, value)
except settings.InvalidValueError as e:
common.show_error(_("Invalid setting value: %s") % e)
def _build_ui_tab(self, group):
# The scrolled window
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.show()
# Notebook tab
tab = self._notebook.append_page(sw, gtk.Label(_(group.get_name())))
# Settings table
table = gtk.Table(len(group), 2, False)
table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
table.show()
sw.add_with_viewport(table)
row = 0
for element in group:
if not isinstance(element, settings.RadioSetting):
continue
# Label
label = gtk.Label(element.get_shortname() + ":")
label.set_alignment(1.0, 0.5)
label.show()
table.attach(label, 0, 1, row, row + 1,
xoptions=gtk.FILL, yoptions=0,
xpadding=6, ypadding=3)
if isinstance(element.value, list) and \
isinstance(element.value[0],
settings.RadioSettingValueInteger):
box = gtk.HBox(True)
else:
box = gtk.VBox(True)
# Widget container
box.show()
table.attach(box, 1, 2, row, row + 1,
xoptions=gtk.FILL, yoptions=0,
xpadding=12, ypadding=3)
for i in list(element.keys()):
value = element[i]
if isinstance(value, settings.RadioSettingValueInteger):
widget = gtk.SpinButton()
adj = widget.get_adjustment()
adj.configure(value.get_value(),
value.get_min(), value.get_max(),
value.get_step(), 1, 0)
widget.connect("value-changed", self._save_setting, value)
elif isinstance(value, settings.RadioSettingValueFloat):
widget = gtk.Entry()
widget.set_width_chars(16)
widget.set_text(value.format())
widget.connect("focus-out-event", lambda w, e, v:
self._save_setting(w, v), value)
elif isinstance(value, settings.RadioSettingValueBoolean):
widget = gtk.CheckButton(_("Enabled"))
widget.set_active(value.get_value())
widget.connect("toggled", self._save_setting, value)
elif isinstance(value, settings.RadioSettingValueList):
choice = miscwidgets.make_choice([], editable=False)
model = choice.get_model()
model.clear()
for option in value.get_options():
choice.append_text(option)
choice.value = value.get_value()
widget = choice.widget
choice.connect("changed", self._save_setting, value)
elif isinstance(value, settings.RadioSettingValueString):
widget = gtk.Entry()
widget.set_width_chars(32)
widget.set_text(str(value).rstrip())
widget.connect("focus-out-event", lambda w, e, v:
self._save_setting(w, v), value)
else:
LOG.error("Unsupported widget type: %s" % value.__class__)
widget.set_sensitive(value.get_mutable())
label.set_mnemonic_widget(widget)
widget.get_accessible().set_name(element.get_shortname())
widget.show()
box.pack_start(widget, 1, 1, 1)
row += 1
return tab
def _build_ui_group(self, group, parent):
tab = self._build_ui_tab(group)
iter = self._store.append(parent)
self._store.set(iter, 0, group.get_shortname(), 1, tab)
for element in group:
if not isinstance(element, settings.RadioSetting):
self._build_ui_group(element, iter)
def _build_ui(self, settings):
if not isinstance(settings, list):
raise Exception("Invalid Radio Settings")
return
self._settings = settings
for group in settings:
self._build_ui_group(group, None)
self._view.expand_all()
def _get_settings_cb(self, settings):
gobject.idle_add(self._build_ui, settings)
def _view_changed_cb(self, selection):
(lst, iter) = selection.get_selected()
tab, = self._store.get(iter, 1)
self._notebook.set_current_page(tab)

View file

@ -1,162 +0,0 @@
#
# Copyright 2008 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
import gobject
import threading
import logging
from chirp import errors, chirp_common
LOG = logging.getLogger(__name__)
class ShiftDialog(gtk.Dialog):
def __init__(self, rthread, parent=None):
buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
gtk.Dialog.__init__(self,
title=_("Shift"),
buttons=buttons)
self.set_default_response(gtk.RESPONSE_OK)
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
self.rthread = rthread
self.__prog = gtk.ProgressBar()
self.__prog.show()
self.__labl = gtk.Label("")
self.__labl.show()
self.vbox.pack_start(self.__prog, 1, 1, 1)
self.vbox.pack_start(self.__labl, 0, 0, 0)
self.quiet = False
self.thread = None
def _status(self, msg, prog):
self.__labl.set_text(msg)
self.__prog.set_fraction(prog)
def status(self, msg, prog):
gobject.idle_add(self._status, msg, prog)
def _shift_memories(self, delta, memories):
count = 0.0
for i in memories:
src = i.number
dst = src + delta
LOG.info("Moving %i to %i" % (src, dst))
self.status(_("Moving {src} to {dst}").format(src=src,
dst=dst),
count / len(memories))
i.number = dst
if i.empty:
self.rthread.radio.erase_memory(i.number)
else:
self.rthread.radio.set_memory(i)
count += 1.0
return int(count)
def _get_mems_until_hole(self, start, endokay=False, all=False):
mems = []
llimit, ulimit = self.rthread.radio.get_features().memory_bounds
pos = start
while pos <= ulimit:
self.status(_("Looking for a free spot ({number})").format(
number=pos), 0)
try:
mem = self.rthread.radio.get_memory(pos)
if mem.empty and not all:
break
except errors.InvalidMemoryLocation:
break
mems.append(mem)
pos += 1
if pos > ulimit and not endokay:
raise errors.InvalidMemoryLocation(_("No space to insert a row"))
LOG.debug("Found a hole: %i" % pos)
return mems
def _insert_hole(self, start):
mems = self._get_mems_until_hole(start)
mems.reverse()
if mems:
ret = self._shift_memories(1, mems)
if ret:
# Clear the hole we made
self.rthread.radio.erase_memory(start)
return ret
else:
LOG.warn("No memory list?")
return 0
def _delete_hole(self, start, all=False):
mems = self._get_mems_until_hole(start+1, endokay=True, all=all)
if mems:
count = self._shift_memories(-1, mems)
self.rthread.radio.erase_memory(count+start)
return count
else:
LOG.warn("No memory list?")
return 0
def finished(self):
if self.quiet:
gobject.idle_add(self.response, gtk.RESPONSE_OK)
else:
gobject.idle_add(self.set_response_sensitive,
gtk.RESPONSE_OK, True)
def threadfn(self, newhole, func, *args):
self.status("Waiting for radio to become available", 0)
self.rthread.lock()
try:
count = func(newhole, *args)
except errors.InvalidMemoryLocation as e:
self.status(str(e), 0)
self.finished()
return
self.rthread.unlock()
self.status(_("Moved {count} memories").format(count=count), 1)
self.finished()
def insert(self, newhole, quiet=False):
self.quiet = quiet
self.thread = threading.Thread(target=self.threadfn,
args=(newhole, self._insert_hole))
self.thread.start()
gtk.Dialog.run(self)
def delete(self, newhole, quiet=False, all=False):
self.quiet = quiet
self.thread = threading.Thread(target=self.threadfn,
args=(newhole, self._delete_hole, all))
self.thread.start()
gtk.Dialog.run(self)

View file

@ -20,10 +20,8 @@ setup(name='chirp',
],
extras_require={
'wx': ['wxPython'],
'gtk': ['PyGObject']
},
entry_points={
'console_scripts': ["chirp=chirp.wxui:chirpmain",
"legacychirp=chirp.ui.__main__:main"],
'console_scripts': ["chirp=chirp.wxui:chirpmain"],
},
)

View file

@ -144,24 +144,6 @@
./chirp/pyPEG.py
./chirp/sources/radioreference.py
./chirp/settings.py
./chirp/ui/__init__.py
./chirp/ui/bandplans.py
./chirp/ui/bankedit.py
./chirp/ui/clone.py
./chirp/ui/cloneprog.py
./chirp/ui/common.py
./chirp/ui/dstaredit.py
./chirp/ui/editorset.py
./chirp/ui/importdialog.py
./chirp/ui/inputdialog.py
./chirp/ui/mainapp.py
./chirp/ui/memdetail.py
./chirp/ui/memedit.py
./chirp/ui/miscwidgets.py
./chirp/ui/radiobrowser.py
./chirp/ui/reporting.py
./chirp/ui/settingsedit.py
./chirp/ui/shiftdialog.py
./chirp/util.py
./chirpc
./rpttool