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.
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
)
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
):
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
:
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
,
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
):
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
)
91 selected
= item
.isSelected()
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
])
100 model
.set_param(model_param
+'_item', '')
101 elif isinstance(widget
, QTreeWidget
):
102 item
= widget
.currentItem()
104 selected
= item
.isSelected()
105 row
= widget
.indexOfTopLevelItem(item
)
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
])
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
])
123 model
.set_param(model_param
+'_item', '')
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."""
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."""
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."""
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
160 view
= str(w
.objectName())
161 self
.__view
_to
_model
[view
] = model_param
162 self
.__autoconnect
(w
)
164 def autoconnect(self
, *widgets
):
166 self
.__autoconnect
(w
)
168 def __autoconnect(self
, widget
):
169 """Automagically connects Qt widgets to QObserver.SLOT"""
171 if widget
in self
.__connected
:
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
)
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
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
):
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', '')
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)
256 self
.model
.set_param(param
+'_item', '')
257 elif isinstance(widget
, QTreeWidget
):
258 self
.model
.set_param(param
+'_item', '')
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)
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)
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
):
289 date
= QDate
.fromString(value
, fmt
)
292 elif isinstance(widget
, QComboBox
):
293 self
.model
.set_param(param
+'_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
])
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
:
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
]:
317 def refresh_view(self
, *params
):
318 """Sends a notification message for each known model parameter."""
320 params
= tuple(self
.__model
_to
_view
.keys()
321 +self
.__actions
.keys())
324 if param
not in notified
:
325 notified
.append(param
)
326 self
.model
.notify_observers(*notified
)