Files
create/src/Gui/Action.cpp
Kacper Donat 34c85e26da Gui: Allow ActionGroups to not remember choice
This is fix to issue mentioned in the #11717, on discord and forum that
for smart dimension tool the chosen tool should not be remembered. This
will ensure that the "smart" tool is always visible on the toolbar and
other tools are accessible in case that such explicit choice is needed.
2023-12-18 17:51:48 +01:00

1386 lines
42 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 "PreferencePages/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 "WorkbenchManager.h"
#include "ShortcutManager.h"
#include "Tools.h"
using namespace Gui;
using namespace Gui::Dialog;
namespace sp = std::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)
, _rememberLast(true)
{
_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);
}
void ActionGroup::setRememberLast(bool remember)
{
_rememberLast = remember;
}
bool ActionGroup::doesRememberLast() const
{
return _rememberLast;
}
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)
{
if (_rememberLast) {
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());
}
// --------------------------------------------------------------------
WorkbenchComboBox::WorkbenchComboBox(QWidget* parent) : QComboBox(parent)
{
}
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::refreshList(QList<QAction*> actionList)
{
clear();
for (QAction* action : actionList) {
QIcon icon = action->icon();
if (icon.isNull()) {
this->addItem(action->text());
}
else {
this->addItem(icon, action->text());
}
if (action->isChecked()) {
this->setCurrentIndex(this->count() - 1);
}
}
}
/* TRANSLATOR Gui::WorkbenchGroup */
WorkbenchGroup::WorkbenchGroup ( Command* pcCmd, QObject * parent )
: ActionGroup( pcCmd, parent )
{
refreshWorkbenchList();
//NOLINTBEGIN
Application::Instance->signalRefreshWorkbenches.connect(std::bind(&WorkbenchGroup::refreshWorkbenchList, this));
//NOLINTEND
connect(getMainWindow(), &MainWindow::workbenchActivated,
this, &WorkbenchGroup::onWorkbenchActivated);
}
void WorkbenchGroup::addTo(QWidget *widget)
{
auto setupBox = [&](WorkbenchComboBox* box) {
box->setIconSize(QSize(16, 16));
box->setToolTip(toolTip());
box->setStatusTip(action()->statusTip());
box->setWhatsThis(action()->whatsThis());
box->refreshList(actions());
connect(this, &WorkbenchGroup::workbenchListRefreshed, box, &WorkbenchComboBox::refreshList);
connect(groupAction(), &QActionGroup::triggered, box, [this, box](QAction* action) {
box->setCurrentIndex(actions().indexOf(action));
});
connect(box, qOverload<int>(&WorkbenchComboBox::activated), this, [this](int index) {
actions()[index]->trigger();
});
};
if (widget->inherits("QToolBar")) {
auto* box = new WorkbenchComboBox(widget);
setupBox(box);
qobject_cast<QToolBar*>(widget)->addWidget(box);
}
else if (widget->inherits("QMenuBar")) {
auto* box = new WorkbenchComboBox(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(actions());
connect(this, &WorkbenchGroup::workbenchListRefreshed, this, [menu](QList<QAction*> actions) {
menu->clear();
menu->addActions(actions);
});
}
}
void WorkbenchGroup::refreshWorkbenchList()
{
QStringList enabled_wbs_list = DlgSettingsWorkbenchesImp::getEnabledWorkbenches();
// Clear the actions.
for (QAction* action : actions()) {
groupAction()->removeAction(action);
delete action;
}
std::string activeWbName = "";
Workbench* activeWB = WorkbenchManager::instance()->active();
if (activeWB)
activeWbName = activeWB->name();
// Create action list of enabled wb
int index = 0;
for (const auto& wbName : enabled_wbs_list) {
QString name = Application::Instance->workbenchMenuText(wbName);
QPixmap px = Application::Instance->workbenchIcon(wbName);
QString tip = Application::Instance->workbenchToolTip(wbName);
QAction* action = groupAction()->addAction(name);
action->setCheckable(true);
action->setData(QVariant(index)); // set the index
action->setObjectName(wbName);
action->setIcon(px);
action->setToolTip(tip);
action->setStatusTip(tr("Select the '%1' workbench").arg(name));
if (index < 9) {
action->setShortcut(QKeySequence(QString::fromUtf8("W,%1").arg(index + 1)));
}
if (wbName.toStdString() == activeWbName) {
action->setChecked(true);
}
index++;
}
// Signal to the widgets (WorkbenchComboBox & menu) to update the wb list
workbenchListRefreshed(actions());
}
void WorkbenchGroup::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());
for (QAction* action : actions()) {
if (action->objectName() == name) {
if (!action->isChecked()) {
action->trigger();
}
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 = std::make_unique<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) {
auto filePath = QString::fromUtf8(it.c_str());
if (QFileInfo::exists(filePath)) {
files.append(filePath);
}
}
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.size(); index++) {
msgMain += QStringLiteral(" %1").arg(accel_col[index]);
}
msgMain += QStringLiteral(" disabled because of conflicts with");
for (int index = 0; index < existingCommands.count(); index++) {
msgMain += QStringLiteral(" %1").arg(existingCommands[index]);
}
msgMain += QStringLiteral(" respectively.\nHint: In Preferences -> Python -> Macro ->"
" Recent Macros menu -> 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"