mirror of
https://gitdl.cn/https://github.com/chakralinux/core.git
synced 2025-02-03 23:38:28 +08:00
1424 lines
49 KiB
Python
1424 lines
49 KiB
Python
#!/usr/bin/env python2
|
|
# -*- coding: UTF-8 -*-
|
|
#
|
|
# suim.py
|
|
#
|
|
# (c) Copyright 2010-2011 Michael Towers (larch42 at googlemail dot com)
|
|
#
|
|
# This file is part of the larch project.
|
|
#
|
|
# larch 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.
|
|
#
|
|
# larch 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 larch; if not, write to the Free Software Foundation, Inc.,
|
|
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
#----------------------------------------------------------------------------
|
|
# 2011.02.01
|
|
|
|
#++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
#TODO
|
|
# Add more widgets
|
|
# Add more attribute handling
|
|
# Add more signal handling
|
|
|
|
# Fetching of image and icon files via a sort of url-like mechanism, I
|
|
# suppose initially using the 'base:' prefix might be ok.
|
|
# Then the cwd of the gui script would be irrelevant.
|
|
|
|
#New file dialog for accessing the 'server' end.
|
|
|
|
# I suspect the DialogButtonBox stuff needs changing to cope with
|
|
# translations.
|
|
|
|
#----------------------------------------------------
|
|
|
|
|
|
"""SUIM - Simple User Interface Module
|
|
|
|
The aim is to provide a means of creating graphical user interfaces of
|
|
moderate complexity while abstracting the interface to the actual underlying
|
|
toolkit in such a way that (at least potentially) an alternative toolkit
|
|
could be used.
|
|
[At present this aspect is rather theoretical since only a pyqt based
|
|
version has been written.]
|
|
|
|
The gui layout is specified as a python data structure, using widget types,
|
|
parameter and signal names independent of the underlying toolkit. All
|
|
widgets are accessible by their tag, which must be specified.
|
|
|
|
A widget is defined by a call to the 'widget' method of the GuiApp instance.
|
|
The first argument is the widget type, the second is the widget tag, the
|
|
remaining ones must be named, they form the parameters to the constructor.
|
|
If the widget is a 'container' (i.e. if it contains other widgets), it will
|
|
need a 'layout' parameter defining the layout of its contents.
|
|
|
|
There is also a 'widgetlist' method which accepts a list of widget
|
|
definitions, each definition being itself a list. The first entry in a
|
|
definition is the widget type, the second is the widget tag, the
|
|
third is a dictionary containing all the parameters. For convenience (I'm not
|
|
sure if I will keep this, though) any entries after the dictionary will be
|
|
treated as signal names. These are just added to the parameter dictionary
|
|
with value '' (enabling the signal with its default tag).
|
|
|
|
Signals have signatures/keys comprising the tag of the emitting widget and
|
|
the signal name (separated by '*'), and this will by default also be the tag
|
|
by which the signal is known for connection purposes. But this can be
|
|
overridden, for example to allow several widgets to emit the same signal.
|
|
In the latter case the widget tag can (optionally) be passed as the first
|
|
argument to the signal handler.
|
|
|
|
Passing signal names as parameters to a widget constructor enables these
|
|
signals. They can later be disabled, if desired.
|
|
|
|
Connect and disconnect methods are available, to associate (or dissociate)
|
|
handler functions with (/from) signals.
|
|
"""
|
|
|
|
import os, sys, traceback, threading
|
|
from PyQt4 import QtGui, QtCore, QtWebKit
|
|
from collections import deque
|
|
|
|
|
|
def debug(text):
|
|
sys.stderr.write("GUI: %s\n" % text)
|
|
sys.stderr.flush()
|
|
|
|
# Either I need to wrap all text input with this or I need to ensure that
|
|
# I get unicode from outside ...
|
|
import locale
|
|
encoding = locale.getdefaultlocale()[1]
|
|
def convert(text):
|
|
"""Try to handle encoding.
|
|
"""
|
|
if isinstance(text, str):
|
|
return text.decode(encoding) if encoding else text.decode()
|
|
else:
|
|
return text
|
|
|
|
|
|
# Widget Base Classes - essentially used as 'Mixins' >>>>>>>>>>>>>>>>
|
|
class WBase:
|
|
def x__tt(self, text):
|
|
"""Set tooltip.
|
|
"""
|
|
self.setToolTip(text) #qt
|
|
|
|
def x__text(self, text=""):
|
|
"""Set widget text.
|
|
"""
|
|
self.setText(convert(text)) #qt
|
|
|
|
def x__enable(self, on):
|
|
"""Enable/Disable widget. on should be True to enable the widget
|
|
(display it in its normal, active state), False to disable it
|
|
(which will normally be paler and non-interactive).
|
|
"""
|
|
self.setEnabled(on) #qt
|
|
|
|
def x__focus(self):
|
|
self.setFocus() #qt
|
|
|
|
def x__width(self, w):
|
|
"""Set the minimum width for the widget.
|
|
"""
|
|
self.setMinimumWidth(w) #qt
|
|
|
|
def x__typewriter(self, on):
|
|
"""Use a typewriter (fixed spacing) font.
|
|
"""
|
|
if on:
|
|
f = QtGui.QFont(self.font()) #qt
|
|
f.setFamily("Courier") #qt
|
|
self.setFont(f) #qt
|
|
|
|
def x__busycursor(self, on):
|
|
"""Set/clear the busy-cursor for this widget.
|
|
"""
|
|
if on:
|
|
self.setCursor(QtCore.Qt.BusyCursor) #qt
|
|
else:
|
|
self.unsetCursor() #qt
|
|
|
|
|
|
class BBase:
|
|
"""Button mixin.
|
|
"""
|
|
def x__icon(self, icon):
|
|
self.setIcon(self.style().standardIcon(icondict[icon])) #qt
|
|
|
|
#qt
|
|
icondict = { "left" : QtGui.QStyle.SP_ArrowLeft,
|
|
"right" : QtGui.QStyle.SP_ArrowRight,
|
|
"down" : QtGui.QStyle.SP_ArrowDown,
|
|
"up" : QtGui.QStyle.SP_ArrowUp,
|
|
"reload" : QtGui.QStyle.SP_BrowserReload,
|
|
"critical" : QtGui.QStyle.SP_MessageBoxCritical,
|
|
"apply" : QtGui.QStyle.SP_DialogApplyButton,
|
|
"close" : QtGui.QStyle.SP_DialogCloseButton,
|
|
"cancel" : QtGui.QStyle.SP_DialogCancelButton,
|
|
}
|
|
|
|
class Container:
|
|
"""This just adds layout management for widgets which contain
|
|
other widgets.
|
|
"""
|
|
def x__layout(self, layout, immediate=False):
|
|
"""A layout specifies and organizes the contents of a widget.
|
|
Note that the layouting is not immediately performed by default as
|
|
it is unlikely that all the contained widgets have been defined yet.
|
|
"""
|
|
self._layout = layout
|
|
if immediate:
|
|
self.x__pack()
|
|
|
|
def x__pack(self):
|
|
"""A layout call specifies and organizes the contents of a widget.
|
|
The layout can be a layout manager list, or a single widget name
|
|
(or an empty string, which will cause a warning to be issued, but
|
|
may be useful during development).
|
|
|
|
There are three sorts of thing which can appear in layout manager
|
|
lists (apart from the layout type at the head of the list and an
|
|
optional attribute dict as second item). There can be named
|
|
widgets, there can be further layout managers (specified as lists,
|
|
nested as deeply as you like) and there can be layout widgets,
|
|
like spacers and separators.
|
|
|
|
A layout widget can have optional arguments, which are separated
|
|
by commas, e.g. 'VLINE,3' passes the argument '3' to the VLINE
|
|
constructor.
|
|
"""
|
|
# getattr avoids having to have an __init__() for Container.
|
|
if getattr(self, '_layout', None):
|
|
if self._layout != '$':
|
|
self.setLayout(self.getlayout(self._layout))
|
|
self._layout = '$'
|
|
else:
|
|
debug("No layout set on '%s'" % self.w_name)
|
|
|
|
def getlayout(self, item):
|
|
if isinstance(item, list):
|
|
try:
|
|
# Create a layout manager instance
|
|
layoutmanager = layout_table[item[0]]()
|
|
assert isinstance(layoutmanager, Layout)
|
|
except:
|
|
gui_error("Unknown layout type: %s" % item[0])
|
|
if (len(item) > 1) and isinstance(item[1], dict):
|
|
dictarg = item[1]
|
|
ilist = item[2:]
|
|
else:
|
|
dictarg = {}
|
|
ilist = item[1:]
|
|
# Build up the list of objects to lay out
|
|
# If the layout manager is a GRID, accept only grid rows ('+')
|
|
if isinstance(layoutmanager, GRID):
|
|
args = []
|
|
rowlen = None
|
|
for i in ilist:
|
|
if isinstance(i, list) and (i[0] == '+'):
|
|
args.append(self.getlayoutlist(i[1:], grid=True))
|
|
if rowlen == None:
|
|
rowlen = len(i)
|
|
elif len(i) != rowlen:
|
|
gui_error("Grid (%s) row lengths unequal"
|
|
% self.w_name)
|
|
else:
|
|
gui_error("Grid (%s) layouts must consist of grid"
|
|
" rows ('+')" % self.w_name)
|
|
else:
|
|
# Otherwise the elements of the argument list can be:
|
|
# A sub-layout
|
|
# A widget
|
|
# A SPACE
|
|
args = self.getlayoutlist(ilist)
|
|
layoutmanager.do_layout(args)
|
|
# Attributes
|
|
for key, val in dictarg:
|
|
handler = "x__" + key
|
|
if hasattr(layoutmanager, handler):
|
|
getattr(layoutmanager, handler)(val)
|
|
return layoutmanager
|
|
|
|
else:
|
|
# It must be a widget, which will need to be put in a box (qt)
|
|
return self.getlayout(['VBOX', item])
|
|
|
|
def getlayoutlist(self, items, grid=False):
|
|
objects = []
|
|
for i in items:
|
|
if isinstance(i, list):
|
|
obj = self.getlayout(i)
|
|
else:
|
|
parts = i.split(',')
|
|
i = parts[0]
|
|
args = parts[1:]
|
|
try:
|
|
obj = layout_table[i](*args)
|
|
if not (isinstance(obj, SPACE) # or a separator line
|
|
or isinstance(obj, QtGui.QWidget)): #qt
|
|
assert (grid and isinstance(obj, Span))
|
|
except:
|
|
obj = guiapp.getwidget(i)
|
|
if obj != None:
|
|
if isinstance(obj, Container):
|
|
obj.x__pack()
|
|
else:
|
|
gui_error("Bad item in layout of '%s': '%s'"
|
|
% (self.w_name, i))
|
|
objects.append(obj)
|
|
return objects
|
|
|
|
|
|
class XContainer(Container):
|
|
"""This is a mixin class for containers which can contain more than
|
|
one layout.
|
|
"""
|
|
def x__layout(self, layout):
|
|
gui_error("An extended container (%s) has no 'layout' method"
|
|
% self.w_name)
|
|
|
|
|
|
class TopLevel(Container):
|
|
def x__show(self):
|
|
self.set_visible()
|
|
|
|
def set_visible(self, on=True):
|
|
self.setVisible(on) #qt
|
|
|
|
def x__size(self, w_h):
|
|
w, h = [int(i) for i in w_h.split("_")]
|
|
self.resize(w, h) #qt
|
|
|
|
def x__icon(self, iconpath):
|
|
guiapp.setWindowIcon(QtGui.QIcon(iconpath)) #qt
|
|
|
|
def x__title(self, text):
|
|
if text == None:
|
|
text = guiapp.appname
|
|
self.setWindowTitle(text) #qt
|
|
|
|
def x__getSize(self):
|
|
s = self.size() #qt
|
|
return "%d_%d" % (s.width(), s.height()) #qt
|
|
|
|
def x__getScreenSize(self):
|
|
dw = guiapp.desktop() #qt
|
|
geom = dw.screenGeometry(self) #qt
|
|
return "%d_%d" % (geom.width(), geom.height()) #qt
|
|
|
|
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
class Window(QtGui.QWidget, TopLevel): #qt
|
|
"""This is needed to trap window closing events.
|
|
"""
|
|
def __init__(self):
|
|
QtGui.QWidget.__init__(self) #qt
|
|
self.closesignal = ""
|
|
|
|
def closeEvent(self, event): #qt
|
|
if self.closesignal:
|
|
guiapp.sendsignal(self.closesignal)
|
|
event.ignore() #qt
|
|
return
|
|
QtGui.QWidget.closeEvent(self, event) #qt
|
|
|
|
def x__closesignal(self, text):
|
|
self.closesignal = text
|
|
|
|
|
|
class Dialog(QtGui.QDialog, TopLevel):
|
|
def __init__(self):
|
|
QtGui.QDialog.__init__(self) #qt
|
|
|
|
def x__showmodal(self):
|
|
return self.exec_() == QtGui.QDialog.Accepted #qt
|
|
|
|
|
|
class DialogButtons(QtGui.QDialogButtonBox): #qt
|
|
def __init__(self):
|
|
return
|
|
|
|
def x__buttons(self, args):
|
|
"""This keyword argument MUST be present.
|
|
"""
|
|
buttons = 0
|
|
for a in args:
|
|
try:
|
|
b = getattr(QtGui.QDialogButtonBox, a) #qt
|
|
assert isinstance(b, int) #qt
|
|
buttons |= b #qt
|
|
except:
|
|
gui_warning("Unknown Dialog button: %s" % a)
|
|
QtGui.QDialogButtonBox.__init__(self, buttons) #qt
|
|
|
|
def x__dialog(self, dname):
|
|
"""This must be set or else the dialog buttons won't do anything.
|
|
"""
|
|
self._dialog = guiapp.getwidget(dname)
|
|
self.connect(self, QtCore.SIGNAL("clicked(QAbstractButton *)"), #qt
|
|
self._clicked) #qt
|
|
|
|
def _clicked(self, button): #qt
|
|
if self.buttonRole(button) == self.AcceptRole: #qt
|
|
self._dialog.accept() #qt
|
|
else:
|
|
self._dialog.reject() #qt
|
|
|
|
|
|
def textLineDialog(label="???:", title=None, text="", pw=False):
|
|
if title == None:
|
|
title = guiapp.appname
|
|
echo = QtGui.QLineEdit.Password if pw else QtGui.QLineEdit.Normal #qt
|
|
res, ok = QtGui.QInputDialog.getText(None, title, label, echo, text) #qt
|
|
return (ok, unicode(res))
|
|
|
|
|
|
def listDialog(label="???", title=None, items=[], current=0):
|
|
if title == None:
|
|
title = guiapp.appname
|
|
res, ok = QtGui.QInputDialog.getItem(None, title, label, items, current)
|
|
return (ok, unicode(res))
|
|
|
|
|
|
def confirmDialog(message, title=None):
|
|
if title == None:
|
|
title = guiapp.appname
|
|
return (QtGui.QMessageBox.question(None, title, convert(message),
|
|
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
|
|
== QtGui.QMessageBox.Ok)
|
|
|
|
|
|
def infoDialog(message, title=None):
|
|
if title == None:
|
|
title = guiapp.appname
|
|
QtGui.QMessageBox.information(None, title, message)
|
|
|
|
|
|
#+++++++++++++++++++++++++++
|
|
# Error handling
|
|
def gui_error(message, title=None):
|
|
if title == None:
|
|
title = "!!! %s !!!" % guiapp.appname
|
|
QtGui.QMessageBox.critical(None, title, message)
|
|
guiapp.quit()
|
|
|
|
def gui_warning(message, title=None):
|
|
if title == None:
|
|
title = guiapp.appname
|
|
QtGui.QMessageBox.warning(None, title, message)
|
|
|
|
def onexcept(text):
|
|
debug(traceback.format_exc())
|
|
gui_error(text, "Exception")
|
|
#---------------------------
|
|
|
|
|
|
fileDialogDir = ''
|
|
# In tests with the gtk dialog the options were not respected,
|
|
# with ro you could still create new directories
|
|
# and all files were shown when only-directories was specified.
|
|
# So I am using the non-native dialogs.
|
|
|
|
def fileDialog_getdir(caption = None, ro = True, startdir=None):
|
|
global fileDialogDir
|
|
if caption == None:
|
|
caption = ''
|
|
if startdir == None:
|
|
startdir = fileDialogDir
|
|
options = QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog
|
|
if ro:
|
|
options |= QtGui.QFileDialog.ReadOnly
|
|
d = QtGui.QFileDialog.getExistingDirectory(None, caption, startdir, options)
|
|
if d:
|
|
d = unicode(d)
|
|
fileDialogDir = d
|
|
return d
|
|
|
|
def fileDialog_open(caption = None, startdir=None, filter=None):
|
|
global fileDialogDir
|
|
if caption == None:
|
|
caption = ''
|
|
if startdir == None:
|
|
startdir = fileDialogDir
|
|
if filter:
|
|
filter = '%s (%s)' % (filter[0], ' '.join(filter[1:])) #qt
|
|
else:
|
|
filter = ''
|
|
d = QtGui.QFileDialog.getOpenFileName(None, caption, startdir, filter, #qt
|
|
options=QtGui.QFileDialog.DontUseNativeDialog | QtGui.QFileDialog.ReadOnly) #qt
|
|
if d:
|
|
d = unicode(d)
|
|
fileDialogDir = os.path.dirname(d)
|
|
return d
|
|
|
|
def fileDialog_save(caption = None, startdir=None, filter=None):
|
|
global fileDialogDir
|
|
if caption == None:
|
|
caption = ''
|
|
if startdir == None:
|
|
startdir = fileDialogDir
|
|
if filter:
|
|
filter = '%s (%s)' % (filter[0], ' '.join(filter[1:])) #qt
|
|
else:
|
|
filter = ''
|
|
d = QtGui.QFileDialog.getSaveFileName(None, caption, startdir, filter, #qt
|
|
options=QtGui.QFileDialog.DontUseNativeDialog) #qt
|
|
if d:
|
|
d = unicode(d)
|
|
fileDialogDir = os.path.dirname(d)
|
|
return d
|
|
|
|
|
|
class Stack(QtGui.QStackedWidget, XContainer): #qt
|
|
def __init__(self):
|
|
QtGui.QStackedWidget.__init__(self) #qt
|
|
self.x_twidgets = []
|
|
|
|
def x__pages(self, pages):
|
|
self.x_twidgets = pages
|
|
|
|
def x__pack(self):
|
|
for name in self.x_twidgets:
|
|
w = guiapp.getwidget(name)
|
|
w.x__pack()
|
|
self.addWidget(w) #qt
|
|
|
|
def x__set(self, index=0):
|
|
self.setCurrentIndex(index) #qt
|
|
|
|
|
|
class Notebook(QtGui.QTabWidget, XContainer): #qt
|
|
def __init__(self):
|
|
QtGui.QTabWidget.__init__(self) #qt
|
|
self.x_tabs = []
|
|
self.x_twidgets = []
|
|
|
|
def x__changed(self, name=''):
|
|
guiapp.signal(self, 'changed', name, 'currentChanged(int)') #qt
|
|
|
|
def x__tabs(self, tabs):
|
|
self.x_twidgets = tabs
|
|
|
|
def x__pack(self):
|
|
for name, title in self.x_twidgets:
|
|
w = guiapp.getwidget(name)
|
|
w.x__pack()
|
|
self.addTab(w, title) #qt
|
|
self.x_tabs.append([name, w])
|
|
|
|
def x__set(self, index=0):
|
|
self.setCurrentIndex(index) #qt
|
|
|
|
def x__enableTab(self, index, on):
|
|
self.setTabEnabled(index, on) #qt
|
|
|
|
|
|
class Page(QtGui.QWidget, Container): #qt
|
|
def __init__(self):
|
|
QtGui.QWidget.__init__(self) #qt
|
|
|
|
def x__enable(self, on):
|
|
"""Enable/Disable widget. on should be True to enable the widget
|
|
(display it in its normal, active state), False to disable it
|
|
(which will normally be paler and non-interactive).
|
|
"""
|
|
self.setEnabled(on) #qt
|
|
|
|
|
|
class Frame(QtGui.QGroupBox, WBase, Container): #qt
|
|
def __init__(self):
|
|
QtGui.QGroupBox.__init__(self) #qt
|
|
self._text = None
|
|
|
|
def x__text(self, text):
|
|
self._text = text
|
|
self.setTitle(text) #qt
|
|
|
|
# A hack to improve spacing
|
|
def setLayout(self, layout):
|
|
topgap = 10 if self._text else 0
|
|
layout.setContentsMargins(0, topgap, 0, 0) #qt
|
|
QtGui.QGroupBox.setLayout(self, layout)
|
|
|
|
|
|
class OptionalFrame(Frame): #qt
|
|
def __init__(self): #qt
|
|
Frame.__init__(self) #qt
|
|
self.setCheckable(True) #qt
|
|
self.setChecked(False) #qt
|
|
|
|
def x__toggled(self, name=''):
|
|
guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
|
|
|
|
def x__opton(self, on):
|
|
self.setChecked(on) #qt
|
|
|
|
#TODO: Is this still needed? (I think it's a qt bug)
|
|
def x__enable_hack(self): #qt
|
|
if not self.isChecked(): #qt
|
|
self.setChecked(True) #qt
|
|
self.setChecked(False) #qt
|
|
|
|
def x__active(self):
|
|
return self.isChecked() #qt
|
|
|
|
|
|
def read_markup(markup):
|
|
def read_markup0(mlist):
|
|
text = ''
|
|
for i in mlist:
|
|
text += read_markup(i) if isinstance(i, list) else i
|
|
return text
|
|
tag = markup[0]
|
|
if tag == '':
|
|
return read_markup0(markup[1:])
|
|
elif tag in ('h1', 'h2', 'h3', 'h4', 'p', 'em', 'strong'):
|
|
return '<%s>%s</%s>' % (tag, read_markup0(markup[1:]), tag)
|
|
elif tag == 'color':
|
|
return '<span style="color:%s;">%s</span>' % (markup[1],
|
|
read_markup0(markup[2:]))
|
|
return "Markup parse error"
|
|
|
|
|
|
class Label(QtGui.QLabel, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QLabel.__init__(self) #qt
|
|
|
|
def x__markup(self, markup):
|
|
self.setText(read_markup(markup)) #qt
|
|
|
|
def x__image(self, path):
|
|
self.setPixmap(QtGui.QPixmap(path)) #qt
|
|
|
|
def x__align(self, pos):
|
|
if pos == "center":
|
|
a = QtCore.Qt.AlignCenter #qt
|
|
else:
|
|
a = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter #qt
|
|
self.setAlignment(a) #qt
|
|
|
|
|
|
class Button(QtGui.QPushButton, WBase, BBase): #qt
|
|
def __init__(self):
|
|
QtGui.QPushButton.__init__(self) #qt
|
|
|
|
def x__clicked(self, name=''):
|
|
guiapp.signal(self, 'clicked', name, 'clicked()') #qt
|
|
|
|
|
|
class ToggleButton(QtGui.QPushButton, WBase, BBase): #qt
|
|
def __init__(self):
|
|
QtGui.QPushButton.__init__(self) #qt
|
|
self.setCheckable(True) #qt
|
|
|
|
def x__toggled(self, name=''):
|
|
guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
|
|
|
|
def x__set(self, on):
|
|
self.setChecked(on) #qt
|
|
|
|
|
|
class CheckBox(QtGui.QCheckBox, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QCheckBox.__init__(self) #qt
|
|
|
|
def x__toggled(self, name=''):
|
|
# A bit of work is needed to get True/False state #qt
|
|
# instead of 0/1/2 #qt
|
|
guiapp.signal(self, 'toggled', name,
|
|
'toggled(bool)', self.s_toggled) #qt
|
|
|
|
def s_toggled(self, state): #qt
|
|
"""Convert the argument to True/False.
|
|
""" #qt
|
|
return (state != QtCore.Qt.Unchecked,) #qt
|
|
|
|
def x__set(self, on):
|
|
self.setCheckState(2 if on else 0) #qt
|
|
|
|
def x__active(self):
|
|
return self.checkState() != QtCore.Qt.Unchecked #qt
|
|
|
|
|
|
class RadioButton(QtGui.QRadioButton, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QPushButton.__init__(self) #qt
|
|
|
|
def x__toggled(self, name=''):
|
|
guiapp.signal(self, 'toggled', name, 'toggled(bool)') #qt
|
|
|
|
def x__set(self, on):
|
|
self.setChecked(on) #qt
|
|
|
|
def x__active(self):
|
|
return self.isChecked() #qt
|
|
|
|
|
|
class ComboBox(QtGui.QComboBox, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QComboBox.__init__(self) #qt
|
|
|
|
def x__changed(self, name=''):
|
|
guiapp.signal(self, 'changed', name, 'currentIndexChanged(int)') #qt
|
|
|
|
def x__changedstr(self, name=''):
|
|
guiapp.signal(self, 'changedstr', name,
|
|
'currentIndexChanged(const QString &)') #qt
|
|
|
|
def x__set(self, items, index=0):
|
|
self.blockSignals(True)
|
|
self.clear() #qt
|
|
if items:
|
|
self.addItems(items) #qt
|
|
self.setCurrentIndex(index) #qt
|
|
self.blockSignals(False)
|
|
|
|
def x__index(self):
|
|
return self.currentIndex() #qt
|
|
|
|
def x__setindex(self, index):
|
|
return self.setCurrentIndex(index) #qt
|
|
|
|
# def x__colour(self, index, colour):
|
|
# Unfortunately this doesn't set the colour of the item when it is
|
|
# selected, only in the list.
|
|
# model = self.model()
|
|
# mitem = model.item(index)
|
|
# mitem.setForeground(QtGui.QColor(colour))
|
|
|
|
def x__icon(self, index, icon):
|
|
self.setItemIcon(index, self.style().standardIcon(icondict[icon])) #qt
|
|
|
|
|
|
class ListChoice(QtGui.QListWidget, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QListWidget.__init__(self) #qt
|
|
|
|
def x__changed(self, name=''):
|
|
guiapp.signal(self, 'changed', name, 'currentRowChanged(int)') #qt
|
|
|
|
def x__set(self, items, index=0):
|
|
self.blockSignals(True)
|
|
self.clear() #qt
|
|
if items:
|
|
self.addItems(items) #qt
|
|
self.setCurrentRow(index) #qt
|
|
self.blockSignals(False)
|
|
|
|
|
|
class List(QtGui.QTreeWidget, WBase): #qt
|
|
# Only using top-level items of the tree
|
|
def __init__(self):
|
|
QtGui.QTreeWidget.__init__(self) #qt
|
|
self.mode = ""
|
|
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) #qt
|
|
self.setRootIsDecorated(False) #qt
|
|
self._hcompact = False # used for scheduling header-compaction
|
|
|
|
def x__select(self, name=''):
|
|
guiapp.signal(self, 'select', name,
|
|
'itemSelectionChanged()', self.s_select) #qt
|
|
|
|
def x__clicked(self, name=''):
|
|
guiapp.signal(self, 'clicked', name,
|
|
'itemClicked(QTreeWidgetItem *,int)', self.s_clicked) #qt
|
|
|
|
def s_select(self):
|
|
# Signal a selection change, passing the new selection list (indexes)
|
|
s = [self.indexOfTopLevelItem(i) for i in self.selectedItems()] #qt
|
|
if self.mode == "Single":
|
|
return s
|
|
else:
|
|
return (s,)
|
|
|
|
def s_clicked(self, item, col): #qt
|
|
"""This is intended for activating a user-defined editing function.
|
|
Tests showed that this is called after the selection is changed, so
|
|
if using this signal, use it only in 'Single' selection mode and
|
|
use this, not 'select' to record selection changes. Clicking on the
|
|
selected row should start editing the cell, otherwise just change
|
|
the selection.
|
|
"""
|
|
ix = self.indexOfTopLevelItem(item) #qt
|
|
return (ix, col)
|
|
|
|
def x__selectionmode(self, sm):
|
|
self.mode = sm
|
|
if sm == "None":
|
|
self.setSelectionMode(QtGui.QAbstractItemView.NoSelection) #qt
|
|
elif sm == "Single":
|
|
self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) #qt
|
|
else:
|
|
self.mode = ""
|
|
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) #qt
|
|
|
|
def x__headers(self, headers): #qt
|
|
self.setHeaderLabels(headers) #qt
|
|
if self._hcompact:
|
|
self._compact()
|
|
|
|
def x__set(self, items, index=0): #qt
|
|
# Note that each item must be a tuple/list containing
|
|
# entries for each column.
|
|
self.clear() #qt
|
|
c = 0
|
|
for i in items:
|
|
item = QtGui.QTreeWidgetItem(self, i) #qt
|
|
self.addTopLevelItem(item) #qt
|
|
if c == index:
|
|
self.setCurrentItem(item)
|
|
c += 1
|
|
if self._hcompact:
|
|
self._compact()
|
|
|
|
def x__compact(self, on=True):
|
|
self._hcompact = on
|
|
if on:
|
|
self._compact()
|
|
|
|
def _compact(self):
|
|
for i in range(self.columnCount()): #qt
|
|
self.resizeColumnToContents(i) #qt
|
|
|
|
def x__single_icon(self, row, column, icon):
|
|
"""Set the icon for the given cell.
|
|
<icon> is the file-path of the icon, the icons being cached to
|
|
avoid duplication.
|
|
Only the setting of a single icon is supported,
|
|
so this is unlikely to be useful in a layout definition.
|
|
"""
|
|
item = self.topLevelItem(row) #qt
|
|
item.setIcon(column, _Icons(icon)) #qt
|
|
if self._hcompact:
|
|
self._compact()
|
|
|
|
def x__single_disable(self, row, disable=True):
|
|
"""Disable (or reenable) the given item.
|
|
For the moment only the setting of a single item is supported,
|
|
so this is unlikely to be useful in a layout definition.
|
|
"""
|
|
item = self.topLevelItem(row) #qt
|
|
item.setDisabled(disable) #qt
|
|
|
|
def x__icon(self, row, column, icon):
|
|
"""Set the icon for the given cell.
|
|
<icon> is the name of a standard icon.
|
|
Only the setting of a single icon is supported,
|
|
so this is unlikely to be useful in a layout definition.
|
|
"""
|
|
item = self.topLevelItem(row) #qt
|
|
item.setIcon(column, self.style().standardIcon(icondict[icon])) #qt
|
|
if self._hcompact:
|
|
self._compact()
|
|
|
|
def x__scroll_to(self, row):
|
|
model = self.model() #qt
|
|
self.scrollTo(model.index(row, 0), self.PositionAtTop) #qt
|
|
|
|
|
|
_iconcache = {}
|
|
def _Icons(filepath):
|
|
icon = _iconcache.get(filepath)
|
|
if not icon:
|
|
icon = QtGui.QIcon(filepath) #qt
|
|
return icon
|
|
|
|
|
|
class LineEdit(QtGui.QLineEdit, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QLineEdit.__init__(self) #qt
|
|
|
|
def x__enter(self, name=''):
|
|
guiapp.signal(self, 'enter', name, 'returnPressed()') #qt
|
|
|
|
def x__changed(self, name=''):
|
|
guiapp.signal(self, 'changed', name, 'textEdited(const QString &)') #qt
|
|
|
|
def x__get(self):
|
|
return unicode(self.text()) #qt
|
|
|
|
def x__ro(self, ro):
|
|
self.setReadOnly(ro) #qt
|
|
|
|
def x__pw(self, star):
|
|
self.setEchoMode(QtGui.QLineEdit.Password if star == "+" #qt
|
|
else QtGui.QLineEdit.NoEcho if star == "-" #qt
|
|
else QtGui.QLineEdit.Normal) #qt
|
|
|
|
|
|
class CheckList(QtGui.QWidget, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QWidget.__init__(self) #qt
|
|
self.box = QtGui.QVBoxLayout(self) #qt
|
|
self.title = None
|
|
if text: #qt
|
|
l.addWidget(QtGui.QLabel(text)) #qt
|
|
self.widget = QtGui.QListWidget() #qt
|
|
l.addWidget(self.widget) #qt
|
|
|
|
def x__title(self, text):
|
|
if self.title:
|
|
self.title.setText(text) #qt
|
|
else:
|
|
self.title = QtGui.QLabel(text) #qt
|
|
self.box.insertWidget(0, self.title) #qt
|
|
|
|
def x__checked(self, index):
|
|
return (self.widget.item(index).checkState() == #qt
|
|
QtCore.Qt.Checked) #qt
|
|
|
|
def x__set(self, items):
|
|
self.widget.blockSignals(True) #qt
|
|
self.widget.clear() #qt
|
|
if items:
|
|
for s, c in items:
|
|
wi = QtGui.QListWidgetItem(s, self.widget) #qt
|
|
wi.setCheckState(QtCore.Qt.Checked if c #qt
|
|
else QtCore.Qt.Unchecked) #qt
|
|
self.blockSignals(False) #qt
|
|
|
|
|
|
class TextEdit(QtGui.QTextEdit, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QTextEdit.__init__(self) #qt
|
|
|
|
def x__ro(self, ro):
|
|
self.setReadOnly(ro) #qt
|
|
|
|
def x__append_and_scroll(self, text):
|
|
self.append(text) #qt
|
|
self.ensureCursorVisible() #qt
|
|
|
|
def x__get(self):
|
|
return unicode(self.toPlainText()) #qt
|
|
|
|
def x__undo(self):
|
|
QtGui.QTextEdit.undo(self) #qt
|
|
|
|
def x__redo(self):
|
|
QtGui.QTextEdit.redo(self) #qt
|
|
|
|
def x__copy(self):
|
|
QtGui.QTextEdit.copy(self) #qt
|
|
|
|
def x__cut(self):
|
|
QtGui.QTextEdit.cut(self) #qt
|
|
|
|
def x__paste(self):
|
|
QtGui.QTextEdit.paste(self) #qt
|
|
|
|
|
|
class HtmlView(QtWebKit.QWebView, WBase): #qt
|
|
def __init__(self):
|
|
QtWebKit.QWebView.__init__(self) #qt
|
|
self.settings().setDefaultTextEncoding('utf-8') #qt
|
|
|
|
def x__html(self, content):
|
|
self.setHtml(content) #qt
|
|
|
|
def x__setUrl(self, url):
|
|
self.load(QtCore.QUrl(url)) #qt
|
|
|
|
def x__prev(self):
|
|
self.back() #qt
|
|
|
|
def x__next(self):
|
|
self.forward() #qt
|
|
|
|
|
|
class SpinBox(QtGui.QDoubleSpinBox, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QDoubleSpinBox.__init__(self) #qt
|
|
self.step = None
|
|
|
|
def x__changed(self, name=''):
|
|
guiapp.signal(self, 'changed', name, 'valueChanged(double)') #qt
|
|
|
|
def x__min(self, min):
|
|
self.setMinimum(min)
|
|
|
|
def x__max(self, max):
|
|
self.setMaximum(max)
|
|
|
|
def x__decimals(self, dec):
|
|
self.setDecimals(dec)
|
|
if not self.step:
|
|
self.setSingleStep(10**(-dec))
|
|
|
|
def x__step(self, step):
|
|
self.setSingleStep(step)
|
|
|
|
def x__value(self, val):
|
|
self.setValue(val)
|
|
|
|
|
|
class ProgressBar(QtGui.QProgressBar, WBase): #qt
|
|
def __init__(self):
|
|
QtGui.QProgressBar.__init__(self) #qt
|
|
|
|
def x__set(self, value):
|
|
self.setValue(value) #qt
|
|
|
|
def x__max(self, max):
|
|
self.setMaximum(max) #qt
|
|
|
|
|
|
|
|
# Layout classes
|
|
class Layout:
|
|
"""A mixin base class for all layout widgets.
|
|
"""
|
|
pass
|
|
|
|
boxmargin=3
|
|
class _BOX(Layout):
|
|
def do_layout(self, items):
|
|
self.setContentsMargins(boxmargin, boxmargin, boxmargin, boxmargin) #qt
|
|
for wl in items:
|
|
if isinstance(wl, QtGui.QWidget): #qt
|
|
self.addWidget(wl) #qt
|
|
elif isinstance(wl, SPACE): #qt
|
|
if wl.size: #qt
|
|
self.addSpacing(wl.size) #qt
|
|
self.addStretch() #qt
|
|
elif isinstance(wl, Layout): #qt
|
|
self.addLayout(wl) #qt
|
|
else: #qt
|
|
gui_error("Invalid Box entry: %s" % repr(wl))
|
|
|
|
|
|
class VBOX(QtGui.QVBoxLayout, _BOX): #qt
|
|
def __init__(self):
|
|
QtGui.QVBoxLayout.__init__(self) #qt
|
|
|
|
|
|
class HBOX(QtGui.QHBoxLayout, _BOX): #qt
|
|
def __init__(self):
|
|
QtGui.QHBoxLayout.__init__(self) #qt
|
|
|
|
|
|
class GRID(QtGui.QGridLayout, Layout): #qt
|
|
def __init__(self):
|
|
QtGui.QGridLayout.__init__(self) #qt
|
|
|
|
def do_layout(self, rows):
|
|
y = -1
|
|
for row in rows:
|
|
y += 1
|
|
x = -1
|
|
for wl in row:
|
|
x += 1
|
|
if isinstance(wl, Span):
|
|
continue
|
|
# Determine the row and column spans
|
|
x1 = x + 1
|
|
while (x1 < len(row)) and isinstance(row[x1], CSPAN):
|
|
x1 += 1
|
|
y1 = y + 1
|
|
while (y1 < len(rows)) and isinstance(rows[y1][x], RSPAN):
|
|
y1 += 1
|
|
|
|
if isinstance(wl, QtGui.QWidget): #qt
|
|
self.addWidget(wl, y, x, y1-y, x1-x) #qt
|
|
elif isinstance(wl, Layout):
|
|
self.addLayout(wl, y, x, y1-y, x1-x) #qt
|
|
elif isinstance(wl, SPACE):
|
|
self.addItem(QtGui.QSpacerItem(wl.size, wl.height),
|
|
y, x, y1-y, x1-x) #qt
|
|
else:
|
|
gui_error("Invalid entry in Grid layout: %s" % repr(wl))
|
|
|
|
|
|
class SPACE:
|
|
"""Can be used in boxes and grids. In boxes only size is of interest,
|
|
and it also means vertical size in the case of a vbox. In grids size
|
|
is the width.
|
|
"""
|
|
def __init__(self, size_width='0', height='0'): #qt
|
|
self.size = int(size_width) #qt
|
|
self.height = int(height) #qt
|
|
|
|
|
|
class Span:
|
|
"""Class to group special grid layout objects together - it doesn't
|
|
actually do anything itself, but is used for checking object types.
|
|
"""
|
|
pass
|
|
|
|
|
|
class CSPAN(Span):
|
|
"""Column-span layout item. It doesn't do anything itself, but it is used
|
|
by the Grid layout constructor.
|
|
"""
|
|
pass
|
|
|
|
|
|
class RSPAN(Span):
|
|
"""Row-span layout item. It doesn't do anything itself, but it is used
|
|
by the Grid layout constructor.
|
|
"""
|
|
pass
|
|
|
|
|
|
class HLINE(QtGui.QFrame): #qt
|
|
def __init__(self, pad=None):
|
|
# pass the pad argument thus: 'HLINE,3'
|
|
QtGui.QFrame.__init__(self) #qt
|
|
self.setFrameShape(QtGui.QFrame.HLine) #qt
|
|
if pad:
|
|
self.setFixedHeight(1 + 2*int(pad)) #qt
|
|
|
|
|
|
class VLINE(QtGui.QFrame): #qt
|
|
def __init__(self, pad=None):
|
|
# pass the pad argument thus: 'VLINE,3'
|
|
QtGui.QFrame.__init__(self) #qt
|
|
self.setFrameShape(QtGui.QFrame.VLine) #qt
|
|
if pad:
|
|
self.setFixedWidth(1 + 2*int(pad)) #qt
|
|
|
|
|
|
class DATA:
|
|
"""This is not really a widget, it just holds a dictionary of
|
|
potentially internationalized messages.
|
|
"""
|
|
def x__messages(self, mdict):
|
|
self.messages = mdict
|
|
|
|
def x__get(self, key):
|
|
return self.messages.get(key)
|
|
|
|
|
|
class Suim(QtGui.QApplication):
|
|
"""This class represents an application gui, possibly with more than
|
|
one top level window.
|
|
"""
|
|
timers = [] # timer objects
|
|
|
|
def __init__(self, appname='suim', busywidgets = []):
|
|
global guiapp, T_
|
|
guiapp = self
|
|
QtGui.QApplication.__init__(self, []) #qt
|
|
self.appname = appname
|
|
self.eno = QtCore.QEvent.registerEventType() #qt
|
|
# This overcomplicated looking bit should deal with translating the built in dialogs
|
|
_translator = QtCore.QTranslator(self)
|
|
if (_translator.load('qt_'+ QtCore.QLocale.system().name(),
|
|
QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath))):
|
|
self.installTranslator(_translator)
|
|
|
|
# list of widgets to disable while 'busy'
|
|
self.busywidgets = busywidgets
|
|
|
|
self.busystate = False
|
|
self.busy_lock = threading.Lock()
|
|
|
|
self.setQuitOnLastWindowClosed(False) #qt
|
|
|
|
self.widgets = {} # all widgets, key = widget tag
|
|
self.signal_dict = {} # signal connections, key = signature
|
|
|
|
# callback list for event loop: (callback, arglist) pairs:
|
|
self.idle_calls = deque()
|
|
|
|
|
|
def event(self, e):
|
|
if e.type() == self.eno:
|
|
# Process item from list
|
|
cb, a = self.idle_calls.popleft()
|
|
cb(*a)
|
|
return True
|
|
else:
|
|
return QtGui.QApplication.event(self, e) #qt
|
|
|
|
|
|
def run(self):
|
|
self.exec_() #qt
|
|
|
|
|
|
def quit(self, cc=0):
|
|
# QCoreApplication provides the static function 'exit'
|
|
self.exit(cc) #qt
|
|
|
|
|
|
def addwidget(self, fullname, wo):
|
|
if self.widgets.has_key(fullname):
|
|
gui_error("Attempted to define widget '%s' twice." % fullname)
|
|
self.widgets[fullname] = wo
|
|
|
|
|
|
def getwidget(self, w):
|
|
widget = self.widgets.get(w)
|
|
if widget == None:
|
|
gui_warning("Unknown widget: %s" % w)
|
|
return widget
|
|
|
|
|
|
def show(self, windowname):
|
|
self.getwidget(windowname).setVisible()
|
|
|
|
|
|
def command(self, cmdtext, *args):
|
|
cmd = specials_table.get(cmdtext)
|
|
if not cmd:
|
|
w, m = cmdtext.split(".")
|
|
wo = self.getwidget(w)
|
|
cmd = getattr(wo, 'x__' + m)
|
|
return cmd(*args)
|
|
|
|
|
|
def widget(self, wtype, wname, args):
|
|
wobj = widget_table[wtype]()
|
|
wobj.w_name = wname
|
|
|
|
# Attributes
|
|
for key, val in args.iteritems():
|
|
handler = "x__" + key
|
|
if hasattr(wobj, handler):
|
|
getattr(wobj, handler)(val)
|
|
# Unrecognized attributes are ignored ...
|
|
|
|
self.addwidget(wname, wobj)
|
|
|
|
|
|
def widgetlist(self, wlist):
|
|
for w in wlist:
|
|
# Add simple signals
|
|
for s in w[3:]:
|
|
w[2][s] = ''
|
|
self.widget(w[0], w[1], w[2])
|
|
|
|
|
|
def signal(self, source, signal, name=None, xsignal=None, convert=None):
|
|
"""Enable or disable a signal.
|
|
Signal.signals is a dictionary of enabled signals.
|
|
The key is constructed from the widget name and the formal signal name.
|
|
The name of the signal which actually gets generated will be the
|
|
same as the key unless the 'name' parameter is set. See the
|
|
'Signal' class for further details.
|
|
If 'name' is None (not ''!), the signal will be disabled.
|
|
"""
|
|
widsig = source.w_name + '*' + signal
|
|
if name == None:
|
|
s = Signal.signals.get(widsig)
|
|
if not s:
|
|
gui_error("Can't disable signal '%s' - it's not enabled"
|
|
% widsig)
|
|
s.disconnect() # Probably not necessary in qt
|
|
del(Signal.signals[widsig])
|
|
else:
|
|
if Signal.signals.has_key(widsig):
|
|
gui_error("Signal already connected: %s" % widsig)
|
|
Signal.signals[widsig] = Signal(source, signal, name, xsignal,
|
|
convert)
|
|
|
|
|
|
def connect(self, signal, function):
|
|
if self.signal_dict.has_key(signal):
|
|
self.signal_dict[signal].append(function)
|
|
else:
|
|
self.signal_dict[signal] = [function]
|
|
|
|
|
|
def connectlist(self, *slotlist):
|
|
for s in slotlist:
|
|
self.connect(*s)
|
|
|
|
|
|
def disconnect(self, signal, function):
|
|
try:
|
|
l = self.signal_dict[signal]
|
|
l.remove(function)
|
|
except:
|
|
gui_error("Slot disconnection for signal '%s' failed"
|
|
% signal)
|
|
|
|
|
|
def sendsignal(self, name, *args):
|
|
# When there are no slots a debug message is output.
|
|
slots = self.signal_dict.get(name)
|
|
if slots:
|
|
try:
|
|
for slot in slots:
|
|
slot(*args)
|
|
except:
|
|
gui_error("Signal handling error:\n %s"
|
|
% traceback.format_exc())
|
|
else:
|
|
debug("Unhandled signal: %s %s" % (name, repr(args)))
|
|
|
|
|
|
def idle_add(self, callback, *args):
|
|
self.idle_calls.append((callback, args))
|
|
e = QtCore.QEvent(self.eno) #qt
|
|
self.postEvent(self, e) #qt
|
|
|
|
|
|
def timer(self, callback, period):
|
|
"""Start a timer which calls the callback function on timeout.
|
|
Only if the callback returns True will the timer be retriggered.
|
|
"""
|
|
Suim.timers.append(Timer(callback, period))
|
|
|
|
|
|
def busy(self, on, busycursor=True):
|
|
"""This activates (or deactivates, for on=False) a 'busy' mechanism,
|
|
which can be one or both of the following:
|
|
Make the application's cursor change to the 'busy cursor'.
|
|
Disable a group of widgets.
|
|
There is a lock to prevent the busy state from being set when it
|
|
is already active, or unset it when already unset.
|
|
"""
|
|
self.busy_lock.acquire()
|
|
if on:
|
|
if self.busystate:
|
|
self.busy_lock.release()
|
|
return
|
|
self.busycursor = busycursor
|
|
if busycursor:
|
|
self.setOverrideCursor(QtCore.Qt.BusyCursor) #qt
|
|
else:
|
|
if not self.busystate:
|
|
self.busy_lock.release()
|
|
return
|
|
if self.busycursor:
|
|
self.restoreOverrideCursor() #qt
|
|
self.busystate = on
|
|
self.busy_lock.release()
|
|
for wn in self.busywidgets:
|
|
w = self.getwidget(wn)
|
|
if w:
|
|
w.setEnabled(not on) #qt
|
|
else:
|
|
debug("*ERROR* No widget '%s'" % wn)
|
|
|
|
|
|
class Timer(QtCore.QTimer): #qt
|
|
def __init__(self, timers, callback, period):
|
|
QtCore.QTimer.__init__(self) #qt
|
|
self.x_callback = callback
|
|
self.connect(self, QtCore.SIGNAL("timeout()"), #qt
|
|
self.x_timeout)
|
|
self.start(int(period * 1000)) #qt
|
|
|
|
def x_timeout(self):
|
|
if not self.x_callback():
|
|
self.stop() #qt
|
|
Suim.timers.remove(self)
|
|
|
|
|
|
|
|
class Signal:
|
|
"""Each instance represents a single connection.
|
|
"""
|
|
signals = {} # Enabled signals
|
|
|
|
def __init__(self, source, signal, name, xsignal, convert):
|
|
"""'source' is the widget object which initiates the signal.
|
|
'signal' is the signal name.
|
|
If 'name' is given (not empty), the signal will get this as its name,
|
|
and this name may be used for more than one connection.
|
|
Otherwise the name is built from the name of the source widget and
|
|
the signal type as 'source*signal' and this is unique.
|
|
If 'name' begins with '+' an additional argument, the source
|
|
widget name, will be inserted at the head of the argument list.
|
|
'xsignal' is a toolkit specific signal descriptor.
|
|
'convert' is an optional function (default None) - toolkit specific -
|
|
to perform signal argument conversions.
|
|
"""
|
|
self.widsig = '%s*%s' % (source.w_name, signal)
|
|
#+ For disconnect?
|
|
self.xsignal = xsignal
|
|
#-
|
|
self.convert = convert # Argument conversion function
|
|
self.tag = name if name else self.widsig
|
|
self.wname = source.w_name if self.tag[0] == '+' else None
|
|
if not source.connect(source, QtCore.SIGNAL(xsignal), #qt
|
|
self.signal):
|
|
gui_error("Couldn't enable signal '%s'" % self.widsig)
|
|
|
|
def signal(self, *args):
|
|
if self.convert:
|
|
args = self.convert(*args)
|
|
if self.wname:
|
|
guiapp.sendsignal(self.tag, self.wname, *args)
|
|
else:
|
|
guiapp.sendsignal(self.tag, *args)
|
|
|
|
def disconnect(self):
|
|
w = guiapp.getwidget(self.widsig.split('*')[0])
|
|
w.disconnect(w, QtCore.SIGNAL(self.xsignal), self.signal) #qt
|
|
|
|
|
|
|
|
#+++++++++++++++++++++++++++
|
|
# Catch all unhandled errors.
|
|
def errorTrap(type, value, tb):
|
|
etext = "".join(traceback.format_exception(type, value, tb))
|
|
debug(etext)
|
|
gui_error(etext, "This error could not be handled.")
|
|
|
|
sys.excepthook = errorTrap
|
|
#---------------------------
|
|
|
|
widget_table = {
|
|
"DATA": DATA,
|
|
"Window": Window,
|
|
"Dialog": Dialog,
|
|
"DialogButtons": DialogButtons,
|
|
"Notebook": Notebook,
|
|
"Stack": Stack,
|
|
"Page": Page,
|
|
"Frame": Frame,
|
|
"Button": Button,
|
|
"ToggleButton": ToggleButton,
|
|
"RadioButton": RadioButton,
|
|
"CheckBox": CheckBox,
|
|
"Label": Label,
|
|
"CheckList": CheckList,
|
|
"List": List,
|
|
"OptionalFrame": OptionalFrame,
|
|
"ComboBox": ComboBox,
|
|
"ListChoice": ListChoice,
|
|
"LineEdit": LineEdit,
|
|
"TextEdit": TextEdit,
|
|
"HtmlView": HtmlView,
|
|
"SpinBox": SpinBox,
|
|
"ProgressBar": ProgressBar,
|
|
}
|
|
|
|
specials_table = {
|
|
"textLineDialog": textLineDialog,
|
|
"infoDialog": infoDialog,
|
|
"confirmDialog": confirmDialog,
|
|
"errorDialog": gui_error,
|
|
"warningDialog": gui_warning,
|
|
"fileDialog_getdir": fileDialog_getdir,
|
|
"fileDialog_open": fileDialog_open,
|
|
"fileDialog_save": fileDialog_save,
|
|
"listDialog": listDialog,
|
|
}
|
|
|
|
layout_table = {
|
|
"VBOX": VBOX,
|
|
"HBOX": HBOX,
|
|
"GRID": GRID,
|
|
# "+": GRIDROW,
|
|
"-": CSPAN,
|
|
"|": RSPAN,
|
|
"*": SPACE,
|
|
"VLINE": VLINE,
|
|
"HLINE": HLINE,
|
|
}
|
|
|