From 53d2557ba58d68aab7b6341232ddec99421ae96e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 Dec 2017 00:51:06 +0100 Subject: [PATCH 1/7] First steps towards fix. Needs review. The patch is based on splitting the application class into two incarnations - one for GUI capability and one without. GSI binding happens dynamically based on the mode chosen. We can do so because the application class is the first one to become active and can decide the mode by itself. In general, the application class carries too much functionality and splitting is a task for the future. Right now, the functionality is inside the base class and the derived classes basically only configure the base class. A better design would be to drop the QApplication inheritance in the RBA::Application class hierarchy and provide access to the QApplication object through qapp_gui getter. --- src/ext/ext/extXORToolDialog.cc | 4 +- src/klayout_main/klayout.cc | 17 +- src/lay/lay/gsiDeclLayApplication.cc | 350 +++++++++++++++------------ src/lay/lay/layApplication.cc | 241 ++++++++++-------- src/lay/lay/layApplication.h | 143 ++++++++--- src/lay/lay/layFontController.cc | 2 +- src/lay/lay/layHelpSource.cc | 10 +- src/lay/lay/layLibraryController.cc | 2 +- src/lay/lay/layMacroController.cc | 4 +- src/lay/lay/layMacroEditorDialog.cc | 44 ++-- src/lay/lay/layMainWindow.cc | 28 +-- src/lay/lay/laySettingsForm.cc | 2 +- src/lay/lay/laySignalHandler.cc | 8 +- src/unit_tests/unit_test_main.cc | 4 +- 14 files changed, 513 insertions(+), 346 deletions(-) diff --git a/src/ext/ext/extXORToolDialog.cc b/src/ext/ext/extXORToolDialog.cc index a41558c61..710f6a16b 100644 --- a/src/ext/ext/extXORToolDialog.cc +++ b/src/ext/ext/extXORToolDialog.cc @@ -901,7 +901,7 @@ XORToolDialog::run_xor () bool summarize = mp_ui->summarize_cb->isChecked (); // TODO: make this a user interface feature later - bool process_el = lay::Application::instance ()->special_app_flag ("ALWAYS_DO_XOR"); + bool process_el = lay::ApplicationBase::instance ()->special_app_flag ("ALWAYS_DO_XOR"); int cv_index_a = mp_ui->layouta->current_cv_index (); int cv_index_b = mp_ui->layoutb->current_cv_index (); @@ -1384,7 +1384,7 @@ XORToolDialog::run_xor () // If the output mode is database, ask whether to keep the data collected so far. // If the answer is yes, remove the RDB. // Don't ask if the application has exit (window was closed) - if (lay::Application::instance ()->main_window () && !lay::Application::instance ()->main_window ()->exited ()) { + if (lay::ApplicationBase::instance ()->main_window () && !lay::ApplicationBase::instance ()->main_window ()->exited ()) { QMessageBox msgbox (QMessageBox::Question, QObject::tr ("Keep Data For Cancelled Job"), QObject::tr ("The job has been cancelled. Keep the data collected so far?"), diff --git a/src/klayout_main/klayout.cc b/src/klayout_main/klayout.cc index d53d1ce2f..a4d688477 100644 --- a/src/klayout_main/klayout.cc +++ b/src/klayout_main/klayout.cc @@ -227,14 +227,19 @@ klayout_main_cont (int &argc, char **argv) } } - lay::Application app (argc, argv, non_ui_mode); + std::auto_ptr app; + if (non_ui_mode) { + app.reset (new lay::NonGuiApplication (argc, argv)); + } else { + app.reset (new lay::GuiApplication (argc, argv)); + } QString locale = QLocale::system ().name (); /* TODO: this kills valgrind QTranslator translator; - if (translator.load (QString::fromUtf8 ("klayout_") + locale)) { - app.installTranslator (&translator); + if (app->qapp () && translator.load (QString::fromUtf8 ("klayout_") + locale)) { + app->qapp ()->installTranslator (&translator); } */ @@ -242,18 +247,18 @@ klayout_main_cont (int &argc, char **argv) QTextCodec::setCodecForTr (QTextCodec::codecForName ("utf8")); #endif - if (app.has_gui ()) { + if (app->has_gui ()) { BEGIN_PROTECTED_CLEANUP - result = app.run (); + result = app->run (); END_PROTECTED_CLEANUP { result = 1; } } else { - result = app.run (); + result = app->run (); } } catch (tl::ExitException &ex) { diff --git a/src/lay/lay/gsiDeclLayApplication.cc b/src/lay/lay/gsiDeclLayApplication.cc index 9648f7e45..1f4aef60f 100644 --- a/src/lay/lay/gsiDeclLayApplication.cc +++ b/src/lay/lay/gsiDeclLayApplication.cc @@ -61,167 +61,207 @@ void crash_me (int reason) } } -static std::string arch (lay::Application *) +template +static std::string arch (C *) { return tl::arch_string (); } -Class decl_Application (QT_EXTERNAL_BASE (QApplication) "Application", - - method ("instance", &lay::Application::instance, - "@brief Return the singleton instance of the application\n" - "\n" - "There is exactly one instance of the application. This instance can be obtained with this " - "method." - ) + - method ("crash_me", &crash_me, "@hide") + - method ("symname", &lay::get_symbol_name_from_address, "@hide") + - method ("is_editable?", &lay::Application::is_editable, - "@brief Returns true if the application is in editable mode\n" - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("get_config", &lay::Application::get_config, - "@brief Gets the value for a configuration parameter\n" - "\n" - "@args name\n" - "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" - "\n" - "@return The value of the parameter\n" - "\n" - "This method returns the value of the given configuration parameter. If the parameter is not " - "known, an exception will be thrown. Use \\get_config_names to obtain a list of all configuration " - "parameter names available.\n" - "\n" - "Configuration parameters are always stored as strings. The actual format of this string is specific " - "to the configuration parameter. The values delivered by this method correspond to the values stored " - "in the configuration file " - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("get_config_names", &lay::Application::get_config_names, - "@brief Gets the configuration parameter names\n" - "\n" - "@return A list of configuration parameter names\n" - "\n" - "This method returns the names of all known configuration parameters. These names can be used to " - "get and set configuration parameter values." - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("set_config", &lay::Application::set_config, - "@brief Sets a configuration parameter with the given name to the given value\n" - "\n" - "@args name, value\n" - "@param name The name of the configuration parameter to set\n" - "@param value The value to which to set the configuration parameter\n" - "\n" - "This method sets the configuration parameter with the given name to the given value. " - "Values can only be strings. Numerical values have to be converted into strings first. " - "The actual format of the value depends on the configuration parameter. The name must " - "be one of the names returned by \\get_config_names." - "\n" - "It is possible to write an arbitrary name/value pair into the configuration database which then is " - "written to the configuration file." - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("commit_config", &lay::Application::config_end, - "@brief Commits the configuration settings\n" - "\n" - "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " - "After a sequence of \\set_config calls, this method should be called to activate the " - "settings made by these calls.\n" - "\n" - "This method has been introduced in version 0.25.\n" - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("write_config", &lay::Application::write_config, - "@brief Writes configuration to a file\n" - "@args file_name\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "If the configuration file cannot be written, \n" - "is returned but no exception is thrown.\n" - ) + - // TODO: basically this method belongs to PluginRoot (aka MainWindow). - // There is separate declaration for PluginRoot which we have to synchronize - // with this method. - method ("read_config", &lay::Application::read_config, - "@brief Reads the configuration from a file\n" - "@args file_name\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "This method siletly does nothing, if the config file does not\n" - "exist. If it does and an error occured, the error message is printed\n" - "on stderr. In both cases, false is returned.\n" - ) + - method ("main_window", &lay::Application::main_window, - "@brief Returns a reference to the main window\n" - "\n" - "@return A object reference to the main window object." - ) + - method ("execute|#exec", &lay::Application::exec, - "@brief Executes the application's main loop\n" - "\n" - "This method must be called in order to execute the application in the main " - "script if a script is provided." - ) + - method ("process_events", (void (lay::Application::*)()) &lay::Application::process_events, - "@brief Processes pending events\n" - "\n" - "This method processes pending events and dispatches them internally. Calling this " - "method periodically during a long operation keeps the application 'alive'" - ) + - method ("application_data_path", &lay::Application::appdata_path, - "@brief Returns the application's data path (where the configuration file is stored for example)\n" - "\n" - "This method has been added in version 0.22." - ) + - method ("inst_path", &lay::Application::inst_path, - "@brief Returns the application's installation path (where the executable is located)\n" - "\n" - "This method has been added in version 0.18. Version 0.22 offers the method \\klayout_path which " - "delivers all components of the search path." - ) + - method ("klayout_path", &lay::Application::klayout_path, - "@brief Returns the KLayout path (search path for KLayout components)\n" - "\n" - "The result is an array containing the components of the path.\n" - "\n" - "This method has been added in version 0.22." - ) + - method ("exit", &lay::Application::exit, - "@args result\n" - "@brief Ends the application with the given exit status\n" - "\n" - "This method should be called instead of simply shutting down the process. It performs some " - "important cleanup without which the process might crash. If the result code is 0 (success), " - "the configuration file will be updated unless that has been disabled by the -nc command line switch." - "\n" - "This method has been added in version 0.22." - ) + - method ("version", &lay::Application::version, - "@brief Returns the application's version string\n" - ) + - method_ext ("arch", &arch, - "@brief Returns the architecture string\n" - "This method has been introduced in version 0.25." - ) - , +template +static gsi::Methods application_methods () +{ + return + method ("crash_me", &crash_me, "@hide") + + method ("symname", &lay::get_symbol_name_from_address, "@hide") + + method ("is_editable?", &C::is_editable, + "@brief Returns true if the application is in editable mode\n" + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method ("get_config", &C::get_config, + "@brief Gets the value for a configuration parameter\n" + "\n" + "@args name\n" + "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" + "\n" + "@return The value of the parameter\n" + "\n" + "This method returns the value of the given configuration parameter. If the parameter is not " + "known, an exception will be thrown. Use \\get_config_names to obtain a list of all configuration " + "parameter names available.\n" + "\n" + "Configuration parameters are always stored as strings. The actual format of this string is specific " + "to the configuration parameter. The values delivered by this method correspond to the values stored " + "in the configuration file " + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method > ("get_config_names", &C::get_config_names, + "@brief Gets the configuration parameter names\n" + "\n" + "@return A list of configuration parameter names\n" + "\n" + "This method returns the names of all known configuration parameters. These names can be used to " + "get and set configuration parameter values." + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method ("set_config", &C::set_config, + "@brief Sets a configuration parameter with the given name to the given value\n" + "\n" + "@args name, value\n" + "@param name The name of the configuration parameter to set\n" + "@param value The value to which to set the configuration parameter\n" + "\n" + "This method sets the configuration parameter with the given name to the given value. " + "Values can only be strings. Numerical values have to be converted into strings first. " + "The actual format of the value depends on the configuration parameter. The name must " + "be one of the names returned by \\get_config_names." + "\n" + "It is possible to write an arbitrary name/value pair into the configuration database which then is " + "written to the configuration file." + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method ("commit_config", &C::config_end, + "@brief Commits the configuration settings\n" + "\n" + "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " + "After a sequence of \\set_config calls, this method should be called to activate the " + "settings made by these calls.\n" + "\n" + "This method has been introduced in version 0.25.\n" + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method ("write_config", &C::write_config, + "@brief Writes configuration to a file\n" + "@args file_name\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "If the configuration file cannot be written, \n" + "is returned but no exception is thrown.\n" + ) + + // TODO: basically this method belongs to PluginRoot (aka MainWindow). + // There is separate declaration for PluginRoot which we have to synchronize + // with this method. + method ("read_config", &C::read_config, + "@brief Reads the configuration from a file\n" + "@args file_name\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "This method siletly does nothing, if the config file does not\n" + "exist. If it does and an error occured, the error message is printed\n" + "on stderr. In both cases, false is returned.\n" + ) + + method ("main_window", &C::main_window, + "@brief Returns a reference to the main window\n" + "\n" + "@return A object reference to the main window object." + ) + + method ("execute|#exec", &C::exec, + "@brief Executes the application's main loop\n" + "\n" + "This method must be called in order to execute the application in the main " + "script if a script is provided." + ) + + method ("process_events", (void (C::*)()) &C::process_events, + "@brief Processes pending events\n" + "\n" + "This method processes pending events and dispatches them internally. Calling this " + "method periodically during a long operation keeps the application 'alive'" + ) + + method ("application_data_path", &C::appdata_path, + "@brief Returns the application's data path (where the configuration file is stored for example)\n" + "\n" + "This method has been added in version 0.22." + ) + + method ("inst_path", &C::inst_path, + "@brief Returns the application's installation path (where the executable is located)\n" + "\n" + "This method has been added in version 0.18. Version 0.22 offers the method \\klayout_path which " + "delivers all components of the search path." + ) + + method &> ("klayout_path", &C::klayout_path, + "@brief Returns the KLayout path (search path for KLayout components)\n" + "\n" + "The result is an array containing the components of the path.\n" + "\n" + "This method has been added in version 0.22." + ) + + method ("exit", &C::exit, + "@args result\n" + "@brief Ends the application with the given exit status\n" + "\n" + "This method should be called instead of simply shutting down the process. It performs some " + "important cleanup without which the process might crash. If the result code is 0 (success), " + "the configuration file will be updated unless that has been disabled by the -nc command line switch." + "\n" + "This method has been added in version 0.22." + ) + + method ("version", &C::version, + "@brief Returns the application's version string\n" + ) + + method_ext ("arch", &arch, + "@brief Returns the architecture string\n" + "This method has been introduced in version 0.25." + ) + + method ("instance", &C::instance, + "@brief Return the singleton instance of the application\n" + "\n" + "There is exactly one instance of the application. This instance can be obtained with this " + "method." + ) + ; +} - "@brief The application object\n" - "\n" - "The application object is the main port from which to access all the internals " - "of the application, in particular the main window." +static std::string application_doc () +{ + return + "@brief The application object\n" + "\n" + "The application object is the main port from which to access all the internals " + "of the application, in particular the main window." + ; +} -); +/** + * @brief Creates the right application object declaration depending on the mode + * + * This declaration factory will register a GuiApplication declaration (derived from QApplication) + * if in GUI mode and a NonGuiApplication declaration (derived from QCoreApplication). + */ +void +LAY_PUBLIC make_application_decl (bool non_gui_mode) +{ + static std::auto_ptr > gui_app_decl; + static std::auto_ptr > non_gui_app_decl; + + if (non_gui_mode) { + + non_gui_app_decl.reset ( + new Class (QT_EXTERNAL_BASE (QCoreApplication) "Application", + application_methods (), + application_doc () + ) + ); + + } else { + + gui_app_decl.reset ( + new Class (QT_EXTERNAL_BASE (QApplication) "Application", + application_methods (), + application_doc () + ) + ); + + } +} } diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index 27727cf60..ef198f11d 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -75,6 +75,11 @@ # include #endif +namespace gsi +{ + void make_application_decl (bool non_gui_mode); +} + namespace lay { @@ -165,7 +170,7 @@ static void ui_exception_handler_def (QWidget *parent) // -------------------------------------------------------------------------------- -static Application *ms_instance = 0; +static ApplicationBase *ms_instance = 0; static PluginDescriptor load_plugin (const std::string &pp) { @@ -210,8 +215,11 @@ static PluginDescriptor load_plugin (const std::string &pp) return desc; } -Application::Application (int &argc, char **argv, bool non_ui_mode) - : QApplication (argc, argv, !non_ui_mode), +// -------------------------------------------------------------------------------- +// ApplicationBase implementation + +ApplicationBase::ApplicationBase () + : gsi::ObjectBase (), m_lyp_map_all_cvs (true), m_lyp_add_default (false), m_write_config_file (true), @@ -224,8 +232,6 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) m_vo_mode (false), m_editable (false), m_enable_undo (true), - mp_qapp (0), - mp_qapp_gui (0), mp_ruby_interpreter (0), mp_python_interpreter (0), mp_mw (0), @@ -234,20 +240,27 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) mp_plugin_root (0), mp_recorder (0) { + // nothing yet - see init +} + +void +ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) +{ + m_no_gui = non_ui_mode; + + gsi::make_application_decl (non_ui_mode); + // TODO: offer a strict mode for exception handling where this takes place: - // lay::Application::instance ()->exit (1); + // lay::ApplicationBase::instance ()->exit (1); if (! non_ui_mode) { tl::set_ui_exception_handlers (ui_exception_handler_tl, ui_exception_handler_std, ui_exception_handler_def); } - 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)); + if (qapp_gui ()) { + qapp_gui ()->setStyle (new lay::BackgroundAwareTreeStyle (0)); } // initialize the system codecs (Hint: this must be done after the QApplication is initialized because @@ -288,7 +301,7 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) // get the KLayout path m_klayout_path = lay::get_klayout_path (); - if (mp_qapp_gui) { + if (qapp_gui ()) { // create the configuration files paths and collect the initialization config files // (the ones used for reset) into m_initial_config_files. @@ -390,7 +403,7 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) m_native_plugins.push_back (load_plugin (m)); modules.insert (mn); } catch (tl::Exception &ex) { - tl::error << tl::to_string (tr ("Unable to load plugin %1: %2").arg (tl::to_qstring (m)).arg (tl::to_qstring (ex.msg ()))); + tl::error << tl::to_string (QObject::tr ("Unable to load plugin %1: %2").arg (tl::to_qstring (m)).arg (tl::to_qstring (ex.msg ()))); } } } @@ -787,17 +800,17 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) db::set_default_editable_mode (m_editable); db::enable_transactions (m_enable_undo); - if (mp_qapp_gui) { - mp_qapp_gui->setWindowIcon (QIcon (QString::fromUtf8 (":/logo.png"))); + if (qapp_gui ()) { + qapp_gui ()->setWindowIcon (QIcon (QString::fromUtf8 (":/logo.png"))); #if QT_VERSION >= 0x040500 - mp_qapp_gui->setAttribute (Qt::AA_DontShowIconsInMenus, false); + qapp_gui ()->setAttribute (Qt::AA_DontShowIconsInMenus, false); #endif } - if (mp_qapp_gui && ! gtf_record.empty ()) { + if (qapp_gui () && ! gtf_record.empty ()) { // since the recorder tracks QAction connections etc., it must be instantiated before every other // object performing a gtf::action_connect for example - mp_recorder = new gtf::Recorder (mp_qapp_gui, gtf_record); + mp_recorder = new gtf::Recorder (qapp_gui (), gtf_record); mp_recorder->save_incremental (gtf_save_incremental); } @@ -821,9 +834,9 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) // suffixes through the MacroInterpreter interface. lym::MacroCollection::root ().rescan (); - if (mp_qapp_gui) { - mp_mw = new lay::MainWindow (mp_qapp_gui, "main_window"); - QObject::connect (mp_mw, SIGNAL (closed ()), mp_qapp_gui, SLOT (quit ())); + if (qapp_gui ()) { + mp_mw = new lay::MainWindow (qapp_gui (), "main_window"); + QObject::connect (mp_mw, SIGNAL (closed ()), qapp_gui (), SLOT (quit ())); mp_plugin_root = mp_mw; } else { mp_pr = new lay::ProgressReporter (); @@ -863,7 +876,7 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } } -Application::~Application () +ApplicationBase::~ApplicationBase () { tl::set_ui_exception_handlers (0, 0, 0); @@ -875,7 +888,7 @@ Application::~Application () } std::vector -Application::scan_global_modules () +ApplicationBase::scan_global_modules () { // NOTE: // this is deprecated functionality - for backward compatibility, global "*.rbm" and "*.pym" modules @@ -914,7 +927,7 @@ Application::scan_global_modules () if (rbm_file.exists () && rbm_file.isReadable ()) { std::string m = tl::to_string (rbm_file.absoluteFilePath ()); if (modules.find (m) == modules.end ()) { - tl::warn << tl::to_string (tr ("Global modules are deprecated. Turn '%1' into an autorun macro instead and put it into 'macros' or 'pymacros'.").arg (tl::to_qstring (m))); + tl::warn << tl::to_string (QObject::tr ("Global modules are deprecated. Turn '%1' into an autorun macro instead and put it into 'macros' or 'pymacros'.").arg (tl::to_qstring (m))); global_modules.push_back (m); modules.insert (m); } @@ -926,22 +939,8 @@ Application::scan_global_modules () return global_modules; } -bool -Application::notify (QObject *receiver, QEvent *e) -{ - // Note: due to a bug in some Qt versions (i.e. 4.8.3) throwing exceptions across - // signals may not be safe. Hence the local BEGIN_PROTECTED .. END_PROTECTED approach - // is still preferred over the global solution through "notify" - - bool ret = true; - BEGIN_PROTECTED - ret = QApplication::notify (receiver, e); - END_PROTECTED - return ret; -} - void -Application::exit (int result) +ApplicationBase::exit (int result) { if (! result) { finish (); @@ -951,7 +950,7 @@ Application::exit (int result) } void -Application::finish () +ApplicationBase::finish () { // save the recorded test events if (mp_mw && mp_recorder && mp_recorder->recording ()) { @@ -978,7 +977,7 @@ Application::finish () } void -Application::shutdown () +ApplicationBase::shutdown () { // uninitialize the plugins for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { @@ -996,7 +995,10 @@ Application::shutdown () } // delete all other top level widgets for safety - we don't want Ruby clean them up for us - QWidgetList tl_widgets = topLevelWidgets (); + QWidgetList tl_widgets; + if (qapp_gui ()) { + tl_widgets = qapp_gui ()->topLevelWidgets (); + } for (QWidgetList::iterator w = tl_widgets.begin (); w != tl_widgets.end (); ++w) { delete *w; } @@ -1026,25 +1028,23 @@ Application::shutdown () mp_python_interpreter = 0; } - mp_qapp = 0; - mp_qapp_gui = 0; ms_instance = 0; } -Application * -Application::instance () +ApplicationBase * +ApplicationBase::instance () { return ms_instance; } std::string -Application::version () const +ApplicationBase::version () const { return std::string (lay::Version::name ()) + " " + lay::Version::version (); } std::string -Application::usage () +ApplicationBase::usage () { std::string r; r = std::string (lay::Version::exe_name ()) + " [] [] ..\n"; @@ -1090,7 +1090,7 @@ Application::usage () } int -Application::run () +ApplicationBase::run () { gtf::Player player (0); @@ -1267,7 +1267,7 @@ Application::run () } void -Application::autorun () +ApplicationBase::autorun () { // call "autorun" on all plugins that wish so for (std::vector ::const_iterator p = m_native_plugins.begin (); p != m_native_plugins.end (); ++p) { @@ -1281,7 +1281,7 @@ Application::autorun () } void -Application::set_editable (bool e) +ApplicationBase::set_editable (bool e) { if (m_editable != e) { m_editable = e; @@ -1310,51 +1310,8 @@ dump_children (QObject *obj, int level = 0) } } -int -Application::exec () -{ - if (m_no_gui) { - return 0; - } else { - - // if requested, dump the widgets - if (tl::verbosity () >= 40) { - - QWidgetList tl_widgets = QApplication::topLevelWidgets (); - - tl::info << tl::to_string (QObject::tr ("Widget tree:")); - for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) { - if (! (*tl)->objectName ().isEmpty ()) { - dump_children (*tl); - } - } - tl::info << ""; - - tl::info << tl::to_string (QObject::tr ("Actions list:")); - for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) { - if (! (*tl)->objectName ().isEmpty ()) { - QList actions = (*tl)->findChildren (); - if (! actions.isEmpty ()) { - tl::info << tl::to_string ((*tl)->objectName ()) << ":"; - for (QList::const_iterator a = actions.begin (); a != actions.end (); ++a) { - if (! (*a)->objectName ().isEmpty ()) { - tl::info << " " << tl::to_string ((*a)->objectName ()); - } - } - } - } - } - tl::info << ""; - - } - - return mp_qapp_gui->exec (); - - } -} - void -Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) +ApplicationBase::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) { if (mp_mw) { @@ -1382,13 +1339,13 @@ Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) } bool -Application::write_config (const std::string &config_file) +ApplicationBase::write_config (const std::string &config_file) { return mp_plugin_root ? mp_plugin_root->write_config (config_file) : 0; } void -Application::reset_config () +ApplicationBase::reset_config () { clear_config (); for (std::vector ::const_iterator c = m_initial_config_files.begin (); c != m_initial_config_files.end (); ++c) { @@ -1399,7 +1356,7 @@ Application::reset_config () } void -Application::clear_config () +ApplicationBase::clear_config () { if (mp_plugin_root) { mp_plugin_root->clear_config (); @@ -1407,13 +1364,13 @@ Application::clear_config () } bool -Application::read_config (const std::string &config_file) +ApplicationBase::read_config (const std::string &config_file) { return mp_plugin_root ? mp_plugin_root->read_config (config_file) : true; } void -Application::set_config (const std::string &name, const std::string &value) +ApplicationBase::set_config (const std::string &name, const std::string &value) { if (mp_plugin_root) { mp_plugin_root->config_set (name, value); @@ -1421,7 +1378,7 @@ Application::set_config (const std::string &name, const std::string &value) } void -Application::config_end () +ApplicationBase::config_end () { if (mp_plugin_root) { mp_plugin_root->config_end (); @@ -1429,7 +1386,7 @@ Application::config_end () } std::string -Application::get_config (const std::string &name) const +ApplicationBase::get_config (const std::string &name) const { if (mp_plugin_root) { return mp_plugin_root->config_get (name); @@ -1439,7 +1396,7 @@ Application::get_config (const std::string &name) const } std::vector -Application::get_config_names () const +ApplicationBase::get_config_names () const { std::vector names; if (mp_plugin_root) { @@ -1449,12 +1406,88 @@ Application::get_config_names () const } bool -Application::special_app_flag (const std::string &name) +ApplicationBase::special_app_flag (const std::string &name) { // TODO: some more elaborate scheme? const char *env = getenv (("KLAYOUT_" + name).c_str ()); return (env && *env); } +// -------------------------------------------------------------------------------- +// GuiApplication implementation + +GuiApplication::GuiApplication (int &argc, char **argv) + : QApplication (argc, argv), ApplicationBase () +{ + init_app (argc, argv, false); +} + +bool +GuiApplication::notify (QObject *receiver, QEvent *e) +{ + // Note: due to a bug in some Qt versions (i.e. 4.8.3) throwing exceptions across + // signals may not be safe. Hence the local BEGIN_PROTECTED .. END_PROTECTED approach + // is still preferred over the global solution through "notify" + + bool ret = true; + BEGIN_PROTECTED + ret = QApplication::notify (receiver, e); + END_PROTECTED + return ret; +} + +int +GuiApplication::exec () +{ + // if requested, dump the widgets + if (tl::verbosity () >= 40) { + + QWidgetList tl_widgets = QApplication::topLevelWidgets (); + + tl::info << tl::to_string (QObject::tr ("Widget tree:")); + for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) { + if (! (*tl)->objectName ().isEmpty ()) { + dump_children (*tl); + } + } + tl::info << ""; + + tl::info << tl::to_string (QObject::tr ("Actions list:")); + for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) { + if (! (*tl)->objectName ().isEmpty ()) { + QList actions = (*tl)->findChildren (); + if (! actions.isEmpty ()) { + tl::info << tl::to_string ((*tl)->objectName ()) << ":"; + for (QList::const_iterator a = actions.begin (); a != actions.end (); ++a) { + if (! (*a)->objectName ().isEmpty ()) { + tl::info << " " << tl::to_string ((*a)->objectName ()); + } + } + } + } + } + tl::info << ""; + + } + + return QApplication::exec (); +} + +// -------------------------------------------------------------------------------- +// NonGuiApplication implementation + +NonGuiApplication::NonGuiApplication (int &argc, char **argv) + : QCoreApplication (argc, argv), ApplicationBase () +{ + init_app (argc, argv, true); +} + +int +NonGuiApplication::exec () +{ + // A non-GUI application does nothing on exec + return 0; +} + } diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index d42d030f8..98d98eb5a 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -82,33 +82,33 @@ struct PluginDescriptor }; /** - * @brief The basic application object + * @brief The application base class * - * This object encapsulates command line parsing, creation of the main window - * widget and the basic execution loop. + * This is the basic functionality for the application class. + * Two specializations exist: one for the GUI-less version (derived from QCoreApplication) + * and one for the GUI version (derived from QApplication). */ -class LAY_PUBLIC Application - : public QApplication, public gsi::ObjectBase +class LAY_PUBLIC ApplicationBase + : public gsi::ObjectBase { public: /** - * @brief The application constructor + * @brief The application constructor * * @param argc The number of external command-line arguments passed. * @param argv The external command-line arguments. - * @param non_ui_mode True, if the UI shall not be enabled */ - Application (int &argc, char **argv, bool non_ui_mode); + ApplicationBase (); /** * @brief Destructor */ - ~Application (); + virtual ~ApplicationBase (); /** * @brief The singleton instance */ - static Application *instance (); + static ApplicationBase *instance (); /** * @brief Exit the application @@ -118,17 +118,12 @@ public: void exit (int result); /** - * @brief Reimplementation of notify from QApplication - */ - bool notify (QObject *receiver, QEvent *e); - - /** - * @brief Return the program's version + * @brief Return the program's version */ std::string version () const; /** - * @brief Return the program's usage string + * @brief Return the program's usage string */ std::string usage (); @@ -152,13 +147,15 @@ public: * * This method issues all the lower level methods required in order to perform the * applications main code. + * Depending on the arguments and UI capabilities, this method will + * either execute the command line macros for open the main window. */ int run (); /** - * @brief Execute the GUI main loop + * @brief Executes the UI loop if GUI is enabled */ - int exec (); + virtual int exec () = 0; /** * @brief Process pending events @@ -221,9 +218,9 @@ public: /** * @brief Gets a value indicating whether the give special application flag is set - * + * * Special application flags are ways to introduce debug or flags for special - * use cases. Such flags have a name and currently are controlled externally by + * use cases. Such flags have a name and currently are controlled externally by * an environment variable called "KLAYOUT_x" where x is the name. If that * variable is set and the value is non-empty, the flag is regarded set. */ @@ -232,7 +229,7 @@ public: /** * @brief Return a reference to the Ruby interpreter */ - gsi::Interpreter &ruby_interpreter () + gsi::Interpreter &ruby_interpreter () { return *mp_ruby_interpreter; } @@ -240,7 +237,7 @@ public: /** * @brief Return a reference to the Ruby interpreter */ - gsi::Interpreter &python_interpreter () + gsi::Interpreter &python_interpreter () { return *mp_python_interpreter; } @@ -287,7 +284,7 @@ public: /** * @brief Reset config to global configuration */ - void reset_config (); + void reset_config (); /** * @brief Synchronize macro collections with technology-specific macros @@ -328,6 +325,20 @@ public: return m_native_plugins; } + /** + * @brief Gets the QCoreApplication object + */ + virtual QCoreApplication *qapp () { return 0; } + + /** + * @brief Gets the QApplication object + * This method will return non-null only if a GUI-enabled application is present. + */ + virtual QApplication *qapp_gui () { return 0; } + +protected: + void init_app (int &argc, char **argv, bool non_ui_mode); + private: void shutdown (); void finish (); @@ -366,8 +377,6 @@ private: bool m_vo_mode; bool m_editable; 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. @@ -381,13 +390,93 @@ private: std::vector m_native_plugins; }; +/** + * @brief The GUI-enabled application class + */ +class LAY_PUBLIC GuiApplication + : public QApplication, public ApplicationBase +{ +public: + GuiApplication (int &argc, char **argv); + + QApplication *qapp_gui () { return this; } + QCoreApplication *qapp () { return this; } + + /** + * @brief Reimplementation of notify from QApplication + */ + bool notify (QObject *receiver, QEvent *e); + + /** + * @brief Gets the application instance, cast to this class + */ + static GuiApplication *instance () + { + return dynamic_cast (ApplicationBase::instance ()); + } + + /** + * @brief Specialization of exec + */ + int exec (); + + /** + * @brief Hides QCoreApplication::exit + */ + void exit (int result) + { + ApplicationBase::exit (result); + } +}; + +/** + * @brief The non-GUI-enabled application class + */ +class LAY_PUBLIC NonGuiApplication + : public QCoreApplication, public ApplicationBase +{ +public: + NonGuiApplication (int &argc, char **argv); + + QApplication *qapp_gui () { return 0; } + QCoreApplication *qapp () { return this; } + + /** + * @brief Gets the application instance, cast to this class + */ + static NonGuiApplication *instance () + { + return dynamic_cast (ApplicationBase::instance ()); + } + + /** + * @brief Specialization of exec + */ + int exec (); + + /** + * @brief Hides QCoreApplication::exit + */ + void exit (int result) + { + ApplicationBase::exit (result); + } +}; + } // namespace lay namespace tl { - template <> struct type_traits : public type_traits { + + template <> struct type_traits : public type_traits { typedef tl::false_tag has_copy_constructor; typedef tl::false_tag has_default_constructor; }; + + template <> struct type_traits : public type_traits { + typedef tl::false_tag has_copy_constructor; + typedef tl::false_tag has_default_constructor; + }; + } #endif diff --git a/src/lay/lay/layFontController.cc b/src/lay/lay/layFontController.cc index de36f4bca..9eac5db70 100644 --- a/src/lay/lay/layFontController.cc +++ b/src/lay/lay/layFontController.cc @@ -119,7 +119,7 @@ FontController::sync_dirs () m_file_watcher->clear (); m_file_watcher->enable (false); - std::vector paths = lay::Application::instance ()->klayout_path (); + std::vector paths = lay::ApplicationBase::instance ()->klayout_path (); // add the salt grains as potential sources for library definitions diff --git a/src/lay/lay/layHelpSource.cc b/src/lay/lay/layHelpSource.cc index 59304dddb..d79e820ec 100644 --- a/src/lay/lay/layHelpSource.cc +++ b/src/lay/lay/layHelpSource.cc @@ -270,12 +270,12 @@ HelpSource::initialize_index () bool ok = false; const QString help_index_cache_file = QString::fromUtf8 ("help-index.xml"); - std::string per_user_cache_file = tl::to_string (QDir (tl::to_qstring (lay::Application::instance ()->appdata_path ())).absoluteFilePath (help_index_cache_file)); + std::string per_user_cache_file = tl::to_string (QDir (tl::to_qstring (lay::ApplicationBase::instance ()->appdata_path ())).absoluteFilePath (help_index_cache_file)); // Try to obtain the help index from the installation or application path std::vector cache_files; - cache_files.push_back (tl::to_string (QDir (tl::to_qstring (lay::Application::instance ()->inst_path ())).absoluteFilePath (help_index_cache_file))); + cache_files.push_back (tl::to_string (QDir (tl::to_qstring (lay::ApplicationBase::instance ()->inst_path ())).absoluteFilePath (help_index_cache_file))); cache_files.push_back (per_user_cache_file); for (std::vector::const_iterator c = cache_files.begin (); ! ok && c != cache_files.end (); ++c) { @@ -283,7 +283,7 @@ HelpSource::initialize_index () try { tl::XMLFileSource in (*c); help_index_structure.parse (in, *this); - if (m_klayout_version == lay::Application::instance ()->version ()) { + if (m_klayout_version == lay::ApplicationBase::instance ()->version ()) { ok = true; } } catch (tl::Exception &ex) { @@ -353,7 +353,7 @@ HelpSource::create_index_file (const std::string &path) std::string HelpSource::klayout_version () const { - return lay::Application::instance ()->version (); + return lay::ApplicationBase::instance ()->version (); } void @@ -513,7 +513,7 @@ HelpSource::get_image (const std::string &u) std::string HelpSource::get_css (const std::string &u) { - std::ifstream t (tl::to_string (QDir (tl::to_qstring (lay::Application::instance()->inst_path ())).absoluteFilePath (QString::fromUtf8 ("help_format.css"))).c_str ()); + std::ifstream t (tl::to_string (QDir (tl::to_qstring (lay::ApplicationBase::instance()->inst_path ())).absoluteFilePath (QString::fromUtf8 ("help_format.css"))).c_str ()); if (t.good ()) { std::string c; while (t.good ()) { diff --git a/src/lay/lay/layLibraryController.cc b/src/lay/lay/layLibraryController.cc index 31906445c..e927daecc 100644 --- a/src/lay/lay/layLibraryController.cc +++ b/src/lay/lay/layLibraryController.cc @@ -129,7 +129,7 @@ LibraryController::sync_files () // build a list of paths vs. technology std::vector > paths; - std::vector klayout_path = lay::Application::instance ()->klayout_path (); + std::vector klayout_path = lay::ApplicationBase::instance ()->klayout_path (); for (std::vector::const_iterator p = klayout_path.begin (); p != klayout_path.end (); ++p) { paths.push_back (std::make_pair (*p, std::string ())); } diff --git a/src/lay/lay/layMacroController.cc b/src/lay/lay/layMacroController.cc index d8db9cc91..b69a42f01 100644 --- a/src/lay/lay/layMacroController.cc +++ b/src/lay/lay/layMacroController.cc @@ -292,7 +292,7 @@ MacroController::drop_url (const std::string &path_or_url) QMessageBox::No) == QMessageBox::Yes) { // Use the application data folder - QDir folder (tl::to_qstring (lay::Application::instance ()->appdata_path ())); + QDir folder (tl::to_qstring (lay::ApplicationBase::instance ()->appdata_path ())); std::string cat = "macros"; if (! macro->category ().empty ()) { @@ -300,7 +300,7 @@ MacroController::drop_url (const std::string &path_or_url) } 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 ()); + throw tl::Exception (tl::to_string (QObject::tr ("Folder '%s' does not exists in installation path '%s' - cannot install")).c_str (), cat, lay::ApplicationBase::instance ()->appdata_path ()); } QFileInfo target (folder, file_name); diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 6dca6adbc..718f680b1 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -527,7 +527,7 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lym::MacroCollection } // scan macro templates - for (std::vector::const_iterator p = lay::Application::instance ()->klayout_path ().begin (); p != lay::Application::instance ()->klayout_path ().end (); ++p) { + for (std::vector::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"))); @@ -760,13 +760,13 @@ MacroEditorDialog::showEvent (QShowEvent *) m_history_index = -1; input_field->clearEditText (); - lay::Application::instance ()->ruby_interpreter ().push_console (this); + lay::ApplicationBase::instance ()->ruby_interpreter ().push_console (this); if (m_debugging_on) { - lay::Application::instance ()->ruby_interpreter ().push_exec_handler (this); + lay::ApplicationBase::instance ()->ruby_interpreter ().push_exec_handler (this); } - lay::Application::instance ()->python_interpreter ().push_console (this); + lay::ApplicationBase::instance ()->python_interpreter ().push_console (this); if (m_debugging_on) { - lay::Application::instance ()->python_interpreter ().push_exec_handler (this); + lay::ApplicationBase::instance ()->python_interpreter ().push_exec_handler (this); } std::string ci; @@ -795,9 +795,9 @@ MacroEditorDialog::showEvent (QShowEvent *) ex.test (";"); if (ip == "ruby") { - m_watch_expressions.push_back (std::make_pair (&lay::Application::instance ()->ruby_interpreter (), expr)); + 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::Application::instance ()->python_interpreter (), expr)); + m_watch_expressions.push_back (std::make_pair (&lay::ApplicationBase::instance ()->python_interpreter (), expr)); } } @@ -900,9 +900,9 @@ MacroEditorDialog::closeEvent (QCloseEvent *) if (! om.empty ()) { om += ";"; } - if (i->first == &lay::Application::instance ()->ruby_interpreter ()) { + if (i->first == &lay::ApplicationBase::instance ()->ruby_interpreter ()) { we += "ruby"; - } else if (i->first == &lay::Application::instance ()->python_interpreter ()) { + } else if (i->first == &lay::ApplicationBase::instance ()->python_interpreter ()) { we += "python"; } we += ":"; @@ -932,10 +932,10 @@ MacroEditorDialog::closeEvent (QCloseEvent *) m_continue = false; m_window_closed = true; - lay::Application::instance ()->ruby_interpreter ().remove_console (this); - lay::Application::instance ()->ruby_interpreter ().remove_exec_handler (this); - lay::Application::instance ()->python_interpreter ().remove_console (this); - lay::Application::instance ()->python_interpreter ().remove_exec_handler (this); + 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 @@ -951,11 +951,11 @@ MacroEditorDialog::set_debugging_on (bool on) if (isVisible ()) { if (on) { - lay::Application::instance ()->ruby_interpreter ().push_exec_handler (this); - lay::Application::instance ()->python_interpreter ().push_exec_handler (this); + lay::ApplicationBase::instance ()->ruby_interpreter ().push_exec_handler (this); + lay::ApplicationBase::instance ()->python_interpreter ().push_exec_handler (this); } else { - lay::Application::instance ()->ruby_interpreter ().remove_exec_handler (this); - lay::Application::instance ()->python_interpreter ().remove_exec_handler (this); + lay::ApplicationBase::instance ()->ruby_interpreter ().remove_exec_handler (this); + lay::ApplicationBase::instance ()->python_interpreter ().remove_exec_handler (this); } } @@ -965,12 +965,12 @@ MacroEditorDialog::set_debugging_on (bool on) void MacroEditorDialog::process_events (QEventLoop::ProcessEventsFlags flags) { - if (lay::Application::instance ()) { + if (lay::ApplicationBase::instance ()) { // disable execution of deferred methods to avoid undesired execution of // code while we are inside a Ruby callback through the silent mode bool last_processing = m_in_processing; m_in_processing = true; - lay::Application::instance ()->process_events (flags, true /*silent*/); + lay::ApplicationBase::instance ()->process_events (flags, true /*silent*/); m_in_processing = last_processing; } } @@ -1132,9 +1132,9 @@ MacroEditorDialog::execute (const QString &cmd) gsi::Interpreter *interpreter = 0; if (rubyLangSel->isChecked ()) { - interpreter = &lay::Application::instance ()->ruby_interpreter (); + interpreter = &lay::ApplicationBase::instance ()->ruby_interpreter (); } else if (pythonLangSel->isChecked ()) { - interpreter = &lay::Application::instance ()->python_interpreter (); + interpreter = &lay::ApplicationBase::instance ()->python_interpreter (); } if (interpreter) { @@ -3036,7 +3036,7 @@ MacroEditorDialog::do_update_ui_to_run_mode () // Force language type to match the current execution context if (m_in_breakpoint && mp_current_interpreter) { - if (mp_current_interpreter == &lay::Application::instance ()->python_interpreter ()) { + if (mp_current_interpreter == &lay::ApplicationBase::instance ()->python_interpreter ()) { pythonLangSel->setChecked (true); rubyLangSel->setChecked (false); } else { diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 4b9e7953f..45f880419 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -1058,7 +1058,7 @@ MainWindow::init_menu () } // if in "viewer-only mode", hide all entries in the "hide_vo" group - if ((lay::Application::instance () && lay::Application::instance ()->is_vo_mode ())) { + if ((lay::ApplicationBase::instance () && lay::ApplicationBase::instance ()->is_vo_mode ())) { std::vector hide_vo_grp = mp_menu->group ("hide_vo"); for (std::vector::const_iterator g = hide_vo_grp.begin (); g != hide_vo_grp.end (); ++g) { mp_menu->action (*g).set_visible (false); @@ -1067,7 +1067,7 @@ MainWindow::init_menu () // if not in editable mode, hide all entries from "edit_mode" group // TODO: later do this on each change of the view - each view might get it's own editable mode - bool view_mode = (lay::Application::instance () && !lay::Application::instance ()->is_editable ()); + bool view_mode = (lay::ApplicationBase::instance () && !lay::ApplicationBase::instance ()->is_editable ()); std::vector edit_mode_grp = mp_menu->group ("edit_mode"); for (std::vector::const_iterator g = edit_mode_grp.begin (); g != edit_mode_grp.end (); ++g) { @@ -1293,7 +1293,7 @@ MainWindow::about_to_exec () } // TODO: later, each view may get it's own editable flag - if (lay::Application::instance () && !lay::Application::instance ()->is_editable ()) { + if (lay::ApplicationBase::instance () && !lay::ApplicationBase::instance ()->is_editable ()) { TipDialog td (this, tl::to_string (QObject::tr ("KLayout has been started in viewer mode. In this mode, editor functions are not available.\n\nTo enable these functions, start KLayout in editor mode by using the \"-e\" command line switch or select it as the default mode in the setup dialog. Choose \"Setup\" in the \"File\" menu and check \"Use editing mode by default\" on the \"Editing Mode\" page in the \"Application\" section.")), "editor-mode"); @@ -1329,7 +1329,7 @@ MainWindow::about_to_exec () edt::combine_mode_type cm = edt::CM_Add; config_get (edt::cfg_edit_combine_mode, cm, edt::CMConverter ()); - if (lay::Application::instance ()->is_editable () && cm != edt::CM_Add) { + if (lay::ApplicationBase::instance ()->is_editable () && cm != edt::CM_Add) { lay::TipDialog td (QApplication::activeWindow (), tl::to_string (QObject::tr ("The background combination mode of the shape editor is set to some other mode than 'Add'.\n" "This can be confusing, because a shape may not be drawn as expected.\n\nTo switch back to normal mode, choose 'Add' for the background combination mode in the toolbar.")), @@ -3735,7 +3735,7 @@ MainWindow::clone_current_view () } // create a new view - view = new lay::LayoutView (current_view (), &m_manager, lay::Application::instance ()->is_editable (), this, mp_view_stack); + view = new lay::LayoutView (current_view (), &m_manager, lay::ApplicationBase::instance ()->is_editable (), this, mp_view_stack); connect (view, SIGNAL (title_changed ()), this, SLOT (view_title_changed ())); connect (view, SIGNAL (dirty_changed ()), this, SLOT (view_title_changed ())); connect (view, SIGNAL (edits_enabled_changed ()), this, SLOT (edits_enabled_changed ())); @@ -4344,7 +4344,7 @@ int MainWindow::do_create_view () { // create a new view - lay::LayoutView *view = new lay::LayoutView (&m_manager, lay::Application::instance ()->is_editable (), this, mp_view_stack); + lay::LayoutView *view = new lay::LayoutView (&m_manager, lay::ApplicationBase::instance ()->is_editable (), this, mp_view_stack); connect (view, SIGNAL (title_changed ()), this, SLOT (view_title_changed ())); connect (view, SIGNAL (dirty_changed ()), this, SLOT (view_title_changed ())); @@ -4538,9 +4538,9 @@ MainWindow::update_window_title () if (current_view ()->is_dirty ()) { sep += "[+] "; } - setWindowTitle (tl::to_qstring (lay::Application::instance ()->version () + sep + current_view ()->title ())); + setWindowTitle (tl::to_qstring (lay::ApplicationBase::instance ()->version () + sep + current_view ()->title ())); } else { - setWindowTitle (tl::to_qstring (lay::Application::instance ()->version ())); + setWindowTitle (tl::to_qstring (lay::ApplicationBase::instance ()->version ())); } } @@ -5681,11 +5681,11 @@ HelpAboutDialog::HelpAboutDialog (QWidget *parent) mp_ui->setupUi (this); std::vector build_options; - if (lay::Application::instance ()->ruby_interpreter ().available ()) { - build_options.push_back (tl::to_string (tr ("Ruby interpreter ")) + lay::Application::instance ()->ruby_interpreter ().version ()); + if (lay::ApplicationBase::instance ()->ruby_interpreter ().available ()) { + build_options.push_back (tl::to_string (tr ("Ruby interpreter ")) + lay::ApplicationBase::instance ()->ruby_interpreter ().version ()); } - if (lay::Application::instance ()->python_interpreter ().available ()) { - build_options.push_back (tl::to_string (tr ("Python interpreter ")) + lay::Application::instance ()->python_interpreter ().version ()); + if (lay::ApplicationBase::instance ()->python_interpreter ().available ()) { + build_options.push_back (tl::to_string (tr ("Python interpreter ")) + lay::ApplicationBase::instance ()->python_interpreter ().version ()); } #if defined(HAVE_QTBINDINGS) build_options.push_back (tl::to_string (tr ("Qt bindings for scripts"))); @@ -5720,12 +5720,12 @@ HelpAboutDialog::HelpAboutDialog (QWidget *parent) s += ""; } - if (! lay::Application::instance ()->native_plugins ().empty ()) { + if (! lay::ApplicationBase::instance ()->native_plugins ().empty ()) { s += "

