Bump to 1.1.6 to fix a user issue
[gonvert] / util / qore_utils.py
1 #!/usr/bin/env python
2
3 import contextlib
4 import logging
5
6 import qt_compat
7 QtCore = qt_compat.QtCore
8
9 import misc
10
11
12 _moduleLogger = logging.getLogger(__name__)
13
14
15 class QThread44(QtCore.QThread):
16         """
17         This is to imitate QThread in Qt 4.4+ for when running on older version
18         See http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong
19         (On Lucid I have Qt 4.7 and this is still an issue)
20         """
21
22         def __init__(self, parent = None):
23                 QtCore.QThread.__init__(self, parent)
24
25         def run(self):
26                 self.exec_()
27
28
29 class _WorkerThread(QtCore.QObject):
30
31         _taskComplete  = qt_compat.Signal(object)
32
33         def __init__(self, futureThread):
34                 QtCore.QObject.__init__(self)
35                 self._futureThread = futureThread
36                 self._futureThread._addTask.connect(self._on_task_added)
37                 self._taskComplete.connect(self._futureThread._on_task_complete)
38
39         @qt_compat.Slot(object)
40         def _on_task_added(self, task):
41                 self.__on_task_added(task)
42
43         @misc.log_exception(_moduleLogger)
44         def __on_task_added(self, task):
45                 if not self._futureThread._isRunning:
46                         _moduleLogger.error("Dropping task")
47
48                 func, args, kwds, on_success, on_error = task
49
50                 try:
51                         result = func(*args, **kwds)
52                         isError = False
53                 except Exception, e:
54                         _moduleLogger.error("Error, passing it back to the main thread")
55                         result = e
56                         isError = True
57
58                 taskResult = on_success, on_error, isError, result
59                 self._taskComplete.emit(taskResult)
60
61
62 class FutureThread(QtCore.QObject):
63
64         _addTask = qt_compat.Signal(object)
65
66         def __init__(self):
67                 QtCore.QObject.__init__(self)
68                 self._thread = QThread44()
69                 self._isRunning = False
70                 self._worker = _WorkerThread(self)
71                 self._worker.moveToThread(self._thread)
72
73         def start(self):
74                 self._thread.start()
75                 self._isRunning = True
76
77         def stop(self):
78                 self._isRunning = False
79                 self._thread.quit()
80
81         def add_task(self, func, args, kwds, on_success, on_error):
82                 assert self._isRunning, "Task queue not started"
83                 task = func, args, kwds, on_success, on_error
84                 self._addTask.emit(task)
85
86         @qt_compat.Slot(object)
87         def _on_task_complete(self, taskResult):
88                 self.__on_task_complete(taskResult)
89
90         @misc.log_exception(_moduleLogger)
91         def __on_task_complete(self, taskResult):
92                 on_success, on_error, isError, result = taskResult
93                 if not self._isRunning:
94                         if isError:
95                                 _moduleLogger.error("Masking: %s" % (result, ))
96                         isError = True
97                         result = StopIteration("Cancelling all callbacks")
98                 callback = on_success if not isError else on_error
99                 try:
100                         callback(result)
101                 except Exception:
102                         _moduleLogger.exception("Callback errored")
103
104
105 def create_single_column_list_model(columnName, **kwargs):
106         """
107         >>> class Single(object): pass
108         >>> SingleListModel = create_single_column_list_model("s")
109         >>> slm = SingleListModel([Single(), Single(), Single()])
110         """
111
112         class SingleColumnListModel(QtCore.QAbstractListModel):
113
114                 def __init__(self, l = None):
115                         QtCore.QAbstractListModel.__init__(self)
116                         self._list = l if l is not None else []
117                         self.setRoleNames({0: columnName})
118
119                 def __len__(self):
120                         return len(self._list)
121
122                 def __getitem__(self, key):
123                         return self._list[key]
124
125                 def __setitem__(self, key, value):
126                         with scoped_model_reset(self):
127                                 self._list[key] = value
128
129                 def __delitem__(self, key):
130                         with scoped_model_reset(self):
131                                 del self._list[key]
132
133                 def __iter__(self):
134                         return iter(self._list)
135
136                 def __repr__(self):
137                         return '<%s (%s)>' % (
138                                 self.__class__.__name__,
139                                 columnName,
140                         )
141
142                 def rowCount(self, parent=QtCore.QModelIndex()):
143                         return len(self._list)
144
145                 def data(self, index, role):
146                         if index.isValid() and role == 0:
147                                 return self._list[index.row()]
148                         return None
149
150         if "name" in kwargs:
151                 SingleColumnListModel.__name__ = kwargs["name"]
152
153         return SingleColumnListModel
154
155
156 def create_tupled_list_model(*columnNames, **kwargs):
157         """
158         >>> class Column0(object): pass
159         >>> class Column1(object): pass
160         >>> class Column2(object): pass
161         >>> MultiColumnedListModel = create_tupled_list_model("c0", "c1", "c2")
162         >>> mclm = MultiColumnedListModel([(Column0(), Column1(), Column2())])
163         """
164
165         class TupledListModel(QtCore.QAbstractListModel):
166
167                 def __init__(self, l = None):
168                         QtCore.QAbstractListModel.__init__(self)
169                         self._list = l if l is not None else []
170                         self.setRoleNames(dict(enumerate(columnNames)))
171
172                 def __len__(self):
173                         return len(self._list)
174
175                 def __getitem__(self, key):
176                         return self._list[key]
177
178                 def __setitem__(self, key, value):
179                         with scoped_model_reset(self):
180                                 self._list[key] = value
181
182                 def __delitem__(self, key):
183                         with scoped_model_reset(self):
184                                 del self._list[key]
185
186                 def __iter__(self):
187                         return iter(self._list)
188
189                 def __repr__(self):
190                         return '<%s (%s)>' % (
191                                 self.__class__.__name__,
192                                 ', '.join(columnNames),
193                         )
194
195                 def rowCount(self, parent=QtCore.QModelIndex()):
196                         return len(self._list)
197
198                 def data(self, index, role):
199                         if index.isValid() and 0 <= role and role < len(columnNames):
200                                 return self._list[index.row()][role]
201                         return None
202
203         if "name" in kwargs:
204                 TupledListModel.__name__ = kwargs["name"]
205
206         return TupledListModel
207
208
209 class FileSystemModel(QtCore.QAbstractListModel):
210         """
211         Wrapper around QtGui.QFileSystemModel
212         """
213
214         FILEINFOS = [
215                 "fileName",
216                 "isDir",
217                 "filePath",
218                 "completeSuffix",
219                 "baseName",
220         ]
221
222         EXTINFOS = [
223                 "type",
224         ]
225
226         ALLINFOS = FILEINFOS + EXTINFOS
227
228         def __init__(self, model, path):
229                 QtCore.QAbstractListModel.__init__(self)
230                 self._path = path
231
232                 self._model = model
233                 self._rootIndex = self._model.index(self._path)
234
235                 self._child = None
236                 self.setRoleNames(dict(enumerate(self.ALLINFOS)))
237                 self._model.directoryLoaded.connect(self._on_directory_loaded)
238
239         childChanged = qt_compat.Signal(QtCore.QObject)
240
241         def _child(self):
242                 assert self._child is not None
243                 return self._child
244
245         child = qt_compat.Property(QtCore.QObject, _child, notify=childChanged)
246
247         backendChanged = qt_compat.Signal()
248
249         def _parent(self):
250                 finfo = self._model.fileInfo(self._rootIndex)
251                 return finfo.fileName()
252
253         parent = qt_compat.Property(str, _parent, notify=backendChanged)
254
255         @qt_compat.Slot(str)
256         def browse_to(self, path):
257                 if self._child is None:
258                         self._child = FileSystemModel(self._model, path)
259                 else:
260                         self._child.switch_to(path)
261                 self.childChanged.emit()
262                 return self._child
263
264         @qt_compat.Slot(str)
265         def switch_to(self, path):
266                 with scoped_model_reset(self):
267                         self._path = path
268                         self._rootIndex = self._model.index(self._path)
269                 self.backendChanged.emit()
270
271         def __len__(self):
272                 return self._model.rowCount(self._rootIndex)
273
274         def __getitem__(self, key):
275                 return self._model.index(key, 0, self._rootIndex)
276
277         def __iter__(self):
278                 return (self[i] for i in xrange(len(self)))
279
280         def rowCount(self, parent=QtCore.QModelIndex()):
281                 return len(self)
282
283         def data(self, index, role):
284                 if index.isValid() and 0 <= role and role < len(self.ALLINFOS):
285                         internalIndex = self._translate_index(index)
286                         info = self._model.fileInfo(internalIndex)
287                         if role < len(self.FILEINFOS):
288                                 field = self.FILEINFOS[role]
289                                 value = getattr(info, field)()
290                         else:
291                                 role -= len(self.FILEINFOS)
292                                 field = self.EXTINFOS[role]
293                                 if field == "type":
294                                         return self._model.type(internalIndex)
295                                 else:
296                                         raise NotImplementedError("Out of range that was already checked")
297                         return value
298                 return None
299
300         def _on_directory_loaded(self, path):
301                 if self._path == path:
302                         self.backendChanged.emit()
303                         self.reset()
304
305         def _translate_index(self, externalIndex):
306                 internalIndex = self._model.index(externalIndex.row(), 0, self._rootIndex)
307                 return internalIndex
308
309
310 @contextlib.contextmanager
311 def scoped_model_reset(model):
312         model.beginResetModel()
313         try:
314                 yield
315         finally:
316                 model.endResetModel()
317
318
319 def create_qobject(*classDef, **kwargs):
320         """
321         >>> Car = create_qobject(
322         ...     ('model', str),
323         ...     ('brand', str),
324         ...     ('year', int),
325         ...     ('inStock', bool),
326         ...     name='Car'
327         ... )
328         >>> print Car
329         <class '__main__.AutoQObject'>
330         >>>  
331         >>> c = Car(model='Fiesta', brand='Ford', year=1337)
332         >>> print c.model, c.brand, c.year, c.inStock
333         Fiesta Ford 1337 False
334         >>> print c
335         <Car (model='Fiesta', brand='Ford', year=1337, inStock=False)>
336         >>>  
337         >>> c.inStock = True
338         >>>  
339         >>> print c.model, c.brand, c.year, c.inStock
340         Fiesta Ford 1337 True
341         >>> print c
342         <Car (model='Fiesta', brand='Ford', year=1337, inStock=True)>
343         """
344
345         class AutoQObject(QtCore.QObject):
346
347                 def __init__(self, **initKwargs):
348                         QtCore.QObject.__init__(self)
349                         for key, val in classDef:
350                                 setattr(self, '_'+key, initKwargs.get(key, val()))
351
352                 def __repr__(self):
353                         values = (
354                                 '%s=%r' % (key, getattr(self, '_'+key))
355                                 for key, value in classDef
356                         )
357                         return '<%s (%s)>' % (
358                                 kwargs.get('name', self.__class__.__name__),
359                                 ', '.join(values),
360                         )
361
362                 for key, value in classDef:
363                         nfy = locals()['_nfy_'+key] = qt_compat.Signal()
364
365                         def _get(key):
366                                 def f(self):
367                                         return self.__dict__['_'+key]
368                                 return f
369
370                         def _set(key):
371                                 def f(self, value):
372                                         setattr(self, '_'+key, value)
373                                         getattr(self, '_nfy_'+key).emit()
374                                 return f
375
376                         setter = locals()['_set_'+key] = _set(key)
377                         getter = locals()['_get_'+key] = _get(key)
378
379                         locals()[key] = qt_compat.Property(value, getter, setter, notify=nfy)
380                 del nfy, _get, _set, getter, setter
381
382         return AutoQObject
383
384
385 class QObjectProxy(object):
386         """
387         Proxy for accessing properties and slots as attributes
388
389         This class acts as a proxy for the object for which it is
390         created, and makes property access more Pythonic while
391         still allowing access to slots (as member functions).
392
393         Attribute names starting with '_' are not proxied.
394         """
395
396         def __init__(self, rootObject):
397                 self._rootObject = rootObject
398                 m = self._rootObject.metaObject()
399                 self._properties = [
400                         m.property(i).name()
401                         for i in xrange(m.propertyCount())
402                 ]
403
404         def __getattr__(self, key):
405                 value = self._rootObject.property(key)
406
407                 # No such property, so assume we call a slot
408                 if value is None and key not in self._properties:
409                         return getattr(self._rootObject, key)
410
411                 return value
412
413         def __setattr__(self, key, value):
414                 if key.startswith('_'):
415                         object.__setattr__(self, key, value)
416                 else:
417                         self._rootObject.setProperty(key, value)
418
419
420 if __name__ == "__main__":
421         import doctest
422         print doctest.testmod()