[py3] Changes to ui so that we can actually start under py3/gi

This adds a chirp.ui.compat module where we can pile things needed to
handle python3 differently. Right now there are a bunch of things that just fail
for reasons I don't fully grasp yet. Those go under a py3safe() decorator,
which logs and then ignores them.

Also, there is an issue with the sensitive=True bit on the columns for empty
memories. Need to figure that out.

#495
This commit is contained in:
Dan Smith 2019-02-07 12:35:09 -08:00
parent 434b9668ea
commit 26b583576c
7 changed files with 116 additions and 44 deletions

31
chirp/ui/compat.py Normal file
View File

@ -0,0 +1,31 @@
import contextlib
import logging
import gtk
LOG = logging.getLogger('uicompat')
@contextlib.contextmanager
def py3safe():
try:
yield
except Exception as e:
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

View File

@ -18,6 +18,7 @@ import gobject
import logging
from chirp.ui import common, miscwidgets
from chirp.ui import compat
LOG = logging.getLogger(__name__)
@ -50,9 +51,10 @@ class CallsignEditor(gtk.HBox):
self.listw.set_editable(1, True)
self.listw.connect("item-set", self._cs_changed)
rend = self.listw.get_renderer(1)
rend.set_property("family", "Monospace")
rend.set_property("width-chars", width)
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)
@ -126,7 +128,7 @@ class DStarEditor(common.Editor):
fixed = self.rthread.radio.get_features().has_implicit_calls
frame = gtk.Frame(_("Your callsign"))
frame = compat.Frame(_("Your callsign"))
self.editor_ucall = CallsignEditor(first_fixed=fixed)
self.editor_ucall.set_size_request(-1, 200)
self.editor_ucall.show()
@ -134,7 +136,7 @@ class DStarEditor(common.Editor):
frame.show()
box.pack_start(frame, 1, 1, 0)
frame = gtk.Frame(_("Repeater callsign"))
frame = compat.Frame(_("Repeater callsign"))
self.editor_rcall = CallsignEditor(first_fixed=fixed)
self.editor_rcall.set_size_request(-1, 200)
self.editor_rcall.show()
@ -142,7 +144,7 @@ class DStarEditor(common.Editor):
frame.show()
box.pack_start(frame, 1, 1, 0)
frame = gtk.Frame(_("My callsign"))
frame = compat.Frame(_("My callsign"))
self.editor_mcall = CallsignEditor()
self.editor_mcall.set_size_request(-1, 200)
self.editor_mcall.show()

View File

