
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2018 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 "layProgress.h"
#include "layTextProgress.h"
#include "layBackgroundAwareTreeStyle.h"
#include "layMacroController.h"
#include "layTechnologyController.h"
#include "laySaltController.h"
#include "laySystemPaths.h"
#include "layPasswordDialog.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 "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 <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 ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent)
{
  //  Prevents severe side effects if there are pending deferred methods
  tl::NoDeferredMethods silent;

  //  if any transaction is pending (this may happen when an operation threw an exception)
  //  close transactions.
  if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) {
    lay::MainWindow::instance ()->manager ().commit ();
  }

  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);

  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 () << tl::to_string (QObject::tr (" (class ")) << gsi_excpt->cls () << ")";
    } else {
      tl::error << gsi_excpt->msg () << tl::to_string (QObject::tr (" (class ")) << gsi_excpt->cls () << ")";
    }

    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 ();
    }
    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;

  //  if any transaction is pending (this may happen when an operation threw an exception)
  //  close transactions.
  if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) {
    lay::MainWindow::instance ()->manager ().commit ();
  }

  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;

  //  if any transaction is pending (this may happen when an operation threw an exception)
  //  close transactions.
  if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) {
    lay::MainWindow::instance ()->manager ().commit ();
  }

  if (! parent) {
    parent = QApplication::activeWindow () ? QApplication::activeWindow () : lay::MainWindow::instance ();
  }
  QMessageBox::critical (parent, QObject::tr ("Error"), QObject::tr ("An unspecific error occured")); 
}

// --------------------------------------------------------------------------------

static ApplicationBase *ms_instance = 0;

static PluginDescriptor load_plugin (const std::string &pp)
{
  PluginDescriptor desc;
  desc.path = pp;

  klp_init_func_t init_func = 0;
  static const char *init_func_name = "klp_init";

  //  NOTE: since we are using a different suffix ("*.klp"), we can't use QLibrary.
#ifdef _WIN32
  //  there is no "dlopen" on mingw, so we need to emulate it.
  HINSTANCE handle = LoadLibraryW ((const wchar_t *) tl::to_qstring (pp).constData ());
  if (! handle) {
    throw tl::Exception (tl::to_string (QObject::tr ("Unable to load plugin: %s with error message: %s ")), pp, GetLastError ());
  }
  init_func = reinterpret_cast<klp_init_func_t> (GetProcAddress (handle, init_func_name));
#else
  void *handle;
  handle = dlopen (tl::string_to_system (pp).c_str (), RTLD_LAZY);
  if (! handle) {
    throw tl::Exception (tl::to_string (QObject::tr ("Unable to load plugin: %s")), pp);
  }
  init_func = reinterpret_cast<klp_init_func_t> (dlsym (handle, init_func_name));
#endif

  //  If present, call the initialization function to fetch some details from the plugin
  if (init_func) {
    const char *version = 0;
    const char *description = 0;
    (*init_func) (&desc.autorun, &desc.autorun_early, &version, &description);
    if (version) {
      desc.version = version;
    }
    if (description) {
      desc.description = description;
    }
  }

  tl::log << "Loaded plugin '" << pp << "'";

  return desc;
}

// --------------------------------------------------------------------------------
//  ApplicationBase implementation

