1
/***************************************************************************
2
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
# include <QInputDialog>
27
# include <QMessageBox>
29
# include <QSignalBlocker>
30
# include <QTextStream>
33
#include <App/Document.h>
34
#include <Base/Interpreter.h>
36
#include "DlgMacroExecuteImp.h"
37
#include "ui_DlgMacroExecute.h"
38
#include "Application.h"
39
#include "BitmapFactory.h"
41
#include "DlgCustomizeImp.h"
42
#include "DlgToolbarsImp.h"
44
#include "EditorView.h"
46
#include "MainWindow.h"
47
#include "PythonEditor.h"
51
using namespace Gui::Dialog;
55
class MacroItem : public QTreeWidgetItem
58
MacroItem(QTreeWidget * widget, bool systemwide)
59
: QTreeWidgetItem(widget),
60
systemWide(systemwide){}
62
~MacroItem() override = default;
70
/* TRANSLATOR Gui::Dialog::DlgMacroExecuteImp */
73
* Constructs a DlgMacroExecuteImp which is a child of 'parent', with the
74
* name 'name' and widget flags set to 'f'
76
* The dialog will by default be modeless, unless you set 'modal' to
77
* true to construct a modal dialog.
79
DlgMacroExecuteImp::DlgMacroExecuteImp( QWidget* parent, Qt::WindowFlags fl )
80
: QDialog( parent, fl )
81
, WindowParameter( "Macro" )
82
, ui(new Ui_DlgMacroExecute)
84
watcher = std::make_unique<PythonTracingWatcher>(this);
88
// retrieve the macro path from parameter or use the user data as default
90
QSignalBlocker blocker(ui->fileChooser);
91
std::string path = getWindowParameter()->GetASCII("MacroPath",
92
App::Application::getUserMacroDir().c_str());
93
this->macroPath = QString::fromUtf8(path.c_str());
94
ui->fileChooser->setFileName(this->macroPath);
98
QStringList labels; labels << tr("Macros");
99
ui->userMacroListBox->setHeaderLabels(labels);
100
ui->userMacroListBox->header()->hide();
101
ui->systemMacroListBox->setHeaderLabels(labels);
102
ui->systemMacroListBox->header()->hide();
104
ui->LineEditFind->setFocus();
108
* Destroys the object and frees any allocated resources
110
DlgMacroExecuteImp::~DlgMacroExecuteImp() = default;
112
void DlgMacroExecuteImp::setupConnections()
114
connect(ui->fileChooser, &FileChooser::fileNameChanged,
115
this, &DlgMacroExecuteImp::onFileChooserFileNameChanged);
116
connect(ui->createButton, &QPushButton::clicked,
117
this, &DlgMacroExecuteImp::onCreateButtonClicked);
118
connect(ui->deleteButton, &QPushButton::clicked,
119
this, &DlgMacroExecuteImp::onDeleteButtonClicked);
120
connect(ui->editButton, &QPushButton::clicked,
121
this, &DlgMacroExecuteImp::onEditButtonClicked);
122
connect(ui->renameButton, &QPushButton::clicked,
123
this, &DlgMacroExecuteImp::onRenameButtonClicked);
124
connect(ui->duplicateButton, &QPushButton::clicked,
125
this, &DlgMacroExecuteImp::onDuplicateButtonClicked);
126
connect(ui->toolbarButton, &QPushButton::clicked,
127
this, &DlgMacroExecuteImp::onToolbarButtonClicked);
128
connect(ui->addonsButton, &QPushButton::clicked,
129
this, &DlgMacroExecuteImp::onAddonsButtonClicked);
130
connect(ui->userMacroListBox, &QTreeWidget::currentItemChanged,
131
this, &DlgMacroExecuteImp::onUserMacroListBoxCurrentItemChanged);
132
connect(ui->systemMacroListBox, &QTreeWidget::currentItemChanged,
133
this, &DlgMacroExecuteImp::onSystemMacroListBoxCurrentItemChanged);
134
connect(ui->tabMacroWidget, &QTabWidget::currentChanged,
135
this, &DlgMacroExecuteImp::onTabMacroWidgetCurrentChanged);
136
connect(ui->LineEditFind, &QLineEdit::textChanged,
137
this, &DlgMacroExecuteImp::onLineEditFindTextChanged);
138
connect(ui->LineEditFindInFiles, &QLineEdit::textChanged,
139
this, &DlgMacroExecuteImp::onLineEditFindInFilesTextChanged);
142
/** Take a folder and return a StringList of the filenames in it
143
* filtered by both filename *and* by content, if the user has
144
* put text in one or both of the search line edits.
146
* First filtering is done by file name, which reduces the
147
* number of files to open and read (relatively expensive operation).
149
* Then we filter by file content after reducing the number of files
150
* to open and read. But both loops are skipped if there is no text
151
* in either of the line edits.
153
* We do this as another function in order to reuse this code for
154
* doing both the User and System macro list boxes in the fillUpList() function.
157
QStringList DlgMacroExecuteImp::filterFiles(const QString& folder){
158
QDir dir(folder, QLatin1String("*.FCMacro *.py"));
159
QStringList unfiltered = dir.entryList(); //all .fcmacro and .py files
160
QString fileFilter = ui->LineEditFind->text(); //used to search by filename
161
QString searchText = ui->LineEditFindInFiles->text(); //used to search in file content
163
if (fileFilter.isEmpty() && searchText.isEmpty()){ //skip filtering if no filters
166
QStringList filteredByFileName;
167
if (fileFilter.isEmpty()){
168
filteredByFileName = unfiltered; //skip the loop if no file filter
170
QRegularExpression regexFileName(fileFilter, QRegularExpression::CaseInsensitiveOption);
171
bool isValidFileFilter = regexFileName.isValid(); //check here instead of inside the loop
172
for (auto uf : unfiltered){
173
if (isValidFileFilter){
174
if (regexFileName.match(uf).hasMatch()) {
175
filteredByFileName.append(uf);
177
} else { //not valid, so do a simple text search
178
if (uf.contains(fileFilter, Qt::CaseInsensitive)) {
179
filteredByFileName.append(uf);
185
if (searchText.isEmpty()){ //skip reading file contents if no find in file filter
186
return filteredByFileName;
189
QRegularExpression regexContent(searchText, QRegularExpression::CaseInsensitiveOption);
190
bool isValidContentFilter = regexContent.isValid();
191
QStringList filteredByContent;
192
for (auto fn : filteredByFileName) {
193
const QString &fileName = fn;
194
QString filePath = dir.filePath(fileName);
195
QFile file(filePath);
196
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
197
QTextStream in(&file);
198
QString fileContent = in.readAll();
199
if (isValidContentFilter){
200
if (regexContent.match(fileContent).hasMatch()) {
201
filteredByContent.append(fileName);
204
if (fileContent.contains(searchText, Qt::CaseInsensitive)){
205
filteredByContent.append(fileName);
211
return filteredByContent;
215
* Fills up the list with macro files found in the specified location
216
* that have been filtered by both filename and by content
218
void DlgMacroExecuteImp::fillUpList()
220
QStringList filteredByContent = this->filterFiles(this->macroPath);
221
ui->userMacroListBox->clear();
222
for (auto fn : filteredByContent){
223
auto item = new MacroItem(ui->userMacroListBox,false);
224
item->setText(0, fn);
227
QString dirstr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro");
228
filteredByContent = this->filterFiles(dirstr);
230
ui->systemMacroListBox->clear();
231
for (auto fn : filteredByContent) {
232
auto item = new MacroItem(ui->systemMacroListBox,true);
233
item->setText(0, fn);
239
* Selects a macro file in the list view.
241
void DlgMacroExecuteImp::onUserMacroListBoxCurrentItemChanged(QTreeWidgetItem* item)
244
ui->LineEditMacroName->setText(item->text(0));
246
ui->executeButton->setEnabled(true);
247
ui->deleteButton->setEnabled(true);
248
ui->toolbarButton->setEnabled(true);
249
ui->createButton->setEnabled(true);
250
ui->editButton->setEnabled(true);
251
ui->renameButton->setEnabled(true);
252
ui->duplicateButton->setEnabled(true);
255
ui->executeButton->setEnabled(false);
256
ui->deleteButton->setEnabled(false);
257
ui->toolbarButton->setEnabled(false);
258
ui->createButton->setEnabled(true);
259
ui->editButton->setEnabled(false);
260
ui->renameButton->setEnabled(false);
261
ui->duplicateButton->setEnabled(false);
265
void DlgMacroExecuteImp::onLineEditFindTextChanged(const QString &text){
270
void DlgMacroExecuteImp::onLineEditFindInFilesTextChanged(const QString &text){
275
void DlgMacroExecuteImp::onSystemMacroListBoxCurrentItemChanged(QTreeWidgetItem* item)
278
ui->LineEditMacroName->setText(item->text(0));
280
ui->executeButton->setEnabled(true);
281
ui->deleteButton->setEnabled(false);
282
ui->toolbarButton->setEnabled(false);
283
ui->createButton->setEnabled(false);
284
ui->editButton->setEnabled(true); //look but don't touch
285
ui->renameButton->setEnabled(false);
286
ui->duplicateButton->setEnabled(false);
289
ui->executeButton->setEnabled(false);
290
ui->deleteButton->setEnabled(false);
291
ui->toolbarButton->setEnabled(false);
292
ui->createButton->setEnabled(false);
293
ui->editButton->setEnabled(false);
294
ui->renameButton->setEnabled(false);
295
ui->duplicateButton->setEnabled(false);
299
void DlgMacroExecuteImp::onTabMacroWidgetCurrentChanged(int index)
301
QTreeWidgetItem* item;
303
if (index == 0) { //user-specific
304
item = ui->userMacroListBox->currentItem();
306
ui->executeButton->setEnabled(true);
307
ui->deleteButton->setEnabled(true);
308
ui->toolbarButton->setEnabled(true);
309
ui->createButton->setEnabled(true);
310
ui->editButton->setEnabled(true);
311
ui->renameButton->setEnabled(true);
312
ui->duplicateButton->setEnabled(true);
315
ui->executeButton->setEnabled(false);
316
ui->deleteButton->setEnabled(false);
317
ui->toolbarButton->setEnabled(false);
318
ui->createButton->setEnabled(true);
319
ui->editButton->setEnabled(false);
320
ui->renameButton->setEnabled(false);
321
ui->duplicateButton->setEnabled(false);
324
else { //index==1 system-wide
325
item = ui->systemMacroListBox->currentItem();
328
ui->executeButton->setEnabled(true);
329
ui->deleteButton->setEnabled(false);
330
ui->toolbarButton->setEnabled(false);
331
ui->createButton->setEnabled(false);
332
ui->editButton->setEnabled(true); //but you can't save it
333
ui->renameButton->setEnabled(false);
334
ui->duplicateButton->setEnabled(false);
337
ui->executeButton->setEnabled(false);
338
ui->deleteButton->setEnabled(false);
339
ui->toolbarButton->setEnabled(false);
340
ui->createButton->setEnabled(false);
341
ui->editButton->setEnabled(false);
342
ui->renameButton->setEnabled(false);
343
ui->duplicateButton->setEnabled(false);
348
ui->LineEditMacroName->setText(item->text(0));
351
ui->LineEditMacroName->clear();
356
* Executes the selected macro file.
358
void DlgMacroExecuteImp::accept()
360
QTreeWidgetItem* item;
362
int index = ui->tabMacroWidget->currentIndex();
363
if (index == 0) { //user-specific
364
item = ui->userMacroListBox->currentItem();
367
//index == 1 system-wide
368
item = ui->systemMacroListBox->currentItem();
375
auto mitem = static_cast<MacroItem *>(item);
379
if (!mitem->systemWide){
380
dir =QDir(this->macroPath);
383
QString dirstr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro");
387
QFileInfo fi(dir, item->text(0));
389
getMainWindow()->setCursor(Qt::WaitCursor);
390
PythonTracingLocker tracelock(watcher->getTrace());
392
getMainWindow()->appendRecentMacro(fi.filePath());
393
Application::Instance->macroManager()->run(Gui::MacroManager::File, fi.filePath().toUtf8());
394
// after macro run recalculate the document
395
if (Application::Instance->activeDocument())
396
Application::Instance->activeDocument()->getDocument()->recompute();
397
getMainWindow()->unsetCursor();
399
catch (const Base::SystemExitException&) {
400
// handle SystemExit exceptions
401
Base::PyGILStateLocker locker;
404
getMainWindow()->unsetCursor();
409
* Specify the location of your macro files. The default location is FreeCAD's home path.
411
void DlgMacroExecuteImp::onFileChooserFileNameChanged(const QString& fn)
415
// save the path in the parameters
416
this->macroPath = fn;
417
getWindowParameter()->SetASCII("MacroPath",fn.toUtf8());
424
* Opens the macro file in an editor.
426
void DlgMacroExecuteImp::onEditButtonClicked()
429
QTreeWidgetItem* item = nullptr;
431
int index = ui->tabMacroWidget->currentIndex();
432
if (index == 0) { //user-specific
433
item = ui->userMacroListBox->currentItem();
434
dir.setPath(this->macroPath);
437
//index == 1 system-wide
438
item = ui->systemMacroListBox->currentItem();
439
dir.setPath(QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro"));
445
auto mitem = static_cast<MacroItem *>(item);
447
QString file = QString::fromLatin1("%1/%2").arg(dir.absolutePath(), item->text(0));
448
auto editor = new PythonEditor();
449
editor->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
450
auto edit = new PythonEditorView(editor, getMainWindow());
451
edit->setDisplayName(PythonEditorView::FileName);
453
edit->resize(400, 300);
454
getMainWindow()->addWindow(edit);
455
getMainWindow()->appendRecentMacro(file);
457
if (mitem->systemWide) {
458
editor->setReadOnly(true);
460
shownName = QString::fromLatin1("%1[*] - [%2]").arg(item->text(0), tr("Read-only"));
461
edit->setWindowTitle(shownName);
466
/** Creates a new macro file. */
467
void DlgMacroExecuteImp::onCreateButtonClicked()
470
bool replaceSpaces = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("ReplaceSpaces", true);
471
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("ReplaceSpaces", replaceSpaces); //create parameter
473
QString fn = QInputDialog::getText(this, tr("Macro file"), tr("Enter a file name, please:"),
474
QLineEdit::Normal, QString(), nullptr, Qt::MSWindowsFixedSizeDialogHint);
477
fn = fn.replace(QString::fromStdString(" "),QString::fromStdString("_"));
482
QString suffix = QFileInfo(fn).suffix().toLower();
483
if (suffix != QLatin1String("fcmacro") && suffix != QLatin1String("py"))
484
fn += QLatin1String(".FCMacro");
485
QDir dir(this->macroPath);
486
// create the macroPath if nonexistent
488
dir.mkpath(this->macroPath);
490
QFileInfo fi(dir, fn);
491
if (fi.exists() && fi.isFile())
493
QMessageBox::warning(this, tr("Existing file"),
494
tr("'%1'.\nThis file already exists.").arg(fi.fileName()));
498
QFile file(fi.absoluteFilePath());
499
if (!file.open(QFile::WriteOnly)) {
500
QMessageBox::warning(this, tr("Cannot create file"),
501
tr("Creation of file '%1' failed.").arg(fi.absoluteFilePath()));
505
auto editor = new PythonEditor();
506
editor->setWindowIcon(Gui::BitmapFactory().iconFromTheme("applications-python"));
507
auto edit = new PythonEditorView(editor, getMainWindow());
508
edit->open(fi.absoluteFilePath());
509
getMainWindow()->appendRecentMacro(fi.absoluteFilePath());
510
edit->setWindowTitle(QString::fromLatin1("%1[*]").arg(fn));
511
edit->resize(400, 300);
512
getMainWindow()->addWindow(edit);
518
/** Deletes the selected macro file from your harddisc. */
519
void DlgMacroExecuteImp::onDeleteButtonClicked()
521
QTreeWidgetItem* item = ui->userMacroListBox->currentItem();
525
auto mitem = static_cast<MacroItem *>(item);
527
if (mitem->systemWide) {
528
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Delete macro"),
529
QObject::tr("Not allowed to delete system-wide macros"));
533
QString fn = item->text(0);
534
auto ret = QMessageBox::question(this, tr("Delete macro"),
535
tr("Do you really want to delete the macro '%1'?").arg( fn ),
536
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
537
if (ret == QMessageBox::Yes) {
538
QDir dir(this->macroPath);
540
int index = ui->userMacroListBox->indexOfTopLevelItem(item);
541
ui->userMacroListBox->takeTopLevelItem(index);
547
* Walk user through process of adding macro to global custom toolbar
548
* We create a custom customize dialog with instructions embedded
549
* within the dialog itself for the user, and the buttons to push in red text
550
* There are 2 dialogs we need to create: the macros dialog and the
554
void DlgMacroExecuteImp::onToolbarButtonClicked()
557
* advise user of what we are doing, offer chance to cancel
558
* unless user already said not to show this messagebox again
561
bool showAgain = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("ShowWalkthroughMessage", true);
564
QAbstractButton* doNotShowAgainButton = msgBox.addButton(tr("Do not show again"), QMessageBox::YesRole);
565
msgBox.setText(tr("Guided Walkthrough"));
566
msgBox.setInformativeText(tr("This will guide you in setting up this macro in a custom \
567
global toolbar. Instructions will be in red text inside the dialog.\n\
569
Note: your changes will be applied when you next switch workbenches\n"));
570
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
571
msgBox.setDefaultButton(QMessageBox::Ok);
572
int result = msgBox.exec();
573
if (result == QMessageBox::Cancel){
576
if (msgBox.clickedButton() == doNotShowAgainButton){
577
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("ShowWalkthroughMessage",false);
581
QTreeWidgetItem* item = ui->userMacroListBox->currentItem();
585
QString fn = item->text(0);
586
QString bareFileName = QFileInfo(fn).baseName(); //for use as default menu text (filename without extension)
588
/** check if user already has custom toolbar, so we can tailor instructions accordingly **/
589
bool hasCustomToolbar = true;
590
if (App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Workbench/Global/Toolbar")->GetGroups().empty()) {
591
hasCustomToolbar=false;
594
/** check if user already has this macro command created, if so skip dialog 1 **/
595
bool hasMacroCommand = false;
596
QString macroMenuText;
597
CommandManager & cCmdMgr = Application::Instance->commandManager();
598
std::vector<Command*> aCmds = cCmdMgr.getGroupCommands("Macros");
599
for (const auto & aCmd : aCmds) {
600
auto mc = dynamic_cast<MacroCommand*>(aCmd);
601
if (mc && fn.compare(QLatin1String(mc->getScriptName())) == 0) {
602
hasMacroCommand = true;
603
macroMenuText = QString::fromLatin1(mc->getMenuText());
607
QTabWidget* tabWidget = nullptr;
609
if (!hasMacroCommand){
610
/** first the custom macros page dialog **/
611
Gui::Dialog::DlgCustomizeImp dlg(this);
613
/** title is normally "Customize" **/
614
dlg.setWindowTitle(tr("Walkthrough, dialog 1 of 2"));
616
tabWidget = dlg.findChild<QTabWidget*>(QString::fromLatin1("Gui__Dialog__TabWidget"));
618
std::cerr << "Toolbar walkthrough error: Unable to find tabwidget" << std::endl;
622
auto setupCustomMacrosPage = tabWidget->findChild<QWidget*>(QString::fromLatin1("Gui__Dialog__DlgCustomActions"));
623
if (!setupCustomMacrosPage) {
624
std::cerr << "Toolbar walkthrough error: Unable to find setupCustomMacrosPage" << std::endl;
627
tabWidget->setCurrentWidget(setupCustomMacrosPage);
629
auto groupBox7 = setupCustomMacrosPage->findChild<QGroupBox*>(QString::fromLatin1("GroupBox7"));
631
Base::Console().Warning("Toolbar walkthrough: Unable to find groupBox7\n");
632
//just warn when not a fatal error
634
/** normally the groupbox title is "Setup Custom Macros", but we change it here **/
635
groupBox7->setTitle(tr("Walkthrough instructions: Fill in missing fields (optional) then click Add, then Close"));
636
groupBox7->setStyleSheet(QString::fromLatin1("QGroupBox::title {color:red}"));
639
auto buttonAddAction = setupCustomMacrosPage->findChild<QPushButton*>(QString::fromLatin1("buttonAddAction"));
640
if (!buttonAddAction) {
641
Base::Console().Warning("Toolbar walkthrough: Unable to find buttonAddAction\n");
643
buttonAddAction->setStyleSheet(QString::fromLatin1("color:red"));
646
auto macroListBox = setupCustomMacrosPage->findChild<QComboBox*>(QString::fromLatin1("actionMacros"));
648
Base::Console().Warning("Toolbar walkthrough: Unable to find actionMacros combo box\n");
650
int macroIndex = macroListBox->findText(fn); //fn is the macro filename
651
macroListBox->setCurrentIndex(macroIndex); //select it for the user so they don't have to
654
auto menuText = setupCustomMacrosPage->findChild<QLineEdit*>(QString::fromLatin1("actionMenu"));
656
Base::Console().Warning("Toolbar walkthrough: Unable to find actionMenu menuText\n");
658
menuText->setText(bareFileName); //user can fill in other fields, e.g. tooltip
664
/** now for the toolbar selection dialog **/
666
Gui::Dialog::DlgCustomizeImp dlg(this);
668
if (hasMacroCommand){
669
dlg.setWindowTitle(tr("Walkthrough, dialog 1 of 1"));
671
dlg.setWindowTitle(tr("Walkthrough, dialog 2 of 2"));
675
tabWidget = dlg.findChild<QTabWidget*>(QString::fromLatin1("Gui__Dialog__TabWidget"));
677
std::cerr << "Toolbar walkthrough: Unable to find tabWidget Gui__Dialog__TabWidget" << std::endl;
681
auto setupToolbarPage = tabWidget->findChild<DlgCustomToolbars*>(QString::fromLatin1("Gui__Dialog__DlgCustomToolbars"));
682
if (!setupToolbarPage){
683
std::cerr << "Toolbar walkthrough: Unable to find setupToolbarPage Gui__Dialog__DlgCustomToolbars" << std::endl;
687
tabWidget->setCurrentWidget(setupToolbarPage);
688
auto moveActionRightButton = setupToolbarPage->findChild<QPushButton*>(QString::fromLatin1("moveActionRightButton"));
689
if (!moveActionRightButton){
690
Base::Console().Warning("Toolbar walkthrough: Unable to find moveActionRightButton\n");
692
moveActionRightButton->setStyleSheet(QString::fromLatin1("background-color: red"));
694
/** tailor instructions depending on whether user already has custom toolbar created
695
* if not, they need to click New button to create one first
698
QString instructions2 = tr("Walkthrough instructions: Click right arrow button (->), then Close.");
699
auto workbenchBox = setupToolbarPage->findChild<QComboBox*>(QString::fromLatin1("workbenchBox"));
701
Base::Console().Warning("Toolbar walkthrough: Unable to find workbenchBox\n");
704
/** find the Global workbench and select it for the user **/
706
int globalIdx = workbenchBox->findData(QString::fromLatin1("Global"));
707
if (globalIdx != -1){
708
workbenchBox->setCurrentIndex(globalIdx);
709
QMetaObject::invokeMethod(setupToolbarPage, "on_workbenchBox_activated",
710
Qt::DirectConnection,
711
Q_ARG(int, globalIdx));
713
Base::Console().Warning("Toolbar walkthrough: Unable to find Global workbench\n");
716
if (!hasCustomToolbar){
717
auto newButton = setupToolbarPage->findChild<QPushButton*>(QString::fromLatin1("newButton"));
719
Base::Console().Warning("Toolbar walkthrough: Unable to find newButton\n");
721
newButton->setStyleSheet(QString::fromLatin1("color:red"));
722
instructions2 = tr("Walkthrough instructions: Click New, then right arrow (->) button, then Close.");
726
/** "label" normally says "Note: the changes become active the next time you load the appropriate workbench" **/
728
auto label = setupToolbarPage->findChild<QLabel*>(QString::fromLatin1("label"));
730
Base::Console().Warning("Toolbar walkthrough: Unable to find label\n");
732
label->setText(instructions2);
733
label->setStyleSheet(QString::fromLatin1("color:red"));
736
/** find Macros category and select it for the user **/
737
auto categoryBox = setupToolbarPage->findChild<QComboBox*>(QString::fromLatin1("categoryBox"));
739
Base::Console().Warning("Toolbar walkthrough: Unable to find categoryBox\n");
741
int macrosIdx = categoryBox->findText(tr("Macros"));
742
if (macrosIdx != -1){
743
categoryBox->setCurrentIndex(macrosIdx);
744
QMetaObject::invokeMethod(setupToolbarPage, "on_categoryBox_activated",
745
Qt::DirectConnection,
746
Q_ARG(int, macrosIdx));
748
Base::Console().Warning("Toolbar walkthrough: Unable to find Macros in categoryBox\n");
752
/** expand custom toolbar items **/
753
auto toolbarTreeWidget = setupToolbarPage->findChild<QTreeWidget*>(QString::fromLatin1("toolbarTreeWidget"));
754
if (!toolbarTreeWidget) {
755
Base::Console().Warning("Toolbar walkthrough: Unable to find toolbarTreeWidget\n");
757
toolbarTreeWidget->expandAll();
760
/** preselect macro command for user **/
762
auto commandTreeWidget = setupToolbarPage->findChild<QTreeWidget*>(QString::fromLatin1("commandTreeWidget"));
763
if (!commandTreeWidget) {
764
Base::Console().Warning("Toolbar walkthrough: Unable to find commandTreeWidget\n");
766
if (!hasMacroCommand){ //will be the last in the list, the one just created
767
commandTreeWidget->setCurrentItem(commandTreeWidget->topLevelItem(commandTreeWidget->topLevelItemCount()-1));
768
commandTreeWidget->scrollToItem(commandTreeWidget->currentItem());
769
} else { //pre-select it for the user (will be the macro menu text)
770
QList <QTreeWidgetItem*> items = commandTreeWidget->findItems(macroMenuText, Qt::MatchFixedString | Qt::MatchWrap,1);
771
if (!items.empty()) {
772
commandTreeWidget->setCurrentItem(items[0]);
773
commandTreeWidget->scrollToItem(commandTreeWidget->currentItem());
783
* renames the selected macro
785
void DlgMacroExecuteImp::onRenameButtonClicked()
788
QTreeWidgetItem* item = nullptr;
790
int index = ui->tabMacroWidget->currentIndex();
791
if (index == 0) { //user-specific
792
item = ui->userMacroListBox->currentItem();
793
dir.setPath(this->macroPath);
799
bool replaceSpaces = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("ReplaceSpaces", true);
800
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("ReplaceSpaces", replaceSpaces); //create parameter
802
QString oldName = item->text(0);
803
QFileInfo oldfi(dir, oldName);
804
QFile oldfile(oldfi.absoluteFilePath());
807
QString fn = QInputDialog::getText(this, tr("Renaming Macro File"),
808
tr("Enter new name:"), QLineEdit::Normal, oldName, nullptr, Qt::MSWindowsFixedSizeDialogHint);
811
fn = fn.replace(QString::fromStdString(" "),QString::fromStdString("_"));
814
if (!fn.isEmpty() && fn != oldName) {
815
QString suffix = QFileInfo(fn).suffix().toLower();
816
if (suffix != QLatin1String("fcmacro") && suffix != QLatin1String("py"))
817
fn += QLatin1String(".FCMacro");
818
QFileInfo fi(dir, fn);
819
// check if new name exists
821
QMessageBox::warning(this, tr("Existing file"),
822
tr("'%1'\n already exists.").arg(fi.absoluteFilePath()));
824
else if (!oldfile.rename(fi.absoluteFilePath())) {
825
QMessageBox::warning(this, tr("Rename Failed"),
826
tr("Failed to rename to '%1'.\nPerhaps a file permission error?").arg(fi.absoluteFilePath()));
829
// keep the item selected although it's not necessarily in alphabetic order
830
item->setText(0, fn);
831
ui->LineEditMacroName->setText(fn);
836
/**Duplicates selected macro
837
* New file has same name as original but with "@" and 3-digit number appended
838
* Begins with "@001" and increments until available name is found
839
* "MyMacro.FCMacro" becomes "MyMacro@001.FCMacro"
840
* "MyMacro@002.FCMacro.py" becomes "MyMacro@003.FCMacro.py" unless there is
841
* no already existing "MyMacro@001.FCMacro.py"
843
void DlgMacroExecuteImp::onDuplicateButtonClicked()
846
QTreeWidgetItem* item = nullptr;
848
//When duplicating a macro we can either begin trying to find a unique name with @001 or begin with the current @NNN if applicable
850
bool from001 = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("DuplicateFrom001", false);
851
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("DuplicateFrom001", from001); //create parameter
853
//A user may wish to add a note to end of the filename when duplicating
854
//example: mymacro@005.fix_bug_in_dialog.FCMacro
855
//and then when duplicating to have the extra note removed so the suggested new name is:
856
//mymacro@006.FCMacro instead of mymacro@006.fix_bug_in_dialog.FCMacro since the new duplicate will be given a new note
858
bool ignoreExtra = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("DuplicateIgnoreExtraNote", false);
859
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("DuplicateIgnoreExtraNote", ignoreExtra); //create parameter
861
//when creating a note it will be convenient to convert spaces to underscores if the user desires this behavior
863
bool replaceSpaces = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->GetBool("ReplaceSpaces", true);
864
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro")->SetBool("ReplaceSpaces", replaceSpaces); //create parameter
866
int index = ui->tabMacroWidget->currentIndex();
867
if (index == 0) { //user-specific
868
item = ui->userMacroListBox->currentItem();
869
dir.setPath(this->macroPath);
876
QString oldName = item->text(0);
877
QFileInfo oldfi(dir, oldName);
878
QFile oldfile(oldfi.absoluteFilePath());
879
QString completeSuffix = oldfi.completeSuffix(); //everything after the first "."
880
QString extraNote = completeSuffix.left(completeSuffix.size()-oldfi.suffix().size());
881
QString baseName = oldfi.baseName(); //everything before first "."
882
QString neutralSymbol = QString::fromStdString("@");
883
QString last3 = baseName.right(3);
884
bool ok = true; //was conversion to int successful?
885
int nLast3 = last3.toInt(&ok);
886
last3 = QString::fromStdString("001"); //increment beginning with 001 unless from001 = false
888
//last3 were all digits, so we strip them from the base name
889
if (baseName.size()>3){ //if <= 3 leave be (e.g. 2.py becomes 2@001.py)
891
last3 = baseName.right(3); //use these instead of 001
893
baseName = baseName.left(baseName.size()-3); //strip digits
894
if (baseName.endsWith(neutralSymbol)){
895
baseName = baseName.left(baseName.size()-1); //trim the "@", will be added back later
899
//at this point baseName = the base name without any digits, e.g. "MyMacro"
900
//neutralSymbol = "@"
901
//last3 is a string representing 3 digits, always "001"
902
//unless from001 = false, in which case we begin with previous numbers
903
//completeSuffix = FCMacro or py or FCMacro.py or else suffix will become FCMacro below
904
//if ignoreExtra any extra notes added between @NN. and .FCMacro will be ignored
905
//when suggesting a new filename
907
if(ignoreExtra && !extraNote.isEmpty()){
909
last3 = QString::number(nLast3);
910
while (last3.size()<3){
911
last3.prepend(QString::fromStdString("0")); //pad 0's if needed
916
QString oldNameDigitized = baseName+neutralSymbol+last3+QString::fromStdString(".")+completeSuffix;
917
QFileInfo fi(dir, oldNameDigitized);
920
// increment until we find available name with smallest digits
921
// test from "001" through "999", then give up and let user enter name of choice
923
while (fi.exists()) {
924
nLast3 = last3.toInt()+1;
925
if (nLast3 >=1000){ //avoid infinite loop, 999 files will have to be enough
928
last3 = QString::number(nLast3);
929
while (last3.size()<3){
930
last3.prepend(QString::fromStdString("0")); //pad 0's if needed
932
oldNameDigitized = baseName+neutralSymbol+last3+QString::fromStdString(".")+completeSuffix;
933
fi = QFileInfo(dir,oldNameDigitized);
936
if(ignoreExtra && !extraNote.isEmpty()){
937
oldNameDigitized = oldNameDigitized.remove(extraNote);
940
// give user a chance to pick a different name from digitized name suggested
941
QString fn = QInputDialog::getText(this, tr("Duplicate Macro"),
942
tr("Enter new name:"), QLineEdit::Normal, oldNameDigitized,
943
nullptr, Qt::MSWindowsFixedSizeDialogHint);
945
fn = fn.replace(QString::fromStdString(" "),QString::fromStdString("_"));
947
if (!fn.isEmpty() && fn != oldName) {
948
QString suffix = QFileInfo(fn).suffix().toLower();
949
if (suffix != QLatin1String("fcmacro") && suffix != QLatin1String("py")){
950
fn += QLatin1String(".FCMacro");
952
QFileInfo fi(dir, fn);
953
// check again if new name exists in case user changed it
955
QMessageBox::warning(this, tr("Existing file"),
956
tr("'%1'\n already exists.").arg(fi.absoluteFilePath()));
958
else if (!oldfile.copy(fi.absoluteFilePath())) {
959
QMessageBox::warning(this, tr("Duplicate Failed"),
960
tr("Failed to duplicate to '%1'.\nPerhaps a file permission error?").arg(fi.absoluteFilePath()));
963
this->fillUpList(); //repopulate list to show new file
969
* convenience link button to open tools -> addon manager
970
* from within macro dialog
972
void DlgMacroExecuteImp::onAddonsButtonClicked()
974
CommandManager& rMgr=Application::Instance->commandManager();
975
rMgr.runCommandByName("Std_AddonMgr");
979
#include "moc_DlgMacroExecuteImp.cpp"