diff options
author | James Meyer <james.meyer@operamail.com> | 2010-11-04 18:06:25 (GMT) |
---|---|---|
committer | James Meyer <james.meyer@operamail.com> | 2010-11-04 18:06:27 (GMT) |
commit | 429f14eea9f4c00ddd8d82f25612213df8d4af84 (patch) | |
tree | d8441dd4ef5ef539c0b263b138d36fb1995c38d8 /build_tools/larch7/larch0/gui/front/uim.py | |
parent | 11ef4af01d6e197a54d0759e688ab5cbd336be4b (diff) | |
download | linhes_dev-429f14eea9f4c00ddd8d82f25612213df8d4af84.zip |
add profiles for larch 7
Diffstat (limited to 'build_tools/larch7/larch0/gui/front/uim.py')
-rw-r--r-- | build_tools/larch7/larch0/gui/front/uim.py | 1327 |
1 files changed, 1327 insertions, 0 deletions
diff --git a/build_tools/larch7/larch0/gui/front/uim.py b/build_tools/larch7/larch0/gui/front/uim.py new file mode 100644 index 0000000..71e106b --- /dev/null +++ b/build_tools/larch7/larch0/gui/front/uim.py @@ -0,0 +1,1327 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# uim.py +# +# (c) Copyright 2010 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 +# +#---------------------------------------------------------------------------- +# 2010.18.07 + +#TODO? +# 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. + + +"""UIM - 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 +#try: +# import json +#except: +# import simplejson as json + +#++++++++++++++++++++++++++++++++++++++++++++++++++++ +#TODO +# Add more widgets +# Add more attribute handling +# Add more signal handling + +#---------------------------------------------------- + +def debug(text): + sys.stderr.write("GUI: %s\n" % text) + sys.stderr.flush() + + +# 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(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, + } + +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): + 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. It also supports + a 'busy' mechanism. + """ + def __init__(self): + QtGui.QWidget.__init__(self) #qt + self.closesignal = "" + self.busystate = False + self.busy_lock = threading.Lock() + + 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 + + def x__busy(self, widgets, 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. + """ + # I couldn't get the following calls to work: + # w.setCursor(QtCore.Qt.BusyCursor) + # w.unsetCursor() + self.busy_lock.acquire() + if on: + if self.busystate: + debug("*ERROR* Attempt to set busy state twice") + self.busy_lock.release() + return + self.busycursor = busycursor + if busycursor: + guiapp.setOverrideCursor(QtCore.Qt.BusyCursor) #qt + else: + if not self.busystate: + debug("*ERROR* Attempt to release busy state twice") + self.busy_lock.release() + return + if self.busycursor: + guiapp.restoreOverrideCursor() #qt + self.busystate = on + self.busy_lock.release() + for wn in widgets: + w = guiapp.getwidget(wn) + if w: + w.setEnabled(not on) #qt + else: + debug("*ERROR* No widget '%s'" % wn) + + +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=None, title=None, text="", pw=False): + if label == None: + label = "Enter the value here:" + if title == None: + title = "Enter Information" + if pw: + echo = QtGui.QLineEdit.Password #qt + else: + echo = QtGui.QLineEdit.Normal #qt + result, ok = QtGui.QInputDialog.getText(None, #qt + title, label, echo, text) #qt + return (ok, unicode(result)) + + +def listDialog(label=None, title=None, items=[], current=0): + if label == None: + label = "Choose one of the entries" + if title == None: + title = "Select an item" + item, ok = QtGui.QInputDialog.getItem(None, title, label, items, + current, editable=False) #qt + return (ok, unicode(item)) + + +def confirmDialog(message, title=None): + if title == None: + title = "Confirmation" + return (QtGui.QMessageBox.question(None, title, message, #qt + QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) == #qt + QtGui.QMessageBox.Yes) #qt + + +def infoDialog(message, title=None): + if title == None: + title = "Information" + QtGui.QMessageBox.information(None, title, message) #qt + + +#+++++++++++++++++++++++++++ +# Error handling +def gui_error(message, title=None): + if title == None: + title = "Error" + QtGui.QMessageBox.critical(None, title, message) #qt + guiapp.exit(1) #qt + +def gui_warning(message, title=None): + if title == None: + title = "Warning" + QtGui.QMessageBox.warning(None, title, message) #qt + +def onexcept(text): + debug(traceback.format_exc()) + gui_error(text, "Exception") +#--------------------------- + +fileDialogDir = "/" +def fileDialog(message, start=None, title=None, dir=False, create=False, + file=None, urls=None, filter=None): + # filter is a list: first a textual description, then acceptable glob filenames + global fileDialogDir + if not start: + start = fileDialogDir + dlg = QtGui.QFileDialog(None, message, start) #qt + if title: + dlg.setWindowTitle(title) #qt + dlg.setReadOnly(not create) #qt + if dir: + dlg.setFileMode(dlg.Directory) #qt + elif not create: + dlg.setFileMode(dlg.ExistingFile) #qt + if filter: + dlg.setNameFilter("%s (%s)" % (filter[0], " ".join(filter[1:]))) #qt + + if urls: + urlsqt = [ QtCore.QUrl.fromLocalFile(u[0]) for u in urls ] #qt + urlsqt.append(QtCore.QUrl.fromLocalFile( + QtGui.QDesktopServices.storageLocation( + QtGui.QDesktopServices.HomeLocation))) + + dlg.setSidebarUrls(urlsqt) #qt + + if file: + dlg.selectFile(file) + + if dlg.exec_(): + path = str(dlg.selectedFiles()[0]).strip() + if os.path.isdir(path): + fileDialogDir = path + elif os.path.isfile(path): + fileDialogDir = os.path.dirname(path) + return path + else: + return "" + + +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) + + +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 + + +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 + + 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): + 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): + 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 Uim(QtGui.QApplication): + """This class represents an application gui, possibly with more than + one top level window. + """ + timers = [] # timer objects + + def __init__(self): + global guiapp + guiapp = self + self.eno = QtCore.QEvent.registerEventType() #qt + QtGui.QApplication.__init__(self, []) #qt + 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): +# self.quit() #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. + """ + Uim.timers.append(Timer(callback, period)) + + +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 + Uim.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": fileDialog, + "listDialog": listDialog, +} + +layout_table = { + "VBOX": VBOX, + "HBOX": HBOX, + "GRID": GRID, +# "+": GRIDROW, + "-": CSPAN, + "|": RSPAN, + "*": SPACE, + "VLINE": VLINE, + "HLINE": HLINE, +} + |