ApplicationBase::ApplicationBase (bool non_ui_mode)
  : gsi::ObjectBase (),
    m_lyp_map_all_cvs (true), 
    m_lyp_add_default (false),
    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 = lay::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 = 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);
      v = std::max (0, v);
      tl::verbosity (v);

    } 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) {

      m_run_macro = args [++i];

    } 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 = std::max (0, r);

    } else if (a == "-gb" && (i + 1) < argc) {

      int s = 0;
      tl::from_string (args [++i], s);
      m_gtf_replay_stop = std::max (0, s);

    } 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) {

      m_native_plugins.push_back (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 == "-v") {

      tl::info << lay::Version::name () << " " << lay::Version::version ();
      exit (0);

    } else if (a == "-h") {

      tl::info << usage () << tl::noendl;
      exit (0);

    } 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[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 native plugins:
  //  Native plugins are DLL's or SO's disguised as "*.klp" files.
  //  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

  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, "-");

  for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {

    std::set<std::string> modules;

    std::vector<QString> klp_paths;
    klp_paths.push_back (tl::to_qstring (*p));
    klp_paths.push_back (QDir (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 *.klp files. 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 (dir.filePath (tl::to_qstring (arch_string + "-" + lay::Version::version())));
      if (vv.size () >= 2) {
        klp_paths.push_back (dir.filePath (tl::to_qstring (arch_string + "-" + vv[0] + "." + vv[1])));
      }
      if (vv.size () >= 1) {
        klp_paths.push_back (dir.filePath (tl::to_qstring (arch_string + "-" + vv[0])));
      }
      klp_paths.push_back (dir.filePath (tl::to_qstring (arch_string + "-" + tl::to_string (lay::Version::version ()))));
      klp_paths.push_back (dir.filePath (tl::to_qstring (arch_string)));
      klp_paths.push_back (dir.filePath (tl::to_qstring (short_arch_string)));
      klp_paths.push_back (tl::to_qstring ((*g)->path ()));
    }

    QStringList name_filters;
    name_filters << QString::fromUtf8 ("*.klp");

    for (std::vector<QString>::const_iterator p = klp_paths.begin (); p != klp_paths.end (); ++p) {

      QStringList inst_modules = QDir (*p).entryList (name_filters);
      inst_modules.sort ();

      for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
        QFileInfo klp_file (*p, *im);
        if (klp_file.exists () && klp_file.isReadable ()) {
          std::string m = tl::to_string (klp_file.absoluteFilePath ());
          std::string mn = tl::to_string (klp_file.fileName ());
          if (modules.find (mn) == modules.end ()) {
            try {
              m_native_plugins.push_back (load_plugin (m));
              modules.insert (mn);
            } catch (tl::Exception &ex) {
              tl::error << tl::to_string (QObject::tr ("Unable to load plugin %1: %2").arg (tl::to_qstring (m)).arg (tl::to_qstring (ex.msg ())));
            }
          }
        }
      }

    }

  }

  //  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 ();

  //  Read some configuration values that we need early
  bool editable_from_config = false;

  {
    lay::PluginRoot 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);
      }
    }

  }

  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);
    }

    //  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) {

        if (tl::verbosity () >= 20) {
          tl::info << "Importing technology from " << f->second.second;
        }

        lay::Technology t;
        t.load (f->second.second);

        tc->add_temp_tech (t);

        f->first = layout_file_with_tech;
        f->second.second = t.name ();

      }

    }

    tc->load ();

  }

  if (mc) {

    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 ());

    for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
      if (p == m_klayout_path.begin ()) {
        mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false);
      } else if (m_klayout_path.size () == 2) {
        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);
  db::enable_transactions (m_enable_undo);

  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::vector <lay::PluginDescriptor>::const_iterator p = m_native_plugins.begin (); p != m_native_plugins.end (); ++p) {
    if (p->autorun_early) {
      (*p->autorun_early) ();
    }
  }

  //  run all early autorun macros
  lym::MacroCollection::root ().autorun_early ();

  //  rescan the folders because early autorun macros might have added 
  //  suffixes through the MacroInterpreter interface.
  lym::MacroCollection::root ().rescan ();

  //  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 (plugin_root ());
  }

  //  establish the configuration
  plugin_root ()->config_setup ();

  //  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;
    }

  }
}

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 (plugin_root ());
  }

  shutdown ();

  ::exit (result);
}

