cola: add more documentation strings to the cola modules
[git-cola.git] / cola / qobserver.py
blobdbacfdd80d62641cdd3f0c0d7e42b0d28ee4c3df
1 #!/usr/bin/env python
2 # Copyright (c) 2008 David Aguilar
3 """This module provides the QObserver class which allows for simple
4 correspondancies between model parameters and Qt widgets.
6 The QObserver class handles receiving notifications from
7 model classes and updating Qt widgets accordingly.
9 Qt signals are also relayed back to the model so that changes
10 are always available in the model without having to worry about the
11 different ways to query Qt widgets.
13 """
16 from PyQt4.QtCore import Qt
17 from PyQt4.QtCore import QObject
18 from PyQt4.QtCore import SIGNAL
19 from PyQt4.QtCore import QDate
20 from PyQt4.QtGui import QComboBox
21 from PyQt4.QtGui import QDateEdit
22 from PyQt4.QtGui import QSpinBox
23 from PyQt4.QtGui import QPixmap
24 from PyQt4.QtGui import QTextEdit
25 from PyQt4.QtGui import QLineEdit
26 from PyQt4.QtGui import QListWidget
27 from PyQt4.QtGui import QCheckBox
28 from PyQt4.QtGui import QFontComboBox
29 from PyQt4.QtGui import QAbstractButton
30 from PyQt4.QtGui import QSplitter
31 from PyQt4.QtGui import QAction
32 from PyQt4.QtGui import QTreeWidget
33 from PyQt4.QtGui import QTreeWidgetItem
34 from PyQt4.QtCore import QVariant
36 from cola.observer import Observer
39 class QObserver(Observer, QObject):
41 def __init__(self, model, view, *args, **kwargs):
42 Observer.__init__(self, model)
43 QObject.__init__(self)
45 self.view = view
47 self.__actions = {}
48 self.__callbacks = {}
49 self.__model_to_view = {}
50 self.__view_to_model = {}
51 self.__connected = set()
53 # Call the subclass's startup routine
54 self.init(model, view, *args, **kwargs)
56 def init(self, model, view, *args, **kwargs):
57 pass
59 def SLOT(self, *args):
60 """Default slot to handle all Qt callbacks.
61 This method delegates to callbacks from add_signals."""
63 widget = self.sender()
64 sender = str(widget.objectName())
66 if sender in self.__view_to_model:
67 model = self.model
68 model_param = self.__view_to_model[sender]
69 if isinstance(widget, QTextEdit):
70 value = unicode(widget.toPlainText())
71 model.set_param(model_param, value,
72 notify=False)
73 elif isinstance(widget, QLineEdit):
74 value = unicode(widget.text())
75 model.set_param(model_param, value)
76 elif isinstance(widget, QCheckBox):
77 model.set_param(model_param, widget.isChecked())
78 elif isinstance(widget, QSpinBox):
79 model.set_param(model_param, widget.value())
80 elif isinstance(widget, QFontComboBox):
81 value = unicode(widget.currentFont().toString())
82 model.set_param(model_param, value)
83 elif isinstance(widget, QDateEdit):
84 fmt = Qt.ISODate
85 value = str(widget.date().toString(fmt))
86 model.set_param(model_param, value)
87 elif isinstance(widget, QListWidget):
88 row = widget.currentRow()
89 item = widget.item(row)
90 if item:
91 selected = item.isSelected()
92 else:
93 selected = False
94 model.set_param(model_param+'_selected', selected)
95 model.set_param(model_param+'_index', row)
96 if selected and row != -1:
97 model.set_param(model_param+'_item',
98 model.get_param(model_param)[row])
99 else:
100 model.set_param(model_param+'_item', '')
101 elif isinstance(widget, QTreeWidget):
102 item = widget.currentItem()
103 if item:
104 selected = item.isSelected()
105 row = widget.indexOfTopLevelItem(item)
106 else:
107 selected = False
108 row = -1
109 model.set_param(model_param+'_selected', selected)
110 model.set_param(model_param+'_index', row)
111 items = model.get_param(model_param)
112 if selected and row != -1 and row < len(items):
113 model.set_param(model_param+'_item', items[row])
114 else:
115 model.set_param(model_param+'_item', '')
116 elif isinstance(widget, QComboBox):
117 idx = widget.currentIndex()
118 model.set_param(model_param+'_index', idx)
119 if idx != -1 and model.get_param(model_param):
120 model.set_param(model_param+'_item',
121 model.get_param(model_param)[idx])
122 else:
123 model.set_param(model_param+'_item', '')
125 else:
126 print("SLOT(): Unknown widget:", sender, widget)
128 if sender in self.__callbacks:
129 self.__callbacks[sender](*args)
131 def connect(self, obj, signal_str, *args):
132 """Convenience function so that subclasses do not have
133 to import QtCore.SIGNAL."""
134 signal = signal_str
135 if type(signal) is str:
136 signal = SIGNAL(signal)
137 return QObject.connect(obj, signal, *args)
139 def add_signals(self, signal_str, *objects):
140 """Connects object's signal to the QObserver."""
141 for obj in objects:
142 self.connect(obj, signal_str, self.SLOT)
144 def add_callbacks(self, **callbacks):
145 """Registers callbacks that are called in response to GUI events."""
146 for sender, callback in callbacks.iteritems():
147 self.__callbacks[sender] = callback
148 self.autoconnect(getattr(self.view, sender))
150 def add_observables(self, *params):
151 """This method assumes that widgets and model params
152 share the same name."""
153 for param in params:
154 self.model_to_view(param, getattr(self.view, param))
156 def model_to_view(self, model_param, *widgets):
157 """Binds model params to qt widgets(model->view)"""
158 self.__model_to_view[model_param] = widgets
159 for w in widgets:
160 view = str(w.objectName())
161 self.__view_to_model[view] = model_param
162 self.__autoconnect(w)
164 def autoconnect(self, *widgets):
165 for w in widgets:
166 self.__autoconnect(w)
168 def __autoconnect(self, widget):
169 """Automagically connects Qt widgets to QObserver.SLOT"""
171 if widget in self.__connected:
172 return
173 self.__connected.add(widget)
175 if isinstance(widget, QTextEdit):
176 self.add_signals('textChanged()', widget)
177 elif isinstance(widget, QLineEdit):
178 self.add_signals('textChanged(const QString&)', widget)
179 elif isinstance(widget, QListWidget):
180 self.add_signals('itemSelectionChanged()', widget)
181 self.add_signals('itemClicked(QListWidgetItem *)', widget)
182 doubleclick = str(widget.objectName())+'_doubleclick'
183 if hasattr(self, doubleclick):
184 self.connect(widget, 'itemDoubleClicked(QListWidgetItem *)',
185 getattr(self, doubleclick))
186 elif isinstance(widget, QTreeWidget):
187 self.add_signals('itemSelectionChanged()', widget)
188 self.add_signals('itemClicked(QTreeWidgetItem *, int)', widget)
189 doubleclick = str(widget.objectName())+'_doubleclick'
190 if hasattr(self, doubleclick):
191 self.connect(widget, 'itemDoubleClicked(QTreeWidgetItem *, int)',
192 getattr(self, doubleclick))
194 elif isinstance(widget, QAbstractButton):
195 self.add_signals('released()', widget)
196 elif isinstance(widget, QAction):
197 self.add_signals('triggered()', widget)
198 elif isinstance(widget, QCheckBox):
199 self.add_signals('stateChanged(int)', widget)
200 elif isinstance(widget, QSpinBox):
201 self.add_signals('valueChanged(int)', widget)
202 elif isinstance(widget, QFontComboBox):
203 self.add_signals('currentFontChanged(const QFont&)', widget)
204 elif isinstance(widget, QSplitter):
205 self.add_signals('splitterMoved(int,int)', widget)
206 elif isinstance(widget, QDateEdit):
207 self.add_signals('dateChanged(const QDate&)', widget)
208 elif isinstance(widget, QComboBox):
209 self.add_signals('currentIndexChanged(int)', widget)
210 else:
211 raise Exception('Asked to connect unknown widget:\n\t%s => %s'
212 % (type(widget), str(widget.objectName())))
214 def add_actions(self, **kwargs):
215 """Register view actions that are called in response to
216 view changes.(view->model)"""
217 for model_param, callback in kwargs.iteritems():
218 if type(callback) is list:
219 self.__actions[model_param] = callback
220 else:
221 self.__actions[model_param] = [callback]
223 def subject_changed(self, param, value):
224 """Sends a model param to the view(model->view)"""
226 if param in self.__model_to_view:
227 notify = self.model.get_notify()
228 self.model.set_notify(False)
229 for widget in self.__model_to_view[param]:
230 sender = str(widget.objectName())
231 if isinstance(widget, QSpinBox):
232 widget.setValue(value)
233 elif isinstance(widget, QPixmap):
234 widget.load(value)
235 elif isinstance(widget, QTextEdit):
236 widget.setText(value)
237 elif isinstance(widget, QLineEdit):
238 widget.setText(value)
239 elif isinstance(widget, QListWidget):
240 self.model.set_param(param+'_item', '')
241 widget.clear()
242 for i in value:
243 widget.addItem(i)
244 if self.model.has_param(param+'_index'):
245 idx = self.model.get_param(param+'_index')
246 if idx != -1 and idx < len(value):
247 item = widget.item(idx)
248 widget.setCurrentItem(item)
249 widget.setItemSelected(item, True)
250 self.model.set_param(param+'_item', value[idx])
251 if sender in self.__callbacks:
252 self.model.set_notify(True)
253 self.__callbacks[sender]()
254 self.model.set_notify(False)
255 else:
256 self.model.set_param(param+'_item', '')
257 elif isinstance(widget, QTreeWidget):
258 self.model.set_param(param+'_item', '')
259 widget.clear()
260 for i in value:
261 item = QTreeWidgetItem([i])
262 item.setData(0, Qt.UserRole,
263 QVariant(widget.topLevelItemCount()))
264 widget.addTopLevelItem(item)
265 if self.model.has_param(param+'_index'):
266 idx = self.model.get_param(param+'_index')
267 if idx != -1 and idx < len(value):
268 item = widget.topLevelItem(idx)
269 widget.setCurrentItem(item)
270 widget.setItemSelected(item, True)
271 val = value[idx]
272 self.model.set_param(param+'_item', val)
273 if sender in self.__callbacks:
274 self.model.set_notify(True)
275 self.__callbacks[sender]()
276 self.model.set_notify(False)
277 else:
278 self.model.set_param(param+'_item', '')
280 elif isinstance(widget, QCheckBox):
281 widget.setChecked(value)
282 elif isinstance(widget, QFontComboBox):
283 font = widget.currentFont()
284 font.fromString(value)
285 elif isinstance(widget, QDateEdit):
286 if not value:
287 return
288 fmt = Qt.ISODate
289 date = QDate.fromString(value, fmt)
290 if date:
291 widget.setDate(date)
292 elif isinstance(widget, QComboBox):
293 self.model.set_param(param+'_item', '')
294 widget.clear()
295 for item in value:
296 widget.addItem(item)
297 if self.model.has_param(param+'_index'):
298 idx = self.model.get_param(param+'_index')
299 if idx != -1 and idx < len(value):
300 widget.setCurrentIndex(idx)
301 self.model.set_param(param+'_item', value[idx])
302 else:
303 print('subject_changed(): Unknown widget:',
304 str(widget.objectName()), widget, value)
305 self.model.set_notify(notify)
307 if param not in self.__actions:
308 return
309 widgets = []
310 if param in self.__model_to_view:
311 for widget in self.__model_to_view[param]:
312 widgets.append(widget)
313 # Call the model callback w/ the view's widgets as the args
314 for action in self.__actions[param]:
315 action(*widgets)
317 def refresh_view(self, *params):
318 """Sends a notification message for each known model parameter."""
319 if not params:
320 params= tuple(self.__model_to_view.keys()
321 +self.__actions.keys())
322 notified = []
323 for param in params:
324 if param not in notified:
325 notified.append(param)
326 self.model.notify_observers(*notified)