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