2011 lines
75 KiB
C++
2011 lines
75 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#ifndef _PreComp_
|
|
# include <boost/bind.hpp>
|
|
# include <QAction>
|
|
# include <QActionGroup>
|
|
# include <QApplication>
|
|
# include <qcursor.h>
|
|
# include <QVBoxLayout>
|
|
# include <qlayout.h>
|
|
# include <qstatusbar.h>
|
|
# include <QContextMenuEvent>
|
|
# include <QMenu>
|
|
# include <QPixmap>
|
|
# include <QTimer>
|
|
# include <QToolTip>
|
|
# include <QHeaderView>
|
|
#endif
|
|
|
|
#include <Base/Console.h>
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <App/DocumentObjectGroup.h>
|
|
#include <App/GeoFeatureGroupExtension.h>
|
|
|
|
#include "Tree.h"
|
|
#include "Command.h"
|
|
#include "Document.h"
|
|
#include "BitmapFactory.h"
|
|
#include "ViewProviderDocumentObject.h"
|
|
#include "MenuManager.h"
|
|
#include "Application.h"
|
|
#include "MainWindow.h"
|
|
#include "View3DInventor.h"
|
|
#include "View3DInventorViewer.h"
|
|
#include "Widgets.h"
|
|
|
|
using namespace Gui;
|
|
|
|
QPixmap* TreeWidget::documentPixmap = 0;
|
|
const int TreeWidget::DocumentType = 1000;
|
|
const int TreeWidget::ObjectType = 1001;
|
|
|
|
|
|
/* TRANSLATOR Gui::TreeWidget */
|
|
TreeWidget::TreeWidget(QWidget* parent)
|
|
: QTreeWidget(parent), contextItem(0), fromOutside(false)
|
|
{
|
|
this->setDragEnabled(true);
|
|
this->setAcceptDrops(true);
|
|
this->setDropIndicatorShown(false);
|
|
this->setRootIsDecorated(false);
|
|
|
|
this->createGroupAction = new QAction(this);
|
|
this->createGroupAction->setText(tr("Create group..."));
|
|
this->createGroupAction->setStatusTip(tr("Create a group"));
|
|
connect(this->createGroupAction, SIGNAL(triggered()),
|
|
this, SLOT(onCreateGroup()));
|
|
|
|
this->relabelObjectAction = new QAction(this);
|
|
this->relabelObjectAction->setText(tr("Rename"));
|
|
this->relabelObjectAction->setStatusTip(tr("Rename object"));
|
|
#ifndef Q_OS_MAC
|
|
this->relabelObjectAction->setShortcut(Qt::Key_F2);
|
|
#endif
|
|
connect(this->relabelObjectAction, SIGNAL(triggered()),
|
|
this, SLOT(onRelabelObject()));
|
|
|
|
this->finishEditingAction = new QAction(this);
|
|
this->finishEditingAction->setText(tr("Finish editing"));
|
|
this->finishEditingAction->setStatusTip(tr("Finish editing object"));
|
|
connect(this->finishEditingAction, SIGNAL(triggered()),
|
|
this, SLOT(onFinishEditing()));
|
|
|
|
this->skipRecomputeAction = new QAction(this);
|
|
this->skipRecomputeAction->setCheckable(true);
|
|
this->skipRecomputeAction->setText(tr("Skip recomputes"));
|
|
this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));
|
|
connect(this->skipRecomputeAction, SIGNAL(toggled(bool)),
|
|
this, SLOT(onSkipRecompute(bool)));
|
|
|
|
this->markRecomputeAction = new QAction(this);
|
|
this->markRecomputeAction->setText(tr("Mark to recompute"));
|
|
this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed"));
|
|
connect(this->markRecomputeAction, SIGNAL(triggered()),
|
|
this, SLOT(onMarkRecompute()));
|
|
|
|
this->searchObjectsAction = new QAction(this);
|
|
this->searchObjectsAction->setText(tr("Search..."));
|
|
this->searchObjectsAction->setStatusTip(tr("Search for objects"));
|
|
connect(this->searchObjectsAction, SIGNAL(triggered()),
|
|
this, SLOT(onSearchObjects()));
|
|
|
|
// Setup connections
|
|
connectNewDocument = Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, _1));
|
|
connectDelDocument = Application::Instance->signalDeleteDocument.connect(boost::bind(&TreeWidget::slotDeleteDocument, this, _1));
|
|
connectRenDocument = Application::Instance->signalRenameDocument.connect(boost::bind(&TreeWidget::slotRenameDocument, this, _1));
|
|
connectActDocument = Application::Instance->signalActiveDocument.connect(boost::bind(&TreeWidget::slotActiveDocument, this, _1));
|
|
connectRelDocument = Application::Instance->signalRelabelDocument.connect(boost::bind(&TreeWidget::slotRelabelDocument, this, _1));
|
|
|
|
QStringList labels;
|
|
labels << tr("Labels & Attributes");
|
|
this->setHeaderLabels(labels);
|
|
// make sure to show a horizontal scrollbar if needed
|
|
#if QT_VERSION >= 0x050000
|
|
this->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
|
#else
|
|
this->header()->setResizeMode(0, QHeaderView::ResizeToContents);
|
|
#endif
|
|
this->header()->setStretchLastSection(false);
|
|
|
|
// Add the first main label
|
|
this->rootItem = new QTreeWidgetItem(this);
|
|
this->rootItem->setText(0, tr("Application"));
|
|
this->rootItem->setFlags(Qt::ItemIsEnabled);
|
|
this->expandItem(this->rootItem);
|
|
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
#if QT_VERSION >= 0x040200
|
|
// causes unexpected drop events (possibly only with Qt4.1.x)
|
|
this->setMouseTracking(true); // needed for itemEntered() to work
|
|
#endif
|
|
|
|
this->statusTimer = new QTimer(this);
|
|
|
|
connect(this->statusTimer, SIGNAL(timeout()),
|
|
this, SLOT(onTestStatus()));
|
|
connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)),
|
|
this, SLOT(onItemEntered(QTreeWidgetItem*)));
|
|
connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)),
|
|
this, SLOT(onItemCollapsed(QTreeWidgetItem*)));
|
|
connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
|
|
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
|
|
connect(this, SIGNAL(itemSelectionChanged()),
|
|
this, SLOT(onItemSelectionChanged()));
|
|
|
|
this->statusTimer->setSingleShot(true);
|
|
this->statusTimer->start(300);
|
|
documentPixmap = new QPixmap(Gui::BitmapFactory().pixmap("Document"));
|
|
}
|
|
|
|
TreeWidget::~TreeWidget()
|
|
{
|
|
connectNewDocument.disconnect();
|
|
connectDelDocument.disconnect();
|
|
connectRenDocument.disconnect();
|
|
connectActDocument.disconnect();
|
|
connectRelDocument.disconnect();
|
|
}
|
|
|
|
void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
|
|
{
|
|
// ask workbenches and view provider, ...
|
|
MenuItem view;
|
|
Gui::Application::Instance->setupContextMenu("Tree", &view);
|
|
|
|
QMenu contextMenu;
|
|
QMenu subMenu;
|
|
QMenu editMenu;
|
|
QActionGroup subMenuGroup(&subMenu);
|
|
subMenuGroup.setExclusive(true);
|
|
connect(&subMenuGroup, SIGNAL(triggered(QAction*)),
|
|
this, SLOT(onActivateDocument(QAction*)));
|
|
MenuManager::getInstance()->setupContextMenu(&view, contextMenu);
|
|
|
|
// get the current item
|
|
this->contextItem = itemAt(e->pos());
|
|
if (this->contextItem && this->contextItem->type() == DocumentType) {
|
|
if (!contextMenu.actions().isEmpty())
|
|
contextMenu.addSeparator();
|
|
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
|
|
App::Document* doc = docitem->document()->getDocument();
|
|
this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute));
|
|
contextMenu.addAction(this->skipRecomputeAction);
|
|
contextMenu.addAction(this->markRecomputeAction);
|
|
contextMenu.addAction(this->createGroupAction);
|
|
contextMenu.addAction(this->searchObjectsAction);
|
|
}
|
|
else if (this->contextItem && this->contextItem->type() == ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
|
|
(this->contextItem);
|
|
if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup
|
|
::getClassTypeId())) {
|
|
QList<QAction*> acts = contextMenu.actions();
|
|
if (!acts.isEmpty()) {
|
|
QAction* first = acts.front();
|
|
QAction* sep = contextMenu.insertSeparator(first);
|
|
contextMenu.insertAction(sep, this->createGroupAction);
|
|
}
|
|
else
|
|
contextMenu.addAction(this->createGroupAction);
|
|
}
|
|
if (!contextMenu.actions().isEmpty())
|
|
contextMenu.addSeparator();
|
|
contextMenu.addAction(this->markRecomputeAction);
|
|
contextMenu.addAction(this->relabelObjectAction);
|
|
|
|
// if only one item is selected setup the edit menu
|
|
if (this->selectedItems().size() == 1) {
|
|
objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing()));
|
|
QList<QAction*> editAct = editMenu.actions();
|
|
if (!editAct.isEmpty()) {
|
|
QAction* topact = contextMenu.actions().front();
|
|
for (QList<QAction*>::iterator it = editAct.begin(); it != editAct.end(); ++it)
|
|
contextMenu.insertAction(topact, *it);
|
|
QAction* first = editAct.front();
|
|
contextMenu.setDefaultAction(first);
|
|
if (objitem->object()->isEditing())
|
|
contextMenu.insertAction(topact, this->finishEditingAction);
|
|
contextMenu.insertSeparator(topact);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add a submenu to active a document if two or more exist
|
|
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
|
|
if (docs.size() >= 2) {
|
|
App::Document* activeDoc = App::GetApplication().getActiveDocument();
|
|
subMenu.setTitle(tr("Activate document"));
|
|
contextMenu.addMenu(&subMenu);
|
|
QAction* active = 0;
|
|
for (std::vector<App::Document*>::iterator it = docs.begin(); it != docs.end(); ++it) {
|
|
QString label = QString::fromUtf8((*it)->Label.getValue());
|
|
QAction* action = subMenuGroup.addAction(label);
|
|
action->setCheckable(true);
|
|
action->setStatusTip(tr("Activate document %1").arg(label));
|
|
action->setData(QByteArray((*it)->getName()));
|
|
if (*it == activeDoc) active = action;
|
|
}
|
|
|
|
if (active)
|
|
active->setChecked(true);
|
|
subMenu.addActions(subMenuGroup.actions());
|
|
}
|
|
|
|
if (contextMenu.actions().count() > 0)
|
|
contextMenu.exec(QCursor::pos());
|
|
}
|
|
|
|
void TreeWidget::onCreateGroup()
|
|
{
|
|
QString name = tr("Group");
|
|
if (this->contextItem->type() == DocumentType) {
|
|
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
|
|
App::Document* doc = docitem->document()->getDocument();
|
|
QString cmd = QString::fromLatin1("App.getDocument(\"%1\").addObject"
|
|
"(\"App::DocumentObjectGroup\",\"%2\")")
|
|
.arg(QString::fromLatin1(doc->getName()), name);
|
|
Gui::Document* gui = Gui::Application::Instance->getDocument(doc);
|
|
gui->openCommand("Create group");
|
|
Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
|
|
gui->commitCommand();
|
|
}
|
|
else if (this->contextItem->type() == ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
|
|
(this->contextItem);
|
|
App::DocumentObject* obj = objitem->object()->getObject();
|
|
App::Document* doc = obj->getDocument();
|
|
QString cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\")"
|
|
".newObject(\"App::DocumentObjectGroup\",\"%3\")")
|
|
.arg(QString::fromLatin1(doc->getName()),
|
|
QString::fromLatin1(obj->getNameInDocument()),
|
|
name);
|
|
Gui::Document* gui = Gui::Application::Instance->getDocument(doc);
|
|
gui->openCommand("Create group");
|
|
Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8());
|
|
gui->commitCommand();
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onRelabelObject()
|
|
{
|
|
QTreeWidgetItem* item = currentItem();
|
|
if (item)
|
|
editItem(item);
|
|
}
|
|
|
|
void TreeWidget::onStartEditing()
|
|
{
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
if (action) {
|
|
if (this->contextItem && this->contextItem->type() == ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
|
|
(this->contextItem);
|
|
int edit = action->data().toInt();
|
|
App::DocumentObject* obj = objitem->object()->getObject();
|
|
if (!obj) return;
|
|
Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
|
|
MDIView *view = doc->getActiveView();
|
|
if (view) getMainWindow()->setActiveWindow(view);
|
|
|
|
// Always open a transaction here doesn't make much sense because:
|
|
// - many objects open transactions when really changing some properties
|
|
// - this leads to certain inconsistencies with the doubleClicked() method
|
|
// So, only the view provider class should decide what to do
|
|
#if 0
|
|
// open a transaction before starting edit mode
|
|
std::string cmd("Edit ");
|
|
cmd += obj->Label.getValue();
|
|
doc->openCommand(cmd.c_str());
|
|
bool ok = doc->setEdit(objitem->object(), edit);
|
|
if (!ok) doc->abortCommand();
|
|
#else
|
|
doc->setEdit(objitem->object(), edit);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onFinishEditing()
|
|
{
|
|
if (this->contextItem && this->contextItem->type() == ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
|
|
(this->contextItem);
|
|
App::DocumentObject* obj = objitem->object()->getObject();
|
|
if (!obj) return;
|
|
Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
|
|
doc->commitCommand();
|
|
doc->resetEdit();
|
|
doc->getDocument()->recompute();
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onSkipRecompute(bool on)
|
|
{
|
|
// if a document item is selected then touch all objects
|
|
if (this->contextItem && this->contextItem->type() == DocumentType) {
|
|
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
|
|
App::Document* doc = docitem->document()->getDocument();
|
|
doc->setStatus(App::Document::SkipRecompute, on);
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onMarkRecompute()
|
|
{
|
|
// if a document item is selected then touch all objects
|
|
if (this->contextItem && this->contextItem->type() == DocumentType) {
|
|
DocumentItem* docitem = static_cast<DocumentItem*>(this->contextItem);
|
|
App::Document* doc = docitem->document()->getDocument();
|
|
std::vector<App::DocumentObject*> obj = doc->getObjects();
|
|
for (std::vector<App::DocumentObject*>::iterator it = obj.begin(); it != obj.end(); ++it)
|
|
(*it)->enforceRecompute();
|
|
}
|
|
// mark all selected objects
|
|
else {
|
|
QList<QTreeWidgetItem*> items = this->selectedItems();
|
|
for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
|
|
if ((*it)->type() == ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>(*it);
|
|
App::DocumentObject* obj = objitem->object()->getObject();
|
|
obj->enforceRecompute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onSearchObjects()
|
|
{
|
|
emitSearchObjects();
|
|
}
|
|
|
|
void TreeWidget::onActivateDocument(QAction* active)
|
|
{
|
|
// activate the specified document
|
|
QByteArray docname = active->data().toByteArray();
|
|
Gui::Document* doc = Application::Instance->getDocument((const char*)docname);
|
|
if (!doc) return;
|
|
MDIView *view = doc->getActiveView();
|
|
if (!view) return;
|
|
getMainWindow()->setActiveWindow(view);
|
|
}
|
|
|
|
Qt::DropActions TreeWidget::supportedDropActions () const
|
|
{
|
|
Qt::DropActions da = QTreeWidget::supportedDropActions();
|
|
QList<QModelIndex> idxs = selectedIndexes();
|
|
|
|
if (idxs.size() == 1) {
|
|
// as we can't know here where the drop action will occur that is the best we can do
|
|
da |= Qt::LinkAction;
|
|
}
|
|
|
|
for (QList<QModelIndex>::Iterator it = idxs.begin(); it != idxs.end(); ++it) {
|
|
QTreeWidgetItem* item = itemFromIndex(*it);
|
|
if (item->type() != TreeWidget::ObjectType) {
|
|
return Qt::IgnoreAction;
|
|
}
|
|
QTreeWidgetItem* parent = item->parent();
|
|
if (parent) {
|
|
if (parent->type() != TreeWidget::ObjectType) {
|
|
da &= ~Qt::MoveAction;
|
|
}
|
|
else {
|
|
// Now check for object with a parent that is an ObjectType, too.
|
|
// If this object is *not* selected and *not* a group we are not allowed to remove
|
|
// its child (e.g. the sketch of a pad).
|
|
Gui::ViewProvider* vp = static_cast<DocumentObjectItem *>(parent)->object();
|
|
App::DocumentObject* obj = static_cast<DocumentObjectItem*>(item)->
|
|
object()->getObject();
|
|
if (!vp->canDragObjects() || !vp->canDragObject(obj)) {
|
|
da &= ~Qt::MoveAction;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
da &= ~Qt::MoveAction;
|
|
}
|
|
}
|
|
|
|
return da;
|
|
}
|
|
|
|
bool TreeWidget::event(QEvent *e)
|
|
{
|
|
#if 0
|
|
if (e->type() == QEvent::ShortcutOverride) {
|
|
QKeyEvent* ke = static_cast<QKeyEvent *>(e);
|
|
switch (ke->key()) {
|
|
case Qt::Key_Delete:
|
|
ke->accept();
|
|
}
|
|
}
|
|
#endif
|
|
return QTreeWidget::event(e);
|
|
}
|
|
|
|
void TreeWidget::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
#if 0
|
|
if (event && event->matches(QKeySequence::Delete)) {
|
|
event->ignore();
|
|
}
|
|
#endif
|
|
QTreeWidget::keyPressEvent(event);
|
|
}
|
|
|
|
void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event)
|
|
{
|
|
QTreeWidgetItem* item = itemAt(event->pos());
|
|
if (!item) return;
|
|
if (item->type() == TreeWidget::DocumentType) {
|
|
//QTreeWidget::mouseDoubleClickEvent(event);
|
|
const Gui::Document* doc = static_cast<DocumentItem*>(item)->document();
|
|
if (!doc) return;
|
|
MDIView *view = doc->getActiveView();
|
|
if (!view) return;
|
|
getMainWindow()->setActiveWindow(view);
|
|
}
|
|
else if (item->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>(item);
|
|
App::DocumentObject* obj = objitem->object()->getObject();
|
|
Gui::Document* doc = Gui::Application::Instance->getDocument(obj->getDocument());
|
|
MDIView *view = doc->getActiveView();
|
|
if (view) getMainWindow()->setActiveWindow(view);
|
|
if (!objitem->object()->doubleClicked())
|
|
QTreeWidget::mouseDoubleClickEvent(event);
|
|
}
|
|
}
|
|
|
|
void TreeWidget::startDrag(Qt::DropActions supportedActions)
|
|
{
|
|
QTreeWidget::startDrag(supportedActions);
|
|
}
|
|
|
|
QMimeData * TreeWidget::mimeData (const QList<QTreeWidgetItem *> items) const
|
|
{
|
|
// all selected items must reference an object from the same document
|
|
App::Document* doc=0;
|
|
for (QList<QTreeWidgetItem *>::ConstIterator it = items.begin(); it != items.end(); ++it) {
|
|
if ((*it)->type() != TreeWidget::ObjectType)
|
|
return 0;
|
|
App::DocumentObject* obj = static_cast<DocumentObjectItem *>(*it)->object()->getObject();
|
|
if (!doc)
|
|
doc = obj->getDocument();
|
|
else if (doc != obj->getDocument())
|
|
return 0;
|
|
}
|
|
return QTreeWidget::mimeData(items);
|
|
}
|
|
|
|
bool TreeWidget::dropMimeData(QTreeWidgetItem *parent, int index,
|
|
const QMimeData *data, Qt::DropAction action)
|
|
{
|
|
return QTreeWidget::dropMimeData(parent, index, data, action);
|
|
}
|
|
|
|
void TreeWidget::dragEnterEvent(QDragEnterEvent * event)
|
|
{
|
|
QTreeWidget::dragEnterEvent(event);
|
|
}
|
|
|
|
void TreeWidget::dragLeaveEvent(QDragLeaveEvent * event)
|
|
{
|
|
QTreeWidget::dragLeaveEvent(event);
|
|
}
|
|
|
|
QList<App::DocumentObject *> TreeWidget::buildListChildren(QTreeWidgetItem* targetitem, Gui::ViewProviderDocumentObject* vp)
|
|
{
|
|
// to check we do not drop the part on itself
|
|
QList<App::DocumentObject *> children;
|
|
children << vp->getObject();
|
|
for (int i=0; i<targetitem->childCount(); i++) {
|
|
Gui::ViewProviderDocumentObject* vpc = static_cast<DocumentObjectItem*>(targetitem->child(i))->object();
|
|
children << vpc->getObject();
|
|
}
|
|
return children;
|
|
}
|
|
|
|
void TreeWidget::dragMoveEvent(QDragMoveEvent *event)
|
|
{
|
|
QTreeWidget::dragMoveEvent(event);
|
|
if (!event->isAccepted())
|
|
return;
|
|
|
|
QTreeWidgetItem* targetItem = itemAt(event->pos());
|
|
if (!targetItem || this->isItemSelected(targetItem)) {
|
|
event->ignore();
|
|
}
|
|
else {
|
|
QTreeWidgetItem* parentTarget = targetItem->parent();
|
|
Qt::DropAction da = event->proposedAction();
|
|
|
|
if (targetItem->type() == TreeWidget::DocumentType) {
|
|
QList<QModelIndex> idxs = selectedIndexes();
|
|
App::Document* doc = static_cast<DocumentItem*>(targetItem)->
|
|
document()->getDocument();
|
|
for (QList<QModelIndex>::Iterator it = idxs.begin(); it != idxs.end(); ++it) {
|
|
QTreeWidgetItem* item = itemFromIndex(*it);
|
|
if (item->type() != TreeWidget::ObjectType) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
App::DocumentObject* obj = static_cast<DocumentObjectItem*>(item)->
|
|
object()->getObject();
|
|
if (doc != obj->getDocument()) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
// Dropping an object on the document that is not part of a composed object is non-sense
|
|
// Dropping an object on document means to remove the object of a composed object
|
|
QTreeWidgetItem* parent = item->parent();
|
|
if (!parent || parent->type() != TreeWidget::ObjectType) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Link is non-sense on a document.
|
|
if (da == Qt::LinkAction) {
|
|
if (event->possibleActions() & Qt::MoveAction) {
|
|
event->setDropAction(Qt::MoveAction);
|
|
event->accept();
|
|
}
|
|
else {
|
|
event->ignore();
|
|
}
|
|
}
|
|
}
|
|
else if (targetItem->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
|
|
Gui::ViewProviderDocumentObject* vpTarget = targetItemObj->object();
|
|
QList<App::DocumentObject *> children = buildListChildren(targetItem, vpTarget);
|
|
App::DocumentObject* targetObj = vpTarget->getObject();
|
|
App::Document* docTarget = targetObj->getDocument();
|
|
|
|
QList<QModelIndex> idxsSelected = selectedIndexes();
|
|
|
|
std::vector<const App::DocumentObject*> dropObjects;
|
|
dropObjects.reserve(idxsSelected.size());
|
|
|
|
for (QList<QModelIndex>::Iterator it = idxsSelected.begin(); it != idxsSelected.end(); ++it) {
|
|
QTreeWidgetItem* item = itemFromIndex(*it);
|
|
if (item->type() != TreeWidget::ObjectType) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
Gui::ViewProviderDocumentObject* vpDropped = static_cast<DocumentObjectItem*>(item)->object();
|
|
App::DocumentObject* objDropped = vpDropped->getObject();
|
|
if (docTarget != objDropped->getDocument()) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
dropObjects.push_back(objDropped);
|
|
|
|
// To avoid a cylic dependency it must be made sure to not allow to
|
|
// drag'n'drop a tree item onto a child or grandchild item of it.
|
|
bool canDrop = !targetObj->isInInListRecursive(objDropped);
|
|
|
|
// if the item is already a child of the target item there is nothing to do
|
|
canDrop = canDrop && vpTarget->canDropObjects() && !children.contains(objDropped);
|
|
|
|
if (!canDrop) {
|
|
if (idxsSelected.size() == 1) {
|
|
if (parentTarget) {
|
|
if (parentTarget->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* parentTargetItemObj =
|
|
static_cast<DocumentObjectItem*>(parentTarget);
|
|
Gui::ViewProviderDocumentObject* vpParent = parentTargetItemObj->object();
|
|
App::DocumentObject* objParentTarget = vpParent->getObject();
|
|
|
|
if (!buildListChildren(parentTarget, vpParent).contains(objDropped) &&
|
|
!objParentTarget->isInInListRecursive(objDropped)) {
|
|
if (da == Qt::CopyAction || da == Qt::MoveAction) {
|
|
event->setDropAction(Qt::LinkAction);
|
|
event->accept();
|
|
return;
|
|
}
|
|
else if (da == Qt::LinkAction) {
|
|
event->acceptProposedAction();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
event->ignore();
|
|
return;
|
|
}
|
|
else {
|
|
// let the view provider decide to accept the object or ignore it
|
|
if (!vpTarget->canDropObject(objDropped)) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
else if (da == Qt::LinkAction) {
|
|
if (!parentTarget || parentTarget->type() != TreeWidget::ObjectType) {
|
|
// no need to test compatibility between parent and objDropped and objParentTarget
|
|
// as test between objDropped and targetObj has been tested before
|
|
event->ignore();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
event->acceptProposedAction();
|
|
}
|
|
else {
|
|
event->ignore();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeWidget::dropEvent(QDropEvent *event)
|
|
{
|
|
//FIXME: This should actually be done inside dropMimeData
|
|
|
|
QTreeWidgetItem* targetItem = itemAt(event->pos());
|
|
// not dropped onto an item
|
|
if (!targetItem)
|
|
return;
|
|
// one of the source items is also the destination item, that's not allowed
|
|
if (this->isItemSelected(targetItem))
|
|
return;
|
|
|
|
// filter out the selected items we cannot handle
|
|
QList<QTreeWidgetItem*> items;
|
|
QList<QModelIndex> idxs = selectedIndexes();
|
|
for (QList<QModelIndex>::Iterator it = idxs.begin(); it != idxs.end(); ++it) {
|
|
// ignore child elements if the parent is selected (issue #0001456)
|
|
QModelIndex parent = (*it).parent();
|
|
if (idxs.contains(parent))
|
|
continue;
|
|
QTreeWidgetItem* item = itemFromIndex(*it);
|
|
if (item == targetItem)
|
|
continue;
|
|
if (item->parent() == targetItem)
|
|
continue;
|
|
items.push_back(item);
|
|
}
|
|
|
|
if (items.isEmpty())
|
|
return; // nothing needs to be done
|
|
|
|
if (targetItem->type() == TreeWidget::ObjectType) {
|
|
// add object to group
|
|
DocumentObjectItem* targetItemObj = static_cast<DocumentObjectItem*>(targetItem);
|
|
|
|
Qt::DropAction da = event->dropAction();
|
|
|
|
Gui::ViewProviderDocumentObject* vpTarget = targetItemObj->object();
|
|
Gui::Document* gui = vpTarget->getDocument();
|
|
|
|
if (da == Qt::LinkAction) {
|
|
// Open command
|
|
gui->openCommand("Drop object");
|
|
for (QList<QTreeWidgetItem*>::Iterator it = items.begin(); it != items.end(); ++it) {
|
|
Gui::ViewProviderDocumentObject* vpDropped = static_cast<DocumentObjectItem*>(*it)->object();
|
|
App::DocumentObject* objDropped = vpDropped->getObject();
|
|
|
|
// does this have a parent object
|
|
QTreeWidgetItem* parent = targetItemObj->parent();
|
|
|
|
if (parent && parent->type() == TreeWidget::ObjectType) {
|
|
Gui::ViewProvider* vpParent = static_cast<DocumentObjectItem *>(parent)->object();
|
|
// now add the object to the target object
|
|
vpParent->replaceObject(vpTarget->getObject(), objDropped);
|
|
}
|
|
|
|
}
|
|
gui->commitCommand();
|
|
}
|
|
else {
|
|
if (!vpTarget->canDropObjects()) {
|
|
return; // no group like object
|
|
}
|
|
|
|
bool dropOnly = da == Qt::CopyAction;
|
|
|
|
// Open command
|
|
gui->openCommand("Drag object");
|
|
for (QList<QTreeWidgetItem*>::Iterator it = items.begin(); it != items.end(); ++it) {
|
|
Gui::ViewProviderDocumentObject* vpDropped = static_cast<DocumentObjectItem*>(*it)->object();
|
|
App::DocumentObject* objDropped = vpDropped->getObject();
|
|
|
|
if (!dropOnly) {
|
|
// does this have a parent object
|
|
QTreeWidgetItem* parent = (*it)->parent();
|
|
if (parent && parent->type() == TreeWidget::ObjectType) {
|
|
Gui::ViewProvider* vpParent = static_cast<DocumentObjectItem *>(parent)->object();
|
|
vpParent->dragObject(objDropped);
|
|
}
|
|
}
|
|
|
|
// now add the object to the target object
|
|
vpTarget->dropObject(objDropped);
|
|
}
|
|
gui->commitCommand();
|
|
}
|
|
}
|
|
else if (targetItem->type() == TreeWidget::DocumentType) {
|
|
// Open command
|
|
bool commit = false;
|
|
App::Document* doc = static_cast<DocumentItem*>(targetItem)->document()->getDocument();
|
|
Gui::Document* gui = Gui::Application::Instance->getDocument(doc);
|
|
gui->openCommand("Move object");
|
|
for (QList<QTreeWidgetItem*>::Iterator it = items.begin(); it != items.end(); ++it) {
|
|
Gui::ViewProviderDocumentObject* vpDropped = static_cast<DocumentObjectItem*>(*it)->object();
|
|
App::DocumentObject* objDropped = vpDropped->getObject();
|
|
|
|
// does this have a parent object
|
|
QTreeWidgetItem* parent = (*it)->parent();
|
|
if (parent && parent->type() == TreeWidget::ObjectType) {
|
|
Gui::ViewProvider* vpParent = static_cast<DocumentObjectItem *>(parent)->object();
|
|
if (vpParent->canDragObject(objDropped)) {
|
|
vpParent->dragObject(objDropped);
|
|
commit = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (commit)
|
|
gui->commitCommand();
|
|
else
|
|
gui->abortCommand();
|
|
}
|
|
}
|
|
|
|
void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const
|
|
{
|
|
QTreeWidget::drawRow(painter, options, index);
|
|
// Set the text and highlighted text color of a hidden object to a dark
|
|
//QTreeWidgetItem * item = itemFromIndex(index);
|
|
//if (item->type() == ObjectType && !(static_cast<DocumentObjectItem*>(item)->previousStatus & 1)) {
|
|
// QStyleOptionViewItem opt(options);
|
|
// opt.state ^= QStyle::State_Enabled;
|
|
// QColor c = opt.palette.color(QPalette::Inactive, QPalette::Dark);
|
|
// opt.palette.setColor(QPalette::Inactive, QPalette::Text, c);
|
|
// opt.palette.setColor(QPalette::Inactive, QPalette::HighlightedText, c);
|
|
// QTreeWidget::drawRow(painter, opt, index);
|
|
//}
|
|
//else {
|
|
// QTreeWidget::drawRow(painter, options, index);
|
|
//}
|
|
}
|
|
|
|
void TreeWidget::slotNewDocument(const Gui::Document& Doc)
|
|
{
|
|
DocumentItem* item = new DocumentItem(&Doc, this->rootItem);
|
|
this->expandItem(item);
|
|
item->setIcon(0, *documentPixmap);
|
|
item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
|
|
DocumentMap[ &Doc ] = item;
|
|
}
|
|
|
|
void TreeWidget::slotDeleteDocument(const Gui::Document& Doc)
|
|
{
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it = DocumentMap.find(&Doc);
|
|
if (it != DocumentMap.end()) {
|
|
this->rootItem->takeChild(this->rootItem->indexOfChild(it->second));
|
|
delete it->second;
|
|
DocumentMap.erase(it);
|
|
}
|
|
}
|
|
|
|
void TreeWidget::slotRenameDocument(const Gui::Document& Doc)
|
|
{
|
|
// do nothing here
|
|
Q_UNUSED(Doc);
|
|
}
|
|
|
|
void TreeWidget::slotRelabelDocument(const Gui::Document& Doc)
|
|
{
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it = DocumentMap.find(&Doc);
|
|
if (it != DocumentMap.end()) {
|
|
it->second->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue()));
|
|
}
|
|
}
|
|
|
|
void TreeWidget::slotActiveDocument(const Gui::Document& Doc)
|
|
{
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator jt = DocumentMap.find(&Doc);
|
|
if (jt == DocumentMap.end())
|
|
return; // signal is emitted before the item gets created
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
|
|
int displayMode = hGrp->GetInt("TreeViewDocument", 0);
|
|
for (std::map<const Gui::Document*, DocumentItem*>::iterator it = DocumentMap.begin();
|
|
it != DocumentMap.end(); ++it)
|
|
{
|
|
QFont f = it->second->font(0);
|
|
f.setBold(it == jt);
|
|
it->second->setHidden(0 == displayMode && it != jt);
|
|
if (2 == displayMode) {
|
|
it->second->setExpanded(it == jt);
|
|
}
|
|
// this must be done as last step
|
|
it->second->setFont(0, f);
|
|
}
|
|
}
|
|
|
|
|
|
void TreeWidget::onTestStatus(void)
|
|
{
|
|
if (isVisible()) {
|
|
std::map<const Gui::Document*,DocumentItem*>::iterator pos;
|
|
for (pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) {
|
|
pos->second->testStatus();
|
|
}
|
|
}
|
|
|
|
this->statusTimer->setSingleShot(true);
|
|
this->statusTimer->start(300);
|
|
}
|
|
|
|
void TreeWidget::onItemEntered(QTreeWidgetItem * item)
|
|
{
|
|
// object item selected
|
|
if (item && item->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(item);
|
|
obj->displayStatusInfo();
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onItemCollapsed(QTreeWidgetItem * item)
|
|
{
|
|
// object item collapsed
|
|
if (item && item->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(item);
|
|
obj->setExpandedStatus(false);
|
|
}
|
|
}
|
|
|
|
void TreeWidget::onItemExpanded(QTreeWidgetItem * item)
|
|
{
|
|
// object item expanded
|
|
if (item && item->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(item);
|
|
obj->setExpandedStatus(true);
|
|
auto it = DocumentMap.find(obj->object()->getDocument());
|
|
if(it==DocumentMap.end())
|
|
Base::Console().Warning("DocumentItem::onItemExpanded: cannot find object document\n");
|
|
else
|
|
it->second->populateItem(obj);
|
|
}
|
|
}
|
|
|
|
void TreeWidget::scrollItemToTop(Gui::Document* doc)
|
|
{
|
|
std::map<const Gui::Document*,DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(doc);
|
|
if (it != DocumentMap.end()) {
|
|
DocumentItem* root = it->second;
|
|
QTreeWidgetItemIterator it(root, QTreeWidgetItemIterator::Selected);
|
|
for (; *it; ++it) {
|
|
if ((*it)->type() == TreeWidget::ObjectType) {
|
|
this->scrollToItem(*it, QAbstractItemView::PositionAtTop);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeWidget::changeEvent(QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
this->headerItem()->setText(0, tr("Labels & Attributes"));
|
|
this->rootItem->setText(0, tr("Application"));
|
|
|
|
this->createGroupAction->setText(tr("Create group..."));
|
|
this->createGroupAction->setStatusTip(tr("Create a group"));
|
|
|
|
this->relabelObjectAction->setText(tr("Rename"));
|
|
this->relabelObjectAction->setStatusTip(tr("Rename object"));
|
|
|
|
this->finishEditingAction->setText(tr("Finish editing"));
|
|
this->finishEditingAction->setStatusTip(tr("Finish editing object"));
|
|
|
|
this->skipRecomputeAction->setText(tr("Skip recomputes"));
|
|
this->skipRecomputeAction->setStatusTip(tr("Enable or disable recomputations of document"));
|
|
|
|
this->markRecomputeAction->setText(tr("Mark to recompute"));
|
|
this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed"));
|
|
}
|
|
|
|
QTreeWidget::changeEvent(e);
|
|
}
|
|
|
|
void TreeWidget::onItemSelectionChanged ()
|
|
{
|
|
// we already got notified by the selection to update the tree items
|
|
if (this->isConnectionBlocked())
|
|
return;
|
|
|
|
// block tmp. the connection to avoid to notify us ourself
|
|
bool lock = this->blockConnection(true);
|
|
std::map<const Gui::Document*,DocumentItem*>::iterator pos;
|
|
for (pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) {
|
|
pos->second->updateSelection();
|
|
}
|
|
this->blockConnection(lock);
|
|
}
|
|
|
|
void TreeWidget::onSelectionChanged(const SelectionChanges& msg)
|
|
{
|
|
switch (msg.Type)
|
|
{
|
|
case SelectionChanges::AddSelection:
|
|
{
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
bool lock = this->blockConnection(true);
|
|
if (it!= DocumentMap.end())
|
|
it->second->setObjectSelected(msg.pObjectName,true);
|
|
this->blockConnection(lock);
|
|
} break;
|
|
case SelectionChanges::RmvSelection:
|
|
{
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
bool lock = this->blockConnection(true);
|
|
if (it!= DocumentMap.end())
|
|
it->second->setObjectSelected(msg.pObjectName,false);
|
|
this->blockConnection(lock);
|
|
} break;
|
|
case SelectionChanges::SetSelection:
|
|
{
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
// we get notified from the selection and must only update the selection on the tree,
|
|
// thus no need to notify again the selection. See also onItemSelectionChanged().
|
|
if (it != DocumentMap.end()) {
|
|
bool lock = this->blockConnection(true);
|
|
it->second->selectItems();
|
|
this->blockConnection(lock);
|
|
}
|
|
} break;
|
|
case SelectionChanges::ClrSelection:
|
|
{
|
|
// clears the complete selection
|
|
if (strcmp(msg.pDocName,"") == 0) {
|
|
this->clearSelection ();
|
|
}
|
|
else {
|
|
// clears the selection of the given document
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
if (it != DocumentMap.end()) {
|
|
it->second->clearSelection();
|
|
}
|
|
}
|
|
this->update();
|
|
} break;
|
|
case SelectionChanges::SetPreselect:
|
|
{
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
if (it!= DocumentMap.end())
|
|
it->second->setObjectHighlighted(msg.pObjectName,true);
|
|
} break;
|
|
case SelectionChanges::RmvPreselect:
|
|
{
|
|
Gui::Document* pDoc = Application::Instance->getDocument(msg.pDocName);
|
|
std::map<const Gui::Document*, DocumentItem*>::iterator it;
|
|
it = DocumentMap.find(pDoc);
|
|
if (it!= DocumentMap.end())
|
|
it->second->setObjectHighlighted(msg.pObjectName,false);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TreeWidget::setItemsSelected (const QList<QTreeWidgetItem *> items, bool select)
|
|
{
|
|
if (items.isEmpty())
|
|
return;
|
|
QItemSelection range;
|
|
for (QList<QTreeWidgetItem*>::const_iterator it = items.begin(); it != items.end(); ++it)
|
|
range.select(this->indexFromItem(*it),this->indexFromItem(*it));
|
|
selectionModel()->select(range, select ?
|
|
QItemSelectionModel::Select :
|
|
QItemSelectionModel::Deselect);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::TreePanel */
|
|
|
|
TreePanel::TreePanel(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
this->treeWidget = new TreeWidget(this);
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
|
|
this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation()));
|
|
|
|
QVBoxLayout* pLayout = new QVBoxLayout(this);
|
|
pLayout->setSpacing(0);
|
|
pLayout->setMargin (0);
|
|
pLayout->addWidget(this->treeWidget);
|
|
connect(this->treeWidget, SIGNAL(emitSearchObjects()),
|
|
this, SLOT(showEditor()));
|
|
|
|
this->searchBox = new Gui::ClearLineEdit(this);
|
|
pLayout->addWidget(this->searchBox);
|
|
this->searchBox->hide();
|
|
this->searchBox->installEventFilter(this);
|
|
#if QT_VERSION >= 0x040700
|
|
this->searchBox->setPlaceholderText(tr("Search"));
|
|
#endif
|
|
connect(this->searchBox, SIGNAL(returnPressed()),
|
|
this, SLOT(accept()));
|
|
connect(this->searchBox, SIGNAL(textEdited(QString)),
|
|
this, SLOT(findMatchingItems(QString)));
|
|
}
|
|
|
|
TreePanel::~TreePanel()
|
|
{
|
|
}
|
|
|
|
void TreePanel::accept()
|
|
{
|
|
QString text = this->searchBox->text();
|
|
if (!text.isEmpty()) {
|
|
for (int i=0; i<treeWidget->topLevelItemCount(); i++) {
|
|
selectTreeItem(treeWidget->topLevelItem(i), text);
|
|
}
|
|
}
|
|
hideEditor();
|
|
}
|
|
|
|
bool TreePanel::eventFilter(QObject *obj, QEvent *ev)
|
|
{
|
|
if (obj != this->searchBox)
|
|
return false;
|
|
|
|
if (ev->type() == QEvent::KeyPress) {
|
|
bool consumed = false;
|
|
int key = static_cast<QKeyEvent*>(ev)->key();
|
|
switch (key) {
|
|
case Qt::Key_Escape:
|
|
hideEditor();
|
|
consumed = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TreePanel::showEditor()
|
|
{
|
|
this->searchBox->show();
|
|
this->searchBox->setFocus();
|
|
}
|
|
|
|
void TreePanel::hideEditor()
|
|
{
|
|
this->searchBox->clear();
|
|
this->searchBox->hide();
|
|
for (int i=0; i<treeWidget->topLevelItemCount(); i++) {
|
|
resetBackground(treeWidget->topLevelItem(i));
|
|
}
|
|
}
|
|
|
|
void TreePanel::findMatchingItems(const QString& text)
|
|
{
|
|
if (text.isEmpty()) {
|
|
for (int i=0; i<treeWidget->topLevelItemCount(); i++) {
|
|
resetBackground(treeWidget->topLevelItem(i));
|
|
}
|
|
}
|
|
else {
|
|
for (int i=0; i<treeWidget->topLevelItemCount(); i++) {
|
|
searchTreeItem(treeWidget->topLevelItem(i), text);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreePanel::searchTreeItem(QTreeWidgetItem* item, const QString& text)
|
|
{
|
|
for (int i=0; i<item->childCount(); i++) {
|
|
QTreeWidgetItem* child = item->child(i);
|
|
child->setBackground(0, QBrush());
|
|
child->setExpanded(false);
|
|
if (child->text(0).indexOf(text, 0, Qt::CaseInsensitive) >= 0) {
|
|
child->setBackground(0, QColor(255, 255, 0, 100));
|
|
QTreeWidgetItem* parent = child->parent();
|
|
while (parent) {
|
|
parent->setExpanded(true);
|
|
parent = parent->parent();
|
|
}
|
|
}
|
|
searchTreeItem(child, text);
|
|
}
|
|
}
|
|
|
|
void TreePanel::selectTreeItem(QTreeWidgetItem* item, const QString& text)
|
|
{
|
|
for (int i=0; i<item->childCount(); i++) {
|
|
QTreeWidgetItem* child = item->child(i);
|
|
if (child->text(0).indexOf(text, 0, Qt::CaseInsensitive) >= 0) {
|
|
child->setSelected(true);
|
|
}
|
|
selectTreeItem(child, text);
|
|
}
|
|
}
|
|
|
|
void TreePanel::resetBackground(QTreeWidgetItem* item)
|
|
{
|
|
for (int i=0; i<item->childCount(); i++) {
|
|
QTreeWidgetItem* child = item->child(i);
|
|
child->setBackground(0, QBrush());
|
|
resetBackground(child);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::TreeDockWidget */
|
|
|
|
TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent)
|
|
: DockWindow(pcDocument,parent)
|
|
{
|
|
setWindowTitle(tr("Tree view"));
|
|
this->treeWidget = new TreeWidget(this);
|
|
this->treeWidget->setRootIsDecorated(false);
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
|
|
this->treeWidget->setIndentation(hGrp->GetInt("Indentation", this->treeWidget->indentation()));
|
|
|
|
QGridLayout* pLayout = new QGridLayout(this);
|
|
pLayout->setSpacing(0);
|
|
pLayout->setMargin (0);
|
|
pLayout->addWidget(this->treeWidget, 0, 0 );
|
|
}
|
|
|
|
TreeDockWidget::~TreeDockWidget()
|
|
{
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent)
|
|
: QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(doc)
|
|
{
|
|
// Setup connections
|
|
connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1));
|
|
connectDelObject = doc->signalDeletedObject.connect(boost::bind(&DocumentItem::slotDeleteObject, this, _1));
|
|
connectChgObject = doc->signalChangedObject.connect(boost::bind(&DocumentItem::slotChangeObject, this, _1));
|
|
connectRenObject = doc->signalRelabelObject.connect(boost::bind(&DocumentItem::slotRenameObject, this, _1));
|
|
connectActObject = doc->signalActivatedObject.connect(boost::bind(&DocumentItem::slotActiveObject, this, _1));
|
|
connectEdtObject = doc->signalInEdit.connect(boost::bind(&DocumentItem::slotInEdit, this, _1));
|
|
connectResObject = doc->signalResetEdit.connect(boost::bind(&DocumentItem::slotResetEdit, this, _1));
|
|
connectHltObject = doc->signalHighlightObject.connect(boost::bind(&DocumentItem::slotHighlightObject, this, _1,_2,_3));
|
|
connectExpObject = doc->signalExpandObject.connect(boost::bind(&DocumentItem::slotExpandObject, this, _1,_2));
|
|
connectScrObject = doc->signalScrollToObject.connect(boost::bind(&DocumentItem::slotScrollToObject, this, _1));
|
|
|
|
setFlags(Qt::ItemIsEnabled/*|Qt::ItemIsEditable*/);
|
|
}
|
|
|
|
DocumentItem::~DocumentItem()
|
|
{
|
|
connectNewObject.disconnect();
|
|
connectDelObject.disconnect();
|
|
connectChgObject.disconnect();
|
|
connectRenObject.disconnect();
|
|
connectActObject.disconnect();
|
|
connectEdtObject.disconnect();
|
|
connectResObject.disconnect();
|
|
connectHltObject.disconnect();
|
|
connectExpObject.disconnect();
|
|
connectScrObject.disconnect();
|
|
}
|
|
|
|
#define FOREACH_ITEM(_item, _obj) \
|
|
auto _it = ObjectMap.find(std::string(_obj.getObject()->getNameInDocument()));\
|
|
if(_it == ObjectMap.end() || _it->second->empty()) return;\
|
|
for(auto _item : *_it->second){{
|
|
|
|
#define FOREACH_ITEM_ALL(_item) \
|
|
for(auto _v : ObjectMap) {\
|
|
for(auto _item : *_v.second) {
|
|
|
|
#define FOREACH_ITEM_NAME(_item,_name) \
|
|
auto _it = ObjectMap.find(_name);\
|
|
if(_it != ObjectMap.end()) {\
|
|
for(auto _item : *_it->second) {
|
|
|
|
#define END_FOREACH_ITEM }}
|
|
|
|
|
|
void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
|
|
{
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
|
|
unsigned long col = hGrp->GetUnsigned("TreeEditColor",4294902015);
|
|
FOREACH_ITEM(item,v)
|
|
item->setBackgroundColor(0,QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff));
|
|
END_FOREACH_ITEM
|
|
}
|
|
|
|
void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v)
|
|
{
|
|
FOREACH_ITEM(item,v)
|
|
item->setData(0, Qt::BackgroundColorRole,QVariant());
|
|
END_FOREACH_ITEM
|
|
}
|
|
|
|
void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) {
|
|
createNewItem(obj);
|
|
}
|
|
|
|
bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj,
|
|
QTreeWidgetItem *parent, int index, DocumentObjectItemsPtr ptrs)
|
|
{
|
|
const char *name;
|
|
if (!obj.showInTree() || !(name=obj.getObject()->getNameInDocument()))
|
|
return false;
|
|
|
|
if (!ptrs) {
|
|
auto &items = ObjectMap[name];
|
|
if (!items) {
|
|
items.reset(new DocumentObjectItems);
|
|
}
|
|
else if(items->size() && parent==NULL) {
|
|
Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n");
|
|
return false;
|
|
}
|
|
ptrs = items;
|
|
}
|
|
|
|
std::string displayName = obj.getObject()->Label.getValue();
|
|
DocumentObjectItem* item = new DocumentObjectItem(
|
|
const_cast<Gui::ViewProviderDocumentObject*>(&obj), ptrs);
|
|
|
|
if (!parent)
|
|
parent = this;
|
|
if (index<0)
|
|
parent->addChild(item);
|
|
else
|
|
parent->insertChild(index,item);
|
|
|
|
// Couldn't be added and thus don't continue populating it
|
|
// and delete it again
|
|
if (!item->parent()) {
|
|
delete item;
|
|
}
|
|
else {
|
|
item->setIcon(0, obj.getIcon());
|
|
item->setText(0, QString::fromUtf8(displayName.c_str()));
|
|
populateItem(item);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool canCreateItem(const App::DocumentObject *obj, const Document *doc)
|
|
{
|
|
// Note: It is possible that we receive an invalid pointer from
|
|
// claimChildren(), e.g. if multiple properties were changed in
|
|
// a transaction and slotChangedObject() is triggered by one
|
|
// property being reset before the invalid pointer has been
|
|
// removed from another. Currently this happens for
|
|
// PartDesign::Body when cancelling a new feature in the dialog.
|
|
// First the new feature is deleted, then the Tip property is
|
|
// reset, but claimChildren() accesses the Model property which
|
|
// still contains the pointer to the deleted feature
|
|
return obj && obj->getNameInDocument() && doc->getDocument()->isIn(obj);
|
|
}
|
|
|
|
void DocumentItem::slotDeleteObject(const Gui::ViewProviderDocumentObject& view)
|
|
{
|
|
auto it = ObjectMap.find(std::string(view.getObject()->getNameInDocument()));
|
|
if (it == ObjectMap.end() || it->second->empty())
|
|
return;
|
|
|
|
auto &items = *(it->second);
|
|
for (auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) {
|
|
++citNext;
|
|
delete *cit;
|
|
}
|
|
|
|
if (items.empty())
|
|
ObjectMap.erase(it);
|
|
|
|
// Check for any child of the deleted object is not in the tree, and put it
|
|
// under document item.
|
|
const auto &children = view.claimChildren();
|
|
for (auto child : children) {
|
|
if (!canCreateItem(child,pDocument))
|
|
continue;
|
|
auto it = ObjectMap.find(child->getNameInDocument());
|
|
if (it==ObjectMap.end() || it->second->empty()) {
|
|
ViewProvider* vp = pDocument->getViewProvider(child);
|
|
if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
|
|
continue;
|
|
createNewItem(static_cast<ViewProviderDocumentObject&>(*vp));
|
|
}
|
|
}
|
|
}
|
|
|
|
void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh)
|
|
{
|
|
if (item->populated && !refresh)
|
|
return;
|
|
|
|
// Lazy loading policy: We will create an item for each children object if
|
|
// a) the item is expanded, or b) there is at least one free child, i.e.
|
|
// child originally located at root.
|
|
|
|
const auto &children = item->object()->claimChildren();
|
|
|
|
item->setChildIndicatorPolicy(children.empty()?
|
|
QTreeWidgetItem::DontShowIndicator:QTreeWidgetItem::ShowIndicator);
|
|
|
|
if (!item->populated && !item->isExpanded()) {
|
|
bool doPopulate = false;
|
|
for (auto child : children) {
|
|
if (!canCreateItem(child,pDocument))
|
|
continue;
|
|
auto it = ObjectMap.find(child->getNameInDocument());
|
|
if (it == ObjectMap.end() || it->second->empty()) {
|
|
ViewProvider* vp = pDocument->getViewProvider(child);
|
|
if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
|
|
continue;
|
|
doPopulate = true;
|
|
break;
|
|
}
|
|
|
|
if ((*it->second->begin())->parent() == this) {
|
|
doPopulate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!doPopulate)
|
|
return;
|
|
}
|
|
|
|
item->populated = true;
|
|
|
|
int i=-1;
|
|
// iterate through the claimed children, and try to synchronize them with the
|
|
// children tree item with the same order of appearance.
|
|
for (auto child : children) {
|
|
if (!canCreateItem(child,pDocument))
|
|
continue;
|
|
|
|
++i; // the current index of the claimed child
|
|
|
|
bool found = false;
|
|
for (int j=0,count=item->childCount();j<count;++j) {
|
|
QTreeWidgetItem *ci = item->child(j);
|
|
if (ci->type() != TreeWidget::ObjectType)
|
|
continue;
|
|
|
|
DocumentObjectItem *childItem = static_cast<DocumentObjectItem*>(ci);
|
|
if (childItem->object()->getObject() != child)
|
|
continue;
|
|
|
|
found = true;
|
|
if (j!=i) { // fix index if it is changed
|
|
item->removeChild(ci);
|
|
item->insertChild(i,ci);
|
|
if (!ci->parent()) {
|
|
delete ci;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
|
|
// This algo will be recursively applied to newly created child items
|
|
// through slotNewObject -> populateItem
|
|
|
|
auto it = ObjectMap.find(child->getNameInDocument());
|
|
if (it==ObjectMap.end() || it->second->empty()) {
|
|
ViewProvider* vp = pDocument->getViewProvider(child);
|
|
if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) ||
|
|
!createNewItem(static_cast<ViewProviderDocumentObject&>(*vp),item,i,
|
|
it==ObjectMap.end()?DocumentObjectItemsPtr():it->second))
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
DocumentObjectItem *childItem = *it->second->begin();
|
|
if (childItem->parent() != this) {
|
|
if(!createNewItem(*childItem->object(),item,i,it->second))
|
|
--i;
|
|
}
|
|
else {
|
|
if (item==childItem || item->isChildOfItem(childItem)) {
|
|
Base::Console().Error("Gui::DocumentItem::populateItem(): Cyclic dependency in %s and %s\n",
|
|
item->object()->getObject()->Label.getValue(),
|
|
childItem->object()->getObject()->Label.getValue());
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
this->removeChild(childItem);
|
|
item->insertChild(i,childItem);
|
|
if (!childItem->parent()) {
|
|
delete childItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
App::GeoFeatureGroupExtension* grp = nullptr;
|
|
if (item->object()->getObject()->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId()))
|
|
grp = item->object()->getObject()->getExtensionByType<App::GeoFeatureGroupExtension>();
|
|
|
|
// When removing a child element then it must either be moved to a new
|
|
// parent or deleted. Just removing and leaving breaks the Qt internal
|
|
// notification (See #0003201).
|
|
for (++i;item->childCount()>i;) {
|
|
QTreeWidgetItem *childItem = item->child(i);
|
|
item->removeChild(childItem);
|
|
if (childItem->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(childItem);
|
|
// Add the child item back to document root if it is the only
|
|
// instance. Now, because of the lazy loading strategy, this may
|
|
// not truly be the last instance of the object. It may belong to
|
|
// other parents not expanded yet. We don't want to traverse the
|
|
// whole tree to confirm that. Just let it be. If the other
|
|
// parent(s) later expanded, this child item will be moved from
|
|
// root to its parent.
|
|
if (obj->myselves->size()==1) {
|
|
// We only make a difference for geofeaturegroups,
|
|
// as otherwise it comes to confusing behavior to the user when things
|
|
// get claimed within the group (e.g. pad/sketch, or group)
|
|
if (!grp || !grp->hasObject(obj->object()->getObject(), true)) {
|
|
this->addChild(childItem);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete childItem;
|
|
}
|
|
}
|
|
|
|
void DocumentItem::slotChangeObject(const Gui::ViewProviderDocumentObject& view)
|
|
{
|
|
QString displayName = QString::fromUtf8(view.getObject()->Label.getValue());
|
|
FOREACH_ITEM(item,view)
|
|
item->setText(0, displayName);
|
|
populateItem(item, true);
|
|
END_FOREACH_ITEM
|
|
|
|
//if the item is in a GeoFeatureGroup we may need to update that too, as the claim children
|
|
//of the geofeaturegroup depends on what the childs claim
|
|
auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(view.getObject());
|
|
if (grp) {
|
|
FOREACH_ITEM_NAME(item, grp->getNameInDocument())
|
|
populateItem(item, true);
|
|
END_FOREACH_ITEM
|
|
}
|
|
}
|
|
|
|
void DocumentItem::slotRenameObject(const Gui::ViewProviderDocumentObject& obj)
|
|
{
|
|
// Do nothing here because the Label is set in slotChangeObject
|
|
Q_UNUSED(obj);
|
|
}
|
|
|
|
void DocumentItem::slotActiveObject(const Gui::ViewProviderDocumentObject& obj)
|
|
{
|
|
#if 0
|
|
std::string objectName = obj.getObject()->getNameInDocument();
|
|
if (ObjectMap.find(objectName) == ObjectMap.end())
|
|
return; // signal is emitted before the item gets created
|
|
|
|
for (auto v : ObjectMap) {
|
|
for (auto item : *v.second) {
|
|
QFont f = item->font(0);
|
|
f.setBold(item->object() == &obj);
|
|
item->setFont(0,f);
|
|
}
|
|
}
|
|
#else
|
|
Q_UNUSED(obj);
|
|
#endif
|
|
}
|
|
|
|
void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj, const Gui::HighlightMode& high, bool set)
|
|
{
|
|
FOREACH_ITEM(item,obj)
|
|
QFont f = item->font(0);
|
|
auto highlight = [&item, &set](const QColor& col){
|
|
if (set)
|
|
item->setBackgroundColor(0, col);
|
|
else
|
|
item->setData(0, Qt::BackgroundColorRole,QVariant());
|
|
};
|
|
|
|
switch (high) {
|
|
case Gui::Bold:
|
|
f.setBold(set);
|
|
break;
|
|
case Gui::Italic:
|
|
f.setItalic(set);
|
|
break;
|
|
case Gui::Underlined:
|
|
f.setUnderline(set);
|
|
break;
|
|
case Gui::Overlined:
|
|
f.setOverline(set);
|
|
break;
|
|
case Gui::Blue:
|
|
highlight(QColor(200,200,255));
|
|
break;
|
|
case Gui::LightBlue:
|
|
highlight(QColor(230,230,255));
|
|
break;
|
|
case Gui::UserDefined:
|
|
{
|
|
QColor color(230,230,255);
|
|
if (set) {
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView");
|
|
bool bold = hGrp->GetBool("TreeActiveBold",true);
|
|
bool italic = hGrp->GetBool("TreeActiveItalic",false);
|
|
bool underlined = hGrp->GetBool("TreeActiveUnderlined",false);
|
|
bool overlined = hGrp->GetBool("TreeActiveOverlined",false);
|
|
f.setBold(bold);
|
|
f.setItalic(italic);
|
|
f.setUnderline(underlined);
|
|
f.setOverline(overlined);
|
|
|
|
unsigned long col = hGrp->GetUnsigned("TreeActiveColor",3873898495);
|
|
color = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff);
|
|
}
|
|
else {
|
|
f.setBold(false);
|
|
f.setItalic(false);
|
|
f.setUnderline(false);
|
|
f.setOverline(false);
|
|
}
|
|
highlight(color);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
item->setFont(0,f);
|
|
END_FOREACH_ITEM
|
|
}
|
|
|
|
void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj,const Gui::TreeItemMode& mode)
|
|
{
|
|
// In the past it was checked if the parent item is collapsed and if yes nothing was done.
|
|
// Now with the auto-expand mechanism of active part containers or bodies it must be made
|
|
// sure to expand all parent items when expanding a child item.
|
|
// Example:
|
|
// When there are two nested part containers and if first the outer and then the inner is
|
|
// activated the outer will be collapsed and thus hides the inner item.
|
|
// Alternatively, this could be handled inside ActiveObjectList::setObject() but querying
|
|
// the parent-children relationship of the view providers is rather inefficient.
|
|
FOREACH_ITEM(item,obj)
|
|
switch (mode) {
|
|
case Gui::ExpandPath: {
|
|
QTreeWidgetItem* parent = item->parent();
|
|
while (parent) {
|
|
parent->setExpanded(true);
|
|
parent = parent->parent();
|
|
}
|
|
item->setExpanded(true);
|
|
} break;
|
|
case Gui::ExpandItem:
|
|
item->setExpanded(true);
|
|
break;
|
|
case Gui::CollapseItem:
|
|
item->setExpanded(false);
|
|
break;
|
|
case Gui::ToggleItem:
|
|
if (item->isExpanded())
|
|
item->setExpanded(false);
|
|
else
|
|
item->setExpanded(true);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
populateItem(item);
|
|
END_FOREACH_ITEM
|
|
}
|
|
|
|
void DocumentItem::slotScrollToObject(const Gui::ViewProviderDocumentObject& obj)
|
|
{
|
|
FOREACH_ITEM(item,obj)
|
|
QTreeWidget* tree = item->treeWidget();
|
|
tree->scrollToItem(item, QAbstractItemView::PositionAtTop);
|
|
END_FOREACH_ITEM
|
|
}
|
|
|
|
const Gui::Document* DocumentItem::document() const
|
|
{
|
|
return this->pDocument;
|
|
}
|
|
|
|
//void DocumentItem::markItem(const App::DocumentObject* Obj,bool mark)
|
|
//{
|
|
// // never call without Object!
|
|
// assert(Obj);
|
|
//
|
|
//
|
|
// std::map<std::string,DocumentObjectItem*>::iterator pos;
|
|
// pos = ObjectMap.find(Obj->getNameInDocument());
|
|
// if (pos != ObjectMap.end()) {
|
|
// QFont f = pos->second->font(0);
|
|
// f.setUnderline(mark);
|
|
// pos->second->setFont(0,f);
|
|
// }
|
|
//}
|
|
|
|
void DocumentItem::testStatus(void)
|
|
{
|
|
FOREACH_ITEM_ALL(item);
|
|
item->testStatus();
|
|
END_FOREACH_ITEM;
|
|
}
|
|
|
|
void DocumentItem::setData (int column, int role, const QVariant & value)
|
|
{
|
|
if (role == Qt::EditRole) {
|
|
QString label = value.toString();
|
|
pDocument->getDocument()->Label.setValue((const char*)label.toUtf8());
|
|
}
|
|
|
|
QTreeWidgetItem::setData(column, role, value);
|
|
}
|
|
|
|
void DocumentItem::setObjectHighlighted(const char* name, bool select)
|
|
{
|
|
Q_UNUSED(select);
|
|
Q_UNUSED(name);
|
|
// FOREACH_ITEM_NAME(item,name);
|
|
//pos->second->setData(0, Qt::TextColorRole, QVariant(Qt::red));
|
|
//treeWidget()->setItemSelected(pos->second, select);
|
|
// END_FOREACH_ITEM;
|
|
}
|
|
|
|
void DocumentItem::setObjectSelected(const char* name, bool select)
|
|
{
|
|
FOREACH_ITEM_NAME(item,name);
|
|
treeWidget()->setItemSelected(item, select);
|
|
END_FOREACH_ITEM;
|
|
}
|
|
|
|
void DocumentItem::clearSelection(void)
|
|
{
|
|
// Block signals here otherwise we get a recursion and quadratic runtime
|
|
bool ok = treeWidget()->blockSignals(true);
|
|
FOREACH_ITEM_ALL(item);
|
|
item->setSelected(false);
|
|
END_FOREACH_ITEM;
|
|
treeWidget()->blockSignals(ok);
|
|
}
|
|
|
|
void DocumentItem::updateSelection(void)
|
|
{
|
|
std::vector<App::DocumentObject*> sel;
|
|
FOREACH_ITEM_ALL(item);
|
|
if (treeWidget()->isItemSelected(item))
|
|
sel.push_back(item->object()->getObject());
|
|
END_FOREACH_ITEM;
|
|
|
|
Gui::Selection().setSelection(pDocument->getDocument()->getName(), sel);
|
|
}
|
|
|
|
namespace Gui {
|
|
struct ObjectItem_Less : public std::binary_function<DocumentObjectItem*,
|
|
DocumentObjectItem*, bool>
|
|
{
|
|
bool operator()(DocumentObjectItem* x, DocumentObjectItem* y) const
|
|
{
|
|
return x->object()->getObject() < y->object()->getObject();
|
|
}
|
|
};
|
|
|
|
struct ObjectItem_Equal : public std::binary_function<DocumentObjectItem*,
|
|
App::DocumentObject*, bool>
|
|
{
|
|
bool operator()(DocumentObjectItem* x, App::DocumentObject* y) const
|
|
{
|
|
return x->object()->getObject() == y;
|
|
}
|
|
};
|
|
}
|
|
|
|
void DocumentItem::selectItems(void)
|
|
{
|
|
// get an array of all tree items of the document and sort it in ascending order
|
|
// with regard to their document object
|
|
std::vector<DocumentObjectItem*> items;
|
|
FOREACH_ITEM_ALL(item);
|
|
items.push_back(item);
|
|
END_FOREACH_ITEM;
|
|
std::sort(items.begin(), items.end(), ObjectItem_Less());
|
|
|
|
// get and sort all selected document objects of the given document
|
|
std::vector<App::DocumentObject*> objs;
|
|
std::vector<SelectionSingleton::SelObj> obj = Selection().getSelection(pDocument->getDocument()->getName());
|
|
for (std::vector<SelectionSingleton::SelObj>::iterator jt = obj.begin(); jt != obj.end(); ++jt) {
|
|
objs.push_back(jt->pObject);
|
|
}
|
|
std::sort(objs.begin(), objs.end());
|
|
|
|
// The document objects in 'objs' is a subset of the document objects stored
|
|
// in 'items'. Since both arrays are sorted we get the wanted tree items in
|
|
// linear time.
|
|
std::vector<DocumentObjectItem*> common;
|
|
std::vector<DocumentObjectItem*>::iterator item_it = items.begin();
|
|
for (std::vector<App::DocumentObject*>::iterator it = objs.begin(); it != objs.end(); ++it) {
|
|
item_it = std::find_if(item_it, items.end(), std::bind2nd(ObjectItem_Equal(), *it));
|
|
if (item_it == items.end())
|
|
break; // should never ever happen
|
|
common.push_back(*item_it);
|
|
}
|
|
|
|
// get all unselected items of the given document
|
|
std::sort(common.begin(), common.end());
|
|
std::sort(items.begin(), items.end());
|
|
std::vector<DocumentObjectItem*> diff;
|
|
std::back_insert_iterator<std::vector<DocumentObjectItem*> > biit(diff);
|
|
std::set_difference(items.begin(), items.end(), common.begin(), common.end(), biit);
|
|
|
|
// select the appropriate items
|
|
QList<QTreeWidgetItem *> selitems;
|
|
for (std::vector<DocumentObjectItem*>::iterator it = common.begin(); it != common.end(); ++it)
|
|
selitems.append(*it);
|
|
static_cast<TreeWidget*>(treeWidget())->setItemsSelected(selitems, true);
|
|
// deselect the appropriate items
|
|
QList<QTreeWidgetItem *> deselitems;
|
|
for (std::vector<DocumentObjectItem*>::iterator it = diff.begin(); it != diff.end(); ++it)
|
|
deselitems.append(*it);
|
|
static_cast<TreeWidget*>(treeWidget())->setItemsSelected(deselitems, false);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
DocumentObjectItem::DocumentObjectItem(Gui::ViewProviderDocumentObject* pcViewProvider,
|
|
DocumentObjectItemsPtr selves)
|
|
: QTreeWidgetItem(TreeWidget::ObjectType), previousStatus(-1), viewObject(pcViewProvider)
|
|
, myselves(selves), populated(false)
|
|
{
|
|
setFlags(flags()|Qt::ItemIsEditable);
|
|
// Setup connections
|
|
connectIcon = pcViewProvider->signalChangeIcon.connect(boost::bind(&DocumentObjectItem::slotChangeIcon, this));
|
|
connectTool = pcViewProvider->signalChangeToolTip.connect(boost::bind(&DocumentObjectItem::slotChangeToolTip, this, _1));
|
|
connectStat = pcViewProvider->signalChangeStatusTip.connect(boost::bind(&DocumentObjectItem::slotChangeStatusTip, this, _1));
|
|
myselves->insert(this);
|
|
}
|
|
|
|
DocumentObjectItem::~DocumentObjectItem()
|
|
{
|
|
auto it = myselves->find(this);
|
|
if(it == myselves->end())
|
|
assert(0);
|
|
else
|
|
myselves->erase(it);
|
|
|
|
connectIcon.disconnect();
|
|
connectTool.disconnect();
|
|
connectStat.disconnect();
|
|
}
|
|
|
|
Gui::ViewProviderDocumentObject* DocumentObjectItem::object() const
|
|
{
|
|
return viewObject;
|
|
}
|
|
|
|
void DocumentObjectItem::testStatus()
|
|
{
|
|
App::DocumentObject* pObject = viewObject->getObject();
|
|
|
|
// if status has changed then continue
|
|
int currentStatus =
|
|
((pObject->isError() ? 1 : 0) << 2) |
|
|
((pObject->mustRecompute() == 1 ? 1 : 0) << 1) |
|
|
(viewObject->isShow() ? 1 : 0);
|
|
if (previousStatus == currentStatus)
|
|
return;
|
|
previousStatus = currentStatus;
|
|
|
|
QPixmap px;
|
|
if (currentStatus & 4) {
|
|
// object is in error state
|
|
static const char * const feature_error_xpm[]={
|
|
"9 9 3 1",
|
|
". c None",
|
|
"# c #ff0000",
|
|
"a c #ffffff",
|
|
"...###...",
|
|
".##aaa##.",
|
|
".##aaa##.",
|
|
"###aaa###",
|
|
"###aaa###",
|
|
"#########",
|
|
".##aaa##.",
|
|
".##aaa##.",
|
|
"...###..."};
|
|
px = QPixmap(feature_error_xpm);
|
|
}
|
|
else if (currentStatus & 2) {
|
|
// object must be recomputed
|
|
static const char * const feature_recompute_xpm[]={
|
|
"9 9 3 1",
|
|
". c None",
|
|
"# c #0000ff",
|
|
"a c #ffffff",
|
|
"...###...",
|
|
".######aa",
|
|
".#####aa.",
|
|
"#####aa##",
|
|
"#aa#aa###",
|
|
"#aaaa####",
|
|
".#aa####.",
|
|
".#######.",
|
|
"...###..."};
|
|
px = QPixmap(feature_recompute_xpm);
|
|
}
|
|
|
|
QIcon::Mode mode = QIcon::Normal;
|
|
if (currentStatus & 1) { // visible
|
|
// Note: By default the foreground, i.e. text color is invalid
|
|
// to make use of the default color of the tree widget's palette.
|
|
// If we temporarily set this color to dark and reset to an invalid
|
|
// color again we cannot do it with setTextColor() or setForeground(),
|
|
// respectively, because for any reason the color would always switch
|
|
// to black which will lead to unreadable text if the system background
|
|
// hss already a dark color.
|
|
// However, it works if we set the appropriate role to an empty QVariant().
|
|
#if QT_VERSION >= 0x040200
|
|
this->setData(0, Qt::ForegroundRole,QVariant());
|
|
#else
|
|
this->setData(0, Qt::TextColorRole,QVariant());
|
|
#endif
|
|
}
|
|
else { // invisible
|
|
QStyleOptionViewItem opt;
|
|
// it can happen that a tree item is not attached to the tree widget (#0003025)
|
|
if (this->treeWidget())
|
|
opt.initFrom(this->treeWidget());
|
|
#if QT_VERSION >= 0x040200
|
|
this->setForeground(0, opt.palette.color(QPalette::Disabled,QPalette::Text));
|
|
#else
|
|
this->setTextColor(0, opt.palette.color(QPalette::Disabled,QPalette::Text);
|
|
#endif
|
|
mode = QIcon::Disabled;
|
|
}
|
|
|
|
// get the original icon set
|
|
QIcon icon_org = viewObject->getIcon();
|
|
QIcon icon_mod;
|
|
int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize);
|
|
|
|
// if needed show small pixmap inside
|
|
if (!px.isNull()) {
|
|
icon_mod.addPixmap(BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::Off),
|
|
px,BitmapFactoryInst::TopRight), QIcon::Normal, QIcon::Off);
|
|
icon_mod.addPixmap(BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On ),
|
|
px,BitmapFactoryInst::TopRight), QIcon::Normal, QIcon::Off);
|
|
}
|
|
else {
|
|
icon_mod.addPixmap(icon_org.pixmap(w, w, mode, QIcon::Off), QIcon::Normal, QIcon::Off);
|
|
icon_mod.addPixmap(icon_org.pixmap(w, w, mode, QIcon::On ), QIcon::Normal, QIcon::On );
|
|
}
|
|
|
|
this->setIcon(0, icon_mod);
|
|
}
|
|
|
|
void DocumentObjectItem::displayStatusInfo()
|
|
{
|
|
App::DocumentObject* Obj = viewObject->getObject();
|
|
|
|
QString info = QString::fromLatin1(Obj->getStatusString());
|
|
if ( Obj->mustExecute() == 1 )
|
|
info += QString::fromLatin1(" (but must be executed)");
|
|
QString status = TreeWidget::tr("%1, Internal name: %2")
|
|
.arg(info,
|
|
QString::fromLatin1(Obj->getNameInDocument()));
|
|
getMainWindow()->showMessage(status);
|
|
|
|
if (Obj->isError()) {
|
|
QTreeWidget* tree = this->treeWidget();
|
|
QPoint pos = tree->visualItemRect(this).topRight();
|
|
QToolTip::showText(tree->mapToGlobal(pos), info);
|
|
}
|
|
}
|
|
|
|
void DocumentObjectItem::setExpandedStatus(bool on)
|
|
{
|
|
App::DocumentObject* Obj = viewObject->getObject();
|
|
Obj->setStatus(App::Expand, on);
|
|
}
|
|
|
|
void DocumentObjectItem::setData (int column, int role, const QVariant & value)
|
|
{
|
|
QTreeWidgetItem::setData(column, role, value);
|
|
if (role == Qt::EditRole) {
|
|
QString label = value.toString();
|
|
App::DocumentObject* obj = viewObject->getObject();
|
|
App::Document* doc = obj->getDocument();
|
|
doc->openTransaction(TreeWidget::tr("Rename object").toUtf8());
|
|
obj->Label.setValue((const char*)label.toUtf8());
|
|
doc->commitTransaction();
|
|
}
|
|
}
|
|
|
|
bool DocumentObjectItem::isChildOfItem(DocumentObjectItem* item)
|
|
{
|
|
int numChild = item->childCount();
|
|
for (int i=0; i<numChild; i++) {
|
|
QTreeWidgetItem* child = item->child(i);
|
|
if (child == this)
|
|
return true;
|
|
if (child->type() == TreeWidget::ObjectType) {
|
|
DocumentObjectItem* obj = static_cast<DocumentObjectItem*>(child);
|
|
if (this->isChildOfItem(obj))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DocumentObjectItem::slotChangeIcon()
|
|
{
|
|
previousStatus = -1;
|
|
testStatus();
|
|
}
|
|
|
|
void DocumentObjectItem::slotChangeToolTip(const QString& tip)
|
|
{
|
|
this->setToolTip(0, tip);
|
|
}
|
|
|
|
void DocumentObjectItem::slotChangeStatusTip(const QString& tip)
|
|
{
|
|
this->setStatusTip(0, tip);
|
|
}
|
|
|
|
#include "moc_Tree.cpp"
|
|
|