6 from PyQt4 import QtCore
7 from PyQt4 import QtGui
12 _moduleLogger = logging.getLogger(__name__)
15 @contextlib.contextmanager
16 def notify_error(log):
23 @contextlib.contextmanager
24 def notify_busy(log, message):
25 log.push_busy(message)
32 class ErrorMessage(object):
38 def __init__(self, message, level):
39 self._message = message
41 self._time = datetime.datetime.now()
52 return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level)
55 class QErrorLog(QtCore.QObject):
57 messagePushed = QtCore.pyqtSignal()
58 messagePopped = QtCore.pyqtSignal()
61 QtCore.QObject.__init__(self)
64 def push_busy(self, message):
65 _moduleLogger.info("Entering state: %s" % message)
66 self._push_message(message, ErrorMessage.LEVEL_BUSY)
68 def push_message(self, message):
69 self._push_message(message, ErrorMessage.LEVEL_INFO)
71 def push_error(self, message):
72 self._push_message(message, ErrorMessage.LEVEL_ERROR)
74 def push_exception(self):
75 userMessage = str(sys.exc_info()[1])
76 _moduleLogger.exception(userMessage)
77 self.push_error(userMessage)
79 def pop(self, message = None):
83 _moduleLogger.info("Exiting state: %s" % message)
86 for (i, error) in enumerate(self._messages)
87 if error.message == message
89 # Might be removed out of order
91 del self._messages[messageIndex[0]]
92 self.messagePopped.emit()
94 def peek_message(self):
95 return self._messages[0]
97 def _push_message(self, message, level):
98 self._messages.append(ErrorMessage(message, level))
99 # Sort is defined as stable, so this should be fine
100 self._messages.sort(key=lambda x: x.level)
101 self.messagePushed.emit()
104 return len(self._messages)
107 class ErrorDisplay(object):
109 _SENTINEL_ICON = QtGui.QIcon()
111 def __init__(self, errorLog):
112 self._errorLog = errorLog
113 self._errorLog.messagePushed.connect(self._on_message_pushed)
114 self._errorLog.messagePopped.connect(self._on_message_popped)
117 self._severityLabel = QtGui.QLabel()
118 self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
120 self._message = QtGui.QLabel()
121 self._message.setText("Boo")
122 self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
123 self._message.setWordWrap(True)
125 self._closeLabel = None
127 self._controlLayout = QtGui.QHBoxLayout()
128 self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter)
129 self._controlLayout.addWidget(self._message, 1000)
131 self._widget = QtGui.QWidget()
132 self._widget.setLayout(self._controlLayout)
139 def _show_error(self):
140 if self._icons is None:
142 ErrorMessage.LEVEL_BUSY:
144 #("process-working", "view-refresh", "general_refresh", "gtk-refresh")
145 ("view-refresh", "general_refresh", "gtk-refresh", )
147 ErrorMessage.LEVEL_INFO:
149 ("dialog-information", "general_notes", "gtk-info")
151 ErrorMessage.LEVEL_ERROR:
153 ("dialog-error", "app_install_error", "gtk-dialog-error")
156 if self._closeLabel is None:
157 closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON)
158 if closeIcon is not self._SENTINEL_ICON:
159 self._closeLabel = QtGui.QPushButton(closeIcon, "")
161 self._closeLabel = QtGui.QPushButton("X")
162 self._closeLabel.clicked.connect(self._on_close)
163 self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter)
164 error = self._errorLog.peek_message()
165 self._message.setText(error.message)
166 self._severityLabel.setPixmap(self._icons[error.level])
170 @QtCore.pyqtSlot(bool)
171 @misc.log_exception(_moduleLogger)
172 def _on_close(self, checked = False):
176 @misc.log_exception(_moduleLogger)
177 def _on_message_pushed(self):
181 @misc.log_exception(_moduleLogger)
182 def _on_message_popped(self):
183 if len(self._errorLog) == 0:
184 self._message.setText("")
190 class QHtmlDelegate(QtGui.QStyledItemDelegate):
194 def __init__(self, *args, **kwd):
195 QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd)
196 self._width = self.UNDEFINED_SIZE
198 def paint(self, painter, option, index):
199 newOption = QtGui.QStyleOptionViewItemV4(option)
200 self.initStyleOption(newOption, index)
201 if newOption.widget is not None:
202 style = newOption.widget.style()
204 style = QtGui.QApplication.style()
206 doc = QtGui.QTextDocument()
207 doc.setHtml(newOption.text)
208 doc.setTextWidth(newOption.rect.width())
211 style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
213 ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
214 if newOption.state & QtGui.QStyle.State_Selected:
215 ctx.palette.setColor(
217 newOption.palette.color(
218 QtGui.QPalette.Active,
219 QtGui.QPalette.HighlightedText
223 ctx.palette.setColor(
225 newOption.palette.color(
226 QtGui.QPalette.Active,
231 textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
233 painter.translate(textRect.topLeft())
234 painter.setClipRect(textRect.translated(-textRect.topLeft()))
235 doc.documentLayout().draw(painter, ctx)
238 def setWidth(self, width, model):
239 if self._width == width:
242 for c in xrange(model.rowCount()):
243 cItem = model.item(c, 0)
244 for r in xrange(model.rowCount()):
245 rItem = cItem.child(r, 0)
246 rIndex = model.indexFromItem(rItem)
247 self.sizeHintChanged.emit(rIndex)
250 def sizeHint(self, option, index):
251 newOption = QtGui.QStyleOptionViewItemV4(option)
252 self.initStyleOption(newOption, index)
254 doc = QtGui.QTextDocument()
255 doc.setHtml(newOption.text)
256 if self._width != self.UNDEFINED_SIZE:
259 width = newOption.rect.width()
260 doc.setTextWidth(width)
261 size = QtCore.QSize(doc.idealWidth(), doc.size().height())
265 class QSignalingMainWindow(QtGui.QMainWindow):
267 closed = QtCore.pyqtSignal()
268 hidden = QtCore.pyqtSignal()
269 shown = QtCore.pyqtSignal()
270 resized = QtCore.pyqtSignal()
272 def __init__(self, *args, **kwd):
273 QtGui.QMainWindow.__init__(*((self, )+args), **kwd)
275 def closeEvent(self, event):
276 val = QtGui.QMainWindow.closeEvent(self, event)
280 def hideEvent(self, event):
281 val = QtGui.QMainWindow.hideEvent(self, event)
285 def showEvent(self, event):
286 val = QtGui.QMainWindow.showEvent(self, event)
290 def resizeEvent(self, event):
291 val = QtGui.QMainWindow.resizeEvent(self, event)
296 def _null_set_stackable(window, isStackable):
300 def _maemo_set_stackable(window, isStackable):
301 window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
305 QtCore.Qt.WA_Maemo5StackedWindow
306 set_stackable = _maemo_set_stackable
307 except AttributeError:
308 set_stackable = _null_set_stackable
311 def _null_set_autorient(window, isStackable):
315 def _maemo_set_autorient(window, isStackable):
316 window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, isStackable)
320 QtCore.Qt.WA_Maemo5AutoOrientation
321 set_autorient = _maemo_set_autorient
322 except AttributeError:
323 set_autorient = _null_set_autorient
326 def screen_orientation():
327 geom = QtGui.QApplication.desktop().screenGeometry()
328 if geom.width() <= geom.height():
329 return QtCore.Qt.Vertical
331 return QtCore.Qt.Horizontal
334 def _null_set_window_orientation(window, orientation):
338 def _maemo_set_window_orientation(window, orientation):
339 if orientation == QtCore.Qt.Vertical:
340 oldHint = QtCore.Qt.WA_Maemo5LandscapeOrientation
341 newHint = QtCore.Qt.WA_Maemo5PortraitOrientation
342 elif orientation == QtCore.Qt.Horizontal:
343 oldHint = QtCore.Qt.WA_Maemo5PortraitOrientation
344 newHint = QtCore.Qt.WA_Maemo5LandscapeOrientation
345 window.setAttribute(oldHint, False)
346 window.setAttribute(newHint, True)
350 QtCore.Qt.WA_Maemo5LandscapeOrientation
351 QtCore.Qt.WA_Maemo5PortraitOrientation
352 set_window_orientation = _maemo_set_window_orientation
353 except AttributeError:
354 set_window_orientation = _null_set_window_orientation
357 def _null_show_progress_indicator(window, isStackable):
361 def _maemo_show_progress_indicator(window, isStackable):
362 window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable)
366 QtCore.Qt.WA_Maemo5ShowProgressIndicator
367 show_progress_indicator = _maemo_show_progress_indicator
368 except AttributeError:
369 show_progress_indicator = _null_show_progress_indicator
372 def _null_mark_numbers_preferred(widget):
376 def _newqt_mark_numbers_preferred(widget):
377 widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
381 QtCore.Qt.ImhPreferNumbers
382 mark_numbers_preferred = _newqt_mark_numbers_preferred
383 except AttributeError:
384 mark_numbers_preferred = _null_mark_numbers_preferred
387 def _null_get_theme_icon(iconNames, fallback = None):
388 icon = fallback if fallback is not None else QtGui.QIcon()
392 def _newqt_get_theme_icon(iconNames, fallback = None):
393 for iconName in iconNames:
394 if QtGui.QIcon.hasThemeIcon(iconName):
395 icon = QtGui.QIcon.fromTheme(iconName)
398 icon = fallback if fallback is not None else QtGui.QIcon()
403 QtGui.QIcon.fromTheme
404 get_theme_icon = _newqt_get_theme_icon
405 except AttributeError:
406 get_theme_icon = _null_get_theme_icon