@ -28,8 +28,9 @@ import gobject
import sys
from chirp.ui import inputdialog, common
from chirp.ui import compat
from chirp import platform, directory, util
from chirp.drivers import generic_xml, generic_csv, repeaterbook
from chirp.drivers import generic_csv, repeaterbook
from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6
from chirp.drivers import icf, ic9x_icf
from chirp import CHIRP_VERSION, chirp_common, detect, errors
@ -45,7 +46,7 @@ if __name__ == "__main__":
try:
import serial
except ImportError, e:
except ImportError as e:
common.log_exception()
common.show_error("\nThe Pyserial module is not installed!")
@ -374,7 +375,7 @@ of file.
if not radio:
return
LOG.debug("Manually selected %s" % radio)
except Exception, e:
except Exception as e:
common.log_exception()
common.show_error(os.path.basename(fname) + ": " + str(e))
return
@ -384,7 +385,7 @@ of file.
eset = editorset.EditorSet(radio, self,
filename=fname,
tempname=tempname)
except Exception, e:
except Exception as e:
common.log_exception()
common.show_error(
_("There was an error opening {fname}: {error}").format(
@ -491,7 +492,7 @@ of file.
try:
eset.save(fname)
except Exception, e:
except Exception as e:
d = inputdialog.ExceptionDialog(e)
d.run()
d.destroy()
@ -574,7 +575,7 @@ of file.
try:
shutil.copy(fn, stock_dir)
LOG.debug("Copying %s -> %s" % (fn, stock_dir))
except Exception, e:
except Exception as e:
LOG.error("Unable to copy %s to %s: %s" % (fn, stock_dir, e))
return False
return True
@ -584,7 +585,7 @@ of file.
if not os.path.isdir(stock_dir):
try:
os.mkdir(stock_dir)
except Exception, e:
except Exception as e:
LOG.error("Unable to create directory: %s" % stock_dir)
return
if not self.copy_shipped_stock_configs(stock_dir):
@ -730,7 +731,7 @@ of file.
rtscts=rclass.HARDWARE_FLOW,
timeout=0.25)
ser.flushInput()
except serial.SerialException, e:
except serial.SerialException as e:
d = inputdialog.ExceptionDialog(e)
d.run()
d.destroy()
@ -776,7 +777,7 @@ of file.
rtscts=radio.HARDWARE_FLOW,
timeout=0.25)
ser.flushInput()
except serial.SerialException, e:
except serial.SerialException as e:
d = inputdialog.ExceptionDialog(e)
d.run()
d.destroy()
@ -910,7 +911,7 @@ of file.
radio = dmrmarc.DMRMARCRadio(None)
radio.set_params(city, state, country)
self.do_open_live(radio, read_only=True)
except errors.RadioError, e:
except errors.RadioError as e:
common.show_error(e)
self.window.set_cursor(None)
@ -1021,7 +1022,6 @@ of file.
query = query % (code,
band and band or "%%",
county and county or "%%")
print query
# Do this in case the import process is going to take a while
# to make sure we process events leading up to this
@ -1045,11 +1045,11 @@ of file.
("query=%s\n" % query) +
("\n") +
("\n".join(radio.errors)))
except errors.InvalidDataError, e:
except errors.InvalidDataError as e:
common.show_error(str(e))
self.window.set_cursor(None)
return
except Exception, e:
except Exception as e:
common.log_exception()
reporting.report_model_usage(radio, "import", True)
@ -1127,7 +1127,6 @@ of file.
query = "https://www.repeaterbook.com/repeaters/downloads/CHIRP/" \
"app_direct.php?loc=%s&band=%s&dist=%s" % (loc, band, dist)
print query
# Do this in case the import process is going to take a while
# to make sure we process events leading up to this
@ -1151,11 +1150,11 @@ of file.
("query=%s\n" % query) +
("\n") +
("\n".join(radio.errors)))
except errors.InvalidDataError, e:
except errors.InvalidDataError as e:
common.show_error(str(e))
self.window.set_cursor(None)
return
except Exception, e:
except Exception as e:
common.log_exception()
reporting.report_model_usage(radio, "import", True)
@ -1239,7 +1238,7 @@ of file.
try:
radio = PRRadio(filename)
except Exception, e:
except Exception as e:
common.show_error(str(e))
return
@ -1378,7 +1377,7 @@ of file.
radio = radioreference.RadioReferenceRadio(None)
radio.set_params(zipcode, username, passwd)
self.do_open_live(radio, read_only=True)
except errors.RadioError, e:
except errors.RadioError as e:
common.show_error(e)
self.window.set_cursor(None)
@ -1620,7 +1619,7 @@ of file.
# See this for why:
# http://stackoverflow.com/questions/2904274/globals-and-locals-in-python-exec
exec(pyc, globals(), globals())
except Exception, e:
except Exception as e:
common.log_exception()
common.show_error("Unable to load module: %s" % e)
@ -1934,15 +1933,17 @@ of file.
box = gtk.HBox(False, 2)
self.sb_general = gtk.Statusbar()
self.sb_general.set_has_resize_grip(False)
self.sb_general.show()
box.pack_start(self.sb_general, 1, 1, 1)
self.sb_radio = gtk.Statusbar()
self.sb_radio.set_has_resize_grip(True)
self.sb_radio.show()
box.pack_start(self.sb_radio, 1, 1, 1)
with compat.py3safe():
self.sb_general.set_has_resize_grip(False)
self.sb_radio.set_has_resize_grip(True)
box.show()
return box
@ -2065,7 +2066,9 @@ of file.
allocation = window.get_allocation()
CONF.set_int("window_w", allocation.width, "state")
CONF.set_int("window_h", allocation.height, "state")
self.connect("expose_event", expose)
with compat.py3safe():
self.connect("expose_event", expose)
def state_change(window, event):
CONF.set_bool(

View File

@ -29,7 +29,10 @@ import pickle
import os
import logging
import six
from chirp.ui import common, shiftdialog, miscwidgets, config, memdetail
from chirp.ui import compat
from chirp.ui import bandplans
from chirp import chirp_common, errors, directory, import_logic
@ -423,7 +426,7 @@ class MemoryEditor(common.Editor):
if extd:
val = extd
return val
return str(val)
def render(self, _, rend, model, iter, colnum):
val, hide = model.get(iter, colnum, self.col("_hide_cols"))
@ -1001,18 +1004,29 @@ class MemoryEditor(common.Editor):
else:
choices = gtk.ListStore(TYPE_STRING, TYPE_STRING)
for choice in self.choices[_cap]:
choices.append([choice, self._render(i, choice)])
choices.append([str(choice), self._render(i, choice)])
rend.set_property("model", choices)
rend.set_property("text-column", 1)
rend.set_property("editable", True)
rend.set_property("has-entry", False)
rend.connect("edited", self.edited, _cap)
col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled)
if six.PY3:
# FIXMEPY3: we can't set sensitive on the column without
# it affecting the whole column (which makes sense).
# Setting it on the renderer doesn't seem to work like
# we want either.
col = gtk.TreeViewColumn(_cap, rend, text=i)
else:
col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled)
col.set_cell_data_func(rend, self.render, i)
else:
rend.set_property("editable", _cap not in non_editable)
rend.connect("edited", self.edited, _cap)
col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled)
if six.PY3:
# FIXMEPY3: See above
col = gtk.TreeViewColumn(_cap, rend, text=i)
else:
col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled)
col.set_cell_data_func(rend, self.render, i)
col.set_reorderable(True)
@ -1191,7 +1205,7 @@ class MemoryEditor(common.Editor):
self._config.get_int(hikey) or 999
self.lo_limit_adj = gtk.Adjustment(lostart, min, max-1, 1, 10)
lo = gtk.SpinButton(self.lo_limit_adj)
lo = compat.SpinButton(self.lo_limit_adj)
lo.connect("value-changed", self._store_limit, "lo")
lo.show()
hbox.pack_start(lo, 0, 0, 0)
@ -1201,7 +1215,7 @@ class MemoryEditor(common.Editor):
hbox.pack_start(lab, 0, 0, 0)
self.hi_limit_adj = gtk.Adjustment(histart, min+1, max, 1, 10)
hi = gtk.SpinButton(self.hi_limit_adj)
hi = compat.SpinButton(self.hi_limit_adj)
hi.connect("value-changed", self._store_limit, "hi")
hi.show()
hbox.pack_start(hi, 0, 0, 0)
@ -1640,13 +1654,15 @@ class DstarMemoryEditor(MemoryEditor):
if i not in self.choices:
continue
column = self.view.get_column(self.col(i))
rend = column.get_cell_renderers()[0]
rend.set_property("has-entry", True)
with compat.py3safe():
rend = column.get_cell_renderers()[0]
rend.set_property("has-entry", True)
for i in _dv_columns:
col = self.view.get_column(self.col(i))
rend = col.get_cell_renderers()[0]
rend.set_property("family", "Monospace")
with compat.py3safe():
rend = col.get_cell_renderers()[0]
rend.set_property("family", "Monospace")
def set_urcall_list(self, urcalls):
store = self.choices["URCALL"]