void 
ApplicationBase::finish ()
{
  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;
      }
      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) {
        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 () const
{
  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 (QObject::tr ("options")) + "\n";
  r += tl::to_string (QObject::tr ("  -b                  Batch mode (same as -zz -nc -rx)")) + "\n";
  r += tl::to_string (QObject::tr ("  -c <config file>    Use this configuration file")) + "\n";
  r += tl::to_string (QObject::tr ("  -nc                 Don't use a configuration file (implies -t)")) + "\n";
  r += tl::to_string (QObject::tr ("  -d <debug level>    Set debug level")) + "\n";
  r += tl::to_string (QObject::tr ("  -e                  Editable mode (allow editing of files)")) + "\n";
  r += tl::to_string (QObject::tr ("  -ne                 Readonly mode (editing of files is disabled)")) + "\n";
  r += tl::to_string (QObject::tr ("  -gr <file name>     Record GUI test file")) + "\n";
  r += tl::to_string (QObject::tr ("  -gp <file name>     Replay GUI test file")) + "\n";
  r += tl::to_string (QObject::tr ("  -gb <line number>   Replay GUI test file up to (including) line")) + "\n";
  r += tl::to_string (QObject::tr ("  -gx <millisec>      Replay rate for GUI test file")) + "\n";
  r += tl::to_string (QObject::tr ("  -gi                 Incremental logs for GUI test file")) + "\n";
  r += tl::to_string (QObject::tr ("  -i                  Disable undo buffering (less memory requirements)")) + "\n";
  r += tl::to_string (QObject::tr ("  -ni                 Enable undo buffering (default, overrides previous -i option)")) + "\n";
  r += tl::to_string (QObject::tr ("  -j <path>           Add the given path to the macro project paths")) + "\n"; 
  r += tl::to_string (QObject::tr ("  -l <lyp file>       Use layer properties file")) + "\n";
  r += tl::to_string (QObject::tr ("  -lx                 With -l: add other layers as well")) + "\n";
  r += tl::to_string (QObject::tr ("  -lf                 With -l: use the lyp file as it is (no expansion to multiple layouts)")) + "\n";
  r += tl::to_string (QObject::tr ("  -m <database file>  Load RDB (report database) file (into previous layout view)")) + "\n";
  r += tl::to_string (QObject::tr ("  -n <technology>     Technology to use for next layout(s) on command line")) + "\n";
  r += tl::to_string (QObject::tr ("  -nn <tech file>     Technology file (.lyt) to use for next layout(s) on command line")) + "\n";
  r += tl::to_string (QObject::tr ("  -p <plugin>         Load the plugin (can be used multiple times)")) + "\n";
  r += tl::to_string (QObject::tr ("  -r <script>         Execute main script on startup (after having loaded files etc.)")) + "\n";
  r += tl::to_string (QObject::tr ("  -rm <script>        Execute module on startup (can be used multiple times)")) + "\n";
  r += tl::to_string (QObject::tr ("  -rd <name>=<value>  Specify skript variable")) + "\n";
  r += tl::to_string (QObject::tr ("  -rx                 Ignore all implicit macros (*.rbm, rbainit, *.lym)")) + "\n";
  r += tl::to_string (QObject::tr ("  -s                  Load files into same view")) + "\n";
  r += tl::to_string (QObject::tr ("  -t                  Don't update the configuration file on exit")) + "\n";
  r += tl::to_string (QObject::tr ("  -nt                 Update the configuration file on exit (default, overrides previous -t option)")) + "\n";
  r += tl::to_string (QObject::tr ("  -u <file name>      Restore session from given file")) + "\n";
  r += tl::to_string (QObject::tr ("  -v                  Print program version and exit")) + "\n";
  r += tl::to_string (QObject::tr ("  -wd <name>=<value>  Define a variable within expressions")) + "\n";
  r += tl::to_string (QObject::tr ("  -x                  Synchronous drawing mode")) + "\n";
  r += tl::to_string (QObject::tr ("  -y <package>        Package installation: install package(s) and exit - can be used more than once")) + "\n";
  r += tl::to_string (QObject::tr ("                      ('package' is a name, an URL and optionally a version in round brackets)")) + "\n";
  r += tl::to_string (QObject::tr ("  -yd                 With -y: include dependencies")) + "\n";
  r += tl::to_string (QObject::tr ("  -z                  Non-GUI mode (hidden views)")) + "\n";
  r += tl::to_string (QObject::tr ("  -zz                 Non-GUI mode (database only, implies -nc)")) + "\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
      plugin_root ()->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::auto_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 ();

  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 (mw->current_view () == 0) {
          mw->create_view ();
        }

        if (mw->current_view () != 0) {
          std::auto_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 ());
        }

      }
    }

    if (! m_layer_props_file.empty ()) {

      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 {
        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);
    }

    //  Give the plugins a change to do some last-minute initialisation and checks
    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 (plugin_root ());
    }

    if (! m_no_gui && m_gtf_replay.empty () && m_gtf_record.empty ()) {
      //  Show initial tip window if required
      mw->about_to_exec ();
    }

  } else if (plugin_root ()) {

    //  Give the plugins a change to do some last-minute initialisation and checks
    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 (plugin_root ());
    }

  }

  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 ();

  } else {
    result = exec ();
  }

  finish ();

  return result;
}

