FreeCAD/src/Gui/Tree.cpp
2014-03-11 19:27:46 +01:00

1354 lines
52 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/signals.hpp>
# include <boost/bind.hpp>
# include <QAction>
# include <QActionGroup>
# include <QApplication>
# include <qcursor.h>
# include <qlayout.h>
# include <qstatusbar.h>
# include <QContextMenuEvent>
# include <QMenu>
# include <QPixmap>
# include <QTimer>
# include <QHeaderView>
#endif
#include <Base/Console.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectGroup.h>
#include "Tree.h"
#include "Document.h"
#include "BitmapFactory.h"
#include "ViewProviderDocumentObject.h"
#include "MenuManager.h"
#include "Application.h"
#include "MainWindow.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), 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"));
this->relabelObjectAction->setShortcut(Qt::Key_F2);
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()));
// Setup connections
Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, _1));
Application::Instance->signalDeleteDocument.connect(boost::bind(&TreeWidget::slotDeleteDocument, this, _1));
Application::Instance->signalRenameDocument.connect(boost::bind(&TreeWidget::slotRenameDocument, this, _1));
Application::Instance->signalActiveDocument.connect(boost::bind(&TreeWidget::slotActiveDocument, this, _1));
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
this->header()->setResizeMode(0, QHeaderView::ResizeToContents);
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()
{
}
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();
contextMenu.addAction(this->createGroupAction);
}
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->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);
active = subMenuGroup.checkedAction();
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::fromAscii("App.getDocument(\"%1\").addObject"
"(\"App::DocumentObjectGroup\",\"%2\")")
.arg(QString::fromAscii(doc->getName())).arg(name);
Gui::Document* gui = Gui::Application::Instance->getDocument(doc);
gui->openCommand("Create group");
Gui::Application::Instance->runPythonCode(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::fromAscii("App.getDocument(\"%1\").getObject(\"%2\")"
".newObject(\"App::DocumentObjectGroup\",\"%3\")")
.arg(QString::fromAscii(doc->getName()))
.arg(QString::fromAscii(obj->getNameInDocument()))
.arg(name);
Gui::Document* gui = Gui::Application::Instance->getDocument(doc);
gui->openCommand("Create group");
Gui::Application::Instance->runPythonCode(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::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);
}
bool TreeWidget::dropMimeData(QTreeWidgetItem *parent, int index,
const QMimeData *data, Qt::DropAction action)
{
return QTreeWidget::dropMimeData(parent, index, data, action);
}
Qt::DropActions TreeWidget::supportedDropActions () const
{
return QTreeWidget::supportedDropActions();
}
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);
}
}
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;
// 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).
QTreeWidgetItem* parent = (*it)->parent();
if (parent && parent->type() == TreeWidget::ObjectType) {
// fix issue #0001456
if (!items.contains(parent)) {
Gui::ViewProvider* vp = static_cast<DocumentObjectItem *>(parent)->object();
if (!vp->canDragObjects()) {
return 0;
}
}
}
}
return QTreeWidget::mimeData(items);
}
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 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;
}
}
}
else if (targetitem->type() == TreeWidget::ObjectType) {
Gui::ViewProviderDocumentObject* vp = static_cast<DocumentObjectItem*>(targetitem)->object();
if (!vp->canDropObjects()) {
event->ignore();
}
QList<QTreeWidgetItem *> children;
for (int i=0; i<targetitem->childCount(); i++)
children << targetitem->child(i);
App::DocumentObject* grp = vp->getObject();
App::Document* doc = grp->getDocument();
QList<QModelIndex> idxs = selectedIndexes();
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;
}
// 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.
if (static_cast<DocumentObjectItem*>(targetitem)->isChildOfItem(
static_cast<DocumentObjectItem*>(item))) {
event->ignore();
return;
}
// if the item is already a child of the target item there is nothing to do
if (children.contains(item)) {
event->ignore();
return;
}
}
}
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
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
Gui::ViewProviderDocumentObject* vp = static_cast<DocumentObjectItem*>(targetitem)->object();
App::DocumentObject* grp = vp->getObject();
if (!vp->canDropObjects()) {
return; // no group like object
}
// Open command
App::Document* doc = grp->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* vpc = static_cast<DocumentObjectItem*>(*it)->object();
App::DocumentObject* obj = vpc->getObject();
// does this have a parent object
QTreeWidgetItem* parent = (*it)->parent();
if (parent && parent->type() == TreeWidget::ObjectType) {
Gui::ViewProvider* vpp = static_cast<DocumentObjectItem *>(parent)->object();
vpp->dragObject(obj);
}
// now add the object to the target object
vp->dropObject(obj);
}
gui->commitCommand();
}
else if (targetitem->type() == TreeWidget::DocumentType) {
// Open command
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* vpc = static_cast<DocumentObjectItem*>(*it)->object();
App::DocumentObject* obj = vpc->getObject();
// does this have a parent object
QTreeWidgetItem* parent = (*it)->parent();
if (parent && parent->type() == TreeWidget::ObjectType) {
Gui::ViewProvider* vpp = static_cast<DocumentObjectItem *>(parent)->object();
vpp->dragObject(obj);
}
}
gui->commitCommand();
}
}
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
}
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
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->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);
}
}
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"));
}
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::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
doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, _1));
doc->signalDeletedObject.connect(boost::bind(&DocumentItem::slotDeleteObject, this, _1));
doc->signalChangedObject.connect(boost::bind(&DocumentItem::slotChangeObject, this, _1));
doc->signalRenamedObject.connect(boost::bind(&DocumentItem::slotRenameObject, this, _1));
doc->signalActivatedObject.connect(boost::bind(&DocumentItem::slotActiveObject, this, _1));
doc->signalInEdit.connect(boost::bind(&DocumentItem::slotInEdit, this, _1));
doc->signalResetEdit.connect(boost::bind(&DocumentItem::slotResetEdit, this, _1));
doc->signalHighlightObject.connect(boost::bind(&DocumentItem::slotHighlightObject, this, _1,_2,_3));
doc->signalExpandObject.connect(boost::bind(&DocumentItem::slotExpandObject, this, _1,_2));
setFlags(Qt::ItemIsEnabled/*|Qt::ItemIsEditable*/);
}
DocumentItem::~DocumentItem()
{
}
void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v)
{
std::string name (v.getObject()->getNameInDocument());
std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.find(name);
if (it != ObjectMap.end())
it->second->setBackgroundColor(0,Qt::yellow);
}
void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v)
{
std::string name (v.getObject()->getNameInDocument());
std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.find(name);
if (it != ObjectMap.end()) {
it->second->setData(0, Qt::BackgroundColorRole,QVariant());
}
}
void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj)
{
std::string displayName = obj.getObject()->Label.getValue();
std::string objectName = obj.getObject()->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.find(objectName);
if (it == ObjectMap.end()) {
// cast to non-const object
DocumentObjectItem* item = new DocumentObjectItem(
const_cast<Gui::ViewProviderDocumentObject*>(&obj), this);
item->setIcon(0, obj.getIcon());
item->setText(0, QString::fromUtf8(displayName.c_str()));
ObjectMap[objectName] = item;
} else {
Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n");
}
}
void DocumentItem::slotDeleteObject(const Gui::ViewProviderDocumentObject& obj)
{
std::string objectName = obj.getObject()->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.find(objectName);
if (it != ObjectMap.end()) {
QTreeWidgetItem* parent = it->second->parent();
if (it->second->childCount() > 0) {
QList<QTreeWidgetItem*> children = it->second->takeChildren();
parent->addChildren(children);
}
parent->takeChild(parent->indexOfChild(it->second));
delete it->second;
ObjectMap.erase(it);
}
}
void DocumentItem::slotChangeObject(const Gui::ViewProviderDocumentObject& view)
{
// As we immediately add a newly created object to the tree we check here which
// item (this or a DocumentObjectItem) is the parent of the associated item of 'view'
App::DocumentObject* obj = view.getObject();
std::string objectName = obj->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.find(objectName);
if (it != ObjectMap.end()) {
// use new grouping style
std::set<QTreeWidgetItem*> children;
std::vector<App::DocumentObject*> group = view.claimChildren();
for (std::vector<App::DocumentObject*>::iterator jt = group.begin(); jt != group.end(); ++jt) {
if (*jt) {
const char* internalName = (*jt)->getNameInDocument();
if (internalName) {
std::map<std::string, DocumentObjectItem*>::iterator kt = ObjectMap.find(internalName);
if (kt != ObjectMap.end()) {
children.insert(kt->second);
QTreeWidgetItem* parent = kt->second->parent();
if (parent && parent != it->second) {
if (it->second != kt->second) {
int index = parent->indexOfChild(kt->second);
parent->takeChild(index);
it->second->addChild(kt->second);
}
else {
Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Object references to itself.\n");
}
}
}
else {
Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Cannot reparent unknown object.\n");
}
}
else {
Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Group references unknown object.\n");
}
}
}
// move all children which are not part of the group anymore to this item
int count = it->second->childCount();
for (int i=0; i < count; i++) {
QTreeWidgetItem* child = it->second->child(i);
if (children.find(child) == children.end()) {
it->second->takeChild(i);
this->addChild(child);
}
}
// set the text label
std::string displayName = obj->Label.getValue();
it->second->setText(0, QString::fromUtf8(displayName.c_str()));
}
else {
Base::Console().Warning("Gui::DocumentItem::slotChangedObject(): Cannot change unknown object.\n");
}
}
void DocumentItem::slotRenameObject(const Gui::ViewProviderDocumentObject& obj)
{
for (std::map<std::string,DocumentObjectItem*>::iterator it = ObjectMap.begin(); it != ObjectMap.end(); ++it) {
if (it->second->object() == &obj) {
DocumentObjectItem* item = it->second;
ObjectMap.erase(it);
std::string objectName = obj.getObject()->getNameInDocument();
ObjectMap[objectName] = item;
return;
}
}
// no such object found
Base::Console().Warning("DocumentItem::slotRenamedObject: Cannot rename unknown object.\n");
}
void DocumentItem::slotActiveObject(const Gui::ViewProviderDocumentObject& obj)
{
std::string objectName = obj.getObject()->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator jt = ObjectMap.find(objectName);
if (jt == ObjectMap.end())
return; // signal is emitted before the item gets created
for (std::map<std::string, DocumentObjectItem*>::iterator it = ObjectMap.begin();
it != ObjectMap.end(); ++it)
{
QFont f = it->second->font(0);
f.setBold(it == jt);
it->second->setFont(0,f);
}
}
void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj,const Gui::HighlightMode& high,bool set)
{
std::string objectName = obj.getObject()->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator jt = ObjectMap.find(objectName);
if (jt == ObjectMap.end())
return; // signal is emitted before the item gets created
QFont f = jt->second->font(0);
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:
if(set)
jt->second->setBackgroundColor(0,QColor(200,200,255));
else
jt->second->setData(0, Qt::BackgroundColorRole,QVariant());
break;
default:
break;
}
jt->second->setFont(0,f);
}
void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj,const Gui::TreeItemMode& mode)
{
std::string objectName = obj.getObject()->getNameInDocument();
std::map<std::string, DocumentObjectItem*>::iterator jt = ObjectMap.find(objectName);
if (jt == ObjectMap.end())
return; // signal is emitted before the item gets created
switch (mode) {
case Gui::Expand:
jt->second->setExpanded(true);
break;
case Gui::Collapse:
jt->second->setExpanded(false);
break;
case Gui::Toggle:
if (jt->second->isExpanded())
jt->second->setExpanded(false);
else
jt->second->setExpanded(true);
break;
default:
// not defined enum
assert(0);
}
}
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)
{
for (std::map<std::string,DocumentObjectItem*>::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) {
pos->second->testStatus();
}
}
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)
{
std::map<std::string,DocumentObjectItem*>::iterator pos;
pos = ObjectMap.find(name);
if (pos != ObjectMap.end()) {
//pos->second->setData(0, Qt::TextColorRole, QVariant(Qt::red));
//treeWidget()->setItemSelected(pos->second, select);
}
}
void DocumentItem::setObjectSelected(const char* name, bool select)
{
std::map<std::string,DocumentObjectItem*>::iterator pos;
pos = ObjectMap.find(name);
if (pos != ObjectMap.end()) {
treeWidget()->setItemSelected(pos->second, select);
}
}
void DocumentItem::clearSelection(void)
{
// Block signals here otherwise we get a recursion and quadratic runtime
bool ok = treeWidget()->blockSignals(true);
for (std::map<std::string,DocumentObjectItem*>::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) {
pos->second->setSelected(false);
}
treeWidget()->blockSignals(ok);
}
void DocumentItem::updateSelection(void)
{
std::vector<App::DocumentObject*> sel;
for (std::map<std::string,DocumentObjectItem*>::iterator pos = ObjectMap.begin();pos!=ObjectMap.end();++pos) {
if (treeWidget()->isItemSelected(pos->second)) {
sel.push_back(pos->second->object()->getObject());
}
}
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;
for (std::map<std::string,DocumentObjectItem*>::iterator it = ObjectMap.begin(); it != ObjectMap.end(); ++it) {
items.push_back(it->second);
}
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,
QTreeWidgetItem* parent)
: QTreeWidgetItem(parent, TreeWidget::ObjectType), previousStatus(-1), viewObject(pcViewProvider)
{
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));
}
DocumentObjectItem::~DocumentObjectItem()
{
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->mustExecute() == 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;
opt.initFrom(this->treeWidget());
#if QT_VERSION >= 0x040200
this->setForeground(0, opt.palette.color(QPalette::Disabled,QPalette::Dark));
#else
this->setTextColor(0, opt.palette.color(QPalette::Disabled,QPalette::Dark));
#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::fromAscii(Obj->getStatusString());
if ( Obj->mustExecute() == 1 )
info += QString::fromAscii(" (but must be executed)");
getMainWindow()->showMessage( 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();
viewObject->getObject()->Label.setValue((const char*)label.toUtf8());
}
}
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"