Applying some feedback from timeless to show counts that the user cares about (for...
[gc-dialer] / src / util / qui_utils.py
1 import sys
2 import logging
3
4 from PyQt4 import QtCore
5 from PyQt4 import QtGui
6
7 import misc
8
9
10 _moduleLogger = logging.getLogger(__name__)
11
12
13 class QErrorLog(QtCore.QObject):
14
15         messagePushed = QtCore.pyqtSignal()
16         messagePopped = QtCore.pyqtSignal()
17
18         def __init__(self):
19                 QtCore.QObject.__init__(self)
20                 self._messages = []
21
22         def push_message(self, message):
23                 self._messages.append(message)
24                 self.messagePushed.emit()
25
26         def push_exception(self):
27                 userMessage = str(sys.exc_info()[1])
28                 _moduleLogger.exception(userMessage)
29                 self.push_message(userMessage)
30
31         def pop_message(self):
32                 del self._messages[0]
33                 self.messagePopped.emit()
34
35         def peek_message(self):
36                 return self._messages[0]
37
38         def __len__(self):
39                 return len(self._messages)
40
41
42 class ErrorDisplay(object):
43
44         def __init__(self, errorLog):
45                 self._errorLog = errorLog
46                 self._errorLog.messagePushed.connect(self._on_message_pushed)
47                 self._errorLog.messagePopped.connect(self._on_message_popped)
48
49                 errorIcon = get_theme_icon(("dialog-error", "app_install_error", "gtk-dialog-error"))
50                 self._severityIcon = errorIcon.pixmap(32, 32)
51                 self._severityLabel = QtGui.QLabel()
52                 self._severityLabel.setPixmap(self._severityIcon)
53
54                 self._message = QtGui.QLabel()
55                 self._message.setText("Boo")
56
57                 closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"))
58                 self._closeLabel = QtGui.QPushButton(closeIcon, "")
59                 self._closeLabel.clicked.connect(self._on_close)
60
61                 self._controlLayout = QtGui.QHBoxLayout()
62                 self._controlLayout.addWidget(self._severityLabel)
63                 self._controlLayout.addWidget(self._message)
64                 self._controlLayout.addWidget(self._closeLabel)
65
66                 self._topLevelLayout = QtGui.QHBoxLayout()
67                 self._topLevelLayout.addLayout(self._controlLayout)
68                 self._widget = QtGui.QWidget()
69                 self._widget.setLayout(self._topLevelLayout)
70                 self._widget.hide()
71
72         @property
73         def toplevel(self):
74                 return self._widget
75
76         @QtCore.pyqtSlot()
77         @QtCore.pyqtSlot(bool)
78         @misc.log_exception(_moduleLogger)
79         def _on_close(self, checked = False):
80                 self._errorLog.pop_message()
81
82         @QtCore.pyqtSlot()
83         @misc.log_exception(_moduleLogger)
84         def _on_message_pushed(self):
85                 if 1 <= len(self._errorLog) and self._widget.isHidden():
86                         self._message.setText(self._errorLog.peek_message())
87                         self._widget.show()
88
89         @QtCore.pyqtSlot()
90         @misc.log_exception(_moduleLogger)
91         def _on_message_popped(self):
92                 if len(self._errorLog) == 0:
93                         self._message.setText("")
94                         self._widget.hide()
95                 else:
96                         self._message.setText(self._errorLog.peek_message())
97
98
99 class QHtmlDelegate(QtGui.QStyledItemDelegate):
100
101         # @bug Doesn't show properly with dark themes (Maemo)
102         # @bug Not showing all of a message
103
104         def paint(self, painter, option, index):
105                 newOption = QtGui.QStyleOptionViewItemV4(option)
106                 self.initStyleOption(newOption, index)
107
108                 doc = QtGui.QTextDocument()
109                 doc.setHtml(newOption.text)
110                 doc.setTextWidth(newOption.rect.width())
111
112                 if newOption.widget is not None:
113                         style = newOption.widget.style()
114                 else:
115                         style = QtGui.QApplication.style()
116
117                 newOption.text = ""
118                 style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
119
120                 ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
121                 if newOption.state & QtGui.QStyle.State_Selected:
122                         ctx.palette.setColor(
123                                 QtGui.QPalette.Text,
124                                 newOption.palette.color(
125                                         QtGui.QPalette.Active,
126                                         QtGui.QPalette.HighlightedText
127                                 )
128                         )
129
130                 textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
131                 painter.save()
132                 painter.translate(textRect.topLeft())
133                 painter.setClipRect(textRect.translated(-textRect.topLeft()))
134                 doc.documentLayout().draw(painter, ctx)
135                 painter.restore()
136
137         def sizeHint(self, option, index):
138                 newOption = QtGui.QStyleOptionViewItemV4(option)
139                 self.initStyleOption(newOption, index)
140
141                 doc = QtGui.QTextDocument()
142                 doc.setHtml(newOption.text)
143                 doc.setTextWidth(newOption.rect.width())
144                 size = QtCore.QSize(doc.idealWidth(), doc.size().height())
145                 return size
146
147
148 def _null_set_stackable(window, isStackable):
149         pass
150
151
152 def _maemo_set_stackable(window, isStackable):
153         window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
154
155
156 try:
157         QtCore.Qt.WA_Maemo5StackedWindow
158         set_stackable = _maemo_set_stackable
159 except AttributeError:
160         set_stackable = _null_set_stackable
161
162
163 def _null_set_autorient(window, isStackable):
164         pass
165
166
167 def _maemo_set_autorient(window, isStackable):
168         window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
169
170
171 try:
172         QtCore.Qt.WA_Maemo5AutoOrientation
173         set_autorient = _maemo_set_autorient
174 except AttributeError:
175         set_autorient = _null_set_autorient
176
177
178 def _null_set_landscape(window, isStackable):
179         pass
180
181
182 def _maemo_set_landscape(window, isStackable):
183         window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
184
185
186 try:
187         QtCore.Qt.WA_Maemo5LandscapeOrientation
188         set_landscape = _maemo_set_landscape
189 except AttributeError:
190         set_landscape = _null_set_landscape
191
192
193 def _null_set_portrait(window, isStackable):
194         pass
195
196
197 def _maemo_set_portrait(window, isStackable):
198         window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
199
200
201 try:
202         QtCore.Qt.WA_Maemo5PortraitOrientation
203         set_portrait = _maemo_set_portrait
204 except AttributeError:
205         set_portrait = _null_set_portrait
206
207
208 def _null_show_progress_indicator(window, isStackable):
209         pass
210
211
212 def _maemo_show_progress_indicator(window, isStackable):
213         window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
214
215
216 try:
217         QtCore.Qt.WA_Maemo5ShowProgressIndicator
218         show_progress_indicator = _maemo_show_progress_indicator
219 except AttributeError:
220         show_progress_indicator = _null_show_progress_indicator
221
222
223 def _null_mark_numbers_preferred(widget):
224         pass
225
226
227 def _newqt_mark_numbers_preferred(widget):
228         widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
229
230
231 try:
232         QtCore.Qt.ImhPreferNumbers
233         mark_numbers_preferred = _newqt_mark_numbers_preferred
234 except AttributeError:
235         mark_numbers_preferred = _null_mark_numbers_preferred
236
237
238 def screen_orientation():
239         geom = QtGui.QApplication.desktop().screenGeometry()
240         if geom.width() <= geom.height():
241                 return QtCore.Qt.Vertical
242         else:
243                 return QtCore.Qt.Horizontal
244
245
246 def _null_get_theme_icon(iconNames, fallback = None):
247         icon = fallback if fallback is not None else QtGui.QIcon()
248         return icon
249
250
251 def _newqt_get_theme_icon(iconNames, fallback = None):
252         for iconName in iconNames:
253                 if QtGui.QIcon.hasThemeIcon(iconName):
254                         icon = QtGui.QIcon.fromTheme(iconName)
255                         break
256         else:
257                 icon = fallback if fallback is not None else QtGui.QIcon()
258         return icon
259
260
261 try:
262         QtGui.QIcon.fromTheme
263         get_theme_icon = _newqt_get_theme_icon
264 except AttributeError:
265         get_theme_icon = _null_get_theme_icon
266