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