"; s += "

"; s += escape_xml (tl::to_string (QObject::tr ("Binary extensions:"))); s += "

    "; - for (std::vector::const_iterator pd = lay::Application::instance ()->native_plugins ().begin (); pd != lay::Application::instance ()->native_plugins ().end (); ++pd) { + for (std::vector::const_iterator pd = lay::ApplicationBase::instance ()->native_plugins ().begin (); pd != lay::ApplicationBase::instance ()->native_plugins ().end (); ++pd) { s += "
  • "; if (! pd->description.empty ()) { s += escape_xml (pd->description); diff --git a/src/lay/lay/laySettingsForm.cc b/src/lay/lay/laySettingsForm.cc index f56d39c2a..20104fd2d 100644 --- a/src/lay/lay/laySettingsForm.cc +++ b/src/lay/lay/laySettingsForm.cc @@ -272,7 +272,7 @@ SettingsForm::reset_clicked () BEGIN_PROTECTED - lay::Application::instance ()->reset_config (); + lay::ApplicationBase::instance ()->reset_config (); setup (); END_PROTECTED diff --git a/src/lay/lay/laySignalHandler.cc b/src/lay/lay/laySignalHandler.cc index 078e90c1a..549522476 100644 --- a/src/lay/lay/laySignalHandler.cc +++ b/src/lay/lay/laySignalHandler.cc @@ -292,12 +292,12 @@ void signal_handler (int signo, siginfo_t *si, void *) std::auto_ptr msg; - bool has_gui = lay::Application::instance () && lay::Application::instance ()->has_gui (); + bool has_gui = lay::ApplicationBase::instance () && lay::ApplicationBase::instance ()->has_gui (); if (has_gui) { msg.reset (new CrashMessage (0, false, tl::to_qstring (text) + QObject::tr ("\nCollecting backtrace .."))); msg->show (); - lay::Application::instance ()->setOverrideCursor (Qt::WaitCursor); + lay::ApplicationBase::instance ()->qapp_gui ()->setOverrideCursor (Qt::WaitCursor); } text += std::string ("\nBacktrace:\n"); @@ -326,7 +326,7 @@ void signal_handler (int signo, siginfo_t *si, void *) for (size_t i = 0; i < nptrs; ++i) { if (has_gui) { - lay::Application::instance ()->processEvents (); + lay::ApplicationBase::instance ()->qapp_gui ()->processEvents (); if (msg->is_cancel_pressed ()) { text += "...\n"; break; @@ -402,7 +402,7 @@ void signal_handler (int signo, siginfo_t *si, void *) if (has_gui) { - lay::Application::instance ()->setOverrideCursor (QCursor ()); + lay::ApplicationBase::instance ()->qapp_gui ()->setOverrideCursor (QCursor ()); // YES! I! KNOW! // In a signal handler you shall not do fancy stuff (in particular not diff --git a/src/unit_tests/unit_test_main.cc b/src/unit_tests/unit_test_main.cc index be5b619ea..8fc313ece 100644 --- a/src/unit_tests/unit_test_main.cc +++ b/src/unit_tests/unit_test_main.cc @@ -68,7 +68,7 @@ main (int argc, char **argv) } static int -run_tests (const std::vector &selected_tests, bool editable, bool non_editable, bool slow, lay::Application &app, bool gsi_coverage, const std::vector &class_names_vector) +run_tests (const std::vector &selected_tests, bool editable, bool non_editable, bool slow, lay::ApplicationBase &app, bool gsi_coverage, const std::vector &class_names_vector) { std::set class_names; class_names.insert (class_names_vector.begin (), class_names_vector.end ()); @@ -374,7 +374,7 @@ main_cont (int &argc, char **argv) static char av3[] = "-rx"; // No mplicit macros char *av[] = { av0, av1, av2, av3, 0 }; int ac = sizeof (av) / sizeof (av[0]) - 1; - lay::Application app (ac, av, false); + lay::GuiApplication app (ac, av); app.ruby_interpreter ().push_console (&console); app.python_interpreter ().push_console (&console); From bbb1514184a75d9448dcef33e2a4da882a212647 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 Dec 2017 22:35:16 +0100 Subject: [PATCH 2/7] WIP: some refactoring. --- src/lay/lay/layApplication.cc | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index ef198f11d..f5e1dfc81 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -258,11 +258,6 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) mp_dm_scheduler.reset (new tl::DeferredMethodScheduler ()); - // install a special style proxy to overcome the issue of black-on-black tree expanders - if (qapp_gui ()) { - qapp_gui ()->setStyle (new lay::BackgroundAwareTreeStyle (0)); - } - // initialize the system codecs (Hint: this must be done after the QApplication is initialized because // it will call setlocale) tl::initialize_codecs (); @@ -301,7 +296,7 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) // get the KLayout path m_klayout_path = lay::get_klayout_path (); - if (qapp_gui ()) { + if (! non_ui_mode) { // create the configuration files paths and collect the initialization config files // (the ones used for reset) into m_initial_config_files. @@ -800,13 +795,6 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) db::set_default_editable_mode (m_editable); db::enable_transactions (m_enable_undo); - if (qapp_gui ()) { - qapp_gui ()->setWindowIcon (QIcon (QString::fromUtf8 (":/logo.png"))); -#if QT_VERSION >= 0x040500 - qapp_gui ()->setAttribute (Qt::AA_DontShowIconsInMenus, false); -#endif - } - if (qapp_gui () && ! gtf_record.empty ()) { // since the recorder tracks QAction connections etc., it must be instantiated before every other // object performing a gtf::action_connect for example @@ -1419,6 +1407,14 @@ ApplicationBase::special_app_flag (const std::string &name) GuiApplication::GuiApplication (int &argc, char **argv) : QApplication (argc, argv), ApplicationBase () { + // install a special style proxy to overcome the issue of black-on-black tree expanders + setStyle (new lay::BackgroundAwareTreeStyle (0)); + + setWindowIcon (QIcon (QString::fromUtf8 (":/logo.png"))); +#if QT_VERSION >= 0x040500 + setAttribute (Qt::AA_DontShowIconsInMenus, false); +#endif + init_app (argc, argv, false); } From 9906565013cc0bd7d90e4fa4300f2fbe100a4c74 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 26 Dec 2017 00:03:40 +0100 Subject: [PATCH 3/7] Further refactoring - more functionality for GUI/non-GUI application classes. --- src/lay/lay/layApplication.cc | 391 +++++++++++++++++++++------------- src/lay/lay/layApplication.h | 90 +++++--- 2 files changed, 305 insertions(+), 176 deletions(-) diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index f5e1dfc81..95ab3307a 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -225,6 +225,8 @@ ApplicationBase::ApplicationBase () m_write_config_file (true), m_gtf_replay_rate (0), m_gtf_replay_stop (-1), + m_gtf_record (), + m_gtf_save_incremental (false), m_no_macros (false), m_same_view (false), m_sync_mode (false), @@ -233,12 +235,7 @@ ApplicationBase::ApplicationBase () m_editable (false), m_enable_undo (true), mp_ruby_interpreter (0), - mp_python_interpreter (0), - mp_mw (0), - mp_pr (0), - mp_pb (0), - mp_plugin_root (0), - mp_recorder (0) + mp_python_interpreter (0) { // nothing yet - see init } @@ -256,8 +253,6 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) tl::set_ui_exception_handlers (ui_exception_handler_tl, ui_exception_handler_std, ui_exception_handler_def); } - mp_dm_scheduler.reset (new tl::DeferredMethodScheduler ()); - // initialize the system codecs (Hint: this must be done after the QApplication is initialized because // it will call setlocale) tl::initialize_codecs (); @@ -284,9 +279,6 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) tl_assert (ms_instance == 0); ms_instance = this; - std::string gtf_record; - bool gtf_save_incremental = false; - // get and create the klayout appdata folder if required m_appdata_path = lay::get_appdata_path (); @@ -491,11 +483,11 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) } else if (a == "-gr" && (i + 1) < argc) { - gtf_record = args [++i]; + m_gtf_record = args [++i]; } else if (a == "-gi") { - gtf_save_incremental = true; + m_gtf_save_incremental = true; } else if (a == "-gp" && (i + 1) < argc) { @@ -795,11 +787,10 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) db::set_default_editable_mode (m_editable); db::enable_transactions (m_enable_undo); - if (qapp_gui () && ! gtf_record.empty ()) { - // since the recorder tracks QAction connections etc., it must be instantiated before every other + if (! m_gtf_record.empty ()) { + // since the recorder tracks QAction connections etc., it must be instantiated before every other // object performing a gtf::action_connect for example - mp_recorder = new gtf::Recorder (qapp_gui (), gtf_record); - mp_recorder->save_incremental (gtf_save_incremental); + prepare_recording (m_gtf_record, m_gtf_save_incremental); } tl::Eval::set_global_var ("appdata_path", tl::Variant (m_appdata_path)); @@ -822,16 +813,8 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) // suffixes through the MacroInterpreter interface. lym::MacroCollection::root ().rescan (); - if (qapp_gui ()) { - mp_mw = new lay::MainWindow (qapp_gui (), "main_window"); - QObject::connect (mp_mw, SIGNAL (closed ()), qapp_gui (), SLOT (quit ())); - mp_plugin_root = mp_mw; - } else { - mp_pr = new lay::ProgressReporter (); - mp_pb = new TextProgress (10 /*verbosity level*/); - mp_pr->set_progress_bar (mp_pb); - mp_plugin_root = new lay::PluginRoot (); - } + // creates the main window or plugin root as required + setup (); // initialize the plugins for the first time if (tl::verbosity () >= 20) { @@ -842,11 +825,11 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) if (tl::verbosity () >= 20) { tl::info << " " << cls.current_name () << " [" << cls.current_position () << "]"; } - pd->initialize (mp_plugin_root); + pd->initialize (plugin_root ()); } // establish the configuration - mp_plugin_root->config_setup (); + plugin_root ()->config_setup (); // Some info output if (tl::verbosity () >= 20) { @@ -868,11 +851,8 @@ ApplicationBase::~ApplicationBase () { tl::set_ui_exception_handlers (0, 0, 0); - if (! ms_instance) { - return; - } - - shutdown (); + // check whether shutdown was called + tl_assert (ms_instance == 0); } std::vector @@ -933,26 +913,28 @@ ApplicationBase::exit (int result) if (! result) { finish (); } + + // uninitialize the plugins + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->uninitialize (plugin_root ()); + } + shutdown (); + ::exit (result); } void ApplicationBase::finish () { - // save the recorded test events - if (mp_mw && mp_recorder && mp_recorder->recording ()) { - mp_recorder->stop (); - mp_recorder->save (); - } - - if (mp_plugin_root && m_write_config_file) { + if (plugin_root () && m_write_config_file) { if (! m_config_file_to_write.empty ()) { if (tl::verbosity () >= 20) { tl::info << tl::to_string (QObject::tr ("Updating configuration file ")) << m_config_file_to_write; } - mp_plugin_root->write_config (m_config_file_to_write); + plugin_root ()->write_config (m_config_file_to_write); } if (! m_config_file_to_delete.empty () && m_config_file_to_delete != m_config_file_to_write) { if (tl::verbosity () >= 20) { @@ -964,48 +946,21 @@ ApplicationBase::finish () } } +void +ApplicationBase::prepare_recording (const std::string & /*gtf_record*/, bool /*gtf_record_incremental*/) +{ + // the base class does nothing +} + +void +ApplicationBase::start_recording () +{ + // the base class does nothing +} + void ApplicationBase::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; - mp_plugin_root = 0; - } else if (mp_plugin_root) { - delete mp_plugin_root; - mp_plugin_root = 0; - } - - // delete all other top level widgets for safety - we don't want Ruby clean them up for us - QWidgetList tl_widgets; - if (qapp_gui ()) { - tl_widgets = qapp_gui ()->topLevelWidgets (); - } - for (QWidgetList::iterator w = tl_widgets.begin (); w != tl_widgets.end (); ++w) { - delete *w; - } - - if (mp_pr) { - delete mp_pr; - mp_pr = 0; - } - - if (mp_pb) { - delete mp_pb; - mp_pb = 0; - } - - if (mp_recorder) { - delete mp_recorder; - mp_recorder = 0; - } - if (mp_ruby_interpreter) { delete mp_ruby_interpreter; mp_ruby_interpreter = 0; @@ -1080,25 +1035,24 @@ ApplicationBase::usage () int ApplicationBase::run () { + lay::MainWindow *mw = main_window (); gtf::Player player (0); - if (mp_mw) { + if (mw) { - mp_mw->set_synchronous (m_sync_mode); + (mw)->set_synchronous (m_sync_mode); if (! m_no_gui) { - mp_mw->setWindowTitle (tl::to_qstring (version ())); - mp_mw->resize (800, 600); - mp_mw->show (); + (mw)->setWindowTitle (tl::to_qstring (version ())); + (mw)->resize (800, 600); + (mw)->show (); } if (! m_gtf_replay.empty ()) { player.load (m_gtf_replay); } - if (mp_recorder) { - mp_recorder->start (); - } + start_recording (); } @@ -1108,7 +1062,7 @@ ApplicationBase::run () for (std::vector ::const_iterator c = m_config_files.begin (); c != m_config_files.end (); ++c) { BEGIN_PROTECTED_CLEANUP - mp_plugin_root->read_config (*c); + plugin_root ()->read_config (*c); // if the last config was read successfully no reset will happen: config_failed = false; END_PROTECTED_CLEANUP { @@ -1153,7 +1107,7 @@ ApplicationBase::run () // Run plugin and macro specific initializations autorun (); - if (mp_mw) { + if (mw) { for (std::vector > >::const_iterator f = m_files.begin (); f != m_files.end (); ++f) { @@ -1162,29 +1116,29 @@ ApplicationBase::run () std::string filename = f->second.first; if (f->first != layout_file_with_tech) { - mp_mw->add_mru (f->second.first); - mp_mw->load_layout (f->second.first, m_same_view ? 2 /*same view*/ : 1 /*new view*/); + mw->add_mru (f->second.first); + mw->load_layout (f->second.first, m_same_view ? 2 /*same view*/ : 1 /*new view*/); } else { - mp_mw->add_mru (f->second.first, f->second.second); - mp_mw->load_layout (f->second.first, f->second.second, m_same_view ? 2 /*same view*/ : 1 /*new view*/); + mw->add_mru (f->second.first, f->second.second); + mw->load_layout (f->second.first, f->second.second, m_same_view ? 2 /*same view*/ : 1 /*new view*/); } // Make the first one loaded the active one. - if (mp_mw->current_view ()) { - mp_mw->current_view ()->set_active_cellview_index (0); + if (mw->current_view ()) { + mw->current_view ()->set_active_cellview_index (0); } } else { - if (mp_mw->current_view () == 0) { - mp_mw->create_view (); + if (mw->current_view () == 0) { + mw->create_view (); } - if (mp_mw->current_view () != 0) { + if (mw->current_view () != 0) { std::auto_ptr db (new rdb::Database ()); db->load (f->second.first); - int rdb_index = mp_mw->current_view ()->add_rdb (db.release ()); - mp_mw->current_view ()->open_rdb_browser (rdb_index, mp_mw->current_view ()->active_cellview_index ()); + int rdb_index = mw->current_view ()->add_rdb (db.release ()); + mw->current_view ()->open_rdb_browser (rdb_index, mw->current_view ()->active_cellview_index ()); } } @@ -1192,23 +1146,23 @@ ApplicationBase::run () if (! m_layer_props_file.empty ()) { - if (m_lyp_map_all_cvs && mp_mw->is_single_cv_layer_properties_file (m_layer_props_file)) { - mp_mw->load_layer_properties (m_layer_props_file, -1, true /*all views*/, m_lyp_add_default); + if (m_lyp_map_all_cvs && mw->is_single_cv_layer_properties_file (m_layer_props_file)) { + mw->load_layer_properties (m_layer_props_file, -1, true /*all views*/, m_lyp_add_default); } else { - mp_mw->load_layer_properties (m_layer_props_file, true /*all views*/, m_lyp_add_default); + mw->load_layer_properties (m_layer_props_file, true /*all views*/, m_lyp_add_default); } tl::log << "Layer properties loaded '" << m_layer_props_file << "'"; // because the layer may carry transformations, we need to refit the cellviews. - for (unsigned int v = 0; v != mp_mw->views (); ++v) { - mp_mw->view (v)->zoom_fit (); + for (unsigned int v = 0; v != mw->views (); ++v) { + mw->view (v)->zoom_fit (); } } if (! m_session_file.empty ()) { - mp_mw->restore_session (m_session_file); + mw->restore_session (m_session_file); tl::log << "Session restored '" << m_session_file << "'"; } @@ -1219,20 +1173,20 @@ ApplicationBase::run () // 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); + pd->initialized (mw); } - if (! m_no_gui && m_gtf_replay.empty () && ! mp_recorder) { + if (! m_no_gui && m_gtf_replay.empty () && m_gtf_record.empty ()) { // Show initial tip window if required - mp_mw->about_to_exec (); + mw->about_to_exec (); } - } else if (mp_plugin_root) { + } else if (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); + pd->initialized (plugin_root ()); } } @@ -1299,37 +1253,15 @@ dump_children (QObject *obj, int level = 0) } void -ApplicationBase::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) +ApplicationBase::process_events (QEventLoop::ProcessEventsFlags /*flags*/, bool /*silent*/) { - if (mp_mw) { - - if (silent) { - mp_dm_scheduler->enable (false); - } - -#if QT_VERSION < 0x050000 - QApplication::syncX (); -#endif - - mp_mw->enter_busy_mode (true); - try { - QApplication::processEvents (flags); - } catch (...) { - // ignore exceptions - } - mp_mw->enter_busy_mode (false); - - if (silent) { - mp_dm_scheduler->enable (true); - } - - } + // The base class implementation does nothing .. } bool ApplicationBase::write_config (const std::string &config_file) { - return mp_plugin_root ? mp_plugin_root->write_config (config_file) : 0; + return plugin_root () ? plugin_root ()->write_config (config_file) : 0; } void @@ -1346,38 +1278,38 @@ ApplicationBase::reset_config () void ApplicationBase::clear_config () { - if (mp_plugin_root) { - mp_plugin_root->clear_config (); + if (plugin_root ()) { + plugin_root ()->clear_config (); } } bool ApplicationBase::read_config (const std::string &config_file) { - return mp_plugin_root ? mp_plugin_root->read_config (config_file) : true; + return plugin_root () ? plugin_root ()->read_config (config_file) : true; } void ApplicationBase::set_config (const std::string &name, const std::string &value) { - if (mp_plugin_root) { - mp_plugin_root->config_set (name, value); + if (plugin_root ()) { + plugin_root ()->config_set (name, value); } } void ApplicationBase::config_end () { - if (mp_plugin_root) { - mp_plugin_root->config_end (); + if (plugin_root ()) { + plugin_root ()->config_end (); } } std::string ApplicationBase::get_config (const std::string &name) const { - if (mp_plugin_root) { - return mp_plugin_root->config_get (name); + if (plugin_root ()) { + return plugin_root ()->config_get (name); } else { return std::string (); } @@ -1387,8 +1319,8 @@ std::vector ApplicationBase::get_config_names () const { std::vector names; - if (mp_plugin_root) { - mp_plugin_root->get_config_names (names); + if (plugin_root ()) { + plugin_root ()->get_config_names (names); } return names; } @@ -1405,7 +1337,9 @@ ApplicationBase::special_app_flag (const std::string &name) // GuiApplication implementation GuiApplication::GuiApplication (int &argc, char **argv) - : QApplication (argc, argv), ApplicationBase () + : QApplication (argc, argv), ApplicationBase (), + mp_mw (0), + mp_recorder (0) { // install a special style proxy to overcome the issue of black-on-black tree expanders setStyle (new lay::BackgroundAwareTreeStyle (0)); @@ -1415,9 +1349,24 @@ GuiApplication::GuiApplication (int &argc, char **argv) setAttribute (Qt::AA_DontShowIconsInMenus, false); #endif + // only a GUI-enabled application runs an event loop and can have a deferred + // method scheduler therefore. + mp_dm_scheduler.reset (new tl::DeferredMethodScheduler ()); + init_app (argc, argv, false); } +GuiApplication::~GuiApplication () +{ + // uninitialize the plugins + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->uninitialize (plugin_root ()); + } + + shutdown (); +} + bool GuiApplication::notify (QObject *receiver, QEvent *e) { @@ -1469,15 +1418,125 @@ GuiApplication::exec () return QApplication::exec (); } +void +GuiApplication::shutdown () +{ + if (mp_mw) { + delete mp_mw; + mp_mw = 0; + } + + // delete all other top level widgets for safety - we don't want Ruby clean them up for us + QWidgetList tl_widgets = topLevelWidgets (); + for (QWidgetList::iterator w = tl_widgets.begin (); w != tl_widgets.end (); ++w) { + delete *w; + } + + if (mp_recorder) { + delete mp_recorder; + mp_recorder = 0; + } + + ApplicationBase::shutdown (); +} + +void +GuiApplication::finish () +{ + // save the recorded test events + if (mp_recorder && mp_recorder->recording ()) { + mp_recorder->stop (); + mp_recorder->save (); + } + + ApplicationBase::finish (); +} + +void +GuiApplication::prepare_recording (const std::string >f_record, bool gtf_save_incremental) +{ + tl_assert (mp_recorder == 0); + + // since the recorder tracks QAction connections etc., it must be instantiated before every other + // object performing a gtf::action_connect for example + mp_recorder = new gtf::Recorder (this, gtf_record); + mp_recorder->save_incremental (gtf_save_incremental); +} + +void +GuiApplication::start_recording () +{ + if (mp_recorder) { + mp_recorder->start (); + } +} + +lay::PluginRoot * +GuiApplication::plugin_root () const +{ + return mp_mw; +} + +void +GuiApplication::setup () +{ + tl_assert (mp_mw == 0); + + mp_mw = new lay::MainWindow (this, "main_window"); + QObject::connect (mp_mw, SIGNAL (closed ()), this, SLOT (quit ())); +} + +void +GuiApplication::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) +{ + if (mp_mw) { + + if (silent) { + mp_dm_scheduler->enable (false); + } + +#if QT_VERSION < 0x050000 + QApplication::syncX (); +#endif + + mp_mw->enter_busy_mode (true); + try { + QApplication::processEvents (flags); + } catch (...) { + // ignore exceptions + } + mp_mw->enter_busy_mode (false); + + if (silent) { + mp_dm_scheduler->enable (true); + } + + } +} + // -------------------------------------------------------------------------------- // NonGuiApplication implementation NonGuiApplication::NonGuiApplication (int &argc, char **argv) - : QCoreApplication (argc, argv), ApplicationBase () + : QCoreApplication (argc, argv), ApplicationBase (), + mp_pr (0), + mp_pb (0), + mp_plugin_root (0) { init_app (argc, argv, true); } +NonGuiApplication::~NonGuiApplication () +{ + // uninitialize the plugins + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + lay::PluginDeclaration *pd = const_cast (&*cls); + pd->uninitialize (plugin_root ()); + } + + shutdown (); +} + int NonGuiApplication::exec () { @@ -1485,5 +1544,35 @@ NonGuiApplication::exec () return 0; } +void +NonGuiApplication::shutdown () +{ + if (mp_plugin_root) { + delete mp_plugin_root; + mp_plugin_root = 0; + } + + if (mp_pr) { + delete mp_pr; + mp_pr = 0; + } + + if (mp_pb) { + delete mp_pb; + mp_pb = 0; + } + + ApplicationBase::shutdown (); +} + +void +NonGuiApplication::setup () +{ + mp_pr = new lay::ProgressReporter (); + mp_pb = new TextProgress (10 /*verbosity level*/); + mp_pr->set_progress_bar (mp_pb); + mp_plugin_root = new lay::PluginRoot (); +} + } diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index 98d98eb5a..87682c295 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -128,14 +128,12 @@ public: std::string usage (); /** - * @brief Return the main window's reference + * @brief Returns the main window's reference * - * If the application has not been initialized properly, this pointer is 0. + * If the application has not been initialized properly or does not support GUI, + * this pointer is 0. */ - MainWindow *main_window () const - { - return mp_mw; - } + virtual MainWindow *main_window () const = 0; /** * @brief Runs plugin and macro specific initializations @@ -164,7 +162,7 @@ public: * handling for the "close application window" case and a "silent" mode. In that mode, processing * of deferred methods is disabled. */ - void process_events (QEventLoop::ProcessEventsFlags flags, bool silent = false); + virtual void process_events (QEventLoop::ProcessEventsFlags flags, bool silent = false); /** * @brief A shortcut for the default process_events @@ -325,11 +323,6 @@ public: return m_native_plugins; } - /** - * @brief Gets the QCoreApplication object - */ - virtual QCoreApplication *qapp () { return 0; } - /** * @brief Gets the QApplication object * This method will return non-null only if a GUI-enabled application is present. @@ -338,10 +331,14 @@ public: protected: void init_app (int &argc, char **argv, bool non_ui_mode); + virtual void setup () = 0; + virtual void shutdown (); + virtual void prepare_recording (const std::string >f_record, bool gtf_record_incremental); + virtual void start_recording (); + virtual lay::PluginRoot *plugin_root () const = 0; + virtual void finish (); private: - void shutdown (); - void finish (); std::vector scan_global_modules (); enum file_type { @@ -366,10 +363,11 @@ private: std::vector m_klayout_path; std::string m_inst_path; std::string m_appdata_path; - std::vector< std::pair > m_macro_categories; bool m_write_config_file; std::vector< std::pair > m_variables; int m_gtf_replay_rate, m_gtf_replay_stop; + std::string m_gtf_record; + bool m_gtf_save_incremental; bool m_no_macros; bool m_same_view; bool m_sync_mode; @@ -377,16 +375,10 @@ private: bool m_vo_mode; bool m_editable; bool m_enable_undo; - 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; gsi::Interpreter *mp_python_interpreter; - MainWindow *mp_mw; - lay::ProgressReporter *mp_pr; - lay::ProgressBar *mp_pb; - lay::PluginRoot *mp_plugin_root; - gtf::Recorder *mp_recorder; std::vector m_native_plugins; }; @@ -398,9 +390,9 @@ class LAY_PUBLIC GuiApplication { public: GuiApplication (int &argc, char **argv); + ~GuiApplication (); QApplication *qapp_gui () { return this; } - QCoreApplication *qapp () { return this; } /** * @brief Reimplementation of notify from QApplication @@ -427,6 +419,33 @@ public: { ApplicationBase::exit (result); } + + /** + * @brief Returns the main window's reference + */ + virtual MainWindow *main_window () const + { + return mp_mw; + } + + /** + * @brief Reimplementation of ApplicationBase interface + */ + virtual void process_events (QEventLoop::ProcessEventsFlags flags, bool silent); + +protected: + virtual void setup (); + virtual void shutdown (); + virtual void finish (); + virtual void prepare_recording (const std::string >f_record, bool gtf_save_incremental); + virtual void start_recording (); + + virtual lay::PluginRoot *plugin_root () const; + +private: + MainWindow *mp_mw; + gtf::Recorder *mp_recorder; + std::auto_ptr mp_dm_scheduler; }; /** @@ -437,9 +456,7 @@ class LAY_PUBLIC NonGuiApplication { public: NonGuiApplication (int &argc, char **argv); - - QApplication *qapp_gui () { return 0; } - QCoreApplication *qapp () { return this; } + ~NonGuiApplication (); /** * @brief Gets the application instance, cast to this class @@ -461,6 +478,29 @@ public: { ApplicationBase::exit (result); } + + /** + * @brief Returns the main window's reference + * This incarnation returns 0 since no GUI is supported. + */ + virtual MainWindow *main_window () const + { + return 0; + } + +protected: + virtual void setup (); + virtual void shutdown (); + + virtual lay::PluginRoot *plugin_root () const + { + return mp_plugin_root; + } + +private: + lay::ProgressReporter *mp_pr; + lay::ProgressBar *mp_pb; + lay::PluginRoot *mp_plugin_root; }; } // namespace lay From c63a47dd899c8fe84b8b2e75dea26f78d6e35cf7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 27 Dec 2017 17:52:35 +0100 Subject: [PATCH 4/7] Added tests for klayout app This commit also contains some important fixes: * Option -wd wasn't working * Relative layout file paths in session files are resolved as relative to the session file. On writing, absolute paths are used, so this change only affects session files build intentionally. Plus: * Program version is available in unit tests too * Fixed a typo in the RBA::Expression documentation --- src/gsi/gsi/gsiDeclTl.cc | 4 +- src/klayout_main/klayout_main.pro | 22 +- .../{ => klayout_main}/klayout.cc | 0 .../{ => klayout_main}/klayout.rc | 0 .../klayout_main/klayout_main.pro | 22 ++ src/klayout_main/{ => klayout_main}/logo.ico | Bin src/klayout_main/tests/klayout_main_tests.cc | 51 +++++ src/klayout_main/tests/tests.pro | 16 ++ src/lay/lay/layApplication.cc | 3 +- src/lay/lay/laySession.cc | 12 +- src/lay/lay/laySession.h | 1 + src/unit_tests/unit_test_main.cc | 13 ++ testdata/klayout_main/main.rb | 148 ++++++++++++ testdata/klayout_main/session.lys | 214 ++++++++++++++++++ testdata/klayout_main/test.py | 3 + testdata/klayout_main/test.rb | 3 + testdata/klayout_main/test1.gds | Bin 0 -> 170 bytes testdata/klayout_main/test2.gds | Bin 0 -> 170 bytes testdata/klayout_main/test2.rb | 3 + testdata/klayout_main/test3.rb | 3 + testdata/klayout_main/test_app.rb | 4 + testdata/klayout_main/test_em.rb | 3 + testdata/klayout_main/test_epilogue.rb | 36 +++ testdata/klayout_main/test_lay.rb | 3 + testdata/klayout_main/test_lay2.rb | 8 + testdata/klayout_main/test_prologue.rb | 37 +++ testdata/klayout_main/test_read_config.rb | 3 + testdata/klayout_main/test_script.rb | 4 + testdata/klayout_main/test_set_config1.rb | 3 + testdata/klayout_main/test_set_config2.rb | 3 + 30 files changed, 597 insertions(+), 25 deletions(-) rename src/klayout_main/{ => klayout_main}/klayout.cc (100%) rename src/klayout_main/{ => klayout_main}/klayout.rc (100%) create mode 100644 src/klayout_main/klayout_main/klayout_main.pro rename src/klayout_main/{ => klayout_main}/logo.ico (100%) create mode 100644 src/klayout_main/tests/klayout_main_tests.cc create mode 100644 src/klayout_main/tests/tests.pro create mode 100644 testdata/klayout_main/main.rb create mode 100644 testdata/klayout_main/session.lys create mode 100644 testdata/klayout_main/test.py create mode 100644 testdata/klayout_main/test.rb create mode 100644 testdata/klayout_main/test1.gds create mode 100644 testdata/klayout_main/test2.gds create mode 100644 testdata/klayout_main/test2.rb create mode 100644 testdata/klayout_main/test3.rb create mode 100644 testdata/klayout_main/test_app.rb create mode 100644 testdata/klayout_main/test_em.rb create mode 100644 testdata/klayout_main/test_epilogue.rb create mode 100644 testdata/klayout_main/test_lay.rb create mode 100644 testdata/klayout_main/test_lay2.rb create mode 100644 testdata/klayout_main/test_prologue.rb create mode 100644 testdata/klayout_main/test_read_config.rb create mode 100644 testdata/klayout_main/test_script.rb create mode 100644 testdata/klayout_main/test_set_config1.rb create mode 100644 testdata/klayout_main/test_set_config2.rb diff --git a/src/gsi/gsi/gsiDeclTl.cc b/src/gsi/gsi/gsiDeclTl.cc index 90130a8f2..81e9af288 100644 --- a/src/gsi/gsi/gsiDeclTl.cc +++ b/src/gsi/gsi/gsiDeclTl.cc @@ -545,9 +545,7 @@ Class decl_ExpressionWrapper ("Expression", "@brief Evaluation of Expressions\n" "\n" "This class allows evaluation of expressions. Expressions are used in many places throughout KLayout and " - "provide computation features for various applications.\n" - "\n" - "This class allows evaluation of expressions. Having a script language, there is no real use for expressions " + "provide computation features for various applications. Having a script language, there is no real use for expressions " "inside a script client. This class is provided mainly for testing purposes.\n" "\n" "An expression is 'compiled' into an Expression object and can be evaluated multiple times.\n" diff --git a/src/klayout_main/klayout_main.pro b/src/klayout_main/klayout_main.pro index fb70da893..704e6f75e 100644 --- a/src/klayout_main/klayout_main.pro +++ b/src/klayout_main/klayout_main.pro @@ -1,22 +1,6 @@ -DESTDIR = $$OUT_PWD/.. +TEMPLATE = subdirs +SUBDIRS = klayout_main tests -include($$PWD/../klayout.pri) +tests.depends += klayout_main -TARGET = klayout - -include($$PWD/../app.pri) -include($$PWD/../with_all_libs.pri) - -HEADERS = \ - -FORMS = \ - -SOURCES = \ - klayout.cc \ - -RESOURCES = \ - -win32 { - RC_FILE = $$PWD/klayout.rc -} diff --git a/src/klayout_main/klayout.cc b/src/klayout_main/klayout_main/klayout.cc similarity index 100% rename from src/klayout_main/klayout.cc rename to src/klayout_main/klayout_main/klayout.cc diff --git a/src/klayout_main/klayout.rc b/src/klayout_main/klayout_main/klayout.rc similarity index 100% rename from src/klayout_main/klayout.rc rename to src/klayout_main/klayout_main/klayout.rc diff --git a/src/klayout_main/klayout_main/klayout_main.pro b/src/klayout_main/klayout_main/klayout_main.pro new file mode 100644 index 000000000..043cf01bb --- /dev/null +++ b/src/klayout_main/klayout_main/klayout_main.pro @@ -0,0 +1,22 @@ + +DESTDIR = $$OUT_PWD/../.. + +include($$PWD/../../klayout.pri) + +TARGET = klayout + +include($$PWD/../../app.pri) +include($$PWD/../../with_all_libs.pri) + +HEADERS = \ + +FORMS = \ + +SOURCES = \ + klayout.cc \ + +RESOURCES = \ + +win32 { + RC_FILE = $$PWD/klayout.rc +} diff --git a/src/klayout_main/logo.ico b/src/klayout_main/klayout_main/logo.ico similarity index 100% rename from src/klayout_main/logo.ico rename to src/klayout_main/klayout_main/logo.ico diff --git a/src/klayout_main/tests/klayout_main_tests.cc b/src/klayout_main/tests/klayout_main_tests.cc new file mode 100644 index 000000000..ca207ea7c --- /dev/null +++ b/src/klayout_main/tests/klayout_main_tests.cc @@ -0,0 +1,51 @@ + +/* + + 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 + +*/ + +// NOTE: klayout_main_tests is actually a Ruby test which does all test automation +// The tests will also test Python capabilities, so Python is required too. + +#if defined(HAVE_RUBY) && defined(HAVE_PYTHON) + +#include "rba.h" +#include "gsiDecl.h" + +// On Windows, ruby.h is not compatible with windows.h which is included by utHead - at least not if +// windows.h is included before ruby.h ... +#include "tlUnitTest.h" + +void run_rubytest (tl::TestBase * /*_this*/, const std::string &fn) +{ + tl_assert (rba::RubyInterpreter::instance ()); + + std::string fp (tl::testsrc ()); + fp += "/testdata/klayout_main/"; + fp += fn; + rba::RubyInterpreter::instance ()->load_file (fp.c_str ()); +} + +#define RUBYTEST(n, file) \ + TEST(n) { run_rubytest(_this, file); } + +RUBYTEST (main, "main.rb") + +#endif + diff --git a/src/klayout_main/tests/tests.pro b/src/klayout_main/tests/tests.pro new file mode 100644 index 000000000..c8132a05c --- /dev/null +++ b/src/klayout_main/tests/tests.pro @@ -0,0 +1,16 @@ + +DESTDIR_UT = $$OUT_PWD/../.. +DESTDIR = $$OUT_PWD/.. + +TARGET = klayout_main_tests + +include($$PWD/../../lib_ut.pri) + +SOURCES = \ + klayout_main_tests.cc + +INCLUDEPATH += $$RBA_INC $$TL_INC $$DB_INC $$GSI_INC +DEPENDPATH += $$RBA_INC $$TL_INC $$DB_INC $$GSI_INC + +LIBS += -L$$DESTDIR_UT -lklayout_rba -lklayout_tl -lklayout_db -lklayout_gsi + diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index 95ab3307a..b955c681c 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -442,7 +442,6 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) } else if (a == "-wd" && (i + 1) < argc) { - std::string v; const char *p = args [++i].c_str (); const char *n0 = p; while (*p && *p != '=') { @@ -450,7 +449,7 @@ ApplicationBase::init_app (int &argc, char **argv, bool non_ui_mode) } std::string n (n0, p - n0); if (*p == '=') { - tl::Eval::set_global_var (n, tl::Variant (v)); + tl::Eval::set_global_var (n, tl::Variant (p + 1)); } else { tl::Eval::set_global_var (n, tl::Variant (true)); } diff --git a/src/lay/lay/laySession.cc b/src/lay/lay/laySession.cc index b908efdfe..91da14a77 100644 --- a/src/lay/lay/laySession.cc +++ b/src/lay/lay/laySession.cc @@ -34,6 +34,7 @@ #include #include +#include namespace lay { @@ -161,10 +162,16 @@ Session::restore (lay::MainWindow &mw) std::map ::const_iterator ld = ld_by_name.find (cvd->layout_name); + std::string fp = ld->second->file_path; + QFileInfo fi (tl::to_qstring (fp)); + if (! m_base_dir.empty () && fi.isRelative ()) { + fp = tl::to_string (QDir (tl::to_qstring (m_base_dir)).filePath (tl::to_qstring (ld->second->file_path))); + } + bool ok = false; if (ld != ld_by_name.end ()) { try { - cv = view->load_layout (ld->second->file_path, ld->second->load_options, cvd->tech_name, true /*add*/); + cv = view->load_layout (fp, ld->second->load_options, cvd->tech_name, true /*add*/); view->cellview (cv)->set_save_options (ld->second->save_options, ld->second->save_options_valid); ok = true; } catch (...) { } @@ -297,6 +304,9 @@ session_structure ("session", void Session::load (const std::string &fn) { + // Take the path to the file as the base directory + m_base_dir = tl::to_string (QFileInfo (tl::to_qstring (fn)).absolutePath ()); + tl::XMLFileSource in (fn); session_structure.parse (in, *this); diff --git a/src/lay/lay/laySession.h b/src/lay/lay/laySession.h index 7238e5472..81818a1de 100644 --- a/src/lay/lay/laySession.h +++ b/src/lay/lay/laySession.h @@ -173,6 +173,7 @@ private: int m_current_view; std::string m_window_state; std::string m_window_geometry; + std::string m_base_dir; }; } diff --git a/src/unit_tests/unit_test_main.cc b/src/unit_tests/unit_test_main.cc index 8fc313ece..a1c7b5e05 100644 --- a/src/unit_tests/unit_test_main.cc +++ b/src/unit_tests/unit_test_main.cc @@ -30,11 +30,14 @@ #include "tlCommandLineParser.h" #include "layApplication.h" #include "laySystemPaths.h" +#include "layVersion.h" #include "rba.h" #include "pya.h" #include "gsiDecl.h" #include "gsiExternalMain.h" +#include "version.h" + #include #include #include @@ -320,6 +323,16 @@ run_tests (const std::vector &selected_tests, bool editable, boo static int main_cont (int &argc, char **argv) { + // install the version strings + lay::Version::set_exe_name (prg_exe_name); + lay::Version::set_name (prg_name); + lay::Version::set_version (prg_version); + + std::string subversion (prg_date); + subversion += " r"; + subversion += prg_rev; + lay::Version::set_subversion (subversion.c_str ()); + int result = 0; ut::TestConsole console (stdout); diff --git a/testdata/klayout_main/main.rb b/testdata/klayout_main/main.rb new file mode 100644 index 000000000..51c1e1ce9 --- /dev/null +++ b/testdata/klayout_main/main.rb @@ -0,0 +1,148 @@ +# encoding: UTF-8 + +# 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 + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +# Tests for the klayout executable +# +# This tests actually runs inside a KLayout/unit_tests instance but +# only for providing the test automation. + +class KLayoutMain_TestClass < TestBase + + def test_1 + + # Basic + version = `./klayout -v` + assert_equal(version, "#{RBA::Application.instance.version}\n") + + end + + def test_2 + + # Basic Ruby + out = `./klayout -b -rd v1=42 -rd v2=hello -r #{File.join(File.dirname(__FILE__), "test.rb")}` + assert_equal(out, "Variable v1=42 v2=hello\n") + + out = `./klayout -b -rd v1=42 -rd v2=hello -r #{File.join(File.dirname(__FILE__), "test.rb")} -rm #{File.join(File.dirname(__FILE__), "test2.rb")} -rm #{File.join(File.dirname(__FILE__), "test3.rb")}` + assert_equal(out, "test2\ntest3\nVariable v1=42 v2=hello\n") + + end + + def test_3 + + # Basic Python + out = `./klayout -b -rd v1=42 -rd v2=hello -r #{File.join(File.dirname(__FILE__), "test.py")}` + assert_equal(out, "Variable v1=42 v2=hello\n") + + end + + def test_4 + + # Application class + + # QCoreApplication for (headless) mode + out = `./klayout -b -r #{File.join(File.dirname(__FILE__), "test_app.rb")}` + assert_equal(out, "RBA::Application superclass RBA::QCoreApplication_Native\nMainWindow is not there\n") + + # QApplication for GUI mode + out = `./klayout -z -nc -rx -r #{File.join(File.dirname(__FILE__), "test_app.rb")}` + assert_equal(out, "RBA::Application superclass RBA::QApplication_Native\nMainWindow is there\n") + + end + + def test_5 + + # Script variables + out = `./klayout -b -wd tv1=17 -wd tv2=25 -wd tv3 -r #{File.join(File.dirname(__FILE__), "test_script.rb")}` + assert_equal(out, "42\ntrue\n") + + end + + def test_6 + + # Editable / Non-editable mode + out = `./klayout -b -ne -r #{File.join(File.dirname(__FILE__), "test_em.rb")}` + assert_equal(out, "false\n") + + out = `./klayout -b -e -r #{File.join(File.dirname(__FILE__), "test_em.rb")}` + assert_equal(out, "true\n") + + end + + def test_7 + + cfg_file = File.join($ut_testtmp, "config.xml") + File.open(cfg_file, "w") { |file| file.puts("") } + + # Special configuration file + `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_set_config1.rb")}` + + out = `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_read_config.rb")}` + assert_equal(out, "42\n") + + # Update + `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_set_config2.rb")}` + + out = `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_read_config.rb")}` + assert_equal(out, "17\n") + + # Reset + `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_set_config1.rb")}` + + out = `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_read_config.rb")}` + assert_equal(out, "42\n") + + # No update + `./klayout -b -t -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_set_config2.rb")}` + + out = `./klayout -b -c #{cfg_file} -r #{File.join(File.dirname(__FILE__), "test_read_config.rb")}` + assert_equal(out, "42\n") + + end + + def test_8 + + # Loading layouts + out = `./klayout -z -nc -rx #{File.join(File.dirname(__FILE__), "test1.gds")} -r #{File.join(File.dirname(__FILE__), "test_lay.rb")}` + assert_equal(out, "TOP1\n") + + out = `./klayout -z -nc -rx #{File.join(File.dirname(__FILE__), "test1.gds")} #{File.join(File.dirname(__FILE__), "test2.gds")} -r #{File.join(File.dirname(__FILE__), "test_lay2.rb")}` + assert_equal(out, "TOP1\nTOP2\n") + + out = `./klayout -z -nc -rx #{File.join(File.dirname(__FILE__), "test1.gds")} #{File.join(File.dirname(__FILE__), "test2.gds")} -s -r #{File.join(File.dirname(__FILE__), "test_lay2.rb")}` + assert_equal(out, "TOP1;TOP2\n") + + end + + def test_9 + + # Sessions + out = `./klayout -z -nc -rx -u #{File.join(File.dirname(__FILE__), "session.lys")} -r #{File.join(File.dirname(__FILE__), "test_lay2.rb")}` + assert_equal(out, "TOP2;TOP1\n") + + end + +end + +load("test_epilogue.rb") diff --git a/testdata/klayout_main/session.lys b/testdata/klayout_main/session.lys new file mode 100644 index 000000000..73ac6c473 --- /dev/null +++ b/testdata/klayout_main/session.lys @@ -0,0 +1,214 @@ + + + 1392 + 912 + AAAA/wAAAAD9AAAAAgAAAAAAAAC5AAADLvwCAAAAAvsAAAAqAG4AYQB2AGkAZwBhAHQAbwByAF8AZABvAGMAawBfAHcAaQBkAGcAZQB0AAAAAAD/////AAAAkgD////7AAAAHABoAHAAXwBkAG8AYwBrAF8AdwBpAGQAZwBlAHQBAAAATAAAAy4AAAAZAP///wAAAAEAAAEdAAADLvwCAAAAAvsAAAAcAGwAcABfAGQAbwBjAGsAXwB3AGkAZABnAGUAdAEAAABMAAACnQAAABkA////+wAAABwAbAB0AF8AZABvAGMAawBfAHcAaQBkAGcAZQB0AQAAAu8AAACLAAAAiwAAAIsAAAOOAAADLgAAAAQAAAAEAAAACAAAAAj8AAAAAQAAAAIAAAABAAAADgB0AG8AbwBsAGIAYQByAQAAAAD/////AAAAAAAAAAA= + AdnQywABAAAAAAEpAAAAFQAABq4AAAPSAAABNAAAADgAAAajAAADxwAAAAAAAA== + 0 + + test1.gds + test1.gds + false + + + 2 + false + false + 1 + * + false + + + true + false + false + false + false + 8000 + 32000 + LIB + + + 0 + + + false + false + + + + + true + layer_map() + true + true + + + 1 + true + true + + + 0.001 + 1 + 100 + 100 + 0 + 0 + false + false + true + layer_map() + + + 0 + 0.001 + layer_map() + true + + + + + test2.gds + test2.gds + true + + + 2 + false + false + 1 + * + false + + + true + false + false + false + false + 8000 + 32000 + LIB + + + 0 + + + false + false + + + + + true + layer_map() + true + true + + + 1 + true + true + + + 0.001 + 1 + 100 + 100 + 0 + 0 + false + false + true + layer_map() + + + 0 + 0.001 + layer_map() + true + + + + + + <active-cellview-index>1</active-cellview-index> + <display> + <x-left>-0.892701664533</x-left> + <x-right>8.8947503201</x-right> + <y-bottom>-0.204353393086</y-bottom> + <y-top>8.19564660691</y-top> + <min-hier>0</min-hier> + <max-hier>1</max-hier> + <cellpaths> + <cellpath> + <cellname>TOP2</cellname> + </cellpath> + <cellpath> + <cellname>TOP1</cellname> + </cellpath> + </cellpaths> + </display> + <cellviews> + <cellview> + <layout-ref>test2.gds</layout-ref> + <tech-name/> + <hidden-cells> + </hidden-cells> + </cellview> + <cellview> + <layout-ref>test1.gds</layout-ref> + <tech-name/> + <hidden-cells> + </hidden-cells> + </cellview> + </cellviews> + <bookmarks> + </bookmarks> + <rdb-files> + </rdb-files> + <current-layer-property-tab>0</current-layer-property-tab> + <layer-properties-tabs> + <layer-properties> + <properties> + <frame-color>#ff80a8</frame-color> + <fill-color>#ff80a8</fill-color> + <frame-brightness>0</frame-brightness> + <fill-brightness>0</fill-brightness> + <dither-pattern>I9</dither-pattern> + <line-style/> + <valid>true</valid> + <visible>true</visible> + <transparent>false</transparent> + <width>1</width> + <marked>false</marked> + <xfill>false</xfill> + <animation>0</animation> + <name/> + <source>2/0@1</source> + </properties> + <properties> + <frame-color>#ff80a8</frame-color> + <fill-color>#ff80a8</fill-color> + <frame-brightness>0</frame-brightness> + <fill-brightness>0</fill-brightness> + <dither-pattern>I9</dither-pattern> + <line-style/> + <valid>true</valid> + <visible>true</visible> + <transparent>false</transparent> + <width/> + <marked>false</marked> + <xfill>false</xfill> + <animation>0</animation> + <name/> + <source>1/0@2</source> + </properties> + <name/> + </layer-properties> + </layer-properties-tabs> + <annotations> + </annotations> + </view> +</session> diff --git a/testdata/klayout_main/test.py b/testdata/klayout_main/test.py new file mode 100644 index 000000000..3870f2bcd --- /dev/null +++ b/testdata/klayout_main/test.py @@ -0,0 +1,3 @@ + +print("Variable v1="+str(v1)+" v2="+str(v2)) + diff --git a/testdata/klayout_main/test.rb b/testdata/klayout_main/test.rb new file mode 100644 index 000000000..8cf0250ed --- /dev/null +++ b/testdata/klayout_main/test.rb @@ -0,0 +1,3 @@ + +puts "Variable v1=#{$v1} v2=#{$v2}" + diff --git a/testdata/klayout_main/test1.gds b/testdata/klayout_main/test1.gds new file mode 100644 index 0000000000000000000000000000000000000000..7cfbe49107ec4eeb1ce3efaaa5664e155cd109d1 GIT binary patch literal 170 zcmZQzV_;&6V31*CVt>fM!ywHd$RNaEg3M;%U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^P4=`k4;b353<7HxCWMJcCVqjp<5nu+ANI>3!fdLsKNwEkrFtD%# HF$)6#f9ny) literal 0 HcmV?d00001 diff --git a/testdata/klayout_main/test2.gds b/testdata/klayout_main/test2.gds new file mode 100644 index 0000000000000000000000000000000000000000..93410146069c624053808507794a9f405f81030e GIT binary patch literal 170 zcmZQzV_;&6V31*CVt>fM!ywHd$RNxhjLc@>U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^P4=`e2;b353<7HxCVqoKAVqjp<5nu+ANI>3!fdLsKNwEkrFtD%# HF$)6#M*9&- literal 0 HcmV?d00001 diff --git a/testdata/klayout_main/test2.rb b/testdata/klayout_main/test2.rb new file mode 100644 index 000000000..b59aa43c1 --- /dev/null +++ b/testdata/klayout_main/test2.rb @@ -0,0 +1,3 @@ + +puts "test2" + diff --git a/testdata/klayout_main/test3.rb b/testdata/klayout_main/test3.rb new file mode 100644 index 000000000..217bb2326 --- /dev/null +++ b/testdata/klayout_main/test3.rb @@ -0,0 +1,3 @@ + +puts "test3" + diff --git a/testdata/klayout_main/test_app.rb b/testdata/klayout_main/test_app.rb new file mode 100644 index 000000000..768fcf107 --- /dev/null +++ b/testdata/klayout_main/test_app.rb @@ -0,0 +1,4 @@ + +puts "RBA::Application superclass " + RBA::Application.instance.class.superclass.to_s +puts "MainWindow is " + (RBA::Application.instance.main_window ? "there" : "not there") + diff --git a/testdata/klayout_main/test_em.rb b/testdata/klayout_main/test_em.rb new file mode 100644 index 000000000..10768b131 --- /dev/null +++ b/testdata/klayout_main/test_em.rb @@ -0,0 +1,3 @@ + +puts RBA::Application.instance.is_editable?.inspect + diff --git a/testdata/klayout_main/test_epilogue.rb b/testdata/klayout_main/test_epilogue.rb new file mode 100644 index 000000000..f0f5bacc3 --- /dev/null +++ b/testdata/klayout_main/test_epilogue.rb @@ -0,0 +1,36 @@ + +# In the test environment, we cannot make sure that we destroy the ruby interpreter before the RBA +# environment is shut down. Therefore we must release all RBA objects by explicitly calling the GC +# and start the test suite manually. + +err = 0 +any = nil +repeat = (ENV["TESTREPEAT"] || "1").to_i + +class MyTestRunner < Test::Unit::UI::Console::TestRunner + def initialize(suite, *args) + super(suite, *args) + end + def test_started(name) + super + end +end + +Object.constants.each do |c| + if c.to_s =~ /_TestClass$/ + repeat.times do + r = MyTestRunner::new(Object.const_get(c)).start + err += r.error_count + r.failure_count + end + any = true + end +end + +if !any + raise("No test class defined (any ending with _TestClass)") +end + +if err > 0 + raise("Tests failed (#{err} Errors + Failures)") +end + diff --git a/testdata/klayout_main/test_lay.rb b/testdata/klayout_main/test_lay.rb new file mode 100644 index 000000000..f30037a65 --- /dev/null +++ b/testdata/klayout_main/test_lay.rb @@ -0,0 +1,3 @@ + +puts RBA::LayoutView::current.active_cellview.layout.top_cell.name + diff --git a/testdata/klayout_main/test_lay2.rb b/testdata/klayout_main/test_lay2.rb new file mode 100644 index 000000000..9208a8090 --- /dev/null +++ b/testdata/klayout_main/test_lay2.rb @@ -0,0 +1,8 @@ + +RBA::Application::instance.main_window.views.times do |v| + view = RBA::Application::instance.main_window.view(v) + tc = [] + view.cellviews.times { |cv| tc << view.cellview(cv).layout.top_cell.name } + puts tc.join(";") +end + diff --git a/testdata/klayout_main/test_prologue.rb b/testdata/klayout_main/test_prologue.rb new file mode 100644 index 000000000..4dcd70124 --- /dev/null +++ b/testdata/klayout_main/test_prologue.rb @@ -0,0 +1,37 @@ + +# in MSVC environment: +if ENV["RUBY"] + ruby_libs = "#{ENV["RUBY"]}/lib/ruby/#{RUBY_VERSION}" + if !$:.member?(ruby_libs) + $:.push(ruby_libs) + end +end + +# Set this to true to disable some tests involving exceptions +$leak_check = ENV["TEST_LEAK_CHECK"] + +# Fetch location of source files and the temp files +$ut_testsrc = ENV["TESTSRC"] || raise("Environment variable $TESTSRC not set") +$ut_testtmp = ENV["TESTTMP_WITH_NAME"] || ENV["TESTTMP"] || raise("Environment variable $TESTTMP not set") + +# Pull packages from vendor drop-in +vendor = File.join($ut_testsrc, "testdata", "vendor", "ruby") +if !$:.member?(vendor) + $:.unshift(vendor) +end + +# Require Test::Unit +require 'test/unit/ui/console/testrunner' + +# TestBase is an alias for "TestCase" +Object.const_defined?(:TestBase) && Object.send(:remove_const, :TestBase) +TestBase = Test::Unit::TestCase + +# undefine existing classes + +Object.constants.each do |c| + if c.to_s =~ /_TestClass$/ + Object.send(:remove_const, c) + end +end + diff --git a/testdata/klayout_main/test_read_config.rb b/testdata/klayout_main/test_read_config.rb new file mode 100644 index 000000000..58f8a9fc0 --- /dev/null +++ b/testdata/klayout_main/test_read_config.rb @@ -0,0 +1,3 @@ + +puts RBA::Application.instance.get_config("key4test") + diff --git a/testdata/klayout_main/test_script.rb b/testdata/klayout_main/test_script.rb new file mode 100644 index 000000000..d5f30132a --- /dev/null +++ b/testdata/klayout_main/test_script.rb @@ -0,0 +1,4 @@ + +puts RBA::Expression::new("to_i(tv1)+to_i(tv2)").eval +puts RBA::Expression::new("tv3").eval + diff --git a/testdata/klayout_main/test_set_config1.rb b/testdata/klayout_main/test_set_config1.rb new file mode 100644 index 000000000..df1e4dacf --- /dev/null +++ b/testdata/klayout_main/test_set_config1.rb @@ -0,0 +1,3 @@ + +RBA::Application.instance.set_config("key4test", "42") + diff --git a/testdata/klayout_main/test_set_config2.rb b/testdata/klayout_main/test_set_config2.rb new file mode 100644 index 000000000..d0099b5cf --- /dev/null +++ b/testdata/klayout_main/test_set_config2.rb @@ -0,0 +1,3 @@ + +RBA::Application.instance.set_config("key4test", "17") + From 7a28389bf180df456bea7a33b90a3c31e236e1d9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein <matthias@koefferlein.de> Date: Wed, 27 Dec 2017 18:27:18 +0100 Subject: [PATCH 5/7] Fixed Windows build. --- src/lay/lay/laySignalHandler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lay/lay/laySignalHandler.cc b/src/lay/lay/laySignalHandler.cc index 549522476..852efdfc4 100644 --- a/src/lay/lay/laySignalHandler.cc +++ b/src/lay/lay/laySignalHandler.cc @@ -216,7 +216,7 @@ LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) SymCleanup (process); - bool has_gui = lay::Application::instance () && lay::Application::instance ()->has_gui (); + bool has_gui = lay::ApplicationBase::instance () && lay::ApplicationBase::instance ()->has_gui (); if (has_gui) { // YES! I! KNOW! From 39d635c7d76533c1f2d6fb57103c9d3cf0d84395 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein <matthias@koefferlein.de> Date: Wed, 27 Dec 2017 19:40:29 +0100 Subject: [PATCH 6/7] Fixed rba basic testsuite when both modes are used. --- testdata/ruby/basic_testcore.rb | 79 ++-------------------------- testdata/ruby/basic_testcore_defs.rb | 78 +++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 76 deletions(-) create mode 100644 testdata/ruby/basic_testcore_defs.rb diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index 6989bffb0..d671e5299 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -1,81 +1,8 @@ # encoding: UTF-8 -# extend A -class RBA::A - - alias_method :org_initialize, :initialize - - def initialize(*args) - org_initialize(*args) - @offset = nil - end - def s( o ) - @offset = o - end - def g - return @offset - end - def m - return @offset+a1 - end - def call_a10_prot(f) - a10_prot(f) - end - def inspect - if @offset - @offset.to_s - else - "a1=" + self.a1.to_s - end - end - -private - @offset - -end - -class MyException < RuntimeError - def initialize(s) - super(s) - end -end - -class XEdge < RBA::Edge - def initialize - super(RBA::Point.new(1,2), RBA::Point.new(3,4)) - end -end - -class RBA::E - def m - @m - end - def m=(x) - @m = x - end - @m = nil -end - -class RBAGObject < RBA::GObject - def initialize(z) - super() - @z = z - end - # reimplementation of "virtual int g()" - def g - return @z*2 - end -end - -class RBAGFactory < RBA::GFactory - def initialize - super() - end - # reimplementation of "virtual GObject *f(int)" - def f(z) - return RBAGObject::new(z) - end -end +# NOTE: we need to do a "require" here since basic_testcore_defs.rb is not +# safe in multiple inclusions +require File.expand_path('../basic_testcore_defs', __FILE__) class Basic_TestClass < TestBase diff --git a/testdata/ruby/basic_testcore_defs.rb b/testdata/ruby/basic_testcore_defs.rb new file mode 100644 index 000000000..fb896a71c --- /dev/null +++ b/testdata/ruby/basic_testcore_defs.rb @@ -0,0 +1,78 @@ + +# extend A +class RBA::A + + alias_method :org_initialize, :initialize + + def initialize(*args) + org_initialize(*args) + @offset = nil + end + def s( o ) + @offset = o + end + def g + return @offset + end + def m + return @offset+a1 + end + def call_a10_prot(f) + a10_prot(f) + end + def inspect + if @offset + @offset.to_s + else + "a1=" + self.a1.to_s + end + end + +private + @offset + +end + +class MyException < RuntimeError + def initialize(s) + super(s) + end +end + +class XEdge < RBA::Edge + def initialize + super(RBA::Point.new(1,2), RBA::Point.new(3,4)) + end +end + +class RBA::E + def m + @m + end + def m=(x) + @m = x + end + @m = nil +end + +class RBAGObject < RBA::GObject + def initialize(z) + super() + @z = z + end + # reimplementation of "virtual int g()" + def g + return @z*2 + end +end + +class RBAGFactory < RBA::GFactory + def initialize + super() + end + # reimplementation of "virtual GObject *f(int)" + def f(z) + return RBAGObject::new(z) + end +end + From 84cf46bcd2298c3a56d226b7110d98746086ed80 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein <matthias@koefferlein.de> Date: Wed, 27 Dec 2017 19:54:06 +0100 Subject: [PATCH 7/7] Fixed an issue with the new process_events implementation: compiler resolution did not work well for the two types - one them them reimplemented and the other not. --- src/lay/lay/layApplication.cc | 4 ++-- src/lay/lay/layApplication.h | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index b955c681c..b69552d4e 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -1252,7 +1252,7 @@ dump_children (QObject *obj, int level = 0) } void -ApplicationBase::process_events (QEventLoop::ProcessEventsFlags /*flags*/, bool /*silent*/) +ApplicationBase::process_events_impl (QEventLoop::ProcessEventsFlags /*flags*/, bool /*silent*/) { // The base class implementation does nothing .. } @@ -1486,7 +1486,7 @@ GuiApplication::setup () } void -GuiApplication::process_events (QEventLoop::ProcessEventsFlags flags, bool silent) +GuiApplication::process_events_impl (QEventLoop::ProcessEventsFlags flags, bool silent) { if (mp_mw) { diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index 87682c295..25feef38e 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -162,14 +162,17 @@ public: * handling for the "close application window" case and a "silent" mode. In that mode, processing * of deferred methods is disabled. */ - virtual void process_events (QEventLoop::ProcessEventsFlags flags, bool silent = false); + void process_events (QEventLoop::ProcessEventsFlags flags, bool silent = false) + { + process_events_impl (flags, silent); + } /** * @brief A shortcut for the default process_events */ void process_events () { - process_events (QEventLoop::AllEvents); + process_events_impl (QEventLoop::AllEvents); } /** @@ -337,6 +340,7 @@ protected: virtual void start_recording (); virtual lay::PluginRoot *plugin_root () const = 0; virtual void finish (); + virtual void process_events_impl (QEventLoop::ProcessEventsFlags flags, bool silent = false); private: std::vector<std::string> scan_global_modules (); @@ -428,17 +432,13 @@ public: return mp_mw; } - /** - * @brief Reimplementation of ApplicationBase interface - */ - virtual void process_events (QEventLoop::ProcessEventsFlags flags, bool silent); - protected: virtual void setup (); virtual void shutdown (); virtual void finish (); virtual void prepare_recording (const std::string >f_record, bool gtf_save_incremental); virtual void start_recording (); + virtual void process_events_impl (QEventLoop::ProcessEventsFlags flags, bool silent); virtual lay::PluginRoot *plugin_root () const;