Files
create/src/Gui/Action.cpp
2023-03-31 14:12:27 +02:00

1490 lines
46 KiB
C++

/***************************************************************************
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <QActionEvent>
# include <QActionGroup>
# include <QApplication>
# include <QEvent>
# include <QMenu>
# include <QMessageBox>
# include <QRegularExpression>
# include <QScreen>
# include <QTimer>
# include <QToolBar>
# include <QToolButton>
# include <QToolTip>
# include <QMenuBar>
#endif
#include <Base/Exception.h>
#include <Base/Interpreter.h>
#include <Base/Tools.h>
#include <App/Document.h>
#include "Action.h"
#include "BitmapFactory.h"
#include "Command.h"
#include "DlgUndoRedo.h"
#include "DlgSettingsWorkbenchesImp.h"
#include "Document.h"
#include "EditorView.h"
#include "FileDialog.h"
#include "Macro.h"
#include "MainWindow.h"
#include "PythonEditor.h"
#include "UserSettings.h"
#include "WhatsThis.h"
#include "Widgets.h"
#include "Workbench.h"
#include "ShortcutManager.h"
#include "Tools.h"
using namespace Gui;
using namespace Gui::Dialog;
namespace bp = boost::placeholders;
/**
* Constructs an action called \a name with parent \a parent. It also stores a pointer
* to the command object.
*/
Action::Action (Command* pcCmd, QObject * parent)
: QObject(parent)
, _action(new QAction( this ))
, _pcCmd(pcCmd)
{
_action->setObjectName(QString::fromLatin1(_pcCmd->getName()));
_connection = connect(_action, &QAction::triggered, this, &Action::onActivated);
}
Action::Action (Command* pcCmd, QAction* action, QObject * parent)
: QObject(parent)
, _action(action)
, _pcCmd(pcCmd)
{
_action->setParent(this);
_action->setObjectName(QString::fromLatin1(_pcCmd->getName()));
_connection = connect(_action, &QAction::triggered, this, &Action::onActivated);
}
Action::~Action()
{
delete _action;
}
/**
* Adds this action to widget \a widget.
*/
void Action::addTo(QWidget *widget)
{
widget->addAction(_action);
}
/**
* Activates the command.
*/
void Action::onActivated ()
{
command()->invoke(0, Command::TriggerAction);
}
/**
* Sets whether the command is toggled.
*/
void Action::onToggled(bool toggle)
{
command()->invoke(toggle ? 1 : 0, Command::TriggerAction);
}
void Action::setCheckable(bool check)
{
if (check == _action->isCheckable()) {
return;
}
_action->setCheckable(check);
if (check) {
disconnect(_connection);
_connection = connect(_action, &QAction::toggled, this, &Action::onToggled);
}
else {
disconnect(_connection);
_connection = connect(_action, &QAction::triggered, this, &Action::onActivated);
}
}
void Action::setChecked(bool check, bool no_signal)
{
bool blocked = false;
if (no_signal) {
blocked = _action->blockSignals(true);
}
_action->setChecked(check);
if (no_signal) {
_action->blockSignals(blocked);
}
}
bool Action::isChecked() const
{
return _action->isChecked();
}
/**
* Sets whether the action is enabled.
*/
void Action::setEnabled(bool enable)
{
_action->setEnabled(enable);
}
bool Action::isEnabled() const
{
return _action->isEnabled();
}
void Action::setVisible(bool visible)
{
_action->setVisible(visible);
}
void Action::setShortcut(const QString & key)
{
_action->setShortcut(key);
setToolTip(_tooltip, _title);
}
QKeySequence Action::shortcut() const
{
return _action->shortcut();
}
void Action::setIcon (const QIcon & icon)
{
_action->setIcon(icon);
}
QIcon Action::icon () const
{
return _action->icon();
}
void Action::setStatusTip(const QString & text)
{
_action->setStatusTip(text);
}
QString Action::statusTip() const
{
return _action->statusTip();
}
void Action::setText(const QString & text)
{
_action->setText(text);
if (_title.isEmpty()) {
setToolTip(_tooltip);
}
}
QString Action::text() const
{
return _action->text();
}
void Action::setToolTip(const QString & text, const QString & title)
{
_tooltip = text;
_title = title;
_action->setToolTip(createToolTip(text,
title.isEmpty() ? _action->text() : title,
_action->font(),
_action->shortcut().toString(QKeySequence::NativeText),
command()));
}
QString Action::cleanTitle(const QString & title)
{
QString text(title);
// Deal with QAction title mnemonic
static QRegularExpression re(QStringLiteral("&(.)"));
text.replace(re, QStringLiteral("\\1"));
// Probably not a good idea to trim ending punctuation
#if 0
// Trim line ending punctuation
static QRegularExpression rePunct(QStringLiteral("[[:punct:]]+$"));
text.replace(rePunct, QString());
#endif
return text;
}
QString Action::commandToolTip(const Command *cmd, bool richFormat)
{
if (!cmd) {
return {};
}
if (richFormat) {
if (auto action = cmd->getAction()) {
return action->_action->toolTip();
}
}
QString title, tooltip;
if (dynamic_cast<const MacroCommand*>(cmd)) {
if (auto txt = cmd->getMenuText()) {
title = QString::fromUtf8(txt);
}
if (auto txt = cmd->getToolTipText()) {
tooltip = QString::fromUtf8(txt);
}
} else {
if (auto txt = cmd->getMenuText()) {
title = qApp->translate(cmd->className(), txt);
}
if (auto txt = cmd->getToolTipText()) {
tooltip = qApp->translate(cmd->className(), txt);
}
}
if (!richFormat) {
return tooltip;
}
return createToolTip(tooltip, title, QFont(), cmd->getShortcut(), cmd);
}
QString Action::commandMenuText(const Command *cmd)
{
if (!cmd) {
return {};
}
QString title;
if (auto action = cmd->getAction()) {
title = action->text();
}
else if (dynamic_cast<const MacroCommand*>(cmd)) {
if (auto txt = cmd->getMenuText()) {
title = QString::fromUtf8(txt);
}
} else {
if (auto txt = cmd->getMenuText()) {
title = qApp->translate(cmd->className(), txt);
}
}
if (title.isEmpty()) {
title = QString::fromUtf8(cmd->getName());
}
else {
title = cleanTitle(title);
}
return title;
}
QString Action::createToolTip(QString helpText,
const QString & title,
const QFont &font,
const QString &shortCut,
const Command *command)
{
QString text = cleanTitle(title);
if (text.isEmpty()) {
return helpText;
}
// The following code tries to make a more useful tooltip by inserting at
// the beginning of the tooltip the action title in bold followed by the
// shortcut.
//
// The long winding code is to deal with the fact that Qt will auto wrap
// a rich text tooltip but the width is too short. We can escape the auto
// wrappin using <p style='white-space:pre'>.
QString shortcut = shortCut;
if (!shortcut.isEmpty() && helpText.endsWith(shortcut)) {
helpText.resize(helpText.size() - shortcut.size());
}
if (!shortcut.isEmpty()) {
shortcut = QString::fromLatin1(" (%1)").arg(shortcut);
}
QString tooltip = QString::fromLatin1(
"<p style='white-space:pre; margin-bottom:0.5em;'><b>%1</b>%2</p>").arg(
text.toHtmlEscaped(), shortcut.toHtmlEscaped());
QString cmdName;
if (command && command->getName()) {
cmdName = QString::fromLatin1(command->getName());
if (auto groupcmd = dynamic_cast<const GroupCommand*>(command)) {
if (auto act = command->getAction()) {
int idx = act->property("defaultAction").toInt();
auto cmd = groupcmd->getCommand(idx);
if (cmd && cmd->getName()) {
cmdName = QStringLiteral("%1 (%2:%3)")
.arg(QString::fromLatin1(cmd->getName()), cmdName)
.arg(idx);
}
}
}
cmdName = QStringLiteral("<p style='white-space:pre; margin-top:0.5em;'><i>%1</i></p>")
.arg(cmdName.toHtmlEscaped());
}
if (!shortcut.isEmpty() && helpText.endsWith(shortcut)) {
helpText.resize(helpText.size() - shortcut.size());
}
if (helpText.isEmpty()
|| helpText == text
|| helpText == title)
{
return tooltip + cmdName;
}
if (Qt::mightBeRichText(helpText)) {
// already rich text, so let it be to avoid duplicated unwrapping
return tooltip + helpText + cmdName;
}
tooltip += QString::fromLatin1(
"<p style='white-space:pre; margin:0;'>");
// If the user supplied tooltip contains line break, we shall honour it.
if (helpText.indexOf(QLatin1Char('\n')) >= 0) {
tooltip += helpText.toHtmlEscaped() + QString::fromLatin1("</p>") ;
}
else {
// If not, try to end the non wrapping paragraph at some pre defined
// width, so that the following text can wrap at that width.
float tipWidth = 400;
QFontMetrics fm(font);
int width = QtTools::horizontalAdvance(fm, helpText);
if (width <= tipWidth) {
tooltip += helpText.toHtmlEscaped() + QString::fromLatin1("</p>") ;
}
else {
int index = tipWidth / width * helpText.size();
// Try to only break at white space
for(int i=0; i<50 && index<helpText.size(); ++i, ++index) {
if (helpText[index] == QLatin1Char(' ')) {
break;
}
}
tooltip += helpText.left(index).toHtmlEscaped()
+ QString::fromLatin1("</p>")
+ helpText.right(helpText.size()-index).trimmed().toHtmlEscaped();
}
}
return tooltip + cmdName;
}
QString Action::toolTip() const
{
return _tooltip;
}
void Action::setWhatsThis(const QString & text)
{
_action->setWhatsThis(text);
}
QString Action::whatsThis() const
{
return _action->whatsThis();
}
void Action::setMenuRole(QAction::MenuRole menuRole)
{
_action->setMenuRole(menuRole);
}
// --------------------------------------------------------------------
/**
* Constructs an action called \a name with parent \a parent. It also stores a pointer
* to the command object.
*/
ActionGroup::ActionGroup ( Command* pcCmd,QObject * parent)
: Action(pcCmd, parent)
, _group(nullptr)
, _dropDown(false)
, _isMode(false)
{
_group = new QActionGroup(this);
connect(_group, &QActionGroup::triggered, this, qOverload<QAction*>(&ActionGroup::onActivated));
connect(_group, &QActionGroup::hovered, this, &ActionGroup::onHovered);
}
ActionGroup::~ActionGroup()
{
delete _group;
}
/**
* Adds this action to widget \a w.
*/
void ActionGroup::addTo(QWidget *widget)
{
// When adding an action that has defined a menu then shortcuts
// of the menu actions don't work. To make this working we must
// set the menu explicitly. This means calling QAction::setMenu()
// and adding this action to the widget doesn't work.
if (_dropDown) {
if (widget->inherits("QMenu")) {
auto menu = new QMenu(widget);
QAction* item = qobject_cast<QMenu*>(widget)->addMenu(menu);
item->setMenuRole(action()->menuRole());
menu->setTitle(action()->text());
menu->addActions(groupAction()->actions());
QObject::connect(menu, &QMenu::aboutToShow, [this, menu]() {
Q_EMIT aboutToShow(menu);
});
QObject::connect(menu, &QMenu::aboutToHide, [this, menu]() {
Q_EMIT aboutToHide(menu);
});
}
else if (widget->inherits("QToolBar")) {
widget->addAction(action());
QToolButton* tb = widget->findChildren<QToolButton*>().constLast();
tb->setPopupMode(QToolButton::MenuButtonPopup);
tb->setObjectName(QString::fromLatin1("qt_toolbutton_menubutton"));
QList<QAction*> acts = groupAction()->actions();
auto menu = new QMenu(tb);
menu->addActions(acts);
tb->setMenu(menu);
QObject::connect(menu, &QMenu::aboutToShow, [this, menu]() {
Q_EMIT aboutToShow(menu);
});
QObject::connect(menu, &QMenu::aboutToHide, [this, menu]() {
Q_EMIT aboutToHide(menu);
});
}
else {
widget->addActions(groupAction()->actions()); // no drop-down
}
}
else {
widget->addActions(groupAction()->actions());
}
}
void ActionGroup::setEnabled( bool check )
{
Action::setEnabled(check);
groupAction()->setEnabled(check);
}
void ActionGroup::setDisabled (bool check)
{
Action::setEnabled(!check);
groupAction()->setDisabled(check);
}
void ActionGroup::setExclusive (bool check)
{
groupAction()->setExclusive(check);
}
bool ActionGroup::isExclusive() const
{
return groupAction()->isExclusive();
}
void ActionGroup::setVisible( bool check )
{
Action::setVisible(check);
groupAction()->setVisible(check);
}
QAction* ActionGroup::addAction(QAction* action)
{
int index = groupAction()->actions().size();
action = groupAction()->addAction(action);
action->setData(QVariant(index));
return action;
}
QAction* ActionGroup::addAction(const QString& text)
{
int index = groupAction()->actions().size();
QAction* action = groupAction()->addAction(text);
action->setData(QVariant(index));
return action;
}
QList<QAction*> ActionGroup::actions() const
{
return groupAction()->actions();
}
int ActionGroup::checkedAction() const
{
QAction* checked = groupAction()->checkedAction();
return checked ? checked->data().toInt() : -1;
}
void ActionGroup::setCheckedAction(int index)
{
auto acts = groupAction()->actions();
QAction* act = acts.at(index);
act->setChecked(true);
this->setIcon(act->icon());
if (!this->_isMode) {
this->setToolTip(act->toolTip(), act->text());
}
this->setProperty("defaultAction", QVariant(index));
}
/**
* Activates the command.
*/
void ActionGroup::onActivated ()
{
command()->invoke(this->property("defaultAction").toInt(), Command::TriggerAction);
}
void ActionGroup::onToggled(bool check)
{
Q_UNUSED(check)
onActivated();
}
/**
* Activates the command.
*/
void ActionGroup::onActivated (QAction* act)
{
int index = groupAction()->actions().indexOf(act);
this->setIcon(act->icon());
if (!this->_isMode) {
this->setToolTip(act->toolTip(), act->text());
}
this->setProperty("defaultAction", QVariant(index));
command()->invoke(index, Command::TriggerChildAction);
}
void ActionGroup::onHovered (QAction *act)
{
QToolTip::showText(QCursor::pos(), act->toolTip());
}
// --------------------------------------------------------------------
namespace Gui {
/**
* The WorkbenchActionEvent class is used to send an event of which workbench must be activated.
* We cannot activate the workbench directly as we will destroy the widget that emits the signal.
* @author Werner Mayer
*/
class WorkbenchActionEvent : public QEvent
{
public:
explicit WorkbenchActionEvent(QAction* act)
: QEvent(QEvent::User), act(act)
{ }
QAction* action() const
{ return act; }
private:
QAction* act;
Q_DISABLE_COPY(WorkbenchActionEvent)
};
}
WorkbenchComboBox::WorkbenchComboBox(WorkbenchGroup* wb, QWidget* parent) : QComboBox(parent), group(wb)
{
connect(this, qOverload<int>(&WorkbenchComboBox::activated),
this, qOverload<int>(&WorkbenchComboBox::onActivated));
connect(getMainWindow(), &MainWindow::workbenchActivated,
this, &WorkbenchComboBox::onWorkbenchActivated);
}
void WorkbenchComboBox::showPopup()
{
int rows = count();
if (rows > 0) {
int height = view()->sizeHintForRow(0);
int maxHeight = QApplication::primaryScreen()->size().height();
view()->setMinimumHeight(qMin(height * rows, maxHeight/2));
}
QComboBox::showPopup();
}
void WorkbenchComboBox::actionEvent ( QActionEvent* qae )
{
QAction *action = qae->action();
switch (qae->type()) {
case QEvent::ActionAdded:
{
if (action->isVisible()) {
QIcon icon = action->icon();
if (icon.isNull()) {
this->addItem(action->text(), action->data());
}
else {
this->addItem(icon, action->text(), action->data());
}
if (action->isChecked()) {
this->setCurrentIndex(action->data().toInt());
}
}
break;
}
case QEvent::ActionChanged:
{
QVariant data = action->data();
int index = this->findData(data);
// added a workbench
if (index < 0 && action->isVisible()) {
QIcon icon = action->icon();
if (icon.isNull())
this->addItem(action->text(), data);
else
this->addItem(icon, action->text(), data);
}
// removed a workbench
else if (index >=0 && !action->isVisible()) {
this->removeItem(index);
}
break;
}
case QEvent::ActionRemoved:
{
//Nothing needs to be done
break;
}
default:
break;
}
}
void WorkbenchComboBox::onActivated(int item)
{
// Send the event to the workbench group to delay the destruction of the emitting widget.
int index = itemData(item).toInt();
auto ev = new WorkbenchActionEvent(this->actions().at(index));
QApplication::postEvent(this->group, ev);
}
void WorkbenchComboBox::onActivated(QAction* action)
{
// set the according item to the action
QVariant data = action->data();
int index = this->findData(data);
setCurrentIndex(index);
}
void WorkbenchComboBox::onWorkbenchActivated(const QString& name)
{
// There might be more than only one instance of WorkbenchComboBox there.
// However, all of them share the same QAction objects. Thus, if the user
// has selected one it also gets checked. Then Application::activateWorkbench
// also invokes this slot method by calling the signal workbenchActivated in
// MainWindow. If calling activateWorkbench() from within the Python console
// the matching action must be set by calling this function.
// To avoid to recursively (but only one recursion level) call Application::
// activateWorkbench the method refreshWorkbenchList() shouldn't set the
// checked item.
//QVariant item = itemData(currentIndex());
QList<QAction*> act = actions();
for (QList<QAction*>::Iterator it = act.begin(); it != act.end(); ++it) {
if ((*it)->objectName() == name) {
if (/*(*it)->data() != item*/!(*it)->isChecked()) {
(*it)->trigger();
}
break;
}
}
}
/* TRANSLATOR Gui::WorkbenchGroup */
WorkbenchGroup::WorkbenchGroup ( Command* pcCmd, QObject * parent )
: ActionGroup( pcCmd, parent )
{
// Start a list with 50 elements but extend it when requested
for (int i=0; i<50; i++) {
QAction* action = groupAction()->addAction(QLatin1String(""));
action->setVisible(false);
action->setCheckable(true);
action->setData(QVariant(i)); // set the index
}
Application::Instance->signalActivateWorkbench.connect(boost::bind(&WorkbenchGroup::slotActivateWorkbench, this, bp::_1));
Application::Instance->signalAddWorkbench.connect(boost::bind(&WorkbenchGroup::slotAddWorkbench, this, bp::_1));
Application::Instance->signalRemoveWorkbench.connect(boost::bind(&WorkbenchGroup::slotRemoveWorkbench, this, bp::_1));
}
void WorkbenchGroup::addTo(QWidget *widget)
{
refreshWorkbenchList();
auto setupBox = [&](WorkbenchComboBox* box) {
box->setIconSize(QSize(16, 16));
box->setToolTip(toolTip());
box->setStatusTip(action()->statusTip());
box->setWhatsThis(action()->whatsThis());
box->addActions(groupAction()->actions());
connect(groupAction(), &QActionGroup::triggered, box, qOverload<QAction*>(&WorkbenchComboBox::onActivated));
};
if (widget->inherits("QToolBar")) {
auto* box = new WorkbenchComboBox(this, widget);
setupBox(box);
qobject_cast<QToolBar*>(widget)->addWidget(box);
}
else if (widget->inherits("QMenuBar")) {
auto* box = new WorkbenchComboBox(this, widget);
setupBox(box);
bool left = WorkbenchSwitcher::isLeftCorner(WorkbenchSwitcher::getValue());
qobject_cast<QMenuBar*>(widget)->setCornerWidget(box, left ? Qt::TopLeftCorner : Qt::TopRightCorner);
}
else if (widget->inherits("QMenu")) {
auto menu = qobject_cast<QMenu*>(widget);
menu = menu->addMenu(action()->text());
menu->addActions(groupAction()->actions());
}
}
void WorkbenchGroup::setWorkbenchData(int index, const QString& wb)
{
QList<QAction*> workbenches = groupAction()->actions();
QString name = Application::Instance->workbenchMenuText(wb);
QPixmap px = Application::Instance->workbenchIcon(wb);
QString tip = Application::Instance->workbenchToolTip(wb);
workbenches[index]->setObjectName(wb);
workbenches[index]->setIcon(px);
workbenches[index]->setText(name);
workbenches[index]->setToolTip(tip);
workbenches[index]->setStatusTip(tr("Select the '%1' workbench").arg(name));
workbenches[index]->setVisible(true);
if (index < 9) {
workbenches[index]->setShortcut(QKeySequence(QString::fromUtf8("W,%1").arg(index+1)));
}
}
void WorkbenchGroup::refreshWorkbenchList()
{
QStringList enabled_wbs_list = DlgSettingsWorkbenchesImp::getEnabledWorkbenches();
// Resize the action group.
QList<QAction*> workbenches = groupAction()->actions();
int numActions = workbenches.size();
int extend = enabled_wbs_list.size() - numActions;
if (extend > 0) {
for (int i=0; i<extend; i++) {
QAction* action = groupAction()->addAction(QLatin1String(""));
action->setCheckable(true);
action->setData(QVariant(numActions++)); // set the index
}
}
// Show all enabled wb
int index = 0;
for (const auto& it : enabled_wbs_list) {
setWorkbenchData(index++, it);
}
}
void WorkbenchGroup::customEvent( QEvent* event )
{
if (event->type() == QEvent::User) {
auto ce = static_cast<Gui::WorkbenchActionEvent*>(event);
ce->action()->trigger();
}
}
void WorkbenchGroup::slotActivateWorkbench(const char* /*name*/)
{
}
void WorkbenchGroup::slotAddWorkbench(const char* name)
{
QList<QAction*> workbenches = groupAction()->actions();
QAction* action = nullptr;
for (auto it : workbenches) {
if (!it->isVisible()) {
action = it;
break;
}
}
if (!action) {
int index = workbenches.size();
action = groupAction()->addAction(QLatin1String(""));
action->setCheckable(true);
action->setData(QVariant(index)); // set the index
}
QString wb = QString::fromLatin1(name);
QPixmap px = Application::Instance->workbenchIcon(wb);
QString text = Application::Instance->workbenchMenuText(wb);
QString tip = Application::Instance->workbenchToolTip(wb);
action->setIcon(px);
action->setObjectName(wb);
action->setText(text);
action->setToolTip(tip);
action->setStatusTip(tr("Select the '%1' workbench").arg(wb));
action->setVisible(true); // do this at last
}
void WorkbenchGroup::slotRemoveWorkbench(const char* name)
{
QString workbench = QString::fromLatin1(name);
QList<QAction*> workbenches = groupAction()->actions();
for (auto it : workbenches) {
if (it->objectName() == workbench) {
it->setObjectName(QString());
it->setIcon(QIcon());
it->setText(QString());
it->setToolTip(QString());
it->setStatusTip(QString());
it->setVisible(false); // do this at last
break;
}
}
}
// --------------------------------------------------------------------
class RecentFilesAction::Private: public ParameterGrp::ObserverType
{
public:
Private(const Private&) = delete;
Private(Private&&) = delete;
void operator= (const Private&) = delete;
void operator= (Private&&) = delete;
Private(RecentFilesAction *master, const char *path):master(master)
{
handle = App::GetApplication().GetParameterGroupByPath(path);
handle->Attach(this);
}
~Private() override
{
handle->Detach(this);
}
void OnChange(Base::Subject<const char*> & sub, const char *reason) override
{
Q_UNUSED(sub)
if (!updating && reason && strcmp(reason, "RecentFiles")==0) {
Base::StateLocker guard(updating);
master->restore();
}
}
void trySaveUserParameter()
{
// update the XML structure and save the user parameter to disk (#0001989)
bool saveParameter = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/General")->GetBool("SaveUserParameter", true);
if (saveParameter) {
saveUserParameter();
}
}
void saveUserParameter()
{
ParameterManager* parmgr = App::GetApplication().GetParameterSet("User parameter");
parmgr->SaveDocument(App::Application::Config()["UserParameter"].c_str());
}
public:
RecentFilesAction *master;
ParameterGrp::handle handle;
bool updating = false;
};
// --------------------------------------------------------------------
/* TRANSLATOR Gui::RecentFilesAction */
RecentFilesAction::RecentFilesAction ( Command* pcCmd, QObject * parent )
: ActionGroup( pcCmd, parent )
, visibleItems(4)
, maximumItems(20)
{
_pimpl.reset(new Private(this, "User parameter:BaseApp/Preferences/RecentFiles"));
restore();
}
RecentFilesAction::~RecentFilesAction()
{
_pimpl.reset(nullptr);
}
/** Adds the new item to the recent files. */
void RecentFilesAction::appendFile(const QString& filename)
{
// restore the list of recent files
QStringList files = this->files();
// if already inside remove and prepend it
files.removeAll(filename);
files.prepend(filename);
setFiles(files);
save();
_pimpl->trySaveUserParameter();
}
/**
* Set the list of recent files. For each item an action object is
* created and added to this action group.
*/
void RecentFilesAction::setFiles(const QStringList& files)
{
QList<QAction*> recentFiles = groupAction()->actions();
int numRecentFiles = std::min<int>(recentFiles.count(), files.count());
for (int index = 0; index < numRecentFiles; index++) {
QFileInfo fi(files[index]);
recentFiles[index]->setText(QString::fromLatin1("%1 %2").arg(index+1).arg(fi.fileName()));
recentFiles[index]->setStatusTip(tr("Open file %1").arg(files[index]));
recentFiles[index]->setToolTip(files[index]); // set the full name that we need later for saving
recentFiles[index]->setData(QVariant(index));
recentFiles[index]->setVisible(true);
}
// if less file names than actions
numRecentFiles = std::min<int>(numRecentFiles, this->visibleItems);
for (int index = numRecentFiles; index < recentFiles.count(); index++) {
recentFiles[index]->setVisible(false);
recentFiles[index]->setText(QString());
recentFiles[index]->setToolTip(QString());
}
}
/**
* Returns the list of defined recent files.
*/
QStringList RecentFilesAction::files() const
{
QStringList files;
QList<QAction*> recentFiles = groupAction()->actions();
for (int index = 0; index < recentFiles.count(); index++) {
QString file = recentFiles[index]->toolTip();
if (file.isEmpty()) {
break;
}
files.append(file);
}
return files;
}
void RecentFilesAction::activateFile(int id)
{
// restore the list of recent files
QStringList files = this->files();
if (id < 0 || id >= files.count()) {
return; // no valid item
}
QString filename = files[id];
QFileInfo fi(filename);
if (!fi.exists() || !fi.isFile()) {
QMessageBox::critical(getMainWindow(), tr("File not found"), tr("The file '%1' cannot be opened.").arg(filename));
files.removeAll(filename);
setFiles(files);
save();
}
else {
// invokes appendFile()
SelectModule::Dict dict = SelectModule::importHandler(filename);
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
Application::Instance->open(it.key().toUtf8(), it.value().toLatin1());
break;
}
}
}
void RecentFilesAction::resizeList(int size)
{
this->visibleItems = size;
int diff = this->visibleItems - this->maximumItems;
// create new items if needed
for (int i=0; i<diff; i++) {
groupAction()->addAction(QLatin1String(""))->setVisible(false);
}
setFiles(files());
}
/** Loads all recent files from the preferences. */
void RecentFilesAction::restore()
{
ParameterGrp::handle hGrp = _pimpl->handle;
// we want at least 20 items but we do only show the number of files
// that is defined in user parameters
this->visibleItems = hGrp->GetInt("RecentFiles", this->visibleItems);
int count = std::max<int>(this->maximumItems, this->visibleItems);
for (int i=0; i<count; i++) {
groupAction()->addAction(QLatin1String(""))->setVisible(false);
}
std::vector<std::string> MRU = hGrp->GetASCIIs("MRU");
QStringList files;
for(const auto& it : MRU) {
files.append(QString::fromUtf8(it.c_str()));
}
setFiles(files);
}
/** Saves all recent files to the preferences. */
void RecentFilesAction::save()
{
ParameterGrp::handle hGrp = _pimpl->handle;
int count = hGrp->GetInt("RecentFiles", this->visibleItems); // save number of files
hGrp->Clear();
// count all set items
QList<QAction*> recentFiles = groupAction()->actions();
int num = std::min<int>(count, recentFiles.count());
for (int index = 0; index < num; index++) {
QString key = QString::fromLatin1("MRU%1").arg(index);
QString value = recentFiles[index]->toolTip();
if (value.isEmpty()) {
break;
}
hGrp->SetASCII(key.toLatin1(), value.toUtf8());
}
Base::StateLocker guard(_pimpl->updating);
hGrp->SetInt("RecentFiles", count); // restore
}
// --------------------------------------------------------------------
/* TRANSLATOR Gui::RecentMacrosAction */
RecentMacrosAction::RecentMacrosAction ( Command* pcCmd, QObject * parent )
: ActionGroup( pcCmd, parent )
, visibleItems(4)
, maximumItems(20)
{
restore();
}
/** Adds the new item to the recent files. */
void RecentMacrosAction::appendFile(const QString& filename)
{
// restore the list of recent files
QStringList files = this->files();
// if already inside remove and prepend it
files.removeAll(filename);
files.prepend(filename);
setFiles(files);
save();
// update the XML structure and save the user parameter to disk (#0001989)
bool saveParameter = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/General")->GetBool("SaveUserParameter", true);
if (saveParameter) {
ParameterManager* parmgr = App::GetApplication().GetParameterSet("User parameter");
parmgr->SaveDocument(App::Application::Config()["UserParameter"].c_str());
}
}
/**
* Set the list of recent macro files. For each item an action object is
* created and added to this action group.
*/
void RecentMacrosAction::setFiles(const QStringList& files)
{
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
->GetGroup("Preferences")->GetGroup("RecentMacros");
this->shortcut_modifiers = hGrp->GetASCII("ShortcutModifiers","Ctrl+Shift+");
this->shortcut_count = std::min<int>(hGrp->GetInt("ShortcutCount",3),9);//max = 9, e.g. Ctrl+Shift+9
this->visibleItems = hGrp->GetInt("RecentMacros",12);
QList<QAction*> recentFiles = groupAction()->actions();
int numRecentFiles = std::min<int>(recentFiles.count(), files.count());
QStringList existingCommands;
auto accel_col = QString::fromStdString(shortcut_modifiers);
for (int index = 0; index < numRecentFiles; index++) {
QFileInfo fi(files[index]);
recentFiles[index]->setText(QString::fromLatin1("%1 %2").arg(index+1).arg(fi.completeBaseName()));
recentFiles[index]->setToolTip(files[index]); // set the full name that we need later for saving
recentFiles[index]->setData(QVariant(index));
QString accel(tr("none"));
if (index < shortcut_count){
auto accel_tmp = QString::fromStdString(shortcut_modifiers);
accel_tmp.append(QString::number(index+1,10)).toStdString();
auto check = Application::Instance->commandManager().checkAcceleratorForConflicts(qPrintable(accel_tmp));
if (check) {
recentFiles[index]->setShortcut(QKeySequence());
accel_col.append(accel_tmp);
existingCommands.append(QLatin1String(check->getName()));
}
else {
accel = accel_tmp;
recentFiles[index]->setShortcut(accel);
}
}
recentFiles[index]->setStatusTip(tr("Run macro %1 (Shift+click to edit) keyboard shortcut: %2").arg(files[index], accel));
recentFiles[index]->setVisible(true);
}
// if less file names than actions
numRecentFiles = std::min<int>(numRecentFiles, this->visibleItems);
for (int index = numRecentFiles; index < recentFiles.count(); index++) {
recentFiles[index]->setVisible(false);
recentFiles[index]->setText(QString());
recentFiles[index]->setToolTip(QString());
}
// Raise a single warning no matter how many conflicts
if (!existingCommands.isEmpty()) {
auto msgMain = QStringLiteral("Recent macros : keyboard shortcut(s)");
for (int index = 0; index < accel_col.count(); index++) {
msgMain = msgMain + QStringLiteral(" %1").arg(accel_col[index]);
}
msgMain = msgMain + QStringLiteral(" disabled because of conflicts with");
for (int index = 0; index < existingCommands.count(); index++) {
msgMain = msgMain + QStringLiteral(" %1").arg(existingCommands[index]);
}
msgMain = msgMain + QStringLiteral(" respectively.\nHint: In Preferences -> Macros -> Recent Macros -> Keyboard Modifiers"
" this should be Ctrl+Shift+ by default, if this is now blank then you should revert"
" it back to Ctrl+Shift+ by pressing both keys at the same time.");
Base::Console().Warning("%s\n", qPrintable(msgMain));
}
}
/**
* Returns the list of defined recent files.
*/
QStringList RecentMacrosAction::files() const
{
QStringList files;
QList<QAction*> recentFiles = groupAction()->actions();
for (int index = 0; index < recentFiles.count(); index++) {
QString file = recentFiles[index]->toolTip();
if (file.isEmpty()) {
break;
}
files.append(file);
}
return files;
}
void RecentMacrosAction::activateFile(int id)
{
// restore the list of recent files
QStringList files = this->files();
if (id < 0 || id >= files.count()) {
return; // no valid item
}
QString filename = files[id];
QFileInfo fi(filename);
if (!fi.exists() || !fi.isFile()) {
QMessageBox::critical(getMainWindow(), tr("File not found"), tr("The file '%1' cannot be opened.").arg(filename));
files.removeAll(filename);
setFiles(files);
}
else {
if (QApplication::keyboardModifiers() == Qt::ShiftModifier){ //open for editing on Shift+click
auto editor = new PythonEditor();
editor->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
auto edit = new PythonEditorView(editor, getMainWindow());
edit->setDisplayName(PythonEditorView::FileName);
edit->open(filename);
edit->resize(400, 300);
getMainWindow()->addWindow(edit);
getMainWindow()->appendRecentMacro(filename);
edit->setWindowTitle(fi.fileName());
} else { //execute macro on normal (non-shifted) click
try {
getMainWindow()->appendRecentMacro(fi.filePath());
Application::Instance->macroManager()->run(Gui::MacroManager::File, fi.filePath().toUtf8());
// after macro run recalculate the document
if (Application::Instance->activeDocument()) {
Application::Instance->activeDocument()->getDocument()->recompute();
}
}
catch (const Base::SystemExitException&) {
// handle SystemExit exceptions
Base::PyGILStateLocker locker;
Base::PyException exc;
exc.ReportException();
}
}
}
}
void RecentMacrosAction::resizeList(int size)
{
this->visibleItems = size;
int diff = this->visibleItems - this->maximumItems;
// create new items if needed
for (int i=0; i<diff; i++) {
groupAction()->addAction(QLatin1String(""))->setVisible(false);
}
setFiles(files());
}
/** Loads all recent files from the preferences. */
void RecentMacrosAction::restore()
{
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
->GetGroup("Preferences")->GetGroup("RecentMacros");
for (int i=groupAction()->actions().size(); i<this->maximumItems; i++) {
groupAction()->addAction(QLatin1String(""))->setVisible(false);
}
resizeList(hGrp->GetInt("RecentMacros"));
std::vector<std::string> MRU = hGrp->GetASCIIs("MRU");
QStringList files;
for (auto& filename : MRU) {
files.append(QString::fromUtf8(filename.c_str()));
}
setFiles(files);
}
/** Saves all recent files to the preferences. */
void RecentMacrosAction::save()
{
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
->GetGroup("Preferences")->GetGroup("RecentMacros");
int count = hGrp->GetInt("RecentMacros", this->visibleItems); // save number of files
hGrp->Clear();
// count all set items
QList<QAction*> recentFiles = groupAction()->actions();
int num = std::min<int>(count, recentFiles.count());
for (int index = 0; index < num; index++) {
QString key = QString::fromLatin1("MRU%1").arg(index);
QString value = recentFiles[index]->toolTip();
if (value.isEmpty()) {
break;
}
hGrp->SetASCII(key.toLatin1(), value.toUtf8());
}
hGrp->SetInt("RecentMacros", count); // restore
hGrp->SetInt("ShortcutCount", this->shortcut_count);
hGrp->SetASCII("ShortcutModifiers",this->shortcut_modifiers.c_str());
}
// --------------------------------------------------------------------
UndoAction::UndoAction (Command* pcCmd,QObject * parent)
: Action(pcCmd, parent)
{
_toolAction = new QAction(this);
_toolAction->setMenu(new UndoDialog());
connect(_toolAction, &QAction::triggered, this, &UndoAction::onActivated);
}
UndoAction::~UndoAction()
{
QMenu* menu = _toolAction->menu();
delete menu;
delete _toolAction;
}
void UndoAction::addTo (QWidget * widget)
{
if (widget->inherits("QToolBar")) {
actionChanged();
connect(action(), &QAction::changed, this, &UndoAction::actionChanged);
widget->addAction(_toolAction);
}
else {
widget->addAction(action());
}
}
void UndoAction::actionChanged()
{
// Do NOT set the shortcut again for _toolAction since this is already
// reserved for _action. Otherwise we get an ambiguity of it with the
// result that it doesn't work anymore.
_toolAction->setText(action()->text());
_toolAction->setToolTip(action()->toolTip());
_toolAction->setStatusTip(action()->statusTip());
_toolAction->setWhatsThis(action()->whatsThis());
_toolAction->setIcon(action()->icon());
}
void UndoAction::setEnabled(bool check)
{
Action::setEnabled(check);
_toolAction->setEnabled(check);
}
void UndoAction::setVisible(bool check)
{
Action::setVisible(check);
_toolAction->setVisible(check);
}
// --------------------------------------------------------------------
RedoAction::RedoAction ( Command* pcCmd,QObject * parent )
: Action(pcCmd, parent)
{
_toolAction = new QAction(this);
_toolAction->setMenu(new RedoDialog());
connect(_toolAction, &QAction::triggered, this, &RedoAction::onActivated);
}
RedoAction::~RedoAction()
{
QMenu* menu = _toolAction->menu();
delete menu;
delete _toolAction;
}
void RedoAction::addTo ( QWidget * widget )
{
if (widget->inherits("QToolBar")) {
actionChanged();
connect(action(), &QAction::changed, this, &RedoAction::actionChanged);
widget->addAction(_toolAction);
}
else {
widget->addAction(action());
}
}
void RedoAction::actionChanged()
{
// Do NOT set the shortcut again for _toolAction since this is already
// reserved for _action. Otherwise we get an ambiguity of it with the
// result that it doesn't work anymore.
_toolAction->setText(action()->text());
_toolAction->setToolTip(action()->toolTip());
_toolAction->setStatusTip(action()->statusTip());
_toolAction->setWhatsThis(action()->whatsThis());
_toolAction->setIcon(action()->icon());
}
void RedoAction::setEnabled ( bool check )
{
Action::setEnabled(check);
_toolAction->setEnabled(check);
}
void RedoAction::setVisible ( bool check )
{
Action::setVisible(check);
_toolAction->setVisible(check);
}
// --------------------------------------------------------------------
DockWidgetAction::DockWidgetAction ( Command* pcCmd, QObject * parent )
: Action(pcCmd, parent)
, _menu(nullptr)
{
}
DockWidgetAction::~DockWidgetAction()
{
delete _menu;
}
void DockWidgetAction::addTo ( QWidget * widget )
{
if (!_menu) {
_menu = new QMenu();
action()->setMenu(_menu);
getMainWindow()->setDockWindowMenu(_menu);
}
widget->addAction(action());
}
// --------------------------------------------------------------------
ToolBarAction::ToolBarAction ( Command* pcCmd, QObject * parent )
: Action(pcCmd, parent)
, _menu(nullptr)
{
}
ToolBarAction::~ToolBarAction()
{
delete _menu;
}
void ToolBarAction::addTo ( QWidget * widget )
{
if (!_menu) {
_menu = new QMenu();
action()->setMenu(_menu);
getMainWindow()->setToolBarMenu(_menu);
}
widget->addAction(action());
}
// --------------------------------------------------------------------
WindowAction::WindowAction ( Command* pcCmd, QObject * parent )
: ActionGroup(pcCmd, parent)
, _menu(nullptr)
{
}
void WindowAction::addTo ( QWidget * widget )
{
auto menu = qobject_cast<QMenu*>(widget);
if (!menu) {
if (!_menu) {
_menu = new QMenu();
action()->setMenu(_menu);
_menu->addActions(groupAction()->actions());
getMainWindow()->setWindowsMenu(_menu);
}
widget->addAction(action());
}
else {
menu->addActions(groupAction()->actions());
getMainWindow()->setWindowsMenu(menu);
}
}
#include "moc_Action.cpp"