diff --git a/src/backend/core/AbstractAspect.cpp b/src/backend/core/AbstractAspect.cpp index e273dada3..1ba24a515 100644 --- a/src/backend/core/AbstractAspect.cpp +++ b/src/backend/core/AbstractAspect.cpp @@ -1,840 +1,832 @@ /*************************************************************************** File : AbstractAspect.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2007-2009 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007-2010 by Knut Franke (knut.franke@gmx.de) Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) Description : Base class for all objects in a Project. ***************************************************************************/ /*************************************************************************** * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/AbstractAspect.h" #include "backend/core/AspectPrivate.h" #include "backend/core/aspectcommands.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/SignallingUndoCommand.h" #include "backend/lib/PropertyChangeCommand.h" -#include -#include -#include -#include -#include - -#include -#include #include /** * \class AbstractAspect * \brief Base class of all persistent objects in a Project. * * Before going into the details, it's useful to understand the ideas behind the * \ref aspect "Aspect Framework". * * Aspects organize themselves into trees, where a parent takes ownership of its children. Usually, * though not necessarily, a Project instance will sit at the root of the tree (without a Project * ancestor, project() will return 0 and undo does not work). Children are organized using * addChild(), removeChild(), child(), indexOfChild() and childCount() on the parent's side as well * as the equivalent convenience methods index() and remove() on the child's side. * In contrast to the similar feature of QObject, Aspect trees are fully undo/redo aware and provide * signals around object adding/removal. * * AbstractAspect manages for every Aspect the properties #name, #comment, #captionSpec and * #creationTime. All of these translate into the caption() as described in the documentation * of setCaptionSpec(). * * If an undoStack() can be found (usually it is managed by Project), changes to the properties * as well as adding/removing children support multi-level undo/redo. In order to support undo/redo * for problem-specific data in derived classes, make sure that all changes to your data are done * by handing appropriate commands to exec(). */ /** * \enum AbstractAspect::ChildIndexFlag * \brief Flags which control numbering scheme of children. */ /** * \var AbstractAspect::IncludeHidden * \brief Include aspects marked as "hidden" in numbering or listing children. */ /** * \var AbstractAspect::Recursive * \brief Recursively handle all descendents, not just immediate children. */ /** * \var AbstractAspect::Compress * \brief Remove all null pointers from the result list. */ //////////////////////////////////////////////////////////////////////////////////////////////////// // documentation of template and inline methods //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \fn template < class T > T *AbstractAspect::ancestor() const * \brief Return the closest ancestor of class T (or NULL if none found). */ /** * \fn template < class T > QVector AbstractAspect::children(const ChildIndexFlags &flags=0) const * \brief Return list of children inheriting from class T. * * Use AbstractAspect for T in order to get all children. */ /** * \fn template < class T > T *AbstractAspect::child(int index, const ChildIndexFlags &flags=0) const * \brief Return child identified by (0 based) index and class. * * Identifying objects by an index is inherently error-prone and confusing, * given that the index can be based on different criteria (viz, counting * only instances of specific classes and including/excluding hidden * aspects). Therefore, it is recommended to avoid indices wherever possible * and instead refer to aspects using AbstractAspect pointers. */ /** * \fn template < class T > T *AbstractAspect::child(const QString &name) const * \brief Get child by name and class. */ /** * \fn template < class T > int AbstractAspect::childCount(const ChildIndexFlags &flags=0) const * \brief Return the number of child Aspects inheriting from given class. */ /** * \fn template < class T > int AbstractAspect::indexOfChild(const AbstractAspect * child, const ChildIndexFlags &flags=0) const * \brief Return (0 based) index of child in the list of children inheriting from class T. */ /** * \fn void AbstractAspect::aspectDescriptionAboutToChange(const AbstractAspect *aspect) * \brief Emitted before the name, comment or caption spec is changed */ /** * \fn void AbstractAspect::aspectDescriptionChanged(const AbstractAspect *aspect) * \brief Emitted after the name, comment or caption spec have changed */ /** * \fn void AbstractAspect::aspectAboutToBeAdded(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect * child) * \brief Emitted before a new child is inserted */ /** * \fn void AbstractAspect::aspectAdded(const AbstractAspect *aspect) * \brief Emitted after a new Aspect has been added to the tree */ /** * \fn void AbstractAspect::aspectAboutToBeRemoved(const AbstractAspect *aspect) * \brief Emitted before an aspect is removed from its parent */ /** * \fn void AbstractAspect::aspectRemoved(const AbstractAspect *parent, const AbstractAspect * before, const AbstractAspect * child) * \brief Emitted from the parent after removing a child */ /** * \fn void AbstractAspect::aspectHiddenAboutToChange(const AbstractAspect *aspect) * \brief Emitted before the hidden attribute is changed */ /** * \fn void AbstractAspect::aspectHiddenChanged(const AbstractAspect *aspect) * \brief Emitted after the hidden attribute has changed */ /** * \fn void AbstractAspect::statusInfo(const QString &text) * \brief Emitted whenever some aspect in the tree wants to give status information to the user * \sa info(const QString&) */ /** * \fn protected void AbstractAspect::info(const QString &text) * \brief Implementations should call this whenever status information should be given to the user. * * This will cause statusInfo() to be emitted. Typically, this will cause the specified string * to be displayed in a status bar, a log window or some similar non-blocking way so as not to * disturb the workflow. */ /** * \fn protected virtual void childSelected(const AbstractAspect*){} * \brief called when a child's child aspect was selected in the model */ /** * \fn protected virtual void childDeselected() * \brief called when a child aspect was deselected in the model */ /** * \fn protected virtual void childDeselected(const AbstractAspect*) * \brief called when a child's child aspect was deselected in the model */ //////////////////////////////////////////////////////////////////////////////////////////////////// // start of AbstractAspect implementation //////////////////////////////////////////////////////////////////////////////////////////////////// AbstractAspect::AbstractAspect(const QString &name) : d(new AbstractAspectPrivate(this, name)) { } AbstractAspect::~AbstractAspect() { delete d; } QString AbstractAspect::name() const { return d->m_name; } void AbstractAspect::setName(const QString &value) { if (value.isEmpty()) { setName("1"); return; } if (value == d->m_name) return; QString new_name; if (d->m_parent) { new_name = d->m_parent->uniqueNameFor(value); if (new_name != value) info(i18n("Intended name \"%1\" was changed to \"%2\" in order to avoid name collision.", value, new_name)); } else { new_name = value; } exec(new PropertyChangeCommand(i18n("%1: rename to %2", d->m_name, new_name), &d->m_name, new_name), "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this)); } QString AbstractAspect::comment() const { return d->m_comment; } void AbstractAspect::setComment(const QString& value) { if (value == d->m_comment) return; exec(new PropertyChangeCommand(i18n("%1: change comment", d->m_name), &d->m_comment, value), "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this)); } void AbstractAspect::setCreationTime(const QDateTime& time) { d->m_creation_time = time; } QDateTime AbstractAspect::creationTime() const { return d->m_creation_time; } bool AbstractAspect::hidden() const { return d->m_hidden; } /** * \brief Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer. */ void AbstractAspect::setHidden(bool value) { if (value == d->m_hidden) return; exec(new PropertyChangeCommand(i18n("%1: change hidden status", d->m_name), &d->m_hidden, value), "aspectHiddenAboutToChange", "aspectHiddenChanged", Q_ARG(const AbstractAspect*,this)); } void AbstractAspect::setIsLoading(bool load) { d->m_isLoading = load; } bool AbstractAspect::isLoading() const { return d->m_isLoading; } /** * \brief Return an icon to be used for decorating my views. */ QIcon AbstractAspect::icon() const { return QIcon(); } /** * \brief Return a new context menu. * * The caller takes ownership of the menu. */ QMenu* AbstractAspect::createContextMenu() { QMenu* menu = new QMenu(); menu->addSection(this->name()); //TODO: activate this again when the functionality is implemented // menu->addAction( KStandardAction::cut(this) ); // menu->addAction(KStandardAction::copy(this)); // menu->addAction(KStandardAction::paste(this)); // menu->addSeparator(); menu->addAction(QIcon::fromTheme("edit-rename"), i18n("Rename"), this, SIGNAL(renameRequested())); //don't allow to delete data spreadsheets in the datapicker curves if ( !(dynamic_cast(this) && dynamic_cast(this->parentAspect())) ) menu->addAction(QIcon::fromTheme("edit-delete"), i18n("Delete"), this, SLOT(remove())); return menu; } /** * \brief Return my parent Aspect or 0 if I currently don't have one. */ AbstractAspect* AbstractAspect::parentAspect() const { return d->m_parent; } void AbstractAspect::setParentAspect(AbstractAspect* parent) { d->m_parent = parent; } /** * \brief Return the folder the Aspect is contained in or 0 if there is none. * * The returned folder may be the aspect itself if it inherits Folder. */ Folder* AbstractAspect::folder() { if(inherits("Folder")) return static_cast(this); AbstractAspect* parent_aspect = parentAspect(); while(parent_aspect && !parent_aspect->inherits("Folder")) parent_aspect = parent_aspect->parentAspect(); return static_cast(parent_aspect); } /** * \brief Return whether the there is a path upwards to the given aspect * * This also returns true if other==this. */ bool AbstractAspect::isDescendantOf(AbstractAspect* other) { if(other == this) return true; AbstractAspect* parent_aspect = parentAspect(); while(parent_aspect) { if(parent_aspect == other) return true; parent_aspect = parent_aspect->parentAspect(); } return false; } /** * \brief Return the Project this Aspect belongs to, or 0 if it is currently not part of one. */ Project* AbstractAspect::project() { return parentAspect() ? parentAspect()->project() : 0; } /** * \brief Return the path that leads from the top-most Aspect (usually a Project) to me. */ QString AbstractAspect::path() const { return parentAspect() ? parentAspect()->path() + '/' + name() : ""; } /** * \brief Add the given Aspect to my list of children. */ void AbstractAspect::addChild(AbstractAspect* child) { Q_CHECK_PTR(child); QString new_name = uniqueNameFor(child->name()); beginMacro(i18n("%1: add %2", name(), new_name)); if (new_name != child->name()) { info(i18n("Renaming \"%1\" to \"%2\" in order to avoid name collision.", child->name(), new_name)); child->setName(new_name); } exec(new AspectChildAddCmd(d, child, d->m_children.count())); endMacro(); } /** * \brief Add the given Aspect to my list of children without any checks and without putting this step onto the undo-stack */ void AbstractAspect::addChildFast(AbstractAspect* child) { emit aspectAboutToBeAdded(this, 0, child); //TODO: before-pointer is 0 here, also in the commands classes. why? d->insertChild(d->m_children.count(), child); emit aspectAdded(child); } /** * \brief Insert the given Aspect at a specific position in my list of children. */ void AbstractAspect::insertChildBefore(AbstractAspect* child, AbstractAspect* before) { Q_CHECK_PTR(child); QString new_name = uniqueNameFor(child->name()); beginMacro(i18n("%1: insert %2 before %3", name(), new_name, before ? before->name() : "end")); if (new_name != child->name()) { info(i18n("Renaming \"%1\" to \"%2\" in order to avoid name collision.", child->name(), new_name)); child->setName(new_name); } int index = d->indexOfChild(before); if (index == -1) index = d->m_children.count(); exec(new AspectChildAddCmd(d, child, index)); endMacro(); } /** * \brief Insert the given Aspect at a specific position in my list of children.without any checks and without putting this step onto the undo-stack */ void AbstractAspect::insertChildBeforeFast(AbstractAspect* child, AbstractAspect* before) { connect(child, SIGNAL(selected(const AbstractAspect*)), this, SLOT(childSelected(const AbstractAspect*))); connect(child, SIGNAL(deselected(const AbstractAspect*)), this, SLOT(childDeselected(const AbstractAspect*))); int index = d->indexOfChild(before); if (index == -1) index = d->m_children.count(); emit aspectAboutToBeAdded(this, 0, child); d->insertChild(index, child); emit aspectAdded(child); } /** * \brief Remove the given Aspect from my list of children. * * The ownership of the child is transferred to the undo command, * i.e., the aspect is deleted by the undo command. * \sa reparent() */ void AbstractAspect::removeChild(AbstractAspect* child) { Q_ASSERT(child->parentAspect() == this); beginMacro(i18n("%1: remove %2", name(), child->name())); exec(new AspectChildRemoveCmd(d, child)); endMacro(); } /** * \brief Remove all child Aspects. */ void AbstractAspect::removeAllChildren() { beginMacro(i18n("%1: remove all children", name())); QVector children_list = children(); QVector::const_iterator i = children_list.constBegin(); AbstractAspect *current = 0, *nextSibling = 0; if (i != children_list.constEnd()) { current = *i; if (++i != children_list.constEnd()) nextSibling = *i; } while (current) { emit aspectAboutToBeRemoved(current); exec(new AspectChildRemoveCmd(d, current)); emit aspectRemoved(this, nextSibling, current); current = nextSibling; if (i != children_list.constEnd() && ++i != children_list.constEnd()) nextSibling = *i; else nextSibling = 0; } endMacro(); } /** * \brief Move a child to another parent aspect and transfer ownership. */ void AbstractAspect::reparent(AbstractAspect* newParent, int newIndex) { Q_ASSERT(parentAspect() != NULL); Q_ASSERT(newParent != NULL); int max_index = newParent->childCount(IncludeHidden); if (newIndex == -1) newIndex = max_index; Q_ASSERT(newIndex >= 0 && newIndex <= max_index); AbstractAspect* old_parent = parentAspect(); int old_index = old_parent->indexOfChild(this, IncludeHidden); AbstractAspect* old_sibling = old_parent->child(old_index+1, IncludeHidden); AbstractAspect* new_sibling = newParent->child(newIndex, IncludeHidden); //TODO check/test this! emit aspectAboutToBeRemoved(this); emit newParent->aspectAboutToBeAdded(newParent, new_sibling, this); exec(new AspectChildReparentCmd(parentAspect()->d, newParent->d, this, newIndex)); emit old_parent->aspectRemoved(old_parent, old_sibling, this); emit aspectAdded(this); endMacro(); } QVector AbstractAspect::children(const char* className, const ChildIndexFlags &flags) { QVector result; for (auto* child : children()) { if (flags & IncludeHidden || !child->hidden()) { if ( child->inherits(className) || !(flags & Compress)) { result << child; if (flags & Recursive){ result << child->children(className, flags); } } } } return result; } const QVector AbstractAspect::children() const { return d->m_children; } /** * \brief Remove me from my parent's list of children. */ void AbstractAspect::remove() { if(parentAspect()) parentAspect()->removeChild(this); } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name serialize/deserialize //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \fn virtual void AbstractAspect::save(QXmlStreamWriter *) const * \brief Save as XML */ /** * \fn virtual bool AbstractAspect::load(XmlStreamReader *) * \brief Load from XML * * XmlStreamReader supports errors as well as warnings. If only * warnings (non-critial errors) occur, this function must return * the reader at the end element corresponding to the current * element at the time the function was called. * * This function is normally intended to be called directly * after the ctor. If you want to call load on an aspect that * has been altered, you must make sure beforehand that * it is in the same state as after creation, e.g., remove * all its child aspects. * * \return false on error */ /** * \brief Save the comment to XML */ void AbstractAspect::writeCommentElement(QXmlStreamWriter * writer) const{ writer->writeStartElement("comment"); writer->writeCharacters(comment()); writer->writeEndElement(); } /** * \brief Load comment from an XML element */ bool AbstractAspect::readCommentElement(XmlStreamReader * reader){ setComment(reader->readElementText()); return true; } /** * \brief Save name and creation time to XML */ void AbstractAspect::writeBasicAttributes(QXmlStreamWriter* writer) const { writer->writeAttribute("creation_time" , creationTime().toString("yyyy-dd-MM hh:mm:ss:zzz")); writer->writeAttribute("name", name()); } /** * \brief Load name and creation time from XML * * \return false on error */ bool AbstractAspect::readBasicAttributes(XmlStreamReader* reader){ const QXmlStreamAttributes& attribs = reader->attributes(); // name QString str = attribs.value("name").toString(); if(str.isEmpty()) reader->raiseWarning(i18n("Attribute 'name' is missing or empty.")); d->m_name = str; // creation time str = attribs.value("creation_time").toString(); if(str.isEmpty()) { reader->raiseWarning(i18n("Invalid creation time for '%1'. Using current time.", name())); d->m_creation_time = QDateTime::currentDateTime(); } else { QDateTime creation_time = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz"); if (creation_time.isValid()) d->m_creation_time = creation_time; else d->m_creation_time = QDateTime::currentDateTime(); } return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name undo related //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// void AbstractAspect::setUndoAware(bool b) { d->m_undoAware = b; } /** * \brief Return the undo stack of the Project, or 0 if this Aspect is not part of a Project. * * It's also possible to construct undo-enabled Aspect trees without Project. * The only requirement is that the root Aspect reimplements undoStack() to get the * undo stack from somewhere (the default implementation just delegates to parentAspect()). */ QUndoStack* AbstractAspect::undoStack() const { return parentAspect() ? parentAspect()->undoStack() : 0; } /** * \brief Execute the given command, pushing it on the undoStack() if available. */ void AbstractAspect::exec(QUndoCommand* cmd) { Q_CHECK_PTR(cmd); if (d->m_undoAware) { QUndoStack *stack = undoStack(); if (stack) stack->push(cmd); else { cmd->redo(); delete cmd; } if (project()) project()->setChanged(true); } else { cmd->redo(); delete cmd; } } /** * \brief Execute command and arrange for signals to be sent before/after it is redone or undone. * * \arg \c command The command to be executed. * \arg \c preChangeSignal The name of the signal to be triggered before re-/undoing the command. * \arg \c postChangeSignal The name of the signal to be triggered after re-/undoing the command. * \arg val0,val1,val2,val3 Arguments to the signals; to be given using Q_ARG(). * * Signal arguments are given using the macro Q_ARG(typename, const value&). Since * the variable given as "value" will likely be out of scope when the signals are emitted, a copy * needs to be created. This uses QMetaType, which means that (non-trivial) argument types need to * be registered using qRegisterMetaType() before giving them to exec() (in particular, this also * goes for pointers to custom data types). * * \sa SignallingUndoCommand */ void AbstractAspect::exec(QUndoCommand* command, const char* preChangeSignal, const char* postChangeSignal, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3) { beginMacro(command->text()); exec(new SignallingUndoCommand("change signal", this, preChangeSignal, postChangeSignal, val0, val1, val2, val3)); exec(command); exec(new SignallingUndoCommand("change signal", this, postChangeSignal, preChangeSignal, val0, val1, val2, val3)); endMacro(); } /** * \brief Begin an undo stack macro (series of commands) */ void AbstractAspect::beginMacro(const QString& text) { if (!d->m_undoAware) return; QUndoStack* stack = undoStack(); if (stack) stack->beginMacro(text); } /** * \brief End the current undo stack macro */ void AbstractAspect::endMacro() { if (!d->m_undoAware) return; QUndoStack* stack = undoStack(); if (stack) stack->endMacro(); } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// /*! * this function is called when the selection in ProjectExplorer was changed. * forwards the selection/deselection to the parent aspect via emitting a signal. */ void AbstractAspect::setSelected(bool s){ if (s) emit selected(this); else emit deselected(this); } void AbstractAspect::childSelected(const AbstractAspect* aspect) { //forward the signal to the highest possible level in the parent-child hierarchy //e.g. axis of a plot was selected. Don't include parent aspects here that do not //need to react on the selection of children: e.g. Folder or XYFitCurve with //the child column for calculated residuals if (aspect->parentAspect() != 0 && !aspect->parentAspect()->inherits("Folder") && !aspect->parentAspect()->inherits("XYFitCurve") && !aspect->parentAspect()->inherits("CantorWorksheet")) emit aspect->parentAspect()->selected(aspect); } void AbstractAspect::childDeselected(const AbstractAspect* aspect) { //forward the signal to the highest possible level in the parent-child hierarchy //e.g. axis of a plot was selected. Don't include parent aspects here that do not //need to react on the deselection of children: e.g. Folder or XYFitCurve with //the child column for calculated residuals if (aspect->parentAspect() != 0 && !aspect->parentAspect()->inherits("Folder") && !aspect->parentAspect()->inherits("XYFitCurve") && !aspect->parentAspect()->inherits("CantorWorksheet")) emit aspect->parentAspect()->deselected(aspect); } /** * \brief Make the specified name unique among my children by incrementing a trailing number. */ QString AbstractAspect::uniqueNameFor(const QString& current_name) const { QStringList child_names; for (auto* child : children()) child_names << child->name(); if (!child_names.contains(current_name)) return current_name; QString base = current_name; int last_non_digit; for (last_non_digit = base.size()-1; last_non_digit>=0 && base[last_non_digit].category() == QChar::Number_DecimalDigit; --last_non_digit) base.chop(1); if (last_non_digit >=0 && base[last_non_digit].category() != QChar::Separator_Space) base.append(" "); int new_nr = current_name.right(current_name.size() - base.size()).toInt(); QString new_name; do new_name = base + QString::number(++new_nr); while (child_names.contains(new_name)); return new_name; } void AbstractAspect::connectChild(AbstractAspect* child) { connect(child, SIGNAL(aspectDescriptionAboutToChange(const AbstractAspect*)), this, SIGNAL(aspectDescriptionAboutToChange(const AbstractAspect*))); connect(child, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SIGNAL(aspectDescriptionChanged(const AbstractAspect*))); connect(child, SIGNAL(aspectAboutToBeAdded(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SIGNAL(aspectAboutToBeAdded(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); connect(child, SIGNAL(aspectAdded(const AbstractAspect*)), this, SIGNAL(aspectAdded(const AbstractAspect*))); connect(child, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*))); connect(child, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); connect(child, SIGNAL(aspectHiddenAboutToChange(const AbstractAspect*)), this, SIGNAL(aspectHiddenAboutToChange(const AbstractAspect*))); connect(child, SIGNAL(aspectHiddenChanged(const AbstractAspect*)), this, SIGNAL(aspectHiddenChanged(const AbstractAspect*))); connect(child, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString))); connect(child, SIGNAL(selected(const AbstractAspect*)), this, SLOT(childSelected(const AbstractAspect*))); connect(child, SIGNAL(deselected(const AbstractAspect*)), this, SLOT(childDeselected(const AbstractAspect*))); } //############################################################################## //###################### Private implementation ############################### //############################################################################## AbstractAspectPrivate::AbstractAspectPrivate(AbstractAspect* owner, const QString& name) : m_name(name.isEmpty() ? "1" : name), m_hidden(false), q(owner), m_parent(0), m_undoAware(true), m_isLoading(false) { m_creation_time = QDateTime::currentDateTime(); } AbstractAspectPrivate::~AbstractAspectPrivate() { for(auto* child : m_children) delete child; } void AbstractAspectPrivate::insertChild(int index, AbstractAspect* child) { m_children.insert(index, child); // Always remove from any previous parent before adding to a new one! // Can't handle this case here since two undo commands have to be created. Q_ASSERT(child->parentAspect() == 0); child->setParentAspect(q); q->connectChild(child); } int AbstractAspectPrivate::indexOfChild(const AbstractAspect* child) const { for(int i=0; isetParentAspect(0); return index; } diff --git a/src/commonfrontend/datapicker/DatapickerImageView.cpp b/src/commonfrontend/datapicker/DatapickerImageView.cpp index 8e719f6a4..97c8ce5d9 100644 --- a/src/commonfrontend/datapicker/DatapickerImageView.cpp +++ b/src/commonfrontend/datapicker/DatapickerImageView.cpp @@ -1,808 +1,807 @@ /*************************************************************************** File : DatapickerImageView.cpp Project : LabPlot Description : DatapickerImage view for datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "commonfrontend/datapicker/DatapickerImageView.h" #include "backend/worksheet/Worksheet.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Datapicker.h" #include "backend/datapicker/Transform.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/datapicker/DatapickerImage.h" #include #include #include #include #include #include #include #include #include #include -#include #include /** * \class DatapickerImageView * \brief Datapicker/DatapickerImage view */ /*! Constructur of the class. Creates a view for the DatapickerImage \c image and initializes the internal model. */ DatapickerImageView::DatapickerImageView(DatapickerImage* image) : QGraphicsView(), m_image(image), m_datapicker(dynamic_cast(m_image->parentAspect())), m_transform(new Transform()), m_mouseMode(SelectAndEditMode), m_selectionBandIsShown(false), magnificationFactor(0), m_rotationAngle(0), tbZoom(0) { setScene(m_image->scene()); setRenderHint(QPainter::Antialiasing); setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorViewCenter); setMinimumSize(16, 16); setFocusPolicy(Qt::StrongFocus); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); setCacheMode(QGraphicsView::CacheBackground); initActions(); initMenus(); selectAndEditModeAction->setChecked(true); m_image->setSegmentsHoverEvent(true); setInteractive(true); changeZoom(zoomOriginAction); currentZoomAction=zoomInViewAction; if (m_image->plotPointsType() == DatapickerImage::AxisPoints) setAxisPointsAction->setChecked(true); else if (m_image->plotPointsType() == DatapickerImage::CurvePoints) setCurvePointsAction->setChecked(true); else selectSegmentAction->setChecked(true); handleImageActions(); changeRotationAngle(); //signal/slot connections //for general actions connect( m_image, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*)) ); connect( m_image, SIGNAL(requestUpdate()), this, SLOT(updateBackground()) ); connect( m_image, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_datapicker, SIGNAL(requestUpdateActions()), this, SLOT(handleImageActions()) ); connect( m_image, SIGNAL(rotationAngleChanged(float)), this, SLOT(changeRotationAngle()) ); } DatapickerImageView::~DatapickerImageView() { delete m_transform; } void DatapickerImageView::initActions() { QActionGroup* zoomActionGroup = new QActionGroup(this); QActionGroup* mouseModeActionGroup = new QActionGroup(this); QActionGroup* plotPointsTypeActionGroup = new QActionGroup(this); navigationActionGroup = new QActionGroup(this); magnificationActionGroup = new QActionGroup(this); //Zoom actions zoomInViewAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom in"), zoomActionGroup); zoomInViewAction->setShortcut(Qt::CTRL+Qt::Key_Plus); zoomOutViewAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom out"), zoomActionGroup); zoomOutViewAction->setShortcut(Qt::CTRL+Qt::Key_Minus); zoomOriginAction = new QAction(QIcon::fromTheme("zoom-original"), i18n("Original size"), zoomActionGroup); zoomOriginAction->setShortcut(Qt::CTRL+Qt::Key_1); zoomFitPageHeightAction = new QAction(QIcon::fromTheme("zoom-fit-height"), i18n("Fit to height"), zoomActionGroup); zoomFitPageWidthAction = new QAction(QIcon::fromTheme("zoom-fit-width"), i18n("Fit to width"), zoomActionGroup); // Mouse mode actions selectAndEditModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), mouseModeActionGroup); selectAndEditModeAction->setCheckable(true); navigationModeAction = new QAction(QIcon::fromTheme("input-mouse"), i18n("Navigate"), mouseModeActionGroup); navigationModeAction->setCheckable(true); zoomSelectionModeAction = new QAction(QIcon::fromTheme("page-zoom"), i18n("Select and Zoom"), mouseModeActionGroup); zoomSelectionModeAction->setCheckable(true); selectAndMoveModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Move"), mouseModeActionGroup); selectAndMoveModeAction->setCheckable(true); setAxisPointsAction = new QAction(QIcon::fromTheme("labplot-plot-axis-points"), i18n("Set Axis Points"), plotPointsTypeActionGroup); setAxisPointsAction->setCheckable(true); setCurvePointsAction = new QAction(QIcon::fromTheme("labplot-xy-curve-points"), i18n("Set Curve Points"), plotPointsTypeActionGroup); setCurvePointsAction->setCheckable(true); selectSegmentAction = new QAction(QIcon::fromTheme("labplot-xy-curve-segments"), i18n("Select Curve Segments"), plotPointsTypeActionGroup); selectSegmentAction->setCheckable(true); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("New Curve"), this); shiftLeftAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left"), navigationActionGroup); shiftLeftAction->setShortcut(Qt::Key_Right); shiftRightAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right"), navigationActionGroup); shiftRightAction->setShortcut(Qt::Key_Left); shiftUpAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Up"), navigationActionGroup); shiftUpAction->setShortcut(Qt::Key_Up); shiftDownAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Down"), navigationActionGroup); shiftDownAction->setShortcut(Qt::Key_Down); noMagnificationAction = new QAction(QIcon::fromTheme("labplot-1x-zoom"), i18n("No Magnification"), magnificationActionGroup); noMagnificationAction->setCheckable(true); noMagnificationAction->setChecked(true); twoTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-2x-zoom"), i18n("2x Magnification"), magnificationActionGroup); twoTimesMagnificationAction->setCheckable(true); threeTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-3x-zoom"), i18n("3x Magnification"), magnificationActionGroup); threeTimesMagnificationAction->setCheckable(true); fourTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-4x-zoom"), i18n("4x Magnification"), magnificationActionGroup); fourTimesMagnificationAction->setCheckable(true); fiveTimesMagnificationAction = new QAction(QIcon::fromTheme("labplot-5x-zoom"), i18n("5x Magnification"), magnificationActionGroup); fiveTimesMagnificationAction->setCheckable(true); connect( mouseModeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(mouseModeChanged(QAction*)) ); connect( zoomActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeZoom(QAction*)) ); connect( plotPointsTypeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changePointsType(QAction*)) ); connect( addCurveAction, SIGNAL(triggered()), this, SLOT(addCurve()) ); connect( navigationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeSelectedItemsPosition(QAction*)) ); connect( magnificationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(magnificationChanged(QAction*)) ); } void DatapickerImageView::initMenus() { m_viewMouseModeMenu = new QMenu(i18n("Mouse Mode"), this); m_viewMouseModeMenu->setIcon(QIcon::fromTheme("input-mouse")); m_viewMouseModeMenu->addAction(selectAndEditModeAction); m_viewMouseModeMenu->addAction(navigationModeAction); m_viewMouseModeMenu->addAction(zoomSelectionModeAction); m_viewMouseModeMenu->addAction(selectAndMoveModeAction); m_viewImageMenu = new QMenu(i18n("Data Entry Mode"), this); m_viewImageMenu->addAction(setAxisPointsAction); m_viewImageMenu->addAction(setCurvePointsAction); m_viewImageMenu->addAction(selectSegmentAction); m_zoomMenu = new QMenu(i18n("Zoom View"), this); m_zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); m_zoomMenu->addAction(zoomInViewAction); m_zoomMenu->addAction(zoomOutViewAction); m_zoomMenu->addAction(zoomOriginAction); m_zoomMenu->addAction(zoomFitPageHeightAction); m_zoomMenu->addAction(zoomFitPageWidthAction); m_navigationMenu = new QMenu(i18n("Move Last Point"), this); m_navigationMenu->addAction(shiftLeftAction); m_navigationMenu->addAction(shiftRightAction); m_navigationMenu->addAction(shiftUpAction); m_navigationMenu->addAction(shiftDownAction); m_magnificationMenu = new QMenu(i18n("Magnification"), this); m_magnificationMenu->setIcon(QIcon::fromTheme("labplot-zoom")); m_magnificationMenu->addAction(noMagnificationAction); m_magnificationMenu->addAction(twoTimesMagnificationAction); m_magnificationMenu->addAction(threeTimesMagnificationAction); m_magnificationMenu->addAction(fourTimesMagnificationAction); m_magnificationMenu->addAction(fiveTimesMagnificationAction); } /*! * Populates the menu \c menu with the image and image-view relevant actions. * The menu is used * - as the context menu in DatapickerImageView * - as the "datapicker menu" in the main menu-bar (called form MainWin) * - as a part of the image context menu in project explorer */ void DatapickerImageView::createContextMenu(QMenu* menu) const { Q_ASSERT(menu); QAction* firstAction = 0; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_viewImageMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, addCurveAction); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_navigationMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_viewMouseModeMenu); menu->insertMenu(firstAction, m_zoomMenu); menu->insertMenu(firstAction, m_magnificationMenu); menu->insertSeparator(firstAction); } void DatapickerImageView::fillToolBar(QToolBar* toolBar) { toolBar->addSeparator(); toolBar->addAction(setAxisPointsAction); toolBar->addAction(setCurvePointsAction); toolBar->addAction(selectSegmentAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addSeparator(); toolBar->addAction(noMagnificationAction); toolBar->addAction(twoTimesMagnificationAction); toolBar->addAction(threeTimesMagnificationAction); toolBar->addAction(fourTimesMagnificationAction); toolBar->addAction(fiveTimesMagnificationAction); toolBar->addSeparator(); toolBar->addAction(shiftRightAction); toolBar->addAction(shiftLeftAction); toolBar->addAction(shiftUpAction); toolBar->addAction(shiftDownAction); toolBar->addSeparator(); toolBar->addAction(selectAndEditModeAction); toolBar->addAction(navigationModeAction); toolBar->addAction(zoomSelectionModeAction); toolBar->addAction(selectAndMoveModeAction); tbZoom = new QToolButton(toolBar); tbZoom->setPopupMode(QToolButton::MenuButtonPopup); tbZoom->setMenu(m_zoomMenu); tbZoom->setDefaultAction(currentZoomAction); toolBar->addWidget(tbZoom); } void DatapickerImageView::setScene(QGraphicsScene* scene) { QGraphicsView::setScene(scene); setTransform(QTransform()); } void DatapickerImageView::drawForeground(QPainter* painter, const QRectF& rect) { if (m_mouseMode==ZoomSelectionMode && m_selectionBandIsShown) { painter->save(); const QRectF& selRect = mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(); painter->setPen(QPen(Qt::black, 5/transform().m11())); painter->drawRect(selRect); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(selRect); painter->restore(); } QGraphicsView::drawForeground(painter, rect); } void DatapickerImageView::drawBackground(QPainter* painter, const QRectF& rect) { painter->save(); QRectF scene_rect = sceneRect(); if (!scene_rect.contains(rect)) painter->fillRect(rect, Qt::lightGray); //shadow int shadowSize = scene_rect.width()*0.02; QRectF rightShadowRect(scene_rect.right(), scene_rect.top() + shadowSize, shadowSize, scene_rect.height()); QRectF bottomShadowRect(scene_rect.left() + shadowSize, scene_rect.bottom(), scene_rect.width(), shadowSize); painter->fillRect(rightShadowRect.intersected(rect), Qt::darkGray); painter->fillRect(bottomShadowRect.intersected(rect), Qt::darkGray); // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(scene_rect.topLeft(), todraw); } else { painter->fillRect(scene_rect, Qt::white); } } else { painter->setBrush(QBrush(Qt::gray)); painter->drawRect(scene_rect); } invalidateScene(rect, QGraphicsScene::BackgroundLayer); painter->restore(); } //############################################################################## //#################################### Events ############################### //############################################################################## void DatapickerImageView::wheelEvent(QWheelEvent *event) { if (m_mouseMode == ZoomSelectionMode) { if (event->delta() > 0) scale(1.2, 1.2); else if (event->delta() < 0) scale(1.0/1.2, 1.0/1.2); } else { QGraphicsView::wheelEvent(event); } } void DatapickerImageView::mousePressEvent(QMouseEvent* event) { //prevent the deselection of items when context menu event //was triggered (right button click) if (event->button() == Qt::RightButton) { event->accept(); return; } if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionStart = event->pos(); m_selectionBandIsShown = true; return; } QPointF eventPos = mapToScene(event->pos()); if ( m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(eventPos) ) { if ( m_image->plotPointsType() == DatapickerImage::AxisPoints ) { int childCount = m_image->childCount(AbstractAspect::IncludeHidden); if (childCount < 3) m_datapicker->addNewPoint(eventPos, m_image); } else if ( m_image->plotPointsType() == DatapickerImage::CurvePoints && m_datapicker->activeCurve() ) { m_datapicker->addNewPoint(eventPos, m_datapicker->activeCurve()); } } // make sure the datapicker (or its currently active curve) is selected in the project explorer if the view was clicked. // We need this for the case when we change from the project-node in the project explorer to the datapicker node by clicking the view. if (m_datapicker->activeCurve() && m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_datapicker->setSelectedInView(false); m_datapicker->activeCurve()->setSelectedInView(true); } else { if (m_datapicker->activeCurve()) m_datapicker->activeCurve()->setSelectedInView(false); m_datapicker->setSelectedInView(true); } QGraphicsView::mousePressEvent(event); } void DatapickerImageView::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && m_mouseMode == ZoomSelectionMode) { m_selectionBandIsShown = false; viewport()->repaint(QRect(m_selectionStart, m_selectionEnd).normalized()); //don't zoom if very small region was selected, avoid occasional/unwanted zooming m_selectionEnd = event->pos(); if ( abs(m_selectionEnd.x()-m_selectionStart.x())>20 && abs(m_selectionEnd.y()-m_selectionStart.y())>20 ) fitInView(mapToScene(QRect(m_selectionStart, m_selectionEnd).normalized()).boundingRect(), Qt::KeepAspectRatio); } QGraphicsView::mouseReleaseEvent(event); } void DatapickerImageView::mouseMoveEvent(QMouseEvent* event) { if ( m_mouseMode == SelectAndEditMode || m_mouseMode == ZoomSelectionMode ) { if (m_image->isLoaded) setCursor(Qt::CrossCursor); else setCursor(Qt::ArrowCursor); } else { setCursor(Qt::ArrowCursor); } //show the selection band if (m_selectionBandIsShown) { QRect rect = QRect(m_selectionStart, m_selectionEnd).normalized(); m_selectionEnd = event->pos(); rect = rect.united(QRect(m_selectionStart, m_selectionEnd).normalized()); int penWidth = 5/transform().m11(); rect.setX(rect.x()-penWidth); rect.setY(rect.y()-penWidth); rect.setHeight(rect.height()+2*penWidth); rect.setWidth(rect.width()+2*penWidth); viewport()->repaint(rect); return; } QPointF pos = mapToScene(event->pos()); //show the current coordinates under the mouse cursor in the status bar if (m_image->plotPointsType() == DatapickerImage::CurvePoints) { QVector3D logicalPos = m_transform->mapSceneToLogical(pos, m_image->axisPoints()); if (m_image->axisPoints().type == DatapickerImage::Ternary) { emit statusInfo( "a =" + QString::number(logicalPos.x()) + ", b =" + QString::number(logicalPos.y()) + ", c =" + QString::number(logicalPos.z())); } else { QString xLabel('x'); QString yLabel('y'); if (m_image->axisPoints().type == DatapickerImage::PolarInDegree) { xLabel = "r"; yLabel = "y(deg)"; } else if (m_image->axisPoints().type == DatapickerImage::PolarInRadians) { xLabel = "r"; yLabel = "y(rad)"; } if (m_datapicker->activeCurve()) { QString statusText = m_datapicker->name() + ", " + i18n("active curve") + " \"" + m_datapicker->activeCurve()->name() + '"'; statusText += ": " + xLabel + '=' + QString::number(logicalPos.x()) + ", " + yLabel + '=' + QString::number(logicalPos.y()); emit statusInfo(statusText); } } } //show the magnification window if ( magnificationFactor && m_mouseMode == SelectAndEditMode && m_image->isLoaded && sceneRect().contains(pos) && m_image->plotPointsType() != DatapickerImage::SegmentPoints ) { if (!m_image->m_magnificationWindow) { // m_image->m_magnificationWindow = new QGraphicsPixmapItem(0, scene()); m_image->m_magnificationWindow = new QGraphicsPixmapItem; scene()->addItem(m_image->m_magnificationWindow); m_image->m_magnificationWindow->setZValue(std::numeric_limits::max()); } m_image->m_magnificationWindow->setVisible(false); //copy the part of the view to be shown magnified const int size = Worksheet::convertToSceneUnits(2.0, Worksheet::Centimeter)/transform().m11(); const QRectF copyRect(pos.x() - size/(2*magnificationFactor), pos.y() - size/(2*magnificationFactor), size/magnificationFactor, size/magnificationFactor); QPixmap px = QPixmap::grabWidget(this, mapFromScene(copyRect).boundingRect()); px = px.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); //draw the bounding rect QPainter painter(&px); const QPen pen = QPen(Qt::lightGray, 2/transform().m11()); painter.setPen(pen); QRect rect = px.rect(); rect.setWidth(rect.width()-pen.widthF()/2); rect.setHeight(rect.height()-pen.widthF()/2); painter.drawRect(rect); //set the pixmap m_image->m_magnificationWindow->setPixmap(px); m_image->m_magnificationWindow->setPos(pos.x()- px.width()/2, pos.y()- px.height()/2); m_image->m_magnificationWindow->setVisible(true); } else if (m_image->m_magnificationWindow) { m_image->m_magnificationWindow->setVisible(false); } QGraphicsView::mouseMoveEvent(event); } void DatapickerImageView::contextMenuEvent(QContextMenuEvent* e) { Q_UNUSED(e); //no need to propagate the event to the scene and graphics items QMenu *menu = new QMenu(this); this->createContextMenu(menu); menu->exec(QCursor::pos()); } //############################################################################## //#################################### SLOTs ############################### //############################################################################## void DatapickerImageView::changePointsType(QAction* action) { if (action==setAxisPointsAction) m_image->setPlotPointsType(DatapickerImage::AxisPoints); else if (action==setCurvePointsAction) m_image->setPlotPointsType(DatapickerImage::CurvePoints); else if (action==selectSegmentAction) m_image->setPlotPointsType(DatapickerImage::SegmentPoints); } void DatapickerImageView::changeZoom(QAction* action) { if (action==zoomInViewAction) { scale(1.2, 1.2); } else if (action==zoomOutViewAction) { scale(1.0/1.2, 1.0/1.2); } else if (action==zoomOriginAction) { static const float hscale = QApplication::desktop()->physicalDpiX()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); static const float vscale = QApplication::desktop()->physicalDpiY()/(25.4*Worksheet::convertToSceneUnits(1,Worksheet::Millimeter)); setTransform(QTransform::fromScale(hscale, vscale)); m_rotationAngle = 0; } else if (action==zoomFitPageWidthAction) { float scaleFactor = viewport()->width()/scene()->sceneRect().width(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } else if (action==zoomFitPageHeightAction) { float scaleFactor = viewport()->height()/scene()->sceneRect().height(); setTransform(QTransform::fromScale(scaleFactor, scaleFactor)); m_rotationAngle = 0; } currentZoomAction=action; if (tbZoom) tbZoom->setDefaultAction(action); //change and set angle if tranform reset changeRotationAngle(); } void DatapickerImageView::changeSelectedItemsPosition(QAction* action) { if (scene()->selectedItems().isEmpty()) return; QPointF shift(0, 0); if (action == shiftLeftAction) shift.setX(1); else if (action == shiftRightAction) shift.setX(-1); else if (action == shiftUpAction) shift.setY(-1); else if (action == shiftDownAction) shift.setY(1); m_image->beginMacro(i18n("%1: change position of selected DatapickerPoints.", m_image->name())); const QVector axisPoints = m_image->children(AbstractAspect::IncludeHidden); for (auto* point : axisPoints) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); int pointIndex = m_image->indexOfChild(point, AbstractAspect::IncludeHidden); if (pointIndex == -1) continue; DatapickerImage::ReferencePoints points = m_image->axisPoints(); points.scenePos[pointIndex].setX(point->position().x()); points.scenePos[pointIndex].setY(point->position().y()); m_image->setUndoAware(false); m_image->setAxisPoints(points); m_image->setUndoAware(true); } for (auto* curve : m_image->parentAspect()->children()) { for (auto* point : curve->children(AbstractAspect::IncludeHidden)) { if (!point->graphicsItem()->isSelected()) continue; QPointF newPos = point->position(); newPos = newPos + shift; point->setPosition(newPos); } } m_image->endMacro(); } void DatapickerImageView::mouseModeChanged(QAction* action) { if (action==selectAndEditModeAction) { m_mouseMode = SelectAndEditMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(true); } else if (action==navigationModeAction) { m_mouseMode = NavigationMode; setInteractive(false); setDragMode(QGraphicsView::ScrollHandDrag); m_image->setSegmentsHoverEvent(false); } else if (action==zoomSelectionModeAction) { m_mouseMode = ZoomSelectionMode; setInteractive(false); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } else { m_mouseMode = SelectAndMoveMode; setInteractive(true); setDragMode(QGraphicsView::NoDrag); m_image->setSegmentsHoverEvent(false); } } void DatapickerImageView::magnificationChanged(QAction* action) { if (action==noMagnificationAction) magnificationFactor = 0; else if (action==twoTimesMagnificationAction) magnificationFactor = 2; else if (action==threeTimesMagnificationAction) magnificationFactor = 3; else if (action==fourTimesMagnificationAction) magnificationFactor = 4; else if (action==fiveTimesMagnificationAction) magnificationFactor = 5; } void DatapickerImageView::addCurve() { m_datapicker->beginMacro(i18n("%1: add new curve.", m_datapicker->name())); DatapickerCurve* curve = new DatapickerCurve(i18n("Curve")); curve->addDatasheet(m_image->axisPoints().type); m_datapicker->addChild(curve); m_datapicker->endMacro(); } void DatapickerImageView::changeRotationAngle() { this->rotate(m_rotationAngle); this->rotate(-m_image->rotationAngle()); m_rotationAngle = m_image->rotationAngle(); updateBackground(); } void DatapickerImageView::handleImageActions() { if (m_image->isLoaded) { magnificationActionGroup->setEnabled(true); setAxisPointsAction->setEnabled(true); int pointsCount = m_image->childCount(AbstractAspect::IncludeHidden); if (pointsCount>0) navigationActionGroup->setEnabled(true); else navigationActionGroup->setEnabled(false); if (pointsCount > 2) { addCurveAction->setEnabled(true); if (m_datapicker->activeCurve()) { setCurvePointsAction->setEnabled(true); selectSegmentAction->setEnabled(true); } else { setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } else { addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); if (m_image->plotPointsType() != DatapickerImage::AxisPoints) { m_image->setUndoAware(false); m_image->setPlotPointsType(DatapickerImage::AxisPoints); m_image->setUndoAware(true); } } } else { navigationActionGroup->setEnabled(false); magnificationActionGroup->setEnabled(false); setAxisPointsAction->setEnabled(false); addCurveAction->setEnabled(false); setCurvePointsAction->setEnabled(false); selectSegmentAction->setEnabled(false); } } void DatapickerImageView::exportToFile(const QString& path, const WorksheetView::ExportFormat format, const int resolution) { QRectF sourceRect; sourceRect = scene()->sceneRect(); //print if (format==WorksheetView::Pdf) { QPrinter printer(QPrinter::HighResolution); printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); printer.setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer.setPageMargins(0,0,0,0, QPrinter::Millimeter); printer.setPrintRange(QPrinter::PageRange); printer.setCreator( QLatin1String("LabPlot ") + LVERSION ); QPainter painter(&printer); painter.setRenderHint(QPainter::Antialiasing); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.begin(&printer); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else if (format==WorksheetView::Svg) { QSvgGenerator generator; generator.setFileName(path); int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*QApplication::desktop()->physicalDpiX()/25.4; h = h*QApplication::desktop()->physicalDpiY()/25.4; generator.setSize(QSize(w, h)); QRectF targetRect(0, 0, w, h); generator.setViewBox(targetRect); QPainter painter; painter.begin(&generator); exportPaint(&painter, targetRect, sourceRect); painter.end(); } else { //PNG //TODO add all formats supported by Qt in QImage int w = Worksheet::convertFromSceneUnits(sourceRect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(sourceRect.height(), Worksheet::Millimeter); w = w*resolution/25.4; h = h*resolution/25.4; QImage image(QSize(w, h), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QRectF targetRect(0, 0, w, h); QPainter painter; painter.begin(&image); painter.setRenderHint(QPainter::Antialiasing); exportPaint(&painter, targetRect, sourceRect); painter.end(); image.save(path, "png"); } } void DatapickerImageView::exportPaint(QPainter* painter, const QRectF& targetRect, const QRectF& sourceRect) { painter->save(); painter->scale(targetRect.width()/sourceRect.width(), targetRect.height()/sourceRect.height()); drawBackground(painter, sourceRect); painter->restore(); m_image->setPrinting(true); scene()->render(painter, QRectF(), sourceRect); m_image->setPrinting(false); } void DatapickerImageView::print(QPrinter* printer) { const QRectF scene_rect = sceneRect(); int w = Worksheet::convertFromSceneUnits(scene_rect.width(), Worksheet::Millimeter); int h = Worksheet::convertFromSceneUnits(scene_rect.height(), Worksheet::Millimeter); printer->setPaperSize( QSizeF(w, h), QPrinter::Millimeter); printer->setPageMargins(0,0,0,0, QPrinter::Millimeter); printer->setPrintRange(QPrinter::PageRange); printer->setCreator( QString("LabPlot ") + LVERSION ); QPainter painter(printer); QRectF targetRect(0, 0, painter.device()->width(),painter.device()->height()); painter.setRenderHint(QPainter::Antialiasing); painter.begin(printer); painter.save(); painter.scale(targetRect.width()/scene_rect.width(), targetRect.height()/scene_rect.height()); // canvas if (m_image->isLoaded) { if (m_image->plotImageType() == DatapickerImage::OriginalImage) { QImage todraw = m_image->originalPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else if (m_image->plotImageType() == DatapickerImage::ProcessedImage) { QImage todraw = m_image->processedPlotImage.scaled(scene_rect.width(), scene_rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(scene_rect.topLeft(), todraw); } else { painter.fillRect(scene_rect, Qt::white); } } else { painter.setBrush(QBrush(Qt::gray)); painter.drawRect(scene_rect); } painter.restore(); m_image->setPrinting(true); scene()->render(&painter, QRectF(), scene_rect); m_image->setPrinting(false); painter.end(); } void DatapickerImageView::updateBackground() { invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer); } diff --git a/src/commonfrontend/datapicker/DatapickerView.cpp b/src/commonfrontend/datapicker/DatapickerView.cpp index c29bfd000..6341229bf 100644 --- a/src/commonfrontend/datapicker/DatapickerView.cpp +++ b/src/commonfrontend/datapicker/DatapickerView.cpp @@ -1,216 +1,216 @@ /*************************************************************************** File : DatapickerView.cpp Project : LabPlot Description : View class for Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatapickerView.h" #include "backend/datapicker/Datapicker.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datapicker/DatapickerImage.h" #include "commonfrontend/workbook/WorkbookView.h" #include "backend/datapicker/DatapickerCurve.h" #include "commonfrontend/datapicker/DatapickerImageView.h" -#include #include #include -#include +#include + #include /*! \class DatapickerView \brief View class for Datapicker \ingroup commonfrontend */ DatapickerView::DatapickerView(Datapicker* datapicker) : QWidget(), m_tabWidget(new TabWidget(this)), m_datapicker(datapicker), lastSelectedIndex(0) { m_tabWidget->setTabPosition(QTabWidget::South); m_tabWidget->setTabShape(QTabWidget::Rounded); // m_tabWidget->setMovable(true); m_tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); m_tabWidget->setMinimumSize(600, 600); QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_tabWidget); //add tab for each children view m_initializing = true; foreach(const AbstractAspect* aspect, m_datapicker->children(AbstractAspect::IncludeHidden)) { handleAspectAdded(aspect); foreach(const AbstractAspect* aspect, aspect->children()) { handleAspectAdded(aspect); } } m_initializing = false; //SIGNALs/SLOTs connect(m_datapicker, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(handleDescriptionChanged(const AbstractAspect*))); connect(m_datapicker, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(handleAspectAdded(const AbstractAspect*))); connect(m_datapicker, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(handleAspectAboutToBeRemoved(const AbstractAspect*))); connect(m_datapicker, SIGNAL(datapickerItemSelected(int)), this, SLOT(itemSelected(int))); connect(m_tabWidget, SIGNAL(currentChanged(int)), SLOT(tabChanged(int))); connect(m_tabWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTabContextMenu(QPoint))); connect(m_tabWidget, SIGNAL(tabMoved(int,int)), this, SLOT(tabMoved(int,int))); } DatapickerView::~DatapickerView() { //delete all children views here, its own view will be deleted in ~AbstractPart() foreach(const AbstractAspect* aspect, m_datapicker->children(AbstractAspect::IncludeHidden)) { foreach(const AbstractAspect* aspect, aspect->children()) { const AbstractPart* part = dynamic_cast(aspect); if (part) part->deleteView(); } const AbstractPart* part = dynamic_cast(aspect); if (part) part->deleteView(); } } void DatapickerView::fillToolBar(QToolBar* toolBar) { DatapickerImageView* view = dynamic_cast(m_datapicker->image()->view()); view->fillToolBar(toolBar); } void DatapickerView::createContextMenu(QMenu* menu) const { Q_ASSERT(menu); m_datapicker->image()->createContextMenu(menu); } int DatapickerView::currentIndex() const { return m_tabWidget->currentIndex(); } //############################################################################## //######################### Private slots #################################### //############################################################################## /*! called when the current tab was changed. Propagates the selection of \c Spreadsheet or of a \c DatapickerImage object to \c Datapicker. */ void DatapickerView::tabChanged(int index) { if (m_initializing) return; if (index==-1) return; m_datapicker->setChildSelectedInView(lastSelectedIndex, false); m_datapicker->setChildSelectedInView(index, true); lastSelectedIndex = index; } void DatapickerView::tabMoved(int from, int to) { Q_UNUSED(from); Q_UNUSED(to); //TODO: // AbstractAspect* aspect = m_datapicker->child(to); // if (aspect) { // m_tabMoving = true; // AbstractAspect* sibling = m_datapicker->child(from); // qDebug()<<"insert: " << to << " " << aspect->name() << ", " << from << " " << sibling->name(); // aspect->remove(); // m_datapicker->insertChildBefore(aspect, sibling); // qDebug()<<"inserted"; // m_tabMoving = false; // } } void DatapickerView::itemSelected(int index) { m_initializing = true; m_tabWidget->setCurrentIndex(index); m_initializing = false; } void DatapickerView::showTabContextMenu(const QPoint& point) { QMenu* menu = 0; AbstractAspect* aspect = m_datapicker->child(m_tabWidget->currentIndex(), AbstractAspect::IncludeHidden); Spreadsheet* spreadsheet = dynamic_cast(aspect); if (spreadsheet) { menu = spreadsheet->createContextMenu(); } else { DatapickerImage* image = dynamic_cast(aspect); if (image) menu = image->createContextMenu(); } if (menu) menu->exec(m_tabWidget->mapToGlobal(point)); } void DatapickerView::handleDescriptionChanged(const AbstractAspect* aspect) { int index = -1; QString name; if (aspect->parentAspect() == m_datapicker) { //datapicker curve was renamed index= m_datapicker->indexOfChild(aspect, AbstractAspect::IncludeHidden); name = aspect->name() + ": " + aspect->children().first()->name(); } else { //data spreadsheet was renamed or one of its columns, which is not relevant here index = m_datapicker->indexOfChild(aspect->parentAspect(), AbstractAspect::IncludeHidden); name = aspect->parentAspect()->name() + ": " + aspect->name(); } if (index != -1) m_tabWidget->setTabText(index, name); } void DatapickerView::handleAspectAdded(const AbstractAspect* aspect) { int index; const AbstractPart* part; QString name; if (dynamic_cast(aspect)) { index = 0; part = dynamic_cast(aspect); name = aspect->name(); } else if (dynamic_cast(aspect)) { index = m_datapicker->indexOfChild(aspect, AbstractAspect::IncludeHidden); const Spreadsheet* spreadsheet = dynamic_cast(aspect->child(0)); Q_ASSERT(spreadsheet); part = dynamic_cast(spreadsheet); name = aspect->name() + ": " + spreadsheet->name(); } else { return; } m_tabWidget->insertTab(index, part->view(), name); m_tabWidget->setTabIcon(m_tabWidget->count(), aspect->icon()); } void DatapickerView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const DatapickerCurve* curve = dynamic_cast(aspect); if (curve) { int index = m_datapicker->indexOfChild(aspect, AbstractAspect::IncludeHidden); m_tabWidget->removeTab(index); } } diff --git a/src/commonfrontend/workbook/WorkbookView.cpp b/src/commonfrontend/workbook/WorkbookView.cpp index b2985ec8f..266bd5c22 100644 --- a/src/commonfrontend/workbook/WorkbookView.cpp +++ b/src/commonfrontend/workbook/WorkbookView.cpp @@ -1,211 +1,208 @@ /*************************************************************************** File : WorkbookView.cpp Project : LabPlot Description : View class for Workbook -------------------------------------------------------------------- Copyright : (C) 2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "WorkbookView.h" #include "backend/core/AbstractAspect.h" #include "backend/core/AbstractPart.h" #include "backend/core/Workbook.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" -#include #include #include -#include -#include #include /*! \class WorkbookView \brief View class for Workbook \ingroup commonfrontend */ WorkbookView::WorkbookView(Workbook* workbook) : QWidget(), m_tabWidget(new TabWidget(this)), m_workbook(workbook), lastSelectedIndex(0) { m_tabWidget->setTabPosition(QTabWidget::South); m_tabWidget->setTabShape(QTabWidget::Rounded); m_tabWidget->setMovable(true); m_tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); m_tabWidget->setMinimumSize(200, 200); QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_tabWidget); //add tab for each children view m_initializing = true; foreach(const AbstractAspect* aspect, m_workbook->children()) handleAspectAdded(aspect); m_initializing = false; //Actions action_add_spreadsheet = new QAction(QIcon::fromTheme("labplot-spreadsheet"), i18n("Add new Spreadsheet"), this); action_add_matrix = new QAction(QIcon::fromTheme("labplot-matrix"), i18n("Add new Matrix"), this); connect(action_add_spreadsheet, SIGNAL(triggered()), this, SLOT(addSpreadsheet())); connect(action_add_matrix, SIGNAL(triggered()), this, SLOT(addMatrix())); //SIGNALs/SLOTs connect(m_workbook, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(handleDescriptionChanged(const AbstractAspect*))); connect(m_workbook, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(handleAspectAdded(const AbstractAspect*))); connect(m_workbook, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(handleAspectAboutToBeRemoved(const AbstractAspect*))); connect(m_workbook, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*))); connect(m_workbook, SIGNAL(workbookItemSelected(int)), this, SLOT(itemSelected(int)) ); connect(m_tabWidget, SIGNAL(currentChanged(int)), SLOT(tabChanged(int))); connect(m_tabWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showTabContextMenu(QPoint))); connect(m_tabWidget, SIGNAL(tabMoved(int,int)), this, SLOT(tabMoved(int,int))); } WorkbookView::~WorkbookView() { //no need to react on currentChanged() in TabWidget when views are deleted disconnect(m_tabWidget, 0, 0, 0); //delete all children views here, its own view will be deleted in ~AbstractPart() foreach(const AbstractPart* part, m_workbook->children()) part->deleteView(); } int WorkbookView::currentIndex() const { return m_tabWidget->currentIndex(); } //############################################################################## //######################### Private slots #################################### //############################################################################## /*! called when the current tab was changed. Propagates the selection of \c Spreadsheet or of a \c Matrix object to \c Workbook. */ void WorkbookView::tabChanged(int index) { if (m_initializing) return; if (index==-1) return; m_workbook->setChildSelectedInView(lastSelectedIndex, false); m_workbook->setChildSelectedInView(index, true); lastSelectedIndex = index; } void WorkbookView::tabMoved(int from, int to) { Q_UNUSED(from); Q_UNUSED(to); //TODO: // AbstractAspect* aspect = m_workbook->child(to); // if (aspect) { // m_tabMoving = true; // AbstractAspect* sibling = m_workbook->child(from); // qDebug()<<"insert: " << to << " " << aspect->name() << ", " << from << " " << sibling->name(); // aspect->remove(); // m_workbook->insertChildBefore(aspect, sibling); // qDebug()<<"inserted"; // m_tabMoving = false; // } } void WorkbookView::itemSelected(int index) { m_tabWidget->setCurrentIndex(index); } /*! * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions. * The menu is used * - as the context menu in WorkbookView * - as the "spreadsheet menu" in the main menu-bar (called form MainWin) * - as a part of the spreadsheet context menu in project explorer */ void WorkbookView::createContextMenu(QMenu* menu) const { Q_ASSERT(menu); QAction* firstAction = 0; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, action_add_spreadsheet); menu->insertAction(firstAction, action_add_matrix); menu->insertSeparator(firstAction); } void WorkbookView::showTabContextMenu(const QPoint& point) { QMenu* menu = 0; AbstractAspect* aspect = m_workbook->child(m_tabWidget->currentIndex()); Spreadsheet* spreadsheet = dynamic_cast(aspect); if (spreadsheet) { menu = spreadsheet->createContextMenu(); } else { Matrix* matrix = dynamic_cast(aspect); if (matrix) menu = matrix->createContextMenu(); } if (menu) menu->exec(m_tabWidget->mapToGlobal(point)); } void WorkbookView::addMatrix() { Matrix* matrix = new Matrix(0, i18n("Matrix")); m_workbook->addChild(matrix); } void WorkbookView::addSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); m_workbook->addChild(spreadsheet); } void WorkbookView::handleDescriptionChanged(const AbstractAspect* aspect) { int index = m_workbook->indexOfChild(aspect); if (index != -1 && indexcount()) m_tabWidget->setTabText(index, aspect->name()); } void WorkbookView::handleAspectAdded(const AbstractAspect* aspect) { const AbstractPart* part = dynamic_cast(aspect); if (!part) return; int index = m_workbook->indexOfChild(aspect); m_tabWidget->insertTab(index, part->view(), aspect->name()); m_tabWidget->setCurrentIndex(index); m_tabWidget->setTabIcon(m_tabWidget->count(), aspect->icon()); this->tabChanged(index); } void WorkbookView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { int index = m_workbook->indexOfChild(aspect); m_tabWidget->removeTab(index); } diff --git a/src/commonfrontend/workbook/WorkbookView.h b/src/commonfrontend/workbook/WorkbookView.h index 1cd62a48b..2e2c95ebd 100644 --- a/src/commonfrontend/workbook/WorkbookView.h +++ b/src/commonfrontend/workbook/WorkbookView.h @@ -1,87 +1,86 @@ /*************************************************************************** File : WorkbookView.h Project : LabPlot Description : View class for Workbook -------------------------------------------------------------------- Copyright : (C) 2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef WORKBOOKVIEW_H #define WORKBOOKVIEW_H -#include #include #include class AbstractAspect; class Workbook; class QAction; class QMenu; class QPrinter; class QToolBar; class TabWidget : public QTabWidget { Q_OBJECT public: explicit TabWidget(QWidget* parent) : QTabWidget(parent) { connect(tabBar(),SIGNAL(tabMoved(int,int)),this, SIGNAL(tabMoved(int,int))); } signals: void tabMoved(int, int); }; class WorkbookView : public QWidget { Q_OBJECT public: explicit WorkbookView(Workbook*); virtual ~WorkbookView(); int currentIndex() const; private: TabWidget* m_tabWidget; Workbook* m_workbook; int lastSelectedIndex; bool m_initializing; //actions QAction* action_add_spreadsheet; QAction* action_add_matrix; private slots: void createContextMenu(QMenu*) const; void showTabContextMenu(const QPoint&); void addSpreadsheet(); void addMatrix(); void itemSelected(int); void tabChanged(int); void tabMoved(int,int); void handleDescriptionChanged(const AbstractAspect*); void handleAspectAdded(const AbstractAspect*); void handleAspectAboutToBeRemoved(const AbstractAspect*); }; #endif