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 ('<', "<")
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