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.
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
)
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
:
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(',')
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
)
73 selected
= item
.isSelected()
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
])
82 model
.set_param(param
+'_item', '')
83 elif isinstance(widget
, QtGui
.QTreeWidget
):
84 item
= widget
.currentItem()
86 selected
= item
.isSelected()
87 row
= widget
.indexOfTopLevelItem(item
)
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
])
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
)
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."""
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."""
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
:
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
):
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
):
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
)
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
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
):
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', '')
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
228 self
.model
.set_param(param
+'_item', '')
229 elif isinstance(widget
, QtGui
.QTreeWidget
):
230 self
.model
.set_param(param
+'_item', '')
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)
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
251 self
.model
.set_param(param
+'_item', '')
253 elif isinstance(widget
, QtGui
.QCheckBox
):
254 widget
.setChecked(value
)
255 elif isinstance(widget
, QtGui
.QFontComboBox
):
257 font
.fromString(value
)
258 widget
.setCurrentFont(font
)
259 elif isinstance(widget
, QtGui
.QDateEdit
):
262 fmt
= QtCore
.Qt
.ISODate
263 date
= QtCore
.QDate
.fromString(value
, fmt
)
266 elif isinstance(widget
, QtGui
.QComboBox
):
267 self
.model
.set_param(param
+'_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
])
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
:
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
]:
292 def refresh_view(self
, *params
):
293 """Sends a notification message for each known model parameter."""
295 params
= tuple(self
._model
_to
_view
.keys() +
296 self
._actions
.keys())
299 if param
not in notified
:
300 notified
.append(param
)
301 self
.model
.notify_observers(*notified
)