2249 lines
66 KiB
C++
2249 lines
66 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* 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 <Inventor/SbSphere.h>
|
|
#include <Inventor/actions/SoGetBoundingBoxAction.h>
|
|
#include <Inventor/nodes/SoOrthographicCamera.h>
|
|
#include <sstream>
|
|
#include <QApplication>
|
|
#include <QByteArray>
|
|
#include <QDir>
|
|
#include <QKeySequence>
|
|
#include <QMessageBox>
|
|
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
|
|
#include <FCConfig.h>
|
|
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <App/AutoTransaction.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Exception.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/PyObjectBase.h>
|
|
#include <Base/Tools.h>
|
|
|
|
#include "Command.h"
|
|
#include "Action.h"
|
|
#include "Application.h"
|
|
#include "BitmapFactory.h"
|
|
#include "Control.h"
|
|
#include "Dialogs/DlgUndoRedo.h"
|
|
#include "Document.h"
|
|
#include "frameobject.h"
|
|
#include "Macro.h"
|
|
#include "MainWindow.h"
|
|
#include "Python.h"
|
|
#include "Selection.h"
|
|
#include "View3DInventor.h"
|
|
#include "View3DInventorViewer.h"
|
|
#include "ViewProviderLink.h"
|
|
#include "WaitCursor.h"
|
|
#include "WhatsThis.h"
|
|
#include "WorkbenchManager.h"
|
|
#include "Workbench.h"
|
|
#include "ShortcutManager.h"
|
|
|
|
|
|
FC_LOG_LEVEL_INIT("Command", true, true)
|
|
|
|
using Base::Interpreter;
|
|
using namespace Gui;
|
|
using namespace Gui::Dialog;
|
|
using namespace Gui::DockWnd;
|
|
|
|
/** \defgroup commands Command Framework
|
|
\ingroup GUI
|
|
\brief Structure for registering commands to the FreeCAD system
|
|
* \section Overview
|
|
* In GUI applications many commands can be invoked via a menu item, a toolbar button or an
|
|
accelerator key. The answer of Qt to master this
|
|
* challenge is the class \a QAction. A QAction object can be added to a popup menu or a toolbar and
|
|
keep the state of the menu item and
|
|
* the toolbar button synchronized.
|
|
*
|
|
* For example, if the user clicks the menu item of a toggle action then the toolbar button gets
|
|
also pressed
|
|
* and vice versa. For more details refer to your Qt documentation.
|
|
*
|
|
* \section Drawbacks
|
|
* Since QAction inherits QObject and emits the \a triggered() signal or \a toggled() signal for
|
|
toggle actions it is very convenient to connect
|
|
* these signals e.g. with slots of your MainWindow class. But this means that for every action an
|
|
appropriate slot of MainWindow is necessary
|
|
* and leads to an inflated MainWindow class. Furthermore, it's simply impossible to provide plugins
|
|
that may also need special slots -- without
|
|
* changing the MainWindow class.
|
|
*
|
|
* \section wayout Way out
|
|
* To solve these problems we have introduced the command framework to decouple QAction and
|
|
MainWindow. The base classes of the framework are
|
|
* \a Gui::CommandBase and \a Gui::Action that represent the link between Qt's QAction world and the
|
|
FreeCAD's command world.
|
|
*
|
|
* The Action class holds a pointer to QAction and CommandBase and acts as a mediator and -- to save
|
|
memory -- that gets created
|
|
* (@ref Gui::CommandBase::createAction()) not before it is added (@ref Gui::Command::addTo()) to a
|
|
menu or toolbar.
|
|
*
|
|
* Now, the implementation of the slots of MainWindow can be done in the method \a activated() of
|
|
subclasses of Command instead.
|
|
*
|
|
* For example, the implementation of the "Open file" command can be done as follows.
|
|
* \code
|
|
* class OpenCommand : public Command
|
|
* {
|
|
* public:
|
|
* OpenCommand() : Command("Std_Open")
|
|
* {
|
|
* // set up menu text, status tip, ...
|
|
* sMenuText = "&Open";
|
|
* sToolTipText = "Open a file";
|
|
* sWhatsThis = "Open a file";
|
|
* sStatusTip = "Open a file";
|
|
* sPixmap = "Open"; // name of a registered pixmap
|
|
* sAccel = "Shift+P"; // or "P" or "P, L" or "Ctrl+X, Ctrl+C" for a sequence
|
|
* }
|
|
* protected:
|
|
* void activated(int)
|
|
* {
|
|
* QString filter ... // make a filter of all supported file formats
|
|
* QStringList FileList = QFileDialog::getOpenFileNames( filter,QString(), getMainWindow() );
|
|
* for ( QStringList::Iterator it = FileList.begin(); it != FileList.end(); ++it ) {
|
|
* getGuiApplication()->open((*it).latin1());
|
|
* }
|
|
* }
|
|
* };
|
|
* \endcode
|
|
* An instance of \a OpenCommand must be created and added to the \ref Gui::CommandManager to make
|
|
the class known to FreeCAD.
|
|
* To see how menus and toolbars can be built go to the @ref workbench.
|
|
*
|
|
* @see Gui::Command, Gui::CommandManager
|
|
*/
|
|
|
|
// list of modules already loaded by a command (not issue again for macro cleanness)
|
|
std::set<std::string> alreadyLoadedModule;
|
|
|
|
class StringCache
|
|
{
|
|
public:
|
|
static const char* New(const char* str)
|
|
{
|
|
using StringList = std::list<std::string>;
|
|
static StringList strings;
|
|
strings.emplace_back(str);
|
|
return strings.back().c_str();
|
|
}
|
|
};
|
|
|
|
CommandBase::CommandBase(
|
|
const char* sMenu,
|
|
const char* sToolTip,
|
|
const char* sWhat,
|
|
const char* sStatus,
|
|
const char* sPixmap,
|
|
const char* sAcc
|
|
)
|
|
: sMenuText(sMenu)
|
|
, sToolTipText(sToolTip)
|
|
, sWhatsThis(sWhat ? sWhat : sToolTip)
|
|
, sStatusTip(sStatus ? sStatus : sToolTip)
|
|
, sPixmap(sPixmap)
|
|
, sAccel(sAcc)
|
|
, _pcAction(nullptr)
|
|
{}
|
|
|
|
CommandBase::~CommandBase()
|
|
{
|
|
// Note: The Action object becomes a children of MainWindow which gets destroyed _before_ the
|
|
// command manager hence before any command object. So the action pointer is a dangling pointer
|
|
// at this state.
|
|
|
|
// Command can be destroyed before the MainWindow, for example, dynamic
|
|
// command created (and later deleted) by user for a pie menu.
|
|
if (getMainWindow()) {
|
|
delete _pcAction;
|
|
}
|
|
}
|
|
|
|
Action* CommandBase::getAction() const
|
|
{
|
|
return _pcAction;
|
|
}
|
|
|
|
Action* CommandBase::createAction()
|
|
{
|
|
// does nothing
|
|
return nullptr;
|
|
}
|
|
|
|
void CommandBase::setMenuText(const char* s)
|
|
{
|
|
this->sMenuText = StringCache::New(s);
|
|
}
|
|
|
|
void CommandBase::setToolTipText(const char* s)
|
|
{
|
|
this->sToolTipText = StringCache::New(s);
|
|
}
|
|
|
|
void CommandBase::setStatusTip(const char* s)
|
|
{
|
|
this->sStatusTip = StringCache::New(s);
|
|
}
|
|
|
|
void CommandBase::setWhatsThis(const char* s)
|
|
{
|
|
this->sWhatsThis = StringCache::New(s);
|
|
}
|
|
|
|
void CommandBase::setPixmap(const char* s)
|
|
{
|
|
this->sPixmap = StringCache::New(s);
|
|
}
|
|
|
|
void CommandBase::setAccel(const char* s)
|
|
{
|
|
this->sAccel = StringCache::New(s);
|
|
}
|
|
|
|
//===========================================================================
|
|
// Command
|
|
//===========================================================================
|
|
|
|
/* TRANSLATOR Gui::Command */
|
|
|
|
Command::Command(const char* name)
|
|
: CommandBase(nullptr)
|
|
, sName(name)
|
|
, sHelpUrl(nullptr)
|
|
{
|
|
sAppModule = "FreeCAD";
|
|
sGroup = "Standard";
|
|
eType = AlterDoc | Alter3DView | AlterSelection;
|
|
bEnabled = true;
|
|
bCanLog = true;
|
|
}
|
|
|
|
Command::~Command() = default;
|
|
|
|
void Command::setShortcut(const QString& shortcut)
|
|
{
|
|
if (_pcAction) {
|
|
_pcAction->setShortcut(shortcut);
|
|
}
|
|
}
|
|
|
|
QString Command::getShortcut() const
|
|
{
|
|
if (_pcAction) {
|
|
return _pcAction->shortcut().toString();
|
|
}
|
|
return ShortcutManager::instance()->getShortcut(getName());
|
|
}
|
|
|
|
bool Command::isViewOfType(Base::Type t) const
|
|
{
|
|
Gui::Document* d = getGuiApplication()->activeDocument();
|
|
if (!d) {
|
|
return false;
|
|
}
|
|
Gui::BaseView* v = d->getActiveView();
|
|
if (!v) {
|
|
return false;
|
|
}
|
|
if (v->isDerivedFrom(t)) {
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Command::initAction()
|
|
{
|
|
if (!_pcAction) {
|
|
_pcAction = createAction();
|
|
#ifdef FC_DEBUG
|
|
// Accelerator conflict can now be dynamically resolved in ShortcutManager
|
|
//
|
|
// printConflictingAccelerators();
|
|
#endif
|
|
setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel()));
|
|
testActive();
|
|
}
|
|
}
|
|
|
|
void Command::addTo(QWidget* pcWidget)
|
|
{
|
|
initAction();
|
|
_pcAction->addTo(pcWidget);
|
|
}
|
|
|
|
void Command::addToGroup(ActionGroup* group, bool checkable)
|
|
{
|
|
addToGroup(group);
|
|
_pcAction->setCheckable(checkable);
|
|
}
|
|
|
|
void Command::addToGroup(ActionGroup* group)
|
|
{
|
|
initAction();
|
|
group->addAction(_pcAction->findChild<QAction*>());
|
|
}
|
|
|
|
Application* Command::getGuiApplication()
|
|
{
|
|
return Application::Instance;
|
|
}
|
|
|
|
App::Document* Command::getActiveDocument() const
|
|
{
|
|
Gui::Document* doc = getActiveGuiDocument();
|
|
return doc ? doc->getDocument() : nullptr;
|
|
}
|
|
|
|
Gui::Document* Command::getActiveGuiDocument() const
|
|
{
|
|
return getGuiApplication()->activeDocument();
|
|
}
|
|
|
|
App::Document* Command::getDocument(const char* Name) const
|
|
{
|
|
if (Name) {
|
|
return App::GetApplication().getDocument(Name);
|
|
}
|
|
|
|
return getActiveDocument();
|
|
}
|
|
|
|
App::DocumentObject* Command::getObject(const char* Name) const
|
|
{
|
|
App::Document* pDoc = getDocument();
|
|
return pDoc ? pDoc->getObject(Name) : nullptr;
|
|
}
|
|
|
|
int Command::_busy;
|
|
|
|
class PendingLine
|
|
{
|
|
public:
|
|
PendingLine(MacroManager::LineType type, const char* line)
|
|
{
|
|
Application::Instance->macroManager()->addPendingLine(type, line);
|
|
}
|
|
~PendingLine()
|
|
{
|
|
cancel();
|
|
}
|
|
void cancel()
|
|
{
|
|
Application::Instance->macroManager()->addPendingLine(MacroManager::Cmt, nullptr);
|
|
}
|
|
};
|
|
|
|
class CommandTrigger
|
|
{
|
|
public:
|
|
CommandTrigger(Command::TriggerSource& trigger, Command::TriggerSource source)
|
|
: trigger(trigger)
|
|
, saved(trigger)
|
|
{
|
|
trigger = source;
|
|
}
|
|
|
|
~CommandTrigger()
|
|
{
|
|
trigger = saved;
|
|
}
|
|
|
|
private:
|
|
Command::TriggerSource& trigger;
|
|
Command::TriggerSource saved;
|
|
};
|
|
|
|
void Command::setupCheckable(int iMsg)
|
|
{
|
|
QAction* action = nullptr;
|
|
auto pcActionGroup = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
if (pcActionGroup) {
|
|
QList<QAction*> a = pcActionGroup->actions();
|
|
assert(iMsg < a.size());
|
|
action = a[iMsg];
|
|
}
|
|
else {
|
|
action = _pcAction->action();
|
|
}
|
|
|
|
if (!action) {
|
|
return;
|
|
}
|
|
|
|
bool checkable = action->isCheckable();
|
|
_pcAction->setCheckable(checkable);
|
|
if (checkable) {
|
|
bool checked = false;
|
|
switch (triggerSource()) {
|
|
case TriggerNone:
|
|
checked = !action->isChecked();
|
|
break;
|
|
case TriggerAction:
|
|
checked = _pcAction->isChecked();
|
|
break;
|
|
case TriggerChildAction:
|
|
checked = action->isChecked();
|
|
break;
|
|
}
|
|
bool blocked = action->blockSignals(true);
|
|
action->setChecked(checked);
|
|
action->blockSignals(blocked);
|
|
if (action != _pcAction->action()) {
|
|
_pcAction->setBlockedChecked(checked);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Command::invoke(int i, TriggerSource trigger)
|
|
{
|
|
CommandTrigger cmdTrigger(_trigger, trigger);
|
|
if (displayText.empty()) {
|
|
displayText = getMenuText() ? getMenuText() : "";
|
|
boost::replace_all(displayText, "&", "");
|
|
if (displayText.empty()) {
|
|
displayText = getName();
|
|
}
|
|
}
|
|
|
|
// Do not query _pcAction since it isn't created necessarily
|
|
#ifdef FC_LOGUSERACTION
|
|
Base::Console().log("CmdG: %s\n", sName);
|
|
#endif
|
|
|
|
_invoke(i, bCanLog && !_busy);
|
|
}
|
|
|
|
void Command::_invoke(int id, bool disablelog)
|
|
{
|
|
try {
|
|
// Because Transaction now captures ViewObject changes, auto named
|
|
// transaction is disabled here to avoid too many unnecessary transactions.
|
|
//
|
|
App::AutoTransaction committer(nullptr, true);
|
|
|
|
// set the application module type for the macro
|
|
getGuiApplication()->macroManager()->setModule(sAppModule);
|
|
|
|
std::unique_ptr<LogDisabler> logdisabler;
|
|
if (disablelog) {
|
|
logdisabler = std::make_unique<LogDisabler>();
|
|
}
|
|
|
|
// check if it really works NOW (could be a delay between click deactivation of the button)
|
|
if (isActive()) {
|
|
auto manager = getGuiApplication()->macroManager();
|
|
auto editDoc = getGuiApplication()->editDocument();
|
|
|
|
if (!logdisabler) {
|
|
activated(id);
|
|
}
|
|
else {
|
|
Gui::SelectionLogDisabler seldisabler;
|
|
auto lines = manager->getLines();
|
|
std::ostringstream ss;
|
|
ss << "### Begin command " << sName;
|
|
// Add a pending line to mark the start of a command
|
|
PendingLine pending(MacroManager::Cmt, ss.str().c_str());
|
|
ss.str("");
|
|
|
|
activated(id);
|
|
|
|
if (manager->getLines() == lines) {
|
|
// This command does not record any lines, lets do it for
|
|
// it. The above LogDisabler is to prevent nested command
|
|
// logging, i.e. we only auto log the first invoking
|
|
// command.
|
|
|
|
// Cancel the above pending line first
|
|
pending.cancel();
|
|
ss << "Gui.runCommand('" << sName << "'," << id << ')';
|
|
manager->addLine(MacroManager::Gui, ss.str().c_str());
|
|
}
|
|
else {
|
|
// In case the command has any output to the console, lets
|
|
// mark the end of the command here
|
|
ss << "### End command " << sName;
|
|
manager->addLine(MacroManager::Cmt, ss.str().c_str());
|
|
}
|
|
}
|
|
|
|
getMainWindow()->updateActions();
|
|
|
|
// If this command starts an editing, let the transaction persist
|
|
if (!editDoc && getGuiApplication()->editDocument()) {
|
|
committer.setEnable(false);
|
|
}
|
|
}
|
|
}
|
|
catch (const Base::SystemExitException&) {
|
|
throw;
|
|
}
|
|
catch (Base::PyException& e) {
|
|
e.reportException();
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyGILStateLocker lock;
|
|
Base::PyException e;
|
|
e.reportException();
|
|
}
|
|
catch (Base::AbortException&) {
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
// Pop-up a dialog for FreeCAD-specific exceptions
|
|
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Exception"), QLatin1String(e.what()));
|
|
}
|
|
catch (std::exception& e) {
|
|
Base::Console().error("C++ exception thrown (%s)\n", e.what());
|
|
}
|
|
catch (const char* e) {
|
|
Base::Console().error("%s\n", e);
|
|
}
|
|
#ifndef FC_DEBUG
|
|
catch (...) {
|
|
Base::Console().error("Gui::Command::activated(%d): Unknown C++ exception thrown\n", id);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Command::testActive()
|
|
{
|
|
if (!_pcAction) {
|
|
return;
|
|
}
|
|
|
|
if (_blockCmd || !bEnabled) {
|
|
_pcAction->setEnabled(false);
|
|
return;
|
|
}
|
|
|
|
if (!(eType & ForEdit)) { // special case for commands which are only in some edit modes active
|
|
|
|
if ((!Gui::Control().isAllowedAlterDocument() && eType & AlterDoc)
|
|
|| (!Gui::Control().isAllowedAlterView() && eType & Alter3DView)
|
|
|| (!Gui::Control().isAllowedAlterSelection() && eType & AlterSelection)) {
|
|
_pcAction->setEnabled(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
if (pcAction) {
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
const auto actions = pcAction->actions();
|
|
for (auto action : actions) {
|
|
auto name = action->property("CommandName").toByteArray();
|
|
if (!name.size()) {
|
|
continue;
|
|
}
|
|
Command* cmd = rcCmdMgr.getCommandByName(name);
|
|
if (cmd) {
|
|
action->setEnabled(cmd->isActive());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bActive = isActive();
|
|
_pcAction->setEnabled(bActive);
|
|
}
|
|
|
|
void Command::setEnabled(bool on)
|
|
{
|
|
if (_pcAction) {
|
|
bEnabled = on;
|
|
_pcAction->setEnabled(on);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Helper methods
|
|
//--------------------------------------------------------------------------
|
|
|
|
bool Command::hasActiveDocument() const
|
|
{
|
|
return getActiveGuiDocument() != nullptr;
|
|
}
|
|
/// true when there is a document and a Feature with Name
|
|
bool Command::hasObject(const char* Name)
|
|
{
|
|
return getDocument() != nullptr && getDocument()->getObject(Name) != nullptr;
|
|
}
|
|
|
|
Gui::SelectionSingleton& Command::getSelection()
|
|
{
|
|
return Gui::Selection();
|
|
}
|
|
|
|
std::string Command::getUniqueObjectName(const char* BaseName, const App::DocumentObject* obj) const
|
|
{
|
|
auto doc = obj ? obj->getDocument() : App::GetApplication().getActiveDocument();
|
|
assert(doc);
|
|
return doc->getUniqueObjectName(BaseName);
|
|
}
|
|
|
|
std::string Command::getObjectCmd(
|
|
const char* Name,
|
|
const App::Document* doc,
|
|
const char* prefix,
|
|
const char* postfix,
|
|
bool gui
|
|
)
|
|
{
|
|
if (!doc) {
|
|
doc = App::GetApplication().getActiveDocument();
|
|
}
|
|
if (!doc || !Name) {
|
|
return {"None"};
|
|
}
|
|
std::ostringstream str;
|
|
if (prefix) {
|
|
str << prefix;
|
|
}
|
|
str << (gui ? "Gui" : "App") << ".getDocument('" << doc->getName() << "').getObject('" << Name
|
|
<< "')";
|
|
if (postfix) {
|
|
str << postfix;
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
std::string Command::getObjectCmd(
|
|
const App::DocumentObject* obj,
|
|
const char* prefix,
|
|
const char* postfix,
|
|
bool gui
|
|
)
|
|
{
|
|
if (!obj || !obj->isAttachedToDocument()) {
|
|
return {"None"};
|
|
}
|
|
return getObjectCmd(obj->getNameInDocument(), obj->getDocument(), prefix, postfix, gui);
|
|
}
|
|
|
|
void Command::setAppModuleName(const char* s)
|
|
{
|
|
this->sAppModule = StringCache::New(s);
|
|
}
|
|
|
|
void Command::setGroupName(const char* s)
|
|
{
|
|
this->sGroup = StringCache::New(s);
|
|
}
|
|
|
|
QString Command::translatedGroupName() const
|
|
{
|
|
QString text = qApp->translate(className(), getGroupName());
|
|
if (text == QString::fromLatin1(getGroupName())) {
|
|
text = qApp->translate("CommandGroup", getGroupName());
|
|
}
|
|
return text;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// UNDO REDO transaction handling
|
|
//--------------------------------------------------------------------------
|
|
/** Open a new Undo transaction on the active document
|
|
* This method opens a new UNDO transaction on the active document. This transaction
|
|
* will later appear in the UNDO REDO dialog with the name of the command. If the user
|
|
* recall the transaction everything changed on the document between OpenCommand() and
|
|
* CommitCommand will be undone (or redone). You can use an alternative name for the
|
|
* operation default is the Command name.
|
|
* @see CommitCommand(),AbortCommand()
|
|
*/
|
|
void Command::openCommand(const char* sCmdName)
|
|
{
|
|
if (!sCmdName) {
|
|
sCmdName = "Command";
|
|
}
|
|
App::GetApplication().setActiveTransaction(sCmdName);
|
|
}
|
|
|
|
void Command::commitCommand()
|
|
{
|
|
App::GetApplication().closeActiveTransaction();
|
|
}
|
|
|
|
void Command::abortCommand()
|
|
{
|
|
App::GetApplication().closeActiveTransaction(true);
|
|
}
|
|
|
|
bool Command::hasPendingCommand()
|
|
{
|
|
return !!App::GetApplication().getActiveTransaction();
|
|
}
|
|
|
|
bool Command::_blockCmd = false;
|
|
|
|
void Command::blockCommand(bool block)
|
|
{
|
|
Command::_blockCmd = block;
|
|
}
|
|
|
|
/// Run a App level Action
|
|
void Command::_doCommand(const char* file, int line, DoCmd_Type eType, const char* sCmd, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, sCmd);
|
|
QString s;
|
|
const QString cmd = s.vasprintf(sCmd, ap);
|
|
va_end(ap);
|
|
|
|
// 'vsprintf' expects a utf-8 string for '%s'
|
|
QByteArray format = cmd.toUtf8();
|
|
|
|
#ifdef FC_LOGUSERACTION
|
|
Base::Console().log("CmdC: %s\n", format.constData());
|
|
#endif
|
|
|
|
_runCommand(file, line, eType, format.constData());
|
|
}
|
|
|
|
void Command::printPyCaller()
|
|
{
|
|
if (!FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
return;
|
|
}
|
|
PyFrameObject* frame = PyEval_GetFrame();
|
|
if (!frame) {
|
|
return;
|
|
}
|
|
int line = PyFrame_GetLineNumber(frame);
|
|
#if PY_VERSION_HEX < 0x030b0000
|
|
const char* file = PyUnicode_AsUTF8(frame->f_code->co_filename);
|
|
printCaller(file ? file : "<no file>", line);
|
|
#else
|
|
PyCodeObject* code = PyFrame_GetCode(frame);
|
|
const char* file = PyUnicode_AsUTF8(code->co_filename);
|
|
printCaller(file ? file : "<no file>", line);
|
|
Py_DECREF(code);
|
|
#endif
|
|
}
|
|
|
|
void Command::printCaller(const char* file, int line)
|
|
{
|
|
if (!FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
return;
|
|
}
|
|
std::ostringstream str;
|
|
#ifdef FC_OS_WIN32
|
|
const char* _f = std::strstr(file, "\\src\\");
|
|
#else
|
|
const char* _f = std::strstr(file, "/src/");
|
|
#endif
|
|
str << "## " << (_f ? _f + 5 : file) << '(' << line << ')';
|
|
Gui::Application::Instance->macroManager()->addLine(MacroManager::Cmt, str.str().c_str());
|
|
}
|
|
|
|
/// Run a App level Action
|
|
void Command::_runCommand(const char* file, int line, DoCmd_Type eType, const char* sCmd)
|
|
{
|
|
LogDisabler d1;
|
|
SelectionLogDisabler d2;
|
|
Base::PyGILStateLocker lock;
|
|
|
|
printCaller(file, line);
|
|
if (eType == Gui) {
|
|
Gui::Application::Instance->macroManager()->addLine(MacroManager::Gui, sCmd);
|
|
}
|
|
else {
|
|
Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd);
|
|
}
|
|
|
|
try {
|
|
Base::Interpreter().runString(sCmd);
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyException::throwException();
|
|
}
|
|
}
|
|
|
|
/// Run a App level Action
|
|
void Command::_runCommand(const char* file, int line, DoCmd_Type eType, const QByteArray& sCmd)
|
|
{
|
|
_runCommand(file, line, eType, sCmd.constData());
|
|
}
|
|
|
|
void Command::addModule(DoCmd_Type eType, const char* sModuleName)
|
|
{
|
|
if (alreadyLoadedModule.find(sModuleName) == alreadyLoadedModule.end()) {
|
|
LogDisabler d1;
|
|
SelectionLogDisabler d2;
|
|
std::string sCmd("import ");
|
|
sCmd += sModuleName;
|
|
if (eType == Gui) {
|
|
Gui::Application::Instance->macroManager()->addLine(MacroManager::Gui, sCmd.c_str());
|
|
}
|
|
else {
|
|
Gui::Application::Instance->macroManager()->addLine(MacroManager::App, sCmd.c_str());
|
|
}
|
|
Base::Interpreter().runString(sCmd.c_str());
|
|
alreadyLoadedModule.insert(sModuleName);
|
|
}
|
|
}
|
|
|
|
std::string Command::_assureWorkbench(const char* file, int line, const char* sName)
|
|
{
|
|
// check if the WB is already open?
|
|
std::string actName = WorkbenchManager::instance()->active()->name();
|
|
// if yes, do nothing
|
|
if (actName == sName) {
|
|
return actName;
|
|
}
|
|
|
|
// else - switch to new WB
|
|
_doCommand(file, line, Gui, "Gui.activateWorkbench('%s')", sName);
|
|
|
|
return actName;
|
|
}
|
|
|
|
void Command::_copyVisual(const char* file, int line, const char* to, const char* attr, const char* from)
|
|
{
|
|
_copyVisual(file, line, to, attr, from, attr);
|
|
}
|
|
|
|
void Command::_copyVisual(
|
|
const char* file,
|
|
int line,
|
|
const char* to,
|
|
const char* attr_to,
|
|
const char* from,
|
|
const char* attr_from
|
|
)
|
|
{
|
|
auto doc = App::GetApplication().getActiveDocument();
|
|
if (!doc) {
|
|
return;
|
|
}
|
|
return _copyVisual(file, line, doc->getObject(to), attr_to, doc->getObject(from), attr_from);
|
|
}
|
|
|
|
void Command::_copyVisual(
|
|
const char* file,
|
|
int line,
|
|
const App::DocumentObject* to,
|
|
const char* attr_to,
|
|
const App::DocumentObject* from,
|
|
const char* attr_from
|
|
)
|
|
{
|
|
if (!from || !from->isAttachedToDocument() || !to || !to->isAttachedToDocument()) {
|
|
return;
|
|
}
|
|
static std::map<std::string, std::string> attrMap = {
|
|
{"ShapeAppearance", "ShapeMaterial"},
|
|
{"Transparency", "Transparency"},
|
|
};
|
|
auto it = attrMap.find(attr_to);
|
|
auto objCmd = getObjectCmd(to);
|
|
if (it != attrMap.end()) {
|
|
auto obj = from;
|
|
for (int depth = 0;; ++depth) {
|
|
auto vp = dynamic_cast<Gui::ViewProviderLink*>(
|
|
Gui::Application::Instance->getViewProvider(obj)
|
|
);
|
|
if (vp && vp->OverrideMaterial.getValue()) {
|
|
_doCommand(
|
|
file,
|
|
line,
|
|
Gui,
|
|
"%s.ViewObject.%s=%s.ViewObject.%s",
|
|
objCmd.c_str(),
|
|
attr_to,
|
|
getObjectCmd(obj).c_str(),
|
|
it->second.c_str()
|
|
);
|
|
return;
|
|
}
|
|
auto linked = obj->getLinkedObject(false, nullptr, false, depth);
|
|
if (!linked || linked == obj) {
|
|
break;
|
|
}
|
|
obj = linked;
|
|
}
|
|
}
|
|
|
|
try {
|
|
_doCommand(
|
|
file,
|
|
line,
|
|
Gui,
|
|
"%s.ViewObject.%s=getattr(%s.getLinkedObject(True).ViewObject,'%s',%s.ViewObject.%s)",
|
|
objCmd.c_str(),
|
|
attr_to,
|
|
getObjectCmd(from).c_str(),
|
|
attr_from,
|
|
objCmd.c_str(),
|
|
attr_to
|
|
);
|
|
}
|
|
catch (Base::Exception& /*e*/) {
|
|
}
|
|
}
|
|
|
|
void Command::_copyVisual(
|
|
const char* file,
|
|
int line,
|
|
const App::DocumentObject* to,
|
|
const char* attr,
|
|
const App::DocumentObject* from
|
|
)
|
|
{
|
|
_copyVisual(file, line, to, attr, from, attr);
|
|
}
|
|
|
|
std::string Command::getPythonTuple(const std::string& name, const std::vector<std::string>& subnames)
|
|
{
|
|
std::stringstream str;
|
|
auto last = --subnames.end();
|
|
str << "(App.ActiveDocument." << name << ",[";
|
|
for (auto it = subnames.cbegin(); it != subnames.cend(); ++it) {
|
|
str << "\"" << *it << "\"";
|
|
if (it != last) {
|
|
str << ",";
|
|
}
|
|
}
|
|
str << "])";
|
|
return str.str();
|
|
}
|
|
|
|
const std::string Command::strToPython(const char* Str)
|
|
{
|
|
return Base::InterpreterSingleton::strToPython(Str);
|
|
}
|
|
|
|
/// Updates the (active) document (propagate changes)
|
|
void Command::updateActive()
|
|
{
|
|
WaitCursor wc;
|
|
doCommand(App, "App.ActiveDocument.recompute()");
|
|
}
|
|
|
|
bool Command::isActiveObjectValid()
|
|
{
|
|
Gui::Document* active = Gui::Application::Instance->activeDocument();
|
|
assert(active);
|
|
App::Document* document = active->getDocument();
|
|
App::DocumentObject* object = document->getActiveObject();
|
|
assert(object);
|
|
return object->isValid();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Online help handling
|
|
//--------------------------------------------------------------------------
|
|
|
|
/// returns the begin of a online help page
|
|
const char* Command::beginCmdHelp()
|
|
{
|
|
return "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
|
|
"<html>\n"
|
|
"<head>\n"
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n"
|
|
"<title>FreeCAD Main Index</title>\n"
|
|
"</head>\n"
|
|
"<body bgcolor=\"#ffffff\">\n\n";
|
|
}
|
|
|
|
/// returns the end of a online help page
|
|
const char* Command::endCmdHelp()
|
|
{
|
|
return "</body></html>\n\n";
|
|
}
|
|
|
|
void Command::applyCommandData(const char* context, Action* action)
|
|
{
|
|
action->setText(QCoreApplication::translate(context, getMenuText()));
|
|
action->setToolTip(QCoreApplication::translate(context, getToolTipText()));
|
|
action->setWhatsThis(QCoreApplication::translate(context, getWhatsThis()));
|
|
if (sStatusTip) {
|
|
action->setStatusTip(QCoreApplication::translate(context, getStatusTip()));
|
|
}
|
|
else {
|
|
action->setStatusTip(QCoreApplication::translate(context, getToolTipText()));
|
|
}
|
|
|
|
// Default to QAction::NoRole instead of QAction::TextHeuristicRole to stop collisions with
|
|
// e.g. "Preferences" and "Copy"
|
|
if (action->action()->menuRole() == QAction::TextHeuristicRole) {
|
|
action->setMenuRole(QAction::NoRole);
|
|
}
|
|
}
|
|
|
|
|
|
const char* Command::keySequenceToAccel(int sk) const
|
|
{
|
|
/* Local class to ensure free()'ing the strings allocated below */
|
|
using StringMap = std::map<int, std::string>;
|
|
static StringMap strings;
|
|
auto i = strings.find(sk);
|
|
|
|
if (i != strings.end()) {
|
|
return i->second.c_str();
|
|
}
|
|
|
|
// In case FreeCAD is loaded without GUI (issue 16407)
|
|
if (!QApplication::instance()) {
|
|
return "";
|
|
}
|
|
|
|
auto type = static_cast<QKeySequence::StandardKey>(sk);
|
|
QKeySequence ks(type);
|
|
QString qs = ks.toString();
|
|
QByteArray data = qs.toLatin1();
|
|
|
|
return (strings[sk] = static_cast<const char*>(data)).c_str();
|
|
}
|
|
|
|
void Command::printConflictingAccelerators() const
|
|
{
|
|
auto cmd = Application::Instance->commandManager().checkAcceleratorForConflicts(sAccel, this);
|
|
if (cmd) {
|
|
Base::Console().warning(
|
|
"Accelerator conflict between %s (%s) and %s (%s)\n",
|
|
sName,
|
|
sAccel,
|
|
cmd->sName,
|
|
cmd->sAccel
|
|
);
|
|
}
|
|
}
|
|
|
|
Action* Command::createAction()
|
|
{
|
|
Action* pcAction;
|
|
pcAction = new Action(this, getMainWindow());
|
|
applyCommandData(this->className(), pcAction);
|
|
if (sPixmap) {
|
|
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap));
|
|
}
|
|
return pcAction;
|
|
}
|
|
|
|
void Command::languageChange()
|
|
{
|
|
if (_pcAction) {
|
|
applyCommandData(this->className(), _pcAction);
|
|
}
|
|
}
|
|
|
|
void Command::updateAction(int)
|
|
{}
|
|
|
|
//===========================================================================
|
|
// GroupCommand
|
|
//===========================================================================
|
|
|
|
GroupCommand::GroupCommand(const char* name)
|
|
: Command(name)
|
|
{}
|
|
|
|
bool GroupCommand::isCheckable() const
|
|
{
|
|
return checkable;
|
|
}
|
|
|
|
void GroupCommand::setCheckable(bool on)
|
|
{
|
|
checkable = on;
|
|
}
|
|
|
|
bool GroupCommand::isExclusive() const
|
|
{
|
|
return exclusive;
|
|
}
|
|
|
|
void GroupCommand::setExclusive(bool on)
|
|
{
|
|
exclusive = on;
|
|
}
|
|
|
|
bool GroupCommand::doesRememberLast() const
|
|
{
|
|
return rememberLast;
|
|
}
|
|
|
|
void GroupCommand::setRememberLast(bool on)
|
|
{
|
|
rememberLast = on;
|
|
}
|
|
|
|
bool GroupCommand::hasDropDownMenu() const
|
|
{
|
|
return dropDownMenu;
|
|
}
|
|
|
|
void GroupCommand::setDropDownMenu(bool on)
|
|
{
|
|
dropDownMenu = on;
|
|
}
|
|
|
|
int GroupCommand::addCommand(Command* cmd, bool reg)
|
|
{
|
|
cmds.emplace_back(cmd, cmds.size());
|
|
if (cmd && reg) {
|
|
Application::Instance->commandManager().addCommand(cmd);
|
|
}
|
|
return (int)cmds.size() - 1;
|
|
}
|
|
|
|
Command* GroupCommand::addCommand(const char* name)
|
|
{
|
|
auto cmd = Application::Instance->commandManager().getCommandByName(name);
|
|
if (cmd) {
|
|
addCommand(cmd, false);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
Command* GroupCommand::getCommand(int idx) const
|
|
{
|
|
if (idx >= 0 && idx < (int)cmds.size()) {
|
|
return cmds[idx].first;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Action* GroupCommand::createAction()
|
|
{
|
|
auto* pcAction = new ActionGroup(this, getMainWindow());
|
|
pcAction->setMenuRole(QAction::NoRole);
|
|
pcAction->setDropDownMenu(hasDropDownMenu());
|
|
pcAction->setExclusive(isExclusive());
|
|
pcAction->setCheckable(isCheckable());
|
|
pcAction->setRememberLast(doesRememberLast());
|
|
pcAction->setWhatsThis(QString::fromLatin1(sWhatsThis));
|
|
|
|
for (auto& v : cmds) {
|
|
if (!v.first) {
|
|
pcAction->addAction(QStringLiteral(""))->setSeparator(true);
|
|
}
|
|
else {
|
|
v.first->addToGroup(pcAction);
|
|
}
|
|
}
|
|
|
|
pcAction->setProperty("defaultAction", QVariant(0));
|
|
setup(pcAction);
|
|
return pcAction;
|
|
}
|
|
|
|
void GroupCommand::activated(int iMsg)
|
|
{
|
|
if (iMsg < 0 || iMsg >= (int)cmds.size()) {
|
|
return;
|
|
}
|
|
|
|
auto& v = cmds[iMsg];
|
|
if (!v.first) {
|
|
return;
|
|
}
|
|
|
|
if (triggerSource() != TriggerChildAction) {
|
|
v.first->invoke(0);
|
|
}
|
|
|
|
Action* cmdAction = v.first->getAction();
|
|
if (_pcAction && cmdAction) {
|
|
_pcAction->setProperty("defaultAction", QVariant((int)v.second));
|
|
setup(_pcAction);
|
|
}
|
|
}
|
|
|
|
void GroupCommand::languageChange()
|
|
{
|
|
Command::languageChange();
|
|
if (_pcAction) {
|
|
setup(_pcAction);
|
|
}
|
|
}
|
|
|
|
void GroupCommand::setup(Action* pcAction)
|
|
{
|
|
|
|
// The tooltip for the group is the tooltip of the active tool (that is, the tool that will
|
|
// be activated when the main portion of the button is clicked).
|
|
int idx = pcAction->property("defaultAction").toInt();
|
|
if (idx >= 0 && idx < (int)cmds.size() && cmds[idx].first) {
|
|
auto cmd = cmds[idx].first;
|
|
QString shortcut = cmd->getShortcut();
|
|
pcAction->setShortcut(shortcut);
|
|
pcAction->setText(QCoreApplication::translate(className(), getMenuText()));
|
|
QIcon icon;
|
|
if (auto childAction = cmd->getAction()) {
|
|
icon = childAction->icon();
|
|
}
|
|
if (icon.isNull()) {
|
|
icon = BitmapFactory().iconFromTheme(cmd->getPixmap());
|
|
}
|
|
pcAction->setIcon(icon);
|
|
const char* context = dynamic_cast<PythonCommand*>(cmd) ? cmd->getName() : cmd->className();
|
|
const char* tooltip = cmd->getToolTipText();
|
|
const char* statustip = cmd->getStatusTip();
|
|
if (!statustip || '\0' == *statustip) {
|
|
statustip = tooltip;
|
|
}
|
|
pcAction->setToolTip(
|
|
QCoreApplication::translate(context, tooltip),
|
|
QCoreApplication::translate(cmd->className(), cmd->getMenuText())
|
|
);
|
|
pcAction->setStatusTip(QCoreApplication::translate(context, statustip));
|
|
}
|
|
else {
|
|
applyCommandData(this->className(), pcAction);
|
|
if (sPixmap) {
|
|
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap));
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
// MacroCommand
|
|
//===========================================================================
|
|
|
|
/* TRANSLATOR Gui::MacroCommand */
|
|
|
|
MacroCommand::MacroCommand(const char* name, bool system)
|
|
: Command(StringCache::New(name))
|
|
, systemMacro(system)
|
|
{
|
|
sGroup = "Macros";
|
|
eType = 0;
|
|
sScriptName = nullptr;
|
|
}
|
|
|
|
MacroCommand::~MacroCommand() = default;
|
|
|
|
void MacroCommand::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
|
|
QDir d;
|
|
if (!systemMacro) {
|
|
std::string cMacroPath;
|
|
|
|
cMacroPath = App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")
|
|
->GetASCII("MacroPath", App::Application::getUserMacroDir().c_str());
|
|
|
|
d = QDir(QString::fromUtf8(cMacroPath.c_str()));
|
|
}
|
|
else {
|
|
QString dirstr = QString::fromStdString(App::Application::getHomePath())
|
|
+ QStringLiteral("Macro");
|
|
d = QDir(dirstr);
|
|
}
|
|
|
|
QFileInfo fi(d, QString::fromUtf8(sScriptName));
|
|
if (!fi.exists()) {
|
|
QMessageBox::critical(
|
|
Gui::getMainWindow(),
|
|
qApp->translate("Gui::MacroCommand", "Macro file doesn't exist"),
|
|
qApp->translate("Gui::MacroCommand", "No such macro file: '%1'").arg(fi.absoluteFilePath())
|
|
);
|
|
}
|
|
else {
|
|
Application::Instance->macroManager()->run(MacroManager::File, fi.filePath().toUtf8());
|
|
// after macro run recalculate the document
|
|
if (Application::Instance->activeDocument()) {
|
|
Application::Instance->activeDocument()->getDocument()->recompute();
|
|
}
|
|
}
|
|
}
|
|
|
|
Action* MacroCommand::createAction()
|
|
{
|
|
Action* pcAction;
|
|
pcAction = new Action(this, getMainWindow());
|
|
pcAction->setText(QString::fromUtf8(sMenuText));
|
|
pcAction->setToolTip(QString::fromUtf8(sToolTipText));
|
|
pcAction->setStatusTip(QString::fromUtf8(sStatusTip));
|
|
if (pcAction->statusTip().isEmpty()) {
|
|
pcAction->setStatusTip(pcAction->toolTip());
|
|
}
|
|
pcAction->setWhatsThis(QString::fromUtf8(sWhatsThis));
|
|
if (sPixmap) {
|
|
pcAction->setIcon(Gui::BitmapFactory().pixmap(sPixmap));
|
|
}
|
|
|
|
return pcAction;
|
|
}
|
|
|
|
void MacroCommand::setScriptName(const char* s)
|
|
{
|
|
this->sScriptName = StringCache::New(s);
|
|
}
|
|
|
|
void MacroCommand::load()
|
|
{
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Macro"
|
|
);
|
|
|
|
if (hGrp->HasGroup("Macros")) {
|
|
hGrp = hGrp->GetGroup("Macros");
|
|
std::vector<Base::Reference<ParameterGrp>> macros = hGrp->GetGroups();
|
|
for (const auto& it : macros) {
|
|
auto macro = new MacroCommand(it->GetGroupName());
|
|
macro->setScriptName(it->GetASCII("Script").c_str());
|
|
macro->setMenuText(it->GetASCII("Menu").c_str());
|
|
macro->setToolTipText(it->GetASCII("Tooltip").c_str());
|
|
macro->setWhatsThis(it->GetASCII("WhatsThis").c_str());
|
|
macro->setStatusTip(it->GetASCII("Statustip").c_str());
|
|
if (it->GetASCII("Pixmap", "nix") != "nix") {
|
|
macro->setPixmap(it->GetASCII("Pixmap").c_str());
|
|
}
|
|
macro->setAccel(it->GetASCII("Accel", nullptr).c_str());
|
|
macro->systemMacro = it->GetBool("System", false);
|
|
Application::Instance->commandManager().addCommand(macro);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacroCommand::save()
|
|
{
|
|
ParameterGrp::handle hGrp = App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Macro")
|
|
->GetGroup("Macros");
|
|
hGrp->Clear();
|
|
|
|
std::vector<Command*> macros = Application::Instance->commandManager().getGroupCommands("Macros");
|
|
if (!macros.empty()) {
|
|
for (const auto& it : macros) {
|
|
auto macro = (MacroCommand*)it;
|
|
ParameterGrp::handle hMacro = hGrp->GetGroup(macro->getName());
|
|
hMacro->SetASCII("Script", macro->getScriptName());
|
|
hMacro->SetASCII("Menu", macro->getMenuText());
|
|
hMacro->SetASCII("Tooltip", macro->getToolTipText());
|
|
hMacro->SetASCII("WhatsThis", macro->getWhatsThis());
|
|
hMacro->SetASCII("Statustip", macro->getStatusTip());
|
|
hMacro->SetASCII("Pixmap", macro->getPixmap());
|
|
hMacro->SetASCII("Accel", macro->getAccel());
|
|
hMacro->SetBool("System", macro->systemMacro);
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
// PythonCommand
|
|
//===========================================================================
|
|
|
|
PythonCommand::PythonCommand(const char* name, PyObject* pcPyCommand, const char* pActivationString)
|
|
: Command(StringCache::New(name))
|
|
, _pcPyCommand(pcPyCommand)
|
|
{
|
|
if (pActivationString) {
|
|
Activation = pActivationString;
|
|
}
|
|
|
|
sGroup = "Python";
|
|
|
|
Py_INCREF(_pcPyCommand);
|
|
|
|
// call the method "GetResources()" of the command object
|
|
_pcPyResourceDict = Interpreter().runMethodObject(_pcPyCommand, "GetResources");
|
|
// check if the "GetResources()" method returns a Dict object
|
|
if (!PyDict_Check(_pcPyResourceDict)) {
|
|
throw Base::TypeError(
|
|
"PythonCommand::PythonCommand(): Method GetResources() of the Python "
|
|
"command object returns the wrong type (has to be dict)"
|
|
);
|
|
}
|
|
|
|
// check for command type
|
|
std::string cmdType = getResource("CmdType");
|
|
if (!cmdType.empty()) {
|
|
int type = 0;
|
|
if (cmdType.find("AlterDoc") != std::string::npos) {
|
|
type += int(AlterDoc);
|
|
}
|
|
if (cmdType.find("Alter3DView") != std::string::npos) {
|
|
type += int(Alter3DView);
|
|
}
|
|
if (cmdType.find("AlterSelection") != std::string::npos) {
|
|
type += int(AlterSelection);
|
|
}
|
|
if (cmdType.find("ForEdit") != std::string::npos) {
|
|
type += int(ForEdit);
|
|
}
|
|
if (cmdType.find("NoTransaction") != std::string::npos) {
|
|
type += int(NoTransaction);
|
|
}
|
|
eType = type;
|
|
}
|
|
|
|
auto& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
connPyCmdInitialized = rcCmdMgr.signalPyCmdInitialized.connect([this]() { this->onActionInit(); });
|
|
}
|
|
|
|
PythonCommand::~PythonCommand()
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
Py_DECREF(_pcPyCommand);
|
|
}
|
|
|
|
const char* PythonCommand::getResource(const char* sName) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* pcTemp;
|
|
|
|
// get the "MenuText" resource string
|
|
pcTemp = PyDict_GetItemString(_pcPyResourceDict, sName);
|
|
if (!pcTemp) {
|
|
return "";
|
|
}
|
|
if (!PyUnicode_Check(pcTemp)) {
|
|
throw Base::TypeError(
|
|
"PythonCommand::getResource(): Method GetResources() of the Python "
|
|
"command object returns a dictionary which holds not only strings"
|
|
);
|
|
}
|
|
return PyUnicode_AsUTF8(pcTemp);
|
|
}
|
|
|
|
void PythonCommand::activated(int iMsg)
|
|
{
|
|
if (Activation.empty()) {
|
|
try {
|
|
if (isCheckable()) {
|
|
Interpreter().runMethod(_pcPyCommand, "Activated", "", nullptr, "(i)", iMsg);
|
|
}
|
|
else {
|
|
Interpreter().runMethodVoid(_pcPyCommand, "Activated");
|
|
}
|
|
}
|
|
catch (const Base::PyException& e) {
|
|
Base::Console().error(
|
|
"Running the Python command '%s' failed:\n%s\n%s",
|
|
sName,
|
|
e.getStackTrace().c_str(),
|
|
e.what()
|
|
);
|
|
}
|
|
catch (const Base::Exception&) {
|
|
Base::Console().error("Running the Python command '%s' failed, try to resume", sName);
|
|
}
|
|
}
|
|
else {
|
|
runCommand(Doc, Activation.c_str());
|
|
}
|
|
}
|
|
|
|
bool PythonCommand::isActive()
|
|
{
|
|
try {
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
if (cmd.hasAttr("IsActive")) {
|
|
Py::Callable call(cmd.getAttr("IsActive"));
|
|
Py::Tuple args;
|
|
Py::Object ret = call.apply(args);
|
|
// if return type is not boolean or not true
|
|
if (!PyBool_Check(ret.ptr()) || ret.ptr() != Py_True) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
Base::PyGILStateLocker lock;
|
|
e.clear();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PythonCommand::languageChange()
|
|
{
|
|
if (_pcAction) {
|
|
applyCommandData(getName(), _pcAction);
|
|
}
|
|
}
|
|
|
|
const char* PythonCommand::getHelpUrl() const
|
|
{
|
|
PyObject* pcTemp;
|
|
pcTemp = Interpreter().runMethodObject(_pcPyCommand, "CmdHelpURL");
|
|
if (!pcTemp) {
|
|
return "";
|
|
}
|
|
if (!PyUnicode_Check(pcTemp)) {
|
|
throw Base::TypeError("PythonCommand::CmdHelpURL(): Method CmdHelpURL() of the Python command object returns no string");
|
|
}
|
|
return PyUnicode_AsUTF8(pcTemp);
|
|
}
|
|
|
|
Action* PythonCommand::createAction()
|
|
{
|
|
auto qtAction = new QAction(nullptr);
|
|
Action* pcAction;
|
|
|
|
pcAction = new Action(this, qtAction, getMainWindow());
|
|
applyCommandData(this->getName(), pcAction);
|
|
if (strcmp(getResource("Pixmap"), "") != 0) {
|
|
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getResource("Pixmap")));
|
|
}
|
|
|
|
try {
|
|
if (isCheckable()) {
|
|
pcAction->setCheckable(true);
|
|
// Here the QAction must be tmp. blocked to avoid to call the 'activated' method
|
|
qtAction->blockSignals(true);
|
|
pcAction->setChecked(isChecked());
|
|
qtAction->blockSignals(false);
|
|
}
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
Base::Console().error("%s\n", e.what());
|
|
}
|
|
|
|
return pcAction;
|
|
}
|
|
|
|
const char* PythonCommand::getWhatsThis() const
|
|
{
|
|
const char* whatsthis = getResource("WhatsThis");
|
|
if (Base::Tools::isNullOrEmpty(whatsthis)) {
|
|
whatsthis = this->getName();
|
|
}
|
|
return whatsthis;
|
|
}
|
|
|
|
const char* PythonCommand::getMenuText() const
|
|
{
|
|
return getResource("MenuText");
|
|
}
|
|
|
|
const char* PythonCommand::getToolTipText() const
|
|
{
|
|
return getResource("ToolTip");
|
|
}
|
|
|
|
const char* PythonCommand::getStatusTip() const
|
|
{
|
|
return getResource("StatusTip");
|
|
}
|
|
|
|
const char* PythonCommand::getPixmap() const
|
|
{
|
|
const char* ret = getResource("Pixmap");
|
|
return !Base::Tools::isNullOrEmpty(ret) ? ret : nullptr;
|
|
}
|
|
|
|
const char* PythonCommand::getAccel() const
|
|
{
|
|
return getResource("Accel");
|
|
}
|
|
|
|
bool PythonCommand::isCheckable() const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* item = PyDict_GetItemString(_pcPyResourceDict, "Checkable");
|
|
return item ? true : false;
|
|
}
|
|
|
|
bool PythonCommand::isChecked() const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* item = PyDict_GetItemString(_pcPyResourceDict, "Checkable");
|
|
if (!item) {
|
|
throw Base::ValueError(
|
|
"PythonCommand::isChecked(): Method GetResources() of the Python "
|
|
"command object doesn't contain the key 'Checkable'"
|
|
);
|
|
}
|
|
|
|
if (PyBool_Check(item)) {
|
|
return Base::asBoolean(item);
|
|
}
|
|
else {
|
|
throw Base::ValueError(
|
|
"PythonCommand::isChecked(): Method GetResources() of the Python "
|
|
"command object contains the key 'Checkable' which is not a boolean"
|
|
);
|
|
}
|
|
}
|
|
|
|
void PythonCommand::onActionInit() const
|
|
{
|
|
try {
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
if (cmd.hasAttr("OnActionInit")) {
|
|
Py::Callable call(cmd.getAttr("OnActionInit"));
|
|
Py::Tuple args;
|
|
Py::Object ret = call.apply(args);
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
Base::PyGILStateLocker lock;
|
|
e.clear();
|
|
}
|
|
|
|
connPyCmdInitialized.disconnect();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PythonGroupCommand
|
|
//===========================================================================
|
|
|
|
PythonGroupCommand::PythonGroupCommand(const char* name, PyObject* pcPyCommand)
|
|
: Command(StringCache::New(name))
|
|
, _pcPyCommand(pcPyCommand)
|
|
{
|
|
sGroup = "Python";
|
|
|
|
Py_INCREF(_pcPyCommand);
|
|
|
|
// call the method "GetResources()" of the command object
|
|
_pcPyResource = Interpreter().runMethodObject(_pcPyCommand, "GetResources");
|
|
// check if the "GetResources()" method returns a Dict object
|
|
if (!PyDict_Check(_pcPyResource)) {
|
|
throw Base::TypeError(
|
|
"PythonGroupCommand::PythonGroupCommand(): Method GetResources() of the Python "
|
|
"command object returns the wrong type (has to be dict)"
|
|
);
|
|
}
|
|
|
|
// check for command type
|
|
std::string cmdType = getResource("CmdType");
|
|
if (!cmdType.empty()) {
|
|
int type = 0;
|
|
if (cmdType.find("AlterDoc") != std::string::npos) {
|
|
type += int(AlterDoc);
|
|
}
|
|
if (cmdType.find("Alter3DView") != std::string::npos) {
|
|
type += int(Alter3DView);
|
|
}
|
|
if (cmdType.find("AlterSelection") != std::string::npos) {
|
|
type += int(AlterSelection);
|
|
}
|
|
if (cmdType.find("ForEdit") != std::string::npos) {
|
|
type += int(ForEdit);
|
|
}
|
|
eType = type;
|
|
}
|
|
|
|
auto& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
connPyCmdInitialized = rcCmdMgr.signalPyCmdInitialized.connect([this]() { this->onActionInit(); });
|
|
}
|
|
|
|
PythonGroupCommand::~PythonGroupCommand()
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
Py_DECREF(_pcPyCommand);
|
|
}
|
|
|
|
void PythonGroupCommand::activated(int iMsg)
|
|
{
|
|
try {
|
|
auto pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
QList<QAction*> a = pcAction->actions();
|
|
assert(iMsg < a.size());
|
|
QAction* act = a[iMsg];
|
|
|
|
setupCheckable(iMsg);
|
|
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
if (cmd.hasAttr("Activated")) {
|
|
Py::Callable call(cmd.getAttr("Activated"));
|
|
Py::Tuple args(1);
|
|
args.setItem(0, Py::Long(iMsg));
|
|
Py::Object ret = call.apply(args);
|
|
}
|
|
// If the command group doesn't implement the 'Activated' method then invoke the command directly
|
|
else {
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
auto cmd = rcCmdMgr.getCommandByName(act->property("CommandName").toByteArray());
|
|
if (cmd) {
|
|
bool checked = act->isCheckable() && act->isChecked();
|
|
cmd->invoke(checked ? 1 : 0, TriggerAction);
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyGILStateLocker lock;
|
|
Base::PyException e;
|
|
Base::Console().error(
|
|
"Running the Python command '%s' failed:\n%s\n%s",
|
|
sName,
|
|
e.getStackTrace().c_str(),
|
|
e.what()
|
|
);
|
|
}
|
|
}
|
|
|
|
bool PythonGroupCommand::isActive()
|
|
{
|
|
try {
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
|
|
if (cmd.hasAttr("IsActive")) {
|
|
Py::Callable call(cmd.getAttr("IsActive"));
|
|
Py::Tuple args;
|
|
Py::Object ret = call.apply(args);
|
|
// if return type is not boolean or not true
|
|
if (!PyBool_Check(ret.ptr()) || ret.ptr() != Py_True) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
Base::PyGILStateLocker lock;
|
|
e.clear();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Action* PythonGroupCommand::createAction()
|
|
{
|
|
auto pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
|
|
pcAction->setDropDownMenu(hasDropDownMenu());
|
|
pcAction->setExclusive(isExclusive());
|
|
|
|
applyCommandData(this->getName(), pcAction);
|
|
|
|
int defaultId = 0;
|
|
|
|
try {
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
Py::Callable call(cmd.getAttr("GetCommands"));
|
|
Py::Sequence args;
|
|
Py::Sequence ret(call.apply(args));
|
|
for (auto it = ret.begin(); it != ret.end(); ++it) {
|
|
Py::String str(*it);
|
|
QAction* cmd = pcAction->addAction(QString());
|
|
cmd->setProperty("CommandName", QByteArray(static_cast<std::string>(str).c_str()));
|
|
|
|
PythonCommand* pycmd = dynamic_cast<PythonCommand*>(
|
|
rcCmdMgr.getCommandByName(cmd->property("CommandName").toByteArray())
|
|
);
|
|
if (pycmd && pycmd->isCheckable()) {
|
|
cmd->setCheckable(true);
|
|
cmd->blockSignals(true);
|
|
cmd->setChecked(pycmd->isChecked());
|
|
cmd->blockSignals(false);
|
|
}
|
|
cmd->setShortcut(
|
|
ShortcutManager::instance()->getShortcut(cmd->property("CommandName").toByteArray())
|
|
);
|
|
}
|
|
|
|
if (cmd.hasAttr("GetDefaultCommand")) {
|
|
Py::Callable call2(cmd.getAttr("GetDefaultCommand"));
|
|
Py::Long def(call2.apply(args));
|
|
defaultId = static_cast<int>(def);
|
|
}
|
|
|
|
QList<QAction*> a = pcAction->actions();
|
|
if (defaultId >= 0 && defaultId < a.size()) {
|
|
QAction* qtAction = a[defaultId];
|
|
if (qtAction->isCheckable()) {
|
|
// if the command is 'exclusive' then activate the default action
|
|
if (pcAction->isExclusive()) {
|
|
qtAction->blockSignals(true);
|
|
qtAction->setChecked(true);
|
|
qtAction->blockSignals(false);
|
|
}
|
|
else if (qtAction->isCheckable()) {
|
|
pcAction->setCheckable(true);
|
|
pcAction->setBlockedChecked(qtAction->isChecked());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Py::Exception&) {
|
|
Base::PyGILStateLocker lock;
|
|
Base::PyException e;
|
|
Base::Console().error(
|
|
"createAction() of the Python command '%s' failed:\n%s\n%s",
|
|
sName,
|
|
e.getStackTrace().c_str(),
|
|
e.what()
|
|
);
|
|
}
|
|
|
|
_pcAction = pcAction;
|
|
languageChange();
|
|
|
|
if (strcmp(getResource("Pixmap"), "") != 0) {
|
|
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getResource("Pixmap")));
|
|
}
|
|
else {
|
|
QList<QAction*> a = pcAction->actions();
|
|
// if out of range then set to 0
|
|
if (defaultId < 0 || defaultId >= a.size()) {
|
|
defaultId = 0;
|
|
}
|
|
if (a.size() > defaultId) {
|
|
pcAction->setIcon(a[defaultId]->icon());
|
|
}
|
|
}
|
|
|
|
pcAction->setProperty("defaultAction", QVariant(defaultId));
|
|
|
|
return pcAction;
|
|
}
|
|
|
|
void PythonGroupCommand::languageChange()
|
|
{
|
|
if (!_pcAction) {
|
|
return;
|
|
}
|
|
|
|
applyCommandData(this->getName(), _pcAction);
|
|
|
|
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
// Reapply setup to ensure group action tooltip includes shortcut
|
|
auto* pcActionGroup = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
QList<QAction*> groupActions = pcActionGroup->actions();
|
|
int idx = _pcAction->property("defaultAction").toInt();
|
|
if (idx >= 0 && idx < groupActions.size()) {
|
|
QAction* defaultAction = groupActions[idx];
|
|
Gui::Command* cmd = rcCmdMgr.getCommandByName(
|
|
defaultAction->property("CommandName").toByteArray()
|
|
);
|
|
if (cmd) {
|
|
const char* context = cmd->getName();
|
|
QString tip = QApplication::translate(context, cmd->getToolTipText());
|
|
_pcAction->setShortcut(cmd->getShortcut());
|
|
QString newTip = Gui::Action::createToolTip(
|
|
tip,
|
|
_pcAction->text(),
|
|
_pcAction->action()->font(),
|
|
_pcAction->shortcut().toString(),
|
|
cmd
|
|
);
|
|
_pcAction->setToolTip(newTip);
|
|
}
|
|
}
|
|
auto* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
|
QList<QAction*> actions = pcAction->actions();
|
|
for (const auto& it : actions) {
|
|
Gui::Command* cmd = rcCmdMgr.getCommandByName(it->property("CommandName").toByteArray());
|
|
if (cmd) {
|
|
// Python command use getName as context
|
|
const char* context = dynamic_cast<PythonCommand*>(cmd) ? cmd->getName()
|
|
: cmd->className();
|
|
const char* tooltip = cmd->getToolTipText();
|
|
const char* statustip = cmd->getStatusTip();
|
|
if (!statustip || '\0' == *statustip) {
|
|
statustip = tooltip;
|
|
}
|
|
|
|
it->setIcon(Gui::BitmapFactory().iconFromTheme(cmd->getPixmap()));
|
|
QString text = QApplication::translate(context, cmd->getMenuText());
|
|
it->setText(text);
|
|
QString helpText = QApplication::translate(context, tooltip);
|
|
QString shortCut = it->shortcut().toString();
|
|
QFont font = it->font();
|
|
QString newTip = Gui::Action::createToolTip(helpText, text, font, shortCut, cmd);
|
|
it->setToolTip(newTip);
|
|
it->setStatusTip(QApplication::translate(context, statustip));
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* PythonGroupCommand::getHelpUrl() const
|
|
{
|
|
return "";
|
|
}
|
|
|
|
const char* PythonGroupCommand::getResource(const char* sName) const
|
|
{
|
|
Base::PyGILStateLocker lock;
|
|
PyObject* pcTemp;
|
|
|
|
// get the "MenuText" resource string
|
|
pcTemp = PyDict_GetItemString(_pcPyResource, sName);
|
|
if (!pcTemp) {
|
|
return "";
|
|
}
|
|
if (!PyUnicode_Check(pcTemp)) {
|
|
throw Base::ValueError(
|
|
"PythonGroupCommand::getResource(): Method GetResources() of the Python "
|
|
"group command object returns a dictionary which holds not only strings"
|
|
);
|
|
}
|
|
return PyUnicode_AsUTF8(pcTemp);
|
|
}
|
|
|
|
const char* PythonGroupCommand::getWhatsThis() const
|
|
{
|
|
const char* whatsthis = getResource("WhatsThis");
|
|
if (Base::Tools::isNullOrEmpty(whatsthis)) {
|
|
whatsthis = this->getName();
|
|
}
|
|
return whatsthis;
|
|
}
|
|
|
|
const char* PythonGroupCommand::getMenuText() const
|
|
{
|
|
return getResource("MenuText");
|
|
}
|
|
|
|
const char* PythonGroupCommand::getToolTipText() const
|
|
{
|
|
return getResource("ToolTip");
|
|
}
|
|
|
|
const char* PythonGroupCommand::getStatusTip() const
|
|
{
|
|
return getResource("StatusTip");
|
|
}
|
|
|
|
const char* PythonGroupCommand::getPixmap() const
|
|
{
|
|
const char* ret = getResource("Pixmap");
|
|
return !Base::Tools::isNullOrEmpty(ret) ? ret : nullptr;
|
|
}
|
|
|
|
const char* PythonGroupCommand::getAccel() const
|
|
{
|
|
return getResource("Accel");
|
|
}
|
|
|
|
bool PythonGroupCommand::isExclusive() const
|
|
{
|
|
PyObject* item = PyDict_GetItemString(_pcPyResource, "Exclusive");
|
|
if (!item) {
|
|
return false;
|
|
}
|
|
|
|
if (PyBool_Check(item)) {
|
|
return Base::asBoolean(item);
|
|
}
|
|
else {
|
|
throw Base::TypeError(
|
|
"PythonGroupCommand::isExclusive(): Method GetResources() of the Python "
|
|
"command object contains the key 'Exclusive' which is not a boolean"
|
|
);
|
|
}
|
|
}
|
|
|
|
bool PythonGroupCommand::hasDropDownMenu() const
|
|
{
|
|
PyObject* item = PyDict_GetItemString(_pcPyResource, "DropDownMenu");
|
|
if (!item) {
|
|
return true;
|
|
}
|
|
|
|
if (PyBool_Check(item)) {
|
|
return Base::asBoolean(item);
|
|
}
|
|
else {
|
|
throw Base::TypeError(
|
|
"PythonGroupCommand::hasDropDownMenu(): Method GetResources() of the Python "
|
|
"command object contains the key 'DropDownMenu' which is not a boolean"
|
|
);
|
|
}
|
|
}
|
|
|
|
void PythonGroupCommand::onActionInit() const
|
|
{
|
|
try {
|
|
Base::PyGILStateLocker lock;
|
|
Py::Object cmd(_pcPyCommand);
|
|
if (cmd.hasAttr("OnActionInit")) {
|
|
Py::Callable call(cmd.getAttr("OnActionInit"));
|
|
Py::Tuple args;
|
|
Py::Object ret = call.apply(args);
|
|
}
|
|
}
|
|
catch (Py::Exception& e) {
|
|
Base::PyGILStateLocker lock;
|
|
e.clear();
|
|
}
|
|
|
|
connPyCmdInitialized.disconnect();
|
|
}
|
|
|
|
//===========================================================================
|
|
// CommandManager
|
|
//===========================================================================
|
|
|
|
CommandManager::CommandManager() = default;
|
|
|
|
CommandManager::~CommandManager()
|
|
{
|
|
clearCommands();
|
|
}
|
|
|
|
void CommandManager::addCommand(Command* pCom)
|
|
{
|
|
auto& cmd = _sCommands[pCom->getName()];
|
|
if (cmd) {
|
|
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
|
FC_ERR("duplicate command " << pCom->getName());
|
|
}
|
|
return;
|
|
}
|
|
++_revision;
|
|
cmd = pCom;
|
|
signalChanged();
|
|
}
|
|
|
|
void CommandManager::removeCommand(Command* pCom)
|
|
{
|
|
std::map<std::string, Command*>::iterator It = _sCommands.find(pCom->getName());
|
|
if (It != _sCommands.end()) {
|
|
++_revision;
|
|
delete It->second;
|
|
_sCommands.erase(It);
|
|
signalChanged();
|
|
}
|
|
}
|
|
|
|
std::string CommandManager::newMacroName() const
|
|
{
|
|
CommandManager& commandManager = Application::Instance->commandManager();
|
|
std::vector<Command*> macros = commandManager.getGroupCommands("Macros");
|
|
|
|
bool used = true;
|
|
int id = 0;
|
|
std::string name;
|
|
while (used) {
|
|
used = false;
|
|
std::ostringstream test_name;
|
|
test_name << "Std_Macro_" << id++;
|
|
|
|
for (const auto& macro : macros) {
|
|
if (test_name.str() == std::string(macro->getName())) {
|
|
used = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!used) {
|
|
name = test_name.str();
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
void CommandManager::clearCommands()
|
|
{
|
|
for (std::map<std::string, Command*>::iterator it = _sCommands.begin(); it != _sCommands.end();
|
|
++it) {
|
|
delete it->second;
|
|
}
|
|
_sCommands.clear();
|
|
++_revision;
|
|
signalChanged();
|
|
}
|
|
|
|
bool CommandManager::addTo(const char* Name, QWidget* pcWidget)
|
|
{
|
|
if (_sCommands.find(Name) == _sCommands.end()) {
|
|
// Print in release mode only a log message instead of an error message to avoid to annoy
|
|
// the user
|
|
#ifdef FC_DEBUG
|
|
Base::Console().error(
|
|
"CommandManager::addTo() try to add an unknown command (%s) to a widget!\n",
|
|
Name
|
|
);
|
|
#else
|
|
Base::Console().warning("Unknown command '%s'\n", Name);
|
|
#endif
|
|
return false;
|
|
}
|
|
else {
|
|
Command* pCom = _sCommands[Name];
|
|
pCom->addTo(pcWidget);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::vector<Command*> CommandManager::getModuleCommands(const char* sModName) const
|
|
{
|
|
std::vector<Command*> vCmds;
|
|
|
|
for (std::map<std::string, Command*>::const_iterator It = _sCommands.begin();
|
|
It != _sCommands.end();
|
|
++It) {
|
|
if (strcmp(It->second->getAppModuleName(), sModName) == 0) {
|
|
vCmds.push_back(It->second);
|
|
}
|
|
}
|
|
|
|
return vCmds;
|
|
}
|
|
|
|
std::vector<Command*> CommandManager::getAllCommands() const
|
|
{
|
|
std::vector<Command*> vCmds;
|
|
|
|
for (std::map<std::string, Command*>::const_iterator It = _sCommands.begin();
|
|
It != _sCommands.end();
|
|
++It) {
|
|
vCmds.push_back(It->second);
|
|
}
|
|
|
|
return vCmds;
|
|
}
|
|
|
|
std::vector<Command*> CommandManager::getGroupCommands(const char* sGrpName) const
|
|
{
|
|
std::vector<Command*> vCmds;
|
|
|
|
for (std::map<std::string, Command*>::const_iterator It = _sCommands.begin();
|
|
It != _sCommands.end();
|
|
++It) {
|
|
if (strcmp(It->second->getGroupName(), sGrpName) == 0) {
|
|
vCmds.push_back(It->second);
|
|
}
|
|
}
|
|
|
|
return vCmds;
|
|
}
|
|
|
|
Command* CommandManager::getCommandByName(const char* sName) const
|
|
{
|
|
std::map<std::string, Command*>::const_iterator it = _sCommands.find(sName);
|
|
return (it != _sCommands.end()) ? it->second : 0;
|
|
}
|
|
|
|
void CommandManager::runCommandByName(const char* sName) const
|
|
{
|
|
Command* pCmd = getCommandByName(sName);
|
|
|
|
if (pCmd) {
|
|
pCmd->invoke(0);
|
|
}
|
|
}
|
|
|
|
void CommandManager::testActive()
|
|
{
|
|
for (std::map<std::string, Command*>::iterator It = _sCommands.begin(); It != _sCommands.end();
|
|
++It) {
|
|
It->second->testActive();
|
|
}
|
|
}
|
|
|
|
void CommandManager::addCommandMode(const char* sContext, const char* sName)
|
|
{
|
|
_sCommandModes[sContext].push_back(sName);
|
|
}
|
|
|
|
void CommandManager::updateCommands(const char* sContext, int mode)
|
|
{
|
|
std::map<std::string, std::list<std::string>>::iterator it = _sCommandModes.find(sContext);
|
|
int rev = _revision;
|
|
if (it != _sCommandModes.end()) {
|
|
for (const auto& jt : it->second) {
|
|
Command* cmd = getCommandByName(jt.c_str());
|
|
if (cmd) {
|
|
cmd->updateAction(mode);
|
|
}
|
|
}
|
|
}
|
|
if (rev != _revision) {
|
|
signalChanged();
|
|
}
|
|
}
|
|
|
|
const Command* Gui::CommandManager::checkAcceleratorForConflicts(
|
|
const char* accel,
|
|
const Command* ignore
|
|
) const
|
|
{
|
|
if (Base::Tools::isNullOrEmpty(accel)) {
|
|
return nullptr;
|
|
}
|
|
|
|
QString newCombo = QString::fromLatin1(accel);
|
|
if (newCombo.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
auto newSequence = QKeySequence::fromString(newCombo);
|
|
if (newSequence.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Does this command shortcut conflict with other commands already defined?
|
|
auto commands = Application::Instance->commandManager().getAllCommands();
|
|
for (const auto& cmd : commands) {
|
|
if (cmd == ignore) {
|
|
continue;
|
|
}
|
|
auto existingAccel = cmd->getAccel();
|
|
if (Base::Tools::isNullOrEmpty(existingAccel)) {
|
|
continue;
|
|
}
|
|
|
|
// Three possible conflict scenarios:
|
|
// 1) Exactly the same combo as another command
|
|
// 2) The new command is a one-char combo that overrides an existing two-char combo
|
|
// 3) The old command is a one-char combo that overrides the new command
|
|
|
|
QString existingCombo = QString::fromLatin1(existingAccel);
|
|
if (existingCombo.isEmpty()) {
|
|
continue;
|
|
}
|
|
auto existingSequence = QKeySequence::fromString(existingCombo);
|
|
if (existingSequence.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
// Exact match
|
|
if (existingSequence == newSequence) {
|
|
return cmd;
|
|
}
|
|
|
|
// If it's not exact, then see if one of the sequences is a partial match for
|
|
// the beginning of the other sequence
|
|
auto numCharsToCheck = std::min(existingSequence.count(), newSequence.count());
|
|
bool firstNMatch = true;
|
|
for (int i = 0; i < numCharsToCheck; ++i) {
|
|
if (newSequence[i] != existingSequence[i]) {
|
|
firstNMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
if (firstNMatch) {
|
|
return cmd;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|