646 lines
20 KiB
C++
646 lines
20 KiB
C++
/****************************************************************************
|
|
* 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 <boost/algorithm/string/predicate.hpp>
|
|
#include <QColorDialog>
|
|
#include <sstream>
|
|
|
|
|
|
#include <App/ElementNamingUtils.h>
|
|
#include <App/Document.h>
|
|
|
|
#include "TaskElementColors.h"
|
|
#include "ui_TaskElementColors.h"
|
|
#include "Application.h"
|
|
#include "BitmapFactory.h"
|
|
#include "Command.h"
|
|
#include "Control.h"
|
|
#include "Document.h"
|
|
#include "FileDialog.h"
|
|
#include "Selection.h"
|
|
#include "SelectionObject.h"
|
|
#include "ViewProviderLink.h"
|
|
|
|
|
|
FC_LOG_LEVEL_INIT("Gui", true, true)
|
|
|
|
using namespace Gui;
|
|
namespace sp = std::placeholders;
|
|
|
|
class ElementColors::Private: public Gui::SelectionGate
|
|
{
|
|
public:
|
|
using Connection = fastsignals::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;
|
|
|
|
explicit Private(ViewProviderDocumentObject* vp, const char* element = "")
|
|
: ui(new Ui_TaskElementColors())
|
|
, vp(vp)
|
|
, vpParent(vp)
|
|
, vpDoc(vp->getDocument())
|
|
, editElement(element)
|
|
{
|
|
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::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() override
|
|
{
|
|
try {
|
|
vpParent->OnTopWhenSelected.setValue(onTopMode);
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
e.reportException();
|
|
}
|
|
}
|
|
|
|
bool allow(App::Document* doc, App::DocumentObject* obj, const char* subname) override
|
|
{
|
|
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");
|
|
auto 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);
|
|
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, color.a);
|
|
px.fill(c);
|
|
auto item = new QListWidgetItem(
|
|
QIcon(px),
|
|
QString::fromLatin1(Data::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, Base::Color> info;
|
|
int count = ui->elementList->count();
|
|
for (int i = 0; i < count; ++i) {
|
|
auto item = ui->elementList->item(i);
|
|
auto col = item->data(Qt::UserRole).value<QColor>();
|
|
std::string sub = qPrintable(item->data(Qt::UserRole + 1).value<QString>());
|
|
info.emplace(sub, Base::Color::fromValue<QColor>(col));
|
|
}
|
|
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.empty()) {
|
|
hiddenSub.clear();
|
|
ui->elementList->clear();
|
|
elements.clear();
|
|
apply();
|
|
}
|
|
}
|
|
|
|
void removeItems()
|
|
{
|
|
const auto items = ui->elementList->selectedItems();
|
|
for (auto item : items) {
|
|
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 (DialogOptions::dontUseNativeColorDialog()) {
|
|
cd.setOption(QColorDialog::DontUseNativeDialog);
|
|
}
|
|
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)) {
|
|
const auto items = ui->elementList->findItems(
|
|
QString::fromLatin1(msg.pSubName - editSub.size()),
|
|
Qt::MatchExactly
|
|
);
|
|
for (auto item : items) {
|
|
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(),
|
|
ResolveMode::NoResolve
|
|
)) {
|
|
if (sel.getFeatName() != editObj) {
|
|
continue;
|
|
}
|
|
for (auto& sub : sel.getSubNames()) {
|
|
if (boost::starts_with(sub, editSub)) {
|
|
sels[sub.c_str() + editSub.size()] = 1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
const auto items = ui->elementList->selectedItems();
|
|
for (auto item : items) {
|
|
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);
|
|
setupConnections();
|
|
|
|
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, ResolveMode::NoResolve);
|
|
|
|
// NOLINTBEGIN
|
|
// clang-format off
|
|
d->connectDelDoc = Application::Instance->signalDeleteDocument.connect(std::bind
|
|
(&ElementColors::slotDeleteDocument, this, sp::_1));
|
|
d->connectDelObj = Application::Instance->signalDeletedObject.connect(std::bind
|
|
(&ElementColors::slotDeleteObject, this, sp::_1));
|
|
// clang-format on
|
|
// NOLINTEND
|
|
|
|
d->populate();
|
|
}
|
|
|
|
ElementColors::~ElementColors()
|
|
{
|
|
d->connectDelDoc.disconnect();
|
|
d->connectDelObj.disconnect();
|
|
Selection().rmvSelectionGate();
|
|
}
|
|
|
|
void ElementColors::setupConnections()
|
|
{
|
|
// clang-format off
|
|
connect(d->ui->removeSelection, &QPushButton::clicked,
|
|
this, &ElementColors::onRemoveSelectionClicked);
|
|
connect(d->ui->addSelection, &QPushButton::clicked,
|
|
this, &ElementColors::onAddSelectionClicked);
|
|
connect(d->ui->removeAll, &QPushButton::clicked,
|
|
this, &ElementColors::onRemoveAllClicked);
|
|
connect(d->ui->elementList, &QListWidget::itemDoubleClicked,
|
|
this, &ElementColors::onElementListItemDoubleClicked);
|
|
connect(d->ui->elementList, &QListWidget::itemSelectionChanged,
|
|
this, &ElementColors::onElementListItemSelectionChanged);
|
|
connect(d->ui->elementList, &QListWidget::itemEntered,
|
|
this, &ElementColors::onElementListItemEntered);
|
|
connect(d->ui->recompute, &QCheckBox::clicked,
|
|
this, &ElementColors::onRecomputeClicked);
|
|
connect(d->ui->onTop, &QCheckBox::clicked,
|
|
this, &ElementColors::onTopClicked);
|
|
connect(d->ui->hideSelection, &QPushButton::clicked,
|
|
this, &ElementColors::onHideSelectionClicked);
|
|
connect(d->ui->boxSelect, &QPushButton::clicked,
|
|
this, &ElementColors::onBoxSelectClicked);
|
|
// clang-format on
|
|
}
|
|
|
|
void ElementColors::onRecomputeClicked(bool checked)
|
|
{
|
|
ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View"
|
|
);
|
|
hPart->SetBool("ColorRecompute", checked);
|
|
}
|
|
|
|
void ElementColors::onTopClicked(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::onRemoveSelectionClicked()
|
|
{
|
|
d->removeItems();
|
|
}
|
|
|
|
void ElementColors::onBoxSelectClicked()
|
|
{
|
|
auto cmd = Application::Instance->commandManager().getCommandByName("Std_BoxElementSelection");
|
|
if (cmd) {
|
|
cmd->invoke(0);
|
|
}
|
|
}
|
|
|
|
void ElementColors::onHideSelectionClicked()
|
|
{
|
|
auto sels = Selection().getSelectionEx(
|
|
d->editDoc.c_str(),
|
|
App::DocumentObject::getClassTypeId(),
|
|
ResolveMode::NoResolve
|
|
);
|
|
for (auto& sel : sels) {
|
|
if (d->editObj != sel.getFeatName()) {
|
|
continue;
|
|
}
|
|
const auto& subs = sel.getSubNames();
|
|
if (!subs.empty()) {
|
|
for (auto& sub : subs) {
|
|
if (boost::starts_with(sub, d->editSub)) {
|
|
auto name = Data::noElementName(sub.c_str() + d->editSub.size());
|
|
name += ViewProvider::hiddenMarker();
|
|
d->addItem(-1, name.c_str());
|
|
}
|
|
}
|
|
d->apply();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ElementColors::onAddSelectionClicked()
|
|
{
|
|
auto sels = Selection().getSelectionEx(
|
|
d->editDoc.c_str(),
|
|
App::DocumentObject::getClassTypeId(),
|
|
ResolveMode::NoResolve
|
|
);
|
|
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.empty()) {
|
|
auto color = d->items.front()->data(Qt::UserRole).value<QColor>();
|
|
QColorDialog cd(color, this);
|
|
cd.setOption(QColorDialog::ShowAlphaChannel);
|
|
if (DialogOptions::dontUseNativeColorDialog()) {
|
|
cd.setOption(QColorDialog::DontUseNativeDialog);
|
|
}
|
|
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::onRemoveAllClicked()
|
|
{
|
|
d->removeAll();
|
|
}
|
|
|
|
bool ElementColors::accept()
|
|
{
|
|
d->accept();
|
|
Application::Instance->setEditDocument(nullptr);
|
|
return true;
|
|
}
|
|
|
|
bool ElementColors::reject()
|
|
{
|
|
d->reset();
|
|
Application::Instance->setEditDocument(nullptr);
|
|
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.empty()) {
|
|
d->vp->partialRender({d->hiddenSub}, false);
|
|
d->hiddenSub.clear();
|
|
}
|
|
}
|
|
|
|
void ElementColors::onElementListItemEntered(QListWidgetItem* item)
|
|
{
|
|
std::string name(qPrintable(item->data(Qt::UserRole + 1).value<QString>()));
|
|
if (!d->hiddenSub.empty()) {
|
|
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() ? Gui::SelectionChanges::MsgSource::TreeView
|
|
: Gui::SelectionChanges::MsgSource::Internal
|
|
);
|
|
}
|
|
|
|
void ElementColors::onElementListItemSelectionChanged()
|
|
{
|
|
d->onSelectionChanged();
|
|
}
|
|
|
|
void ElementColors::onSelectionChanged(const SelectionChanges& msg)
|
|
{
|
|
d->onSelectionChanged(msg);
|
|
}
|
|
|
|
void ElementColors::onElementListItemDoubleClicked(QListWidgetItem* item)
|
|
{
|
|
d->editItem(this, item);
|
|
}
|
|
|
|
/* TRANSLATOR Gui::TaskElementColors */
|
|
|
|
TaskElementColors::TaskElementColors(ViewProviderDocumentObject* vp, bool noHide)
|
|
{
|
|
widget = new ElementColors(vp, noHide);
|
|
addTaskBox(widget);
|
|
}
|
|
|
|
TaskElementColors::~TaskElementColors() = default;
|
|
|
|
void TaskElementColors::open()
|
|
{}
|
|
|
|
void TaskElementColors::clicked(int id)
|
|
{
|
|
Q_UNUSED(id)
|
|
}
|
|
|
|
bool TaskElementColors::accept()
|
|
{
|
|
return widget->accept();
|
|
}
|
|
|
|
bool TaskElementColors::reject()
|
|
{
|
|
return widget->reject();
|
|
}
|
|
|
|
#include "moc_TaskElementColors.cpp"
|