diff --git a/rkward/core/robject.cpp b/rkward/core/robject.cpp index 1c0ca12e..c879fd22 100644 --- a/rkward/core/robject.cpp +++ b/rkward/core/robject.cpp @@ -1,803 +1,809 @@ /*************************************************************************** robject - description ------------------- begin : Thu Aug 19 2004 - copyright : (C) 2004-2013 by Thomas Friedrichsmeier + copyright : (C) 2004-2016 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "robject.h" #include #include #include "../rbackend/rinterface.h" #include "../rbackend/rkrbackendprotocol_shared.h" #include "../rkglobals.h" #include "robjectlist.h" #include "rcontainerobject.h" #include "rkpseudoobjects.h" #include "rkvariable.h" #include "renvironmentobject.h" #include "rfunctionobject.h" #include "rkmodificationtracker.h" #include "rkrownames.h" #include "../debug.h" namespace RObjectPrivate { QVector dim_null (1, 0); } // static QHash RObject::pseudo_object_types; QHash RObject::slots_objects; QHash RObject::namespace_objects; QHash RObject::rownames_objects; RObject::RObject (RObject *parent, const QString &name) { RK_TRACE (OBJECTS); RObject::parent = parent; RObject::name = name; type = 0; meta_map = 0; contained_objects = 0; dimensions = RObjectPrivate::dim_null; // safe initialization } RObject::~RObject () { RK_TRACE (OBJECTS); cancelOutstandingCommands (); if (hasPseudoObject (SlotsObject)) delete slots_objects.take (this); if (hasPseudoObject (NamespaceObject)) delete namespace_objects.take (this); if (hasPseudoObject (RowNamesObject)) delete rownames_objects.take (this); } bool RObject::irregularShortName (const QString &name) { // no trace static const QRegExp invalidChars ("[^a-zA-z0-9\\._]"); return (name.contains (invalidChars)); } QString RObject::getFullName () const { RK_TRACE (OBJECTS); return parent->makeChildName (RObject::name, type & Misplaced); } QString RObject::getBaseName () const { RK_TRACE (OBJECTS); return parent->makeChildBaseName (RObject::name); } QString RObject::getLabel () const { RK_TRACE (OBJECTS); return getMetaProperty ("label"); } RObject* RObject::findObjects (const QStringList &path, RObjectSearchMap *matches, const QString &op) { RK_TRACE (OBJECTS); // not a container if (op == "@") { if (slotsPseudoObject ()) return (slotsPseudoObject ()->findObjects (path, matches, "$")); } return 0; } QString RObject::getMetaProperty (const QString &id) const { RK_TRACE (OBJECTS); if (meta_map) return (meta_map->value (id)); return QString (); } QString RObject::getDescription () const { RK_TRACE (OBJECTS); if (meta_map) { QString label = meta_map->value ("label"); if (!label.isEmpty ()) return (getShortName () + " (" + label + ')'); } return getShortName ();; } QString RObject::getObjectDescription () const { RK_TRACE (OBJECTS); #define ESCS replace ('<', "<") QString ret; ret.append ("" + getShortName ().ESCS + ""); ret.append ("
" + i18n ("Full location:") + " " + getFullName ().ESCS); QString lab = getLabel (); if (!lab.isEmpty ()) ret.append ("
" + i18n ("Label:") + " " + lab.ESCS); ret.append ("
" + i18n ("Type:") + " "); if (isType (Function)) { ret.append (i18n ("Function")); ret.append ("
" + i18n ("Usage: ") + " " + getShortName ().ESCS + '(' + static_cast (this)->printArgs ().ESCS + ')'); } else if (isType (DataFrame)) { ret.append (i18n ("Data frame")); } else if (isType (Array)) { ret.append (i18n ("Array")); } else if (isType (Matrix)) { ret.append (i18n ("Matrix")); } else if (isType (List)) { ret.append (i18n ("List")); } else if (isType (Variable)) { ret.append (i18n ("Variable")); ret.append ("
" + i18n ("Data Type:") + " " + typeToText (getDataType ())); } else if (isType (Environment)) { ret.append (i18n ("Environment")); } if (isType (Container | Variable)) { if (dimensions.size () == 1) { ret.append ("
" + i18n ("Length: ") + " " + QString::number (dimensions[0])); } else if (dimensions.size () > 1) { ret.append ("
" + i18n ("Dimensions: ") + " "); for (int i=0; i < dimensions.size (); ++i) { if (i) ret.append (", "); ret.append (QString::number (dimensions[i])); } } } ret.append ("
" + i18n ("Class(es):") + " " + makeClassString (",").ESCS); return ret; } void RObject::setLabel (const QString &value, bool sync) { RK_TRACE (OBJECTS); setMetaProperty ("label", value, sync); } void RObject::setMetaProperty (const QString &id, const QString &value, bool sync) { RK_TRACE (OBJECTS); if (value.isEmpty ()) { if (meta_map && meta_map->contains (id)) meta_map->remove (id); else return; } else { if (!meta_map) meta_map = new MetaMap; else if (meta_map->value (id) == value) return; meta_map->insert (id, value); } if (sync) writeMetaData (0); RKGlobals::tracker ()->objectMetaChanged (this); } QString RObject::makeClassString (const QString &sep) const { RK_TRACE (OBJECTS); return (classnames.join (sep)); } bool RObject::inherits (const QString &class_name) const { RK_TRACE (OBJECTS); return (classnames.contains (class_name)); } QString RObject::makeChildName (const QString &short_child_name, bool) const { RK_TRACE (OBJECTS); return (getFullName () + "[[" + rQuote (short_child_name) + "]]"); } QString RObject::makeChildBaseName (const QString &short_child_name) const { RK_TRACE (OBJECTS); return (getBaseName () + "[[" + rQuote (short_child_name) + "]]"); } void RObject::writeMetaData (RCommandChain *chain) { RK_TRACE (OBJECTS); if (!meta_map) return; QString map_string; if (meta_map->isEmpty ()) { map_string.append ("NULL"); delete meta_map; // now that is is synced, delete it meta_map = 0; } else { for (MetaMap::const_iterator it = meta_map->constBegin (); it != meta_map->constEnd (); ++it) { if (!map_string.isEmpty ()) map_string.append (", "); map_string.append (rQuote (it.key ()) + '=' + rQuote (it.value ())); } map_string = "c (" + map_string + ')'; } RCommand *command = new RCommand (".rk.set.meta (" + getFullName () + ", " + map_string + ')', RCommand::App | RCommand::Sync); RKGlobals::rInterface ()->issueCommand (command, chain); } void RObject::updateFromR (RCommandChain *chain) { RK_TRACE (OBJECTS); RCommand *command; if (parentObject () == RObjectList::getGlobalEnv ()) { #ifdef __GNUC__ # warning TODO: find a generic solution #endif // 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. command = new RCommand (".rk.get.structure.global (" + rQuote (getShortName ()) + ')', RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, ROBJECT_UDPATE_STRUCTURE_COMMAND); } else { RK_ASSERT (false); // non-catastrophic, but do we get here? command = new RCommand (".rk.get.structure (" + getFullName () + ", " + rQuote (getShortName ()) + ')', RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, ROBJECT_UDPATE_STRUCTURE_COMMAND); } RKGlobals::rInterface ()->issueCommand (command, chain); type |= Updating; // will be cleared, implicitly, when the new structure gets set } void RObject::fetchMoreIfNeeded (int levels) { RK_TRACE (OBJECTS); if (isType (Updating)) return; if (isType (Incomplete)) { updateFromR (0); return; } RSlotsPseudoObject *spo = slotsPseudoObject (); if (spo) spo->fetchMoreIfNeeded (levels); // Note: We do NOT do the same for any namespaceEnvironment, deliberately if (levels <= 0) return; if (!isContainer ()) return; const RObjectMap children = static_cast (this)->childmap; foreach (RObject* child, children) { child->fetchMoreIfNeeded (levels - 1); } } void RObject::rCommandDone (RCommand *command) { RK_TRACE (OBJECTS); if (command->getFlags () == ROBJECT_UDPATE_STRUCTURE_COMMAND) { if (command->failed ()) { RK_DEBUG (OBJECTS, DL_INFO, "command failed while trying to update object '%s'. No longer present?", getShortName ().toLatin1 ().data ()); // this may happen, if the object has been removed in the workspace in between RKGlobals::tracker ()->removeObject (this, 0, true); return; } if (parent && parent->isContainer ()) static_cast (parent)->updateChildStructure (this, command); // this may result in a delete, so nothing after this! else updateStructure (command); // no (container) parent can happen for RObjectList and pseudo objects return; } else { RK_ASSERT (false); } } bool RObject::updateStructure (RData *new_data) { RK_TRACE (OBJECTS); if (new_data->getDataLength () == 0) { // can happen, if the object no longer exists return false; } RK_ASSERT (new_data->getDataLength () >= StorageSizeBasicInfo); RK_ASSERT (new_data->getDataType () == RData::StructureVector); if (!canAccommodateStructure (new_data)) return false; if (isPending ()) { type -= Pending; return true; // Do not update any info for pending objects } bool properties_change = false; RData::RDataStorage new_data_data = new_data->structureVector (); properties_change = updateName (new_data_data.at (StoragePositionName)); properties_change = updateType (new_data_data.at (StoragePositionType)); properties_change = updateClasses (new_data_data.at (StoragePositionClass)); properties_change = updateMeta (new_data_data.at (StoragePositionMeta)); properties_change = updateDimensions (new_data_data.at (StoragePositionDims)); properties_change = updateSlots (new_data_data.at (StoragePositionSlots)); if (properties_change) RKGlobals::tracker ()->objectMetaChanged (this); if (type & NeedDataUpdate) updateDataFromR (0); if (type & Incomplete) { // If the (new!) type is "Incomplete", it means, the structure getter simply stopped at this point. // In case we already have child info, we should update it (TODO: perhaps only, if anything is listening for child objects?) if (numChildrenForObjectModel () && (!isType (Updating))) updateFromR (0); return true; } return true; } //virtual void RObject::updateDataFromR (RCommandChain *) { RK_TRACE (OBJECTS); type -= (type & NeedDataUpdate); } void RObject::markDataDirty () { RK_TRACE (OBJECTS); type |= NeedDataUpdate; if (isContainer ()) { RContainerObject* this_container = static_cast (this); RObjectMap children = this_container->childmap; for (int i = children.size () - 1; i >= 0; --i) { children[i]->markDataDirty (); } if (this_container->hasPseudoObject (RowNamesObject)) this_container->rowNames ()->markDataDirty (); } } bool RObject::canAccommodateStructure (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () >= StorageSizeBasicInfo); RK_ASSERT (new_data->getDataType () == RData::StructureVector); RData::RDataStorage new_data_data = new_data->structureVector (); if (!isValidName (new_data_data.at (StoragePositionName))) return false; if (!isValidType (new_data_data.at (StoragePositionType))) return false; return true; } bool RObject::isValidName (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () == 1); RK_ASSERT (new_data->getDataType () == RData::StringVector); QString new_name = new_data->stringVector ().at (0); if (name != new_name) { RK_ASSERT (false); name = new_name; return false; } return true; } bool RObject::updateName (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () == 1); RK_ASSERT (new_data->getDataType () == RData::StringVector); bool changed = false; QString new_name = new_data->stringVector ().at (0); if (name != new_name) { changed = true; RK_ASSERT (false); name = new_name; } return changed; } bool RObject::isValidType (RData *new_data) const { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () == 1); RK_ASSERT (new_data->getDataType () == RData::IntVector); int new_type = new_data->intVector ().at (0); if (!isMatchingType (type, new_type)) return false; return true; } bool RObject::updateType (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () == 1); RK_ASSERT (new_data->getDataType () == RData::IntVector); bool changed = false; int new_type = new_data->intVector ().at (0); if (type & PseudoObject) new_type |= PseudoObject; if (type & Misplaced) new_type |= Misplaced; 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 if (type & NeedDataUpdate) new_type |= NeedDataUpdate; if (type != new_type) { changed = true; type = new_type; } return changed; } bool RObject::updateClasses (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () >= 1); // or can there be classless objects in R? RK_ASSERT (new_data->getDataType () == RData::StringVector); bool change = false; QStringList new_classes = new_data->stringVector (); if (new_classes != classnames) { change = true; classnames = new_classes; } return change; } bool RObject::updateMeta (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataType () == RData::StringVector); QStringList data= new_data->stringVector (); int len = data.size (); bool change = false; if (len) { if (!meta_map) meta_map = new MetaMap; else meta_map->clear (); RK_ASSERT (!(len % 2)); int cut = len/2; for (int i=0; i < cut; ++i) { meta_map->insert (data.at (i), data.at (i+cut)); } // TODO: only signal change, if there really was a change! change = true; } else { // no meta data received if (meta_map) { delete meta_map; meta_map = 0; change = true; } } return change; } bool RObject::updateDimensions (RData *new_data) { RK_TRACE (OBJECTS); RK_ASSERT (new_data->getDataLength () >= 1); RK_ASSERT (new_data->getDataType () == RData::IntVector); QVector new_dimensions = new_data->intVector (); if (new_dimensions != dimensions) { if (new_dimensions.isEmpty ()) { if (dimensions != RObjectPrivate::dim_null) { dimensions = RObjectPrivate::dim_null; return (true); } } else { #ifdef __GNUC__ # warning TODO: ugly hack. Should be moved to RKVariable, somehow. #endif if (type & Variable) static_cast (this)->extendToLength (new_dimensions[0]); dimensions = new_dimensions; return (true); } } return (false); } bool RObject::updateSlots (RData *new_data) { RK_TRACE (OBJECTS); if (new_data->getDataLength ()) { RK_ASSERT (new_data->getDataType () == RData::StructureVector); bool added = false; RSlotsPseudoObject *spo = slotsPseudoObject (); if (!spo) { spo = new RSlotsPseudoObject (this); added = true; RKGlobals::tracker ()->lockUpdates (true); } bool ret = spo->updateStructure (new_data->structureVector ().at (0)); if (added) { RKGlobals::tracker ()->lockUpdates (false); setSpecialChildObject (spo, SlotsObject); } return ret; } else if (slotsPseudoObject ()) { setSpecialChildObject (0, SlotsObject); } return false; } int RObject::getObjectModelIndexOf (RObject *child) const { RK_TRACE (OBJECTS); int offset = 0; if (isContainer ()) { int pos = static_cast (this)->childmap.indexOf (child); if (pos >= 0) return pos + offset; offset += static_cast (this)->childmap.size (); } if (hasPseudoObject (SlotsObject)) { if (child == slotsPseudoObject ()) return offset; offset += 1; } if (hasPseudoObject (NamespaceObject)) { if (child == namespaceEnvironment ()) return offset; offset += 1; } if (isType (Workspace)) { if (child == static_cast (this)->orphanNamespacesObject ()) return offset; offset += 1; } return -1; } int RObject::numChildrenForObjectModel () const { RK_TRACE (OBJECTS); int ret = isContainer () ? static_cast(this)->numChildren () : 0; if (hasPseudoObject (SlotsObject)) ret += 1; if (hasPseudoObject (NamespaceObject)) ret += 1; if (isType (Workspace)) ret += 1; // for the RKOrphanNamespacesObject return ret; } RObject *RObject::findChildByObjectModelIndex (int index) const { if (isContainer ()) { const RContainerObject *container = static_cast(this); if (index < container->numChildren ()) return container->findChildByIndex (index); index -= container->numChildren (); } if (hasPseudoObject (SlotsObject)) { if (index == 0) return slotsPseudoObject (); --index; } if (hasPseudoObject (NamespaceObject)) { if (index == 0) return namespaceEnvironment (); --index; } if (isType (Workspace)) { if (index == 0) return static_cast (this)->orphanNamespacesObject (); --index; } return 0; } RKEditor *RObject::editor () const { return (RKGlobals::tracker ()->objectEditor (this)); } void RObject::rename (const QString &new_short_name) { RK_TRACE (OBJECTS); RK_ASSERT (canRename ()); static_cast (parent)->renameChild (this, new_short_name); } void RObject::setSpecialChildObject (RObject* special, PseudoObjectType special_type) { RK_TRACE (OBJECTS); RObject *old_special = 0; if (special_type == SlotsObject) old_special = slotsPseudoObject (); else if (special_type == NamespaceObject) old_special = namespaceEnvironment (); else if (special_type == RowNamesObject) old_special = rownames_objects.value (this); else RK_ASSERT (false); if (special == old_special) return; if (old_special) { RKGlobals::tracker ()->removeObject (old_special, 0, true); RK_ASSERT (!hasPseudoObject (special_type)); // should have been removed in the above statement via RObject::remove() } if (special) { if (special_type == SlotsObject) slots_objects.insert (this, static_cast (special)); else if (special_type == NamespaceObject) namespace_objects.insert (this, static_cast (special)); else if (special_type == RowNamesObject) rownames_objects.insert (this, static_cast (special)); contained_objects |= special_type; if (special->isType (NonVisibleObject)) return; int index = getObjectModelIndexOf (special); // HACK: Newly added object must not be included in the index before beginAddObject (but must be included above for getObjectModelIncexOf() to work) contained_objects -= special_type; RKGlobals::tracker ()->beginAddObject (special, this, index); contained_objects |= special_type; RKGlobals::tracker ()->endAddObject (special, this, index); } } void RObject::remove (bool removed_in_workspace) { RK_TRACE (OBJECTS); RK_ASSERT (canRemove () || removed_in_workspace); if (isPseudoObject ()) { RK_ASSERT (removed_in_workspace); PseudoObjectType type = getPseudoObjectType (); if (parent->hasPseudoObject (type)) { // not always true for NamespaceObjects, which the RKOrphanNamespacesObject keeps as regular children! if (type == SlotsObject) slots_objects.remove (parent); else if (type == NamespaceObject) namespace_objects.remove (parent); else if (type == RowNamesObject) rownames_objects.remove (parent); parent->contained_objects -= type; delete this; return; } } static_cast (parent)->removeChild (this, removed_in_workspace); } //static QString RObject::typeToText (RDataType var_type) { // TODO: These are non-i18n, and not easily i18n-able due to being used, internally. // But they _are_ display strings, too. if (var_type == DataUnknown) { return "Unknown"; } else if (var_type == DataNumeric) { return "Numeric"; } else if (var_type == DataCharacter) { return "String"; } else if (var_type == DataFactor) { return "Factor"; } else if (var_type == DataLogical) { return "Logical"; } else { RK_ASSERT (false); return "Invalid"; } } //static RObject::RDataType RObject::textToType (const QString &text) { if (text == "Unknown") { return DataUnknown; } else if (text == "Numeric") { return DataNumeric; } else if (text == "String") { return DataCharacter; } else if (text == "Factor") { return DataFactor; } else if (text == "Logical") { return DataLogical; } else { RK_ASSERT (false); return DataUnknown; } } //static QString RObject::rQuote (const QString &string) { return (RKRSharedFunctionality::quote (string)); } //static QStringList RObject::parseObjectPath (const QString &path) { RK_TRACE (OBJECTS); QStringList ret; QString fragment; int end = path.length (); QChar quote_char; bool escaped = false; bool seek_bracket_end = false; for (int i = 0; i < end; ++i) { QChar c = path.at (i); if (quote_char.isNull ()) { if (c == '\'' || c == '\"' || c == '`') { quote_char = c; } else { if (!seek_bracket_end) { if (c == '$') { ret.append (fragment); ret.append ("$"); fragment.clear (); } else if (c == '[') { ret.append (fragment); ret.append ("$"); fragment.clear (); if ((i+1 < end) && (path.at (i+1) == '[')) ++i; seek_bracket_end = true; } else if (c == ':') { ret.append (fragment); if ((i+1 < end) && (path.at (i+1) == ':')) ++i; if ((i+1 < end) && (path.at (i+1) == ':')) { ++i; ret.append (":::"); } else ret.append ("::"); fragment.clear (); } else if (c == '@') { ret.append (fragment); ret.append ("@"); fragment.clear (); } else { fragment.append (c); } } else { if (c == ']') { if ((i+1 < end) && (path.at (i+1) == ']')) ++i; seek_bracket_end = false; continue; } else { fragment.append (c); } } } } else { // inside a quote if (c == '\\') escaped = !escaped; else { if (escaped) { if (c == 't') fragment.append ('\t'); else if (c == 'n') fragment.append ('\n'); else fragment.append ('\\' + c); } else { if (c == quote_char) { quote_char = QChar (); } else { fragment.append (c); } } } } } if (!fragment.isEmpty ()) ret.append (fragment); RK_DEBUG (OBJECTS, DL_DEBUG, "parsed object path %s into %s", qPrintable (path), qPrintable (ret.join ("-"))); return ret; } //virtual void RObject::beginEdit () { RK_ASSERT (false); } //virtual void RObject::endEdit () { RK_ASSERT (false); } bool RObject::canWrite () const { RK_TRACE (OBJECTS); // TODO: find out, if binding is locked: // if (isLocked ()) return false; return (isInGlobalEnv ()); } bool RObject::canRead () const { RK_TRACE (OBJECTS); return (this != RObjectList::getObjectList ()); } bool RObject::canRename () const { RK_TRACE (OBJECTS); if (isPseudoObject ()) return false; if (parent && parent->isSlotsPseudoObject ()) return false; // TODO: find out, if binding is locked: // if (isLocked ()) return false; return (isInGlobalEnv ()); } bool RObject::canRemove () const { RK_TRACE (OBJECTS); if (isPseudoObject ()) return false; if (parent && parent->isSlotsPseudoObject ()) return false; // TODO: find out, if binding is locked: // if (isLocked ()) return false; return (isInGlobalEnv ()); } -bool RObject::isInGlobalEnv () const { +REnvironmentObject* RObject::toplevelEnvironment () const { RK_TRACE (OBJECTS); // could be made recursive instead, but likely it's faster like this RObject *o = const_cast (this); // it's ok, all we need to do is find the toplevel parent while (o && (!o->isType (ToplevelEnv))) { o = o->parent; } - if (!o) { RK_ASSERT (this == RObjectList::getObjectList ()); - return false; + return RObjectList::getGlobalEnv (); } + return static_cast (o); +} + +bool RObject::isInGlobalEnv () const { + RK_TRACE (OBJECTS); + + RObject* o = toplevelEnvironment (); if (o->isType (GlobalEnv)) { if (o != this) return true; // the GlobalEnv is not inside the GlobalEnv! } return false; } diff --git a/rkward/core/robject.h b/rkward/core/robject.h index 92308c07..02496221 100644 --- a/rkward/core/robject.h +++ b/rkward/core/robject.h @@ -1,314 +1,317 @@ /*************************************************************************** robject - description ------------------- begin : Thu Aug 19 2004 - copyright : (C) 2004-2013 by Thomas Friedrichsmeier + copyright : (C) 2004-2016 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef ROBJECT_H #define ROBJECT_H #include #include #include #include #include "../rbackend/rcommandreceiver.h" class RSlotsPseudoObject; class REnvironmentObject; class RContainerObject; class RKRowNames; class RCommandChain; class RKEditor; class RData; #define ROBJECT_UDPATE_STRUCTURE_COMMAND 1 /** Base class for representations of objects in the R-workspace. RObject is never used directly (contains pure virtual functions). @author Thomas Friedrichsmeier */ class RObject : public RCommandReceiver { public: RObject (RObject *parent, const QString &name); virtual ~RObject (); /** types of objects, RKWard knows about */ enum RObjectType { DataFrame=1, Matrix=1 << 1, Array=1 << 2, List=1 << 3, Container=1 << 4, Variable=1 << 5, Workspace=1 << 6, Function=1 << 7, Environment=1 << 8, GlobalEnv=1 << 9, ToplevelEnv=1 << 10, PackageEnv=1 << 11, Misplaced=1 << 12, /** < the object is not in the namespace where it would be expected */ S4Object=1 << 13, Numeric=1 << 14, Factor=2 << 14, Character=3 << 14, Logical=4 << 14, DataTypeMask=Numeric | Factor | Character | Logical, PseudoObject = 1 << 26, /** < The object is an internal representation, only, and does not exist in R. Currently, this is the case only for the slots-pseudo object */ Updating=1 << 27, /** < The object is about to be updated from R */ Incomplete=1 << 28, /** < The information on this object is not complete (typically, it's children have not been scanned, yet). */ NonVisibleObject=1 << 29, /** < the object is not listed in the object list. Currently, this is only the case for row.names()-objects */ NeedDataUpdate=1 << 30, /** < the object's data should be (re-) fetched from R. The main purpose of this flag is to make sure the data is synced *after* the structure has been synced */ Pending=1 << 31 /** < the object is pending, i.e. it has been created in the object list, but we have not seen it in R, yet. This is used by data editors to create the illusion that a new object was added immediately, while in fact it takes some time to create it in the backend. */ }; enum RDataType { DataUnknown=0, DataNumeric=1, DataFactor=2, DataCharacter=3, DataLogical=4, MinKnownDataType = DataNumeric, MaxKnownDataType = DataLogical }; /** For passing data between RKStructureGetter and RObject. Be very careful about changing the values in this enum. It is for better readability / searchability of the code, only. */ enum { StoragePositionName = 0, StoragePositionType = 1, StoragePositionClass = 2, StoragePositionMeta = 3, StoragePositionDims = 4, StoragePositionSlots = 5, StoragePositionChildren = 6, StoragePositionNamespace = 7, StoragePositionFunArgs = 6, StoragePositionFunValues = 7, StorageSizeBasicInfo = 6, }; enum PseudoObjectType { InvalidPseudoObject = 0, SlotsObject = 1, NamespaceObject = 1 << 1, OrphanNamespacesObject = 1 << 2, RowNamesObject = 1 << 3 }; #define ROBJECT_TYPE_INTERNAL_MASK (RObject::Container | RObject::Variable | RObject::Workspace | RObject::Environment | RObject::Function) /** @returns false if an object of the given old type cannot represent an object of the given new type (e.g. (new_type & RObjectType::Variable), but (old_type & RObjectType::Container)). */ static bool isMatchingType (int old_type, int new_type) { return ((old_type & ROBJECT_TYPE_INTERNAL_MASK) == (new_type & ROBJECT_TYPE_INTERNAL_MASK)); }; QString getShortName () const { return name; }; virtual QString getFullName () const; virtual QString getBaseName () const; QString getLabel () const; QString getMetaProperty (const QString &id) const; QString getDescription () const; void setLabel (const QString &value, bool sync=true); void setMetaProperty (const QString &id, const QString &value, bool sync=true); bool isContainer () const { return (type & (Container | Environment | Workspace)); }; bool isDataFrame () const { return (type & DataFrame); }; bool isVariable () const { return (type & Variable); }; /** see RObjectType */ bool isType (int type) const { return (RObject::type & type); }; bool isPseudoObject () const { return isType (PseudoObject); }; PseudoObjectType getPseudoObjectType () const { return pseudo_object_types.value (this, InvalidPseudoObject); }; bool isSlotsPseudoObject () const { return (this && isPseudoObject () && (getPseudoObjectType () == SlotsObject)); }; bool isPackageNamespace () const { return (this && isPseudoObject () && (getPseudoObjectType () == NamespaceObject)); }; bool hasPseudoObject (const PseudoObjectType type) const { return (contained_objects & type); }; bool hasMetaObject () const { return (meta_map); }; /** see RObjectType::Pending */ bool isPending () const { return type & Pending; }; /** trigger an update of this and all descendent objects */ virtual void updateFromR (RCommandChain *chain); /** fetch updated data from the backend, if there are any listeners. Default implementation does nothing except clearing the dirty flag */ virtual void updateDataFromR (RCommandChain *chain); /** mark the data of this object and all of its children as dirty (recursively). Dirty data will be updated *after* the new structure update (if the object is opened for editing) */ void markDataDirty (); /** Returns the editor of this object, if any, or 0 */ RKEditor* editor () const; bool canWrite () const; bool canRead () const; bool canRename () const; bool canRemove () const; +/** returns true, if this object is inside the .GlobalEnv. The .GlobalEnv is not considered to be inside itself. */ bool isInGlobalEnv () const; +/** returns the toplevel environment that this object is in. May the the same as the object. */ + REnvironmentObject *toplevelEnvironment () const; void rename (const QString &new_short_name); void remove (bool removed_in_workspace); const QStringList &classNames () const { return classnames; }; QString makeClassString (const QString &sep) const; /** @param class_name the name of the class to check for @returns true, if the object has (among others) the given class, false otherwise */ bool inherits (const QString &class_name) const; /** get vector of dimensions. For simplicity, In RKWard each object is considered to have at least one dimension (but that dimension may be 0 in length) */ const QVector &getDimensions () const { return dimensions; }; /** short hand for getDimension (0). Meaningful for one-dimensional objects */ int getLength () const { return dimensions[0]; }; /** return the index of the given child, or -1 if there is no such child */ int getObjectModelIndexOf (RObject *child) const; int numChildrenForObjectModel () const; RObject *findChildByObjectModelIndex (int) const; /** A QList of RObjects. Internally the same as RObjectMap, but can be considered "public" */ typedef QList ObjectList; typedef QMap RObjectSearchMap; /** A map of values to labels. This is used both in regular objects, in which it just represents a map of named values, if any. The more important use is in factors, where it represents the factor levels. Here, the key is always a string representation of a positive integer. */ typedef QMap ValueLabels; /** write the MetaData to the backend. Commands will be issued in the given chain */ virtual void writeMetaData (RCommandChain *chain); /** Returns the parent of this object. All objects have a parent except for the RObjectList (which returns 0) */ RObject *parentObject () const { return (parent); }; RDataType getDataType () const { return (typeToDataType (type)); }; int getType () const { return type; }; static RDataType typeToDataType (int ftype) { return ((RDataType) ((ftype & DataTypeMask) >> 14)); }; void setDataType (RDataType new_type) { int n_type = type - (type & DataTypeMask); type = n_type + (new_type << 14); }; /** returns a textual representation of the given RDataType */ static QString typeToText (RDataType); /** converts the given text to a VarType. Returns Invalid on failure */ static RDataType textToType (const QString &text); /** Returns the given string in quotes, taking care of escaping quotation marks inside the string. */ static QString rQuote (const QString &string); /** Returns a pretty description of the object, and its most important properties. */ virtual QString getObjectDescription () const; /** Parses an object path (such as package::name[["a"]]$b@slot) into its components, returning them as a list (in this case 'package', '::' 'name', '$', 'a', '$', 'b', '@', 'slot'). */ static QStringList parseObjectPath (const QString &path); /** Tests whether the given name is "irregular", i.e. contains spaces, quotes, operators, or the like. @see RContainerObject::validizeName () */ static bool irregularShortName (const QString &name); /** try to find the object as a child object of this object. @param name of the object (relative to this object) @returns a pointer to the object (if found) or 0 if not found */ RObject *findObject (const QString &name) { return findObjects (parseObjectPath (name), 0, "$"); }; /** Function for code completion: given the partial name, find all objects matching this partial name @param partial_name The partial name to look up @param current_list A pointer to a valid (but probably initially empty) RObjectMap. Matches will be added to this list */ void findObjectsMatching (const QString &partial_name, RObjectSearchMap *current_list) { findObjects (parseObjectPath (partial_name), current_list, "$"); }; /** Fetch more levels of object representation (if needed). Note: Data is fetched asynchronously. @param levels levels to recurse (0 = only direct children). */ void fetchMoreIfNeeded (int levels=1); /** Representation of changes to an edited object (currently for vector data, only) */ struct ChangeSet { ChangeSet (int from = -1, int to = -1, bool reset = false) : from_index(from), to_index(to), full_reset(reset) {}; int from_index; /**< first changed index */ int to_index; /**< last changed index */ bool full_reset; /**< Model should do a full reset (e.g. dimensions may have changed) */ }; /** generates a (full) name for a child of this object with the given name. */ virtual QString makeChildName (const QString &short_child_name, bool misplaced=false) const; protected: // why do I need those to compile? I thought they were derived classes! friend class RContainerObject; friend class RObjectList; friend class REnvironmentObject; /** A map of objects accessible by index. Used in RContainerObject. Defined here for technical reasons. */ typedef QList RObjectMap; RObject *parent; QString name; /** or-ed combination of RObjectType flags for this object */ int type; QVector dimensions; QStringList classnames; /** or-ed combination of PseudoObjectType flags of pseudo objects available in this object */ qint8 contained_objects; RSlotsPseudoObject *slotsPseudoObject () const { return (hasPseudoObject (SlotsObject) ? slots_objects.value (this) : 0); }; /** returns the namespace environment for this object. Always returns 0 for objects which are not a package environment! */ REnvironmentObject* namespaceEnvironment () const { return (hasPseudoObject (NamespaceObject) ? namespace_objects.value (this) : 0); }; void setSpecialChildObject (RObject *special, PseudoObjectType special_type); /** Worker function for findObject() and findObjectsMatching(). If matches != 0, look for partial matches, and store them in the map (findObjectsMatching()). Else look for exact matches and return the first match (findObject()). */ virtual RObject *findObjects (const QStringList &path, RObjectSearchMap *matches, const QString &op); virtual QString makeChildBaseName (const QString &short_child_name) const; /** Update object to reflect the structure passed in the new_data argument. If the data is mismatching (i.e. can not be accommodated by this type of object) false is returned (calls canAccommodateStructure () internally). In this case you should delete the object, and create a new one. @returns true if the changes could be done, false if this */ virtual bool updateStructure (RData *new_data); typedef QMap MetaMap; MetaMap *meta_map; virtual bool canAccommodateStructure (RData *new_data); bool isValidName (RData *new_data); bool isValidType (RData *new_data) const; /** handles updating the object name from the given data (common functionality between RContainerObject and RKVariable. This should really never return true, as the name should never change. Hence also raises an assert. Is still useful for it's side effect of detaching and deleting the data from the RData structure after checking it. @param new_data The data. Make sure it really is the classes field of an .rk.get.structure-command to update classes *before* calling this function! WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @returns whether this caused any changes */ bool updateName (RData *new_data); /** update type information from the given data. @param new_data The command. Make sure it really is the classification field of an .rk.get.structure-command to update classes *before* calling this function! WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @returns whether this caused any changes */ virtual bool updateType (RData *new_data); /** handles updating class names from the given data (common functionality between RContainerObject and RKVariable @param new_data The data. Make sure it really is the classes field of an .rk.get.structure-command to update classes *before* calling this function! WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @returns whether this caused any changes */ bool updateClasses (RData *new_data); /** handles updating the meta data from the given data (common functionality between RContainerObject and RKVariable. WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @param new_data The data. Make sure it really is the meta field of an .rk.get.structure-command to update classes *before* calling this function! @returns whether this caused any changes */ bool updateMeta (RData *new_data); /** update dimension information from the given data. @param new_data The command. Make sure it really is the dims field of an .rk.get.structure-command to update classes *before* calling this function! WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @returns whether this caused any changes */ bool updateDimensions (RData *new_data); /** update information on slots of this object (if it is an S4 object) @param new_data The command. Make sure it really is the slots field of an .rk.get.structure-command to update classes *before* calling this function! WARNING: the new_data object may get changed during this call. Call canAccommodateStructure () before calling this function! @returns whether this caused any changes */ bool updateSlots (RData *new_data); friend class RKModificationTracker; /** Notify the object that some model needs its data. The object should take care of fetching the data from the backend, unless it already has the data. The default implementation does nothing (raises an assert). */ virtual void beginEdit (); /** Notify the object that a model no longer needs its data. If there have been as many endEdit() as beginEdit() calls, the object should discard its data storage. The default implementation does nothing (raises an assert). */ virtual void endEdit (); void rCommandDone (RCommand *command); /* Storage hashes for special objects which are held by some but not all objects, and thus should not have a pointer * in the class declaration. Some apply only to specific RObject types, but moving storage to the relevant classes, would make it more * difficult to maintain the generic bits. */ static QHash slots_objects; static QHash namespace_objects; static QHash rownames_objects; friend class RSlotsPseudoObject; friend class RKPackageNamespaceObject; friend class RKOrphanNamespacesObject; friend class RKRowNames; static QHash pseudo_object_types; }; #endif diff --git a/rkward/rbackend/FindR.cmake b/rkward/rbackend/FindR.cmake index 867b1bf3..aa6bc47a 100644 --- a/rkward/rbackend/FindR.cmake +++ b/rkward/rbackend/FindR.cmake @@ -1,204 +1,204 @@ # find the R binary MESSAGE(STATUS "Looking for R executable") IF(R_EXECUTABLE) MESSAGE(STATUS "Specified by user") ENDIF(R_EXECUTABLE) FIND_PROGRAM(R_EXECUTABLE R) IF(R_EXECUTABLE-NOTFOUND) MESSAGE(FATAL_ERROR "Could NOT find R (TODO: name option)") ELSE(R_EXECUTABLE-NOTFOUND) MESSAGE(STATUS "Using R at ${R_EXECUTABLE}") ENDIF(R_EXECUTABLE-NOTFOUND) # find out about R architecture (needed for some paths) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.version$arch)" OUTPUT_VARIABLE R_ARCH) MESSAGE (STATUS "R architecture is ${R_ARCH}") # check R version. -SET (R_MIN_VERSION "2.8.0") +SET (R_MIN_VERSION "2.10.0") MESSAGE (STATUS "Checking R version") EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat (paste(R.version$major, R.version$minor, sep='.'))" OUTPUT_VARIABLE R_VERSION) MESSAGE (STATUS "R version is ${R_VERSION}") EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "min_ver <- '${R_MIN_VERSION}'; if (compareVersion ('${R_VERSION}', min_ver) < 0) cat ('At least R version', min_ver, 'is required')" OUTPUT_VARIABLE R_VERSION_STATUS) IF (R_VERSION_STATUS) MESSAGE (FATAL_ERROR ${R_VERSION_STATUS}) ENDIF (R_VERSION_STATUS) # find R_HOME MESSAGE(STATUS "Looking for R_HOME") IF(NOT R_HOME) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home())" OUTPUT_VARIABLE R_HOME) ELSE(NOT R_HOME) MESSAGE(STATUS "Specified by user") ENDIF(NOT R_HOME) IF(NOT R_HOME) MESSAGE(FATAL_ERROR "Could NOT determine R_HOME (probably you misspecified the location of R)") ELSE(NOT R_HOME) MESSAGE(STATUS "R_HOME is ${R_HOME}") ENDIF(NOT R_HOME) # find R include dir MESSAGE(STATUS "Looking for R include files") IF(NOT R_INCLUDEDIR) IF(WIN32 OR APPLE) # This version of the test will not work with R < 2.9.0, but the other version (in the else part) will not work on windows or apple (but we do not really need to support ancient versions of R, there). EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(R.home('include'))" OUTPUT_VARIABLE R_INCLUDEDIR) ELSE(WIN32 OR APPLE) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} CMD sh -c "echo -n $R_INCLUDE_DIR" OUTPUT_VARIABLE R_INCLUDEDIR) ENDIF(WIN32 OR APPLE) ELSE(NOT R_INCLUDEDIR) MESSAGE(STATUS "Location specified by user") ENDIF(NOT R_INCLUDEDIR) IF(NOT R_INCLUDEDIR) SET(R_INCLUDEDIR ${R_HOME}/include) MESSAGE(STATUS "Not findable via R. Guessing") ENDIF(NOT R_INCLUDEDIR) MESSAGE(STATUS "Include files should be at ${R_INCLUDEDIR}. Checking for R.h") FIND_FILE(R_H R.h PATHS ${R_INCLUDEDIR} NO_DEFAULT_PATH) IF(NOT R_H) MESSAGE(FATAL_ERROR "Not found") ELSE(NOT R_H) MESSAGE(STATUS "Found at ${R_H}") GET_FILENAME_COMPONENT(R_INCLUDEDIR ${R_H} PATH) ENDIF(NOT R_H) SET(R_INCLUDEDIR ${R_INCLUDEDIR} ${R_INCLUDEDIR}/${R_ARCH}) # check for existence of libR.so MESSAGE(STATUS "Checking for existence of R shared library") FIND_LIBRARY(LIBR_SO R PATHS ${R_HOME}/lib ${R_SHAREDLIBDIR} ${R_HOME}/bin ${R_HOME}/bin/${R_ARCH} ${R_HOME}/lib/${R_ARCH} NO_DEFAULT_PATH) IF(NOT LIBR_SO) MESSAGE(FATAL_ERROR "Not found. Make sure the location of R was detected correctly, above, and R was compiled with the --enable-R-shlib option") ELSE(NOT LIBR_SO) MESSAGE(STATUS "Exists at ${LIBR_SO}") GET_FILENAME_COMPONENT(R_SHAREDLIBDIR ${LIBR_SO} PATH) SET(R_USED_LIBS R) ENDIF(NOT LIBR_SO) # for at least some versions of R, we seem to have to link against -lRlapack. Else loading some # R packages will fail due to unresolved symbols, or we can't link against -lR. # However, we can't do this unconditionally, # as this is not available in some configurations of R MESSAGE(STATUS "Checking whether we should link against Rlapack library") FIND_LIBRARY(LIBR_LAPACK Rlapack PATHS ${R_SHAREDLIBDIR} NO_DEFAULT_PATH) IF(NOT LIBR_LAPACK) MESSAGE(STATUS "No, it does not exist in ${R_SHAREDLIBDIR}") ELSE(NOT LIBR_LAPACK) MESSAGE(STATUS "Yes, ${LIBR_LAPACK} exists") SET(R_USED_LIBS ${R_USED_LIBS} Rlapack) IF(WIN32 OR APPLE) ELSE(WIN32 OR APPLE) # needed when linking to Rlapack on linux for some unknown reason. # apparently not needed on windows (let's see, when it comes back to bite us, though) # and compiling on windows is hard enough even without requiring libgfortran, too. SET(R_USED_LIBS ${R_USED_LIBS} gfortran) ENDIF(WIN32 OR APPLE) ENDIF(NOT LIBR_LAPACK) # for at least some versions of R, we seem to have to link against -lRlapack. Else loading some # R packages will fail due to unresolved symbols, or we can't link against -lR. # However, we can't do this unconditionally, # as this is not available in some configurations of R MESSAGE(STATUS "Checking whether we should link against Rblas library") FIND_LIBRARY(LIBR_BLAS Rblas PATHS ${R_SHAREDLIBDIR} NO_DEFAULT_PATH) IF(NOT LIBR_BLAS) MESSAGE(STATUS "No, it does not exist in ${R_SHAREDLIBDIR}") ELSE(NOT LIBR_BLAS) MESSAGE(STATUS "Yes, ${LIBR_BLAS} exists") SET(R_USED_LIBS ${R_USED_LIBS} Rblas) ENDIF(NOT LIBR_BLAS) # find R package library location IF(WIN32) SET(PATH_SEP ";") ELSE(WIN32) SET(PATH_SEP ":") ENDIF(WIN32) MESSAGE(STATUS "Checking for R package library location to use") IF(NOT R_LIBDIR) EXECUTE_PROCESS( COMMAND ${R_EXECUTABLE} "--slave" "--no-save" "-e" "cat(paste(unique (c(.Library.site, .Library)), collapse='${PATH_SEP}'))" OUTPUT_VARIABLE R_LIBDIR) ELSE(NOT R_LIBDIR) MESSAGE(STATUS "Location specified by user") ENDIF(NOT R_LIBDIR) # strip whitespace STRING(REGEX REPLACE "[ \n]+" "" R_LIBDIR "${R_LIBDIR}") # strip leading colon(s) STRING(REGEX REPLACE "^${PATH_SEP}+" "" R_LIBDIR "${R_LIBDIR}") # strip trailing colon(s) STRING(REGEX REPLACE "${PATH_SEP}+$" "" R_LIBDIR "${R_LIBDIR}") # find first path STRING(REGEX REPLACE "${PATH_SEP}" " " R_LIBDIR "${R_LIBDIR}") IF(NOT R_LIBDIR) MESSAGE(STATUS "Not reliably determined or specified. Guessing.") SET(R_LIBDIR ${R_HOME}/library) ENDIF(NOT R_LIBDIR) SET(R_LIBDIRS ${R_LIBDIR}) SEPARATE_ARGUMENTS(R_LIBDIRS) SET(R_LIBDIR) FOREACH(CURRENTDIR ${R_LIBDIRS}) IF(NOT USE_R_LIBDIR) IF(EXISTS ${CURRENTDIR}) SET(R_LIBDIR ${CURRENTDIR}) SET(USE_R_LIBDIR 1) ELSE(EXISTS ${CURRENTDIR}) MESSAGE(STATUS "${CURRENTDIR} does not exist. Skipping") ENDIF(EXISTS ${CURRENTDIR}) ENDIF(NOT USE_R_LIBDIR) ENDFOREACH(CURRENTDIR ${R_LIBDIRS}) IF(NOT EXISTS ${R_LIBDIR}) MESSAGE(FATAL_ERROR "No existing library location found") ELSE(NOT EXISTS ${R_LIBDIR}) MESSAGE(STATUS "Will use ${R_LIBDIR}") ENDIF(NOT EXISTS ${R_LIBDIR}) diff --git a/rkward/rbackend/rpackages/rkward/R/internal_help.R b/rkward/rbackend/rpackages/rkward/R/internal_help.R index 84ce1137..f4eae65c 100644 --- a/rkward/rbackend/rpackages/rkward/R/internal_help.R +++ b/rkward/rbackend/rpackages/rkward/R/internal_help.R @@ -1,53 +1,55 @@ ## Internal functions related to help search / display # retrieve the (expected) "base" url of help files. Most importantly this will be a local port for R 2.10.0 and above, but a local directory for 2.9.x and below. As a side effect, in R 2.10.0 and above, the dynamic help server is started. #' @export ".rk.getHelpBaseUrl" <- function () { port <- NA if (compareVersion (as.character (getRversion()), "2.10.0") >= 0) { try ({ port <- tools::startDynamicHelp () }) if (is.na (port)) { try ({ port <- tools:::httpdPort }) } } if (is.na (port)) { return (paste ("file://", R.home (), sep="")) } return (paste ("http://127.0.0.1", port, sep=":")) } # a simple wrapper around help() that makes it easier to detect in code, whether help was found or not. # used from RKHelpSearchWindow::getFunctionHelp #' @export -".rk.getHelp" <- function (...) { - if (compareVersion (as.character (getRversion()), "2.10.0") >= 0) { - res <- help (..., help_type="html") - } else { - res <- help (..., chmhelp=FALSE, htmlhelp=TRUE) - } +".rk.getHelp" <- function (topic, package=NULL, ...) { + res <- help (topic, (package), ..., help_type="html") if (!length (as.character (res))) { # this seems undocumented, but it is what utils:::print.help_files_with_topic checks - show (res) - stop ("No help found") + if (!is.null (package)) { + # if no help found, try once more, without package restriction + res <- help (topic, package=NULL, ..., help_type="html") + } + if (!length (as.character (res))) { + show (res) + stop ("No help found") + } } show (res) invisible (TRUE) } # Simple wrapper around help.search. Concatenates the relevant fields of the results in order for passing to the frontend. #' @export ".rk.get.search.results" <- function (pattern, ...) { H = as.data.frame(help.search(pattern, ...)$matches) fields <- names (H) c( # NOTE: Field header capitalization appears to have changed in some version of R around 3.2.x as.character (if ("topic" %in% fields) H$topic else H$Topic), as.character (if ("title" %in% fields) H$title else H$Title), as.character (if ("package" %in% fields) H$package else H$Package), # NOTE: The field "Type" was added in R 2.14.0. For earlier versions of R, only help pages were returned as results of help.search() as.character (if ("Type" %in% fields) H$Type else rep ("help", length.out=dim(H)[1])) ) } diff --git a/rkward/windows/robjectbrowser.cpp b/rkward/windows/robjectbrowser.cpp index 1a6d6eed..345caeb6 100644 --- a/rkward/windows/robjectbrowser.cpp +++ b/rkward/windows/robjectbrowser.cpp @@ -1,279 +1,281 @@ /*************************************************************************** robjectbrowser - description ------------------- begin : Thu Aug 19 2004 - copyright : (C) 2004 - 2015 by Thomas Friedrichsmeier + copyright : (C) 2004 - 2016 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "robjectbrowser.h" #include #include #include #include #include #include #include #include #include #include "../rkward.h" #include "rkhelpsearchwindow.h" #include "../rkglobals.h" #include "../core/robjectlist.h" #include "../core/renvironmentobject.h" #include "../core/rkmodificationtracker.h" #include "../rbackend/rinterface.h" #include "../misc/rkobjectlistview.h" #include "../misc/rkdummypart.h" #include "../misc/rkstandardicons.h" +#include "../misc/rkstandardactions.h" #include "rkworkplace.h" #include "../dataeditor/rkeditor.h" #include "../debug.h" // static RObjectBrowser* RObjectBrowser::object_browser = 0; RObjectBrowser::RObjectBrowser (QWidget *parent, bool tool_window, const char *name) : RKMDIWindow (parent, WorkspaceBrowserWindow, tool_window, name) { RK_TRACE (APP); internal = 0; locked = true; QVBoxLayout *layout = new QVBoxLayout (this); layout->setContentsMargins (0, 0, 0, 0); layout_widget = new KVBox (this); layout->addWidget (layout_widget); layout_widget->setFocusPolicy (Qt::StrongFocus); RKDummyPart *part = new RKDummyPart (this, layout_widget); setPart (part); setMetaInfo (i18n ("R workspace browser"), "rkward://page/rkward_workspace_browser", RKSettings::PageObjectBrowser); initializeActivationSignals (); setCaption (i18n ("R Workspace")); } RObjectBrowser::~RObjectBrowser () { RK_TRACE (APP); } void RObjectBrowser::unlock () { RK_TRACE (APP); locked = false; if (!isHidden ()) { initialize (); } } void RObjectBrowser::showEvent (QShowEvent *e) { RK_TRACE (APP); initialize (); RKMDIWindow::showEvent (e); } void RObjectBrowser::initialize () { RK_TRACE (APP); if (internal) return; if (locked) return; RK_DEBUG (APP, DL_INFO, "creating workspace browser"); - internal = new RObjectBrowserInternal (layout_widget); + internal = new RObjectBrowserInternal (layout_widget, this); setFocusProxy (internal); setMinimumSize (internal->minimumSize ()); } ///////////////////////// RObjectBrowserInternal ///////////////////////////// -RObjectBrowserInternal::RObjectBrowserInternal (QWidget *parent) : QWidget (parent) { +RObjectBrowserInternal::RObjectBrowserInternal (QWidget *parent, RObjectBrowser *browser) : QWidget (parent) { RK_TRACE (APP); setFocusPolicy (Qt::ClickFocus); QVBoxLayout *vbox = new QVBoxLayout (this); vbox->setContentsMargins (0, 0, 0, 0); list_view = new RKObjectListView (true, this); vbox->addWidget (list_view->getSettings ()->filterWidget (this)); vbox->addWidget (list_view); update_button = new QPushButton (i18n ("Update"), this); vbox->addWidget (update_button); - actions.insert (Help, new QAction (i18n ("Search Help"), this)); - connect (actions[Help], SIGNAL(triggered(bool)), this, SLOT(popupHelp())); + actions.insert (Help, RKStandardActions::functionHelp (browser, this, SLOT(popupHelp()))); actions.insert (Edit, new QAction (i18n ("Edit"), this)); connect (actions[Edit], SIGNAL(triggered(bool)), this, SLOT(popupEdit())); actions.insert (View, new QAction (i18n ("View"), this)); connect (actions[View], SIGNAL(triggered(bool)), this, SLOT(popupView())); actions.insert (Rename, new QAction (i18n ("Rename"), this)); connect (actions[Rename], SIGNAL(triggered(bool)), this, SLOT(popupRename())); actions.insert (Copy, new QAction (i18n ("Copy to new symbol"), this)); connect (actions[Copy], SIGNAL(triggered(bool)), this, SLOT(popupCopy())); actions.insert (CopyToGlobalEnv, new QAction (i18n ("Copy to .GlobalEnv"), this)); connect (actions[CopyToGlobalEnv], SIGNAL(triggered(bool)), this, SLOT(popupCopyToGlobalEnv())); actions.insert (Delete, new QAction (i18n ("Delete"), this)); connect (actions[Delete], SIGNAL(triggered(bool)), this, SLOT(popupDelete())); actions.insert (Unload, new QAction (i18n ("Unload Package"), this)); connect (actions[Unload], SIGNAL(triggered(bool)), this, SLOT(popupUnload())); actions.insert (LoadUnloadPackages, new QAction (i18n ("Load / Unload Packages"), this)); connect (actions[LoadUnloadPackages], SIGNAL(triggered(bool)), RKWardMainWindow::getMain(), SLOT(slotFileLoadLibs())); QAction* sep = list_view->contextMenu ()->insertSeparator (list_view->contextMenu ()->actions ().value (0)); list_view->contextMenu ()->insertActions (sep, actions); connect (list_view, SIGNAL (aboutToShowContextMenu(RObject*,bool*)), this, SLOT (contextMenuCallback(RObject*,bool*))); connect (list_view, SIGNAL (doubleClicked(QModelIndex)), this, SLOT (doubleClicked(QModelIndex))); resize (minimumSizeHint ().expandedTo (QSize (400, 480))); list_view->initialize (); connect (update_button, SIGNAL (clicked()), this, SLOT (updateButtonClicked())); } RObjectBrowserInternal::~RObjectBrowserInternal () { RK_TRACE (APP); } void RObjectBrowserInternal::focusInEvent (QFocusEvent *e) { RK_TRACE (APP); list_view->getSettings ()->filterWidget (this)->setFocus (); if (e->reason () != Qt::MouseFocusReason) { list_view->setObjectCurrent (RObjectList::getGlobalEnv (), true); } } void RObjectBrowserInternal::updateButtonClicked () { RK_TRACE (APP); RObjectList::getObjectList ()->updateFromR (0); } void RObjectBrowserInternal::popupHelp () { RK_TRACE (APP); - if (list_view->menuObject ()) RKHelpSearchWindow::mainHelpSearch ()->getFunctionHelp (list_view->menuObject ()->getShortName ()); + RObject *object = list_view->menuObject (); + if (!object) return; + RKHelpSearchWindow::mainHelpSearch ()->getFunctionHelp (object->getShortName (), object->isInGlobalEnv () ? QString () : object->toplevelEnvironment ()->packageName ()); } void RObjectBrowserInternal::popupEdit () { RK_TRACE (APP); if (list_view->menuObject ()) RKWorkplace::mainWorkplace ()->editObject (list_view->menuObject ()); } void RObjectBrowserInternal::popupCopy () { RK_TRACE (APP); bool ok; RObject *object = list_view->menuObject (); QString suggested_name = RObjectList::getGlobalEnv ()->validizeName (object->getShortName ()); QString name = KInputDialog::getText (i18n ("Copy object"), i18n ("Enter the name to copy to"), suggested_name, &ok, this); if (ok) { QString valid = RObjectList::getGlobalEnv ()->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("The name you specified was already in use or not valid. Renamed to %1", valid), i18n ("Invalid Name")); RKGlobals::rInterface ()->issueCommand (RObject::rQuote (valid) + " <- " + object->getFullName (), RCommand::App | RCommand::ObjectListUpdate); } } void RObjectBrowserInternal::popupCopyToGlobalEnv () { RK_TRACE (APP); RObject *object = list_view->menuObject (); QString name = object->getShortName (); QString valid = RObjectList::getGlobalEnv ()->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("An object named '%1' already exists in the GlobalEnv. Created the copy as '%2' instead.", name, valid), i18n ("Name already in use")); RKGlobals::rInterface ()->issueCommand (RObject::rQuote (valid) + " <- " + object->getFullName (), RCommand::App | RCommand::ObjectListUpdate); } void RObjectBrowserInternal::popupView () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->flushAllData (); RKWorkplace::mainWorkplace ()->newObjectViewer (list_view->menuObject ()); } void RObjectBrowserInternal::popupDelete () { RK_TRACE (APP); RKGlobals::tracker ()->removeObject (list_view->menuObject ()); } void RObjectBrowserInternal::popupUnload () { RK_TRACE (APP); RObject *object = list_view->menuObject (); RK_ASSERT (object); RK_ASSERT (object->isType (RObject::PackageEnv)); QStringList messages = RObjectList::getObjectList ()->detachPackages (QStringList (object->getShortName ())); if (!messages.isEmpty ()) KMessageBox::sorry (this, messages.join ("\n")); } void RObjectBrowserInternal::popupRename () { RK_TRACE (APP); bool ok; QString name = KInputDialog::getText (i18n ("Rename object"), i18n ("Enter the new name"), list_view->menuObject ()->getShortName (), &ok, this); if (ok) { QString valid = static_cast (list_view->menuObject ()->parentObject ())->validizeName (name); if (valid != name) KMessageBox::sorry (this, i18n ("The name you specified was already in use or not valid. Renamed to %1", valid), i18n ("Invalid Name")); RKGlobals::tracker ()->renameObject (list_view->menuObject (), valid); } } void RObjectBrowserInternal::contextMenuCallback (RObject *, bool *) { RK_TRACE (APP); RObject *object = list_view->menuObject (); if (!object) { RK_ASSERT (actions.size () == ActionCount); for (int i = 0; i < ActionCount; ++i) { actions[i]->setVisible (false); } actions[LoadUnloadPackages]->setVisible (true); return; } actions[Help]->setVisible (!(object->isType (RObject::ToplevelEnv) || object->isInGlobalEnv ())); actions[Edit]->setText (object->canWrite () ? i18n ("Edit") : i18n ("View in editor (read-only)")); actions[Edit]->setVisible (RKWorkplace::mainWorkplace ()->canEditObject (object)); actions[View]->setVisible (object->canRead ()); actions[Rename]->setVisible (object->canRename ()); actions[Copy]->setVisible (object->canRead () && (!object->isType (RObject::ToplevelEnv))); actions[CopyToGlobalEnv]->setVisible (object->canRead () && (!object->isInGlobalEnv()) && (!object->isType (RObject::ToplevelEnv))); actions[Delete]->setVisible (object->canRemove ()); actions[Unload]->setVisible (object->isType (RObject::PackageEnv)); actions[LoadUnloadPackages]->setVisible (object == RObjectList::getObjectList ()); } void RObjectBrowserInternal::doubleClicked (const QModelIndex& index) { RK_TRACE (APP); RObject *object = list_view->objectAtIndex (index); if (!object) return; if (object == RObjectList::getObjectList ()) return; if (RKWorkplace::mainWorkplace ()->canEditObject (object)) { RKWorkplace::mainWorkplace ()->editObject (object); } else { RKWorkplace::mainWorkplace ()->flushAllData (); RKWorkplace::mainWorkplace ()->newObjectViewer (object); } } #include "robjectbrowser.moc" diff --git a/rkward/windows/robjectbrowser.h b/rkward/windows/robjectbrowser.h index afcadb00..2f038f79 100644 --- a/rkward/windows/robjectbrowser.h +++ b/rkward/windows/robjectbrowser.h @@ -1,104 +1,104 @@ /*************************************************************************** robjectbrowser - description ------------------- begin : Thu Aug 19 2004 - copyright : (C) 2004 - 2015 by Thomas Friedrichsmeier + copyright : (C) 2004 - 2016 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef ROBJECTBROWSER_H #define ROBJECTBROWSER_H #include "rkmdiwindow.h" #include #include class RKObjectListView; class RKObjectListViewSettings; class QPushButton; class RObject; class RObjectBrowserInternal; class KVBox; /** This widget provides a browsable list of all objects in the R workspace Note: Most actual functionality is realized in RObjectBrowserInternal, which is created as soon as the RObjectBrowser is shown for the first time. @author Thomas Friedrichsmeier */ class RObjectBrowser : public RKMDIWindow { public: RObjectBrowser (QWidget *parent, bool tool_window, const char *name=0); ~RObjectBrowser (); void unlock (); static RObjectBrowser *mainBrowser () { return object_browser; }; /** reimplemented to create the real file browser widget only when the file browser is shown for the first time */ void showEvent (QShowEvent *e); private: RObjectBrowserInternal *internal; KVBox *layout_widget; bool locked; friend class RKWardMainWindow; static RObjectBrowser *object_browser; void initialize (); }; /** Provides most of the functionality of RObjectBrowser @author Thomas Friedrichsmeier */ class RObjectBrowserInternal : public QWidget { Q_OBJECT public: - explicit RObjectBrowserInternal (QWidget *parent); + explicit RObjectBrowserInternal (QWidget *parent, RObjectBrowser *browser); ~RObjectBrowserInternal (); private slots: void updateButtonClicked (); void contextMenuCallback (RObject *object, bool *suppress); void popupHelp (); void popupEdit (); void popupCopy (); /** essentially like popupCopy, but does not ask for a name */ void popupCopyToGlobalEnv (); void popupView (); void popupDelete (); void popupUnload (); void popupRename (); /** when an object in the list is double clicked, insert its name in the current RKCommandEditor window */ void doubleClicked (const QModelIndex &index); protected: /** reimplemnented from QWidget to make show the globalenv object when activated (other than by mouse click) */ void focusInEvent (QFocusEvent *e); private: enum PopupActions { Help=0, Edit, View, Rename, Copy, CopyToGlobalEnv, Delete, Unload, LoadUnloadPackages, ActionCount }; QList actions; QPushButton *update_button; RKObjectListView *list_view; }; #endif