mirror of https://github.com/KLayout/klayout.git
3705 lines
107 KiB
C++
3705 lines
107 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2022 Matthias Koefferlein
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
#include "ui_MacroTemplateSelectionDialog.h"
|
|
#include "layConfigurationDialog.h"
|
|
#include "layMacroController.h"
|
|
#include "layMacroEditorTree.h"
|
|
#include "layMacroEditorDialog.h"
|
|
#include "layMacroEditorSetupPage.h"
|
|
#include "layMacroPropertiesDialog.h"
|
|
#include "layFileDialog.h"
|
|
#include "layMainWindow.h"
|
|
#include "layHelpDialog.h"
|
|
#include "layProgressWidget.h"
|
|
#include "layApplication.h"
|
|
#include "layBrowserPanel.h"
|
|
#include "layTipDialog.h"
|
|
#include "layQtTools.h"
|
|
#include "layConfig.h"
|
|
#include "layWidgets.h"
|
|
#include "layProgress.h"
|
|
#include "lymMacroInterpreter.h"
|
|
#include "tlString.h"
|
|
#include "tlClassRegistry.h"
|
|
#include "tlExceptions.h"
|
|
#include "tlFileUtils.h"
|
|
#include "tlInclude.h"
|
|
|
|
#include <cstdio>
|
|
#include <limits>
|
|
#include <set>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <memory>
|
|
|
|
#include <QMessageBox>
|
|
#include <QKeyEvent>
|
|
#include <QItemDelegate>
|
|
#include <QInputDialog>
|
|
#include <QFileDialog>
|
|
#include <QHeaderView>
|
|
#include <QResource>
|
|
|
|
namespace lay
|
|
{
|
|
|
|
const std::string cfg_macro_editor_styles ("macro-editor-styles");
|
|
const std::string cfg_macro_editor_save_all_on_run ("macro-editor-save-all-on-run");
|
|
const std::string cfg_macro_editor_stop_on_exception ("macro-editor-stop-on-exception");
|
|
const std::string cfg_macro_editor_file_watcher_enabled ("macro-editor-file-watcher-enabled");
|
|
const std::string cfg_macro_editor_font_family ("macro-editor-font-family");
|
|
const std::string cfg_macro_editor_font_size ("macro-editor-font-size");
|
|
const std::string cfg_macro_editor_tab_width ("macro-editor-tab-width");
|
|
const std::string cfg_macro_editor_indent ("macro-editor-indent");
|
|
const std::string cfg_macro_editor_window_state ("macro-editor-window-state");
|
|
const std::string cfg_macro_editor_console_mru ("macro-editor-console-mru");
|
|
const std::string cfg_macro_editor_console_interpreter ("macro-editor-console-interpreter");
|
|
const std::string cfg_macro_editor_open_macros ("macro-editor-open-macros");
|
|
const std::string cfg_macro_editor_current_macro ("macro-editor-current-macro");
|
|
const std::string cfg_macro_editor_active_macro ("macro-editor-active-macro");
|
|
const std::string cfg_macro_editor_watch_expressions ("macro-editor-watch-expressions");
|
|
const std::string cfg_macro_editor_debugging_enabled ("macro-editor-debugging-enabled");
|
|
const std::string cfg_macro_editor_ignore_exception_list ("macro-editor-ignore-exception-list");
|
|
|
|
// -----------------------------------------------------------------------------------------
|
|
// Implementation of the macro template selection dialog
|
|
|
|
class MacroTemplateSelectionDialog
|
|
: public QDialog, private Ui::MacroTemplateSelectionDialog
|
|
{
|
|
public:
|
|
MacroTemplateSelectionDialog (QWidget *parent, const std::vector<lym::Macro *> &templates, const std::string &cat)
|
|
: QDialog (parent)
|
|
{
|
|
setupUi (this);
|
|
|
|
#if QT_VERSION >= 0x040300
|
|
templateView->setWordWrap (true);
|
|
#endif
|
|
templateView->header ()->hide ();
|
|
|
|
m_template_count = 0;
|
|
m_default_id = -1;
|
|
|
|
// Build a tree from the templates. Groups are formed by prepending a group title in the description
|
|
// separated from the actual description by ";;"
|
|
int index = 0;
|
|
for (std::vector<lym::Macro *>::const_iterator t = templates.begin (); t != templates.end (); ++t, ++index) {
|
|
|
|
const std::string &c = (*t)->category ();
|
|
|
|
bool take = false;
|
|
if ((cat.empty () || cat == "macros") && c.empty ()) {
|
|
// take ones without explicit category in "macros" category
|
|
take = true;
|
|
} else if (! c.empty ()) {
|
|
// others are checked whether the category name is part of the category list
|
|
std::vector<std::string> cc = tl::split (c, ",");
|
|
for (std::vector<std::string>::const_iterator ic = cc.begin (); ic != cc.end () && !take; ++ic) {
|
|
if (*ic == cat) {
|
|
take = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! take) {
|
|
continue;
|
|
}
|
|
|
|
std::string group_title;
|
|
std::string description = (*t)->description ();
|
|
if (description.empty ()) {
|
|
description = (*t)->name ();
|
|
}
|
|
|
|
size_t sep = description.find (";;");
|
|
if (sep != std::string::npos) {
|
|
group_title = std::string (description, 0, sep);
|
|
description = std::string (description, sep + 2);
|
|
}
|
|
|
|
QTreeWidgetItem *item = 0;
|
|
if (group_title.empty ()) {
|
|
item = new QTreeWidgetItem (templateView);
|
|
} else {
|
|
QString gt = tl::to_qstring (group_title);
|
|
for (int i = 0; i < templateView->topLevelItemCount (); ++i) {
|
|
if (templateView->topLevelItem (i)->text (0) == gt) {
|
|
item = new QTreeWidgetItem (templateView->topLevelItem (i));
|
|
break;
|
|
}
|
|
}
|
|
if (! item) {
|
|
QTreeWidgetItem *group = new QTreeWidgetItem (templateView);
|
|
group->setText (0, gt);
|
|
QFont f (templateView->font ());
|
|
f.setWeight (QFont::Bold);
|
|
group->setData (0, Qt::FontRole, f);
|
|
item = new QTreeWidgetItem (group);
|
|
}
|
|
}
|
|
|
|
m_default_id = index;
|
|
++m_template_count;
|
|
|
|
item->setData (0, Qt::UserRole, QVariant (index));
|
|
QString qd = tl::to_qstring (description + "\n");
|
|
qd.replace (QString::fromUtf8 ("\\n"), QString::fromUtf8 ("\n"));
|
|
item->setText (0, qd);
|
|
|
|
}
|
|
|
|
templateView->expandAll ();
|
|
}
|
|
|
|
int exec_dialog ()
|
|
{
|
|
templateView->setCurrentItem (0);
|
|
if (m_template_count <= 1) {
|
|
return m_default_id;
|
|
} else if (exec ()) {
|
|
if (templateView->currentItem () && templateView->currentItem ()->data (0, Qt::UserRole) != QVariant ()) {
|
|
return templateView->currentItem ()->data (0, Qt::UserRole).toInt ();
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private:
|
|
int m_default_id;
|
|
size_t m_template_count;
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------------------
|
|
// A custom delegate that uses UserRole + 2 for getting and setting the text
|
|
|
|
class EditRoleDelegate
|
|
: public QItemDelegate
|
|
{
|
|
public:
|
|
EditRoleDelegate (QWidget *parent)
|
|
: QItemDelegate (parent)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void setEditorData (QWidget *widget, const QModelIndex &index) const
|
|
{
|
|
QLineEdit *editor = dynamic_cast<QLineEdit *> (widget);
|
|
if (editor) {
|
|
editor->setText (index.model ()->data (index, Qt::UserRole).toString ());
|
|
}
|
|
}
|
|
|
|
void setModelData (QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
|
|
{
|
|
QLineEdit *editor = dynamic_cast<QLineEdit *> (widget);
|
|
if (editor) {
|
|
model->setData (index, QVariant (editor->text ()), Qt::UserRole);
|
|
}
|
|
}
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// MacroEditorDialog implementation
|
|
|
|
static lay::MacroEditorDialog *s_macro_editor_instance = 0;
|
|
|
|
MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection *root)
|
|
: QDialog (0 /*show as individual top widget*/, Qt::Window),
|
|
lay::Plugin (pr, true),
|
|
mp_plugin_root (pr),
|
|
mp_root (root),
|
|
m_first_show (true), m_debugging_on (true),
|
|
mp_run_macro (0),
|
|
md_update_console_text (this, &MacroEditorDialog::update_console_text),
|
|
md_search_edited (this, &MacroEditorDialog::do_search_edited),
|
|
m_in_event_handler (false),
|
|
m_os (OS_none),
|
|
m_new_line (true),
|
|
m_highlighters (this),
|
|
m_in_exec (false),
|
|
m_in_breakpoint (false),
|
|
m_ignore_exec_events (false),
|
|
mp_exec_controller (0),
|
|
mp_current_interpreter (0),
|
|
m_continue (false),
|
|
m_trace_count (0), m_current_stack_depth (-1), m_stop_stack_depth (-1),
|
|
m_eval_context (-1),
|
|
m_process_events_interval (0.0),
|
|
m_window_closed (true),
|
|
m_needs_update (true),
|
|
m_ntab (8),
|
|
m_nindent (2),
|
|
m_save_all_on_run (false),
|
|
m_stop_on_exception (true),
|
|
m_file_watcher_enabled (true),
|
|
m_font_size (0),
|
|
m_edit_trace_index (-1),
|
|
m_add_edit_trace_enabled (true),
|
|
dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher),
|
|
dm_update_ui_to_run_mode (this, &MacroEditorDialog::do_update_ui_to_run_mode)
|
|
{
|
|
// Makes this dialog receive events while progress bars are on - this way we can set breakpoints
|
|
// during execution of a macro even if anything lengthy is running.
|
|
lay::mark_widget_alive (this, true);
|
|
|
|
Ui::MacroEditorDialog::setupUi (this);
|
|
|
|
input_field->setFont (monospace_font ());
|
|
console_text_frame->setFont (monospace_font ());
|
|
|
|
connect (mp_root, SIGNAL (macro_changed (lym::Macro *)), this, SLOT (macro_changed (lym::Macro *)));
|
|
connect (mp_root, SIGNAL (macro_deleted (lym::Macro *)), this, SLOT (macro_deleted (lym::Macro *)));
|
|
connect (mp_root, SIGNAL (macro_collection_deleted (lym::MacroCollection *)), this, SLOT (macro_collection_deleted (lym::MacroCollection *)));
|
|
connect (mp_root, SIGNAL (macro_collection_changed (lym::MacroCollection *)), this, SLOT (macro_collection_changed (lym::MacroCollection *)));
|
|
|
|
m_categories = lay::MacroController::instance ()->macro_categories ();
|
|
|
|
treeTab->clear ();
|
|
|
|
for (size_t i = 0; i < m_categories.size (); ++i) {
|
|
|
|
lay::MacroEditorTree *macro_tree = new lay::MacroEditorTree (treeTab, m_categories [i].name);
|
|
m_macro_trees.push_back (macro_tree);
|
|
|
|
treeTab->addTab(macro_tree, tl::to_qstring (m_categories [i].description));
|
|
|
|
macro_tree->setup (this);
|
|
|
|
macro_tree->setSortingEnabled (true);
|
|
macro_tree->sortByColumn (0, Qt::AscendingOrder);
|
|
macro_tree->setObjectName (tl::to_qstring (m_categories [i].name) + QString::fromUtf8 ("_tree"));
|
|
|
|
macro_tree->setContextMenuPolicy (Qt::ActionsContextMenu);
|
|
|
|
macro_tree->addAction (actionRefresh);
|
|
QAction *s0 = new QAction (macro_tree);
|
|
s0->setSeparator (true);
|
|
macro_tree->addAction (s0);
|
|
macro_tree->addAction (actionAddLocation);
|
|
macro_tree->addAction (actionRemoveLocation);
|
|
QAction *s1 = new QAction (macro_tree);
|
|
s1->setSeparator (true);
|
|
macro_tree->addAction (s1);
|
|
macro_tree->addAction (actionNewFolder);
|
|
QAction *s2 = new QAction (macro_tree);
|
|
s2->setSeparator (true);
|
|
macro_tree->addAction (s2);
|
|
macro_tree->addAction (actionAddMacro);
|
|
macro_tree->addAction (actionDelete);
|
|
macro_tree->addAction (actionRename);
|
|
macro_tree->addAction (actionImport);
|
|
QAction *s3 = new QAction (macro_tree);
|
|
s3->setSeparator (true);
|
|
macro_tree->addAction (s3);
|
|
macro_tree->addAction (actionSaveAll);
|
|
macro_tree->addAction (actionSave);
|
|
macro_tree->addAction (actionSaveAs);
|
|
|
|
macro_tree->header ()->hide ();
|
|
|
|
macro_tree->setItemDelegate (new EditRoleDelegate (macro_tree));
|
|
|
|
connect (macro_tree, SIGNAL (macro_double_clicked (lym::Macro *)), this, SLOT (item_double_clicked (lym::Macro *)));
|
|
connect (macro_tree, SIGNAL (move_macro (lym::Macro *, lym::MacroCollection *)), this, SLOT (move_macro (lym::Macro *, lym::MacroCollection *)));
|
|
connect (macro_tree, SIGNAL (move_folder (lym::MacroCollection *, lym::MacroCollection *)), this, SLOT (move_folder (lym::MacroCollection *, lym::MacroCollection *)));
|
|
connect (macro_tree, SIGNAL (folder_renamed (lym::MacroCollection *)), this, SLOT (folder_renamed (lym::MacroCollection *)));
|
|
connect (macro_tree, SIGNAL (macro_renamed (lym::Macro *)), this, SLOT (macro_renamed (lym::Macro *)));
|
|
|
|
}
|
|
|
|
setObjectName (QString::fromUtf8 ("MacroEditorDialog"));
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout (console_text_frame);
|
|
layout->setContentsMargins (0, 0, 0, 0);
|
|
console_text_frame->setLayout (layout);
|
|
mp_console_text = new TextEditWidget (console_text_frame);
|
|
mp_console_text->setReadOnly (true);
|
|
mp_console_text->setFont (monospace_font ());
|
|
mp_console_text->setWordWrapMode (QTextOption::NoWrap);
|
|
layout->addWidget (mp_console_text);
|
|
|
|
m_stdout_format = mp_console_text->currentCharFormat ();
|
|
m_echo_format = m_stdout_format;
|
|
m_echo_format.setForeground (QColor (0, 0, 255));
|
|
m_stderr_format = m_stdout_format;
|
|
m_stderr_format.setForeground (QColor (255, 0, 0));
|
|
m_stderr_format.setFontWeight (QFont::Bold);
|
|
|
|
input_field->setCompleter (0);
|
|
|
|
forwardButton->setEnabled (false);
|
|
backwardButton->setEnabled (false);
|
|
|
|
QMenu *m = new QMenu (searchEditBox);
|
|
m->addAction (actionUseRegularExpressions);
|
|
m->addAction (actionCaseSensitive);
|
|
connect (actionUseRegularExpressions, SIGNAL (triggered ()), this, SLOT (search_editing ()));
|
|
connect (actionCaseSensitive, SIGNAL (triggered ()), this, SLOT (search_editing ()));
|
|
|
|
addAction (actionSearchReplace);
|
|
connect (actionSearchReplace, SIGNAL (triggered ()), this, SLOT (search_replace ()));
|
|
|
|
searchEditBox->set_clear_button_enabled (true);
|
|
searchEditBox->set_options_button_enabled (true);
|
|
searchEditBox->set_options_menu (m);
|
|
searchEditBox->set_escape_signal_enabled (true);
|
|
searchEditBox->set_tab_signal_enabled (true);
|
|
replaceText->set_clear_button_enabled (true);
|
|
replaceText->set_escape_signal_enabled (true);
|
|
replaceText->set_tab_signal_enabled (true);
|
|
#if QT_VERSION >= 0x40700
|
|
searchEditBox->setPlaceholderText (tr ("Find text ..."));
|
|
replaceText->setPlaceholderText (tr ("Replace text ..."));
|
|
#endif
|
|
|
|
connect (forwardButton, SIGNAL (clicked ()), this, SLOT (forward ()));
|
|
connect (backwardButton, SIGNAL (clicked ()), this, SLOT (backward ()));
|
|
|
|
connect (clear_button, SIGNAL (clicked ()), this, SLOT (clear_log ()));
|
|
connect (input_field, SIGNAL (editTextChanged (const QString &)), this, SLOT (immediate_command_text_changed (const QString &)));
|
|
|
|
m_history_index = -1;
|
|
|
|
#if QT_VERSION >= 0x040500
|
|
tabWidget->setMovable (true);
|
|
tabWidget->setTabsClosable (true);
|
|
connect (tabWidget, SIGNAL (tabCloseRequested (int)), this, SLOT (tab_close_requested (int)));
|
|
#endif
|
|
|
|
dbgOn->setEnabled (true);
|
|
runButton->setEnabled (true);
|
|
runThisButton->setEnabled (true);
|
|
pauseButton->setEnabled (false);
|
|
stopButton->setEnabled (false);
|
|
singleStepButton->setEnabled (true);
|
|
nextStepButton->setEnabled (true);
|
|
runtimeFrame->hide ();
|
|
|
|
watchList->setContextMenuPolicy (Qt::ActionsContextMenu);
|
|
watchList->addAction (actionAddWatch);
|
|
watchList->addAction (actionEditWatch);
|
|
watchList->addAction (actionDeleteWatches);
|
|
watchList->addAction (actionClearWatches);
|
|
|
|
connect (actionAddWatch, SIGNAL (triggered ()), this, SLOT (add_watch ()));
|
|
connect (actionEditWatch, SIGNAL (triggered ()), this, SLOT (edit_watch ()));
|
|
connect (actionDeleteWatches, SIGNAL (triggered ()), this, SLOT (del_watches ()));
|
|
connect (actionClearWatches, SIGNAL (triggered ()), this, SLOT (clear_watches ()));
|
|
connect (actionRefresh, SIGNAL (triggered ()), this, SLOT (refresh ()));
|
|
connect (actionAddLocation, SIGNAL (triggered ()), this, SLOT (add_location ()));
|
|
connect (actionRemoveLocation, SIGNAL (triggered ()), this, SLOT (remove_location ()));
|
|
connect (helpButton, SIGNAL (clicked ()), this, SLOT (help_button_clicked ()));
|
|
connect (addButton, SIGNAL (clicked ()), this, SLOT (add_button_clicked ()));
|
|
connect (actionAddMacro, SIGNAL (triggered ()), this, SLOT (add_button_clicked ()));
|
|
connect (deleteButton, SIGNAL (clicked ()), this, SLOT (delete_button_clicked ()));
|
|
connect (actionDelete, SIGNAL (triggered ()), this, SLOT (delete_button_clicked ()));
|
|
connect (renameButton, SIGNAL (clicked ()), this, SLOT (rename_button_clicked ()));
|
|
connect (actionRename, SIGNAL (triggered ()), this, SLOT (rename_button_clicked ()));
|
|
connect (importButton, SIGNAL (clicked ()), this, SLOT (import_button_clicked ()));
|
|
connect (actionImport, SIGNAL (triggered ()), this, SLOT (import_button_clicked ()));
|
|
connect (newFolderButton, SIGNAL (clicked ()), this, SLOT (new_folder_button_clicked ()));
|
|
connect (actionNewFolder, SIGNAL (triggered ()), this, SLOT (new_folder_button_clicked ()));
|
|
connect (saveAllButton, SIGNAL (clicked ()), this, SLOT (save_all_button_clicked ()));
|
|
connect (actionSaveAll, SIGNAL (triggered ()), this, SLOT (save_all_button_clicked ()));
|
|
connect (saveButton, SIGNAL (clicked ()), this, SLOT (save_button_clicked ()));
|
|
connect (actionSave, SIGNAL (triggered ()), this, SLOT (save_button_clicked ()));
|
|
connect (actionSaveAs, SIGNAL (triggered ()), this, SLOT (save_as_button_clicked ()));
|
|
connect (dbgOn, SIGNAL (clicked (bool)), this, SLOT (set_debugging_on (bool)));
|
|
connect (runButton, SIGNAL (clicked ()), this, SLOT (run_button_clicked ()));
|
|
connect (runThisButton, SIGNAL (clicked ()), this, SLOT (run_this_button_clicked ()));
|
|
connect (pauseButton, SIGNAL (clicked ()), this, SLOT (pause_button_clicked ()));
|
|
connect (stopButton, SIGNAL (clicked ()), this, SLOT (stop_button_clicked ()));
|
|
connect (breakpointButton, SIGNAL (clicked ()), this, SLOT (breakpoint_button_clicked ()));
|
|
connect (clearBreakpointsButton, SIGNAL (clicked ()), this, SLOT (clear_breakpoints_button_clicked ()));
|
|
connect (propertiesButton, SIGNAL (clicked ()), this, SLOT (properties_button_clicked ()));
|
|
connect (setupButton, SIGNAL (clicked ()), this, SLOT (setup_button_clicked ()));
|
|
connect (tabWidget, SIGNAL (currentChanged (int)), this, SLOT (current_tab_changed (int)));
|
|
connect (callStack, SIGNAL (itemDoubleClicked (QListWidgetItem *)), this, SLOT (stack_element_double_clicked (QListWidgetItem *)));
|
|
connect (singleStepButton, SIGNAL (clicked ()), this, SLOT (single_step_button_clicked ()));
|
|
connect (nextStepButton, SIGNAL (clicked ()), this, SLOT (next_step_button_clicked ()));
|
|
connect (searchEditBox, SIGNAL (textEdited (const QString &)), this, SLOT (search_editing ()));
|
|
connect (searchEditBox, SIGNAL (returnPressed ()), this, SLOT (search_edited ()));
|
|
connect (searchEditBox, SIGNAL (editingFinished ()), this, SLOT (search_edited ()));
|
|
connect (searchEditBox, SIGNAL (esc_pressed ()), this, SLOT (search_finished ()));
|
|
connect (searchEditBox, SIGNAL (tab_pressed ()), this, SLOT (find_next_button_clicked ()));
|
|
connect (searchEditBox, SIGNAL (backtab_pressed ()), this, SLOT (find_prev_button_clicked ()));
|
|
connect (replaceText, SIGNAL (esc_pressed ()), this, SLOT (search_finished ()));
|
|
connect (replaceText, SIGNAL (tab_pressed ()), this, SLOT (find_next_button_clicked ()));
|
|
connect (replaceText, SIGNAL (backtab_pressed ()), this, SLOT (find_prev_button_clicked ()));
|
|
connect (replaceText, SIGNAL (returnPressed ()), this, SLOT (replace_next_button_clicked ()));
|
|
connect (replaceModeButton, SIGNAL (clicked ()), this, SLOT (replace_mode_button_clicked ()));
|
|
connect (replaceNextButton, SIGNAL (clicked ()), this, SLOT (replace_next_button_clicked ()));
|
|
connect (findNextButton, SIGNAL (clicked ()), this, SLOT (find_next_button_clicked ()));
|
|
connect (replaceAllButton, SIGNAL (clicked ()), this, SLOT (replace_all_button_clicked ()));
|
|
connect (allVariables, SIGNAL (clicked (bool)), variableList, SLOT (set_show_all (bool)));
|
|
|
|
splitter->setCollapsible (1, false);
|
|
replaceFrame->hide ();
|
|
|
|
tabWidget->clear ();
|
|
|
|
// add standard templates
|
|
QResource res (QString::fromUtf8 (":/macro-templates/index.txt"));
|
|
QByteArray data;
|
|
#if QT_VERSION >= 0x60000
|
|
if (res.compressionAlgorithm () == QResource::ZlibCompression) {
|
|
#else
|
|
if (res.isCompressed ()) {
|
|
#endif
|
|
data = qUncompress ((const unsigned char *)res.data (), (int)res.size ());
|
|
} else {
|
|
data = QByteArray ((const char *)res.data (), (int)res.size ());
|
|
}
|
|
|
|
// Read standard templates from :/macro-templates/x
|
|
std::vector<std::string> lines = tl::split (std::string (data.constData (), data.size ()), "\n");
|
|
std::string description_prefix;
|
|
std::string category;
|
|
for (std::vector<std::string>::const_iterator l = lines.begin (); l != lines.end (); ++l) {
|
|
|
|
std::string ll = tl::trim (*l);
|
|
if (! ll.empty () && ll [0] != '#') {
|
|
|
|
if (ll [0] == '[') {
|
|
|
|
size_t closing = ll.find ("]");
|
|
if (closing != std::string::npos) {
|
|
category = tl::trim (std::string (ll, 1, closing - 1));
|
|
}
|
|
|
|
} else if (ll [0] == ':') {
|
|
|
|
description_prefix = tl::trim (std::string (ll, 1));
|
|
|
|
} else {
|
|
|
|
std::string description;
|
|
size_t colon = ll.find (":");
|
|
if (colon != std::string::npos) {
|
|
description = tl::trim (std::string (ll, colon + 1));
|
|
ll = tl::trim (std::string (ll, 0, colon));
|
|
}
|
|
|
|
std::string url = ":/macro-templates/" + ll;
|
|
|
|
lym::Macro *m = new lym::Macro ();
|
|
try {
|
|
|
|
m->rename (tl::basename (url));
|
|
m->load_from (url);
|
|
if (! description.empty ()) {
|
|
m->set_description (description_prefix + description);
|
|
} else {
|
|
m->set_description (description_prefix + m->description ());
|
|
}
|
|
m->set_readonly (true);
|
|
if (! category.empty ()) {
|
|
m->set_category (category);
|
|
}
|
|
m_macro_templates.push_back (m);
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Using macro template from " << url << " (with name " << m->name () << ")";
|
|
}
|
|
|
|
} catch (tl::Exception &ex) {
|
|
delete m;
|
|
tl::error << "Reading " << url << ": " << ex.msg ();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// scan macro templates
|
|
for (std::vector<std::string>::const_iterator p = lay::ApplicationBase::instance ()->klayout_path ().begin (); p != lay::ApplicationBase::instance ()->klayout_path ().end (); ++p) {
|
|
|
|
QDir dir (QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("macro-templates")));
|
|
|
|
QStringList filters;
|
|
filters << QString::fromUtf8 ("*.lym");
|
|
filters << QString::fromUtf8 ("*.txt");
|
|
filters << QString::fromUtf8 ("*.rb");
|
|
filters << QString::fromUtf8 ("*.py");
|
|
|
|
// add the suffixes in the DSL interpreter declarations
|
|
for (tl::Registrar<lym::MacroInterpreter>::iterator cls = tl::Registrar<lym::MacroInterpreter>::begin (); cls != tl::Registrar<lym::MacroInterpreter>::end (); ++cls) {
|
|
if (! cls->suffix ().empty ()) {
|
|
filters << tl::to_qstring ("*." + cls->suffix ());
|
|
}
|
|
}
|
|
|
|
QStringList files = dir.entryList (filters, QDir::Files);
|
|
for (QStringList::ConstIterator f = files.begin (); f != files.end (); ++f) {
|
|
|
|
lym::Macro *m = new lym::Macro ();
|
|
try {
|
|
|
|
m->rename (tl::to_string (QFileInfo (*f).baseName ()));
|
|
m->load_from (tl::to_string (dir.filePath (*f)));
|
|
m->set_readonly (true);
|
|
m_macro_templates.push_back (m);
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Using macro template from " << tl::to_string (dir.filePath (*f)) << " (with name " << m->name () << ")";
|
|
}
|
|
|
|
} catch (...) {
|
|
delete m;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// finally fetch the templates of the DSL interpreters
|
|
for (tl::Registrar<lym::MacroInterpreter>::iterator cls = tl::Registrar<lym::MacroInterpreter>::begin (); cls != tl::Registrar<lym::MacroInterpreter>::end (); ++cls) {
|
|
size_t n = m_macro_templates.size ();
|
|
cls->get_templates (m_macro_templates);
|
|
if (tl::verbosity () >= 20) {
|
|
for (std::vector<lym::Macro *>::const_iterator t = m_macro_templates.begin () + n; t != m_macro_templates.end (); ++t) {
|
|
tl::info << "Using DSL macro template for " << (*t)->dsl_interpreter () << " with name " << (*t)->name ();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_file_changed_timer = new QTimer (this);
|
|
m_file_changed_timer->setSingleShot (true);
|
|
connect (m_file_changed_timer, SIGNAL (timeout ()), this, SLOT (file_changed_timer ()));
|
|
|
|
m_file_watcher = new tl::FileSystemWatcher (this);
|
|
connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_changed (const QString &)));
|
|
connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_removed (const QString &)));
|
|
|
|
QTimer *timer = new QTimer (this);
|
|
connect (timer, SIGNAL (timeout ()), this, SLOT (commit ()));
|
|
timer->start (500);
|
|
|
|
mainHSplitter->setStretchFactor (1, 1);
|
|
|
|
// Install a global event filter that allows us to lock out the application while a script is running
|
|
// or we are inside a breakpoint and other modifications.
|
|
QCoreApplication::instance ()->installEventFilter (this);
|
|
|
|
if (! s_macro_editor_instance) {
|
|
s_macro_editor_instance = this;
|
|
}
|
|
|
|
config_setup ();
|
|
}
|
|
|
|
MacroEditorDialog::~MacroEditorDialog ()
|
|
{
|
|
if (s_macro_editor_instance == this) {
|
|
s_macro_editor_instance = 0;
|
|
}
|
|
|
|
for (std::vector<lym::Macro *>::iterator t = m_macro_templates.begin (); t != m_macro_templates.end (); ++t) {
|
|
delete *t;
|
|
}
|
|
m_macro_templates.clear ();
|
|
}
|
|
|
|
MacroEditorDialog *
|
|
MacroEditorDialog::instance ()
|
|
{
|
|
return s_macro_editor_instance;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::select_category (const std::string &cat)
|
|
{
|
|
for (size_t i = 0; i < m_categories.size (); ++i) {
|
|
if (m_categories [i].name == cat) {
|
|
treeTab->setCurrentIndex (int (i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::clear_log ()
|
|
{
|
|
mp_console_text->clear ();
|
|
m_new_line = true;
|
|
m_os = OS_none;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::show (const std::string &cat, bool force_add)
|
|
{
|
|
BEGIN_PROTECTED
|
|
|
|
if (isMinimized ()) {
|
|
QDialog::showNormal ();
|
|
} else {
|
|
QDialog::show ();
|
|
}
|
|
activateWindow ();
|
|
raise ();
|
|
|
|
if (m_first_show) {
|
|
|
|
m_first_show = false;
|
|
|
|
if (! cat.empty ()) {
|
|
select_category (cat);
|
|
}
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
lym::MacroCollection *collection = ct->current_macro_collection ();
|
|
|
|
// Select the first writeable collection if none is selected
|
|
if (! collection || collection->is_readonly ()) {
|
|
for (lym::MacroCollection::const_child_iterator c = mp_root->begin_children (); c != mp_root->end_children (); ++c) {
|
|
if (c->second->category () == ct->category () && ! c->second->is_readonly ()) {
|
|
ct->set_current (c->second);
|
|
collection = c->second;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool open_template_dialog = false;
|
|
if (! force_add && collection && (collection->begin () == collection->end () && collection->begin_children () == collection->end_children ())) {
|
|
TipDialog td (this,
|
|
tl::to_string (QObject::tr ("<html><body>To get started with the macro development feature, read the documentation provided: <a href=\"int:/about/macro_editor.xml\">About Macro Development</a>.</body></html>")),
|
|
"macro-editor-basic-tips");
|
|
open_template_dialog = td.exec_dialog () && td.will_be_shown ();
|
|
}
|
|
|
|
if (collection && (force_add || open_template_dialog)) {
|
|
lym::Macro *m = new_macro ();
|
|
if (force_add && m) {
|
|
set_run_macro (m);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (! cat.empty ()) {
|
|
select_category (cat);
|
|
}
|
|
|
|
if (force_add) {
|
|
lym::Macro *m = new_macro ();
|
|
if (m) {
|
|
set_run_macro (m);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
lay::MacroEditorTree *
|
|
MacroEditorDialog::current_macro_tree ()
|
|
{
|
|
lay::MacroEditorTree *t = dynamic_cast<lay::MacroEditorTree *> (treeTab->currentWidget ());
|
|
tl_assert (t != 0);
|
|
return t;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::config_finalize ()
|
|
{
|
|
if (m_needs_update) {
|
|
|
|
for (int i = 0; i < tabWidget->count (); ++i) {
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (i));
|
|
if (page) {
|
|
page->set_ntab (m_ntab);
|
|
page->set_nindent (m_nindent);
|
|
page->apply_attributes ();
|
|
page->set_font (m_font_family, m_font_size);
|
|
}
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
m_needs_update = false;
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
MacroEditorDialog::configure (const std::string &name, const std::string &value)
|
|
{
|
|
// Reads the dynamic configuration
|
|
|
|
if (name == cfg_macro_editor_styles) {
|
|
|
|
if (m_styles != value) {
|
|
m_styles = value;
|
|
m_needs_update = true;
|
|
}
|
|
m_highlighters.load (value);
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_save_all_on_run) {
|
|
tl::from_string (value, m_save_all_on_run);
|
|
return true;
|
|
} else if (name == cfg_macro_editor_stop_on_exception) {
|
|
tl::from_string (value, m_stop_on_exception);
|
|
return true;
|
|
} else if (name == cfg_macro_editor_file_watcher_enabled) {
|
|
|
|
bool en = m_file_watcher_enabled;
|
|
tl::from_string (value, en);
|
|
if (en != m_file_watcher_enabled) {
|
|
m_file_watcher_enabled = en;
|
|
m_needs_update = true;
|
|
}
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_font_family) {
|
|
|
|
if (m_font_family != value) {
|
|
m_font_family = value;
|
|
m_needs_update = true;
|
|
}
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_font_size) {
|
|
|
|
int v = m_font_size;
|
|
if (! value.empty ()) {
|
|
tl::from_string (value, v);
|
|
}
|
|
if (v != m_font_size) {
|
|
m_font_size = v;
|
|
m_needs_update = true;
|
|
}
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_tab_width) {
|
|
|
|
int v = m_ntab;
|
|
tl::from_string (value, v);
|
|
if (v != m_ntab) {
|
|
m_ntab = v;
|
|
m_needs_update = true;
|
|
}
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_indent) {
|
|
|
|
int v = m_nindent;
|
|
tl::from_string (value, v);
|
|
if (v != m_nindent) {
|
|
m_nindent = v;
|
|
m_needs_update = true;
|
|
}
|
|
return true;
|
|
|
|
} else if (name == cfg_macro_editor_ignore_exception_list) {
|
|
|
|
m_ignore_exception_list.clear ();
|
|
tl::Extractor ex (value.c_str ());
|
|
while (! ex.at_end ()) {
|
|
std::string f;
|
|
ex.read_word_or_quoted (f);
|
|
ex.test (";");
|
|
m_ignore_exception_list.insert (f);
|
|
}
|
|
return true;
|
|
|
|
} else {
|
|
return lay::Plugin::configure (name, value);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::showEvent (QShowEvent *)
|
|
{
|
|
if (! m_window_closed) {
|
|
// show after showNormal
|
|
return;
|
|
}
|
|
|
|
m_window_closed = false;
|
|
|
|
// read debugger environment from configuration
|
|
|
|
mp_plugin_root->config_get (cfg_macro_editor_debugging_enabled, m_debugging_on);
|
|
|
|
std::string ws;
|
|
mp_plugin_root->config_get (cfg_macro_editor_window_state, ws);
|
|
lay::restore_dialog_state (this, ws);
|
|
|
|
input_field->clear ();
|
|
|
|
try {
|
|
std::string hi;
|
|
mp_plugin_root->config_get (cfg_macro_editor_console_mru, hi);
|
|
tl::Extractor ex (hi.c_str ());
|
|
while (! ex.at_end ()) {
|
|
std::string h;
|
|
ex.read_word_or_quoted (h);
|
|
ex.test (";");
|
|
input_field->addItem (tl::to_qstring (h));
|
|
}
|
|
} catch (...) { }
|
|
m_history_index = -1;
|
|
input_field->clearEditText ();
|
|
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().push_console (this);
|
|
if (m_debugging_on) {
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().push_exec_handler (this);
|
|
}
|
|
lay::ApplicationBase::instance ()->python_interpreter ().push_console (this);
|
|
if (m_debugging_on) {
|
|
lay::ApplicationBase::instance ()->python_interpreter ().push_exec_handler (this);
|
|
}
|
|
|
|
std::string ci;
|
|
mp_plugin_root->config_get (cfg_macro_editor_console_interpreter, ci);
|
|
if (ci == "ruby") {
|
|
pythonLangSel->setChecked (false);
|
|
rubyLangSel->setChecked (true);
|
|
} else if (ci == "python") {
|
|
pythonLangSel->setChecked (true);
|
|
rubyLangSel->setChecked (false);
|
|
}
|
|
|
|
try {
|
|
|
|
m_watch_expressions.clear ();
|
|
|
|
std::string we;
|
|
mp_plugin_root->config_get (cfg_macro_editor_watch_expressions, we);
|
|
tl::Extractor ex (we.c_str ());
|
|
while (! ex.at_end ()) {
|
|
|
|
std::string ip, expr;
|
|
ex.read_word (ip);
|
|
ex.test (":");
|
|
ex.read_word_or_quoted (expr);
|
|
ex.test (";");
|
|
|
|
if (ip == "ruby") {
|
|
m_watch_expressions.push_back (std::make_pair (&lay::ApplicationBase::instance ()->ruby_interpreter (), expr));
|
|
} else if (ip == "python") {
|
|
m_watch_expressions.push_back (std::make_pair (&lay::ApplicationBase::instance ()->python_interpreter (), expr));
|
|
}
|
|
|
|
}
|
|
|
|
} catch (...) { }
|
|
|
|
try {
|
|
std::string om;
|
|
mp_plugin_root->config_get (cfg_macro_editor_open_macros, om);
|
|
tl::Extractor ex (om.c_str ());
|
|
while (! ex.at_end ()) {
|
|
std::string h;
|
|
ex.read_word_or_quoted (h);
|
|
ex.test (";");
|
|
// this will open an editor for the macro with path h
|
|
editor_for_file (h);
|
|
}
|
|
} catch (...) { }
|
|
|
|
std::string am;
|
|
mp_plugin_root->config_get (cfg_macro_editor_active_macro, am);
|
|
if (! am.empty ()) {
|
|
lym::Macro *macro = mp_root->find_macro (am);
|
|
if (macro) {
|
|
set_run_macro (macro);
|
|
}
|
|
}
|
|
|
|
dbgOn->setChecked (m_debugging_on);
|
|
|
|
std::string cm;
|
|
mp_plugin_root->config_get (cfg_macro_editor_current_macro, cm);
|
|
if (! cm.empty ()) {
|
|
// this will make that macro the current one
|
|
editor_for_file (cm);
|
|
}
|
|
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator page = m_tab_widgets.begin (); page != m_tab_widgets.end (); ++page) {
|
|
page->second->set_debugging_on (m_debugging_on);
|
|
}
|
|
|
|
// clear the navigator on show - this way we get rid of the pseudo trace events we got while we
|
|
// built the pages
|
|
clear_edit_trace ();
|
|
add_edit_trace (false);
|
|
|
|
// set up the file system watcher if file system monitoring is requested
|
|
refresh_file_watcher ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::reject ()
|
|
{
|
|
// .. ignore Esc ..
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::accept ()
|
|
{
|
|
// .. ignore Enter ..
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::closeEvent (QCloseEvent *)
|
|
{
|
|
// save the debugging enabled state
|
|
mp_plugin_root->config_set (cfg_macro_editor_debugging_enabled, m_debugging_on);
|
|
|
|
// save the window state
|
|
mp_plugin_root->config_set (cfg_macro_editor_window_state, lay::save_dialog_state (this));
|
|
|
|
// save the console history (at maximum the last 200 entries)
|
|
std::string hi;
|
|
for (int i = std::max (0, input_field->count () - 200); i < input_field->count (); ++i) {
|
|
if (! hi.empty ()) {
|
|
hi += ";";
|
|
}
|
|
hi += tl::to_quoted_string (tl::to_string (input_field->itemText (i)));
|
|
}
|
|
mp_plugin_root->config_set (cfg_macro_editor_console_mru, hi);
|
|
|
|
// save the open macro list
|
|
std::string om;
|
|
for (int i = 0; i < tabWidget->count (); ++i) {
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (i));
|
|
if (page && page->macro ()) {
|
|
if (! om.empty ()) {
|
|
om += ";";
|
|
}
|
|
om += tl::to_quoted_string (page->macro ()->path ());
|
|
}
|
|
}
|
|
mp_plugin_root->config_set (cfg_macro_editor_open_macros, om);
|
|
|
|
// save the watch expressions
|
|
std::string we;
|
|
for (std::vector<std::pair<gsi::Interpreter *, std::string> >::const_iterator i = m_watch_expressions.begin (); i != m_watch_expressions.end (); ++i) {
|
|
if (! om.empty ()) {
|
|
om += ";";
|
|
}
|
|
if (i->first == &lay::ApplicationBase::instance ()->ruby_interpreter ()) {
|
|
we += "ruby";
|
|
} else if (i->first == &lay::ApplicationBase::instance ()->python_interpreter ()) {
|
|
we += "python";
|
|
}
|
|
we += ":";
|
|
we += tl::to_quoted_string (i->second);
|
|
}
|
|
mp_plugin_root->config_set (cfg_macro_editor_watch_expressions, we);
|
|
|
|
// save the active (run) macro
|
|
mp_plugin_root->config_set (cfg_macro_editor_active_macro, mp_run_macro ? mp_run_macro->path () : std::string ());
|
|
|
|
// save the current macro
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
std::string cm = page && page->macro () ? page->macro ()->path () : std::string ();
|
|
mp_plugin_root->config_set (cfg_macro_editor_current_macro, cm);
|
|
|
|
// save the current interpreter in the console
|
|
std::string ci;
|
|
if (rubyLangSel->isChecked ()) {
|
|
ci = "ruby";
|
|
} else if (pythonLangSel->isChecked ()) {
|
|
ci = "python";
|
|
}
|
|
mp_plugin_root->config_set (cfg_macro_editor_console_interpreter, ci);
|
|
|
|
// stop execution when the window is closed
|
|
m_in_exec = false;
|
|
m_continue = false;
|
|
m_window_closed = true;
|
|
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().remove_console (this);
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().remove_exec_handler (this);
|
|
lay::ApplicationBase::instance ()->python_interpreter ().remove_console (this);
|
|
lay::ApplicationBase::instance ()->python_interpreter ().remove_exec_handler (this);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::set_debugging_on (bool on)
|
|
{
|
|
if (m_debugging_on != on) {
|
|
|
|
m_debugging_on = on;
|
|
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator page = m_tab_widgets.begin (); page != m_tab_widgets.end (); ++page) {
|
|
page->second->set_debugging_on (m_debugging_on);
|
|
}
|
|
|
|
if (isVisible ()) {
|
|
if (on) {
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().push_exec_handler (this);
|
|
lay::ApplicationBase::instance ()->python_interpreter ().push_exec_handler (this);
|
|
} else {
|
|
lay::ApplicationBase::instance ()->ruby_interpreter ().remove_exec_handler (this);
|
|
lay::ApplicationBase::instance ()->python_interpreter ().remove_exec_handler (this);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::process_events (QEventLoop::ProcessEventsFlags flags)
|
|
{
|
|
if (lay::ApplicationBase::instance ()) {
|
|
// NOTE: we disable execution of deferred methods to avoid undesired execution of
|
|
// code while we are inside a Ruby callback through the silent mode
|
|
// NOTE: process_events will set BusySection::is_busy
|
|
lay::ApplicationBase::instance ()->process_events (flags, true /*silent*/);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
any_modified(lym::MacroCollection *parent)
|
|
{
|
|
for (lym::MacroCollection::child_iterator c = parent->begin_children (); c != parent->end_children (); ++c) {
|
|
if (any_modified (c->second)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (lym::MacroCollection::iterator c = parent->begin (); c != parent->end (); ++c) {
|
|
if (c->second->is_modified ()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
MacroEditorDialog::can_exit ()
|
|
{
|
|
if (any_modified (mp_root)) {
|
|
if (QMessageBox::question (this, QObject::tr ("Save Macros"),
|
|
QObject::tr ("Some macros are modified. Do you want to save them?"),
|
|
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
|
|
save_all_button_clicked ();
|
|
}
|
|
}
|
|
|
|
// simulate close event so we do a clean shut down and save the console MRU list for example
|
|
if (isVisible ()) {
|
|
closeEvent (0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::add_edit_trace (bool compress)
|
|
{
|
|
const size_t max_entries = 1000;
|
|
|
|
if (! m_add_edit_trace_enabled) {
|
|
return;
|
|
}
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page || ! page->macro ()) {
|
|
return;
|
|
}
|
|
|
|
std::string path = page->macro ()->path ();
|
|
int line = page->current_line ();
|
|
int pos = page->current_pos ();
|
|
|
|
if (m_edit_trace.size () > m_edit_trace_index + 1) {
|
|
m_edit_trace.erase (m_edit_trace.begin () + m_edit_trace_index + 1, m_edit_trace.end ());
|
|
}
|
|
|
|
if (compress
|
|
&& m_edit_trace [m_edit_trace_index].path == path
|
|
&& m_edit_trace [m_edit_trace_index].line == line) {
|
|
|
|
// update position only if the line did not change
|
|
m_edit_trace [m_edit_trace_index].pos = pos;
|
|
|
|
} else {
|
|
|
|
m_edit_trace.push_back (EditTrace ());
|
|
m_edit_trace.back ().path = page->macro ()->path ();
|
|
m_edit_trace.back ().line = page->current_line ();
|
|
m_edit_trace.back ().pos = page->current_pos ();
|
|
++m_edit_trace_index;
|
|
|
|
// reduce when there are too many entries
|
|
if (m_edit_trace.size () > max_entries) {
|
|
m_edit_trace.erase (m_edit_trace.begin ());
|
|
--m_edit_trace_index;
|
|
}
|
|
|
|
}
|
|
|
|
backwardButton->setEnabled (m_edit_trace_index > 0);
|
|
forwardButton->setEnabled (m_edit_trace_index + 1 < m_edit_trace.size ());
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::clear_edit_trace ()
|
|
{
|
|
m_edit_trace.clear ();
|
|
m_edit_trace_index = -1;
|
|
|
|
backwardButton->setEnabled (false);
|
|
forwardButton->setEnabled (false);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::backward ()
|
|
{
|
|
if (m_edit_trace_index > 0) {
|
|
select_trace (m_edit_trace_index - 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::forward ()
|
|
{
|
|
if (m_edit_trace_index + 1 < m_edit_trace.size ()) {
|
|
select_trace (m_edit_trace_index + 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::select_trace (size_t index)
|
|
{
|
|
if (index < m_edit_trace.size ()) {
|
|
|
|
m_edit_trace_index = index;
|
|
m_add_edit_trace_enabled = false;
|
|
|
|
backwardButton->setEnabled (m_edit_trace_index > 0);
|
|
forwardButton->setEnabled (m_edit_trace_index + 1 < m_edit_trace.size ());
|
|
|
|
lay::MacroEditorPage *page = editor_for_file (m_edit_trace [index].path);
|
|
if (page) {
|
|
page->goto_position (m_edit_trace [index].line, m_edit_trace [index].pos);
|
|
}
|
|
|
|
m_add_edit_trace_enabled = true;
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::immediate_command_text_changed (const QString &text)
|
|
{
|
|
m_history_index = -1;
|
|
if (! m_in_event_handler) {
|
|
m_edit_text = text;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::execute (const QString &cmd)
|
|
{
|
|
try {
|
|
|
|
write_str ("> ", OS_echo);
|
|
write_str (tl::to_string (cmd).c_str (), OS_echo);
|
|
write_str ("\n", OS_echo);
|
|
|
|
gsi::Interpreter *interpreter = 0;
|
|
if (rubyLangSel->isChecked ()) {
|
|
interpreter = &lay::ApplicationBase::instance ()->ruby_interpreter ();
|
|
} else if (pythonLangSel->isChecked ()) {
|
|
interpreter = &lay::ApplicationBase::instance ()->python_interpreter ();
|
|
}
|
|
|
|
if (interpreter) {
|
|
int context = m_in_breakpoint ? m_eval_context : -1;
|
|
interpreter->eval_string_and_print (tl::to_string (cmd).c_str (), 0, 1, context);
|
|
}
|
|
|
|
update_inspected ();
|
|
|
|
} catch (tl::ScriptError &ex) {
|
|
|
|
handle_error (ex);
|
|
write_str (ex.msg ().c_str (), OS_stderr);
|
|
write_str ("\n", OS_stderr);
|
|
|
|
} catch (tl::CancelException & /*ex*/) {
|
|
|
|
// ignore CancelException
|
|
|
|
} catch (tl::Exception &ex) {
|
|
|
|
write_str (ex.msg ().c_str (), OS_stderr);
|
|
write_str ("\n", OS_stderr);
|
|
|
|
} catch (std::runtime_error &ex) {
|
|
|
|
write_str (ex.what (), OS_stderr);
|
|
write_str ("\n", OS_stderr);
|
|
|
|
} catch (...) {
|
|
write_str ("Unknown error\n", OS_stderr);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::update_inspected ()
|
|
{
|
|
if (! m_in_breakpoint || ! m_in_exec || ! mp_current_interpreter) {
|
|
variableList->set_inspector (0);
|
|
} else {
|
|
|
|
std::unique_ptr<gsi::Inspector> ci (mp_current_interpreter->inspector (m_eval_context));
|
|
variableListFrame->setVisible (ci.get () != 0);
|
|
variableList->set_inspector (ci.release ());
|
|
|
|
update_watches ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::update_watches ()
|
|
{
|
|
std::set <std::string> expressions;
|
|
for (std::vector<std::pair<gsi::Interpreter *, std::string> >::const_iterator w = m_watch_expressions.begin (); w != m_watch_expressions.end (); ++w) {
|
|
expressions.insert (w->second);
|
|
}
|
|
|
|
for (int i = 0; i < watchList->topLevelItemCount (); ) {
|
|
if (expressions.find (tl::to_string (watchList->topLevelItem (i)->text (0))) == expressions.end ()) {
|
|
delete watchList->takeTopLevelItem (i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
int i = 0;
|
|
for (std::vector<std::pair<gsi::Interpreter *, std::string> >::const_iterator w = m_watch_expressions.begin (); w != m_watch_expressions.end (); ++w, ++i) {
|
|
|
|
QString value;
|
|
if (w->first != mp_current_interpreter) {
|
|
value = tr ("(inactive)");
|
|
} else {
|
|
try {
|
|
value = pretty_print (w->first->eval_expr (w->second.c_str (), 0, 1, m_eval_context));
|
|
} catch (tl::ScriptError &ex) {
|
|
value = tr ("Error") + QString::fromUtf8 (": ") + tl::to_qstring (ex.basic_msg ());
|
|
} catch (tl::Exception &ex) {
|
|
value = tr ("Error") + QString::fromUtf8 (": ") + tl::to_qstring (ex.msg ());
|
|
} catch (...) {
|
|
value = tr ("Error (unspecific)");
|
|
}
|
|
}
|
|
|
|
if (i == watchList->topLevelItemCount ()) {
|
|
|
|
QTreeWidgetItem *item = new QTreeWidgetItem ();
|
|
item->setText (0, tl::to_qstring (w->second));
|
|
QFont f (item->font (0));
|
|
f.setWeight (QFont::Bold);
|
|
item->setFont (0, f);
|
|
item->setText (1, value);
|
|
item->setToolTip (1, value);
|
|
|
|
watchList->addTopLevelItem (item);
|
|
|
|
} else {
|
|
|
|
QTreeWidgetItem *item = watchList->topLevelItem (i);
|
|
|
|
item->setText (0, tl::to_qstring (w->second));
|
|
|
|
if (item->text (1) != value) {
|
|
QFont f (item->font (1));
|
|
f.setWeight (QFont::Bold);
|
|
item->setFont (1, f);
|
|
item->setText (1, value);
|
|
} else {
|
|
QFont f (item->font (1));
|
|
f.setWeight (QFont::Normal);
|
|
item->setFont (1, f);
|
|
}
|
|
|
|
}
|
|
|
|
watchList->topLevelItem (i)->setDisabled (w->first != mp_current_interpreter);
|
|
|
|
}
|
|
}
|
|
|
|
static QString s_watch_expr;
|
|
|
|
void
|
|
MacroEditorDialog::edit_watch ()
|
|
{
|
|
int index = watchList->indexOfTopLevelItem (watchList->currentItem ());
|
|
if (index >= 0) {
|
|
|
|
bool ok = false;
|
|
QString we = QInputDialog::getText (this, tr ("Add Watch Expressions"), tr ("Enter expression to evaluate:"), QLineEdit::Normal, watchList->currentItem ()->text (0), &ok);
|
|
if (ok && ! we.isEmpty()) {
|
|
s_watch_expr = we;
|
|
m_watch_expressions [index].second = tl::to_string (we);
|
|
update_watches ();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::add_watch ()
|
|
{
|
|
if (mp_current_interpreter) {
|
|
|
|
bool ok = false;
|
|
QString we = QInputDialog::getText (this, tr ("Add Watch Expressions"), tr ("Enter expression to evaluate:"), QLineEdit::Normal, s_watch_expr, &ok);
|
|
if (ok && ! we.isEmpty()) {
|
|
s_watch_expr = we;
|
|
m_watch_expressions.push_back (std::make_pair (mp_current_interpreter, tl::to_string (we)));
|
|
}
|
|
|
|
update_watches ();
|
|
|
|
watchList->setCurrentItem (watchList->topLevelItem (int (m_watch_expressions.size ()) - 1));
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::del_watches ()
|
|
{
|
|
for (int i = 0; i < watchList->topLevelItemCount (); ) {
|
|
if (watchList->topLevelItem (i)->isSelected ()) {
|
|
delete watchList->takeTopLevelItem (i);
|
|
m_watch_expressions.erase (m_watch_expressions.begin () + i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::clear_watches ()
|
|
{
|
|
watchList->clear ();
|
|
m_watch_expressions.clear ();
|
|
}
|
|
|
|
bool
|
|
MacroEditorDialog::eventFilter (QObject *obj, QEvent *event)
|
|
{
|
|
// do not handle events that are not targeted towards widgets
|
|
QWidget *rec = dynamic_cast <QWidget *> (obj);
|
|
if (! rec) {
|
|
return false;
|
|
}
|
|
|
|
// do not handle events if a modal widget is active (i.e. a message box)
|
|
if (QApplication::activeModalWidget () && QApplication::activeModalWidget () != this) {
|
|
return false;
|
|
}
|
|
|
|
if (lay::BusySection::is_busy () && (m_in_breakpoint || m_in_exec) && (dynamic_cast <QInputEvent *> (event) != 0 || dynamic_cast <QPaintEvent *> (event) != 0)) {
|
|
|
|
// In breakpoint or execution mode and while processing the events from the debugger,
|
|
// ignore all input or paint events targeted to widgets which are not children of this or the assistant dialog.
|
|
// Ignoring the paint event is required because otherwise a repaint action would be triggered on a layout which
|
|
// is potentially unstable or inconsistent.
|
|
// We nevertheless allow events send to a HelpDialog or ProgressWidget since those are vital for the application's
|
|
// functionality and are known not to cause any interference.
|
|
QObject *rec = obj;
|
|
while (rec && (rec != this && !dynamic_cast<lay::HelpDialog *> (rec) && !dynamic_cast<lay::ProgressWidget *> (rec))) {
|
|
rec = rec->parent ();
|
|
}
|
|
if (! rec) {
|
|
// TODO: reschedule the paint events (?)
|
|
return true;
|
|
}
|
|
|
|
} else if (! lay::BusySection::is_busy () && m_in_exec) {
|
|
|
|
// While no explicit event processing is in progress and we are executing, this is an indication that
|
|
// "real" events are processed. In that case, we can postpone excplit processing. This avoids interference
|
|
// with GUI code run in the debugger.
|
|
m_last_process_events = tl::Clock::current ();
|
|
|
|
}
|
|
|
|
// Handle events targeted towards the input edit box. This implements the special behavior of the command line.
|
|
if (obj == input_field && event->type() == QEvent::KeyPress) {
|
|
|
|
QKeyEvent *key_event = dynamic_cast<QKeyEvent *> (event);
|
|
if (key_event && key_event->key () == Qt::Key_Return) {
|
|
|
|
QString cmd = input_field->currentText ();
|
|
if (! cmd.isEmpty ()) {
|
|
|
|
if (m_history_index >= 0 && m_history_index < input_field->count () && cmd == input_field->itemText (m_history_index)) {
|
|
input_field->removeItem (m_history_index);
|
|
}
|
|
input_field->addItem (cmd);
|
|
|
|
execute (cmd);
|
|
|
|
input_field->clearEditText ();
|
|
m_edit_text = QString ();
|
|
m_history_index = -1;
|
|
|
|
}
|
|
|
|
// eat the event
|
|
return true;
|
|
|
|
} else if (key_event && key_event->key () == Qt::Key_Up) {
|
|
|
|
m_in_event_handler = true; // prevent setting of m_edit_text
|
|
|
|
int hi = m_history_index;
|
|
if (hi < 0) {
|
|
if (input_field->count () > 0) {
|
|
hi = input_field->count () - 1;
|
|
input_field->setCurrentIndex (hi);
|
|
}
|
|
} else if (hi > 0 && hi <= input_field->count ()) {
|
|
--hi;
|
|
input_field->setCurrentIndex (hi);
|
|
}
|
|
|
|
m_in_event_handler = false;
|
|
m_history_index = hi;
|
|
|
|
// eat the event
|
|
return true;
|
|
|
|
} else if (key_event && key_event->key () == Qt::Key_Down) {
|
|
|
|
m_in_event_handler = true; // prevent setting of m_edit_text
|
|
|
|
int hi = m_history_index;
|
|
if (hi < 0) {
|
|
if (input_field->count () > 0) {
|
|
hi = input_field->count () - 1;
|
|
input_field->setCurrentIndex (hi);
|
|
}
|
|
} else if (hi < input_field->count () - 1) {
|
|
++hi;
|
|
input_field->setCurrentIndex (hi);
|
|
} else {
|
|
hi = input_field->count ();
|
|
input_field->setEditText (m_edit_text);
|
|
}
|
|
|
|
m_in_event_handler = false;
|
|
m_history_index = hi;
|
|
|
|
// eat the event
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::flush ()
|
|
{
|
|
// .. no specific implementation required for flush() ..
|
|
}
|
|
|
|
bool
|
|
MacroEditorDialog::is_tty ()
|
|
{
|
|
// TODO: implement ANSI sequences?
|
|
return false;
|
|
}
|
|
|
|
int
|
|
MacroEditorDialog::columns ()
|
|
{
|
|
QFontMetrics fm (mp_console_text->font ());
|
|
#if QT_VERSION >= 0x60000
|
|
int cw = fm.horizontalAdvance (QString::fromUtf8 ("X"));
|
|
#else
|
|
int cw = fm.width (QString::fromUtf8 ("X"));
|
|
#endif
|
|
if (cw > 0) {
|
|
return mp_console_text->viewport ()->width () / cw;
|
|
} else {
|
|
// fallback:
|
|
return 80;
|
|
}
|
|
}
|
|
|
|
int
|
|
MacroEditorDialog::rows ()
|
|
{
|
|
QFontMetrics fm (mp_console_text->font ());
|
|
int ch = fm.height ();
|
|
if (ch > 0) {
|
|
return mp_console_text->viewport ()->height () / ch;
|
|
} else {
|
|
// fallback:
|
|
return 20;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::write_str (const char *text, output_stream os)
|
|
{
|
|
if (! mp_console_text->textCursor ().atEnd ()) {
|
|
QTextCursor c = mp_console_text->textCursor ();
|
|
c.movePosition (QTextCursor::End);
|
|
mp_console_text->setTextCursor (c);
|
|
}
|
|
|
|
if (m_os != OS_none && os != m_os && ! m_new_line) {
|
|
// insert a new line if the stream changes ..
|
|
write_str ("\n", m_os);
|
|
}
|
|
|
|
if (m_os != os) {
|
|
if (os == OS_stdout) {
|
|
mp_console_text->setCurrentCharFormat(m_stdout_format);
|
|
} else if (os == OS_echo) {
|
|
mp_console_text->setCurrentCharFormat(m_echo_format);
|
|
} else if (os == OS_stderr) {
|
|
mp_console_text->setCurrentCharFormat(m_stderr_format);
|
|
}
|
|
}
|
|
|
|
m_os = os;
|
|
|
|
for (const char *t = text; *t; ) {
|
|
|
|
const char *t0 = t;
|
|
for ( ; *t && *t != '\n'; ++t)
|
|
;
|
|
|
|
mp_console_text->insertPlainText (QString::fromUtf8 (t0, t - t0));
|
|
|
|
if (*t == '\n') {
|
|
++t;
|
|
// new line: terminate line
|
|
mp_console_text->insertPlainText (QString::fromUtf8 ("\n"));
|
|
m_new_line = true;
|
|
} else {
|
|
m_new_line = false;
|
|
}
|
|
|
|
}
|
|
|
|
md_update_console_text ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::update_console_text ()
|
|
{
|
|
mp_console_text->ensureCursorVisible ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::commit ()
|
|
{
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator page = m_tab_widgets.begin (); page != m_tab_widgets.end (); ++page) {
|
|
if (page->second->is_modified ()) {
|
|
page->second->commit ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::macro_collection_deleted (lym::MacroCollection *collection)
|
|
{
|
|
// close the tab pages related to the collection we want to delete
|
|
std::set <lym::Macro *> used_macros;
|
|
std::set <lym::MacroCollection *> used_collections;
|
|
collection->collect_used_nodes (used_macros, used_collections);
|
|
|
|
for (std::set <lym::Macro *>::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) {
|
|
|
|
if (mp_run_macro == *mc) {
|
|
mp_run_macro = 0;
|
|
}
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator p = m_tab_widgets.find (*mc);
|
|
if (p != m_tab_widgets.end ()) {
|
|
// disable the macro on the page - we'll ask for updates when the file
|
|
// watcher becomes active. So long, the macro is "zombie".
|
|
p->second->connect_macro (0);
|
|
m_tab_widgets.erase (p);
|
|
}
|
|
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
update_ui_to_run_mode ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::macro_deleted (lym::Macro *macro)
|
|
{
|
|
if (mp_run_macro == macro) {
|
|
mp_run_macro = 0;
|
|
}
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (macro);
|
|
if (page != m_tab_widgets.end ()) {
|
|
// disable the macro on the page - we'll ask for updates when the file
|
|
// watcher becomes active. So long, the macro is "zombie".
|
|
page->second->connect_macro (0);
|
|
m_tab_widgets.erase (page);
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
update_ui_to_run_mode ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::macro_collection_changed (lym::MacroCollection * /*collection*/)
|
|
{
|
|
refresh_file_watcher ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::macro_changed (lym::Macro *macro)
|
|
{
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (macro);
|
|
if (page != m_tab_widgets.end ()) {
|
|
int index = tabWidget->indexOf (page->second);
|
|
QString tt = tl::to_qstring (macro->summary ());
|
|
QString title = tl::to_qstring (macro->name ());
|
|
if (tabWidget->tabToolTip (index) != tt) {
|
|
tabWidget->setTabToolTip (index, tt);
|
|
}
|
|
if (tabWidget->tabText (index) != title) {
|
|
tabWidget->setTabText (index, title);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::current_tab_changed (int index)
|
|
{
|
|
add_edit_trace (false);
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (index));
|
|
if (page) {
|
|
int tab_index = 0;
|
|
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt, ++tab_index) {
|
|
if ((*mt)->set_current (page->macro ())) {
|
|
treeTab->setCurrentIndex (tab_index);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear the search
|
|
searchEditBox->clear ();
|
|
replaceFrame->setEnabled (page && page->macro () && !page->macro ()->is_readonly ());
|
|
|
|
apply_search ();
|
|
|
|
do_update_ui_to_run_mode ();
|
|
}
|
|
|
|
lym::Macro *MacroEditorDialog::create_macro_here (const char *prefix)
|
|
{
|
|
lay::MacroEditorTree *mt = current_macro_tree ();
|
|
lym::MacroCollection *collection = mt->current_macro_collection ();
|
|
if (! collection) {
|
|
lym::Macro *m = mt->current_macro ();
|
|
if (m) {
|
|
collection = m->parent ();
|
|
}
|
|
}
|
|
|
|
if (! collection || collection->is_readonly ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot add a macro here - the folder is read-only")));
|
|
}
|
|
|
|
return collection->create (prefix);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::macro_renamed (lym::Macro * /*macro*/)
|
|
{
|
|
refresh_file_watcher ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::folder_renamed (lym::MacroCollection * /*mc*/)
|
|
{
|
|
refresh_file_watcher ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::move_macro (lym::Macro *source, lym::MacroCollection *target)
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
if (source->parent () != target) {
|
|
|
|
lym::Macro *m = target->create (source->name ().c_str (), source->format ());
|
|
m->assign (*source);
|
|
m->set_readonly (false);
|
|
m->save ();
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (source);
|
|
if (page != m_tab_widgets.end ()) {
|
|
MacroEditorPage *w = page->second;
|
|
w->connect_macro (m);
|
|
m_tab_widgets.erase (page);
|
|
m_tab_widgets.insert (std::make_pair (m, w));
|
|
tabWidget->setTabToolTip (tabWidget->indexOf (w), tl::to_qstring (m->summary ()));
|
|
tabWidget->setTabText (tabWidget->indexOf (w), tl::to_qstring (m->name ()));
|
|
}
|
|
|
|
if (! source->is_readonly ()) {
|
|
lym::MacroCollection *collection = source->parent ();
|
|
if (collection && ! collection->is_readonly ()) {
|
|
if (source->del ()) {
|
|
collection->erase (source);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) {
|
|
(*mt)->set_current (m);
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::move_subfolder (lym::MacroCollection *source, lym::MacroCollection *target)
|
|
{
|
|
lym::MacroCollection *mt = target->create_folder (source->name ().c_str ());
|
|
if (! mt) {
|
|
return;
|
|
}
|
|
|
|
std::vector <lym::MacroCollection::iterator> m_del;
|
|
|
|
for (lym::MacroCollection::iterator mm = source->begin (); mm != source->end (); ++mm) {
|
|
|
|
lym::Macro *m = mt->create (mm->second->name ().c_str ());
|
|
if (!m) {
|
|
continue;
|
|
}
|
|
|
|
m->assign (*mm->second);
|
|
m->set_readonly (false);
|
|
m->save ();
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (mm->second);
|
|
if (page != m_tab_widgets.end ()) {
|
|
MacroEditorPage *w = page->second;
|
|
w->connect_macro (m);
|
|
m_tab_widgets.erase (page);
|
|
m_tab_widgets.insert (std::make_pair (m, w));
|
|
tabWidget->setTabToolTip (tabWidget->indexOf (w), tl::to_qstring (m->summary ()));
|
|
tabWidget->setTabText (tabWidget->indexOf (w), tl::to_qstring (m->name ()));
|
|
}
|
|
|
|
if (! mm->second->is_readonly ()) {
|
|
if (mm->second->del ()) {
|
|
m_del.push_back (mm);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::vector <lym::MacroCollection::iterator>::const_iterator d = m_del.begin (); d != m_del.end (); ++d) {
|
|
source->erase (*d);
|
|
}
|
|
|
|
std::vector <lym::MacroCollection::child_iterator> mc_del;
|
|
|
|
for (lym::MacroCollection::child_iterator m = source->begin_children (); m != source->end_children (); ++m) {
|
|
move_subfolder (m->second, mt);
|
|
if (! m->second->is_readonly ()) {
|
|
if (m->second->del ()) {
|
|
mc_del.push_back (m);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::vector <lym::MacroCollection::child_iterator>::const_iterator d = mc_del.begin (); d != mc_del.end (); ++d) {
|
|
source->erase (*d);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::move_folder (lym::MacroCollection *source, lym::MacroCollection *target)
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
if (source->parent () != target) {
|
|
|
|
move_subfolder (source, target);
|
|
if (source->parent () && ! source->is_readonly ()) {
|
|
if (source->del ()) {
|
|
source->parent ()->erase (source);
|
|
}
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::set_editor_focus ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
lay::SignalBlocker signal_blocker (searchEditBox);
|
|
page->set_editor_focus ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::replace_mode_button_clicked ()
|
|
{
|
|
if (replaceFrame->isVisible ()) {
|
|
replaceFrame->hide ();
|
|
replaceModeButton->setArrowType (Qt::RightArrow);
|
|
} else {
|
|
replaceFrame->show ();
|
|
replaceText->setFocus ();
|
|
replaceModeButton->setArrowType (Qt::LeftArrow);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::find_next_button_clicked ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search (true);
|
|
page->find_next ();
|
|
if (sender () != searchEditBox && sender () != replaceText) {
|
|
set_editor_focus ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::find_prev_button_clicked ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search (true);
|
|
page->find_prev ();
|
|
if (sender () != searchEditBox && sender () != replaceText) {
|
|
set_editor_focus ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::replace_next_button_clicked ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search (true);
|
|
page->replace_and_find_next (replaceText->text ());
|
|
if (sender () != replaceText) {
|
|
set_editor_focus ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::replace_all_button_clicked ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search (true);
|
|
page->replace_all (replaceText->text ());
|
|
set_editor_focus ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::search_requested (const QString &s)
|
|
{
|
|
if (! s.isNull ()) {
|
|
searchEditBox->setText (s);
|
|
} else {
|
|
searchEditBox->selectAll ();
|
|
}
|
|
searchEditBox->setFocus ();
|
|
search_editing ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::search_replace ()
|
|
{
|
|
searchEditBox->setFocus (Qt::TabFocusReason);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::search_editing ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search ();
|
|
page->find_reset (); // search from the initial position
|
|
page->find_next ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::search_edited ()
|
|
{
|
|
// since we want to move the focus to the text field, we have to do this in the deferred method
|
|
// (this method is called from an event handler and setFocus does not have an effect then)
|
|
md_search_edited ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::search_finished ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
page->find_reset (); // search from the initial position
|
|
set_editor_focus ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::do_search_edited ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
apply_search ();
|
|
page->find_reset (); // search from the initial position
|
|
page->find_next ();
|
|
set_editor_focus ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::apply_search (bool if_needed)
|
|
{
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
if (! searchEditBox->text ().isEmpty ()) {
|
|
QRegExp re (searchEditBox->text (),
|
|
actionCaseSensitive->isChecked () ? Qt::CaseSensitive : Qt::CaseInsensitive,
|
|
actionUseRegularExpressions->isChecked () ? QRegExp::RegExp : QRegExp::FixedString);
|
|
if (! if_needed || page->get_search () != re) {
|
|
page->set_search (re);
|
|
}
|
|
} else {
|
|
if (! if_needed || page->get_search () != QRegExp ()) {
|
|
// this is really a "null" regexp:
|
|
page->set_search (QRegExp ());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::save_button_clicked ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
lym::Macro *m = current_macro_tree ()->current_macro ();
|
|
if (m) {
|
|
m->save ();
|
|
} else if (tabWidget->currentWidget ()) {
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (page && page->macro ()) {
|
|
page->macro ()->save ();
|
|
}
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::save_as_button_clicked ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
lym::Macro *m = current_macro_tree ()->current_macro ();
|
|
if (! m) {
|
|
return;
|
|
}
|
|
|
|
lay::FileDialog file_dialog (lay::MainWindow::instance (), tl::to_string (QObject::tr ("Save Macro As")), tl::to_string (QObject::tr ("All files (*)")), "");
|
|
|
|
std::string fn = m->path ();
|
|
if (file_dialog.get_save (fn)) {
|
|
|
|
m->save_to (fn);
|
|
|
|
reload_macros ();
|
|
|
|
lym::Macro *lym = mp_root->find_macro (fn);
|
|
if (lym) {
|
|
open_macro (lym);
|
|
}
|
|
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::setup_button_clicked ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
lay::ConfigurationDialog config_dialog (this, mp_plugin_root, "MacroEditor");
|
|
if (config_dialog.exec ()) {
|
|
refresh_file_watcher ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::properties_button_clicked ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
if (! tabWidget->currentWidget ()) {
|
|
return;
|
|
}
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page || ! page->macro ()) {
|
|
return;
|
|
}
|
|
|
|
lym::Macro *macro = page->macro ();
|
|
if (macro->format () == lym::Macro::PlainTextWithHashAnnotationsFormat) {
|
|
page->commit ();
|
|
}
|
|
|
|
lay::MacroPropertiesDialog dia (this);
|
|
|
|
if (dia.exec_dialog (macro)) {
|
|
macro->sync_text_with_properties ();
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::help_requested(const QString &s)
|
|
{
|
|
lay::MainWindow::instance ()->show_assistant_topic (tl::to_string (s));
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::help_button_clicked()
|
|
{
|
|
lay::MainWindow::instance ()->show_assistant_url ("int:/code/index.xml");
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::add_button_clicked()
|
|
{
|
|
BEGIN_PROTECTED
|
|
new_macro ();
|
|
END_PROTECTED
|
|
}
|
|
|
|
lym::Macro *
|
|
MacroEditorDialog::new_macro()
|
|
{
|
|
ensure_writeable_collection_selected ();
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
|
|
if (! ct->current_macro () && ! ct->current_macro_collection ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Select a position where to add the macro")));
|
|
}
|
|
|
|
// ask for a template
|
|
std::string cat;
|
|
if (treeTab->currentIndex () < int (m_categories.size ())) {
|
|
cat = m_categories [treeTab->currentIndex ()].name;
|
|
}
|
|
lay::MacroTemplateSelectionDialog template_dialog (this, m_macro_templates, cat);
|
|
int template_index = template_dialog.exec_dialog ();
|
|
if (template_index < 0) {
|
|
return 0;
|
|
}
|
|
|
|
lym::Macro *m = create_macro_here (m_macro_templates [template_index]->name ().c_str ());
|
|
m->assign (*m_macro_templates [template_index]);
|
|
m->set_readonly (false);
|
|
// we don't want to keep the template's description
|
|
m->set_description (std::string ());
|
|
|
|
open_macro (m);
|
|
|
|
// NOTE: we save to make the file watcher go silent and to keep the file system in sync
|
|
m->save ();
|
|
|
|
ct->set_current (m);
|
|
if (ct->currentIndex ().isValid () && (ct->model ()->flags (ct->currentIndex ()) & Qt::ItemIsEditable)) {
|
|
ct->edit (ct->currentIndex ());
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
return m;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::tab_close_requested (int index)
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
if (! tabWidget->widget (index)) {
|
|
return;
|
|
}
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (index));
|
|
if (! page) {
|
|
delete tabWidget->currentWidget ();
|
|
return;
|
|
}
|
|
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::iterator p = m_tab_widgets.begin (); p != m_tab_widgets.end (); ++p) {
|
|
if (p->second == page) {
|
|
m_tab_widgets.erase (p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
page->connect_macro (0);
|
|
delete page;
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::close_button_clicked ()
|
|
{
|
|
tab_close_requested (tabWidget->currentIndex ());
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::delete_button_clicked()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
|
|
lym::MacroCollection *collection = ct->current_macro_collection ();
|
|
lym::Macro *m = ct->current_macro ();
|
|
|
|
if (collection) {
|
|
|
|
if (collection->virtual_mode ()) {
|
|
throw tl::Exception ("Can't delete this folder - it is a macro group");
|
|
}
|
|
if (collection->is_readonly ()) {
|
|
throw tl::Exception ("Can't delete this folder - it is read-only");
|
|
}
|
|
if (collection->begin () != collection->end () || collection->begin_children () != collection->end_children ()) {
|
|
throw tl::Exception ("Can't delete this folder - it is not empty");
|
|
}
|
|
|
|
lym::MacroCollection *p = collection->parent ();
|
|
|
|
if (p) {
|
|
|
|
if (QMessageBox::question (this, QObject::tr ("Delete Folder"),
|
|
tl::to_qstring (tl::to_string (QObject::tr ("Are you sure to delete the folder ")) + collection->path () + "?"),
|
|
QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) {
|
|
return;
|
|
}
|
|
|
|
if (! collection->del ()) {
|
|
throw tl::Exception ("Can't delete this folder - there may still be some other files inside it");
|
|
}
|
|
p->erase (collection);
|
|
|
|
}
|
|
|
|
ct->set_current (p);
|
|
|
|
} else if (m) {
|
|
|
|
lym::MacroCollection *collection = m->parent ();
|
|
if (m->is_readonly ()) {
|
|
throw tl::Exception ("Can't delete this macro - it is readonly");
|
|
}
|
|
if (collection) {
|
|
|
|
if (QMessageBox::question (this, QObject::tr ("Delete Macro File"),
|
|
tl::to_qstring (tl::to_string (QObject::tr ("Are you sure to delete the macro file ")) + m->path () + "?"),
|
|
QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) {
|
|
return;
|
|
}
|
|
|
|
if (! m->del ()) {
|
|
throw tl::Exception ("Can't delete this macro");
|
|
}
|
|
|
|
collection->erase (m);
|
|
|
|
}
|
|
|
|
ct->set_current (collection);
|
|
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::rename_button_clicked()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
QModelIndex index = ct->currentIndex ();
|
|
if (index.isValid ()) {
|
|
if (ct->model ()->flags (index) & Qt::ItemIsEditable) {
|
|
ct->edit (index);
|
|
} else {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot edit this item's name")));
|
|
}
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::ensure_writeable_collection_selected ()
|
|
{
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
lym::MacroCollection *collection = ct->current_macro_collection ();
|
|
if (! collection) {
|
|
lym::Macro *macro = ct->current_macro ();
|
|
if (macro) {
|
|
collection = macro->parent ();
|
|
}
|
|
}
|
|
|
|
// Select the first writeable collection if none is selected
|
|
if (! collection || collection->is_readonly ()) {
|
|
for (lym::MacroCollection::const_child_iterator c = mp_root->begin_children (); c != mp_root->end_children (); ++c) {
|
|
if (c->second->category () == ct->category () && ! c->second->is_readonly ()) {
|
|
ct->set_current (c->second);
|
|
collection = c->second;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! collection) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot perform that operation - no place selected")));
|
|
}
|
|
if (collection->is_readonly ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot perform that operation here - this place is read-only")));
|
|
}
|
|
}
|
|
|
|
static std::vector<std::pair<std::string, std::string> >
|
|
get_custom_paths (lay::Dispatcher *root)
|
|
{
|
|
std::vector <std::pair<std::string, std::string> > paths;
|
|
|
|
std::string mp;
|
|
root->config_get (cfg_custom_macro_paths, mp);
|
|
|
|
try {
|
|
|
|
tl::Extractor ex (mp.c_str ());
|
|
while (! ex.at_end ()) {
|
|
|
|
paths.push_back (std::make_pair (std::string (), std::string ("macros")));
|
|
ex.read_word_or_quoted (paths.back ().first);
|
|
if (ex.test (":")) {
|
|
ex.read_word (paths.back ().second);
|
|
}
|
|
|
|
ex.test (";");
|
|
|
|
}
|
|
|
|
} catch (...) { }
|
|
|
|
return paths;
|
|
}
|
|
|
|
static void
|
|
set_custom_paths (lay::Dispatcher *root, const std::vector<std::pair<std::string, std::string> > &paths)
|
|
{
|
|
std::string mp;
|
|
|
|
// add paths from our category
|
|
for (std::vector<std::pair<std::string, std::string> >::const_iterator p = paths.begin (); p != paths.end (); ++p) {
|
|
if (! mp.empty ()) {
|
|
mp += ";";
|
|
}
|
|
mp += tl::to_quoted_string (p->first);
|
|
mp += ":";
|
|
mp += p->second;
|
|
}
|
|
|
|
root->config_set (cfg_custom_macro_paths, mp);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::file_changed_timer ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
|
|
// Make the names unique
|
|
std::sort (m_changed_files.begin (), m_changed_files.end ());
|
|
m_changed_files.erase (std::unique (m_changed_files.begin (), m_changed_files.end ()), m_changed_files.end ());
|
|
|
|
// Make the names unique
|
|
std::sort (m_removed_files.begin (), m_removed_files.end ());
|
|
m_removed_files.erase (std::unique (m_removed_files.begin (), m_removed_files.end ()), m_removed_files.end ());
|
|
|
|
if (m_changed_files.empty () && m_removed_files.empty ()) {
|
|
return;
|
|
}
|
|
|
|
std::set<std::string> modified_files;
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator m = m_tab_widgets.begin (); m != m_tab_widgets.end (); ++m) {
|
|
if (m->first->is_modified ()) {
|
|
modified_files.insert (m->first->path ());
|
|
}
|
|
}
|
|
|
|
std::vector<QString> conflicts;
|
|
for (std::vector<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
|
|
if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) {
|
|
conflicts.push_back (*f);
|
|
}
|
|
}
|
|
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
|
|
if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) {
|
|
conflicts.push_back (*f);
|
|
}
|
|
}
|
|
|
|
QString msg;
|
|
|
|
if (m_changed_files.size () + m_removed_files.size () == 1) {
|
|
msg = QObject::tr ("The following file has been changed on disk:\n\n");
|
|
for (std::vector<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
|
|
msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f);
|
|
}
|
|
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
|
|
msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f);
|
|
}
|
|
if (!conflicts.empty ()) {
|
|
msg += tr ("\nThis file has been modified in the editor as well.\nRefresh this file and discard changes?");
|
|
} else {
|
|
msg += tr ("\nRefresh this file?");
|
|
}
|
|
} else {
|
|
msg = QObject::tr ("The following files have been changed on disk:\n\n");
|
|
for (std::vector<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
|
|
msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f);
|
|
}
|
|
for (std::vector<QString>::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) {
|
|
msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f);
|
|
}
|
|
if (!conflicts.empty ()) {
|
|
msg += tr("\nSome of these files are modified on disk and in the editor:\n\n");
|
|
for (std::vector<QString>::const_iterator f = conflicts.begin (); f != conflicts.end (); ++f) {
|
|
msg += QString::fromUtf8 (" %1 (conflict)\n").arg (*f);
|
|
}
|
|
msg += tr ("\nRefresh these and the other files and discard all changes?");
|
|
} else {
|
|
msg += tr ("\nRefresh those files?");
|
|
}
|
|
}
|
|
|
|
m_changed_files.clear ();
|
|
m_removed_files.clear ();
|
|
|
|
if (QMessageBox::question (this, tr ("Refresh Files"), msg, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
|
reload_macros ();
|
|
}
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::file_changed (const QString &path)
|
|
{
|
|
m_changed_files.push_back (path);
|
|
|
|
// Wait a little to allow for more reload requests to collect
|
|
m_file_changed_timer->setInterval (300);
|
|
m_file_changed_timer->start ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::file_removed (const QString &path)
|
|
{
|
|
m_removed_files.push_back (path);
|
|
|
|
// Wait a little to let more to allow for more reload requests to collect
|
|
m_file_changed_timer->setInterval (300);
|
|
m_file_changed_timer->start ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::sync_file_watcher (lym::MacroCollection * /*collection*/)
|
|
{
|
|
#if 0
|
|
// this would monitor the whole tree - but it's a little too deep. This
|
|
// solution also reports changes in directories that are in no way related to
|
|
// macro files.
|
|
if (QDir (tl::to_qstring (collection->path ())).exists ()) {
|
|
|
|
m_file_watcher->add_file (collection->path ());
|
|
|
|
for (lym::MacroCollection::iterator m = collection->begin (); m != collection->end (); ++m) {
|
|
if (m->second->is_file ()) {
|
|
m_file_watcher->add_file (m->second->path());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (lym::MacroCollection::child_iterator m = collection->begin_children (); m != collection->end_children (); ++m) {
|
|
sync_file_watcher (m->second);
|
|
}
|
|
#else
|
|
// This solution monitors the open files only
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator m = m_tab_widgets.begin (); m != m_tab_widgets.end (); ++m) {
|
|
m_file_watcher->add_file (m->first->path ());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::refresh_file_watcher ()
|
|
{
|
|
m_file_watcher->clear ();
|
|
m_file_watcher->enable (false);
|
|
|
|
if (m_file_watcher_enabled) {
|
|
dm_refresh_file_watcher ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::do_refresh_file_watcher ()
|
|
{
|
|
try {
|
|
if (m_file_watcher_enabled) {
|
|
sync_file_watcher (mp_root);
|
|
m_file_watcher->enable (true);
|
|
}
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::reload_macros ()
|
|
{
|
|
m_file_watcher->clear ();
|
|
try {
|
|
mp_root->reload (false);
|
|
refresh_file_watcher ();
|
|
} catch (...) {
|
|
refresh_file_watcher ();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::refresh ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
|
|
// save all so that we don't get differences in the text
|
|
commit ();
|
|
mp_root->save ();
|
|
|
|
reload_macros ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::add_location ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
QString new_dir = QFileDialog::getExistingDirectory (this, QObject::tr ("Add Location"));
|
|
if (new_dir.isNull ()) {
|
|
return;
|
|
}
|
|
|
|
std::string cat = current_macro_tree ()->category ();
|
|
|
|
std::vector <std::pair<std::string, std::string> > paths = get_custom_paths (mp_plugin_root);
|
|
std::string new_path = tl::to_string (QFileInfo (new_dir).absoluteFilePath ());
|
|
paths.push_back (std::make_pair (new_path, cat));
|
|
|
|
lym::MacroCollection *c = mp_root->add_folder (tl::to_string (QObject::tr ("Project")) + " - " + new_path, new_path, cat, false);
|
|
if (!c) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("The selected directory is already installed as custom location")));
|
|
}
|
|
|
|
set_custom_paths (mp_plugin_root, paths);
|
|
|
|
if (c->has_autorun ()) {
|
|
if (QMessageBox::question (this, QObject::tr ("Run Macros"), QObject::tr ("The selected folder has macros configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
|
c->autorun ();
|
|
}
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::remove_location ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
lym::MacroCollection *collection = ct->current_macro_collection ();
|
|
if (! collection) {
|
|
lym::Macro *m = ct->current_macro ();
|
|
if (m) {
|
|
collection = m->parent ();
|
|
}
|
|
}
|
|
|
|
if (! collection) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Select tree location to remove")));
|
|
}
|
|
|
|
std::vector <std::pair <std::string, std::string> > paths = get_custom_paths (mp_plugin_root);
|
|
|
|
bool found = false;
|
|
|
|
// locate the location in the set of paths
|
|
for (std::vector <std::pair <std::string, std::string> >::iterator p = paths.begin (); p != paths.end (); ++p) {
|
|
if (p->first == collection->path () && p->second == ct->category ()) {
|
|
paths.erase (p);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! found) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove that location")));
|
|
}
|
|
|
|
// actually remove the collection (update is done through the
|
|
// macro_collection_deleted signal handler).
|
|
mp_root->erase (collection);
|
|
|
|
// save the new paths
|
|
set_custom_paths (mp_plugin_root, paths);
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::import_button_clicked ()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
ensure_writeable_collection_selected ();
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
if (! ct->current_macro () && ! ct->current_macro_collection ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Select a position where to import the macro")));
|
|
}
|
|
|
|
// TODO: risky: file_dialog might be deleted because the MainWindow is deleted (it's the parent)
|
|
static lay::FileDialog *file_dialog = 0;
|
|
if (! file_dialog) {
|
|
|
|
std::string filters = tl::to_string (QObject::tr ("All files (*);;KLayout macro files (*.lym);;Ruby files (*.rb);;Python files (*.py)"));
|
|
|
|
// add the suffixes in the DSL interpreter declarations
|
|
for (tl::Registrar<lym::MacroInterpreter>::iterator cls = tl::Registrar<lym::MacroInterpreter>::begin (); cls != tl::Registrar<lym::MacroInterpreter>::end (); ++cls) {
|
|
if (! cls->suffix ().empty ()) {
|
|
filters += ";;";
|
|
if (! cls->description ().empty ()) {
|
|
filters += cls->description () + " ";
|
|
}
|
|
filters += "(*.";
|
|
filters += cls->suffix ();
|
|
filters += ")";
|
|
}
|
|
}
|
|
|
|
file_dialog = new lay::FileDialog (lay::MainWindow::instance (), tl::to_string (QObject::tr ("Import Macro File")), filters, "lym");
|
|
|
|
}
|
|
|
|
std::string fn;
|
|
if (file_dialog->get_open (fn)) {
|
|
|
|
// create a new macro and use the new name as the base name
|
|
lym::Macro *m = create_macro_here (tl::to_string (QFileInfo (tl::to_qstring (fn)).baseName ()).c_str ());
|
|
|
|
try {
|
|
m->load_from (fn);
|
|
} catch (...) {
|
|
// On error delete the macro
|
|
if (m->parent ()) {
|
|
m->parent ()->erase (m);
|
|
}
|
|
throw;
|
|
}
|
|
|
|
ct->set_current (m);
|
|
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::new_folder_button_clicked()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
ensure_writeable_collection_selected ();
|
|
|
|
lay::MacroEditorTree *ct = current_macro_tree ();
|
|
lym::MacroCollection *collection = ct->current_macro_collection ();
|
|
if (! collection) {
|
|
lym::Macro *m = ct->current_macro ();
|
|
if (m) {
|
|
collection = m->parent ();
|
|
}
|
|
}
|
|
|
|
if (! collection || collection->is_readonly ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Cannot create a folder here")));
|
|
}
|
|
|
|
lym::MacroCollection *mm = collection->create_folder ();
|
|
if (! mm) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Failed to create the folder here")));
|
|
}
|
|
|
|
ct->set_current (mm);
|
|
if (ct->currentIndex ().isValid () && (ct->model ()->flags (ct->currentIndex ()) & Qt::ItemIsEditable)) {
|
|
ct->edit (ct->currentIndex ());
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::save_all_button_clicked()
|
|
{
|
|
if (m_in_exec) {
|
|
return;
|
|
}
|
|
|
|
BEGIN_PROTECTED
|
|
|
|
commit ();
|
|
mp_root->save ();
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::open_macro (lym::Macro *m)
|
|
{
|
|
MacroEditorPage *page = create_page (m);
|
|
m_tab_widgets.insert (std::make_pair (m, page));
|
|
int index = tabWidget->addTab (page, tl::to_qstring (m->name ()));
|
|
tabWidget->setTabToolTip (index, tl::to_qstring (m->summary ()));
|
|
tabWidget->setCurrentWidget (page);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::item_double_clicked(lym::Macro *m)
|
|
{
|
|
BEGIN_PROTECTED
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (m);
|
|
if (page == m_tab_widgets.end ()) {
|
|
open_macro (m);
|
|
} else {
|
|
tabWidget->setCurrentIndex (tabWidget->indexOf (page->second));
|
|
}
|
|
|
|
refresh_file_watcher ();
|
|
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::start_exec (gsi::Interpreter *ec)
|
|
{
|
|
// ignore calls from other interpreters
|
|
if (m_in_exec) {
|
|
tl_assert (ec != mp_exec_controller);
|
|
return;
|
|
} else if (m_ignore_exec_events) {
|
|
return;
|
|
}
|
|
|
|
// prevents recursion
|
|
m_ignore_exec_events = true;
|
|
|
|
try {
|
|
|
|
m_file_to_widget.clear ();
|
|
m_include_expanders.clear ();
|
|
m_include_paths_to_ids.clear ();
|
|
m_include_file_id_cache.clear ();
|
|
|
|
m_last_process_events = tl::Clock::current ();
|
|
|
|
m_in_exec = true;
|
|
mp_exec_controller = ec;
|
|
m_in_breakpoint = false;
|
|
m_continue = true;
|
|
m_trace_count = 0;
|
|
m_current_stack_depth = -1;
|
|
m_process_events_interval = 0.05;
|
|
|
|
for (std::map<lym::Macro *, MacroEditorPage *>::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) {
|
|
f->second->exec_model ()->set_current_line (-1);
|
|
f->second->exec_model ()->set_run_mode (true);
|
|
}
|
|
|
|
do_update_ui_to_run_mode ();
|
|
|
|
} catch (...) {
|
|
// .. ignore exceptions here ..
|
|
}
|
|
|
|
m_ignore_exec_events = false;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::end_exec (gsi::Interpreter *ec)
|
|
{
|
|
if ((m_in_exec && ec != mp_exec_controller) || m_ignore_exec_events) {
|
|
return;
|
|
}
|
|
|
|
// prevents recursion
|
|
m_ignore_exec_events = true;
|
|
|
|
try {
|
|
|
|
m_in_exec = false;
|
|
mp_exec_controller = 0;
|
|
m_continue = false;
|
|
m_current_stack_depth = -1;
|
|
|
|
if (QApplication::activeModalWidget () == this) {
|
|
// close this window if it was shown in modal mode
|
|
QDialog::accept ();
|
|
}
|
|
|
|
for (std::map<lym::Macro *, MacroEditorPage *>::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) {
|
|
f->second->exec_model ()->set_run_mode (false);
|
|
}
|
|
|
|
do_update_ui_to_run_mode ();
|
|
|
|
} catch (...) {
|
|
// .. ignore exceptions here ..
|
|
}
|
|
|
|
m_ignore_exec_events = false;
|
|
}
|
|
|
|
const size_t pseudo_file_offset = std::numeric_limits<size_t>::max () / 2;
|
|
|
|
size_t
|
|
MacroEditorDialog::id_for_path (gsi::Interpreter *, const std::string &path)
|
|
{
|
|
for (std::map <lym::Macro *, MacroEditorPage *>::const_iterator m = m_tab_widgets.begin (); m != m_tab_widgets.end (); ++m) {
|
|
if (m->first->path () == path) {
|
|
m_file_to_widget.push_back (*m);
|
|
return m_file_to_widget.size ();
|
|
}
|
|
}
|
|
|
|
lym::Macro *macro = mp_root->find_macro (path);
|
|
if (macro) {
|
|
m_file_to_widget.push_back (std::make_pair (macro, (MacroEditorPage *) 0));
|
|
return m_file_to_widget.size ();
|
|
}
|
|
|
|
if (! path.empty () && path[0] == '@') {
|
|
m_include_expanders.push_back (tl::IncludeExpander::from_string (path));
|
|
return pseudo_file_offset + m_include_expanders.size () - 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::translate_pseudo_id (size_t &file_id, int &line)
|
|
{
|
|
if (file_id >= pseudo_file_offset) {
|
|
|
|
file_id -= pseudo_file_offset;
|
|
|
|
std::pair<size_t, int> ck (file_id, line);
|
|
|
|
std::map<std::pair<size_t, int>, std::pair<size_t, int> >::iterator ic = m_include_file_id_cache.find (ck);
|
|
if (ic != m_include_file_id_cache.end ()) {
|
|
|
|
file_id = ic->second.first;
|
|
line = ic->second.second;
|
|
|
|
} else {
|
|
|
|
if (file_id < m_include_expanders.size ()) {
|
|
|
|
std::pair<std::string, int> fp = m_include_expanders [file_id].translate_to_original (line);
|
|
line = fp.second;
|
|
|
|
std::map<std::string, size_t>::const_iterator i = m_include_paths_to_ids.find (fp.first);
|
|
if (i == m_include_paths_to_ids.end ()) {
|
|
|
|
size_t new_id = id_for_path (0, fp.first);
|
|
if (new_id < pseudo_file_offset) {
|
|
file_id = new_id;
|
|
} else {
|
|
file_id = 0;
|
|
}
|
|
|
|
m_include_paths_to_ids.insert (std::make_pair (fp.first, file_id));
|
|
|
|
} else {
|
|
file_id = i->second;
|
|
}
|
|
|
|
} else {
|
|
|
|
// give up.
|
|
file_id = 0;
|
|
line = 0;
|
|
|
|
}
|
|
|
|
m_include_file_id_cache.insert (std::make_pair (ck, std::make_pair (file_id, line)));
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static void exit_from_macro ()
|
|
{
|
|
throw tl::ExitException ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_id, int line, const std::string &eclass, const std::string &emsg, const gsi::StackTraceProvider *stack_trace_provider)
|
|
{
|
|
// no action if stop on exception is disabled
|
|
if (!m_stop_on_exception) {
|
|
return;
|
|
}
|
|
|
|
if (!m_in_exec) {
|
|
exit_from_macro ();
|
|
}
|
|
|
|
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
|
|
if (lay::BusySection::is_busy ()) {
|
|
return;
|
|
}
|
|
|
|
// translate the pseudo file ID and line to the real one (include file processing)
|
|
translate_pseudo_id (file_id, line);
|
|
|
|
try {
|
|
|
|
// If the exception is thrown in code that is inside a file managed by the macro collection,
|
|
// offer to stop the debugger there.
|
|
std::vector<tl::BacktraceElement> bt = stack_trace_provider->stack_trace ();
|
|
size_t scope_index = stack_trace_provider->scope_index ();
|
|
if (bt.empty () || !mp_root->find_macro (bt [scope_index].file)) {
|
|
return;
|
|
}
|
|
|
|
std::string p;
|
|
if (file_id > 0 && file_id <= m_file_to_widget.size () && m_file_to_widget [file_id - 1].first) {
|
|
p = m_file_to_widget [file_id - 1].first->path ();
|
|
if (m_ignore_exception_list.find (p) != m_ignore_exception_list.end ()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int res = QMessageBox::critical (this, QObject::tr ("Exception Caught"),
|
|
tl::to_qstring (tl::to_string (QObject::tr ("Caught the following exception:\n")) + emsg + " (Class " + eclass + ")\n\n" + tl::to_string (QObject::tr ("Press 'Ok' to continue.\nPress 'Ignore' to ignore this and future exceptions from this file.\nPress 'Cancel' to stop in the debugger"))),
|
|
QMessageBox::Cancel | QMessageBox::Ok | QMessageBox::Ignore,
|
|
QMessageBox::Ok);
|
|
|
|
if (res == QMessageBox::Ok) {
|
|
|
|
return;
|
|
|
|
} else if (res == QMessageBox::Ignore) {
|
|
|
|
std::string il;
|
|
il += tl::to_quoted_string (p);
|
|
for (std::set<std::string>::const_iterator i = m_ignore_exception_list.begin (); i != m_ignore_exception_list.end (); ++i) {
|
|
il += ";";
|
|
il += tl::to_quoted_string (*i);
|
|
}
|
|
mp_plugin_root->config_set (cfg_macro_editor_ignore_exception_list, il);
|
|
return;
|
|
|
|
}
|
|
|
|
write_str (emsg.c_str (), OS_stderr);
|
|
write_str ("\n", OS_stderr);
|
|
|
|
if (file_id > 0 && file_id <= m_file_to_widget.size () && m_file_to_widget [file_id - 1].second) {
|
|
m_file_to_widget [file_id - 1].second->set_error_line (line);
|
|
}
|
|
|
|
enter_breakpoint_mode (interpreter, stack_trace_provider);
|
|
|
|
if (QApplication::activeModalWidget () && QApplication::activeModalWidget () != this) {
|
|
|
|
// apparently that is the only way to override the event handling mechanism of Qt:
|
|
// if the breakpoint is issued from inside an event handler of a modal dialog, the
|
|
// editor window does not receive events, not even if we requested filtering.
|
|
hide ();
|
|
exec ();
|
|
show ();
|
|
|
|
} else {
|
|
|
|
while (m_in_breakpoint && m_in_exec)
|
|
{
|
|
process_events (QEventLoop::WaitForMoreEvents);
|
|
}
|
|
|
|
}
|
|
|
|
leave_breakpoint_mode ();
|
|
|
|
} catch (...) {
|
|
leave_breakpoint_mode ();
|
|
throw;
|
|
}
|
|
|
|
if (! m_in_exec) {
|
|
exit_from_macro ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int line, const gsi::StackTraceProvider *stack_trace_provider)
|
|
{
|
|
if (!m_in_exec) {
|
|
exit_from_macro ();
|
|
}
|
|
|
|
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
|
|
if (lay::BusySection::is_busy ()) {
|
|
return;
|
|
}
|
|
|
|
// adjust the current stack level after an exception
|
|
if (m_current_stack_depth < 0) {
|
|
m_current_stack_depth = stack_trace_provider->stack_depth ();
|
|
}
|
|
|
|
// translate the pseudo file ID and line to the real one (include file processing)
|
|
translate_pseudo_id (file_id, line);
|
|
|
|
// Note: only scripts running in the context of the execution controller (the one who called start_exec)
|
|
// can be interrupted and single-stepped, but breakpoints can make the debugger stop in other interpreters.
|
|
if (file_id > 0 && ((interpreter == mp_exec_controller && m_stop_stack_depth >= 0 && stack_trace_provider->stack_depth () <= m_stop_stack_depth) ||
|
|
(interpreter == mp_exec_controller && ! m_continue) ||
|
|
(file_id <= m_file_to_widget.size () && m_file_to_widget [file_id - 1].second && m_file_to_widget [file_id - 1].second->exec_model ()->is_breakpoint (line)))) {
|
|
|
|
try {
|
|
|
|
enter_breakpoint_mode (interpreter, stack_trace_provider);
|
|
|
|
if (QApplication::activeModalWidget () && QApplication::activeModalWidget () != this) {
|
|
|
|
// apparently that is the only way to override the event handling mechanism of Qt:
|
|
// if the breakpoint is issued from inside an event handler of a modal dialog, the
|
|
// editor window does not receive events, not even if we requested filtering.
|
|
hide ();
|
|
exec ();
|
|
show ();
|
|
|
|
} else {
|
|
|
|
while (m_in_breakpoint && m_in_exec) {
|
|
process_events (QEventLoop::WaitForMoreEvents);
|
|
}
|
|
|
|
}
|
|
|
|
leave_breakpoint_mode ();
|
|
|
|
} catch (...) {
|
|
leave_breakpoint_mode ();
|
|
throw;
|
|
}
|
|
|
|
if (! m_in_exec) {
|
|
exit_from_macro ();
|
|
}
|
|
|
|
} else if (++m_trace_count == 20) {
|
|
|
|
m_trace_count = 0;
|
|
|
|
if ((tl::Clock::current () - m_last_process_events).seconds () > m_process_events_interval) {
|
|
|
|
tl::Clock start = tl::Clock::current ();
|
|
|
|
process_events ();
|
|
|
|
// adjust the process events interval
|
|
m_last_process_events = tl::Clock::current ();
|
|
m_process_events_interval = std::max (0.05, std::min (2.0, (m_last_process_events - start).seconds () * 5.0));
|
|
|
|
if (!m_in_exec) {
|
|
exit_from_macro ();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::enter_breakpoint_mode (gsi::Interpreter *interpreter, const gsi::StackTraceProvider *stack_trace_provider)
|
|
{
|
|
m_in_breakpoint = true;
|
|
m_eval_context = -1;
|
|
mp_current_interpreter = interpreter;
|
|
|
|
if (isMinimized ()) {
|
|
showNormal ();
|
|
}
|
|
activateWindow ();
|
|
raise ();
|
|
show ();
|
|
|
|
size_t scope_index = stack_trace_provider->scope_index ();
|
|
|
|
callStack->clear ();
|
|
std::vector<tl::BacktraceElement> bt = stack_trace_provider->stack_trace ();
|
|
for (std::vector<tl::BacktraceElement>::const_iterator b = bt.begin (); b != bt.end (); ++b) {
|
|
QListWidgetItem *item = new QListWidgetItem (callStack);
|
|
item->setText (tl::to_qstring (b->to_string ()));
|
|
item->setData (Qt::UserRole, tl::to_qstring (b->file));
|
|
item->setData (Qt::UserRole + 1, b->line);
|
|
item->setData (Qt::UserRole + 2, int (b - bt.begin ()));
|
|
callStack->addItem (item);
|
|
}
|
|
|
|
callStack->setCurrentRow (int (scope_index));
|
|
|
|
// Adjust the current stack level
|
|
m_current_stack_depth = stack_trace_provider->stack_depth ();
|
|
|
|
do_update_ui_to_run_mode ();
|
|
|
|
// Hint: apparently it's necessary to process the events to make the layout system
|
|
// recognize that we have hidden parts from the edit field by the runtime frame.
|
|
process_events (QEventLoop::ExcludeUserInputEvents);
|
|
|
|
if (! bt.empty ()) {
|
|
set_exec_point (&bt[scope_index].file, bt[scope_index].line, int (scope_index));
|
|
}
|
|
|
|
update_inspected ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::leave_breakpoint_mode ()
|
|
{
|
|
m_in_breakpoint = false;
|
|
m_eval_context = -1;
|
|
mp_current_interpreter = 0;
|
|
do_update_ui_to_run_mode ();
|
|
set_exec_point (0, -1, -1);
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::update_ui_to_run_mode ()
|
|
{
|
|
dm_update_ui_to_run_mode ();
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::do_update_ui_to_run_mode ()
|
|
{
|
|
double alpha = 0.95;
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
|
|
dbgOn->setEnabled (! m_in_exec);
|
|
runButton->setEnabled ((! m_in_exec && (mp_run_macro || (page && page->macro () && page->macro ()->interpreter () != lym::Macro::None))) || m_in_breakpoint);
|
|
runThisButton->setEnabled ((! m_in_exec && page && page->macro () && page->macro ()->interpreter () != lym::Macro::None) || m_in_breakpoint);
|
|
singleStepButton->setEnabled (! m_in_exec || m_in_breakpoint);
|
|
nextStepButton->setEnabled (! m_in_exec || m_in_breakpoint);
|
|
stopButton->setEnabled (m_in_exec);
|
|
pauseButton->setEnabled (m_in_exec && ! m_in_breakpoint);
|
|
breakpointButton->setEnabled (page && page->macro ());
|
|
clearBreakpointsButton->setEnabled (page && page->macro ());
|
|
|
|
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) {
|
|
(*mt)->setEditTriggers (m_in_exec ? QAbstractItemView::NoEditTriggers : QAbstractItemView::SelectedClicked);
|
|
}
|
|
|
|
addButton->setEnabled (! m_in_exec);
|
|
actionAddMacro->setEnabled (! m_in_exec);
|
|
deleteButton->setEnabled (! m_in_exec);
|
|
actionDelete->setEnabled (! m_in_exec);
|
|
renameButton->setEnabled (! m_in_exec);
|
|
actionRename->setEnabled (! m_in_exec);
|
|
importButton->setEnabled (! m_in_exec);
|
|
actionImport->setEnabled (! m_in_exec);
|
|
newFolderButton->setEnabled (! m_in_exec);
|
|
actionNewFolder->setEnabled (! m_in_exec);
|
|
saveAllButton->setEnabled (! m_in_exec);
|
|
actionSaveAll->setEnabled (! m_in_exec);
|
|
saveButton->setEnabled (! m_in_exec);
|
|
actionSave->setEnabled (! m_in_exec);
|
|
actionRefresh->setEnabled (! m_in_exec);
|
|
actionAddLocation->setEnabled (! m_in_exec);
|
|
actionRemoveLocation->setEnabled (! m_in_exec);
|
|
propertiesButton->setEnabled (! m_in_exec && page && page->macro () && (page->macro ()->format () == lym::Macro::MacroFormat || page->macro ()->format () == lym::Macro::PlainTextWithHashAnnotationsFormat));
|
|
setupButton->setEnabled (! m_in_exec);
|
|
langSelFrame->setEnabled (! m_in_exec);
|
|
|
|
// Force language type to match the current execution context
|
|
if (m_in_breakpoint && mp_current_interpreter) {
|
|
if (mp_current_interpreter == &lay::ApplicationBase::instance ()->python_interpreter ()) {
|
|
pythonLangSel->setChecked (true);
|
|
rubyLangSel->setChecked (false);
|
|
} else {
|
|
pythonLangSel->setChecked (false);
|
|
rubyLangSel->setChecked (true);
|
|
}
|
|
}
|
|
|
|
QColor base_color = qApp->palette ().color (QPalette::Base);
|
|
QColor alt_base_color = qApp->palette ().color (QPalette::AlternateBase);
|
|
|
|
if (m_in_exec) {
|
|
|
|
if (m_in_breakpoint && mp_current_interpreter) {
|
|
|
|
base_color = QColor (base_color.red (), int (0.5 + base_color.green () * alpha), int (0.5 + base_color.blue () * alpha));
|
|
alt_base_color = QColor (alt_base_color.red (), int (0.5 + alt_base_color.green () * alpha), int (0.5 + alt_base_color.blue () * alpha));
|
|
runtimeFrame->show ();
|
|
|
|
} else {
|
|
|
|
base_color = QColor (int (0.5 + base_color.red () * alpha), base_color.green (), int (0.5 + base_color.blue () * alpha));
|
|
alt_base_color = QColor (int (0.5 + alt_base_color.red () * alpha), alt_base_color.green (), int (0.5 + alt_base_color.blue () * alpha));
|
|
runtimeFrame->hide ();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
variableListFrame->setVisible (false);
|
|
variableList->set_inspector (0);
|
|
runtimeFrame->hide ();
|
|
|
|
}
|
|
|
|
QPalette p = palette ();
|
|
p.setColor (QPalette::Base, base_color);
|
|
p.setColor (QPalette::AlternateBase, alt_base_color);
|
|
setPalette (p);
|
|
|
|
// for some reason, callStack, variableList and watchList don't inherit the palette ...
|
|
callStack->setPalette (p);
|
|
variableList->setPalette (p);
|
|
watchList->setPalette (p);
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::const_iterator t = m_tab_widgets.find (mp_run_macro);
|
|
if (t != m_tab_widgets.end ()) {
|
|
int index = tabWidget->indexOf (t->second);
|
|
if (index >= 0) {
|
|
tabWidget->setTabIcon (index, QIcon (QString::fromUtf8 (m_in_exec ? (m_in_breakpoint ? ":/pause.png" : ":/stop.png") : ":/run.png")));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::stack_element_double_clicked (QListWidgetItem *item)
|
|
{
|
|
std::string f = tl::to_string (item->data (Qt::UserRole).toString ());
|
|
|
|
int context = item->data (Qt::UserRole + 2).toInt ();
|
|
set_exec_point (&f, item->data (Qt::UserRole + 1).toInt (), context);
|
|
|
|
update_inspected ();
|
|
}
|
|
|
|
MacroEditorPage *
|
|
MacroEditorDialog::create_page (lym::Macro *macro)
|
|
{
|
|
std::unique_ptr<MacroEditorPage> editor (new MacroEditorPage (this, &m_highlighters));
|
|
editor->set_ntab (m_ntab);
|
|
editor->set_nindent (m_nindent);
|
|
editor->set_font (m_font_family, m_font_size);
|
|
editor->exec_model ()->set_run_mode (m_in_exec);
|
|
editor->connect_macro (macro);
|
|
connect (editor.get (), SIGNAL (help_requested (const QString &)), this, SLOT (help_requested (const QString &)));
|
|
connect (editor.get (), SIGNAL (search_requested (const QString &)), this, SLOT (search_requested (const QString &)));
|
|
connect (editor.get (), SIGNAL (edit_trace (bool)), this, SLOT (add_edit_trace (bool)));
|
|
return editor.release ();
|
|
}
|
|
|
|
MacroEditorPage *
|
|
MacroEditorDialog::editor_for_macro (lym::Macro *macro)
|
|
{
|
|
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) {
|
|
(*mt)->set_current (macro);
|
|
}
|
|
|
|
MacroEditorPage *editor = 0;
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::iterator page = m_tab_widgets.find (macro);
|
|
if (page == m_tab_widgets.end ()) {
|
|
|
|
editor = create_page (macro);
|
|
int index = tabWidget->addTab (editor, tl::to_qstring (macro->name ()));
|
|
tabWidget->setTabToolTip (index, tl::to_qstring (macro->summary ()));
|
|
if (macro == mp_run_macro) {
|
|
tabWidget->setTabIcon (index, QIcon (QString::fromUtf8 (m_in_exec ? (m_in_breakpoint ? ":/pause.png" : ":/stop.png") : ":/run.png")));
|
|
}
|
|
tabWidget->setCurrentWidget (editor);
|
|
m_tab_widgets.insert (std::make_pair (macro, editor));
|
|
|
|
refresh_file_watcher ();
|
|
|
|
for (std::vector <std::pair<lym::Macro *, MacroEditorPage *> >::iterator f = m_file_to_widget.begin (); f != m_file_to_widget.end (); ++f) {
|
|
if (f->first == macro) {
|
|
f->second = editor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
editor = page->second;
|
|
tabWidget->setCurrentIndex (tabWidget->indexOf (editor));
|
|
}
|
|
|
|
return editor;
|
|
}
|
|
|
|
MacroEditorPage *
|
|
MacroEditorDialog::editor_for_file (const std::string &path)
|
|
{
|
|
lym::Macro *macro = mp_root->find_macro (path);
|
|
if (macro) {
|
|
return editor_for_macro (macro);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::set_exec_point (const std::string *file, int line, int eval_context)
|
|
{
|
|
MacroEditorPage *editor = 0;
|
|
if (file) {
|
|
editor = editor_for_file (*file);
|
|
}
|
|
|
|
for (std::map<lym::Macro *, MacroEditorPage *>::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) {
|
|
f->second->exec_model ()->set_current_line (f->second == editor ? line : -1, true);
|
|
}
|
|
|
|
m_eval_context = eval_context;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::handle_error (tl::ScriptError &re)
|
|
{
|
|
// navigate to the file/line
|
|
MacroEditorPage *editor = editor_for_file (re.sourcefile ());
|
|
if (editor) {
|
|
editor->set_error_line (re.line ());
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::breakpoint_button_clicked ()
|
|
{
|
|
MacroEditorPage *page = dynamic_cast <MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page) {
|
|
return;
|
|
}
|
|
|
|
page->exec_model ()->toggle_breakpoint (page->current_line ());
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::clear_breakpoints_button_clicked ()
|
|
{
|
|
for (std::map<lym::Macro *, MacroEditorPage *>::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) {
|
|
f->second->exec_model ()->set_breakpoints (std::set <int> ());
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::pause_button_clicked ()
|
|
{
|
|
m_continue = false;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::stop_button_clicked ()
|
|
{
|
|
if (QApplication::activeModalWidget () == this) {
|
|
// close this window if it was shown in modal mode
|
|
accept ();
|
|
}
|
|
|
|
m_in_exec = false;
|
|
m_continue = false;
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::next_step_button_clicked ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
run (m_in_exec ? std::max (0, m_current_stack_depth) : std::numeric_limits <int>::max (), current_run_macro ());
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::single_step_button_clicked ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
run (std::numeric_limits <int>::max (), current_run_macro ());
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::run_button_clicked ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
run (-1, current_run_macro ());
|
|
END_PROTECTED
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::run_this_button_clicked ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
run (-1, 0);
|
|
END_PROTECTED
|
|
}
|
|
|
|
lym::Macro *
|
|
MacroEditorDialog::current_run_macro ()
|
|
{
|
|
// validate the current run macro against the macros present in the collection and
|
|
// return 0 if invalid (that takes the current one)
|
|
|
|
std::set<lym::Macro *> macros;
|
|
std::set<lym::MacroCollection *> macro_collections;
|
|
mp_root->collect_used_nodes (macros, macro_collections);
|
|
|
|
if (macros.find (mp_run_macro) != macros.end ()) {
|
|
return mp_run_macro;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::run (int stop_stack_depth, lym::Macro *macro)
|
|
{
|
|
m_stop_stack_depth = stop_stack_depth;
|
|
m_continue = true;
|
|
|
|
if (m_in_breakpoint) {
|
|
|
|
if (QApplication::activeModalWidget () == this) {
|
|
// close this window if it was shown in modal mode
|
|
accept ();
|
|
}
|
|
|
|
// in a breakpoint
|
|
m_in_breakpoint = false;
|
|
|
|
} else {
|
|
|
|
if (! macro) {
|
|
|
|
// initial -> run
|
|
if (! tabWidget->currentWidget ()) {
|
|
return;
|
|
}
|
|
|
|
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
|
|
if (! page || ! page->macro ()) {
|
|
return;
|
|
}
|
|
macro = page->macro ();
|
|
|
|
} else {
|
|
// TODO: clarify whether we should switch to the page which is run - this
|
|
// is annoying sometimes:
|
|
// editor_for_macro (macro);
|
|
}
|
|
|
|
if (! m_save_all_on_run && any_modified (mp_root)) {
|
|
if (QMessageBox::question (this, QObject::tr ("Save Macros"),
|
|
QObject::tr ("Some files are modified and need to be saved before running the macro. Do you want to save them?"),
|
|
QMessageBox::Yes, QMessageBox::Cancel) == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// save all macros
|
|
// Hint: although it looks like to touch decision, it's important to save every change since
|
|
// files may be included/loaded/required by other files.
|
|
commit ();
|
|
mp_root->save ();
|
|
|
|
refresh_file_watcher ();
|
|
|
|
set_run_macro (macro);
|
|
|
|
try {
|
|
macro->run ();
|
|
m_stop_stack_depth = -1;
|
|
} catch (tl::ExitException &) {
|
|
m_stop_stack_depth = -1;
|
|
// .. ignore exit exceptions ..
|
|
} catch (tl::BreakException &) {
|
|
m_stop_stack_depth = -1;
|
|
// .. ignore break exceptions ..
|
|
} catch (tl::ScriptError &re) {
|
|
m_stop_stack_depth = -1;
|
|
handle_error (re);
|
|
throw;
|
|
} catch (...) {
|
|
m_stop_stack_depth = -1;
|
|
throw;
|
|
}
|
|
|
|
// TODO: clarify whether we should switch to the page which is run - this
|
|
// is annoying sometimes:
|
|
#if 0
|
|
if (mp_run_macro) {
|
|
std::map <Macro *, MacroEditorPage *>::const_iterator t = m_tab_widgets.find (mp_run_macro);
|
|
if (t != m_tab_widgets.end ()) {
|
|
tabWidget->setCurrentWidget (t->second);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroEditorDialog::set_run_macro (lym::Macro *m)
|
|
{
|
|
if (m != mp_run_macro) {
|
|
|
|
std::map <lym::Macro *, MacroEditorPage *>::const_iterator t = m_tab_widgets.find (mp_run_macro);
|
|
if (t != m_tab_widgets.end ()) {
|
|
int index = tabWidget->indexOf (t->second);
|
|
if (index >= 0) {
|
|
tabWidget->setTabIcon (index, QIcon ());
|
|
}
|
|
}
|
|
|
|
mp_run_macro = m;
|
|
|
|
t = m_tab_widgets.find (mp_run_macro);
|
|
if (t != m_tab_widgets.end ()) {
|
|
int index = tabWidget->indexOf (t->second);
|
|
if (index >= 0) {
|
|
tabWidget->setTabIcon (index, QIcon (QString::fromUtf8 (":/run.png")));
|
|
}
|
|
}
|
|
|
|
for (std::vector<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) {
|
|
(*mt)->update_data (); // to switch icon
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------
|
|
// The plugin declaration that enables persistency though configuration options
|
|
|
|
class MacroEditorPluginDeclaration
|
|
: public lay::PluginDeclaration
|
|
{
|
|
public:
|
|
virtual lay::ConfigPage *config_page (QWidget *parent, std::string &title) const
|
|
{
|
|
title = tl::to_string (QObject::tr ("Application|Macro Development IDE"));
|
|
return new MacroEditorSetupPage (parent);
|
|
}
|
|
|
|
virtual void get_options (std::vector < std::pair<std::string, std::string> > &options) const
|
|
{
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_styles, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_save_all_on_run, "false"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_debugging_enabled, "true"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_file_watcher_enabled, "true"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_font_size, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_font_family, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_stop_on_exception, "true"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_tab_width, "8"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_indent, "2"));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_window_state, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_console_mru, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_console_interpreter, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_open_macros, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_current_macro, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_active_macro, ""));
|
|
options.push_back (std::pair<std::string, std::string> (cfg_macro_editor_watch_expressions, ""));
|
|
}
|
|
};
|
|
|
|
static tl::RegisteredClass<lay::PluginDeclaration> config_decl (new MacroEditorPluginDeclaration (), 1500, "MacroEditor");
|
|
|
|
}
|
|
|