klayout/src/lay/lay/layApplication.cc

1897 lines
57 KiB
C++

/*
KLayout Layout Viewer
Copyright (C) 2006-2026 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "layApplication.h"
#include "laySignalHandler.h"
#include "laybasicConfig.h"
#include "layConfig.h"
#include "layMainWindow.h"
#include "layMacroEditorDialog.h"
#include "layNativePlugin.h"
#include "layVersion.h"
#include "laySignalHandler.h"
#include "layRuntimeErrorForm.h"
#include "layReaderErrorForm.h"
#include "layProgress.h"
#include "layTextProgress.h"
#include "layBackgroundAwareTreeStyle.h"
#include "layMacroController.h"
#include "layTechnologyController.h"
#include "laySaltController.h"
#include "laySystemPaths.h"
#include "layPasswordDialog.h"
#include "layInit.h"
#include "lymMacro.h"
#include "gtf.h"
#include "gsiDecl.h"
#include "gsiInterpreter.h"
#include "rba.h"
#include "pya.h"
#include "rdb.h"
#include "dbStatic.h"
#include "dbLibrary.h"
#include "dbLibraryManager.h"
#include "dbInit.h"
#include "dbLayoutToNetlist.h"
#include "tlExceptions.h"
#include "tlException.h"
#include "tlAssert.h"
#include "tlLog.h"
#include "tlString.h"
#include "tlExpression.h"
#include "tlExceptions.h"
#include "tlInternational.h"
#include "tlHttpStream.h"
#include "tlArch.h"
#include "tlFileUtils.h"
#include "tlEnv.h"
#include <QIcon>
#include <QDir>
#include <QFileInfo>
#include <QFile>
#include <QAction>
#include <QMessageBox>
#include <iostream>
#include <memory>
#include <algorithm>
#ifdef _WIN32
# include <windows.h>
#else
# include <dlfcn.h>
#endif
namespace gsi
{
void make_application_decl (bool non_gui_mode);
}
namespace lay
{
// --------------------------------------------------------------------------------
// Exception handlers
static void close_transaction ()
{
// if any transaction is pending (this may happen when an operation threw an exception)
// close transactions.
// NOTE: don't do this in breakpoint mode as we do not want to interfere with things happening outside
if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting () &&
!(lay::MacroEditorDialog::instance () && lay::MacroEditorDialog::instance ()->in_breakpoint ())) {
lay::MainWindow::instance ()->manager ().commit ();
}
}
static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent)
{
// Prevents severe side effects if there are pending deferred methods
tl::NoDeferredMethods silent;
close_transaction ();
const tl::ExitException *gsi_exit = dynamic_cast <const tl::ExitException *> (&ex);
const tl::BreakException *gsi_break = dynamic_cast <const tl::BreakException *> (&ex);
const tl::ScriptError *gsi_excpt = dynamic_cast <const tl::ScriptError *> (&ex);
const db::ReaderUnknownFormatException *reader_excpt = dynamic_cast <const db::ReaderUnknownFormatException *> (&ex);
if (gsi_exit || gsi_break) {
// exit and break exceptions are not shown - they are issued when a script is aborted or
// an operation is cancelled
} else if (gsi_excpt) {
// show and GSI errors in the context of the macro editor if that is open
if (! parent && lay::MacroEditorDialog::instance () && lay::MacroEditorDialog::instance ()->isVisible ()) {
parent = lay::MacroEditorDialog::instance ();
}
if (! parent) {
parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance ();
}
if (gsi_excpt->line () > 0) {
tl::error << gsi_excpt->sourcefile () << ":" << gsi_excpt->line () << ": "
<< gsi_excpt->msg ();
} else {
tl::error << gsi_excpt->msg ();
}
lay::RuntimeErrorForm error_dialog (parent, "ruby_error_form", gsi_excpt);
error_dialog.exec ();
} else {
tl::error << ex.msg ();
if (! parent) {
parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance ();
}
if (reader_excpt) {
lay::ReaderErrorForm error_dialog (parent, "reader_error_form", reader_excpt);
error_dialog.exec ();
} else {
QMessageBox::critical (parent, QObject::tr ("Error"), tl::to_qstring (ex.msg ()));
}
}
}
static void ui_exception_handler_std (const std::exception &ex, QWidget *parent)
{
// Prevents severe side effects if there are pending deferred methods
tl::NoDeferredMethods silent;
close_transaction ();
tl::error << ex.what ();
if (! parent) {
parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance ();
}
QMessageBox::critical (parent, QObject::tr ("Error"), tl::to_qstring (ex.what ()));
}
static void ui_exception_handler_def (QWidget *parent)
{
// Prevents severe side effects if there are pending deferred methods
tl::NoDeferredMethods silent;
close_transaction ();
if (! parent) {
parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance ();
}
QMessageBox::critical (parent, QObject::tr ("Error"), QObject::tr ("An unspecific error occurred"));
}
// --------------------------------------------------------------------------------
static ApplicationBase *ms_instance = 0;
// --------------------------------------------------------------------------------
// ApplicationBase implementation
ApplicationBase::ApplicationBase (bool non_ui_mode)
: gsi::ObjectBase (),
m_lyp_map_all_cvs (true),
m_lyp_add_default (false),
m_run_macro_and_exit (true),
m_packages_with_dep (false),
m_write_config_file (false),
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),
m_no_gui (non_ui_mode),
m_vo_mode (false),
m_editable (false),
m_editable_set (false),
m_enable_undo (true),
mp_ruby_interpreter (0),
mp_python_interpreter (0)
{
// TODO: offer a strict mode for exception handling where this takes place:
// lay::ApplicationBase::instance ()->exit (1);
if (! m_no_gui) {
tl::set_ui_exception_handlers (ui_exception_handler_tl, ui_exception_handler_std, ui_exception_handler_def);
}
gsi::make_application_decl (m_no_gui);
// initialize the system codecs (Hint: this must be done after the QApplication is initialized because
// it will call setlocale)
tl::initialize_codecs ();
tl_assert (ms_instance == 0);
ms_instance = this;
// get and create the klayout appdata folder if required
m_appdata_path = lay::get_appdata_path ();
// get the installation path
m_inst_path = tl::get_inst_path ();
}
void
ApplicationBase::parse_cmd (int &argc, char **argv)
{
// get the KLayout path
m_klayout_path = lay::get_klayout_path ();
// by default write the configuration
m_write_config_file = true;
if (! m_no_gui) {
// create the configuration files paths and collect the initialization config files
// (the ones used for reset) into m_initial_config_files.
{
// Fallback to ~/.layviewrc for backward compatibility
QDir qd (QDir::home ());
QString filename = QString::fromUtf8 (".layviewrc");
if (qd.exists (filename) && QFileInfo (qd.absoluteFilePath (filename)).isReadable ()) {
m_config_files.push_back (tl::to_string (qd.absoluteFilePath (filename)));
m_config_file_to_delete = m_config_files.back ();
}
}
m_config_file_to_write.clear ();
if (! m_appdata_path.empty ()) {
m_config_file_to_write = tl::to_string (QDir (tl::to_qstring (m_appdata_path)).absoluteFilePath (QString::fromUtf8 ("klayoutrc")));
}
// Hint: the order is reverse in the sense that the first one wins ...
for (std::vector <std::string>::const_iterator p = m_klayout_path.end (); p != m_klayout_path.begin (); ) {
--p;
QDir qd (tl::to_qstring (*p));
QString filename = QString::fromUtf8 ("klayoutrc");
if (qd.exists (filename) && QFileInfo (qd.absoluteFilePath (filename)).isReadable ()) {
m_config_files.push_back (tl::to_string (qd.absoluteFilePath (filename)));
if (m_config_files.back () != m_config_file_to_write) {
m_initial_config_files.push_back (m_config_files.back ());
}
}
}
}
// transscribe the arguments to UTF8
std::vector<std::string> args;
args.reserve (argc);
for (int i = 0; i < argc; ++i) {
args.push_back (argv [i]);
}
#if defined(KLAYOUT_VIEWER_ONLY)
// viewer-only mode compiled in
m_vo_mode = true;
#else
// determine viewer-only mode from executable name. "klayout_vo*" will enable
// viewer-only mode
std::string vo_exe_name (std::string (lay::Version::exe_name ()) + "_vo");
if (! args.empty () && std::string (tl::to_string (QFileInfo (tl::to_qstring (args.front ())).fileName ()), 0, vo_exe_name.size ()) == vo_exe_name) {
m_vo_mode = true;
}
#endif
m_no_macros = false;
// currently: technology is always set to make "default" technology the default
bool tech_set = true;
std::string tech;
std::string tech_file;
for (int i = 1; i < argc; ++i) {
const std::string &a = args [i];
if (a == "-d" && (i + 1) < argc) {
int v = 0;
tl::from_string (args [++i], v);
tl::verbosity (v);
} else if (a == "-k" && (i + 1) < argc) {
// ignored (handled earlier)
++i;
} else if (a == "-l" && (i + 1) < argc) {
m_layer_props_file = args [++i];
} else if (a == "-lx") {
m_lyp_add_default = true;
} else if (a == "-lf") {
m_lyp_map_all_cvs = false;
} else if (a == "-u" && (i + 1) < argc) {
m_session_file = args [++i];
} else if (a == "-wd" && (i + 1) < argc) {
const char *p = args [++i].c_str ();
const char *n0 = p;
while (*p && *p != '=') {
++p;
}
std::string n (n0, p - n0);
if (*p == '=') {
tl::Eval::set_global_var (n, tl::Variant (p + 1));
} else {
tl::Eval::set_global_var (n, tl::Variant (true));
}
} else if (a == "-rd" && (i + 1) < argc) {
std::string v;
const char *p = args [++i].c_str ();
const char *n0 = p;
while (*p && *p != '=') {
++p;
}
std::string n (n0, p - n0);
m_variables.push_back (std::make_pair (n, v));
if (*p == '=') {
m_variables.back ().second = ++p;
}
} else if (a == "-rm" && (i + 1) < argc) {
m_load_macros.push_back (args [++i]);
} else if (a == "-r" && (i + 1) < argc) {
if (! m_run_macro.empty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Option -r or -rr can only be used once")));
}
m_run_macro = args [++i];
} else if (a == "-rr" && (i + 1) < argc) {
if (! m_run_macro.empty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Option -r or -rr can only be used once")));
}
m_run_macro = args [++i];
m_run_macro_and_exit = false;
} else if (a == "-rx") {
m_no_macros = true;
} else if (a == "-gr" && (i + 1) < argc) {
m_gtf_record = args [++i];
} else if (a == "-gi") {
m_gtf_save_incremental = true;
} else if (a == "-gp" && (i + 1) < argc) {
m_gtf_replay = args [++i];
// test mode replay forces sync mode to true
m_sync_mode = true;
} else if (a == "-gx" && (i + 1) < argc) {
int r = 0;
tl::from_string (args [++i], r);
m_gtf_replay_rate = r > 0 ? r : 0;
} else if (a == "-gb" && (i + 1) < argc) {
int s = 0;
tl::from_string (args [++i], s);
m_gtf_replay_stop = s > 0 ? s : 0;
} else if (a == "-c" && (i + 1) < argc) {
m_config_files.clear ();
m_config_files.push_back (args [++i]);
m_initial_config_files = m_config_files;
m_config_file_to_write = m_config_files.back ();
} else if (a == "-nc") {
m_config_files.clear ();
m_initial_config_files = m_config_files;
m_config_file_to_write.clear ();
} else if (a == "-n" && (i + 1) < argc) {
tech = args [++i];
tech_file.clear ();
tech_set = true;
} else if (a == "-nn" && (i + 1) < argc) {
tech_file = args [++i];
tech.clear ();
tech_set = true;
} else if (a == "-p" && (i + 1) < argc) {
lay::load_plugin (args [++i]);
} else if (a == "-s") {
m_same_view = true;
} else if (a == "-e") {
m_editable = ! m_vo_mode;
m_editable_set = true;
} else if (a == "-ne") {
m_editable = false;
m_editable_set = true;
} else if (a == "-i") {
m_enable_undo = false;
} else if (a == "-ni") {
m_enable_undo = true;
} else if (a == "-j" && (i + 1) < argc) {
m_custom_macro_paths.push_back (std::pair<std::string, std::string> (args [++i], std::string ()));
} else if (a == "-nt") {
m_write_config_file = true;
} else if (a == "-t") {
m_write_config_file = false;
} else if (a == "-z") {
m_no_gui = true;
} else if (a == "-zz") {
m_no_gui = true;
// other consequences have been dealt with before
} else if (a == "-b") {
// -nc:
m_config_files.clear ();
m_initial_config_files = m_config_files;
m_config_file_to_write.clear ();
// -rx:
m_no_macros = true;
// -zz:
m_no_gui = true;
// other consequences have been dealt with before
} else if (a == "-x") {
m_sync_mode = true;
} else if (a == "-y" && (i + 1) < argc) {
m_package_inst.push_back (args [++i]);
} else if (a == "-yd") {
m_packages_with_dep = true;
} else if (a == "-m" && (i + 1) < argc) {
m_files.push_back (std::make_pair (rdb_file, std::make_pair (std::string (args [++i]), std::string ())));
} else if (a == "-mn" && (i + 1) < argc) {
m_files.push_back (std::make_pair (l2ndb_file, std::make_pair (std::string (args [++i]), std::string ())));
} else if (a[0] == '-') {
throw tl::Exception (tl::to_string (QObject::tr ("Unknown option: ")) + a);
} else if (tech_set) {
if (! tech.empty ()) {
m_files.push_back (std::make_pair (layout_file_with_tech, std::make_pair (a, tech)));
} else if (! tech_file.empty ()) {
m_files.push_back (std::make_pair (layout_file_with_tech_file, std::make_pair (a, tech_file)));
} else {
m_files.push_back (std::make_pair (layout_file, std::make_pair (a, std::string ())));
}
} else {
m_files.push_back (std::make_pair (layout_file, std::make_pair (a, std::string ())));
}
}
}
void
ApplicationBase::init_app ()
{
// Try to locate the plugins:
// The are installed either
// - directly in one of the KLAYOUT_PATH directories
// - in a folder named by the architecture (i.e. "i686-win32-mingw" or "x86_64-linux-gcc") below
// one of these folders
// - in one of the Salt packages
// - in one of the Salt packages, in a folder named after the architecture
// Below this, the following folders are looked up:
// - db_plugins for db module plugins
// - lay_plugins for lay module plugins
std::string version = lay::Version::version ();
std::vector<std::string> vv = tl::split (version, ".");
std::string arch_string = tl::arch_string ();
std::vector<std::string> as = tl::split (arch_string, "-");
if (as.size () > 2) {
as.resize (2);
}
std::string short_arch_string = tl::join (as, "-");
#if defined(_WIN32)
// Set SSL_CERT_FILE for Windows installation and libcrypto.
std::string cert_file = "cert.pem";
std::string cert_env_var = "SSL_CERT_FILE";
if (! tl::has_env (cert_env_var)) {
std::string cert_path = tl::combine_path (m_inst_path, cert_file);
if (tl::file_exists (cert_path)) {
tl::set_env (cert_env_var, cert_path);
}
}
#endif
std::vector<std::string> klp_paths;
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
klp_paths.push_back (*p);
klp_paths.push_back (tl::to_string (QDir (tl::to_qstring (klp_paths.back ())).filePath (tl::to_qstring (tl::arch_string ()))));
lay::Salt salt;
salt.add_location (tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("salt"))));
// Build the search path for the plugin locations. The search priority is for example:
// salt/mypackage/x86_64-linux-gcc-0.25.1
// salt/mypackage/x86_64-linux-gcc-0.25
// salt/mypackage/x86_64-linux-gcc-0
// salt/mypackage/x86_64-linux-gcc
// salt/mypackage/x86_64-linux
// salt/mypackage
for (lay::Salt::flat_iterator g = salt.begin_flat (); g != salt.end_flat (); ++g) {
QDir dir = QDir (tl::to_qstring ((*g)->path ()));
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (arch_string + "-" + lay::Version::version()))));
if (vv.size () >= 2) {
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (arch_string + "-" + vv[0] + "." + vv[1]))));
}
if (vv.size () >= 1) {
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (arch_string + "-" + vv[0]))));
}
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (arch_string + "-" + tl::to_string (lay::Version::version ())))));
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (arch_string))));
klp_paths.push_back (tl::to_string (dir.filePath (tl::to_qstring (short_arch_string))));
klp_paths.push_back ((*g)->path ());
}
}
// initialize the modules (load their plugins from the paths)
db::init (klp_paths);
lay::init (klp_paths);
// initialize the GSI class system (Variant binding, Expression support)
// We have to do this now since plugins may register GSI classes and before the
// ruby interpreter, because it depends on a proper class system.
gsi::initialize ();
// initialize the tl::Expression subsystem with GSI-bound classes
gsi::initialize_expressions ();
// create the ruby and python interpreter instances now.
// Hint: we do this after load_plugin, because that way the plugins can register GSI classes and methods.
// TODO: do this through some auto-registration
mp_ruby_interpreter = new rba::RubyInterpreter ();
mp_python_interpreter = new pya::PythonInterpreter ();
// initialize the Python interpreter - load the pya module
pya::PythonInterpreter::initialize ();
// Read some configuration values that we need early
bool editable_from_config = false;
{
lay::Dispatcher cfg;
for (std::vector <std::string>::const_iterator c = m_config_files.begin (); c != m_config_files.end (); ++c) {
try {
cfg.read_config (*c);
} catch (...) { }
}
try {
cfg.config_get (cfg_edit_mode, editable_from_config);
} catch (...) { }
try {
std::string mp;
cfg.config_get (cfg_custom_macro_paths, mp);
tl::Extractor ex (mp.c_str ());
while (! ex.at_end ()) {
std::string p;
ex.read_word_or_quoted (p);
m_custom_macro_paths.push_back (std::pair<std::string, std::string> (p, std::string ()));
if (ex.test (":")) {
ex.read_word (m_custom_macro_paths.back ().second);
}
ex.test (";");
}
} catch (...) { }
}
// Install the signal handlers after the interpreters, so we can be sure we
// installed our handler.
install_signal_handlers ();
lay::SaltController *sc = lay::SaltController::instance ();
lay::TechnologyController *tc = lay::TechnologyController::instance ();
lay::MacroController *mc = lay::MacroController::instance ();
if (sc) {
// auto-import salt grains
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
sc->add_path (*p);
}
sc->set_salt_mine_url (lay::salt_mine_url ());
// Do package installation if requested.
if (!m_package_inst.empty ()) {
if (! sc->install_packages (m_package_inst, m_packages_with_dep)) {
exit (1);
} else {
exit (0);
}
}
sc->salt_changed_event.add (this, &ApplicationBase::salt_changed);
}
if (tc) {
// auto-import technologies
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
tc->add_path (*p);
}
tc->load ();
bool needs_reload = false;
// disambiguator for tech name
std::map<std::string, int> tech_disambiguator;
std::map<std::string, std::string> tech_name_for_file;
for (auto t = db::Technologies::instance ()->begin (); t != db::Technologies::instance ()->end (); ++t) {
tech_disambiguator.insert (std::make_pair (t->name (), 0));
}
// import technologies from the command line
for (std::vector <std::pair<file_type, std::pair<std::string, std::string> > >::iterator f = m_files.begin (); f != m_files.end (); ++f) {
if (f->first == layout_file_with_tech_file) {
const std::string &tech_file = f->second.second;
if (tl::verbosity () >= 20) {
tl::info << "Importing technology from " << f->second.second;
}
db::Technology t;
t.load (tech_file);
// disambiguate the name, so we do not overwrite technologies with the same name from the config
if (tech_name_for_file.find (tech_file) != tech_name_for_file.end ()) {
t.set_name (tech_name_for_file [tech_file]);
} else if (tech_disambiguator.find (t.name ()) != tech_disambiguator.end ()) {
int &index = tech_disambiguator [t.name ()];
index += 1;
t.set_name (t.name () + tl::sprintf ("[%d]", index));
tech_name_for_file.insert (std::make_pair (tech_file, t.name ()));
} else {
tech_disambiguator.insert (std::make_pair (t.name (), 0));
tech_name_for_file.insert (std::make_pair (tech_file, t.name ()));
}
tc->add_temp_tech (t);
f->first = layout_file_with_tech;
f->second.second = t.name ();
needs_reload = true;
}
}
if (needs_reload) {
tc->load ();
}
}
if (mc) {
// create the basic macro categories
if (ruby_interpreter ().available ()) {
std::vector<std::string> folders;
folders.push_back ("macros");
folders.push_back ("ruby");
mc->add_macro_category ("macros", "Ruby", folders);
}
if (python_interpreter ().available ()) {
std::vector<std::string> folders;
folders.push_back ("pymacros");
folders.push_back ("python");
mc->add_macro_category ("pymacros", "Python", folders);
}
mc->enable_implicit_macros (! m_no_macros);
// Add the global ruby modules as the first ones.
// TODO: this is a deprecated feature.
std::vector<std::string> global_modules = scan_global_modules ();
m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ());
size_t local_folders = (lay::get_appdata_path ().empty () ? 0 : 1);
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
if (size_t (p - m_klayout_path.begin ()) < local_folders) {
mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false);
} else if (m_klayout_path.size () == 1 + local_folders) {
mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true);
} else {
mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true);
}
}
// Install the custom folders
for (std::vector <std::pair<std::string, std::string> >::const_iterator p = m_custom_macro_paths.begin (); p != m_custom_macro_paths.end (); ++p) {
mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false);
}
// Actually load the macros and/or establish the search path
mc->finish ();
}
// If the editable flag was not set, use it from the
// configuration. Since it is too early now, we cannot use the
// configuration once it is read
if (! m_editable_set && ! m_vo_mode) {
m_editable = editable_from_config;
}
db::set_default_editable_mode (m_editable);
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
prepare_recording (m_gtf_record, m_gtf_save_incremental);
}
tl::Eval::set_global_var ("appdata_path", tl::Variant (m_appdata_path));
tl::Eval::set_global_var ("inst_path", tl::Variant (m_inst_path));
tl::Variant kp (m_klayout_path.begin (), m_klayout_path.end ());
tl::Eval::set_global_var ("klayout_path", kp);
// call "autorun_early" on all plugins that wish so
for (std::list<lay::PluginDescriptor>::const_iterator p = lay::plugins ().begin (); p != lay::plugins ().end (); ++p) {
if (p->autorun_early) {
(*p->autorun_early) ();
}
}
// run all early autorun macros
lym::MacroCollection::root ().autorun_early ();
// redo gsi::initialize as the macros may have registered new external classes
// through the "doc to external class" mechanism.
gsi::initialize ();
// autorun_early may have added macro categories, so we need to call finish() again
if (mc) {
mc->finish ();
// as this regenerates the macro collection, autorun_early is required again
// note: this does not re-execute macros that have been executed already
lym::MacroCollection::root ().autorun_early ();
}
// rescan the folders because early autorun macros might have added
// suffixes through the MacroInterpreter interface.
lym::MacroCollection::root ().rescan ();
// and yet another autorun_early pass ..
lym::MacroCollection::root ().autorun_early ();
// creates the main window or plugin root as required
setup ();
// initialize the plugins for the first time
if (tl::verbosity () >= 20) {
tl::info << "Initializing plugins:";
}
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
if (tl::verbosity () >= 20) {
tl::info << " " << cls.current_name () << " [" << cls.current_position () << "]";
}
pd->initialize (dispatcher ());
}
// establish the configuration
dispatcher ()->config_setup ();
// deferred method processing for those plugins which need this
process_events ();
// some info output
if (tl::verbosity () >= 20) {
tl::info << "KLayout path:";
for (std::vector <std::string>::const_iterator c = m_klayout_path.begin (); c != m_klayout_path.end (); ++c) {
tl::info << " " << *c;
}
tl::info << "Config file to write: " << m_config_file_to_write;
tl::info << "Config files to read:";
for (std::vector <std::string>::const_iterator c = m_config_files.begin (); c != m_config_files.end (); ++c) {
tl::info << " " << *c;
}
}
}
void
ApplicationBase::add_macro_category (const std::string &name, const std::string &description, const std::vector<std::string> &folders)
{
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
mc->add_macro_category (name, description, folders);
}
}
void
ApplicationBase::salt_changed ()
{
BEGIN_PROTECTED_SILENT
salt_changed_event ();
END_PROTECTED_SILENT
}
ApplicationBase::~ApplicationBase ()
{
tl::set_ui_exception_handlers (0, 0, 0);
// check whether shutdown was called
tl_assert (ms_instance == 0);
}
std::vector<std::string>
ApplicationBase::scan_global_modules ()
{
// NOTE:
// this is deprecated functionality - for backward compatibility, global "*.rbm" and "*.pym" modules
// are still considered. The desired solution is autorun macros.
// try to locate a global rbainit file and rbm modules
std::vector<std::string> global_modules;
std::set<std::string> modules;
// try to locate a global plugins
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
#if 0
// deprecated functionality
QFileInfo rbainit_file (tl::to_qstring (*p), QString::fromUtf8 ("rbainit"));
if (rbainit_file.exists () && rbainit_file.isReadable ()) {
std::string m = tl::to_string (rbainit_file.absoluteFilePath ());
if (modules.find (m) == modules.end ()) {
global_modules.push_back (m);
modules.insert (m);
}
}
#endif
QDir inst_path_dir (tl::to_qstring (*p));
QStringList name_filters;
name_filters << QString::fromUtf8 ("*.rbm");
name_filters << QString::fromUtf8 ("*.pym");
QStringList inst_modules = inst_path_dir.entryList (name_filters);
inst_modules.sort ();
for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
QFileInfo rbm_file (tl::to_qstring (*p), *im);
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 (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);
}
}
}
}
return global_modules;
}
void
ApplicationBase::exit (int result)
{
if (! result) {
finish ();
}
// uninitialize the plugins
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->uninitialize (dispatcher ());
}
shutdown ();
::exit (result);
}
const int number_of_config_file_backups = 10;
void
ApplicationBase::finish ()
{
if (dispatcher () && 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;
}
dispatcher ()->write_config (m_config_file_to_write, number_of_config_file_backups);
}
if (! m_config_file_to_delete.empty () && m_config_file_to_delete != m_config_file_to_write) {
if (tl::verbosity () >= 20) {
tl::info << tl::to_string (QObject::tr ("Deleting configuration file ")) << m_config_file_to_delete;
}
QFile::remove (tl::to_qstring (m_config_file_to_delete));
}
}
}
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 ()
{
if (mp_ruby_interpreter) {
delete mp_ruby_interpreter;
mp_ruby_interpreter = 0;
}
if (mp_python_interpreter) {
delete mp_python_interpreter;
mp_python_interpreter = 0;
}
ms_instance = 0;
}
ApplicationBase *
ApplicationBase::instance ()
{
return ms_instance;
}
std::string
ApplicationBase::version ()
{
return std::string (lay::Version::name ()) + " " + lay::Version::version ();
}
std::string
ApplicationBase::usage ()
{
std::string r;
r = std::string (lay::Version::exe_name ()) + " [<options>] [<file>] ..\n";
r += tl::to_string (tr (
"\n"
"The format of the input files is usually detected automatically. In some cases, the format is derived from\n"
"the file's suffix. If your file does not have the neccessary suffix, you can explicitly specify the format\n"
"by adding the respective suffix to the file name in square brackets - e.g. 'file.data[def]' will read\n"
"the file 'file.data' as a DEF file.\n"
"\n"
"Options:\n"
" -b Batch mode (same as -zz -nc -rx)\n"
" -c <config file> Use this configuration file\n"
" -nc Don't use a configuration file (implies -t)\n"
" -d <log level> Set log level\n"
" -e Editable mode (allow editing of files)\n"
" -ne Readonly mode (editing of files is disabled)\n"
" -gr <file name> Record GUI test file\n"
" -gp <file name> Replay GUI test file\n"
" -gb <line number> Replay GUI test file up to (including) line\n"
" -gx <millisec> Replay rate for GUI test file\n"
" -gi Incremental logs for GUI test file\n"
" -i Disable undo buffering (less memory requirements)\n"
" -ni Enable undo buffering (default, overrides previous -i option)\n"
" -j <path> Add the given path to the macro project paths\n"
" -k <log file> Write log to the given file plus stdout/stderr\n"
" -l <lyp file> Use layer properties file\n"
" -lx With -l: add other layers as well\n"
" -lf With -l: use the lyp file as it is (no expansion to multiple layouts)\n"
" -m <database file> Load RDB (report database) file (into previous layout view)\n"
" -mn <database file> Load L2NDB (layout to netlist database) file (into previous layout view)\n"
" -n <technology> Technology to use for next layout(s) on command line\n"
" -nn <tech file> Technology file (.lyt) to use for next layout(s) on command line\n"
" -p <plugin> Load the plugin (can be used multiple times)\n"
" -r <script> Execute main script on startup (after having loaded files etc.)\n"
" -rr <script> Like -r, but does not exit after executing the script\n"
" -rm <script> Execute script on startup before loading files (can be used multiple times)\n"
" -rd <name>=<value> Specify script variable\n"
" -rx Ignore all implicit macros (*.rbm, rbainit, *.lym)\n"
" -s Load files into same view\n"
" -t Don't update the configuration file on exit\n"
" -nt Update the configuration file on exit (default, overrides previous -t option)\n"
" -u <file name> Restore session from given file\n"
" -v Print program version and exit (also available as --version)\n"
" -wd <name>=<value> Define a variable within expressions\n"
" -x Synchronous drawing mode\n"
" -y <package> Package installation: install package(s) and exit - can be used more than once\n"
" ('package' is a name, an URL and optionally a version in round brackets)\n"
" -yd With -y: include dependencies\n"
" -z Non-GUI mode (hidden views)\n"
" -zz Non-GUI mode (database only, implies -nc)\n"
"\n"
"Environment variables:\n"
" KLAYOUT_HOME The path to KLayout's data (configuration, macros, libraries etc.).\n"
" Default path is '~/.klayout' on Linux and '%USERPROFILE%\\KLayout' on Windows.\n"
" KLAYOUT_PATH A list of locations to look into additionally to KLAYOUT_HOME for macros, libraries etc.\n"
" Separator for the locations is ':' on Linux and ';' on Windows.\n"
" KLAYOUT_SALT_MINE The package manager repository URL. Default is 'http://sami.klayout.org/repository.xml'.\n"
" KLAYOUT_IGNORE_MACRO_DIRS A colon-separated list of folder names to ignore in the macro editor tree.\n"
" By default, only '__pycache__' is ignored.\n"
" KLAYOUT_LEF_FORMAT A space-separated list of formats to be recognized as LEF files.\n"
" By default this format list is '*.lef *.LEF *.lef.gz *.LEF.gz'.\n"
" KLAYOUT_DEF_FORMAT A space-separated list of formats to be recognized as DEF files.\n"
" By default this format list is '*.def *.DEF *.def.gz *.DEF.gz'.\n"
" KLAYOUT_PYTHONPATH The equivalent of PYTHONPATH for KLayout.\n"
" KLAYOUT_PYTHONHOME The equivalent of PYTHONHOME for KLayout.\n"
" KLAYOUT_HTTP_TIMEOUT The timeout for HTTP(S) requests in seconds. The default value is 10.\n"
" KLAYOUT_VERBOSITY The default verbosity (see -d option).\n"
));
return r;
}
int
ApplicationBase::run ()
{
lay::MainWindow *mw = main_window ();
gtf::Player player (0);
if (mw) {
(mw)->set_synchronous (m_sync_mode);
if (! m_no_gui) {
(mw)->setWindowTitle (tl::to_qstring (version ()));
(mw)->resize (800, 600);
(mw)->show ();
}
if (! m_gtf_replay.empty ()) {
player.load (m_gtf_replay);
}
start_recording ();
}
int result = 0;
bool config_failed = false;
for (std::vector <std::string>::const_iterator c = m_config_files.begin (); c != m_config_files.end (); ++c) {
BEGIN_PROTECTED_CLEANUP
dispatcher ()->read_config (*c);
// if the last config was read successfully no reset will happen:
config_failed = false;
END_PROTECTED_CLEANUP {
config_failed = true;
}
}
if (config_failed) {
reset_config ();
}
for (std::vector< std::pair<std::string, std::string> >::const_iterator v = m_variables.begin (); v != m_variables.end (); ++v) {
ruby_interpreter ().define_variable (v->first, v->second);
python_interpreter ().define_variable (v->first, v->second);
tl::log << "Variable definition: " << v->first << "='" << v->second << "'";
}
for (std::vector<std::string>::const_iterator m = m_load_macros.begin (); m != m_load_macros.end (); ++m) {
BEGIN_PROTECTED
std::unique_ptr<lym::Macro> macro (new lym::Macro ());
macro->load_from (*m);
macro->set_file_path (*m);
if (macro->show_in_menu ()) {
// menu-based macros are just registered so they are shown in the menu
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
tl::log << "Registering macro '" << *m << "'";
mc->add_temp_macro (macro.release ());
}
} else {
// other macros given with -rm are run
tl::log << "Run macro '" << *m << "'";
macro->run ();
}
END_PROTECTED
}
// Run plugin and macro specific initializations
autorun ();
// Some objects we need during batch mode view generation
db::Manager batch_mode_manager;
tl::shared_ptr<LayoutView> batch_mode_view;
if (mw) {
for (std::vector <std::pair<file_type, std::pair<std::string, std::string> > >::const_iterator f = m_files.begin (); f != m_files.end (); ++f) {
if (f->first == layout_file || f->first == layout_file_with_tech) {
std::string filename = f->second.first;
if (f->first != layout_file_with_tech) {
mw->add_mru (f->second.first);
mw->load_layout (f->second.first, m_same_view ? 2 /*same view*/ : 1 /*new view*/);
} else {
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 (mw->current_view ()) {
mw->current_view ()->set_active_cellview_index (0);
}
} else if (f->first == rdb_file) {
if (mw->current_view () == 0) {
mw->create_view ();
}
if (mw->current_view () != 0) {
std::unique_ptr <rdb::Database> db (new rdb::Database ());
db->load (f->second.first);
int rdb_index = mw->current_view ()->add_rdb (db.release ());
mw->current_view ()->open_rdb_browser (rdb_index, mw->current_view ()->active_cellview_index ());
}
} else if (f->first == l2ndb_file) {
if (mw->current_view () == 0) {
mw->create_view ();
}
if (mw->current_view () != 0) {
int l2ndb_index = mw->current_view ()->add_l2ndb (db::LayoutToNetlist::create_from_file (f->second.first));
mw->current_view ()->open_l2ndb_browser (l2ndb_index, mw->current_view ()->active_cellview_index ());
}
}
}
if (! m_layer_props_file.empty ()) {
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 != mw->views (); ++v) {
mw->view (v)->zoom_fit ();
}
}
if (! m_session_file.empty ()) {
mw->restore_session (m_session_file);
tl::log << "Session restored '" << m_session_file << "'";
}
if (! m_gtf_replay.empty ()) {
player.replay (m_gtf_replay_rate, m_gtf_replay_stop);
}
} else {
// in batch mode create at least one
for (std::vector <std::pair<file_type, std::pair<std::string, std::string> > >::const_iterator f = m_files.begin (); f != m_files.end (); ++f) {
if (f->first == layout_file || f->first == layout_file_with_tech) {
std::string filename = f->second.first;
if (batch_mode_view.get () != 0 && ! m_same_view) {
tl::warn << tl::sprintf (tl::to_string (tr ("Ignoring additional views in batch mode (file %s)")), filename);
continue;
}
if (! batch_mode_view) {
batch_mode_view.reset (create_view (batch_mode_manager));
}
if (f->first != layout_file_with_tech) {
batch_mode_view->load_layout (f->second.first, true);
} else {
batch_mode_view->load_layout (f->second.first, f->second.second, true);
}
// Make the first one loaded the active one.
batch_mode_view->set_active_cellview_index (0);
} else if (f->first == rdb_file) {
if (! batch_mode_view) {
batch_mode_view.reset (create_view (batch_mode_manager));
}
std::unique_ptr <rdb::Database> db (new rdb::Database ());
db->load (f->second.first);
batch_mode_view->add_rdb (db.release ());
} else if (f->first == l2ndb_file) {
if (! batch_mode_view) {
batch_mode_view.reset (create_view (batch_mode_manager));
}
batch_mode_view->add_l2ndb (db::LayoutToNetlist::create_from_file (f->second.first));
}
}
if (! m_layer_props_file.empty () && batch_mode_view.get ()) {
batch_mode_view->load_layer_props (m_layer_props_file, 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.
batch_mode_view->zoom_fit ();
}
}
// Give the plugins a change to do some last-minute initialisation and checks
if (dispatcher ()) {
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->initialized (dispatcher ());
}
}
if (mw && ! m_no_gui && m_gtf_replay.empty () && m_gtf_record.empty ()) {
// Show initial tip window if required
mw->about_to_exec ();
}
if (! m_run_macro.empty ()) {
tl::log << "Run macro '" << m_run_macro << "'";
lym::Macro macro;
macro.load_from (m_run_macro);
macro.set_file_path (m_run_macro);
result = macro.run ();
if (result == 0 && ! m_run_macro_and_exit) {
result = exec ();
}
} else {
result = exec ();
}
finish ();
batch_mode_view.reset (0);
return result;
}
lay::LayoutView *
ApplicationBase::create_view (db::Manager &manager)
{
// create a new view
lay::LayoutView *view = new lay::LayoutView (&manager, lay::ApplicationBase::instance ()->is_editable (), dispatcher ());
// set initial attributes
view->set_synchronous (m_sync_mode);
int tl = 0;
dispatcher ()->config_get (cfg_initial_hier_depth, tl);
view->set_hier_levels (std::make_pair (0, tl));
view->set_current ();
return view;
}
void
ApplicationBase::autorun ()
{
// call "autorun" on all plugins that wish so
for (std::list <lay::PluginDescriptor>::const_iterator p = lay::plugins ().begin (); p != lay::plugins ().end (); ++p) {
if (p->autorun) {
(*p->autorun) ();
}
}
// run all autorun macros
lym::MacroCollection::root ().autorun ();
}
void
ApplicationBase::set_editable (bool e)
{
if (m_editable != e) {
m_editable = e;
db::set_default_editable_mode (m_editable);
}
}
static void
dump_children (QObject *obj, int level = 0)
{
QObjectList children = obj->children ();
if (! children.isEmpty () || ! obj->objectName ().isEmpty ()) {
std::string info;
for (int i = 0; i < level; ++i) {
info += " ";
}
if (obj->objectName ().isEmpty ()) {
info += "<unnamed>";
} else {
info += tl::to_string (obj->objectName ());
}
tl::info << info;
for (QObjectList::const_iterator child = children.begin (); child != children.end (); ++child) {
dump_children (*child, level + 1);
}
}
}
void
ApplicationBase::process_events_impl (QEventLoop::ProcessEventsFlags /*flags*/, bool /*silent*/)
{
// in the non-UI case there are no events, but we can at least schedule deferred method calls.
tl::DeferredMethodScheduler::execute ();
}
bool
ApplicationBase::write_config (const std::string &config_file, int keep_backups)
{
return dispatcher () ? dispatcher ()->write_config (config_file, keep_backups) : 0;
}
void
ApplicationBase::reset_config ()
{
clear_config ();
for (std::vector <std::string>::const_iterator c = m_initial_config_files.begin (); c != m_initial_config_files.end (); ++c) {
try {
read_config (*c);
} catch (...) { }
}
}
void
ApplicationBase::clear_config ()
{
if (dispatcher ()) {
dispatcher ()->clear_config ();
}
}
bool
ApplicationBase::read_config (const std::string &config_file)
{
return dispatcher () ? dispatcher ()->read_config (config_file) : true;
}
void
ApplicationBase::set_config (const std::string &name, const std::string &value)
{
if (dispatcher ()) {
dispatcher ()->config_set (name, value);
}
}
void
ApplicationBase::config_end ()
{
if (dispatcher ()) {
dispatcher ()->config_end ();
}
}
std::string
ApplicationBase::get_config (const std::string &name) const
{
if (dispatcher ()) {
return dispatcher ()->config_get (name);
} else {
return std::string ();
}
}
std::vector<std::string>
ApplicationBase::get_config_names () const
{
std::vector<std::string> names;
if (dispatcher ()) {
dispatcher ()->get_config_names (names);
}
return names;
}
// --------------------------------------------------------------------------------
// GuiApplication implementation
GuiApplication::GuiApplication (int &argc, char **argv)
: QApplication (argc, argv), ApplicationBase (false),
mp_mw (0),
mp_recorder (0),
m_in_notify (0)
{
// 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")));
}
GuiApplication::~GuiApplication ()
{
// uninitialize the plugins
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->uninitialize (dispatcher ());
}
shutdown ();
}
void
GuiApplication::initialize ()
{
#if QT_VERSION >= 0x040500
QCoreApplication::setAttribute (Qt::AA_DontShowIconsInMenus, false);
#endif
#if QT_VERSION >= 0x050000
QCoreApplication::setAttribute (Qt::AA_UseHighDpiPixmaps, true);
#endif
#if QT_VERSION >= 0x050600
QCoreApplication::setAttribute (Qt::AA_EnableHighDpiScaling, true);
#endif
}
bool
GuiApplication::notify (QObject *receiver, QEvent *e)
{
QWheelEvent *wheel_event = dynamic_cast<QWheelEvent *>(e);
if (wheel_event) {
// intercept wheel events targeting QComboBox objects to avoid
// changing them through wheel actions.
for (auto r = receiver; r != 0; r = r->parent ()) {
if (dynamic_cast<QComboBox *>(r)) {
// stop further processing
return true;
}
}
}
if (dynamic_cast<QPaintEvent *> (e)) {
// NOTE: we don't want recursive paint events - the painters are not reentrant.
// Hence we disable process_events_impl (specifically for progress reporters).
lay::BusySection busy;
return do_notify (receiver, e);
} else {
return do_notify (receiver, e);
}
}
bool
GuiApplication::do_notify (QObject *receiver, QEvent *e)
{
bool in_notify = (m_in_notify > 0);
bool ret = true;
++m_in_notify;
// 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".
// Because END_PROTECTED may raise other events (message box) and this may cause other
// exceptions, we use silent mode inside notify to avoid recursion.
if (in_notify) {
BEGIN_PROTECTED_SILENT
ret = QApplication::notify (receiver, e);
END_PROTECTED_SILENT
} else {
BEGIN_PROTECTED
ret = QApplication::notify (receiver, e);
END_PROTECTED
}
--m_in_notify;
return ret;
}
void
GuiApplication::enter_busy_mode (bool bm)
{
if (mp_mw) {
mp_mw->enter_busy_mode (bm);
}
}
bool
GuiApplication::is_busy () const
{
return mp_mw && mp_mw->is_busy ();
}
void
GuiApplication::force_update_app_menu ()
{
#if defined(__APPLE__)
// This is a workaround for a bug in the MacOS native menu integration:
// this signal forces the menu to become updated. Without this, any
// new menu items stay disabled.
emit focusWindowChanged (focusWindow ());
#endif
}
bool
GuiApplication::event (QEvent *event)
{
#if defined(__APPLE__)
// By Thomas Lima (March 7, 2018)
//
// This event interceptor catches MacOS "Open With" event, and KLayout should respond
// similarly to the Drop event in MainWindow::dropEvent.
//
// This particular implementation always creates a new window.
//
// This was implemented with the inspiration of http://doc.qt.io/qt-5/qfileopenevent.html
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
if (mp_mw)
{
const std::string tech = mp_mw->initial_technology();
const std::string file = tl::to_string (openEvent->file());
const int mode = 1; // open in new window
mp_mw->load_layout (file, tech, mode);
mp_mw->add_mru (file, tech);
}
}
#endif
return QApplication::event(event);
}
static void atexit_handler ()
{
if (lay::ApplicationBase::instance ()) {
lay::ApplicationBase::instance ()->shutdown ();
}
}
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<QAction *> actions = (*tl)->findChildren<QAction *> ();
if (! actions.isEmpty ()) {
tl::info << tl::to_string ((*tl)->objectName ()) << ":";
for (QList<QAction *>::const_iterator a = actions.begin (); a != actions.end (); ++a) {
if (! (*a)->objectName ().isEmpty ()) {
tl::info << " " << tl::to_string ((*a)->objectName ());
}
}
}
}
}
tl::info << "";
}
// register an exit handler to shutdown cleanly in case of an explicit exit
// inside the code
::atexit (&atexit_handler);
return QApplication::exec ();
}
void
GuiApplication::shutdown ()
{
// avoid deferred execution later on where there isn't a valid main window anymore
// (problem case: showing a dialog inside main windows's destroyed signal - this will
// process events and trigger execution if not disabled)
if (! tl::DeferredMethodScheduler::instance ()->is_disabled ()) {
tl::DeferredMethodScheduler::instance ()->execute ();
}
tl::DeferredMethodScheduler::instance ()->enable (false);
if (mp_mw) {
delete mp_mw;
mp_mw = 0;
}
// detach all top level widgets from Ruby/Python - we don't want the interpreter do this
// for us. Qt will delete all top level widgets itself.
// NOTE: we must only detach (= "keep" on C++ side), not delete them as top level widgets
// may be owned by someone else.
QWidgetList tl_widgets = topLevelWidgets ();
for (QWidgetList::iterator w = tl_widgets.begin (); w != tl_widgets.end (); ++w) {
gsi::ObjectBase *gsi_obj = dynamic_cast<gsi::ObjectBase *> (*w);
if (gsi_obj) {
gsi_obj->keep ();
}
}
while (! (tl_widgets = topLevelWidgets ()).empty ()) {
delete tl_widgets [0];
}
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 &gtf_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::Dispatcher *
GuiApplication::dispatcher () const
{
return mp_mw ? mp_mw->dispatcher () : 0;
}
void
GuiApplication::setup ()
{
tl_assert (mp_mw == 0);
mp_mw = new lay::MainWindow (this, "main_window", is_undo_enabled ());
QObject::connect (mp_mw, SIGNAL (closed ()), this, SLOT (quit ()));
// create a password dialog for use with the HTTP streams
lay::PasswordDialog *pw_dialog = new lay::PasswordDialog (mp_mw);
tl::InputHttpStream::set_credential_provider (pw_dialog);
}
void
GuiApplication::process_events_impl (QEventLoop::ProcessEventsFlags flags, bool silent)
{
// prevent recursive process_events
if (is_busy ()) {
return;
}
if (mp_mw) {
lay::BusySection busy;
if (silent) {
tl::DeferredMethodScheduler::enable (false);
}
try {
#if QT_VERSION < 0x050000
QApplication::syncX ();
#endif
QApplication::processEvents (flags);
// Qt seems not to send posted UserEvents in some cases (e.g. in the unit test application with GLib?
// Glib not doing this without a main window visible?). Hence we do this explicitly here.
QApplication::sendPostedEvents ();
} catch (...) {
// ignore exceptions
}
if (silent) {
tl::DeferredMethodScheduler::enable (true);
}
}
}
// --------------------------------------------------------------------------------
// NonGuiApplication implementation
NonGuiApplication::NonGuiApplication (int &argc, char **argv)
: QCoreApplication (argc, argv), ApplicationBase (true),
mp_pr (0),
mp_pb (0),
mp_dispatcher (0)
{
// .. nothing yet ..
}
NonGuiApplication::~NonGuiApplication ()
{
// uninitialize the plugins
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->uninitialize (dispatcher ());
}
shutdown ();
}
int
NonGuiApplication::exec ()
{
// A non-GUI application does nothing on exec
return 0;
}
void
NonGuiApplication::shutdown ()
{
if (mp_dispatcher) {
delete mp_dispatcher;
mp_dispatcher = 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_dispatcher = new lay::Dispatcher ();
}
}