First pass at switching to distutils
[gonvert] / gonvert / util / qui_utils.py
diff --git a/gonvert/util/qui_utils.py b/gonvert/util/qui_utils.py
new file mode 100644 (file)
index 0000000..11b3453
--- /dev/null
@@ -0,0 +1,419 @@
+import sys
+import contextlib
+import datetime
+import logging
+
+import qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def notify_error(log):
+       try:
+               yield
+       except:
+               log.push_exception()
+
+
+@contextlib.contextmanager
+def notify_busy(log, message):
+       log.push_busy(message)
+       try:
+               yield
+       finally:
+               log.pop(message)
+
+
+class ErrorMessage(object):
+
+       LEVEL_ERROR = 0
+       LEVEL_BUSY = 1
+       LEVEL_INFO = 2
+
+       def __init__(self, message, level):
+               self._message = message
+               self._level = level
+               self._time = datetime.datetime.now()
+
+       @property
+       def level(self):
+               return self._level
+
+       @property
+       def message(self):
+               return self._message
+
+       def __repr__(self):
+               return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level)
+
+
+class QErrorLog(QtCore.QObject):
+
+       messagePushed = qt_compat.Signal()
+       messagePopped = qt_compat.Signal()
+
+       def __init__(self):
+               QtCore.QObject.__init__(self)
+               self._messages = []
+
+       def push_busy(self, message):
+               _moduleLogger.info("Entering state: %s" % message)
+               self._push_message(message, ErrorMessage.LEVEL_BUSY)
+
+       def push_message(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_INFO)
+
+       def push_error(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_ERROR)
+
+       def push_exception(self):
+               userMessage = str(sys.exc_info()[1])
+               _moduleLogger.exception(userMessage)
+               self.push_error(userMessage)
+
+       def pop(self, message = None):
+               if message is None:
+                       del self._messages[0]
+               else:
+                       _moduleLogger.info("Exiting state: %s" % message)
+                       messageIndex = [
+                               i
+                               for (i, error) in enumerate(self._messages)
+                               if error.message == message
+                       ]
+                       # Might be removed out of order
+                       if messageIndex:
+                               del self._messages[messageIndex[0]]
+               self.messagePopped.emit()
+
+       def peek_message(self):
+               return self._messages[0]
+
+       def _push_message(self, message, level):
+               self._messages.append(ErrorMessage(message, level))
+               # Sort is defined as stable, so this should be fine
+               self._messages.sort(key=lambda x: x.level)
+               self.messagePushed.emit()
+
+       def __len__(self):
+               return len(self._messages)
+
+
+class ErrorDisplay(object):
+
+       _SENTINEL_ICON = QtGui.QIcon()
+
+       def __init__(self, errorLog):
+               self._errorLog = errorLog
+               self._errorLog.messagePushed.connect(self._on_message_pushed)
+               self._errorLog.messagePopped.connect(self._on_message_popped)
+
+               self._icons = None
+               self._severityLabel = QtGui.QLabel()
+               self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+               self._message = QtGui.QLabel()
+               self._message.setText("Boo")
+               self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+               self._message.setWordWrap(True)
+
+               self._closeLabel = None
+
+               self._controlLayout = QtGui.QHBoxLayout()
+               self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter)
+               self._controlLayout.addWidget(self._message, 1000)
+
+               self._widget = QtGui.QWidget()
+               self._widget.setLayout(self._controlLayout)
+               self._widget.hide()
+
+       @property
+       def toplevel(self):
+               return self._widget
+
+       def _show_error(self):
+               if self._icons is None:
+                       self._icons = {
+                               ErrorMessage.LEVEL_BUSY:
+                                       get_theme_icon(
+                                               #("process-working", "view-refresh", "general_refresh", "gtk-refresh")
+                                               ("view-refresh", "general_refresh", "gtk-refresh", )
+                                       ).pixmap(32, 32),
+                               ErrorMessage.LEVEL_INFO:
+                                       get_theme_icon(
+                                               ("dialog-information", "general_notes", "gtk-info")
+                                       ).pixmap(32, 32),
+                               ErrorMessage.LEVEL_ERROR:
+                                       get_theme_icon(
+                                               ("dialog-error", "app_install_error", "gtk-dialog-error")
+                                       ).pixmap(32, 32),
+                       }
+               if self._closeLabel is None:
+                       closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON)
+                       if closeIcon is not self._SENTINEL_ICON:
+                               self._closeLabel = QtGui.QPushButton(closeIcon, "")
+                       else:
+                               self._closeLabel = QtGui.QPushButton("X")
+                       self._closeLabel.clicked.connect(self._on_close)
+                       self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter)
+               error = self._errorLog.peek_message()
+               self._message.setText(error.message)
+               self._severityLabel.setPixmap(self._icons[error.level])
+               self._widget.show()
+
+       @qt_compat.Slot()
+       @qt_compat.Slot(bool)
+       @misc.log_exception(_moduleLogger)
+       def _on_close(self, checked = False):
+               self._errorLog.pop()
+
+       @qt_compat.Slot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_pushed(self):
+               self._show_error()
+
+       @qt_compat.Slot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_popped(self):
+               if len(self._errorLog) == 0:
+                       self._message.setText("")
+                       self._widget.hide()
+               else:
+                       self._show_error()
+
+
+class QHtmlDelegate(QtGui.QStyledItemDelegate):
+
+       UNDEFINED_SIZE = -1
+
+       def __init__(self, *args, **kwd):
+               QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd)
+               self._width = self.UNDEFINED_SIZE
+
+       def paint(self, painter, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+               if newOption.widget is not None:
+                       style = newOption.widget.style()
+               else:
+                       style = QtGui.QApplication.style()
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               doc.setTextWidth(newOption.rect.width())
+
+               newOption.text = ""
+               style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
+
+               ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
+               if newOption.state & QtGui.QStyle.State_Selected:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.HighlightedText
+                               )
+                       )
+               else:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.Text
+                               )
+                       )
+
+               textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
+               painter.save()
+               painter.translate(textRect.topLeft())
+               painter.setClipRect(textRect.translated(-textRect.topLeft()))
+               doc.documentLayout().draw(painter, ctx)
+               painter.restore()
+
+       def setWidth(self, width, model):
+               if self._width == width:
+                       return
+               self._width = width
+               for c in xrange(model.rowCount()):
+                       cItem = model.item(c, 0)
+                       for r in xrange(model.rowCount()):
+                               rItem = cItem.child(r, 0)
+                               rIndex = model.indexFromItem(rItem)
+                               self.sizeHintChanged.emit(rIndex)
+                               return
+
+       def sizeHint(self, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               if self._width != self.UNDEFINED_SIZE:
+                       width = self._width
+               else:
+                       width = newOption.rect.width()
+               doc.setTextWidth(width)
+               size = QtCore.QSize(doc.idealWidth(), doc.size().height())
+               return size
+
+
+class QSignalingMainWindow(QtGui.QMainWindow):
+
+       closed = qt_compat.Signal()
+       hidden = qt_compat.Signal()
+       shown = qt_compat.Signal()
+       resized = qt_compat.Signal()
+
+       def __init__(self, *args, **kwd):
+               QtGui.QMainWindow.__init__(*((self, )+args), **kwd)
+
+       def closeEvent(self, event):
+               val = QtGui.QMainWindow.closeEvent(self, event)
+               self.closed.emit()
+               return val
+
+       def hideEvent(self, event):
+               val = QtGui.QMainWindow.hideEvent(self, event)
+               self.hidden.emit()
+               return val
+
+       def showEvent(self, event):
+               val = QtGui.QMainWindow.showEvent(self, event)
+               self.shown.emit()
+               return val
+
+       def resizeEvent(self, event):
+               val = QtGui.QMainWindow.resizeEvent(self, event)
+               self.resized.emit()
+               return val
+
+def set_current_index(selector, itemText, default = 0):
+       for i in xrange(selector.count()):
+               if selector.itemText(i) == itemText:
+                       selector.setCurrentIndex(i)
+                       break
+       else:
+               itemText.setCurrentIndex(default)
+
+
+def _null_set_stackable(window, isStackable):
+       pass
+
+
+def _maemo_set_stackable(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5StackedWindow
+       set_stackable = _maemo_set_stackable
+except AttributeError:
+       set_stackable = _null_set_stackable
+
+
+def _null_set_autorient(window, doAutoOrient):
+       pass
+
+
+def _maemo_set_autorient(window, doAutoOrient):
+       window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, doAutoOrient)
+
+
+try:
+       QtCore.Qt.WA_Maemo5AutoOrientation
+       set_autorient = _maemo_set_autorient
+except AttributeError:
+       set_autorient = _null_set_autorient
+
+
+def screen_orientation():
+       geom = QtGui.QApplication.desktop().screenGeometry()
+       if geom.width() <= geom.height():
+               return QtCore.Qt.Vertical
+       else:
+               return QtCore.Qt.Horizontal
+
+
+def _null_set_window_orientation(window, orientation):
+       pass
+
+
+def _maemo_set_window_orientation(window, orientation):
+       if orientation == QtCore.Qt.Vertical:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, True)
+       elif orientation == QtCore.Qt.Horizontal:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, True)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
+       elif orientation is None:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
+       else:
+               raise RuntimeError("Unknown orientation: %r" % orientation)
+
+
+try:
+       QtCore.Qt.WA_Maemo5LandscapeOrientation
+       QtCore.Qt.WA_Maemo5PortraitOrientation
+       set_window_orientation = _maemo_set_window_orientation
+except AttributeError:
+       set_window_orientation = _null_set_window_orientation
+
+
+def _null_show_progress_indicator(window, isStackable):
+       pass
+
+
+def _maemo_show_progress_indicator(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5ShowProgressIndicator
+       show_progress_indicator = _maemo_show_progress_indicator
+except AttributeError:
+       show_progress_indicator = _null_show_progress_indicator
+
+
+def _null_mark_numbers_preferred(widget):
+       pass
+
+
+def _newqt_mark_numbers_preferred(widget):
+       widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
+
+
+try:
+       QtCore.Qt.ImhPreferNumbers
+       mark_numbers_preferred = _newqt_mark_numbers_preferred
+except AttributeError:
+       mark_numbers_preferred = _null_mark_numbers_preferred
+
+
+def _null_get_theme_icon(iconNames, fallback = None):
+       icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+def _newqt_get_theme_icon(iconNames, fallback = None):
+       for iconName in iconNames:
+               if QtGui.QIcon.hasThemeIcon(iconName):
+                       icon = QtGui.QIcon.fromTheme(iconName)
+                       break
+       else:
+               icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+try:
+       QtGui.QIcon.fromTheme
+       get_theme_icon = _newqt_get_theme_icon
+except AttributeError:
+       get_theme_icon = _null_get_theme_icon
+