models.main: Optimize stage_paths(), unstage_paths(), and revert_paths()
[git-cola.git] / cola / qobserver.py
blob5153f8ca51fc28bb24cf2b0d51bc4f2e1dad5e81
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 QtCore
16 from PyQt4 import QtGui
18 from cola import observer
20 class QObserver(observer.Observer, QtCore.QObject):
22 def __init__(self, model, view, *args, **kwargs):
23 """Binds a model and Qt view"""
24 observer.Observer.__init__(self, model)
25 QtCore.QObject.__init__(self)
27 self.view = view
29 self._actions = {}
30 self._callbacks = {}
31 self._widget_names = {}
32 self._model_to_view = {}
33 self._mapped_params = set()
34 self._connected = set()
35 self._in_textfield = False
36 self._in_callback = False
38 def SLOT(self, *args):
39 """Default slot to handle all Qt callbacks.
40 This method delegates to callbacks from add_signals."""
42 self._in_textfield = False
44 widget = self.sender()
45 param = self._widget_names[widget]
47 if param in self._mapped_params:
48 model = self.model
49 if isinstance(widget, QtGui.QTextEdit):
50 self._in_textfield = True
51 value = unicode(widget.toPlainText())
52 model.set_param(param, value, notify=False)
53 elif isinstance(widget, QtGui.QLineEdit):
54 self._in_textfield = True
55 value = unicode(widget.text())
56 model.set_param(param, value)
57 elif isinstance(widget, QtGui.QCheckBox):
58 model.set_param(param, widget.isChecked())
59 elif isinstance(widget, QtGui.QSpinBox):
60 model.set_param(param, widget.value())
61 elif isinstance(widget, QtGui.QFontComboBox):
62 value = unicode(widget.currentFont().toString())
63 if model.has_param(param+'_size'):
64 size = model.param(param+'_size')
65 props = value.split(',')
66 props[1] = str(size)
67 value = ','.join(props)
68 model.set_param(param, value)
69 elif isinstance(widget, QtGui.QDateEdit):
70 fmt = QtCore.Qt.ISODate
71 value = str(widget.date().toString(fmt))
72 model.set_param(param, value)
73 elif isinstance(widget, QtGui.QListWidget):
74 row = widget.currentRow()
75 item = widget.item(row)
76 if item:
77 selected = item.isSelected()
78 else:
79 selected = False
80 model.set_param(param+'_selected', selected)
81 model.set_param(param+'_index', row)
82 if selected and row != -1:
83 model.set_param(param+'_item',
84 model.param(param)[row])
85 else:
86 model.set_param(param+'_item', '')
87 elif isinstance(widget, QtGui.QTreeWidget):
88 item = widget.currentItem()
89 if item:
90 selected = item.isSelected()
91 row = widget.indexOfTopLevelItem(item)
92 else:
93 selected = False
94 row = -1
95 model.set_param(param+'_selected', selected)
96 model.set_param(param+'_index', row)
97 items = model.param(param)
98 if selected and row != -1 and row < len(items):
99 model.set_param(param+'_item', items[row])
100 else:
101 model.set_param(param+'_item', '')
102 elif isinstance(widget, QtGui.QComboBox):
103 idx = widget.currentIndex()
104 txt = unicode(widget.currentText())
105 model.set_param(param+'_index', idx)
106 model.set_param(param+'_item', txt)
107 else:
108 print("SLOT(): Unknown widget:", param, widget)
110 self._in_callback = True
111 if param in self._callbacks:
112 self._callbacks[param](*args)
113 self._in_callback = False
114 self._in_textfield = False
116 def connect(self, obj, signal_str, *args):
117 """Convenience function so that subclasses do not have
118 to import QtCore.SIGNAL."""
119 signal = signal_str
120 if type(signal) is str:
121 signal = QtCore.SIGNAL(signal)
122 return QtCore.QObject.connect(obj, signal, *args)
124 def add_signals(self, signal_str, *objects):
125 """Connects object's signal to the QObserver."""
126 for obj in objects:
127 self.connect(obj, signal_str, self.SLOT)
129 def add_callbacks(self, **callbacks):
130 """Registers callbacks that are called in response to GUI events."""
131 for sender, callback in callbacks.iteritems():
132 self._callbacks[sender] = callback
133 widget = getattr(self.view, sender)
134 self._widget_names[widget] = sender
135 self._autoconnect(widget)
137 def add_observables(self, *params):
138 """This method assumes that widgets and model params
139 share the same name."""
140 for param in params:
141 widget = getattr(self.view, param)
142 self._model_to_view[param] = widget
143 self._widget_names[widget] = param
144 self._mapped_params.add(param)
145 self._autoconnect(widget)
147 def _autoconnect(self, widget):
148 """Automagically connects Qt widgets to QObserver.SLOT"""
150 if widget in self._connected:
151 return
152 self._connected.add(widget)
154 if isinstance(widget, QtGui.QTextEdit):
155 self.add_signals('textChanged()', widget)
156 elif isinstance(widget, QtGui.QLineEdit):
157 self.add_signals('textChanged(const QString&)', widget)
158 elif isinstance(widget, QtGui.QListWidget):
159 self.add_signals('itemSelectionChanged()', widget)
160 self.add_signals('itemClicked(QListWidgetItem *)', widget)
161 doubleclick = str(widget.objectName())+'_doubleclick'
162 if hasattr(self, doubleclick):
163 self.connect(widget, 'itemDoubleClicked(QListWidgetItem *)',
164 getattr(self, doubleclick))
165 elif isinstance(widget, QtGui.QTreeWidget):
166 self.add_signals('itemSelectionChanged()', widget)
167 self.add_signals('itemClicked(QTreeWidgetItem *, int)', widget)
168 doubleclick = str(widget.objectName())+'_doubleclick'
169 if hasattr(self, doubleclick):
170 self.connect(widget, 'itemDoubleClicked(QTreeWidgetItem *, int)',
171 getattr(self, doubleclick))
173 elif isinstance(widget, QtGui.QAbstractButton):
174 self.add_signals('released()', widget)
175 elif isinstance(widget, QtGui.QAction):
176 self.add_signals('triggered()', widget)
177 elif isinstance(widget, QtGui.QCheckBox):
178 self.add_signals('stateChanged(int)', widget)
179 elif isinstance(widget, QtGui.QSpinBox):
180 self.add_signals('valueChanged(int)', widget)
181 elif isinstance(widget, QtGui.QFontComboBox):
182 self.add_signals('currentFontChanged(const QFont&)', widget)
183 elif isinstance(widget, QtGui.QSplitter):
184 self.add_signals('splitterMoved(int,int)', widget)
185 elif isinstance(widget, QtGui.QDateEdit):
186 self.add_signals('dateChanged(const QDate&)', widget)
187 elif isinstance(widget, QtGui.QComboBox):
188 self.add_signals('currentIndexChanged(int)', widget)
189 self.add_signals('editTextChanged(QString)', widget)
190 else:
191 raise Exception('Asked to connect unknown widget:\n\t%s => %s'
192 % (type(widget), str(widget.objectName())))
194 def add_actions(self, **kwargs):
196 Register view actions.
198 Action are called in response to view changes.(view->model).
201 for param, callback in kwargs.iteritems():
202 if type(callback) is list:
203 self._actions[param] = callback
204 else:
205 self._actions[param] = [callback]
207 def subject_changed(self, param, value):
208 """Sends a model param to the view(model->view)"""
210 if self._in_textfield and not self._in_callback:
211 # A slot has changed the model and we're not in
212 # a user callback. In this case the event is causing
213 # a feedback loop so skip redundant work and return.
214 return
216 if param in self._model_to_view:
217 # Temporarily disable notification to avoid loop-backs
218 notify = self.model.notification_enabled
219 self.model.notification_enabled = False
221 widget = self._model_to_view[param]
222 if isinstance(widget, QtGui.QSpinBox):
223 widget.setValue(value)
224 elif isinstance(widget, QtGui.QPixmap):
225 widget.load(value)
226 elif isinstance(widget, QtGui.QTextEdit):
227 widget.setText(value)
228 elif isinstance(widget, QtGui.QLineEdit):
229 widget.setText(value)
230 elif isinstance(widget, QtGui.QListWidget):
231 self.model.set_param(param+'_item', '')
232 widget.clear()
233 for i in value:
234 widget.addItem(i)
235 if self.model.has_param(param+'_index'):
236 idx = self.model.param(param+'_index')
237 if idx != -1 and idx < len(value):
238 item = widget.item(idx)
239 widget.setCurrentItem(item)
240 widget.setItemSelected(item, True)
241 self.model.set_param(param+'_item', value[idx])
242 if param in self._callbacks:
243 self.model.notification_enabled = True
244 self._callbacks[param]()
245 self.model.notification_enabled = False
246 else:
247 self.model.set_param(param+'_item', '')
248 elif isinstance(widget, QtGui.QTreeWidget):
249 self.model.set_param(param+'_item', '')
250 widget.clear()
251 for i in value:
252 item = QtGui.QTreeWidgetItem([i])
253 count = widget.topLevelItemCount()
254 item.setData(0, QtCore.Qt.UserRole,
255 QtCore.QVariant(count))
256 widget.addTopLevelItem(item)
257 if self.model.has_param(param+'_index'):
258 idx = self.model.param(param+'_index')
259 if idx != -1 and idx < len(value):
260 item = widget.topLevelItem(idx)
261 widget.setCurrentItem(item)
262 widget.setItemSelected(item, True)
263 val = value[idx]
264 self.model.set_param(param+'_item', val)
265 if param in self._callbacks:
266 self.model.notification_enabled = True
267 self._callbacks[param]()
268 self.model.notification_enabled = False
269 else:
270 self.model.set_param(param+'_item', '')
272 elif isinstance(widget, QtGui.QCheckBox):
273 widget.setChecked(value)
274 elif isinstance(widget, QtGui.QFontComboBox):
275 font = QtGui.QFont()
276 font.fromString(value)
277 widget.setCurrentFont(font)
278 elif isinstance(widget, QtGui.QDateEdit):
279 if not value:
280 return
281 fmt = QtCore.Qt.ISODate
282 date = QtCore.QDate.fromString(value, fmt)
283 if date:
284 widget.setDate(date)
285 elif isinstance(widget, QtGui.QComboBox):
286 self.model.set_param(param+'_item', '')
287 widget.clear()
288 for item in value:
289 widget.addItem(item)
290 if self.model.has_param(param+'_index'):
291 idx = self.model.param(param+'_index')
292 if idx != -1 and idx < len(value):
293 widget.setCurrentIndex(idx)
294 self.model.set_param(param+'_item', value[idx])
295 else:
296 print('subject_changed(): Unknown widget:',
297 str(widget.objectName()), widget, value)
298 self.model.notification_enabled = notify
300 if param not in self._actions:
301 return
302 widgets = []
303 if param in self._model_to_view:
304 widget = self._model_to_view[param]
305 widgets.append(widget)
306 # Call the model callback w/ the view's widgets as the args
307 for action in self._actions[param]:
308 action(*widgets)
310 def refresh_view(self, *params):
311 """Sends a notification message for each known model parameter."""
312 if not params:
313 params= tuple(self._model_to_view.keys() +
314 self._actions.keys())
315 notified = []
316 for param in params:
317 if param not in notified:
318 notified.append(param)
319 self.model.notify_observers(*notified)