mirror of
https://github.com/kk7ds/chirp
synced 2026-08-13 17:51:36 -04:00
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:
parent
6964f1a0cc
commit
b9054684e7
22 changed files with 1 additions and 9401 deletions
|
|
@ -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/>.
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
@ -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)
|
||||
2386
chirp/ui/mainapp.py
2386
chirp/ui/mainapp.py
File diff suppressed because it is too large
Load diff
|
|
@ -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()]
|
||||
1751
chirp/ui/memedit.py
1751
chirp/ui/memedit.py
File diff suppressed because it is too large
Load diff
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
4
setup.py
4
setup.py
|
|
@ -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"],
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue