1 /***************************************************************************
2 rkmodificationtracker - description
3 -------------------
4 begin : Tue Aug 31 2004
5 copyright : (C) 2004-2017 by Thomas Friedrichsmeier
6 email : thomas.friedrichsmeier@kdemail.net
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17 #include "rkmodificationtracker.h"
18
19 #include <kmessagebox.h>
20 #include <KLocalizedString>
21
22 #include "../rkglobals.h"
23 #include "../dataeditor/rkeditor.h"
24 #include "../dataeditor/rkvareditmodel.h"
25 #include "rcontainerobject.h"
26 #include "robjectlist.h"
27 #include "renvironmentobject.h"
28 #include "../windows/rkworkplace.h"
29 #include "../misc/rkstandardicons.h"
30
31 #include "../debug.h"
32
RKModificationTracker(QObject * parent)33 RKModificationTracker::RKModificationTracker (QObject *parent) : RKObjectListModel (parent) {
34 RK_TRACE (OBJECTS);
35
36 updates_locked = 0;
37 }
38
~RKModificationTracker()39 RKModificationTracker::~RKModificationTracker () {
40 RK_TRACE (OBJECTS);
41
42 RK_ASSERT (updates_locked == 0);
43 RK_ASSERT (listeners.isEmpty ());
44 }
45
lockUpdates(bool lock)46 void RKModificationTracker::lockUpdates (bool lock) {
47 RK_TRACE (OBJECTS);
48
49 if (lock) ++updates_locked;
50 else {
51 --updates_locked;
52 RK_ASSERT (updates_locked >= 0);
53 }
54 }
55
removeObject(RObject * object,RKEditor * editor,bool removed_in_workspace)56 bool RKModificationTracker::removeObject (RObject *object, RKEditor *editor, bool removed_in_workspace) {
57 RK_TRACE (OBJECTS);
58 // WARNING: This does not work, if a sub-object is being edited!
59 RKEditor* ed = objectEditors (object).value (0);
60 RK_ASSERT (object);
61 RK_ASSERT (!((editor) && (!ed)));
62 RK_ASSERT (!(removed_in_workspace && editor));
63
64 if (!object->isPseudoObject ()) {
65 if (removed_in_workspace) {
66 if (ed && (ed->getObject () == object) && object->canWrite ()) { // NOTE: do not allow restoring of columns in a data.frame this way. See https://mail.kde.org/pipermail/rkward-devel/2012-March/003225.html and replies.
67 if (KMessageBox::questionYesNo (0, i18n ("The object '%1' was removed from workspace or changed to a different type of object, but is currently opened for editing. Do you want to restore it?", object->getFullName ()), i18n ("Restore object?")) == KMessageBox::Yes) {
68 ed->restoreObject (object);
69 /* TODO: It would make a lot of sense to allow restoring to a different name, and possibly different location. This may need some thinking. Probably something like:
70 * object->parentObject ()->removeChildNoDelete (parent);
71 * object->setParentObject (RObjectList::getGlobalEnv ());
72 * // make sure new_name is unique in new parent!
73 * RObjectList::getGlobalEnv ()->insertChild (object, -1);
74 * object->setName (new_name);
75 * along with proper begin/endAdd/RemoveRows().
76 * Oh, and listener notifications. That might be tricky.
77 * */
78 return false;
79 }
80 }
81 } else {
82 if (editor || ed) {
83 if (KMessageBox::questionYesNo (0, i18n ("Do you really want to remove the object '%1'? The object is currently opened for editing, it will be removed in the editor, too. There's no way to get it back.", object->getFullName ()), i18n ("Remove object?")) != KMessageBox::Yes) {
84 return false;
85 }
86 } else {
87 // TODO: check for other editors editing this object
88 if (KMessageBox::questionYesNo (0, i18n ("Do you really want to remove the object '%1'? There's no way to get it back.", object->getFullName ()), i18n ("Remove object?")) != KMessageBox::Yes) {
89 return false;
90 }
91 }
92 }
93 }
94
95 RK_ASSERT (object);
96 if (!object->parentObject ()) {
97 RK_DEBUG (OBJECTS, DL_ERROR, "Trying to remove root level object. Backend crashed?");
98 return false;
99 }
100 bool view_update = !updates_locked && !object->isType (RObject::NonVisibleObject);
101
102 if (view_update) {
103 QModelIndex object_index = indexFor (object->parentObject ());
104 int object_row = object->parentObject ()->getObjectModelIndexOf (object);
105 RK_ASSERT (object_row >= 0);
106 beginRemoveRows (object_index, object_row, object_row);
107 }
108
109 if (!(updates_locked || object->isPseudoObject ())) sendListenerNotification (RObjectListener::ObjectRemoved, object, 0, 0, 0);
110
111 object->remove (removed_in_workspace);
112
113 if (view_update) endRemoveRows ();
114
115 return true;
116 }
117
moveObject(RContainerObject * parent,RObject * child,int old_index,int new_index)118 void RKModificationTracker::moveObject (RContainerObject *parent, RObject* child, int old_index, int new_index) {
119 RK_TRACE (OBJECTS);
120 RK_ASSERT (!child->isPseudoObject ());
121
122 QModelIndex parent_index;
123
124 if (!updates_locked) {
125 parent_index = indexFor (parent);
126 beginRemoveRows (parent_index, old_index, old_index);
127 }
128 RK_ASSERT (parent->findChildByIndex (old_index) == child);
129 parent->removeChildNoDelete (child);
130 if (!updates_locked) {
131 endRemoveRows ();
132
133 beginInsertRows (parent_index, new_index, new_index);
134 }
135 parent->insertChild (child, new_index);
136 RK_ASSERT (parent->findChildByIndex (new_index) == child);
137 if (!updates_locked) {
138 endInsertRows ();
139 sendListenerNotification (RObjectListener::ChildMoved, parent, old_index, new_index, 0);
140 }
141 }
142
renameObject(RObject * object,const QString & new_name)143 void RKModificationTracker::renameObject (RObject *object, const QString &new_name) {
144 RK_TRACE (OBJECTS);
145
146 object->rename (new_name);
147
148 if (!updates_locked) {
149 sendListenerNotification (RObjectListener::MetaChanged, object, 0, 0, 0);
150
151 QModelIndex object_index = indexFor (object);
152 emit (dataChanged (object_index, object_index));
153 }
154 }
155
beginAddObject(RObject * object,RObject * parent,int position)156 void RKModificationTracker::beginAddObject (RObject *object, RObject* parent, int position) {
157 RK_TRACE (OBJECTS);
158 Q_UNUSED (object); // Kept for consistency of function signature
159
160 if (!updates_locked) {
161 QModelIndex parent_index = indexFor (parent);
162 beginInsertRows (parent_index, position, position);
163 }
164 }
165
endAddObject(RObject * object,RObject * parent,int position)166 void RKModificationTracker::endAddObject (RObject *object, RObject* parent, int position) {
167 RK_TRACE (OBJECTS);
168
169 if (!updates_locked) {
170 if (!object->isPseudoObject ()) sendListenerNotification (RObjectListener::ChildAdded, parent, position, 0, 0);
171 endInsertRows ();
172 }
173 }
174
objectMetaChanged(RObject * object)175 void RKModificationTracker::objectMetaChanged (RObject *object) {
176 RK_TRACE (OBJECTS);
177
178 if (!updates_locked) {
179 sendListenerNotification (RObjectListener::MetaChanged, object, 0, 0, 0);
180
181 QModelIndex object_index = indexFor (object);
182 emit (dataChanged (object_index, object_index));
183 }
184 }
185
objectDataChanged(RObject * object,RObject::ChangeSet * changes)186 void RKModificationTracker::objectDataChanged (RObject *object, RObject::ChangeSet *changes) {
187 RK_TRACE (OBJECTS);
188
189 if (!updates_locked) {
190 sendListenerNotification (RObjectListener::DataChanged, object, 0, 0, changes);
191 delete changes;
192
193 QModelIndex object_index = indexFor (object);
194 emit (dataChanged (object_index, object_index)); // might have changed dimensions, for instance
195 }
196 }
197
addObjectListener(RObject * object,RObjectListener * listener)198 void RKModificationTracker::addObjectListener (RObject* object, RObjectListener* listener) {
199 RK_TRACE (OBJECTS);
200
201 listeners.insert (object, listener);
202 if (listener->listenerType () == RObjectListener::DataModel) object->beginEdit ();
203 }
204
removeObjectListener(RObject * object,RObjectListener * listener)205 void RKModificationTracker::removeObjectListener (RObject* object, RObjectListener* listener) {
206 RK_TRACE (OBJECTS);
207
208 listeners.remove (object, listener);
209 if (listener->listenerType () == RObjectListener::DataModel) object->endEdit ();
210 }
211
sendListenerNotification(RObjectListener::NotificationType type,RObject * o,int index,int new_index,RObject::ChangeSet * changes)212 void RKModificationTracker::sendListenerNotification (RObjectListener::NotificationType type, RObject* o, int index, int new_index, RObject::ChangeSet* changes) {
213 RK_TRACE (OBJECTS);
214
215 QList<RObjectListener*> obj_listeners = listeners.values (o);
216 for (int i = obj_listeners.size () - 1; i >= 0; --i) {
217 RObjectListener* listener = obj_listeners[i];
218 if (!listener->wantsNotificationType (type)) continue;
219
220 if (type == RObjectListener::ObjectRemoved) {
221 listener->objectRemoved (o);
222 } else if (type == RObjectListener::ChildAdded) {
223 listener->childAdded (index, o);
224 } else if (type == RObjectListener::ChildMoved) {
225 listener->childMoved (index, new_index, o);
226 } else if (type == RObjectListener::MetaChanged) {
227 listener->objectMetaChanged (o);
228 } else if (type == RObjectListener::DataChanged) {
229 listener->objectDataChanged (o, changes);
230 } else {
231 RK_ASSERT (false);
232 }
233 }
234
235 // when a container is removed, we need to send child notifications recursively, so listeners listening
236 // for child objects will know the object is gone.
237 if (type == RObjectListener::ObjectRemoved) {
238 if (o->isContainer ()) {
239 RContainerObject *c = static_cast<RContainerObject*> (o);
240 for (int i = c->numChildren () - 1; i >= 0; --i) {
241 sendListenerNotification (RObjectListener::ObjectRemoved, c->findChildByIndex (i), 0, 0, 0);
242 }
243 }
244 }
245 }
246
objectEditors(const RObject * object) const247 QList<RKEditor*> RKModificationTracker::objectEditors (const RObject* object) const {
248 RK_TRACE (OBJECTS);
249
250 QList<RKEditor*> ret;
251 QList<RObjectListener*> obj_listeners = listeners.values (const_cast<RObject*> (object));
252 for (int i = obj_listeners.size () - 1; i >= 0; --i) {
253 RObjectListener* listener = obj_listeners[i];
254 if (!(listener->listenerType () == RObjectListener::DataModel)) continue;
255
256 RKEditor* ed = static_cast<RKVarEditModel*> (listener)->getEditor ();
257 if (ed) ret.append (ed);
258 }
259
260 return ret;
261 }
262
263 ///////////////// RKObjectListModel ///////////////////////////
264
RKObjectListModel(QObject * parent)265 RKObjectListModel::RKObjectListModel (QObject *parent) : QAbstractItemModel (parent) {
266 RK_TRACE (OBJECTS);
267 }
268
~RKObjectListModel()269 RKObjectListModel::~RKObjectListModel () {
270 RK_TRACE (OBJECTS);
271 }
272
index(int row,int column,const QModelIndex & parent) const273 QModelIndex RKObjectListModel::index (int row, int column, const QModelIndex& parent) const {
274 RK_TRACE (OBJECTS);
275 if (!parent.isValid ()) {
276 RK_ASSERT (row < 2);
277 // must cast to RObject, here. Else casting to void* and back will confuse the hell out of GCC 4.2
278 if (row == 0) return (createIndex (0, column, static_cast<RObject *> (RObjectList::getGlobalEnv ())));
279 if (row == 1) return (createIndex (1, column, static_cast<RObject *> (RObjectList::getObjectList ())));
280 RK_ASSERT (false);
281 return QModelIndex ();
282 }
283 RObject* parent_object = static_cast<RObject*> (parent.internalPointer ());
284
285 RK_ASSERT (row < parent_object->numChildrenForObjectModel ());
286
287 return (createIndex (row, column, parent_object->findChildByObjectModelIndex (row)));
288 }
289
parent(const QModelIndex & index) const290 QModelIndex RKObjectListModel::parent (const QModelIndex& index) const {
291 RK_TRACE (OBJECTS);
292
293 if (!index.isValid ()) return QModelIndex ();
294 RObject* child = static_cast<RObject*> (index.internalPointer ());
295 RK_ASSERT (child);
296 if (child == RObjectList::getGlobalEnv ()) return QModelIndex ();
297 return (indexFor (child->parentObject ()));
298 }
299
rowCount(const QModelIndex & parent) const300 int RKObjectListModel::rowCount (const QModelIndex& parent) const {
301 RK_TRACE (OBJECTS);
302
303 RObject* parent_object = 0;
304 if (parent.isValid ()) parent_object = static_cast<RObject*> (parent.internalPointer ());
305 else return 2; // the root item
306
307 if (!parent_object) return 0;
308 return (parent_object->numChildrenForObjectModel ());
309 }
310
columnCount(const QModelIndex &) const311 int RKObjectListModel::columnCount (const QModelIndex&) const {
312 //RK_TRACE (OBJECTS); // no need to trace this
313
314 return ColumnCount;
315 }
316
data(const QModelIndex & index,int role) const317 QVariant RKObjectListModel::data (const QModelIndex& index, int role) const {
318 RK_TRACE (OBJECTS);
319
320 int col = index.column ();
321 RObject *object = static_cast<RObject*> (index.internalPointer ());
322
323 if (!object) {
324 RK_ASSERT (object);
325 return QVariant ();
326 }
327
328 if (role == Qt::DisplayRole) {
329 if (col == NameColumn) return object->getShortName ();
330 if (col == LabelColumn) return object->getLabel ();
331 if (col == TypeColumn) {
332 if (object->isVariable ()) return RObject::typeToText (object->getDataType ());
333 return QVariant ();
334 }
335 if ((col == ClassColumn) && (!object->isPseudoObject ())) return object->makeClassString ("; ");
336 } else if (role == Qt::FontRole) {
337 if (col == NameColumn && object->isPseudoObject ()) {
338 QFont font;
339 font.setItalic (true);
340 return (font);
341 }
342 } else if (role == Qt::DecorationRole) {
343 if (col == NameColumn) return RKStandardIcons::iconForObject (object);
344 } else if (role == Qt::ToolTipRole) {
345 return object->getObjectDescription ();
346 }
347
348 RK_ASSERT (col < columnCount ());
349 return QVariant ();
350 }
351
headerData(int section,Qt::Orientation orientation,int role) const352 QVariant RKObjectListModel::headerData (int section, Qt::Orientation orientation, int role) const {
353 RK_TRACE (OBJECTS);
354
355 if (orientation != Qt::Horizontal) return QVariant ();
356 if (role != Qt::DisplayRole) return QVariant ();
357
358 if (section == NameColumn) return i18n ("Name");
359 if (section == LabelColumn) return i18n ("Label");
360 if (section == TypeColumn) return i18n ("Type");
361 if (section == ClassColumn) return i18n ("Class");
362
363 RK_ASSERT (false);
364 return QVariant ();
365 }
366
hasChildren(const QModelIndex & parent) const367 bool RKObjectListModel::hasChildren(const QModelIndex& parent) const {
368 RK_TRACE (OBJECTS);
369
370 RObject* parent_object = 0;
371 if (parent.isValid ()) parent_object = static_cast<RObject*> (parent.internalPointer ());
372 else return true; // the root item
373
374 if (!parent_object) return false;
375 return (parent_object->isType (RObject::Incomplete) || parent_object->numChildrenForObjectModel ());
376 }
377
canFetchMore(const QModelIndex & parent) const378 bool RKObjectListModel::canFetchMore (const QModelIndex &parent) const {
379 RK_TRACE (OBJECTS);
380
381 RObject *object = static_cast<RObject*> (parent.internalPointer ());
382 return (object && object->isType (RObject::Incomplete));
383 }
384
fetchMore(const QModelIndex & parent)385 void RKObjectListModel::fetchMore (const QModelIndex &parent) {
386 RK_TRACE (OBJECTS);
387
388 RObject *object = static_cast<RObject*> (parent.internalPointer ());
389 RK_ASSERT (object && object->isType (RObject::Incomplete));
390 object->fetchMoreIfNeeded ();
391 }
392
indexFor(RObject * object) const393 QModelIndex RKObjectListModel::indexFor (RObject *object) const {
394 RK_TRACE (OBJECTS);
395
396 if (!object) return QModelIndex ();
397 if (object->isType (RObject::NonVisibleObject)) return QModelIndex ();
398
399 RObject *parent = object->parentObject ();
400 // must cast to RObject, here. Else casting to void* and back will confuse the hell out of GCC 4.2
401 if (!parent) {
402 if (object == RObjectList::getObjectList ()) {
403 return createIndex (1, 0, static_cast<RObject*> (RObjectList::getObjectList ()));
404 } else {
405 RK_ASSERT (object == RObjectList::getGlobalEnv ());
406 return createIndex (0, 0, static_cast<RObject*> (RObjectList::getGlobalEnv ()));
407 }
408 }
409
410 int row = parent->getObjectModelIndexOf (object);
411 if (row < 0) {
412 RK_ASSERT (false);
413 return QModelIndex ();
414 }
415
416 return (createIndex (row, 0, object));
417 }
418
419
420 ///////////////////// RObjectListener ////////////////////////
421
RObjectListener(ListenerType type)422 RObjectListener::RObjectListener (ListenerType type) {
423 RK_TRACE (OBJECTS);
424
425 RObjectListener::type = type;
426 notifications = 0;
427 num_watched_objects = 0;
428 }
429
~RObjectListener()430 RObjectListener::~RObjectListener () {
431 RK_TRACE (OBJECTS);
432
433 RK_ASSERT (num_watched_objects == 0);
434 }
435
objectRemoved(RObject *)436 void RObjectListener::objectRemoved (RObject*) {
437 RK_ASSERT (false); // listeners that receive this notification should have reimplemented this function
438 }
439
childAdded(int,RObject *)440 void RObjectListener::childAdded (int, RObject*) {
441 RK_ASSERT (false); // listeners that receive this notification should have reimplemented this function
442 }
443
childMoved(int,int,RObject *)444 void RObjectListener::childMoved (int, int, RObject*) {
445 RK_ASSERT (false); // listeners that receive this notification should have reimplemented this function
446 }
447
objectMetaChanged(RObject *)448 void RObjectListener::objectMetaChanged (RObject*) {
449 RK_ASSERT (false); // listeners that receive this notification should have reimplemented this function
450 }
451
objectDataChanged(RObject *,const RObject::ChangeSet *)452 void RObjectListener::objectDataChanged (RObject*, const RObject::ChangeSet *) {
453 RK_ASSERT (false); // listeners that receive this notification should have reimplemented this function
454 }
455
listenForObject(RObject * object)456 void RObjectListener::listenForObject (RObject* object) {
457 RK_TRACE (OBJECTS);
458
459 RKGlobals::tracker ()->addObjectListener (object, this);
460 ++num_watched_objects;
461 }
462
stopListenForObject(RObject * object)463 void RObjectListener::stopListenForObject (RObject* object) {
464 RK_TRACE (OBJECTS);
465
466 RKGlobals::tracker ()->removeObjectListener (object, this);
467 --num_watched_objects;
468 }
469