void
ApplicationBase::autorun ()
{
  //  call "autorun" on all plugins that wish so
  for (std::vector <lay::PluginDescriptor>::const_iterator p = m_native_plugins.begin (); p != m_native_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*/)
{
  //  The base class implementation does nothing ..
}

bool 
ApplicationBase::write_config (const std::string &config_file)
{
  return plugin_root () ? plugin_root ()->write_config (config_file) : 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 (plugin_root ()) {
    plugin_root ()->clear_config ();
  }
}

bool 
ApplicationBase::read_config (const std::string &config_file)
{
  return plugin_root () ? plugin_root ()->read_config (config_file) : true;
}

void 
ApplicationBase::set_config (const std::string &name, const std::string &value)
{
  if (plugin_root ()) {
    plugin_root ()->config_set (name, value);
  }
}

void 
ApplicationBase::config_end ()
{
  if (plugin_root ()) {
    plugin_root ()->config_end ();
  }
}

std::string 
ApplicationBase::get_config (const std::string &name) const
{
  if (plugin_root ()) {
    return plugin_root ()->config_get (name);
  } else {
    return std::string ();
  }
}

std::vector<std::string> 
ApplicationBase::get_config_names () const
{
  std::vector<std::string> names;
  if (plugin_root ()) {
    plugin_root ()->get_config_names (names);
  }
  return names;
}

bool
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 (false),
    mp_mw (0),
    mp_plugin_root (0),
    mp_recorder (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")));
#if QT_VERSION >= 0x040500
  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 ());
}

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 (plugin_root ());
  }

  shutdown ();

  delete mp_plugin_root;
  mp_plugin_root = 0;
}

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;
}

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
}

#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
bool
GuiApplication::event (QEvent *event)
{
  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);
      }
  }

  return QApplication::event(event);
}
#endif


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 << "";

  }

  return QApplication::exec ();
}

void
GuiApplication::shutdown ()
{
  if (mp_mw) {
    delete mp_mw;
    mp_mw = 0;
  }

  //  top-level widgets created from Ruby create a special issue:
  //  those are without parents and not deleted by QApplication. But after
  //  QApplication has died, they must not be deleted by Ruby, otherwise
  //  we'll get a segfault. So we clean up all of them, but not the ones
  //  Qt has created by itself (see GibHub #203).
  QWidgetList tl_widgets = topLevelWidgets ();
  for (QWidgetList::iterator w = tl_widgets.begin (); w != tl_widgets.end (); ++w) {
    if (dynamic_cast<gsi::ObjectBase *> (*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 &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::PluginRoot *
GuiApplication::plugin_root () const
{
  return mp_plugin_root;
}

void
GuiApplication::setup ()
{
  tl_assert (mp_mw == 0 && mp_plugin_root == 0);

  mp_plugin_root = new lay::PluginRootToMainWindow ();
  mp_mw = new lay::MainWindow (this, mp_plugin_root, "main_window");
  mp_plugin_root->attach_to (mp_mw);

  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)
{
  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 (true),
    mp_pr (0),
    mp_pb (0),
    mp_plugin_root (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 (plugin_root ());
  }

  shutdown ();
}

int
NonGuiApplication::exec ()
{
  //  A non-GUI application does nothing on 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 ();
}

}

