Implementation of Link
This patch includes the actual implementation of Link, which is implemented as an extension named LinkBaseExtension in App namespace, and a full view provider ViewProviderLink in Gui. The reason of not using ViewProviderExtension is because it need full control when display, not just extending existing functionalities. Please see [here](https://git.io/fjPue) for more details of the implementation. This patch also includes a set of link manipulation commands, and a task panel for overriding geometry element colors.
This commit is contained in:
533
src/Gui/TaskElementColors.cpp
Normal file
533
src/Gui/TaskElementColors.cpp
Normal file
@@ -0,0 +1,533 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
|
||||
* *
|
||||
* 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 <sstream>
|
||||
#endif
|
||||
|
||||
#include <QColorDialog>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include "ui_TaskElementColors.h"
|
||||
#include "TaskElementColors.h"
|
||||
#include "ViewProviderLink.h"
|
||||
|
||||
#include <Base/Console.h>
|
||||
#include <App/ComplexGeoData.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Control.h"
|
||||
#include "Document.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Selection.h"
|
||||
#include "BitmapFactory.h"
|
||||
#include "Command.h"
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
|
||||
FC_LOG_LEVEL_INIT("Gui",true,true)
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
class ElementColors::Private: public Gui::SelectionGate
|
||||
{
|
||||
public:
|
||||
typedef boost::signals2::connection Connection;
|
||||
std::unique_ptr<Ui_TaskElementColors> ui;
|
||||
ViewProviderDocumentObject *vp;
|
||||
ViewProviderDocumentObject *vpParent;
|
||||
Document *vpDoc;
|
||||
std::map<std::string,QListWidgetItem*> elements;
|
||||
std::vector<QListWidgetItem*> items;
|
||||
std::string hiddenSub;
|
||||
Connection connectDelDoc;
|
||||
Connection connectDelObj;
|
||||
QPixmap px;
|
||||
bool busy;
|
||||
long onTopMode;
|
||||
bool touched;
|
||||
|
||||
std::string editDoc;
|
||||
std::string editObj;
|
||||
std::string editSub;
|
||||
std::string editElement;
|
||||
|
||||
Private(ViewProviderDocumentObject* vp, const char *element="")
|
||||
: ui(new Ui_TaskElementColors()), vp(vp),editElement(element)
|
||||
{
|
||||
vpDoc = vp->getDocument();
|
||||
vpParent = vp;
|
||||
auto doc = Application::Instance->editDocument();
|
||||
if(doc) {
|
||||
auto editVp = doc->getInEdit(&vpParent,&editSub);
|
||||
if(editVp == vp) {
|
||||
auto obj = vpParent->getObject();
|
||||
editDoc = obj->getDocument()->getName();
|
||||
editObj = obj->getNameInDocument();
|
||||
editSub = Data::ComplexGeoData::noElementName(editSub.c_str());
|
||||
}
|
||||
}
|
||||
if(editDoc.empty()) {
|
||||
vpParent = vp;
|
||||
editDoc = vp->getObject()->getDocument()->getName();
|
||||
editObj = vp->getObject()->getNameInDocument();
|
||||
editSub.clear();
|
||||
}
|
||||
onTopMode = vpParent->OnTopWhenSelected.getValue();
|
||||
busy = false;
|
||||
touched = false;
|
||||
int w = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width();
|
||||
px = QPixmap(w,w);
|
||||
}
|
||||
|
||||
~Private() {
|
||||
vpParent->OnTopWhenSelected.setValue(onTopMode);
|
||||
}
|
||||
|
||||
bool allow(App::Document *doc, App::DocumentObject *obj, const char *subname) {
|
||||
if(editDoc!=doc->getName() ||
|
||||
editObj!=obj->getNameInDocument() ||
|
||||
!boost::starts_with(subname,editSub))
|
||||
return false;
|
||||
if(editElement.empty())
|
||||
return true;
|
||||
const char *dot = strrchr(subname,'.');
|
||||
if(!dot)
|
||||
dot = subname;
|
||||
else
|
||||
++dot;
|
||||
return *dot==0 || boost::starts_with(dot,editElement);
|
||||
}
|
||||
|
||||
void populate() {
|
||||
int i=0;
|
||||
for(auto &v : vp->getElementColors())
|
||||
addItem(i++,v.first.c_str());
|
||||
apply();
|
||||
}
|
||||
|
||||
void addItem(int i,const char *sub, bool push=false) {
|
||||
auto itE = elements.find(sub);
|
||||
if(i<0 && itE!=elements.end()) {
|
||||
if(push && !ViewProvider::hasHiddenMarker(sub))
|
||||
items.push_back(itE->second);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *marker = ViewProvider::hasHiddenMarker(sub);
|
||||
if(marker) {
|
||||
auto icon = BitmapFactory().pixmap("Invisible");
|
||||
QListWidgetItem* item = new QListWidgetItem(icon,
|
||||
QString::fromLatin1(std::string(sub,marker-sub).c_str()), ui->elementList);
|
||||
item->setData(Qt::UserRole,QColor());
|
||||
item->setData(Qt::UserRole+1,QString::fromLatin1(sub));
|
||||
elements.emplace(sub,item);
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto &v : vp->getElementColors(sub)) {
|
||||
auto it = elements.find(v.first.c_str());
|
||||
if(it!=elements.end()) {
|
||||
if(push)
|
||||
items.push_back(it->second);
|
||||
continue;
|
||||
}
|
||||
auto color = v.second;
|
||||
QColor c;
|
||||
c.setRgbF(color.r,color.g,color.b,1.0-color.a);
|
||||
px.fill(c);
|
||||
QListWidgetItem* item = new QListWidgetItem(QIcon(px),
|
||||
QString::fromLatin1(Data::ComplexGeoData::oldElementName(v.first.c_str()).c_str()),
|
||||
ui->elementList);
|
||||
item->setData(Qt::UserRole,c);
|
||||
item->setData(Qt::UserRole+1,QString::fromLatin1(v.first.c_str()));
|
||||
if(push)
|
||||
items.push_back(item);
|
||||
elements.emplace(v.first,item);
|
||||
}
|
||||
}
|
||||
|
||||
void apply() {
|
||||
std::map<std::string,App::Color> info;
|
||||
int count = ui->elementList->count();
|
||||
for(int i=0;i<count;++i) {
|
||||
auto item = ui->elementList->item(i);
|
||||
auto color = item->data(Qt::UserRole).value<QColor>();
|
||||
std::string sub = qPrintable(item->data(Qt::UserRole+1).value<QString>());
|
||||
info.emplace(qPrintable(item->data(Qt::UserRole+1).value<QString>()),
|
||||
App::Color(color.redF(),color.greenF(),color.blueF(),1.0-color.alphaF()));
|
||||
}
|
||||
if(!App::GetApplication().getActiveTransaction())
|
||||
App::GetApplication().setActiveTransaction("Set colors");
|
||||
vp->setElementColors(info);
|
||||
touched = true;
|
||||
Selection().clearSelection();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
touched = false;
|
||||
App::GetApplication().closeActiveTransaction(true);
|
||||
Selection().clearSelection();
|
||||
}
|
||||
|
||||
void accept() {
|
||||
if(touched && ui->recompute->isChecked()) {
|
||||
auto obj = vp->getObject();
|
||||
obj->touch();
|
||||
obj->getDocument()->recompute(obj->getInListRecursive());
|
||||
touched = false;
|
||||
}
|
||||
App::GetApplication().closeActiveTransaction();
|
||||
}
|
||||
|
||||
void removeAll() {
|
||||
if(elements.size()) {
|
||||
hiddenSub.clear();
|
||||
ui->elementList->clear();
|
||||
elements.clear();
|
||||
apply();
|
||||
}
|
||||
}
|
||||
|
||||
void removeItems() {
|
||||
for(auto item : ui->elementList->selectedItems()) {
|
||||
std::string sub = qPrintable(item->data(Qt::UserRole+1).value<QString>());
|
||||
if(sub == hiddenSub)
|
||||
hiddenSub.clear();
|
||||
elements.erase(sub);
|
||||
delete item;
|
||||
}
|
||||
apply();
|
||||
}
|
||||
|
||||
void editItem(QWidget *parent, QListWidgetItem *item) {
|
||||
std::string sub = qPrintable(item->data(Qt::UserRole+1).value<QString>());
|
||||
if(ViewProvider::hasHiddenMarker(sub.c_str()))
|
||||
return;
|
||||
auto color = item->data(Qt::UserRole).value<QColor>();
|
||||
QColorDialog cd(color, parent);
|
||||
cd.setOption(QColorDialog::ShowAlphaChannel);
|
||||
if (cd.exec()!=QDialog::Accepted || color==cd.selectedColor())
|
||||
return;
|
||||
color = cd.selectedColor();
|
||||
item->setData(Qt::UserRole,color);
|
||||
px.fill(color);
|
||||
item->setIcon(QIcon(px));
|
||||
apply();
|
||||
}
|
||||
|
||||
void onSelectionChanged(const SelectionChanges &msg) {
|
||||
// no object selected in the combobox or no sub-element was selected
|
||||
if (busy)
|
||||
return;
|
||||
busy = true;
|
||||
switch(msg.Type) {
|
||||
case SelectionChanges::ClrSelection:
|
||||
ui->elementList->clearSelection();
|
||||
break;
|
||||
case SelectionChanges::AddSelection:
|
||||
case SelectionChanges::RmvSelection:
|
||||
if(msg.pDocName && msg.pObjectName && msg.pSubName && msg.pSubName[0]) {
|
||||
if(editDoc == msg.pDocName &&
|
||||
editObj == msg.pObjectName &&
|
||||
boost::starts_with(msg.pSubName,editSub))
|
||||
{
|
||||
for(auto item : ui->elementList->findItems(
|
||||
QString::fromLatin1(msg.pSubName-editSub.size()),0))
|
||||
item->setSelected(msg.Type==SelectionChanges::AddSelection);
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
busy = false;
|
||||
}
|
||||
|
||||
void onSelectionChanged() {
|
||||
if(busy) return;
|
||||
busy = true;
|
||||
std::map<std::string,int> sels;
|
||||
for(auto &sel : Selection().getSelectionEx(
|
||||
editDoc.c_str(),App::DocumentObject::getClassTypeId(),0))
|
||||
{
|
||||
if(sel.getFeatName()!=editObj) continue;
|
||||
for(auto &sub : sel.getSubNames()) {
|
||||
if(boost::starts_with(sub,editSub))
|
||||
sels[sub.c_str()+editSub.size()] = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for(auto item : ui->elementList->selectedItems()) {
|
||||
std::string name(qPrintable(item->data(Qt::UserRole+1).value<QString>()));
|
||||
if(ViewProvider::hasHiddenMarker(name.c_str()))
|
||||
continue;
|
||||
auto &v = sels[name];
|
||||
if(!v)
|
||||
Selection().addSelection(editDoc.c_str(),
|
||||
editObj.c_str(), (editSub+name).c_str());
|
||||
v = 2;
|
||||
}
|
||||
for(auto &v : sels) {
|
||||
if(v.second!=2) {
|
||||
Selection().rmvSelection(editDoc.c_str(),
|
||||
editObj.c_str(), (editSub+v.first).c_str());
|
||||
}
|
||||
}
|
||||
busy = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* TRANSLATOR Gui::TaskElementColors */
|
||||
|
||||
ElementColors::ElementColors(ViewProviderDocumentObject* vp, bool noHide)
|
||||
:d(new Private(vp))
|
||||
{
|
||||
d->ui->setupUi(this);
|
||||
d->ui->objectLabel->setText(QString::fromUtf8(vp->getObject()->Label.getValue()));
|
||||
d->ui->elementList->setMouseTracking(true); // needed for itemEntered() to work
|
||||
|
||||
if(noHide)
|
||||
d->ui->hideSelection->setVisible(false);
|
||||
|
||||
ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath
|
||||
("User parameter:BaseApp/Preferences/View");
|
||||
d->ui->recompute->setChecked(hPart->GetBool("ColorRecompute",true));
|
||||
d->ui->onTop->setChecked(hPart->GetBool("ColorOnTop",true));
|
||||
if(d->ui->onTop->isChecked())
|
||||
d->vpParent->OnTopWhenSelected.setValue(3);
|
||||
|
||||
Selection().addSelectionGate(d,0);
|
||||
|
||||
d->connectDelDoc = Application::Instance->signalDeleteDocument.connect(boost::bind
|
||||
(&ElementColors::slotDeleteDocument, this, _1));
|
||||
d->connectDelObj = Application::Instance->signalDeletedObject.connect(boost::bind
|
||||
(&ElementColors::slotDeleteObject, this, _1));
|
||||
|
||||
d->populate();
|
||||
}
|
||||
|
||||
ElementColors::~ElementColors()
|
||||
{
|
||||
d->connectDelDoc.disconnect();
|
||||
d->connectDelObj.disconnect();
|
||||
Selection().rmvSelectionGate();
|
||||
}
|
||||
|
||||
void ElementColors::on_recompute_clicked(bool checked) {
|
||||
ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath
|
||||
("User parameter:BaseApp/Preferences/View");
|
||||
hPart->SetBool("ColorRecompute",checked);
|
||||
}
|
||||
|
||||
void ElementColors::on_onTop_clicked(bool checked) {
|
||||
ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath
|
||||
("User parameter:BaseApp/Preferences/View");
|
||||
hPart->SetBool("ColorOnTop",checked);
|
||||
d->vpParent->OnTopWhenSelected.setValue(checked?3:d->onTopMode);
|
||||
}
|
||||
|
||||
void ElementColors::slotDeleteDocument(const Document& Doc)
|
||||
{
|
||||
if (d->vpDoc==&Doc || d->editDoc==Doc.getDocument()->getName())
|
||||
Control().closeDialog();
|
||||
}
|
||||
|
||||
void ElementColors::slotDeleteObject(const ViewProvider& obj)
|
||||
{
|
||||
if (d->vp==&obj)
|
||||
Control().closeDialog();
|
||||
}
|
||||
|
||||
void ElementColors::on_removeSelection_clicked()
|
||||
{
|
||||
d->removeItems();
|
||||
}
|
||||
|
||||
void ElementColors::on_boxSelect_clicked()
|
||||
{
|
||||
auto cmd = Application::Instance->commandManager().getCommandByName("Std_BoxElementSelection");
|
||||
if(cmd)
|
||||
cmd->invoke(0);
|
||||
}
|
||||
|
||||
void ElementColors::on_hideSelection_clicked() {
|
||||
auto sels = Selection().getSelectionEx(d->editDoc.c_str(),App::DocumentObject::getClassTypeId(),0);
|
||||
for(auto &sel : sels) {
|
||||
if(d->editObj!=sel.getFeatName())
|
||||
continue;
|
||||
const auto &subs = sel.getSubNames();
|
||||
if(subs.size()) {
|
||||
for(auto &sub : subs) {
|
||||
if(boost::starts_with(sub,d->editSub)) {
|
||||
auto name = Data::ComplexGeoData::noElementName(sub.c_str()+d->editSub.size());
|
||||
name += ViewProvider::hiddenMarker();
|
||||
d->addItem(-1,name.c_str());
|
||||
}
|
||||
}
|
||||
d->apply();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColors::on_addSelection_clicked()
|
||||
{
|
||||
auto sels = Selection().getSelectionEx(d->editDoc.c_str(),App::DocumentObject::getClassTypeId(),0);
|
||||
d->items.clear();
|
||||
if(sels.empty())
|
||||
d->addItem(-1,"Face",true);
|
||||
else {
|
||||
for(auto &sel : sels) {
|
||||
if(d->editObj!=sel.getFeatName())
|
||||
continue;
|
||||
const auto &subs = sel.getSubNames();
|
||||
if(subs.empty()) {
|
||||
d->addItem(-1,"Face",true);
|
||||
break;
|
||||
}
|
||||
for(auto &sub : subs) {
|
||||
if(boost::starts_with(sub,d->editSub))
|
||||
d->addItem(-1,sub.c_str()+d->editSub.size(),true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d->items.size()) {
|
||||
auto color = d->items.front()->data(Qt::UserRole).value<QColor>();
|
||||
QColorDialog cd(color, this);
|
||||
cd.setOption(QColorDialog::ShowAlphaChannel);
|
||||
if (cd.exec()!=QDialog::Accepted)
|
||||
return;
|
||||
color = cd.selectedColor();
|
||||
for(auto item : d->items) {
|
||||
item->setData(Qt::UserRole,color);
|
||||
d->px.fill(color);
|
||||
item->setIcon(QIcon(d->px));
|
||||
}
|
||||
d->apply();
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColors::on_removeAll_clicked()
|
||||
{
|
||||
d->removeAll();
|
||||
}
|
||||
|
||||
bool ElementColors::accept()
|
||||
{
|
||||
d->accept();
|
||||
Application::Instance->setEditDocument(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElementColors::reject()
|
||||
{
|
||||
d->reset();
|
||||
Application::Instance->setEditDocument(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ElementColors::changeEvent(QEvent *e)
|
||||
{
|
||||
QWidget::changeEvent(e);
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
d->ui->retranslateUi(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColors::leaveEvent(QEvent *e) {
|
||||
QWidget::leaveEvent(e);
|
||||
Selection().rmvPreselect();
|
||||
if(d->hiddenSub.size()) {
|
||||
d->vp->partialRender({d->hiddenSub},false);
|
||||
d->hiddenSub.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ElementColors::on_elementList_itemEntered(QListWidgetItem *item) {
|
||||
std::string name(qPrintable(item->data(Qt::UserRole+1).value<QString>()));
|
||||
if(d->hiddenSub.size()) {
|
||||
d->vp->partialRender({d->hiddenSub},false);
|
||||
d->hiddenSub.clear();
|
||||
}
|
||||
if(ViewProvider::hasHiddenMarker(name.c_str())) {
|
||||
d->hiddenSub = name;
|
||||
d->vp->partialRender({name},true);
|
||||
name.resize(name.size()-ViewProvider::hiddenMarker().size());
|
||||
}
|
||||
Selection().setPreselect(d->editDoc.c_str(),
|
||||
d->editObj.c_str(), (d->editSub+name).c_str(),0,0,0,
|
||||
d->ui->onTop->isChecked()?2:1);
|
||||
}
|
||||
|
||||
void ElementColors::on_elementList_itemSelectionChanged() {
|
||||
d->onSelectionChanged();
|
||||
}
|
||||
|
||||
void ElementColors::onSelectionChanged(const SelectionChanges& msg)
|
||||
{
|
||||
d->onSelectionChanged(msg);
|
||||
}
|
||||
|
||||
void ElementColors::on_elementList_itemDoubleClicked(QListWidgetItem *item) {
|
||||
d->editItem(this,item);
|
||||
}
|
||||
|
||||
/* TRANSLATOR Gui::TaskElementColors */
|
||||
|
||||
TaskElementColors::TaskElementColors(ViewProviderDocumentObject* vp, bool noHide)
|
||||
{
|
||||
widget = new ElementColors(vp,noHide);
|
||||
taskbox = new TaskView::TaskBox(
|
||||
QPixmap(), widget->windowTitle(), true, 0);
|
||||
taskbox->groupLayout()->addWidget(widget);
|
||||
Content.push_back(taskbox);
|
||||
}
|
||||
|
||||
TaskElementColors::~TaskElementColors()
|
||||
{
|
||||
}
|
||||
|
||||
void TaskElementColors::open()
|
||||
{
|
||||
}
|
||||
|
||||
void TaskElementColors::clicked(int)
|
||||
{
|
||||
}
|
||||
|
||||
bool TaskElementColors::accept()
|
||||
{
|
||||
return widget->accept();
|
||||
}
|
||||
|
||||
bool TaskElementColors::reject()
|
||||
{
|
||||
return widget->reject();
|
||||
}
|
||||
|
||||
#include "moc_TaskElementColors.cpp"
|
||||
Reference in New Issue
Block a user