From abb5424ecea977efefaab0c78633ce676c71a311 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 5 Apr 2017 00:20:32 +0200 Subject: [PATCH] WIP: introducing MacroController The MacroController is the central facility for managing macros and their views. The plugin framework has been extended to support such a design. In addition, some small bugs have been fixed related to macro interpreters (specifically the DRC). --- src/lay/built_in_macros/drc_interpreters.lym | 30 +- src/lay/lay.pro | 246 ++++++------ src/lay/layApplication.cc | 41 +- src/lay/layApplication.h | 7 + src/lay/layMacroController.cc | 395 +++++++++++++++++++ src/lay/layMacroController.h | 157 ++++++++ src/lay/layMacroEditorDialog.cc | 5 + src/lay/layMacroEditorDialog.h | 7 +- src/lay/layMainConfigPages.cc | 3 + src/lay/layMainWindow.cc | 351 +++------------- src/lay/layMainWindow.h | 26 -- src/lay/layProgress.cc | 109 ++++- src/lay/layProgress.h | 10 +- src/lay/layTechnologySelector.cc | 7 +- src/laybasic/layLayoutView.cc | 32 +- src/laybasic/layLayoutView.h | 12 +- src/laybasic/layPlugin.h | 43 ++ src/tl/tlDeferredExecution.cc | 21 +- src/tl/tlDeferredExecution.h | 55 ++- src/tl/tlStream.cc | 7 +- src/unit_tests/tlDeferredExecution.cc | 10 +- 21 files changed, 1032 insertions(+), 542 deletions(-) create mode 100644 src/lay/layMacroController.cc create mode 100644 src/lay/layMacroController.h diff --git a/src/lay/built_in_macros/drc_interpreters.lym b/src/lay/built_in_macros/drc_interpreters.lym index 04581f9b6..8a870f969 100644 --- a/src/lay/built_in_macros/drc_interpreters.lym +++ b/src/lay/built_in_macros/drc_interpreters.lym @@ -90,8 +90,17 @@ module DRC # Constructor def initialize + + # Make the DSL use ruby syntax highlighting + self.syntax_scheme = "ruby" + self.suffix = "drc" + self.debugger_scheme = RBA::MacroInterpreter::RubyDebugger + self.storage_scheme = RBA::MacroInterpreter::PlainTextWithHashAnnotationsFormat + self.description = "DRC (Text)" + # Registers the new interpreter register("drc-dsl") + end # Implements the execute method @@ -99,27 +108,6 @@ module DRC DRC::execute_drc(macro) end - # Make the DSL use ruby syntax highlighting - def syntax_scheme - "ruby" - end - - def suffix - "drc" - end - - def debugger_scheme - return RBA::MacroInterpreter::RubyDebugger - end - - def storage_scheme - return RBA::MacroInterpreter::PlainTextWithHashAnnotationsFormat - end - - def description - return "DRC (Text)" - end - end # Register the new interpreters diff --git a/src/lay/lay.pro b/src/lay/lay.pro index ce67b831d..bd89c9af8 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -9,138 +9,140 @@ DEFINES += MAKE_LAY_LIBRARY TEMPLATE = lib HEADERS = \ - layApplication.h \ - layClipDialog.h \ - layCrashMessage.h \ - layFillDialog.h \ - layGenericSyntaxHighlighter.h \ - layGSIHelpProvider.h \ - layHelpDialog.h \ - layHelpProvider.h \ - layHelpSource.h \ - layLayoutStatisticsForm.h \ - layLogViewerDialog.h \ - layMacro.h \ - layMacroEditorDialog.h \ - layMacroEditorPage.h \ - layMacroEditorSetupDialog.h \ - layMacroEditorTree.h \ - layMacroInterpreter.h \ - layMacroPropertiesDialog.h \ - layMacroVariableView.h \ - layMainConfigPages.h \ - layMainWindow.h \ - layNavigator.h \ - layProgress.h \ - layProgressWidget.h \ - layResourceHelpProvider.h \ - layRuntimeErrorForm.h \ - laySearchReplaceConfigPage.h \ - laySearchReplaceDialog.h \ - laySearchReplacePropertiesWidgets.h \ - laySelectCellViewForm.h \ - laySession.h \ - laySettingsForm.h \ - layTechnologySelector.h \ - layTechSetupDialog.h \ - layTextProgress.h \ - layVersion.h \ + layApplication.h \ + layClipDialog.h \ + layCrashMessage.h \ + layFillDialog.h \ + layGenericSyntaxHighlighter.h \ + layGSIHelpProvider.h \ + layHelpDialog.h \ + layHelpProvider.h \ + layHelpSource.h \ + layLayoutStatisticsForm.h \ + layLogViewerDialog.h \ + layMacro.h \ + layMacroEditorDialog.h \ + layMacroEditorPage.h \ + layMacroEditorSetupDialog.h \ + layMacroEditorTree.h \ + layMacroInterpreter.h \ + layMacroPropertiesDialog.h \ + layMacroVariableView.h \ + layMainConfigPages.h \ + layMainWindow.h \ + layNavigator.h \ + layProgress.h \ + layProgressWidget.h \ + layResourceHelpProvider.h \ + layRuntimeErrorForm.h \ + laySearchReplaceConfigPage.h \ + laySearchReplaceDialog.h \ + laySearchReplacePropertiesWidgets.h \ + laySelectCellViewForm.h \ + laySession.h \ + laySettingsForm.h \ + layTechnologySelector.h \ + layTechSetupDialog.h \ + layTextProgress.h \ + layVersion.h \ layCommon.h \ - layConfig.h + layConfig.h \ + layMacroController.h FORMS = \ - ClipDialog.ui \ - CrashMessage.ui \ - Console.ui \ - DeleteModeDialog.ui \ - FillDialog.ui \ - HelpAboutDialog.ui \ - KeyBindingsConfigPage.ui \ - LayoutStatistics.ui \ - LogViewerDialog.ui \ - MacroEditorDialog.ui \ - MacroEditorSetupDialog.ui \ - MacroPropertiesDialog.ui \ - MacroTemplateSelectionDialog.ui \ - MainConfigPage.ui \ - MainConfigPage2.ui \ - MainConfigPage3.ui \ - MainConfigPage4.ui \ - MainConfigPage5.ui \ - MainConfigPage6.ui \ - ReplacePropertiesBox.ui \ - ReplacePropertiesInstance.ui \ - ReplacePropertiesPath.ui \ - ReplacePropertiesShape.ui \ - ReplacePropertiesText.ui \ - RuntimeErrorForm.ui \ - SearchPropertiesBox.ui \ - SearchPropertiesInstance.ui \ - SearchPropertiesPath.ui \ - SearchPropertiesShape.ui \ - SearchPropertiesText.ui \ - SearchReplaceConfigPage.ui \ - SearchReplaceDialog.ui \ - SelectCellViewForm.ui \ - SettingsForm.ui \ - TechBaseEditorPage.ui \ - TechComponentSetupDialog.ui \ - TechLayerMappingEditorPage.ui \ - TechMacrosPage.ui \ - TechSetupDialog.ui \ - XORToolDialog.ui \ + ClipDialog.ui \ + CrashMessage.ui \ + Console.ui \ + DeleteModeDialog.ui \ + FillDialog.ui \ + HelpAboutDialog.ui \ + KeyBindingsConfigPage.ui \ + LayoutStatistics.ui \ + LogViewerDialog.ui \ + MacroEditorDialog.ui \ + MacroEditorSetupDialog.ui \ + MacroPropertiesDialog.ui \ + MacroTemplateSelectionDialog.ui \ + MainConfigPage.ui \ + MainConfigPage2.ui \ + MainConfigPage3.ui \ + MainConfigPage4.ui \ + MainConfigPage5.ui \ + MainConfigPage6.ui \ + ReplacePropertiesBox.ui \ + ReplacePropertiesInstance.ui \ + ReplacePropertiesPath.ui \ + ReplacePropertiesShape.ui \ + ReplacePropertiesText.ui \ + RuntimeErrorForm.ui \ + SearchPropertiesBox.ui \ + SearchPropertiesInstance.ui \ + SearchPropertiesPath.ui \ + SearchPropertiesShape.ui \ + SearchPropertiesText.ui \ + SearchReplaceConfigPage.ui \ + SearchReplaceDialog.ui \ + SelectCellViewForm.ui \ + SettingsForm.ui \ + TechBaseEditorPage.ui \ + TechComponentSetupDialog.ui \ + TechLayerMappingEditorPage.ui \ + TechMacrosPage.ui \ + TechSetupDialog.ui \ + XORToolDialog.ui \ TechLoadOptionsEditorPage.ui \ TechSaveOptionsEditorPage.ui \ MainConfigPage7.ui SOURCES = \ - gsiDeclLayApplication.cc \ - gsiDeclLayHelpDialog.cc \ - gsiDeclLayMacro.cc \ - gsiDeclLayMainWindow.cc \ - layApplication.cc \ - layClipDialog.cc \ - layCrashMessage.cc \ - layFillDialog.cc \ - layGenericSyntaxHighlighter.cc \ - layGSIHelpProvider.cc \ - layHelpDialog.cc \ - layHelpProvider.cc \ - layHelpSource.cc \ - layLayoutStatisticsForm.cc \ - layLogViewerDialog.cc \ - layMacro.cc \ - layMacroEditorDialog.cc \ - layMacroEditorPage.cc \ - layMacroEditorSetupDialog.cc \ - layMacroEditorTree.cc \ - layMacroInterpreter.cc \ - layMacroPropertiesDialog.cc \ - layMacroVariableView.cc \ - layMainConfigPages.cc \ - layMainWindow.cc \ - layNavigator.cc \ - layProgress.cc \ - layProgressWidget.cc \ - layResourceHelpProvider.cc \ - layRuntimeErrorForm.cc \ - laySearchReplaceConfigPage.cc \ - laySearchReplaceDialog.cc \ - laySearchReplacePlugin.cc \ - laySearchReplacePropertiesWidgets.cc \ - laySelectCellViewForm.cc \ - laySession.cc \ - laySettingsForm.cc \ - layTechnologySelector.cc \ - layTechSetupDialog.cc \ - layTextProgress.cc \ - layVersion.cc \ + gsiDeclLayApplication.cc \ + gsiDeclLayHelpDialog.cc \ + gsiDeclLayMacro.cc \ + gsiDeclLayMainWindow.cc \ + layApplication.cc \ + layClipDialog.cc \ + layCrashMessage.cc \ + layFillDialog.cc \ + layGenericSyntaxHighlighter.cc \ + layGSIHelpProvider.cc \ + layHelpDialog.cc \ + layHelpProvider.cc \ + layHelpSource.cc \ + layLayoutStatisticsForm.cc \ + layLogViewerDialog.cc \ + layMacro.cc \ + layMacroEditorDialog.cc \ + layMacroEditorPage.cc \ + layMacroEditorSetupDialog.cc \ + layMacroEditorTree.cc \ + layMacroInterpreter.cc \ + layMacroPropertiesDialog.cc \ + layMacroVariableView.cc \ + layMainConfigPages.cc \ + layMainWindow.cc \ + layNavigator.cc \ + layProgress.cc \ + layProgressWidget.cc \ + layResourceHelpProvider.cc \ + layRuntimeErrorForm.cc \ + laySearchReplaceConfigPage.cc \ + laySearchReplaceDialog.cc \ + laySearchReplacePlugin.cc \ + laySearchReplacePropertiesWidgets.cc \ + laySelectCellViewForm.cc \ + laySession.cc \ + laySettingsForm.cc \ + layTechnologySelector.cc \ + layTechSetupDialog.cc \ + layTextProgress.cc \ + layVersion.cc \ + layMacroController.cc RESOURCES = layBuildInMacros.qrc \ - layHelpResources.qrc \ - layLayoutStatistics.qrc \ - layMacroTemplates.qrc \ - layResources.qrc \ + layHelpResources.qrc \ + layLayoutStatistics.qrc \ + layMacroTemplates.qrc \ + layResources.qrc \ INCLUDEPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt DEPENDPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index 44ff914ca..c74bb0f4c 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -33,6 +33,7 @@ #include "layProgress.h" #include "layTextProgress.h" #include "layBackgroundAwareTreeStyle.h" +#include "layMacroController.h" #include "gtf.h" #include "gsiDecl.h" #include "gsiInterpreter.h" @@ -500,6 +501,8 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) mp_qapp = this; mp_qapp_gui = (non_ui_mode ? 0 : this); + mp_dm_scheduler.reset (new tl::DeferredMethodScheduler ()); + // install a special style proxy to overcome the issue of black-on-black tree expanders if (mp_qapp_gui) { mp_qapp_gui->setStyle (new lay::BackgroundAwareTreeStyle (0)); @@ -1099,11 +1102,10 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) mp_plugin_root = new lay::PluginRoot (); } - // initialize the plugins (this should be the last action in the constructor since the - // main window should be functional now. + // initialize the plugins for the first time for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { lay::PluginDeclaration *pd = const_cast (&*cls); - pd->initialize (mp_mw); + pd->initialize (mp_plugin_root); } // establish the configuration @@ -1199,6 +1201,12 @@ Application::finish () void Application::shutdown () { + // uninitialize the plugins + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->uninitialize (mp_plugin_root); + } + if (mp_mw) { delete mp_mw; mp_mw = 0; @@ -1357,9 +1365,10 @@ Application::run () macro->set_file_path (*m); if (macro->show_in_menu ()) { // menu-based macros are just registered so they are shown in the menu - if (mp_mw) { - tl::log << "Register macro '" << *m << "'"; - mp_mw->add_temp_macro (macro.release ()); + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + tl::log << "Registering macro '" << *m << "'"; + mc->add_temp_macro (macro.release ()); } } else { // other macros given with -rm are run @@ -1479,15 +1488,25 @@ Application::run () player.replay (m_gtf_replay_rate, m_gtf_replay_stop); } - // update the menus with the macro menu bindings as late as possible (now we - // can be sure that the menus are created propertly) - mp_mw->update_menu_with_macros (); + // Give the plugins a change to do some last-minute initialisation and checks + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->initialized (mp_mw); + } if (! m_no_gui && m_gtf_replay.empty () && ! mp_recorder) { // Show initial tip window if required mp_mw->about_to_exec (); } + } else if (mp_plugin_root) { + + // Give the plugins a change to do some last-minute initialisation and checks + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->initialized (mp_plugin_root); + } + } if (! m_run_macro.empty ()) { @@ -1586,7 +1605,7 @@ Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) if (mp_mw) { if (silent) { - tl::DeferredMethodScheduler::instance ()->enable (false); + mp_dm_scheduler->enable (false); } #if QT_VERSION < 0x050000 @@ -1598,7 +1617,7 @@ Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) mp_mw->enter_busy_mode (false); if (silent) { - tl::DeferredMethodScheduler::instance ()->enable (true); + mp_dm_scheduler->enable (true); } } diff --git a/src/lay/layApplication.h b/src/lay/layApplication.h index 7497785a9..bdc319f7b 100644 --- a/src/lay/layApplication.h +++ b/src/lay/layApplication.h @@ -33,6 +33,7 @@ #include #include +#include namespace gsi { @@ -44,6 +45,11 @@ namespace gtf class Recorder; } +namespace tl +{ + class DeferredMethodScheduler; +} + namespace lay { @@ -339,6 +345,7 @@ private: bool m_enable_undo; QCoreApplication *mp_qapp; QApplication *mp_qapp_gui; + std::auto_ptr mp_dm_scheduler; // HINT: the ruby interpreter must be destroyed before MainWindow // in order to maintain a valid MainWindow reference for ruby scripts and Ruby's GC all the time. gsi::Interpreter *mp_ruby_interpreter; diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc new file mode 100644 index 000000000..93f8e5920 --- /dev/null +++ b/src/lay/layMacroController.cc @@ -0,0 +1,395 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 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 "layMacroController.h" +#include "layMacroEditorDialog.h" +#include "layMacroInterpreter.h" +#include "layMainWindow.h" +#include "layMainConfigPages.h" +#include "layConfig.h" +#include "layApplication.h" + +#include +#include +#include + +namespace lay +{ + +MacroController::MacroController () + : mp_macro_editor (0), mp_mw (0), + dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros) +{ + connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); + connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); +} + +void +MacroController::initialized (lay::PluginRoot *root) +{ + mp_mw = dynamic_cast (root); + if (mp_mw) { + mp_macro_editor = new lay::MacroEditorDialog (mp_mw, &lay::MacroCollection::root ()); + mp_macro_editor->setModal (false); + } + + connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); + connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + + // update the menus with the macro menu bindings as late as possible (now we + // can be sure that the menus are created propertly) + do_update_menu_with_macros (); +} + +void +MacroController::uninitialize (lay::PluginRoot * /*root*/) +{ + disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); + disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + + delete mp_macro_editor; + mp_macro_editor = 0; + mp_mw = 0; +} + +bool +MacroController::configure (const std::string &key, const std::string &value) +{ + if (key == cfg_key_bindings && mp_mw) { + + // Update the shortcuts of the menu item if they have been edited in the configuration editor + std::vector > key_bindings = unpack_key_binding (value); + for (std::vector >::const_iterator kb = key_bindings.begin (); kb != key_bindings.end (); ++kb) { + if (mp_mw->menu ()->is_valid (kb->first)) { + lay::Action a = mp_mw->menu ()->action (kb->first); + if (m_action_to_macro.find (a.qaction ()) != m_action_to_macro.end ()) { + m_action_to_macro [a.qaction ()]->set_shortcut (kb->second); + } + } + } + + } + + return false; +} + +void +MacroController::config_finalize() +{ + // .. nothing yet .. +} + +bool +MacroController::can_exit (lay::PluginRoot * /*root*/) const +{ + if (mp_macro_editor) { + return mp_macro_editor->can_exit (); + } else { + return true; + } +} + +bool +MacroController::accepts_drop (const std::string &path_or_url) const +{ + QUrl url (tl::to_qstring (path_or_url)); + QFileInfo file_info (url.path ()); + QString suffix = file_info.suffix ().toLower (); + + if (suffix == QString::fromUtf8 ("rb") || + suffix == QString::fromUtf8 ("py") || + suffix == QString::fromUtf8 ("lym")) { + return true; + } + + // check the suffixes in the DSL interpreter declarations + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + if (suffix == tl::to_qstring (cls->suffix ())) { + return true; + } + } + + return false; +} + +void +MacroController::drop_url (const std::string &path_or_url) +{ + // Normalize the URL to become either a normal path or a URL + std::string path = path_or_url; + + QUrl url (tl::to_qstring (path_or_url)); + QString file_name = QFileInfo (url.path ()).fileName (); + + if (url.scheme () == QString::fromUtf8 ("file")) { + path = tl::to_string (url.toLocalFile ()); + } + + // load and run macro + std::auto_ptr macro (new lay::Macro ()); + macro->load_from (path); + macro->set_file_path (path); + + if (macro->is_autorun () || macro->show_in_menu ()) { + + // install macro permanently + if (QMessageBox::question (mp_mw, + QObject::tr ("Install Macro"), + QObject::tr ("Install macro '%1' permanently?\n\nPress 'Yes' to install the macro in the application settings folder permanently.").arg (file_name), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == QMessageBox::Yes) { + + // Use the application data folder + QDir folder (tl::to_qstring (lay::Application::instance ()->appdata_path ())); + + std::string cat = "macros"; + if (! macro->category ().empty ()) { + cat = macro->category (); + } + + if (! folder.cd (tl::to_qstring (cat))) { + throw tl::Exception (tl::to_string (QObject::tr ("Folder '%s' does not exists in installation path '%s' - cannot install")).c_str (), cat, lay::Application::instance ()->appdata_path ()); + } + + QFileInfo target (folder, file_name); + + if (! target.exists () || QMessageBox::question (mp_mw, + QObject::tr ("Overwrite Macro"), + QObject::tr ("Overwrite existing macro?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No) == QMessageBox::Yes) { + + QFile target_file (target.filePath ()); + if (target.exists () && ! target_file.remove ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove file '%1'").arg (target.filePath ()))); + } + + macro->set_file_path (tl::to_string (target.filePath ())); + + // run the macro now - if it fails, it is not installed, but the file path is already set to + // the target path. + if (macro->is_autorun ()) { + macro->run (); + } + + macro->save (); + + // refresh macro editor to show new macro plus to install the menus + refresh (); + + } + + } else { + + if (macro->is_autorun ()) { + // If it is not installed, run it now .. + macro->run (); + } else if (macro->show_in_menu ()) { + // .. or add as temporary macro so it is shown in the menu. + add_temp_macro (macro.release ()); + } + + } + + } else { + macro->run (); + } +} + +void +MacroController::show_editor (const std::string &cat, bool force_add) +{ + if (mp_macro_editor) { + mp_macro_editor->show (cat, force_add); + } +} + +void +MacroController::refresh () +{ + if (mp_macro_editor) { + mp_macro_editor->refresh (); + } +} + +void +MacroController::add_temp_macro (lay::Macro *m) +{ + m_temp_macros.add_unspecific (m); +} + +void +MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings) +{ + for (lay::MacroCollection::child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { + + // check whether the macro collection is associated with the selected technology (if there is one) + bool consider = false; + if (! tech || c->second->virtual_mode () != lay::MacroCollection::TechFolder) { + consider = true; + } else { + const std::vector > &mc = lay::Application::instance ()->macro_categories (); + for (std::vector >::const_iterator cc = mc.begin (); cc != mc.end () && !consider; ++cc) { + consider = (c->second->path () == tl::to_string (QDir (tl::to_qstring (tech->base_path ())).filePath (tl::to_qstring (cc->first)))); + } + } + + if (consider) { + add_macro_items_to_menu (*c->second, n, groups, 0 /*don't check 2nd level and below*/, key_bindings); + } + + } + + for (lay::MacroCollection::iterator c = collection.begin (); c != collection.end (); ++c) { + + std::string sc = tl::trim (c->second->shortcut ()); + + if (c->second->show_in_menu ()) { + + std::string mp = tl::trim (c->second->menu_path ()); + if (mp.empty ()) { + mp = "macros_menu.end"; + } + + std::string gn = tl::trim (c->second->group_name ()); + if (! gn.empty () && groups.find (gn) == groups.end ()) { + groups.insert (gn); + lay::Action as; + as.set_separator (true); + m_macro_actions.push_back (as); + mp_mw->menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), as); + } + + lay::Action a; + if (c->second->description ().empty ()) { + a.set_title (c->second->path ()); + } else { + a.set_title (c->second->description ()); + } + a.set_shortcut (sc); + m_macro_actions.push_back (a); + mp_mw->menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), a); + + m_action_to_macro.insert (std::make_pair (a.qaction (), c->second)); + + MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second); + QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ())); + + // store the key bindings in the array + if (!sc.empty () && key_bindings) { + key_bindings->push_back (std::make_pair (mp, sc)); + } + + } else if (! sc.empty ()) { + + // Create actions for shortcut-only actions too and add them to the main window + // to register their shortcut. + + lay::Action a; + if (c->second->description ().empty ()) { + a.set_title (c->second->path ()); + } else { + a.set_title (c->second->description ()); + } + a.set_shortcut (sc); + m_macro_actions.push_back (a); + + mp_mw->addAction (a.qaction ()); + MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second); + QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ())); + + } + + } +} + +void +MacroController::update_menu_with_macros () +{ + // empty action to macro table now we know it's invalid + m_action_to_macro.clear (); + dm_do_update_menu_with_macros (); +} + +void +MacroController::do_update_menu_with_macros () +{ + if (!mp_mw) { + return; + } + + // TODO: implement this by asking the technology manager for the active technology + const lay::Technology *tech = 0; + if (mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { + std::string active_tech = mp_mw->current_view ()->active_cellview ()->tech_name (); + tech = lay::Technologies::instance ()->technology_by_name (active_tech); + } + + std::vector > key_bindings = unpack_key_binding (mp_mw->config_get (cfg_key_bindings)); + std::sort (key_bindings.begin (), key_bindings.end ()); + + std::vector > new_key_bindings; + for (std::vector >::const_iterator kb = key_bindings.begin (); kb != key_bindings.end (); ++kb) { + if (mp_mw->menu ()->is_valid (kb->first)) { + lay::Action a = mp_mw->menu ()->action (kb->first); + if (m_action_to_macro.find (a.qaction ()) == m_action_to_macro.end ()) { + new_key_bindings.push_back (*kb); + } + } + } + + // delete all existing items + for (std::vector::iterator a = m_macro_actions.begin (); a != m_macro_actions.end (); ++a) { + mp_mw->menu ()->delete_items (*a); + } + m_macro_actions.clear (); + m_action_to_macro.clear (); + + int n = 1; + std::set groups; + add_macro_items_to_menu (m_temp_macros, n, groups, tech, 0); + add_macro_items_to_menu (lay::MacroCollection::root (), n, groups, tech, &new_key_bindings); + + // update the key bindings if required + std::sort (new_key_bindings.begin (), new_key_bindings.end ()); + if (new_key_bindings != key_bindings) { + mp_mw->config_set (cfg_key_bindings, pack_key_binding (new_key_bindings)); + } +} + +MacroController * +MacroController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + MacroController *mc = dynamic_cast (cls.operator-> ()); + if (mc) { + return mc; + } + } + return 0; +} + +// The singleton instance of the macro controller +static tl::RegisteredClass macro_controller_decl (new lay::MacroController (), 120, "MacroController"); + +} + diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h new file mode 100644 index 000000000..08f996c2d --- /dev/null +++ b/src/lay/layMacroController.h @@ -0,0 +1,157 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 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 + +*/ + + +#ifndef HDR_layMacroController +#define HDR_layMacroController + +#include "layCommon.h" +#include "layPlugin.h" +#include "layMacro.h" +#include "tlObject.h" +#include "tlDeferredExecution.h" + +#include +#include +#include + +#include + +namespace lay +{ + +class MacroEditorDialog; +class MainWindow; +class Technology; + +/** + * @brief A controller for the macro environment + * + * This object is a singleton that acts as a controller + * for the macro environment. The controller is responsible + * to managing the macro folders, autorunning of macros + * and other things. + * + * It interacts with the MacroEditorDialog which basically + * is the view for the macros. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class MacroController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + MacroController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path + */ + virtual bool accepts_drop (const std::string &path_or_url) const; + + /** + * @brief Gets called when a file or URL is dropped on the plugin + */ + virtual void drop_url (const std::string &path_or_url); + + /** + * @brief Shows the macro editor + * + * Depending on the category, a different tip dialog will be shown. + * If "force_add" is true, a new macro will be created, otherwise only + * if none exists yet. + */ + void show_editor (const std::string &cat = std::string (), bool force_add = false); + + /** + * @brief Reloads all macros from the paths registered + */ + void refresh (); + + /** + * @brief Adds a temporary macro + * + * Temporary macros are such present on the command line or + * dragged into the main window without installing. + * They need to be present so they participate in the + * menu building. Hence they are stored temporarily. + * The MainWindow object will become owner of the macro object. + */ + void add_temp_macro (lay::Macro *m); + + /** + * @brief Gets the singleton instance for this object + */ + static MacroController *instance (); + +public slots: + /** + * @brief Update the menu with macros bound to a menu + */ + void update_menu_with_macros (); + +private: + lay::MacroEditorDialog *mp_macro_editor; + lay::MainWindow *mp_mw; + tl::DeferredMethod dm_do_update_menu_with_macros; + std::vector m_macro_actions; + std::map m_action_to_macro; + lay::MacroCollection m_temp_macros; + + void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); + void do_update_menu_with_macros (); +}; + +} + +#endif + diff --git a/src/lay/layMacroEditorDialog.cc b/src/lay/layMacroEditorDialog.cc index 51defab1e..1f40e9b52 100644 --- a/src/lay/layMacroEditorDialog.cc +++ b/src/lay/layMacroEditorDialog.cc @@ -37,6 +37,7 @@ #include "layQtTools.h" #include "layConfig.h" #include "layWidgets.h" +#include "layProgress.h" #include "tlString.h" #include "tlClassRegistry.h" #include "tlExceptions.h" @@ -253,6 +254,10 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection m_add_edit_trace_enabled (true), dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher) { + // 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); connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *))); diff --git a/src/lay/layMacroEditorDialog.h b/src/lay/layMacroEditorDialog.h index 2c72ecbb0..476baa492 100644 --- a/src/lay/layMacroEditorDialog.h +++ b/src/lay/layMacroEditorDialog.h @@ -148,6 +148,12 @@ public: void select_category (const std::string &cat); public slots: + /** + * @brief Reloads all macros from the paths registered + */ + void refresh (); + +private slots: void help_button_clicked (); void add_button_clicked (); void close_button_clicked (); @@ -166,7 +172,6 @@ public slots: void properties_button_clicked (); void setup_button_clicked (); void breakpoint_button_clicked (); - void refresh (); void add_location (); void remove_location (); void clear_breakpoints_button_clicked (); diff --git a/src/lay/layMainConfigPages.cc b/src/lay/layMainConfigPages.cc index f56e3411b..179d102c0 100644 --- a/src/lay/layMainConfigPages.cc +++ b/src/lay/layMainConfigPages.cc @@ -474,6 +474,9 @@ KeyBindingsConfigPage::apply (const std::vectorbinding_le->setText (QString ()); + mp_ui->binding_le->setEnabled (false); + m_enable_event = true; } diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index 1452a29be..422e7c963 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -50,6 +50,7 @@ #include "tlAssert.h" #include "tlDeferredExecution.h" #include "tlStream.h" +#include "tlExceptions.h" #include "dbMemStatistics.h" #include "dbManager.h" #include "dbStream.h" @@ -81,7 +82,6 @@ #include "layMainConfigPages.h" #include "layAbstractMenu.h" #include "layQtTools.h" -#include "tlExceptions.h" #include "laySaveLayoutOptionsDialog.h" #include "layLoadLayoutOptionsDialog.h" #include "layLogViewerDialog.h" @@ -93,6 +93,7 @@ #include "laySelectCellViewForm.h" #include "layLayoutPropertiesForm.h" #include "layLayoutStatisticsForm.h" +#include "layMacroController.h" #include "ui_HelpAboutDialog.h" #include "gsi.h" #include "gtf.h" @@ -410,7 +411,6 @@ MainWindow::MainWindow (QApplication *app, const char *name) m_open_mode (0), m_disable_tab_selected (false), m_exited (false), - dm_do_update_menu_with_macros (this, &MainWindow::do_update_menu_with_macros), dm_do_update_menu (this, &MainWindow::do_update_menu), m_grid_micron (0.001), m_default_grids_updated (true), @@ -419,7 +419,6 @@ MainWindow::MainWindow (QApplication *app, const char *name) m_synchronized_views (false), m_synchronous (false), m_busy (false), - m_work_in_progress (false), mp_app (app) { setObjectName (QString::fromUtf8 (name)); @@ -437,16 +436,6 @@ MainWindow::MainWindow (QApplication *app, const char *name) lay::register_help_handler (this, SLOT (show_help (const QString &))); - connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); - - if (lay::Application::instance ()) { - mp_macro_editor = new lay::MacroEditorDialog (this, &lay::MacroCollection::root ()); - mp_macro_editor->setModal (false); - } else { - mp_macro_editor = 0; - } - mp_assistant = new lay::HelpDialog (this); mp_pr = new lay::ProgressReporter (); @@ -679,13 +668,6 @@ MainWindow::~MainWindow () { lay::register_help_handler (0, 0); - // uninitialize the plugins (this should be the first action in the constructor since the - // main window should be functional still. - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { - lay::PluginDeclaration *pd = const_cast (&*cls); - pd->uninitialize (this); - } - // since the configuration actions unregister themselves, we need to do this before the main // window is gone: m_ca_collection.clear (); @@ -712,9 +694,6 @@ MainWindow::~MainWindow () delete mp_log_viewer_dialog; mp_log_viewer_dialog = 0; - delete mp_macro_editor; - mp_macro_editor = 0; - delete mp_assistant; mp_assistant = 0; } @@ -1183,12 +1162,6 @@ MainWindow::close_all () void MainWindow::about_to_exec () { - // Give the plugins a change to do some last-minute initialisation and checks - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { - lay::PluginDeclaration *pd = const_cast (&*cls); - pd->initialized (this); - } - bool f; // TODO: later, each view may get it's own editable flag @@ -1654,18 +1627,10 @@ void MainWindow::apply_key_bindings () { for (std::vector >::const_iterator kb = m_key_bindings.begin (); kb != m_key_bindings.end (); ++kb) { - if (menu ()->is_valid (kb->first)) { - lay::Action a = menu ()->action (kb->first); a.set_shortcut (kb->second); - - if (m_action_to_macro.find (a.qaction ()) != m_action_to_macro.end ()) { - m_action_to_macro [a.qaction ()]->set_shortcut (kb->second); - } - } - } } @@ -1793,8 +1758,11 @@ MainWindow::can_close () } - if (mp_macro_editor && ! mp_macro_editor->can_exit ()) { - return false; + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + if (! pd->can_exit (this)) { + return false; + } } std::string df_list; @@ -1901,6 +1869,8 @@ MainWindow::cm_view_log () void MainWindow::cm_print () { + // TODO: move to lay::LayoutView + BEGIN_PROTECTED // Late-initialize the printer to save time on startup @@ -2295,116 +2265,6 @@ MainWindow::cm_redo () END_PROTECTED } -void -MainWindow::add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech) -{ - for (lay::MacroCollection::child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { - - // check whether the macro collection is associated with the selected technology (if there is one) - bool consider = false; - if (! tech || c->second->virtual_mode () != lay::MacroCollection::TechFolder) { - consider = true; - } else { - const std::vector > &mc = lay::Application::instance ()->macro_categories (); - for (std::vector >::const_iterator cc = mc.begin (); cc != mc.end () && !consider; ++cc) { - consider = (c->second->path () == tl::to_string (QDir (tl::to_qstring (tech->base_path ())).filePath (tl::to_qstring (cc->first)))); - } - } - - if (consider) { - add_macro_items_to_menu (*c->second, n, groups, 0 /*don't check 2nd level and below*/); - } - - } - - for (lay::MacroCollection::iterator c = collection.begin (); c != collection.end (); ++c) { - - std::string sc = tl::trim (c->second->shortcut ()); - - if (c->second->show_in_menu ()) { - - std::string mp = tl::trim (c->second->menu_path ()); - if (mp.empty ()) { - mp = "macros_menu.end"; - } - - std::string gn = tl::trim (c->second->group_name ()); - if (! gn.empty () && groups.find (gn) == groups.end ()) { - groups.insert (gn); - lay::Action as; - as.set_separator (true); - m_macro_actions.push_back (as); - menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), as); - } - - lay::Action a; - if (c->second->description ().empty ()) { - a.set_title (c->second->path ()); - } else { - a.set_title (c->second->description ()); - } - a.set_shortcut (sc); - m_macro_actions.push_back (a); - menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), a); - - m_action_to_macro.insert (std::make_pair (a.qaction (), c->second)); - - MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second); - QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ())); - - } else if (! sc.empty ()) { - - // Create actions for shortcut-only actions too and add them to the main window - // to register their shortcut. - - lay::Action a; - if (c->second->description ().empty ()) { - a.set_title (c->second->path ()); - } else { - a.set_title (c->second->description ()); - } - a.set_shortcut (sc); - m_macro_actions.push_back (a); - - addAction (a.qaction ()); - MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second); - QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ())); - - } - - } -} - -void -MainWindow::update_menu_with_macros () -{ - // empty action to macro table now we know it's invalid - m_action_to_macro.clear (); - dm_do_update_menu_with_macros (); -} - -void -MainWindow::do_update_menu_with_macros () -{ - const lay::Technology *tech = 0; - if (current_view () && current_view ()->active_cellview_index () >= 0 && current_view ()->active_cellview_index () <= int (current_view ()->cellviews ())) { - std::string active_tech = current_view ()->active_cellview ()->tech_name (); - tech = lay::Technologies::instance ()->technology_by_name (active_tech); - } - - // delete all existing items - for (std::vector::iterator a = m_macro_actions.begin (); a != m_macro_actions.end (); ++a) { - menu ()->delete_items (*a); - } - m_macro_actions.clear (); - m_action_to_macro.clear (); - - int n = 1; - std::set groups; - add_macro_items_to_menu (m_temp_macros, n, groups, tech); - add_macro_items_to_menu (lay::MacroCollection::root (), n, groups, tech); -} - void MainWindow::bookmark_menu_show () { @@ -3475,6 +3335,8 @@ MainWindow::cm_adjust_origin () void MainWindow::cm_new_cell () { + // TODO: move this function to lay::LayoutView + BEGIN_PROTECTED lay::LayoutView *curr = current_view (); @@ -4634,68 +4496,15 @@ MainWindow::show_progress_bar (bool show) } else { - if (m_work_in_progress != show) { - - m_work_in_progress = show; - if (show) { - // to avoid recursions of any kind, disallow any user interaction except - // cancelling the operation - mp_app->installEventFilter (this); - QApplication::setOverrideCursor (QCursor (Qt::WaitCursor)); - } else { - mp_app->removeEventFilter (this); - QApplication::restoreOverrideCursor (); - } - - // HINT: enabling the scheduler is accumulative - make sure that this method is only called when necessary - tl::DeferredMethodScheduler::instance ()->enable (! show); - - mp_main_stack_widget->setCurrentIndex (show ? 1 : 0); - - if (show) { - clear_current_pos (); - } - + mp_main_stack_widget->setCurrentIndex (show ? 1 : 0); + if (show) { + clear_current_pos (); } - return true; } } -bool -MainWindow::eventFilter (QObject *obj, QEvent *event) -{ - // do not handle events that are not targeted towards widgets - if (! dynamic_cast (obj)) { - 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 (dynamic_cast (event)) { - - QObject *o = obj; - while (o) { - // If the watched object is a child of the progress widget or the macro editor, pass the event on to this. - // Including the macro editor keeps it alive while progress events are processed. - if (o == mp_progress_widget || o == mp_macro_editor) { - return false; - } - o = o->parent (); - } - - // eat the event - return true; - - } else { - return false; - } -} - void MainWindow::cm_technologies () { @@ -4716,41 +4525,42 @@ MainWindow::cm_technologies () } // because the macro-tech association might have changed, do this: - update_menu_with_macros (); + // TODO: let the macro controller monitor the technologies. + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + mc->update_menu_with_macros (); + } } } -void -MainWindow::add_temp_macro (lay::Macro *m) -{ - m_temp_macros.add_unspecific (m); - update_menu_with_macros (); -} - void MainWindow::show_macro_editor (const std::string &cat, bool add) { - if (mp_macro_editor) { - mp_macro_editor->show (cat, add); + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + mc->show_editor (cat, add); } } void MainWindow::cm_edit_drc_scripts () { + // TODO: implement this as generic menu provided by the Interpreter show_macro_editor ("drc", false); } void MainWindow::cm_new_drc_script () { + // TODO: implement this as generic menu provided by the Interpreter show_macro_editor ("drc", true); } void MainWindow::cm_macro_editor () { + // TODO: implement this as generic menu provided by the plugin declaration show_macro_editor (); } @@ -5394,8 +5204,10 @@ MainWindow::dropEvent(QDropEvent *event) QList urls = event->mimeData ()->urls (); for (QList::const_iterator url = urls.begin (); url != urls.end (); ++url) { + QUrl eff_url (*url); + QString path; - if (url->scheme () == QString::fromUtf8 ("file")) { + if (eff_url.scheme () == QString::fromUtf8 ("file")) { path = url->toLocalFile (); #if defined(__APPLE__) @@ -5452,101 +5264,38 @@ MainWindow::dropEvent(QDropEvent *event) CFRelease( relCFURL ); CFRelease( relCFStringRef ); } + + eff_url = QUrl::fromLocalFile (path); #endif - } else if (url->scheme () == QString::fromUtf8 ("http") || url->scheme () == QString::fromUtf8 ("https")) { - path = url->toString (); + } else if (eff_url.scheme () == QString::fromUtf8 ("http") || eff_url.scheme () == QString::fromUtf8 ("https")) { + path = eff_url.toString (); } else { // other schemes are not supported currently. continue; } -#if defined(__APPLE__) - QFileInfo file_info (path); // Use the one resolved above - QString suffix = file_info.suffix ().toLower (); -#else - QFileInfo file_info (url->path ()); // Original - QString suffix = file_info.suffix ().toLower (); -#endif - if (suffix == QString::fromUtf8 ("rb") || - suffix == QString::fromUtf8 ("rbm") || - suffix == QString::fromUtf8 ("lym") || - suffix == QString::fromUtf8 ("lydrc") || - suffix == QString::fromUtf8 ("drc")) { + // Let the plugins decide if they accept the drop - // load and run macro - std::auto_ptr macro (new lay::Macro ()); - macro->load_from (tl::to_string (path)); - macro->set_file_path (tl::to_string (path)); - - if (macro->is_autorun () || macro->show_in_menu ()) { - - // install ruby module permanently - if (QMessageBox::question (this, - QObject::tr ("Install Macro"), - tl::to_qstring (tl::sprintf (tl::to_string (QObject::tr ("Install macro '%s' permanently?\n\nPress 'Yes' to install the macro in the application settings folder permanently.")).c_str (), tl::to_string (file_info.fileName ()).c_str ())), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) == QMessageBox::Yes) { - - // Use the application data folder - QDir folder (tl::to_qstring (lay::Application::instance ()->appdata_path ())); - - std::string cat = "macros"; - if (! macro->category ().empty ()) { - cat = macro->category (); - } - - if (! folder.cd (tl::to_qstring (cat))) { - throw tl::Exception (tl::to_string (QObject::tr ("Folder '%s' does not exists in installation path '%s' - cannot install")).c_str (), cat, lay::Application::instance ()->appdata_path ()); - } - - QFileInfo target (folder, file_info.fileName ()); - - if (! target.exists () || QMessageBox::question (this, - QObject::tr ("Overwrite Macro"), - QObject::tr ("Overwrite existing macro?"), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) == QMessageBox::Yes) { - - QFile target_file (target.filePath ()); - if (target.exists () && ! target_file.remove ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove file '%s'")).c_str (), tl::to_string (target.filePath ()).c_str ()); - } - - macro->set_file_path (tl::to_string (target.filePath ())); - - // run the macro now - if it fails, it is not installed, but the file path is already set to - // the target path. - if (macro->is_autorun ()) { - macro->run (); - } - - macro->save (); - - // refresh macro editor to show new macro plus to install the menus - if (mp_macro_editor) { - mp_macro_editor->refresh (); - } - - } - - } else { - - if (macro->is_autorun ()) { - // If it is not installed, run it now .. - macro->run (); - } else if (macro->show_in_menu ()) { - // .. or add as temporary macro so it is shown in the menu. - add_temp_macro (macro.release ()); - } - - } - - } else { - macro->run (); + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + if (pd->accepts_drop (tl::to_string (eff_url.toString ()))) { + pd->drop_url (tl::to_string (eff_url.toString ())); + return; } + } - } else if (suffix == QString::fromUtf8 ("lyp")) { + if (current_view () && current_view ()->accepts_drop (tl::to_string (eff_url.toString ()))) { + current_view ()->drop_url (tl::to_string (eff_url.toString ())); + return; + } + + // Now try the built-in ones + + QFileInfo file_info (eff_url.path ()); + QString suffix = file_info.suffix ().toLower (); + + if (suffix == QString::fromUtf8 ("lyp")) { load_layer_properties (tl::to_string (path), false /*current view only*/, false /*don't add a default*/); diff --git a/src/lay/layMainWindow.h b/src/lay/layMainWindow.h index 96331cc95..232a1df62 100644 --- a/src/lay/layMainWindow.h +++ b/src/lay/layMainWindow.h @@ -520,17 +520,6 @@ public: */ void show_macro_editor (const std::string &cat = std::string (), bool add = false); - /** - * @brief Adds a temporary macro - * - * Temporary macros are such present on the command line or - * dragged into the main window without installing. - * They need to be present so they participate in the - * menu building. Hence they are stored temporarily. - * The MainWindow object will become owner of the macro object. - */ - void add_temp_macro (lay::Macro *m); - /** * @brief Reimplementation of the plugin interface: handle a generic menu request */ @@ -828,11 +817,6 @@ public slots: * Intended as a connection target for QLabel linkVisisted signals. */ void show_help (const QString &url); - - /** - * @brief Update the menu with macros bound to a menu - */ - void update_menu_with_macros (); /** * @brief visibility of one of the dock widgets changed @@ -852,7 +836,6 @@ protected slots: protected: void update_content (); void do_update_menu (); - void do_update_menu_with_macros (); private: TextProgressDelegate m_text_progress; @@ -895,7 +878,6 @@ private: QLabel *mp_tech_status_label; bool m_disable_tab_selected; bool m_exited; - tl::DeferredMethod dm_do_update_menu_with_macros; tl::DeferredMethod dm_do_update_menu; QTimer m_message_timer; QTimer m_file_changed_timer; @@ -914,16 +896,11 @@ private: bool m_synchronized_views; bool m_synchronous; bool m_busy; - bool m_work_in_progress; QApplication *mp_app; - lay::MacroEditorDialog *mp_macro_editor; lay::HelpDialog *mp_assistant; std::string m_current_session; std::string m_message; std::auto_ptr mp_printer; - std::vector m_macro_actions; - std::map m_action_to_macro; - lay::MacroCollection m_temp_macros; std::vector m_changed_files; std::map m_actions_for_slot; @@ -937,7 +914,6 @@ private: void init_menu (); - bool eventFilter (QObject *watched, QEvent *event); void closeEvent (QCloseEvent *event); void resizeEvent (QResizeEvent *event); @@ -963,8 +939,6 @@ private: virtual void plugin_removed (lay::PluginDeclaration *cls); void libraries_changed (); - - void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech); void apply_key_bindings (); }; diff --git a/src/lay/layProgress.cc b/src/lay/layProgress.cc index e97322dba..d6b8b6694 100644 --- a/src/lay/layProgress.cc +++ b/src/lay/layProgress.cc @@ -23,20 +23,40 @@ #include #include +#include #include #include #include "layProgress.h" #include "layMainWindow.h" -#include "layApplication.h" +#include "layProgressWidget.h" #include "tlProgress.h" +#include "tlDeferredExecution.h" namespace lay { // -------------------------------------------------------------------- +static const char *s_alive_prop_name = "klayout_progressAlive"; + +void mark_widget_alive (QWidget *w, bool alive) +{ + if (alive) { + w->setProperty (s_alive_prop_name, QVariant (true)); + } else { + w->setProperty (s_alive_prop_name, QVariant ()); + } +} + +static bool is_marked_alive (QObject *obj) +{ + return obj->property (s_alive_prop_name).isValid (); +} + +// -------------------------------------------------------------------- + ProgressReporter::ProgressReporter () : m_start_time (), mp_pb (0), m_pw_visible (false) { @@ -55,11 +75,11 @@ ProgressReporter::set_progress_bar (lay::ProgressBar *pb) return; } if (mp_pb) { - mp_pb->show_progress_bar (m_pw_visible); + set_visible (m_pw_visible); } mp_pb = pb; if (mp_pb) { - mp_pb->show_progress_bar (m_pw_visible); + set_visible (m_pw_visible); } } @@ -75,10 +95,7 @@ ProgressReporter::register_object (tl::Progress *progress) // make dialog visible after some time has passed if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) { - if (mp_pb) { - mp_pb->show_progress_bar (true); - } - m_pw_visible = true; + set_visible (true); } update_and_yield (); @@ -95,10 +112,9 @@ ProgressReporter::unregister_object (tl::Progress *progress) // close or refresh window if (mp_objects.empty ()) { - if (mp_pb) { - mp_pb->show_progress_bar (false); + if (m_pw_visible) { + set_visible (false); } - m_pw_visible = false; m_start_time = tl::Clock (); } @@ -116,10 +132,7 @@ ProgressReporter::trigger (tl::Progress *progress) if (! mp_objects.empty () && mp_objects.front () == progress) { // make dialog visible after some time has passed if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) { - if (mp_pb) { - mp_pb->show_progress_bar (true); - } - m_pw_visible = true; + set_visible (true); } update_and_yield (); } @@ -130,10 +143,7 @@ ProgressReporter::yield (tl::Progress * /*progress*/) { // make dialog visible after some time has passed if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) { - if (mp_pb) { - mp_pb->show_progress_bar (true); - } - m_pw_visible = true; + set_visible (true); update_and_yield (); } else if (m_pw_visible) { // process events if necessary @@ -166,8 +176,67 @@ ProgressReporter::update_and_yield () void ProgressReporter::process_events () { - if (m_pw_visible && lay::MainWindow::instance () && lay::Application::instance ()) { - lay::Application::instance ()->process_events (); + if (m_pw_visible && lay::MainWindow::instance () && QApplication::instance ()) { + QApplication::instance ()->processEvents (QEventLoop::AllEvents); + } +} + +void +ProgressReporter::set_visible (bool vis) +{ + if (mp_pb) { + mp_pb->show_progress_bar (vis); + } + + if (vis != m_pw_visible) { + // prevent deferred method execution inside progress events - this might interfere with the + // actual operation + tl::DeferredMethodScheduler::enable (!vis); + } + + m_pw_visible = vis; + + if (QApplication::instance()) { + if (vis) { + // to avoid recursions of any kind, disallow any user interaction except + // cancelling the operation + QApplication::instance ()->installEventFilter (this); + } else { + QApplication::instance ()->removeEventFilter (this); + } + } +} + +bool +ProgressReporter::eventFilter (QObject *obj, QEvent *event) +{ + // do not handle events that are not targeted towards widgets + if (! dynamic_cast (obj)) { + return false; + } + + // do not handle events if a modal widget is active (i.e. a message box) + if (QApplication::activeModalWidget () && ! dynamic_cast (QApplication::activeModalWidget ())) { + return false; + } + + if (dynamic_cast (event)) { + + QObject *o = obj; + while (o) { + // If the watched object is a child of the progress widget or the macro editor, pass the event on to this. + // Including the macro editor keeps it alive while progress events are processed. + if (dynamic_cast (o) || is_marked_alive (o)) { + return false; + } + o = o->parent (); + } + + // eat the event + return true; + + } else { + return false; } } diff --git a/src/lay/layProgress.h b/src/lay/layProgress.h index 1c12d9116..db644dc33 100644 --- a/src/lay/layProgress.h +++ b/src/lay/layProgress.h @@ -54,7 +54,7 @@ public: }; class LAY_PUBLIC ProgressReporter - : public tl::ProgressAdaptor + : public QObject, public tl::ProgressAdaptor { public: ProgressReporter (); @@ -64,6 +64,7 @@ public: virtual void unregister_object (tl::Progress *progress); virtual void trigger (tl::Progress *progress); virtual void yield (tl::Progress *progress); + virtual bool eventFilter (QObject *dest, QEvent *event); void signal_break (); void set_progress_bar (lay::ProgressBar *pb); @@ -76,8 +77,15 @@ private: void process_events (); void update_and_yield (); + void set_visible (bool vis); }; +/** + * @brief Marks a widget as alive + * "alive" widgets receive input events also while a progress reporter is shown + */ +LAY_PUBLIC void mark_widget_alive (QWidget *w, bool alive); + } #endif diff --git a/src/lay/layTechnologySelector.cc b/src/lay/layTechnologySelector.cc index 676659286..e894c1f11 100644 --- a/src/lay/layTechnologySelector.cc +++ b/src/lay/layTechnologySelector.cc @@ -25,6 +25,7 @@ #include "layPlugin.h" #include "laybasicConfig.h" #include "layMainWindow.h" +#include "layMacroController.h" #include "layTechnology.h" #include "laybasicConfig.h" #include "tlDeferredExecution.h" @@ -112,8 +113,12 @@ private: lay::MainWindow *mw = lay::MainWindow::instance (); if (mw) { mw->tech_message (tech_string_from_name (active_tech)); + } + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + // TODO: let the macro controller monitor the active technology // need to do this since macros may be bound to the new technology - mw->update_menu_with_macros (); + mc->update_menu_with_macros (); } } diff --git a/src/laybasic/layLayoutView.cc b/src/laybasic/layLayoutView.cc index cd909b10b..06c674f3c 100644 --- a/src/laybasic/layLayoutView.cc +++ b/src/laybasic/layLayoutView.cc @@ -664,6 +664,26 @@ void LayoutView::viewport_changed () viewport_changed_event (); } +bool LayoutView::accepts_drop (const std::string &path_or_url) const +{ + for (std::vector::const_iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { + if ((*p)->accepts_drop (path_or_url)) { + return true; + } + } + return false; +} + +void LayoutView::drop_url (const std::string &path_or_url) +{ + for (std::vector::const_iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { + if ((*p)->accepts_drop (path_or_url)) { + (*p)->drop_url (path_or_url); + break; + } + } +} + lay::Plugin *LayoutView::create_plugin (lay::PluginRoot *root, const lay::PluginDeclaration *cls) { lay::Plugin *p = cls->create_plugin (manager (), root, this); @@ -2584,7 +2604,7 @@ LayoutView::get_screenshot () tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save screenshot"))); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); return mp_canvas->screenshot (); } @@ -2614,7 +2634,7 @@ LayoutView::save_screenshot (const std::string &fn) writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (desc)); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); if (! writer.write (mp_canvas->screenshot ())) { throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ())); @@ -2629,7 +2649,7 @@ LayoutView::get_image (unsigned int width, unsigned int height) tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save image"))); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); return mp_canvas->image (width, height); } @@ -2641,7 +2661,7 @@ LayoutView::get_image_with_options (unsigned int width, unsigned int height, int tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save image"))); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); return mp_canvas->image_with_options (width, height, linewidth, oversampling, resolution, background, foreground, active, target_box, monochrome); } @@ -2667,7 +2687,7 @@ LayoutView::save_image (const std::string &fn, unsigned int width, unsigned int writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (vp.box ().to_string ())); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); if (! writer.write (mp_canvas->image (width, height))) { throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ())); @@ -2699,7 +2719,7 @@ LayoutView::save_image_with_options (const std::string &fn, writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (vp.box ().to_string ())); // Execute all deferred methods - ensure there are no pending tasks - tl::DeferredMethodScheduler::instance ()->execute (); + tl::DeferredMethodScheduler::execute (); if (! writer.write (mp_canvas->image_with_options (width, height, linewidth, oversampling, resolution, background, foreground, active, target_box, monochrome))) { throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ())); diff --git a/src/laybasic/layLayoutView.h b/src/laybasic/layLayoutView.h index 7748c1c2c..508d4bd58 100644 --- a/src/laybasic/layLayoutView.h +++ b/src/laybasic/layLayoutView.h @@ -1878,7 +1878,17 @@ public: return m_drawing_workers; } - /** + /** + * @brief Gets a value indicating whether the view will accept a dropped file with the given URL or path + */ + virtual bool accepts_drop (const std::string &path_or_url) const; + + /** + * @brief Gets called when a file or URL is dropped on the view + */ + virtual void drop_url (const std::string &path_or_url); + + /** * @brief Localize a plugin by name * * This method will return 0, if no such plugin is registered diff --git a/src/laybasic/layPlugin.h b/src/laybasic/layPlugin.h index 4ffe88a85..848fd95f7 100644 --- a/src/laybasic/layPlugin.h +++ b/src/laybasic/layPlugin.h @@ -264,6 +264,17 @@ public: // .. the default implementation does nothing .. } + /** + * @brief Gets a value indicating whether the plugin can exit + * + * If the plugin wants to prevent the application from closing, it may return false + * in this method. + */ + virtual bool can_exit (lay::PluginRoot * /*root*/) const + { + return true; + } + /** * @brief Fetch the menu objects for this plugin * @@ -369,6 +380,22 @@ public: return m_editable_enabled; } + /** + * @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path + */ + virtual bool accepts_drop (const std::string & /*path_or_url*/) const + { + return false; + } + + /** + * @brief Gets called when a file or URL is dropped on the plugin + */ + virtual void drop_url (const std::string & /*path_or_url*/) + { + // .. nothing yet .. + } + /** * @brief Notifies that plugin root that a new plugin was registered * @@ -661,6 +688,22 @@ public: return 0; } + /** + * @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path + */ + virtual bool accepts_drop (const std::string & /*path_or_url*/) const + { + return false; + } + + /** + * @brief Gets called when a file or URL is dropped on the plugin + */ + virtual void drop_url (const std::string & /*path_or_url*/) + { + // .. nothing yet .. + } + /** * @brief Associate a plugin with the plugin declaration for that service (getter) */ diff --git a/src/tl/tlDeferredExecution.cc b/src/tl/tlDeferredExecution.cc index 853019c5e..b89dccf93 100644 --- a/src/tl/tlDeferredExecution.cc +++ b/src/tl/tlDeferredExecution.cc @@ -34,8 +34,8 @@ namespace tl static DeferredMethodScheduler *s_inst = 0; -DeferredMethodScheduler::DeferredMethodScheduler (QObject *parent) - : QObject (parent), +DeferredMethodScheduler::DeferredMethodScheduler () + : QObject (0), m_disabled (0), m_scheduled (false) { connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timer ())); @@ -47,6 +47,9 @@ DeferredMethodScheduler::DeferredMethodScheduler (QObject *parent) connect (&m_fallback_timer, SIGNAL (timeout ()), this, SLOT (timer ())); m_fallback_timer.setInterval (500); m_fallback_timer.setSingleShot (false); + + tl_assert (! s_inst); + s_inst = this; } DeferredMethodScheduler::~DeferredMethodScheduler () @@ -57,12 +60,8 @@ DeferredMethodScheduler::~DeferredMethodScheduler () DeferredMethodScheduler * DeferredMethodScheduler::instance () { - static QMutex lock; - lock.lock (); - if (s_inst == 0) { - s_inst = new DeferredMethodScheduler (qApp); - } - lock.unlock (); + // NOTE: this is not locked intentionally. The instance is shut down once in the application shortly before it + // will terminate and it is created initially before any threads start. return s_inst; } @@ -98,7 +97,7 @@ DeferredMethodScheduler::unqueue (DeferredMethodBase *method) } void -DeferredMethodScheduler::enable (bool en) +DeferredMethodScheduler::do_enable (bool en) { m_lock.lock (); if (en) { @@ -129,7 +128,7 @@ DeferredMethodScheduler::timer () m_timer.start (); } else { try { - execute (); + do_execute (); } catch (tl::Exception &ex) { tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.msg (); } catch (std::exception &ex) { @@ -141,7 +140,7 @@ DeferredMethodScheduler::timer () } void -DeferredMethodScheduler::execute () +DeferredMethodScheduler::do_execute () { std::list methods; diff --git a/src/tl/tlDeferredExecution.h b/src/tl/tlDeferredExecution.h index 9b10583c9..2a8c9537f 100644 --- a/src/tl/tlDeferredExecution.h +++ b/src/tl/tlDeferredExecution.h @@ -71,6 +71,16 @@ class TL_PUBLIC DeferredMethodScheduler { Q_OBJECT public: + /** + * @brief Constructor + */ + DeferredMethodScheduler (); + + /** + * @brief Destructor + */ + ~DeferredMethodScheduler (); + /** * @brief The singleton instance of the scheduler */ @@ -93,17 +103,27 @@ public: * when the application is supposed only to show a progress bar and * therefore will process events but is not supposed to execute anything else. * - * Enabling is cumulative: multiple enable(true) calls must be matched to - * the same number of enable(false) calls. + * Enabling is cumulative: multiple enable(true) calls must be matched to + * the same number of enable(false) calls. */ - void enable (bool en); + static void enable (bool en) + { + if (instance ()) { + instance ()->do_enable (en); + } + } /** * @brief Execute all queued methods - * + * * This method can be called to force execution of all queued methods. */ - void execute (); + static void execute () + { + if (instance ()) { + instance ()->do_execute (); + } + } private slots: void timer (); @@ -115,10 +135,9 @@ private: QTimer m_timer, m_fallback_timer; QMutex m_lock; - DeferredMethodScheduler (QObject *parent); - ~DeferredMethodScheduler (); - virtual bool event (QEvent *event); + void do_enable (bool en); + void do_execute (); }; /** @@ -169,7 +188,9 @@ public: */ ~DeferredMethod () { - DeferredMethodScheduler::instance ()->unqueue (this); + if (DeferredMethodScheduler::instance ()) { + DeferredMethodScheduler::instance ()->unqueue (this); + } } /** @@ -177,9 +198,13 @@ public: */ void operator() () { - // management of m_compressed and m_scheduled is done in the DeferredMethodScheduler - - // there it is safely locked. Doing it there saves a private mutex for this object. - DeferredMethodScheduler::instance ()->schedule (this); + if (DeferredMethodScheduler::instance ()) { + // management of m_compressed and m_scheduled is done in the DeferredMethodScheduler - + // there it is safely locked. Doing it there saves a private mutex for this object. + DeferredMethodScheduler::instance ()->schedule (this); + } else { + execute (); + } } /** @@ -187,7 +212,9 @@ public: */ void cancel () { - DeferredMethodScheduler::instance ()->unqueue (this); + if (DeferredMethodScheduler::instance ()) { + DeferredMethodScheduler::instance ()->unqueue (this); + } } /** @@ -196,7 +223,7 @@ public: void execute () { // cancel execution which might be pending - DeferredMethodScheduler::instance ()->unqueue (this); + cancel (); (mp_t->*m_method) (); } diff --git a/src/tl/tlStream.cc b/src/tl/tlStream.cc index 294e06728..1d71b1d03 100644 --- a/src/tl/tlStream.cc +++ b/src/tl/tlStream.cc @@ -41,6 +41,7 @@ #include "tlString.h" #include +#include namespace tl { @@ -325,7 +326,8 @@ InputStream::InputStream (const std::string &abstract_path) mp_delegate = new InputPipe (ex.get ()); #endif } else if (ex.test ("file:")) { - mp_delegate = new InputZLibFile (ex.get ()); + QUrl url (tl::to_qstring (abstract_path)); + mp_delegate = new InputZLibFile (tl::to_string (url.toLocalFile ())); } else { mp_delegate = new InputZLibFile (abstract_path); } @@ -345,7 +347,8 @@ std::string InputStream::absolute_path (const std::string &abstract_path) return abstract_path; #endif } else if (ex.test ("file:")) { - return tl::to_string (QFileInfo (tl::to_qstring (ex.get ())).absoluteFilePath ()); + QUrl url (tl::to_qstring (abstract_path)); + return tl::to_string (QFileInfo (url.toLocalFile ()).absoluteFilePath ()); } else { return tl::to_string (QFileInfo (tl::to_qstring (abstract_path)).absoluteFilePath ()); } diff --git a/src/unit_tests/tlDeferredExecution.cc b/src/unit_tests/tlDeferredExecution.cc index 22f60a7ab..058fe8be2 100644 --- a/src/unit_tests/tlDeferredExecution.cc +++ b/src/unit_tests/tlDeferredExecution.cc @@ -44,6 +44,8 @@ public: TEST(1) { + tl::DeferredMethodScheduler scheduler (); + g_na = g_nb = 0; QCoreApplication::instance ()->processEvents (); @@ -70,8 +72,8 @@ TEST(1) EXPECT_EQ (g_na, 0); EXPECT_EQ (g_nb, 0); - tl::DeferredMethodScheduler::instance ()->enable (false); - tl::DeferredMethodScheduler::instance ()->enable (false); + tl::DeferredMethodScheduler::enable (false); + tl::DeferredMethodScheduler::enable (false); QCoreApplication::instance ()->processEvents (); @@ -80,7 +82,7 @@ TEST(1) EXPECT_EQ (g_na, 0); EXPECT_EQ (g_nb, 0); - tl::DeferredMethodScheduler::instance ()->enable (true); + tl::DeferredMethodScheduler::enable (true); x->db (); x->db (); @@ -92,7 +94,7 @@ TEST(1) EXPECT_EQ (g_na, 0); EXPECT_EQ (g_nb, 0); - tl::DeferredMethodScheduler::instance ()->enable (true); + tl::DeferredMethodScheduler::enable (true); QCoreApplication::instance ()->processEvents ();