View File

@ -21,6 +21,7 @@ import os
import logging
from chirp import platform
from chirp.ui import compat
LOG = logging.getLogger(__name__)
@ -186,9 +187,10 @@ class KeyedListWidget(gtk.HBox):
def set_editable(self, column, is_editable):
col = self.__view.get_column(column)
rend = col.get_cell_renderers()[0]
rend.set_property("editable", True)
rend.connect("edited", self._edited, column + 1)
with compat.py3safe():
rend = col.get_cell_renderers()[0]
rend.set_property("editable", True)
rend.connect("edited", self._edited, column + 1)
def set_sort_column(self, column, value=None):
if not value:
@ -197,7 +199,8 @@ class KeyedListWidget(gtk.HBox):
col.set_sort_column_id(value)
def get_renderer(self, colnum):
return self.__view.get_column(colnum).get_cell_renderers()[0]
with compat.py3safe():
return self.__view.get_column(colnum).get_cell_renderers()[0]
class ListWidget(gtk.HBox):

20
chirpw
View File

@ -15,14 +15,30 @@
# 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 glob
import os
import six
if six.PY3:
from gi import pygtkcompat
pygtkcompat.enable()
pygtkcompat.enable_gtk(version='3.0')
from chirp import logger
from chirp import elib_intl
from chirp import platform
from chirp.drivers import *
from chirp.ui import config
# Safe import of everything in chirp/drivers
driver_files = glob.glob('chirp/drivers/*.py')
for driver_file in driver_files:
module, _ = os.path.splitext(driver_file)
module = module.replace('/', '.')
try:
__import__(module)
except Exception as e:
print('Failed to import %s: %s' % (module, e))
import sys
import os
@ -99,7 +115,7 @@ except:
vmaj, vmin = pyver.split(".", 2)
vrel = 0
if int(vmaj) < 2 or int(vmin) < 6:
if int(vmaj) == 2 and int(vmin) < 6:
# Python <2.6, emulate str.format()
import __builtin__

View File

@ -1 +1,2 @@
six
PyGObject