// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2004 Werner Mayer * * * * 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 #include #include #include #include #include #include #include #include #include "Dialogs/DlgActionsImp.h" #include "ui_DlgActions.h" #include "Action.h" #include "Application.h" #include "BitmapFactory.h" #include "Command.h" #include "ShortcutManager.h" #include "ui_DlgChooseIcon.h" using namespace Gui::Dialog; /* TRANSLATOR Gui::Dialog::DlgCustomActionsImp */ /** * Constructs a DlgCustomActionsImp which is a child of 'parent', with the * name 'name' and widget flags set to 'f' * * The dialog will by default be modeless, unless you set 'modal' to * true to construct a modal dialog. */ DlgCustomActionsImp::DlgCustomActionsImp(QWidget* parent) : CustomizeActionPage(parent) , ui(new Ui_DlgCustomActions) { ui->setupUi(this); setupConnections(); // search for all macros std::string cMacroPath = App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") ->GetASCII("MacroPath", App::Application::getUserMacroDir().c_str()); QDir d(QString::fromUtf8(cMacroPath.c_str()), QLatin1String("*.FCMacro *.py")); for (unsigned int i = 0; i < d.count(); i++) { ui->actionMacros->insertItem(0, d[i], QVariant(false)); } QString systemMacroDirStr = QString::fromStdString(App::Application::getHomePath()) + QStringLiteral("Macro"); d = QDir(systemMacroDirStr, QLatin1String("*.FCMacro *.py")); if (d.exists()) { for (unsigned int i = 0; i < d.count(); i++) { ui->actionMacros->insertItem(0, d[i], QVariant(true)); } } QStringList labels; labels << tr("Icons") << tr("Macros"); ui->actionListWidget->setHeaderLabels(labels); ui->actionListWidget->header()->hide(); ui->actionListWidget->setIconSize(QSize(32, 32)); ui->actionListWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); showActions(); } /** Destroys the object and frees any allocated resources */ DlgCustomActionsImp::~DlgCustomActionsImp() { if (bChanged) { MacroCommand::save(); } } void DlgCustomActionsImp::setupConnections() { // clang-format off connect(ui->actionListWidget, &QTreeWidget::itemActivated, this, &DlgCustomActionsImp::onActionListWidgetItemActivated); connect(ui->buttonChoosePixmap, &QToolButton::clicked, this, &DlgCustomActionsImp::onButtonChoosePixmapClicked); connect(ui->buttonAddAction, &QPushButton::clicked, this, &DlgCustomActionsImp::onButtonAddActionClicked); connect(ui->buttonRemoveAction, &QPushButton::clicked, this, &DlgCustomActionsImp::onButtonRemoveActionClicked); connect(ui->buttonReplaceAction, &QPushButton::clicked, this, &DlgCustomActionsImp::onButtonReplaceActionClicked); // clang-format on } bool DlgCustomActionsImp::event(QEvent* e) { // clang-format off bool ok = QWidget::event(e); if (e->type() == QEvent::ParentChange || e->type() == QEvent::ParentAboutToChange) { QWidget* topLevel = this->parentWidget(); while (topLevel && !topLevel->inherits("QDialog")) topLevel = topLevel->parentWidget(); if ( topLevel ) { int index = topLevel->metaObject()->indexOfSignal( QMetaObject::normalizedSignature("addMacroAction(const QByteArray&)") ); if ( index >= 0 ) { if ( e->type() == QEvent::ParentChange ) { connect(this, SIGNAL(addMacroAction( const QByteArray& )), topLevel, SIGNAL(addMacroAction( const QByteArray& ))); connect(this, SIGNAL(removeMacroAction( const QByteArray& )), topLevel, SIGNAL(removeMacroAction( const QByteArray& ))); connect(this, SIGNAL(modifyMacroAction( const QByteArray& )), topLevel, SIGNAL(modifyMacroAction( const QByteArray& ))); } else { disconnect(this, SIGNAL(addMacroAction( const QByteArray& )), topLevel, SIGNAL(addMacroAction( const QByteArray& ))); disconnect(this, SIGNAL(removeMacroAction( const QByteArray& )), topLevel, SIGNAL(removeMacroAction( const QByteArray& ))); disconnect(this, SIGNAL(modifyMacroAction( const QByteArray& )), topLevel, SIGNAL(modifyMacroAction( const QByteArray& ))); } } } } // clang-format on return ok; } void DlgCustomActionsImp::onAddMacroAction(const QByteArray&) { bChanged = true; } void DlgCustomActionsImp::onRemoveMacroAction(const QByteArray& name) { bChanged = true; ShortcutManager::instance()->reset(name.constData()); } void DlgCustomActionsImp::onModifyMacroAction(const QByteArray&) { bChanged = true; } void DlgCustomActionsImp::showActions() { CommandManager& rclMan = Application::Instance->commandManager(); std::vector aclCurMacros = rclMan.getGroupCommands("Macros"); for (const auto& aclCurMacro : aclCurMacros) { auto item = new QTreeWidgetItem(ui->actionListWidget); QByteArray actionName = aclCurMacro->getName(); item->setData(1, Qt::UserRole, actionName); item->setText(1, QString::fromUtf8(aclCurMacro->getMenuText())); item->setSizeHint(0, QSize(32, 32)); if (aclCurMacro->getPixmap()) { item->setIcon(0, BitmapFactory().pixmap(aclCurMacro->getPixmap())); } } } void DlgCustomActionsImp::onActionListWidgetItemActivated(QTreeWidgetItem* item) { if (!item) { return; // no valid item } // search for the command in the manager and if necessary in the temporary created ones QByteArray actionName = item->data(1, Qt::UserRole).toByteArray(); CommandManager& rclMan = Application::Instance->commandManager(); Command* pCmd = rclMan.getCommandByName(actionName.constData()); auto pScript = dynamic_cast(pCmd); // if valid command if (pScript) { bool bFound = false; QString scriptName = QString::fromUtf8(pScript->getScriptName()); for (int i = 0; i < ui->actionMacros->count(); i++) { if (ui->actionMacros->itemText(i).startsWith(scriptName, Qt::CaseSensitive)) { bFound = true; ui->actionMacros->setCurrentIndex(i); break; } } if (!bFound) { QMessageBox::critical( this, tr("Macro not found"), tr("Could not find macro file '%1'").arg(scriptName) ); } // fill up labels with the command's data ui->actionWhatsThis->setText(QString::fromUtf8(pScript->getWhatsThis())); ui->actionMenu->setText(QString::fromUtf8(pScript->getMenuText())); ui->actionToolTip->setText(QString::fromUtf8(pScript->getToolTipText())); ui->actionStatus->setText(QString::fromUtf8(pScript->getStatusTip())); ui->actionAccel->setKeySequence(QKeySequence( ShortcutManager::instance()->getShortcut(actionName.constData(), pScript->getAccel()) )); ui->pixmapLabel->clear(); m_sPixmap.clear(); const char* name = pScript->getPixmap(); if (name && std::strlen(name) > 2) { QPixmap p = Gui::BitmapFactory().pixmap(pScript->getPixmap()); ui->pixmapLabel->setPixmap(p); m_sPixmap = QString::fromUtf8(name); // can also be a path } } } void DlgCustomActionsImp::onButtonAddActionClicked() { if (ui->actionMacros->currentText().isEmpty()) { QMessageBox::warning(this, tr("Empty macro"), tr("Specify the macro first")); return; } if (ui->actionMenu->text().isEmpty()) { QMessageBox::warning(this, tr("Empty text"), tr("Specify the menu text first")); return; } // search for the command in the manager CommandManager& rclMan = Application::Instance->commandManager(); QByteArray actionName = QString::fromStdString(rclMan.newMacroName()).toLatin1(); auto macro = new MacroCommand( actionName, ui->actionMacros->itemData(ui->actionMacros->currentIndex()).toBool() ); rclMan.addCommand(macro); // add new action auto item = new QTreeWidgetItem(ui->actionListWidget); item->setData(1, Qt::UserRole, actionName); item->setText(1, ui->actionMenu->text()); item->setSizeHint(0, QSize(32, 32)); item->setIcon(0, ui->pixmapLabel->pixmap(Qt::ReturnByValue)); // Convert input text into utf8 if (!ui->actionWhatsThis->text().isEmpty()) { macro->setWhatsThis(ui->actionWhatsThis->text().toUtf8()); } ui->actionWhatsThis->clear(); if (!ui->actionMacros->currentText().isEmpty()) { macro->setScriptName(ui->actionMacros->currentText().toUtf8()); } if (!ui->actionMenu->text().isEmpty()) { macro->setMenuText(ui->actionMenu->text().toUtf8()); } ui->actionMenu->clear(); if (!ui->actionToolTip->text().isEmpty()) { macro->setToolTipText(ui->actionToolTip->text().toUtf8()); } ui->actionToolTip->clear(); if (!ui->actionStatus->text().isEmpty()) { macro->setStatusTip(ui->actionStatus->text().toUtf8()); } ui->actionStatus->clear(); if (!m_sPixmap.isEmpty()) { macro->setPixmap(m_sPixmap.toLatin1()); } ui->pixmapLabel->clear(); m_sPixmap.clear(); if (!ui->actionAccel->isEmpty()) { QString text = ui->actionAccel->text(); ShortcutManager::instance()->setShortcut(actionName.constData(), text.toLatin1().constData()); } ui->actionAccel->clear(); // emit signal to notify the container widget Q_EMIT addMacroAction(actionName); } void DlgCustomActionsImp::onButtonReplaceActionClicked() { QTreeWidgetItem* item = ui->actionListWidget->currentItem(); if (!item) { QMessageBox::warning(this, tr("No item selected"), tr("Select a macro item first")); return; } if (ui->actionMenu->text().isEmpty()) { QMessageBox::warning(this, tr("Empty text"), tr("Specify the menu text first")); return; } // search for the command in the manager QByteArray actionName = item->data(1, Qt::UserRole).toByteArray(); item->setText(1, ui->actionMenu->text()); CommandManager& rclMan = Application::Instance->commandManager(); Command* pCmd = rclMan.getCommandByName(actionName.constData()); auto macro = dynamic_cast(pCmd); if (!macro) { return; } if (!ui->actionWhatsThis->text().isEmpty()) { macro->setWhatsThis(ui->actionWhatsThis->text().toUtf8()); } ui->actionWhatsThis->clear(); if (!ui->actionMacros->currentText().isEmpty()) { macro->setScriptName(ui->actionMacros->currentText().toUtf8()); } if (!ui->actionMenu->text().isEmpty()) { macro->setMenuText(ui->actionMenu->text().toUtf8()); } ui->actionMenu->clear(); if (!ui->actionToolTip->text().isEmpty()) { macro->setToolTipText(ui->actionToolTip->text().toUtf8()); } ui->actionToolTip->clear(); if (!ui->actionStatus->text().isEmpty()) { macro->setStatusTip(ui->actionStatus->text().toUtf8()); } ui->actionStatus->clear(); if (!m_sPixmap.isEmpty()) { macro->setPixmap(m_sPixmap.toLatin1()); } ui->pixmapLabel->clear(); m_sPixmap.clear(); if (!ui->actionAccel->isEmpty()) { QString text = ui->actionAccel->text(); macro->setAccel(text.toLatin1()); } ui->actionAccel->clear(); // check whether the macro is already in use Action* action = macro->getAction(); if (action) { // does all the text related stuff action->setText(QString::fromUtf8(macro->getMenuText())); action->setToolTip(QString::fromUtf8(macro->getToolTipText())); action->setWhatsThis(QString::fromUtf8(macro->getWhatsThis())); action->setStatusTip(QString::fromUtf8(macro->getStatusTip())); if (macro->getPixmap()) { action->setIcon(Gui::BitmapFactory().pixmap(macro->getPixmap())); } action->setShortcut( ShortcutManager::instance()->getShortcut(actionName.constData(), macro->getAccel()) ); } // emit signal to notify the container widget Q_EMIT modifyMacroAction(actionName); // call this at the end because it internally invokes the highlight method if (macro->getPixmap()) { item->setIcon(0, Gui::BitmapFactory().pixmap(macro->getPixmap())); } } void DlgCustomActionsImp::onButtonRemoveActionClicked() { // remove item from list view QTreeWidgetItem* item = ui->actionListWidget->currentItem(); if (!item) { return; } int current = ui->actionListWidget->indexOfTopLevelItem(item); ui->actionListWidget->takeTopLevelItem(current); QByteArray actionName = item->data(1, Qt::UserRole).toByteArray(); delete item; // if the command is registered in the manager just remove it CommandManager& rclMan = Application::Instance->commandManager(); std::vector aclCurMacros = rclMan.getGroupCommands("Macros"); for (auto& aclCurMacro : aclCurMacros) { if (actionName == aclCurMacro->getName()) { // emit signal to notify the container widget Q_EMIT removeMacroAction(actionName); // remove from manager and delete it immediately rclMan.removeCommand(aclCurMacro); break; } } } IconDialog::IconDialog(QWidget* parent) : QDialog(parent) , ui(new Ui_DlgChooseIcon) { ui->setupUi(this); ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // signals and slots connections connect(ui->listWidget, &QListWidget::itemClicked, this, &IconDialog::accept); connect(ui->addButton, &QPushButton::clicked, this, &IconDialog::onAddIconPath); QListWidgetItem* item; QStringList names = BitmapFactory().findIconFiles(); for (const auto& name : names) { item = new QListWidgetItem(ui->listWidget); item->setIcon(QIcon(BitmapFactory().pixmap((const char*)name.toUtf8()))); item->setText(QFileInfo(name).baseName()); item->setToolTip(name); } } IconDialog::~IconDialog() { delete ui; } QListWidgetItem* IconDialog::currentItem() const { return ui->listWidget->currentItem(); } void IconDialog::resizeEvent(QResizeEvent*) { ui->listWidget->setFlow(QListView::LeftToRight); } void IconDialog::onAddIconPath() { // Add the user defined paths Base::Reference group = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Bitmaps" ); std::vector paths = group->GetASCIIs("CustomPath"); QStringList pathList; for (const auto& path : paths) { pathList << QString::fromUtf8(path.c_str()); } IconFolders dlg(pathList, this); dlg.setWindowTitle(tr("Icon Folders")); if (dlg.exec()) { QStringList paths = dlg.getPaths(); // Write to user config group->Clear(); int index = 0; for (QStringList::iterator it = paths.begin(); it != paths.end(); ++it, ++index) { std::stringstream str; str << "CustomPath" << index; group->SetASCII(str.str().c_str(), (const char*)it->toUtf8()); } QStringList search = BitmapFactory().getPaths(); for (auto& it : search) { it = QDir::toNativeSeparators(it); } for (const auto& path : paths) { if (search.indexOf(path) < 0) { QStringList filters; QList formats = QImageReader::supportedImageFormats(); for (const auto& format : formats) { filters << QStringLiteral("*.%1").arg(QString::fromLatin1(format).toLower()); } QDir d(path); d.setNameFilters(filters); QFileInfoList fi = d.entryInfoList(); for (const auto& jt : fi) { QString file = jt.absoluteFilePath(); auto item = new QListWidgetItem(ui->listWidget); item->setIcon(QIcon(file)); item->setText(jt.baseName()); item->setToolTip(file); } BitmapFactory().addPath(path); } } } } void DlgCustomActionsImp::onButtonChoosePixmapClicked() { // create a dialog showing all pixmaps Gui::Dialog::IconDialog dlg(this); dlg.setModal(true); dlg.exec(); ui->pixmapLabel->clear(); m_sPixmap.clear(); if (dlg.result() == QDialog::Accepted) { QListWidgetItem* item = dlg.currentItem(); if (item) { m_sPixmap = item->text(); ui->pixmapLabel->setPixmap(item->icon().pixmap(QSize(32, 32))); } } } void DlgCustomActionsImp::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); ui->actionListWidget->clear(); showActions(); } QWidget::changeEvent(e); } IconFolders::IconFolders(const QStringList& paths, QWidget* parent) : QDialog(parent) , restart(false) , maxLines(10) { resize(600, 400); auto buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &IconFolders::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &IconFolders::reject); gridLayout = new QGridLayout(); auto mainLayout = new QGridLayout(this); mainLayout->addLayout(gridLayout, 0, 0, 1, 1); auto verticalSpacer = new QSpacerItem(20, 108, QSizePolicy::Minimum, QSizePolicy::Expanding); mainLayout->addItem(verticalSpacer, 1, 0, 1, 1); mainLayout->addWidget(buttonBox, 2, 0, 1, 1); // Add the user defined paths int numPaths = static_cast(paths.size()); int maxRow = this->maxLines; for (int row = 0; row < maxRow; row++) { auto edit = new QLineEdit(this); edit->setReadOnly(true); gridLayout->addWidget(edit, row, 0, 1, 1); auto removeButton = new QPushButton(this); removeButton->setIcon(BitmapFactory().iconFromTheme("list-remove")); gridLayout->addWidget(removeButton, row, 1, 1, 1); if (row < numPaths) { edit->setText(paths[row]); } else { edit->hide(); removeButton->hide(); } buttonMap.append(qMakePair(edit, removeButton)); connect(removeButton, &QPushButton::clicked, this, &IconFolders::removeFolder); } textLabel = new QLabel(this); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); textLabel->setText(tr("Add or remove custom icon folders")); addButton = new QPushButton(this); addButton->setIcon(BitmapFactory().iconFromTheme("list-add")); gridLayout->addWidget(textLabel, maxRow, 0, 1, 1); gridLayout->addWidget(addButton, maxRow, 1, 1, 1); connect(addButton, &QPushButton::clicked, this, &IconFolders::addFolder); if (numPaths >= this->maxLines) { addButton->setDisabled(true); } } IconFolders::~IconFolders() = default; void IconFolders::addFolder() { int countHidden = -1; QStringList paths; for (const auto& it : buttonMap) { if (it.first->isHidden()) { countHidden++; if (countHidden == 0) { QString dir = QFileDialog::getExistingDirectory( this, IconDialog::tr("Add icon folder"), QString() ); if (!dir.isEmpty() && paths.indexOf(dir) < 0) { QLineEdit* edit = it.first; edit->setVisible(true); edit->setText(dir); QPushButton* removeButton = it.second; removeButton->setVisible(true); } } } else { paths << QDir::toNativeSeparators(it.first->text()); } } if (countHidden <= 0) { addButton->setDisabled(true); } } void IconFolders::removeFolder() { if (!restart) { restart = true; QMessageBox::information( this, tr("Remove folder"), tr("Removing a folder only takes effect after an application restart") ); } addButton->setEnabled(true); auto remove = static_cast(sender()); QLineEdit* edit = nullptr; for (const auto& it : buttonMap) { if (it.second == remove) { edit = it.first; } else if (edit) { // move up the text of the line edits edit->setText(it.first->text()); if (it.first->isVisible()) { edit = it.first; remove = it.second; } else { edit->hide(); remove->hide(); break; } } } } QStringList IconFolders::getPaths() const { QStringList paths; for (const auto& it : buttonMap) { if (!it.first->isHidden()) { paths << QDir::toNativeSeparators(it.first->text()); } else { break; } } return paths; } #include "moc_DlgActionsImp.cpp"