doc: Add 1.4.2.2 release notes
[git-cola.git] / cola / qobserver.py
blobd937ddc3b97521a935132020cdb3f58f1d9b60ca
1 # Copyright (c) 2008 David Aguilar
2 """This module provides the QObserver class which allows for simple
3 correspondancies between model parameters and Qt widgets.
5 The QObserver class handles receiving notifications from
6 model classes and updating Qt widgets accordingly.
8 Qt signals are also relayed back to the model so that changes
9 are always available in the model without having to worry about the
10 different ways to query Qt widgets.
12 """
13 import types
15 from PyQt4 import QtGui
16 from PyQt4 import QtCore
17 from PyQt4.QtCore import SIGNAL
19 from cola import observer
20 from cola.compat import set
22 class QObserver(observer.Observer, QtCore.QObject):
24 def __init__(self, model, view, *args, **kwargs):
25 """Binds a model and Qt view"""
26 observer.Observer.__init__(self, model)
27 QtCore.QObject.__init__(self)
29 self.view = view
31 self._actions = {}
32 self._callbacks = {}
33 self._widget_names = {}
34 self._model_to_view = {}
35 self._mapped_params = set()
36 self._connected = set()
38 def SLOT(self, *args):
39 """Default slot to handle all Qt callbacks.
40 This method delegates to callbacks from add_signals."""
42 widget = self.sender()
43 param = self._widget_names[widget]
45 if param in self._mapped_params:
46 model = self.model
47 if isinstance(widget, QtGui.QTextEdit):
48 value = unicode(widget.toPlainText())
49 model.set_param(param, value, notify=False)
50 elif isinstance(widget, QtGui.QLineEdit):
51 value = unicode(widget.text())
52 model.set_param(param, value, notify=False)
53 elif isinstance(widget, QtGui.QCheckBox):
54 model.set_param(param, widget.isChecked())
55 elif isinstance(widget, QtGui.QSpinBox):
56 model.set_param(param, widget.value())
57 elif isinstance(widget, QtGui.QFontComboBox):
58 value = unicode(widget.currentFont().toString())
59 if model.has_param(param+'_size'):
60 size = model.param(param+'_size')
61 props = value.split(',')
62 props[1] = str(size)
63 value = ','.join(props)
64 model.set_param(param, value)
65 elif isinstance(widget, QtGui.QDateEdit):
66 fmt = QtCore.Qt.ISODate
67 value = str(widget.date().toString(fmt))
68 model.set_param(param, value)
69 elif isinstance(widget, QtGui.QListWidget):
70 row = widget.currentRow()
71 item = widget.item(row)
72 if item:
73 selected = item.isSelected()
74 else:
75 selected = False
76 model.set_param(param+'_selected', selected)
77 model.set_param(param+'_index', row)
78 if selected and row != -1:
79 model.set_param(param+'_item',
80 model.param(param)[row])
81 else:
82 model.set_param(param+'_item', '')
83 elif isinstance(widget, QtGui.QTreeWidget):
84 item = widget.currentItem()
85 if item:
86 selected = item.isSelected()
87 row = widget.indexOfTopLevelItem(item)
88 else:
89 selected = False
90 row = -1
91 model.set_param(param+'_selected', selected)
92 model.set_param(param+'_index', row)
93 items = model.param(param)
94 if selected and row != -1 and row < len(items):
95 model.set_param(param+'_item', items[row])
96 else:
97 model.set_param(param+'_item', '')
98 elif isinstance(widget, QtGui.QComboBox):
99 idx = widget.currentIndex()
100 txt = unicode(widget.currentText())
101 model.set_param(param+'_index', idx)
102 model.set_param(param+'_item', txt)
103 else:
104 print("SLOT(): Unknown widget:", param, widget)
106 if param in self._callbacks:
107 self._callbacks[param](*args)
109 def add_signals(self, signal_str, *objects):
110 """Connects object's signal to the QObserver."""
111 for obj in objects:
112 self.connect(obj, SIGNAL(signal_str), self.SLOT)
114 def add_callbacks(self, **callbacks):
115 """Registers callbacks that are called in response to GUI events."""
116 for sender, callback in callbacks.iteritems():
117 self._callbacks[sender] = callback
118 widget = getattr(self.view, sender)
119 self._widget_names[widget] = sender
120 self._autoconnect(widget)
122 def add_observables(self, *params):
123 """This method assumes that widgets and model params
124 share the same name."""
125 for param in params:
126 widget = getattr(self.view, param)
127 self._model_to_view[param] = widget
128 self._widget_names[widget] = param
129 self._mapped_params.add(param)
130 self._autoconnect(widget)
132 def _autoconnect(self, widget):
133 """Automagically connects Qt widgets to QObserver.SLOT"""
135 if widget in self._connected:
136 return
137 self._connected.add(widget)
139 if isinstance(widget, QtGui.QTextEdit):
140 self.add_signals('textChanged()', widget)
141 elif isinstance(widget, QtGui.QLineEdit):
142 self.add_signals('textChanged(const QString&)', widget)
143 elif isinstance(widget, QtGui.QListWidget):
144 self.add_signals('itemSelectionChanged()', widget)
145 self.add_signals('itemClicked(QListWidgetItem *)', widget)
146 doubleclick = str(widget.objectName())+'_doubleclick'
147 if hasattr(self, doubleclick):
148 self.connect(widget,
149 SIGNAL('itemDoubleClicked(QListWidgetItem *)'),
150 getattr(self, doubleclick))
151 elif isinstance(widget, QtGui.QTreeWidget):
152 self.add_signals('itemSelectionChanged()', widget)
153 self.add_signals('itemClicked(QTreeWidgetItem *, int)', widget)
154 doubleclick = str(widget.objectName())+'_doubleclick'
155 if hasattr(self, doubleclick):
156 self.connect(widget,
157 SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'),
158 getattr(self, doubleclick))
160 elif isinstance(widget, QtGui.QAbstractButton):
161 self.add_signals('released()', widget)
162 elif isinstance(widget, QtGui.QAction):
163 self.add_signals('triggered()', widget)
164 elif isinstance(widget, QtGui.QCheckBox):
165 self.add_signals('stateChanged(int)', widget)
166 elif isinstance(widget, QtGui.QSpinBox):
167 self.add_signals('valueChanged(int)', widget)
168 elif isinstance(widget, QtGui.QFontComboBox):
169 self.add_signals('currentFontChanged(const QFont&)', widget)
170 elif isinstance(widget, QtGui.QSplitter):
171 self.add_signals('splitterMoved(int,int)', widget)
172 elif isinstance(widget, QtGui.QDateEdit):
173 self.add_signals('dateChanged(const QDate&)', widget)
174 elif isinstance(widget, QtGui.QComboBox):
175 self.add_signals('currentIndexChanged(int)', widget)
176 self.add_signals('editTextChanged(QString)', widget)
177 else:
178 raise Exception('Asked to connect unknown widget:\n\t%s => %s'
179 % (type(widget), str(widget.objectName())))
181 def add_actions(self, **kwargs):
183 Register view actions.
185 Action are called in response to view changes.(view->model).
188 for param, callback in kwargs.iteritems():
189 if type(callback) is list:
190 self._actions[param] = callback
191 else:
192 self._actions[param] = [callback]
194 def subject_changed(self, param, value):
195 """Sends a model param to the view(model->view)"""
197 if param in self._model_to_view:
198 # Temporarily disable notification to avoid loop-backs
199 notify = self.model.notification_enabled
200 self.model.notification_enabled = False
202 widget = self._model_to_view[param]
203 if isinstance(widget, QtGui.QSpinBox):
204 widget.setValue(value)
205 elif isinstance(widget, QtGui.QPixmap):
206 widget.load(value)
207 elif isinstance(widget, QtGui.QTextEdit):
208 widget.setPlainText(value)
209 elif isinstance(widget, QtGui.QLineEdit):
210 widget.setText(value)
211 elif isinstance(widget, QtGui.QListWidget):
212 self.model.set_param(param+'_item', '')
213 widget.clear()
214 for i in value:
215 widget.addItem(i)
216 if self.model.has_param(param+'_index'):
217 idx = self.model.param(param+'_index')
218 if idx != -1 and idx < len(value):
219 item = widget.item(idx)
220 widget.setCurrentItem(item)
221 widget.setItemSelected(item, True)
222 self.model.set_param(param+'_item', value[idx])
223 if param in self._callbacks:
224 self.model.notification_enabled = True
225 self._callbacks[param]()
226 self.model.notification_enabled = False
227 else:
228 self.model.set_param(param+'_item', '')
229 elif isinstance(widget, QtGui.QTreeWidget):
230 self.model.set_param(param+'_item', '')
231 widget.clear()
232 for i in value:
233 item = QtGui.QTreeWidgetItem([i])
234 count = widget.topLevelItemCount()
235 item.setData(0, QtCore.Qt.UserRole,
236 QtCore.QVariant(count))
237 widget.addTopLevelItem(item)
238 if self.model.has_param(param+'_index'):
239 idx = self.model.param(param+'_index')
240 if idx != -1 and idx < len(value):
241 item = widget.topLevelItem(idx)
242 widget.setCurrentItem(item)
243 widget.setItemSelected(item, True)
244 val = value[idx]
245 self.model.set_param(param+'_item', val)
246 if param in self._callbacks:
247 self.model.notification_enabled = True
248 self._callbacks[param]()
249 self.model.notification_enabled = False
250 else:
251 self.model.set_param(param+'_item', '')
253 elif isinstance(widget, QtGui.QCheckBox):
254 widget.setChecked(value)
255 elif isinstance(widget, QtGui.QFontComboBox):
256 font = QtGui.QFont()
257 font.fromString(value)
258 widget.setCurrentFont(font)
259 elif isinstance(widget, QtGui.QDateEdit):
260 if not value:
261 return
262 fmt = QtCore.Qt.ISODate
263 date = QtCore.QDate.fromString(value, fmt)
264 if date:
265 widget.setDate(date)
266 elif isinstance(widget, QtGui.QComboBox):
267 self.model.set_param(param+'_item', '')
268 widget.clear()
269 for item in value:
270 widget.addItem(item)
271 if self.model.has_param(param+'_index'):
272 idx = self.model.param(param+'_index')
273 if idx != -1 and idx < len(value):
274 widget.setCurrentIndex(idx)
275 self.model.set_param(param+'_item', value[idx])
276 else:
277 print('subject_changed(): Unknown widget:',
278 str(widget.objectName()), widget, value)
280 self.model.notification_enabled = notify
282 if param not in self._actions:
283 return
284 widgets = []
285 if param in self._model_to_view:
286 widget = self._model_to_view[param]
287 widgets.append(widget)
288 # Call the model callback w/ the view's widgets as the args
289 for action in self._actions[param]:
290 action(*widgets)
292 def refresh_view(self, *params):
293 """Sends a notification message for each known model parameter."""
294 if not params:
295 params= tuple(self._model_to_view.keys() +
296 self._actions.keys())
297 notified = []
298 for param in params:
299 if param not in notified:
300 notified.append(param)
301 self.model.notify_observers(*notified)