1 /***************************************************************************
2                           robject  -  description
3                              -------------------
4     begin                : Thu Aug 19 2004
5     copyright            : (C) 2004-2019 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 
18 
19 #include "robject.h"
20 
21 #include <qregexp.h>
22 #include <KLocalizedString>
23 
24 #include "../rbackend/rkrinterface.h"
25 #include "../rbackend/rkrbackendprotocol_shared.h"
26 #include "../rkglobals.h"
27 #include "robjectlist.h"
28 #include "rcontainerobject.h"
29 #include "rkpseudoobjects.h"
30 #include "rkvariable.h"
31 #include "renvironmentobject.h"
32 #include "rfunctionobject.h"
33 #include "rkmodificationtracker.h"
34 #include "rkrownames.h"
35 
36 #include "../debug.h"
37 
38 namespace RObjectPrivate {
39 	QVector<qint32> dim_null (1, 0);
40 }
41 
42 // static
43 QHash<const RObject*, RObject::PseudoObjectType> RObject::pseudo_object_types;
44 QHash<const RObject*, RSlotsPseudoObject*> RObject::slots_objects;
45 QHash<const RObject*, REnvironmentObject*> RObject::namespace_objects;
46 QHash<const RObject*, RKRowNames*> RObject::rownames_objects;
47 
RObject(RObject * parent,const QString & name)48 RObject::RObject (RObject *parent, const QString &name) {
49 	RK_TRACE (OBJECTS);
50 
51 	RObject::parent = parent;
52 	RObject::name = name;
53 	type = 0;
54 	meta_map = 0;
55 	contained_objects = 0;
56 	dimensions = RObjectPrivate::dim_null;	// safe initialization
57 }
58 
~RObject()59 RObject::~RObject () {
60 	RK_TRACE (OBJECTS);
61 
62 	cancelOutstandingCommands ();
63 	if (hasPseudoObject (SlotsObject)) delete slots_objects.take (this);
64 	if (hasPseudoObject (NamespaceObject)) delete namespace_objects.take (this);
65 	if (hasPseudoObject (RowNamesObject)) delete rownames_objects.take (this);
66 }
67 
irregularShortName(const QString & name)68 bool RObject::irregularShortName (const QString &name) {
69 	// no trace
70 	static const QRegExp invalidChars ("[^a-zA-z0-9\\._]");
71 	return (name.contains (invalidChars));
72 }
73 
getFullName(int options) const74 QString RObject::getFullName (int options) const {
75 	RK_TRACE (OBJECTS);
76 	return parent->makeChildName (RObject::name, type & Misplaced, options);
77 }
78 
getLabel() const79 QString RObject::getLabel () const {
80 	RK_TRACE (OBJECTS);
81 	return getMetaProperty ("label");
82 }
83 
findObjects(const QStringList & path,bool partial,const QString & op)84 RObject::ObjectList RObject::findObjects (const QStringList &path, bool partial, const QString &op) {
85 	RK_TRACE (OBJECTS);
86 	// not a container
87 	if (op == "@") {
88 		if (slotsPseudoObject ()) return (slotsPseudoObject ()->findObjects (path, partial, "$"));
89 	}
90 	return ObjectList();
91 }
92 
getMetaProperty(const QString & id) const93 QString RObject::getMetaProperty (const QString &id) const {
94 	RK_TRACE (OBJECTS);
95 	if (meta_map) return (meta_map->value (id));
96 	return QString ();
97 }
98 
getDescription() const99 QString RObject::getDescription () const {
100 	RK_TRACE (OBJECTS);
101 	if (meta_map) {
102 		QString label = meta_map->value ("label");
103 		if (!label.isEmpty ()) return (getShortName () + " (" + label + ')');
104 	}
105 	return getShortName ();
106 }
107 
getObjectDescription() const108 QString RObject::getObjectDescription () const {
109 	RK_TRACE (OBJECTS);
110 
111 #define ESCS replace ('<', "&lt;")
112 
113 	QString ret;
114 	ret.append ("<i>" + getShortName ().ESCS + "</i>");
115 	ret.append ("<br><b>" + i18n ("Full location:") + " </b>" + getFullName ().ESCS);
116 	QString lab = getLabel ();
117 	if (!lab.isEmpty ()) ret.append ("<br><b>" + i18n ("Label:") + " </b>" + lab.ESCS);
118 	ret.append ("<br><b>" + i18n ("Type:") + " </b>");
119 
120 	if (isType (Function)) {
121 		ret.append (i18n ("Function"));
122 		ret.append ("<br><b>" + i18n ("Usage: ") + " </b>" + getShortName ().ESCS + '(' + static_cast<const RFunctionObject *> (this)->printArgs ().ESCS + ')');
123 	} else if (isType (DataFrame)) {
124 		ret.append (i18n ("Data frame"));
125 	} else if (isType (Array)) {
126 		ret.append (i18n ("Array"));
127 	} else if (isType (Matrix)) {
128 		ret.append (i18n ("Matrix"));
129 	} else if (isType (List)) {
130 		ret.append (i18n ("List"));
131 	} else if (isType (Variable)) {
132 		ret.append (i18n ("Variable"));
133 		ret.append ("<br><b>" + i18n ("Data Type:") + " </b>" + typeToText (getDataType ()));
134 	} else if (isType (Environment)) {
135 		ret.append (i18n ("Environment"));
136 	}
137 
138 	if (isType (Container | Variable)) {
139 		if (dimensions.size () == 1) {
140 			ret.append ("<br><b>" + i18n ("Length: ") + " </b>"  + QString::number (dimensions[0]));
141 		} else if (dimensions.size () > 1) {
142 			ret.append ("<br><b>" + i18n ("Dimensions: ") + " </b>");
143 			for (int i=0; i < dimensions.size (); ++i) {
144 				if (i) ret.append (", ");
145 				ret.append (QString::number (dimensions[i]));
146 			}
147 		}
148 	}
149 
150 	ret.append ("<br><b>" + i18n ("Class(es):") + " </b>" + makeClassString (",").ESCS);
151 
152 	return ret;
153 }
154 
setLabel(const QString & value,bool sync)155 void RObject::setLabel (const QString &value, bool sync) {
156 	RK_TRACE (OBJECTS);
157 	setMetaProperty ("label", value, sync);
158 }
159 
setMetaProperty(const QString & id,const QString & value,bool sync)160 void RObject::setMetaProperty (const QString &id, const QString &value, bool sync) {
161 	RK_TRACE (OBJECTS);
162 	if (value.isEmpty ()) {
163 		if (meta_map && meta_map->contains (id)) meta_map->remove (id);
164 		else return;
165 	} else {
166 		if (!meta_map) meta_map = new MetaMap;
167 		else if (meta_map->value (id) == value) return;
168 
169 		meta_map->insert (id, value);
170 	}
171 
172 	if (sync) writeMetaData (0);
173 	RKGlobals::tracker ()->objectMetaChanged (this);
174 }
175 
makeClassString(const QString & sep) const176 QString RObject::makeClassString (const QString &sep) const {
177 	RK_TRACE (OBJECTS);
178 	return (classnames.join (sep));
179 }
180 
inherits(const QString & class_name) const181 bool RObject::inherits (const QString &class_name) const {
182 	RK_TRACE (OBJECTS);
183 
184 	return (classnames.contains (class_name));
185 }
186 
makeChildName(const QString & short_child_name,bool,int options) const187 QString RObject::makeChildName (const QString &short_child_name, bool, int options) const {
188 	RK_TRACE (OBJECTS);
189 	if (options & DollarExpansion) {
190 		if (irregularShortName (short_child_name)) return (getFullName (options) + '$' + rQuote (short_child_name));
191 		return (getFullName (options) + '$' + short_child_name);  // Do not return list$"member", unless necessary
192 	}
193 	return (getFullName (options) + "[[" + rQuote (short_child_name) + "]]");
194 }
195 
writeMetaData(RCommandChain * chain)196 void RObject::writeMetaData (RCommandChain *chain) {
197 	RK_TRACE (OBJECTS);
198 
199 	if (!meta_map) return;
200 
201 	QString map_string;
202 	if (meta_map->isEmpty ()) {
203 		map_string.append ("NULL");
204 
205 		delete meta_map;	// now that is is synced, delete it
206 		meta_map = 0;
207 	} else {
208 		for (MetaMap::const_iterator it = meta_map->constBegin (); it != meta_map->constEnd (); ++it) {
209 			if (!map_string.isEmpty ()) map_string.append (", ");
210 			map_string.append (rQuote (it.key ()) + '=' + rQuote (it.value ()));
211 		}
212 		map_string = "c (" + map_string + ')';
213 	}
214 
215 	RCommand *command = new RCommand (".rk.set.meta (" + getFullName () + ", " + map_string + ')', RCommand::App | RCommand::Sync);
216 	RKGlobals::rInterface ()->issueCommand (command, chain);
217 }
218 
updateFromR(RCommandChain * chain)219 void RObject::updateFromR (RCommandChain *chain) {
220 	RK_TRACE (OBJECTS);
221 
222 	RCommand *command;
223 	if (parentObject () == RObjectList::getGlobalEnv ()) {
224 #ifdef __GNUC__
225 #	warning TODO: find a generic solution
226 #endif
227 // We handle objects directly in .GlobalEnv differently. That's to avoid forcing promises, when addressing the object directly. In the long run, .rk.get.structure should be reworked to simply not need the value-argument in any case.
228 		 command = new RCommand (".rk.get.structure.global (" + rQuote (getShortName ()) + ')', RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, ROBJECT_UDPATE_STRUCTURE_COMMAND);
229 	} else {
230 		RK_ASSERT (false);	// non-catastrophic, but do we get here?
231 
232 		command = new RCommand (".rk.get.structure (" + getFullName () + ", " + rQuote (getShortName ()) + ')', RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, ROBJECT_UDPATE_STRUCTURE_COMMAND);
233 	}
234 	RKGlobals::rInterface ()->issueCommand (command, chain);
235 
236 	type |= Updating;	// will be cleared, implicitly, when the new structure gets set
237 }
238 
fetchMoreIfNeeded(int levels)239 void RObject::fetchMoreIfNeeded (int levels) {
240 	RK_TRACE (OBJECTS);
241 
242 	if (isType (Updating)) return;
243 	if (isType (Incomplete)) {
244 		updateFromR (0);
245 		return;
246 	}
247 	RSlotsPseudoObject *spo = slotsPseudoObject ();
248 	if (spo) spo->fetchMoreIfNeeded (levels);
249 	// Note: We do NOT do the same for any namespaceEnvironment, deliberately
250 	if (levels <= 0) return;
251 	if (!isContainer ()) return;
252 	const RObjectMap children = static_cast<RContainerObject*> (this)->childmap;
253 	foreach (RObject* child, children) {
254 		child->fetchMoreIfNeeded (levels - 1);
255 	}
256 }
257 
rCommandDone(RCommand * command)258 void RObject::rCommandDone (RCommand *command) {
259 	RK_TRACE (OBJECTS);
260 
261 	if (command->getFlags () == ROBJECT_UDPATE_STRUCTURE_COMMAND) {
262 		if (command->failed ()) {
263 			RK_DEBUG (OBJECTS, DL_INFO, "command failed while trying to update object '%s'. No longer present?", getShortName ().toLatin1 ().data ());
264 			// this may happen, if the object has been removed in the workspace in between
265 			RKGlobals::tracker ()->removeObject (this, 0, true);
266 			return;
267 		}
268 		if (parent && parent->isContainer ()) static_cast<RContainerObject*> (parent)->updateChildStructure (this, command);		// this may result in a delete, so nothing after this!
269 		else updateStructure (command);		// no (container) parent can happen for RObjectList and pseudo objects
270 		return;
271 	} else {
272 		RK_ASSERT (false);
273 	}
274 }
275 
updateStructure(RData * new_data)276 bool RObject::updateStructure (RData *new_data) {
277 	RK_TRACE (OBJECTS);
278 	if (new_data->getDataLength () == 0) { // can happen, if the object no longer exists
279 		return false;
280 	}
281 
282 	RK_ASSERT (new_data->getDataLength () >= StorageSizeBasicInfo);
283 	RK_ASSERT (new_data->getDataType () == RData::StructureVector);
284 
285 	if (!canAccommodateStructure (new_data)) return false;
286 
287 	if (isPending ()) {
288 		type -= Pending;
289 		return true;	// Do not update any info for pending objects
290 	}
291 
292 	bool properties_change = false;
293 
294 	RData::RDataStorage new_data_data = new_data->structureVector ();
295 	properties_change = updateName (new_data_data.at (StoragePositionName));
296 	properties_change = updateType (new_data_data.at (StoragePositionType));
297 	properties_change = updateClasses (new_data_data.at (StoragePositionClass));
298 	properties_change = updateMeta (new_data_data.at (StoragePositionMeta));
299 	properties_change = updateDimensions (new_data_data.at (StoragePositionDims));
300 	properties_change = updateSlots (new_data_data.at (StoragePositionSlots));
301 
302 	if (properties_change) RKGlobals::tracker ()->objectMetaChanged (this);
303 	if (type & NeedDataUpdate) updateDataFromR (0);
304 
305 	if (type & Incomplete) {
306 		// If the (new!) type is "Incomplete", it means, the structure getter simply stopped at this point.
307 		// In case we already have child info, we should update it (TODO: perhaps only, if anything is listening for child objects?)
308 		if (numChildrenForObjectModel () && (!isType (Updating))) updateFromR (0);
309 		return true;
310 	}
311 
312 	return true;
313 }
314 
315 //virtual
updateDataFromR(RCommandChain *)316 void RObject::updateDataFromR (RCommandChain *) {
317 	RK_TRACE (OBJECTS);
318 
319 	type -= (type & NeedDataUpdate);
320 }
321 
markDataDirty()322 void RObject::markDataDirty () {
323 	RK_TRACE (OBJECTS);
324 
325 	type |= NeedDataUpdate;
326 	if (isContainer ()) {
327 	    RContainerObject* this_container = static_cast<RContainerObject*> (this);
328 		RObjectMap children = this_container->childmap;
329 		for (int i = children.size () - 1; i >= 0; --i) {
330 			children[i]->markDataDirty ();
331 		}
332 		if (this_container->hasPseudoObject (RowNamesObject)) this_container->rowNames ()->markDataDirty ();
333 	}
334 }
335 
canAccommodateStructure(RData * new_data)336 bool RObject::canAccommodateStructure (RData *new_data) {
337 	RK_TRACE (OBJECTS);
338 	RK_ASSERT (new_data->getDataLength () >= StorageSizeBasicInfo);
339 	RK_ASSERT (new_data->getDataType () == RData::StructureVector);
340 
341 	RData::RDataStorage new_data_data = new_data->structureVector ();
342 	if (!isValidName (new_data_data.at (StoragePositionName))) return false;
343 	if (!isValidType (new_data_data.at (StoragePositionType))) return false;
344 	return true;
345 }
346 
isValidName(RData * new_data)347 bool RObject::isValidName (RData *new_data) {
348 	RK_TRACE (OBJECTS);
349 	RK_ASSERT (new_data->getDataLength () == 1);
350 	RK_ASSERT (new_data->getDataType () == RData::StringVector);
351 
352 	QString new_name = new_data->stringVector ().at (0);
353 	if (name != new_name) {
354 		RK_ASSERT (false);
355 		name = new_name;
356 		return false;
357 	}
358 	return true;
359 }
360 
updateName(RData * new_data)361 bool RObject::updateName (RData *new_data) {
362 	RK_TRACE (OBJECTS);
363 	RK_ASSERT (new_data->getDataLength () == 1);
364 	RK_ASSERT (new_data->getDataType () == RData::StringVector);
365 
366 	bool changed = false;
367 	QString new_name = new_data->stringVector ().at (0);
368 	if (name != new_name) {
369 		changed = true;
370 		RK_ASSERT (false);
371 		name = new_name;
372 	}
373 	return changed;
374 }
375 
isValidType(RData * new_data) const376 bool RObject::isValidType (RData *new_data) const {
377 	RK_TRACE (OBJECTS);
378 	RK_ASSERT (new_data->getDataLength () == 1);
379 	RK_ASSERT (new_data->getDataType () == RData::IntVector);
380 
381 	int new_type = new_data->intVector ().at (0);
382 	if (!isMatchingType (type, new_type)) return false;
383 
384 	return true;
385 }
386 
updateType(RData * new_data)387 bool RObject::updateType (RData *new_data) {
388 	RK_TRACE (OBJECTS);
389 	RK_ASSERT (new_data->getDataLength () == 1);
390 	RK_ASSERT (new_data->getDataType () == RData::IntVector);
391 
392 	bool changed = false;
393 	int new_type = new_data->intVector ().at (0);
394 	if (type & PseudoObject) new_type |= PseudoObject;
395 	if (type & Misplaced) new_type |= Misplaced;
396 	if (type & Pending) new_type |= Pending;	// NOTE: why don't we just clear the pending flag, here? Well, we don't want to generate a change notification for this. TODO: rethink the logic, and maybe use an appropriate mask
397 	if (type & NeedDataUpdate) new_type |= NeedDataUpdate;
398 	if (type != new_type) {
399 		changed = true;
400 		type = new_type;
401 	}
402 	return changed;
403 }
404 
updateClasses(RData * new_data)405 bool RObject::updateClasses (RData *new_data) {
406 	RK_TRACE (OBJECTS);
407 	RK_ASSERT (new_data->getDataLength () >= 1);		// or can there be classless objects in R?
408 	RK_ASSERT (new_data->getDataType () == RData::StringVector);
409 
410 	bool change = false;
411 
412 	QStringList new_classes = new_data->stringVector ();
413 	if (new_classes != classnames) {
414 		change = true;
415 		classnames = new_classes;
416 	}
417 
418 	return change;
419 }
420 
updateMeta(RData * new_data)421 bool RObject::updateMeta (RData *new_data) {
422 	RK_TRACE (OBJECTS);
423 
424 	RK_ASSERT (new_data->getDataType () == RData::StringVector);
425 
426 	QStringList data= new_data->stringVector ();
427 	int len = data.size ();
428 	bool change = false;
429 	if (len) {
430 		if (!meta_map) meta_map = new MetaMap;
431 		else meta_map->clear ();
432 
433 		RK_ASSERT (!(len % 2));
434 		int cut = len/2;
435 		for (int i=0; i < cut; ++i) {
436 			meta_map->insert (data.at (i), data.at (i+cut));
437 		}
438 
439 		// TODO: only signal change, if there really was a change!
440 		change = true;
441 	} else {		// no meta data received
442 		if (meta_map) {
443 			delete meta_map;
444 			meta_map = 0;
445 			change = true;
446 		}
447 	}
448 	return change;
449 }
450 
updateDimensions(RData * new_data)451 bool RObject::updateDimensions (RData *new_data) {
452 	RK_TRACE (OBJECTS);
453 	RK_ASSERT (new_data->getDataLength () >= 1);
454 	RK_ASSERT (new_data->getDataType () == RData::IntVector);
455 
456 	QVector<qint32> new_dimensions = new_data->intVector ();
457 	if (new_dimensions != dimensions) {
458 		if (new_dimensions.isEmpty ()) {
459 			if (dimensions != RObjectPrivate::dim_null) {
460 				dimensions = RObjectPrivate::dim_null;
461 				return (true);
462 			}
463 		} else {
464 #ifdef __GNUC__
465 #	warning TODO: ugly hack. Should be moved to RKVariable, somehow.
466 #endif
467 			if (type & Variable) static_cast<RKVariable*> (this)->extendToLength (new_dimensions[0]);
468 
469 			dimensions = new_dimensions;
470 			return (true);
471 		}
472 	}
473 	return (false);
474 }
475 
updateSlots(RData * new_data)476 bool RObject::updateSlots (RData *new_data) {
477 	RK_TRACE (OBJECTS);
478 
479 	if (new_data->getDataLength ()) {
480 		RK_ASSERT (new_data->getDataType () == RData::StructureVector);
481 		bool added = false;
482 		RSlotsPseudoObject *spo = slotsPseudoObject ();
483 		if (!spo) {
484 			spo = new RSlotsPseudoObject (this);
485 			added = true;
486 			RKGlobals::tracker ()->lockUpdates (true);
487 		}
488 		bool ret = spo->updateStructure (new_data->structureVector ().at (0));
489 		if (added) {
490 			RKGlobals::tracker ()->lockUpdates (false);
491 			setSpecialChildObject (spo, SlotsObject);
492 		}
493 		return ret;
494 	} else if (slotsPseudoObject ()) {
495 		setSpecialChildObject (0, SlotsObject);
496 	}
497 	return false;
498 }
499 
getObjectModelIndexOf(RObject * child) const500 int RObject::getObjectModelIndexOf (RObject *child) const {
501 	RK_TRACE (OBJECTS);
502 
503 	int offset = 0;
504 	if (isContainer ()) {
505 		int pos = static_cast<const RContainerObject*> (this)->childmap.indexOf (child);
506 		if (pos >= 0) return pos + offset;
507 		offset += static_cast<const RContainerObject*> (this)->childmap.size ();
508 	}
509 	if (hasPseudoObject (SlotsObject)) {
510 		if (child == slotsPseudoObject ()) return offset;
511 		offset += 1;
512 	}
513 	if (hasPseudoObject (NamespaceObject)) {
514 		if (child == namespaceEnvironment ()) return offset;
515 		offset += 1;
516 	}
517 	if (isType (Workspace)) {
518 		if (child == static_cast<const RObjectList*> (this)->orphanNamespacesObject ()) return offset;
519 		offset += 1;
520 	}
521 	return -1;
522 }
523 
numChildrenForObjectModel() const524 int RObject::numChildrenForObjectModel () const {
525 	RK_TRACE (OBJECTS);
526 
527 	int ret = isContainer () ? static_cast<const RContainerObject*>(this)->numChildren () : 0;
528 	if (hasPseudoObject (SlotsObject)) ret += 1;
529 	if (hasPseudoObject (NamespaceObject)) ret += 1;
530 	if (isType (Workspace)) ret += 1;	// for the RKOrphanNamespacesObject
531 	return ret;
532 }
533 
findChildByObjectModelIndex(int index) const534 RObject *RObject::findChildByObjectModelIndex (int index) const {
535 	if (isContainer ()) {
536 		const RContainerObject *container = static_cast<const RContainerObject*>(this);
537 		if (index < container->numChildren ()) return container->findChildByIndex (index);
538 		index -= container->numChildren ();
539 	}
540 	if (hasPseudoObject (SlotsObject)) {
541 		if (index == 0) return slotsPseudoObject ();
542 		--index;
543 	}
544 	if (hasPseudoObject (NamespaceObject)) {
545 		if (index == 0) return namespaceEnvironment ();
546 		--index;
547 	}
548 	if (isType (Workspace)) {
549 		if (index == 0) return static_cast<const RObjectList *> (this)->orphanNamespacesObject ();
550 		--index;
551 	}
552 	return 0;
553 }
554 
editors() const555 QList <RKEditor*> RObject::editors () const {
556 	return (RKGlobals::tracker ()->objectEditors (this));
557 }
558 
rename(const QString & new_short_name)559 void RObject::rename (const QString &new_short_name) {
560 	RK_TRACE (OBJECTS);
561 	RK_ASSERT (canRename ());
562 	static_cast<RContainerObject*> (parent)->renameChild (this, new_short_name);
563 }
564 
setSpecialChildObject(RObject * special,PseudoObjectType special_type)565 void RObject::setSpecialChildObject (RObject* special, PseudoObjectType special_type) {
566 	RK_TRACE (OBJECTS);
567 
568 	RObject *old_special = 0;
569 	if (special_type == SlotsObject) old_special = slotsPseudoObject ();
570 	else if (special_type == NamespaceObject) old_special = namespaceEnvironment ();
571 	else if (special_type == RowNamesObject) old_special = rownames_objects.value (this);
572 	else RK_ASSERT (false);
573 
574 	if (special == old_special) return;
575 
576 	if (old_special) {
577 		RKGlobals::tracker ()->removeObject (old_special, 0, true);
578 		RK_ASSERT (!hasPseudoObject (special_type));	// should have been removed in the above statement via RObject::remove()
579 	}
580 
581 	if (special) {
582 		if (special_type == SlotsObject) slots_objects.insert (this, static_cast<RSlotsPseudoObject*> (special));
583 		else if (special_type == NamespaceObject) namespace_objects.insert (this, static_cast<REnvironmentObject*> (special));
584 		else if (special_type == RowNamesObject) rownames_objects.insert (this, static_cast<RKRowNames*> (special));
585 		contained_objects |= special_type;
586 
587 		if (special->isType (NonVisibleObject)) return;
588 
589 		int index = getObjectModelIndexOf (special);
590 		// HACK: Newly added object must not be included in the index before beginAddObject (but must be included above for getObjectModelIncexOf() to work)
591 		contained_objects -= special_type;
592 		RKGlobals::tracker ()->beginAddObject (special, this, index);
593 		contained_objects |= special_type;
594 		RKGlobals::tracker ()->endAddObject (special, this, index);
595 	}
596 }
597 
remove(bool removed_in_workspace)598 void RObject::remove (bool removed_in_workspace) {
599 	RK_TRACE (OBJECTS);
600 	RK_ASSERT (canRemove () || removed_in_workspace);
601 
602 	if (isPseudoObject ()) {
603 		RK_ASSERT (removed_in_workspace);
604 		PseudoObjectType type = getPseudoObjectType ();
605 		if (parent->hasPseudoObject (type)) {	// not always true for NamespaceObjects, which the RKOrphanNamespacesObject keeps as regular children!
606 			if (type == SlotsObject) slots_objects.remove (parent);
607 			else if (type == NamespaceObject) namespace_objects.remove (parent);
608 			else if (type == RowNamesObject) rownames_objects.remove (parent);
609 			parent->contained_objects -= type;
610 			delete this;
611 			return;
612 		}
613 	}
614 
615 	static_cast<RContainerObject*> (parent)->removeChild (this, removed_in_workspace);
616 }
617 
618 //static
typeToText(RDataType var_type)619 QString RObject::typeToText (RDataType var_type) {
620 	// TODO: These are non-i18n, and not easily i18n-able due to being used, internally.
621 	// But they _are_ display strings, too.
622 	if (var_type == DataUnknown) {
623 		return "Unknown";
624 	} else if (var_type == DataNumeric) {
625 		return "Numeric";
626 	} else if (var_type == DataCharacter) {
627 		return "String";
628 	} else if (var_type == DataFactor) {
629 		return "Factor";
630 	} else if (var_type == DataLogical) {
631 		return "Logical";
632 	} else {
633 		RK_ASSERT (false);
634 		return "Invalid";
635 	}
636 }
637 
638 //static
textToType(const QString & text)639 RObject::RDataType RObject::textToType (const QString &text) {
640 	if (text == "Unknown") {
641 		return DataUnknown;
642 	} else if (text == "Numeric") {
643 		return DataNumeric;
644 	} else if (text == "String") {
645 		return DataCharacter;
646 	} else if (text == "Factor") {
647 		return DataFactor;
648 	} else if (text == "Logical") {
649 		return DataLogical;
650 	} else {
651 		RK_ASSERT (false);
652 		return DataUnknown;
653 	}
654 }
655 
656 //static
rQuote(const QString & string)657 QString RObject::rQuote (const QString &string) {
658 	return (RKRSharedFunctionality::quote (string));
659 }
660 
661 //static
parseObjectPath(const QString & path)662 QStringList RObject::parseObjectPath (const QString &path) {
663 	RK_TRACE (OBJECTS);
664 
665 	QStringList ret;
666 	QString fragment;
667 
668 	int end = path.length ();
669 	QChar quote_char;
670 	bool escaped = false;
671 	bool seek_bracket_end = false;
672 	for (int i = 0; i < end; ++i) {
673 		QChar c = path.at (i);
674 		if (quote_char.isNull ()) {
675 			if (c == '\'' || c == '\"' || c == '`') {
676 				quote_char = c;
677 			} else {
678 				if (!seek_bracket_end) {
679 					if (c == '$') {
680 						ret.append (fragment);
681 						ret.append ("$");
682 						fragment.clear ();
683 					} else if (c == '[') {
684 						ret.append (fragment);
685 						ret.append ("$");
686 						fragment.clear ();
687 						if ((i+1 < end) && (path.at (i+1) == '[')) ++i;
688 						seek_bracket_end = true;
689 					} else if (c == ':') {
690 						ret.append (fragment);
691 						if ((i+1 < end) && (path.at (i+1) == ':')) ++i;
692 						if ((i+1 < end) && (path.at (i+1) == ':')) {
693 							++i;
694 							ret.append (":::");
695 						} else ret.append ("::");
696 						fragment.clear ();
697 					} else if (c == '@') {
698 						ret.append (fragment);
699 						ret.append ("@");
700 						fragment.clear ();
701 					} else {
702 						fragment.append (c);
703 					}
704 				} else {
705 					if (c == ']') {
706 						if ((i+1 < end) && (path.at (i+1) == ']')) ++i;
707 						seek_bracket_end = false;
708 						continue;
709 					} else {
710 						fragment.append (c);
711 					}
712 				}
713 			}
714 		} else {	// inside a quote
715 			if (c == '\\') escaped = !escaped;
716 			else {
717 				if (escaped) {
718 					if (c == 't') fragment.append ('\t');
719 					else if (c == 'n') fragment.append ('\n');
720 					else fragment.append ('\\' + c);
721 				} else {
722 					if (c == quote_char) {
723 						quote_char = QChar ();
724 					} else {
725 						fragment.append (c);
726 					}
727 				}
728 			}
729 		}
730 	}
731 	if (!fragment.isEmpty ()) ret.append (fragment);
732 	RK_DEBUG (OBJECTS, DL_DEBUG, "parsed object path %s into %s", qPrintable (path), qPrintable (ret.join ("-")));
733 	return ret;
734 }
735 
736 //virtual
beginEdit()737 void RObject::beginEdit () {
738 	RK_ASSERT (false);
739 }
740 
741 //virtual
endEdit()742 void RObject::endEdit () {
743 	RK_ASSERT (false);
744 }
745 
canWrite() const746 bool RObject::canWrite () const {
747 	RK_TRACE (OBJECTS);
748 
749 	// TODO: find out, if binding is locked:
750 	// if (isLocked ()) return false;
751 	return (isInGlobalEnv ());
752 }
753 
canRead() const754 bool RObject::canRead () const {
755 	RK_TRACE (OBJECTS);
756 
757 	return (this != RObjectList::getObjectList ());
758 }
759 
canRename() const760 bool RObject::canRename () const {
761 	RK_TRACE (OBJECTS);
762 
763 	if (isPseudoObject ()) return false;
764 	if (parent && parent->isSlotsPseudoObject ()) return false;
765 	// TODO: find out, if binding is locked:
766 	// if (isLocked ()) return false;
767 	return (isInGlobalEnv ());
768 }
769 
canRemove() const770 bool RObject::canRemove () const {
771 	RK_TRACE (OBJECTS);
772 
773 	if (isPseudoObject ()) return false;
774 	if (parent && parent->isSlotsPseudoObject ()) return false;
775 	// TODO: find out, if binding is locked:
776 	// if (isLocked ()) return false;
777 	return (isInGlobalEnv ());
778 }
779 
toplevelEnvironment() const780 REnvironmentObject* RObject::toplevelEnvironment () const {
781 	RK_TRACE (OBJECTS);
782 
783 // could be made recursive instead, but likely it's faster like this
784 	RObject *o = const_cast<RObject*> (this);	// it's ok, all we need to do is find the toplevel parent
785 	while (o && (!o->isType (ToplevelEnv))) {
786 		o = o->parent;
787 	}
788 	if (!o) {
789 		RK_ASSERT (this == RObjectList::getObjectList ());
790 		return RObjectList::getGlobalEnv ();
791 	}
792 	return static_cast<REnvironmentObject*> (o);
793 }
794 
isInGlobalEnv() const795 bool RObject::isInGlobalEnv () const {
796 	RK_TRACE (OBJECTS);
797 
798 	RObject* o = toplevelEnvironment ();
799 	if (o->isType (GlobalEnv)) {
800 		if (o != this) return true;	// the GlobalEnv is not inside the GlobalEnv!
801 	}
802 	return false;
803 }
804