
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2017 Matthias Koefferlein

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include "layStream.h"
#include "layTechSetupDialog.h"
#include "layTechnology.h"
#include "tlExceptions.h"
#include "layFileDialog.h"
#include "layTipDialog.h"
#include "layMainWindow.h"
#include "layApplication.h"
#include "layMacroEditorTree.h"
#include "layMacro.h"
#include "tlAssert.h"
#include "tlStream.h"
#include "dbStream.h"

#include <QInputDialog>
#include <QMessageBox>
#include <QHeaderView>
#include <QFileDialog>
#include <QScrollArea>

#include <stdio.h>
#include <fstream>

namespace lay
{

// ----------------------------------------------------------------
//  TechBaseEditorPage implementation

TechBaseEditorPage::TechBaseEditorPage (QWidget *parent)
  : TechnologyComponentEditor (parent)
{
  Ui::TechBaseEditorPage::setupUi (this);
  connect (browse_pb, SIGNAL (clicked ()), this, SLOT (browse_clicked ()));
  connect (browse_lyp_pb, SIGNAL (clicked ()), this, SLOT (browse_lyp_clicked ()));
}

void 
TechBaseEditorPage::setup ()
{
  name_le->setText (tl::to_qstring (tech ()->name ()));
  desc_le->setText (tl::to_qstring (tech ()->description ()));
  dbu_le->setText (tl::to_qstring (tl::to_string (tech ()->dbu ())));
  desc_le->setEnabled (! tech ()->name ().empty ());
  base_path_le->setText (tl::to_qstring (tech ()->explicit_base_path ()));
#if QT_VERSION >= 0x040700
  base_path_le->setPlaceholderText (tl::to_qstring (tech ()->default_base_path ()));
#endif

  const std::string &lyp = tech ()->layer_properties_file ();
  lyp_grp->setChecked (! lyp.empty ());
  lyp_le->setText (tl::to_qstring (lyp));
  add_other_layers_cbx->setChecked (tech ()->add_other_layers ());
}

void 
TechBaseEditorPage::commit ()
{
  tech ()->set_description (tl::to_string (desc_le->text ()));
  tech ()->set_explicit_base_path (tl::to_string (base_path_le->text ()));

  double d = 0.001;
  tl::from_string (tl::to_string (dbu_le->text ()), d);
  tech ()->set_dbu (d);

  if (! lyp_grp->isChecked ()) {
    tech ()->set_layer_properties_file (std::string ());
    tech ()->set_add_other_layers (true);
  } else {
    tech ()->set_layer_properties_file (tl::to_string (lyp_le->text ()));
    tech ()->set_add_other_layers (add_other_layers_cbx->isChecked ());
  }
}

void 
TechBaseEditorPage::browse_clicked ()
{
  QString p = QFileDialog::getExistingDirectory (this, QObject::tr ("Choose Base Path"), 
                                                       base_path_le->text ());
  if (! p.isNull ()) {
    base_path_le->setText (p);
  }
}

void
TechBaseEditorPage::browse_lyp_clicked ()
{
  lay::FileDialog open_dialog (this,
                               tl::to_string (QObject::tr ("Browse Layer Properties File")),
                               tl::to_string (QObject::tr ("Layer properties files (*.lyp);;Text files (*.txt);;All files (*)")));

  std::string lyp = tech ()->base_path ();
  if (open_dialog.get_open (lyp)) {
    lyp_le->setText (tl::to_qstring (tech ()->correct_path (lyp)));
  }
}

// ----------------------------------------------------------------
//  TechLoadOptionsEditorPage implementation

TechLoadOptionsEditorPage::TechLoadOptionsEditorPage (QWidget *parent)
  : TechnologyComponentEditor (parent)
{
  Ui::TechLoadOptionsEditorPage::setupUi (this);

  while (options_tab->count () > 0) {
    options_tab->removeTab (0);
  }

  bool any_option = false;

  for (tl::Registrar<db::StreamFormatDeclaration>::iterator fmt = tl::Registrar<db::StreamFormatDeclaration>::begin (); fmt != tl::Registrar<db::StreamFormatDeclaration>::end (); ++fmt) {

    StreamReaderOptionsPage *page = 0;

    //  obtain the config page from the plugin which we identify by format name
    const StreamReaderPluginDeclaration *decl = StreamReaderPluginDeclaration::plugin_for_format (fmt->format_name ());
    if (decl) {
      QScrollArea *page_host = new QScrollArea (options_tab);
      page_host->setFrameStyle (QFrame::NoFrame);
      page_host->setWidgetResizable (true);
      page = decl->format_specific_options_page (options_tab);
      if (page) {
        page_host->setWidget (page);
        options_tab->addTab (page_host, tl::to_qstring (fmt->format_desc ()));
        any_option = true;
      } else {
        delete page_host;
      }
    }

#if 0
    //  Add dummy pages for empty options
    if (!page) {
      QLabel *empty = new QLabel (options_tab);
      empty->setAlignment (Qt::AlignCenter);
      empty->setText (QObject::tr ("No specific options available for this format"));
      options_tab->addTab (empty, tl::to_qstring (fmt->format_desc ()));
    }
#endif

    if (page) {
      m_pages.push_back (std::make_pair (page, fmt->format_name ()));
    }

  }

  if (! any_option) {
    options_tab->hide ();
  }
}

void
TechLoadOptionsEditorPage::setup ()
{
  for (std::vector< std::pair<StreamReaderOptionsPage *, std::string> >::iterator page = m_pages.begin (); page != m_pages.end (); ++page) {
    if (page->first) {
      page->first->setup (tech ()->load_layout_options ().get_options (page->second), tech ());
    }
  }
}

void
TechLoadOptionsEditorPage::commit ()
{
  //  create the particular options for all formats
  db::LoadLayoutOptions options = tech ()->load_layout_options ();
  for (std::vector< std::pair<StreamReaderOptionsPage *, std::string> >::iterator page = m_pages.begin (); page != m_pages.end (); ++page) {
    if (page->first) {
      db::FormatSpecificReaderOptions *specific_options = options.get_options (page->second);
      if (! specific_options) {
        specific_options = StreamReaderPluginDeclaration::plugin_for_format (page->second)->create_specific_options ();
        options.set_options (specific_options);
      }
      page->first->commit (specific_options, tech ());
    }
  }
  tech ()->set_load_layout_options (options);
}

// ----------------------------------------------------------------
//  TechSaveOptionsEditorPage implementation

TechSaveOptionsEditorPage::TechSaveOptionsEditorPage (QWidget *parent)
  : TechnologyComponentEditor (parent)
{
  Ui::TechSaveOptionsEditorPage::setupUi (this);

  while (options_tab->count () > 0) {
    options_tab->removeTab (0);
  }

  bool any_option = false;

  for (tl::Registrar<db::StreamFormatDeclaration>::iterator fmt = tl::Registrar<db::StreamFormatDeclaration>::begin (); fmt != tl::Registrar<db::StreamFormatDeclaration>::end (); ++fmt) {

    StreamWriterOptionsPage *page = 0;

    //  obtain the config page from the plugin which we identify by format name
    const StreamWriterPluginDeclaration *decl = StreamWriterPluginDeclaration::plugin_for_format (fmt->format_name ());
    if (decl) {
      QScrollArea *page_host = new QScrollArea (options_tab);
      page_host->setFrameStyle (QFrame::NoFrame);
      page_host->setWidgetResizable (true);
      page = decl->format_specific_options_page (options_tab);
      if (page) {
        page_host->setWidget (page);
        options_tab->addTab (page_host, tl::to_qstring (fmt->format_desc ()));
        any_option = true;
      } else {
        delete page_host;
      }
    }

#if 0
    //  Add dummy pages for empty options
    if (!page) {
      QLabel *empty = new QLabel (options_tab);
      empty->setAlignment (Qt::AlignCenter);
      empty->setText (QObject::tr ("No specific options available for this format"));
      options_tab->addTab (empty, tl::to_qstring (fmt->format_desc ()));
    }
#endif

    if (page) {
      m_pages.push_back (std::make_pair (page, fmt->format_name ()));
    }

  }

  if (! any_option) {
    options_tab->hide ();
  }
}

void
TechSaveOptionsEditorPage::setup ()
{
  for (std::vector< std::pair<StreamWriterOptionsPage *, std::string> >::iterator page = m_pages.begin (); page != m_pages.end (); ++page) {
    if (page->first) {
      page->first->setup (tech ()->save_layout_options ().get_options (page->second), tech ());
    }
  }
}

void
TechSaveOptionsEditorPage::commit ()
{
  //  create the particular options for all formats
  db::SaveLayoutOptions options = tech ()->save_layout_options ();
  for (std::vector< std::pair<StreamWriterOptionsPage *, std::string> >::iterator page = m_pages.begin (); page != m_pages.end (); ++page) {
    if (page->first) {
      db::FormatSpecificWriterOptions *specific_options = options.get_options (page->second);
      if (! specific_options) {
        specific_options = StreamWriterPluginDeclaration::plugin_for_format (page->second)->create_specific_options ();
        options.set_options (specific_options);
      }
      page->first->commit (specific_options, tech (), false /*gzip*/);
    }
  }
  tech ()->set_save_layout_options (options);
}

// ----------------------------------------------------------------
//  TechMacrosPage implementation

TechMacrosPage::TechMacrosPage (QWidget *parent, const std::string &cat, const std::string &cat_desc)
  : TechnologyComponentEditor (parent), m_cat (cat), m_cat_desc (cat_desc)
{
  Ui::TechMacrosPage::setupUi (this);

  m_original_labels.push_back (std::make_pair (title_label, title_label->text ()));
  m_original_labels.push_back (std::make_pair (note_label, note_label->text ()));
  m_original_labels.push_back (std::make_pair (empty_label1, empty_label1->text ()));
  m_original_labels.push_back (std::make_pair (empty_label2, empty_label2->text ()));
  m_original_labels.push_back (std::make_pair (empty_label3, empty_label3->text ()));

  folder_tree->header ()->hide ();
  connect (folder_tree, SIGNAL (clicked (const QModelIndex &)), this, SLOT (macro_selected (const QModelIndex &)));

  QFont f = macro_text->font ();
  f.setFixedPitch (true);
  f.setFamily (tl::to_qstring ("Monospace"));
  macro_text->setFont (f);

  connect (create_folder_button, SIGNAL (clicked ()), this, SLOT (create_folder_clicked ()));
}

TechMacrosPage::~TechMacrosPage ()
{
  //  do this before the collection gets deleted.
  delete folder_tree->model ();
}

void 
TechMacrosPage::setup ()
{
  title_label->show ();
  macro_frame->show ();
  note_label->show ();
  empty_label1->hide ();
  empty_label3->hide ();
  empty_label2_frame->hide ();

  QDir base_dir (tl::to_qstring (tech ()->base_path ()));
  QDir macro_dir (base_dir.filePath (tl::to_qstring (m_cat)));
  QString cp = macro_dir.canonicalPath ();

  //  if a macro collection already exists, show a readonly copy of this one
  const lay::MacroCollection *original = 0;
  const lay::MacroCollection *root = &lay::MacroCollection::root ();
  for (lay::MacroCollection::const_child_iterator m = root->begin_children (); m != root->end_children () && ! original; ++m) {
    if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m->second->category () == m_cat && QDir (tl::to_qstring (m->second->path ())).canonicalPath () == cp) {
      original = m->second;
    }
  }

  const lay::MacroCollection *alt = 0;
  for (lay::MacroCollection::const_child_iterator m = root->begin_children (); m != root->end_children () && ! alt; ++m) {
    if (m->second->virtual_mode () != lay::MacroCollection::TechFolder && QDir (tl::to_qstring (m->second->path ())).canonicalPath () == cp) {
      alt = m->second;
    }
  }

  //  adjust labels
  for (std::vector<std::pair<QLabel *, QString> >::const_iterator ol = m_original_labels.begin (); ol != m_original_labels.end (); ++ol) {
    QString l = ol->second;
    l.replace (QString::fromUtf8 ("%CAT%"), tl::to_qstring (m_cat));
    l.replace (QString::fromUtf8 ("%CAT_DESC%"), tl::to_qstring (m_cat_desc));
    l.replace (QString::fromUtf8 ("%BASE_PATH%"), tl::to_qstring (tech ()->base_path ()));
    if (alt) {
      l.replace (QString::fromUtf8 ("%ALT_DESC%"), tl::to_qstring (alt->description ()));
    } else {
      l.replace (QString::fromUtf8 ("%ALT_DESC%"), QString::fromUtf8 ("*unknown*"));
    }
    ol->first->setText (l);
  }

  if (tech ()->base_path ().empty ()) {

    //  no base path set
    title_label->hide ();
    empty_label1->show ();
    macro_frame->hide ();
    note_label->hide ();

  } else {
    
    if (! macro_dir.exists ()) {

      //  macro folder not found
      title_label->hide ();
      empty_label2_frame->show ();
      macro_frame->hide ();
      note_label->hide ();

    } else {
      
      //  valid macros to show
      std::string mp = tl::to_string (macro_dir.path ());
      if (mp_collection.get () && m_current_path == mp) {
        //  .. nothing to do ..
      } else {

        if (! original && alt) {

          //  this can happen, if the macro collection is already there in a different context.
          //  Show a message indicating that
          title_label->hide ();
          empty_label3->show ();
          macro_frame->hide ();
          note_label->hide ();

        } else {

          std::string desc;
          if (original) {
            desc = original->description ();
          } else {
            desc = tl::to_string (QObject::tr ("Technology")) + " - " + tech ()->name ();
          }

          lay::MacroCollection *mc = new lay::MacroCollection ();
          mp_collection.reset (mc);
          mc->add_folder (desc, mp, m_cat, true);
          m_current_path = mp;

          delete folder_tree->model ();
          folder_tree->setModel (new lay::MacroTreeModel (this, mc, m_cat));
          folder_tree->expandAll ();
          macro_text->hide ();

        }

      }

    }

  }
}

void
TechMacrosPage::create_folder_clicked ()
{
BEGIN_PROTECTED
  QString macro_dir = QDir (tl::to_qstring (tech ()->base_path ())).absoluteFilePath (tl::to_qstring (m_cat));
  if (! QDir::root ().mkpath (macro_dir)) {
    throw tl::Exception (tl::to_string (QObject::tr ("Failed to create folder: ")) + tl::to_string (macro_dir));
  }
  setup ();
END_PROTECTED
}

void
TechMacrosPage::macro_selected (const QModelIndex &index)
{
  const lay::Macro *m = 0;
  lay::MacroTreeModel *model = dynamic_cast<lay::MacroTreeModel *> (folder_tree->model ());
  if (model && model->is_valid_pointer (index.internalPointer ())) {
    m = dynamic_cast <lay::Macro *> ((QObject *) index.internalPointer ());
  }

  if (! m) {
    macro_text->hide ();
  } else {
    macro_text->show ();
    macro_text->setPlainText (tl::to_qstring (m->text ()));
  }
}

void 
TechMacrosPage::commit ()
{
  // .. noting yet ..
}

// ----------------------------------------------------------------
//  TechSetupDialog implementation

static bool s_first_show = true;

TechSetupDialog::TechSetupDialog (QWidget *parent)
  : QDialog (parent), mp_current_tech (0), mp_current_editor (0), mp_current_tech_component (0)
{
  setObjectName (QString::fromUtf8 ("tech_setup_dialog"));

  Ui::TechSetupDialog::setupUi (this);

  QAction *add_action = new QAction (QObject::tr ("Add Technology"), this);
  connect (add_action, SIGNAL (triggered ()), this, SLOT (add_clicked ()));
  QAction *delete_action = new QAction (QObject::tr ("Delete Technology"), this);
  connect (delete_action, SIGNAL (triggered ()), this, SLOT (delete_clicked ()));
  QAction *rename_action = new QAction (QObject::tr ("Rename Technology"), this);
  connect (rename_action, SIGNAL (triggered ()), this, SLOT (rename_clicked ()));
  QAction *import_action = new QAction (QObject::tr ("Import Technology"), this);
  connect (import_action, SIGNAL (triggered ()), this, SLOT (import_clicked ()));
  QAction *export_action = new QAction (QObject::tr ("Export Technology"), this);
  connect (export_action, SIGNAL (triggered ()), this, SLOT (export_clicked ()));

  tech_tree->addAction (add_action);
  tech_tree->addAction (delete_action);
  tech_tree->addAction (rename_action);
  QAction *separator = new QAction (this);
  separator->setSeparator (true);
  tech_tree->addAction (separator);
  tech_tree->addAction (import_action);
  tech_tree->addAction (export_action);

  tech_tree->header ()->hide ();
  connect (tech_tree, SIGNAL (currentItemChanged (QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_tech_changed (QTreeWidgetItem *, QTreeWidgetItem *)));
  connect (add_pb, SIGNAL (clicked ()), this, SLOT (add_clicked ()));
  connect (delete_pb, SIGNAL (clicked ()), this, SLOT (delete_clicked ()));
  connect (rename_pb, SIGNAL (clicked ()), this, SLOT (rename_clicked ()));

  if (s_first_show) {
    TipDialog td (this, 
                  tl::to_string (QObject::tr ("<html><body>To get started with the technology manager, read the documentation provided: <a href=\"int:/about/technology_manager.xml\">About Technology Management</a>.</body></html>")), 
                  "tech-manager-basic-tips");
    td.exec_dialog ();
    s_first_show = false;
  }
}

TechSetupDialog::~TechSetupDialog ()
{
  clear_components ();
}

void
TechSetupDialog::clear_components ()
{
  for (std::map <std::string, lay::TechnologyComponent *>::iterator tc = m_technology_components.begin (); tc != m_technology_components.end (); ++tc) {
    delete tc->second;
  }
  m_technology_components.clear ();

  for (std::map <std::string, lay::TechnologyComponentEditor *>::iterator tce = m_component_editors.begin (); tce != m_component_editors.end (); ++tce) {
    tc_stack->removeWidget (tce->second);
    delete tce->second;
  }
  m_component_editors.clear ();

  mp_current_editor = 0;
  mp_current_tech_component = 0;
}

int
TechSetupDialog::exec ()
{
  m_technologies = *lay::Technologies ().instance ();
  update_tech_tree ();
  tech_tree->setCurrentItem (tech_tree->topLevelItem (0));
  update_tech (selected_tech ());
  tc_stack->setMinimumSize (tc_stack->sizeHint ());

  int ret = QDialog::exec ();
  if (ret) {
    *lay::Technologies ().instance () = m_technologies;
  }

  //  clean up
  update_tech (0);
  clear_components ();
  m_technologies = lay::Technologies ();
  update_tech_tree ();

  return ret;
}

void 
TechSetupDialog::add_clicked ()
{
BEGIN_PROTECTED

  commit_tech_component ();

  lay::Technology *t = selected_tech ();
  if (! t) {
    t = m_technologies.technology_by_name (std::string ());
    tl_assert (t != 0);
  }

  std::string d = t->name ();
  if (! d.empty () && ! t->description ().empty ()) {
    d += " - ";
  }
  d += t->description ();

  bool ok = false;
  QString tn = QInputDialog::getText (this, QObject::tr ("Add Technology"), 
                                      tl::to_qstring (tl::sprintf (tl::to_string (QObject::tr ("This will create a new technology based on the selected technology '%s'.\nChoose a name for the new technology.")), d)), 
                                      QLineEdit::Normal,
                                      QString (), 
                                      &ok);
  if (ok && !tn.isEmpty()) {

    tn = tn.simplified ();

    if (m_technologies.has_technology (tl::to_string (tn))) {
      throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists")));
    }

    lay::Technology *nt = new lay::Technology (*t);
    nt->set_name (tl::to_string (tn));
    nt->set_description (std::string ());
    m_technologies.add (nt);

    update_tech_tree ();
    select_tech (*m_technologies.technology_by_name (tl::to_string (tn)));

  }

END_PROTECTED
}

void 
TechSetupDialog::delete_clicked ()
{
BEGIN_PROTECTED

  lay::Technology *t = selected_tech ();
  if (! t) {
    throw tl::Exception (tl::to_string (QObject::tr ("No technology selected")));
  }

  if (t->name ().empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("The default technology cannot be deleted")));
  }

  if (! t->is_persisted ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Auto-imported technologies cannot be deleted")));
  }

  if (QMessageBox::question (this, QObject::tr ("Deleting Technology"),
                                   QObject::tr ("Are you sure to delete this technology?\nThis operation cannot be undone, except by cancelling the technology manager."),
                                   QMessageBox::No | QMessageBox::Yes) == QMessageBox::Yes) {

    for (lay::Technologies::const_iterator i = m_technologies.begin (); i != m_technologies.end (); ++i) {

      if (i->name () == t->name ()) {

        m_technologies.remove (i->name ());
        update_tech_tree ();
        select_tech (*m_technologies.technology_by_name (std::string ()));

        break;

      }
    }

  }

END_PROTECTED
}

void 
TechSetupDialog::rename_clicked ()
{
BEGIN_PROTECTED

  commit_tech_component ();

  lay::Technology *t = selected_tech ();
  if (! t) {
    throw tl::Exception (tl::to_string (QObject::tr ("No technology selected")));
  }

  if (t->name ().empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("The default technology cannot be renamed")));
  }

  if (! t->is_persisted ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Auto-imported technologies cannot be renamed")));
  }

  bool ok = false;
  QString tn = QInputDialog::getText (this, QObject::tr ("Rename Technology"), 
                                      QObject::tr ("Choose a name for the technology"), 
                                      QLineEdit::Normal,
                                      tl::to_qstring (t->name ()), 
                                      &ok);
  if (ok && !tn.isEmpty()) {

    tn = tn.simplified ();

    if (m_technologies.has_technology (tl::to_string (tn))) {
      throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists")));
    }

    t->set_name (tl::to_string (tn));

    update_tech_tree ();
    select_tech (*t);

  }

END_PROTECTED
}

void 
TechSetupDialog::import_clicked ()
{
BEGIN_PROTECTED

  lay::FileDialog open_dialog (this, tl::to_string (QObject::tr ("Import Technology")), tl::to_string (QObject::tr ("KLayout technology files (*.lyt);;All files (*)")));

  std::string fn;
  if (open_dialog.get_open (fn)) {

    lay::Technology t;
    t.load (fn);

    if (m_technologies.has_technology (t.name ())) {
      *m_technologies.technology_by_name (t.name ()) = t;
    } else {
      m_technologies.add (new lay::Technology (t));
    }

    update_tech_tree ();
    select_tech (*m_technologies.technology_by_name (t.name ()));

  }

END_PROTECTED
}

void 
TechSetupDialog::export_clicked ()
{
BEGIN_PROTECTED

  lay::Technology *t = selected_tech ();
  if (! t) {
    throw tl::Exception (tl::to_string (QObject::tr ("No technology selected")));
  }

  lay::FileDialog save_dialog (this, tl::to_string (QObject::tr ("Export Technology")), tl::to_string (QObject::tr ("KLayout technology files (*.lyt);;All files (*)")));

  std::string fn;
  if (save_dialog.get_save (fn)) {
    t->save (fn);
  }

END_PROTECTED
}

void
TechSetupDialog::update_tech_tree ()
{
  tech_tree->clear ();

  std::map <std::string, const lay::Technology *> tech_by_name;
  for (lay::Technologies::const_iterator t = m_technologies.begin (); t != m_technologies.end (); ++t) {
    tech_by_name.insert (std::make_pair (t->name (), &*t));
  }

  for (std::map <std::string, const lay::Technology *>::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t) {
    
    QFont f (tech_tree->font ());
    f.setItalic (! t->second->is_persisted ());

    std::string d;
    d += t->first;
    if (! d.empty () && ! t->second->description ().empty ()) {
      d += " - ";
    }
    d += t->second->description ();

    QTreeWidgetItem *ti = new QTreeWidgetItem (tech_tree);
    ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d)));
    ti->setData (0, Qt::UserRole, QVariant (tl::to_qstring (t->first)));
    ti->setData (0, Qt::FontRole, QVariant (f));
    
    std::vector <std::string> tc_names = t->second->component_names ();
    std::map <std::string, const lay::TechnologyComponent *> tc_by_name;
    for (std::vector <std::string>::const_iterator n = tc_names.begin (); n != tc_names.end (); ++n) {
      tc_by_name.insert (std::make_pair (*n, t->second->component_by_name (*n)));
    }

    QTreeWidgetItem *tci = new QTreeWidgetItem (ti);
    tci->setData (0, Qt::DisplayRole, QVariant (QObject::tr ("General")));
    tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring ("_general")));
    tci->setData (0, Qt::FontRole, QVariant (f));

    tci = new QTreeWidgetItem (ti);
    tci->setData (0, Qt::DisplayRole, QVariant (QObject::tr ("Reader Options")));
    tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring ("_load_options")));
    tci->setData (0, Qt::FontRole, QVariant (f));

    tci = new QTreeWidgetItem (ti);
    tci->setData (0, Qt::DisplayRole, QVariant (QObject::tr ("Writer Options")));
    tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring ("_save_options")));
    tci->setData (0, Qt::FontRole, QVariant (f));

    const std::vector<std::pair<std::string, std::string> > &mc = lay::Application::instance ()->macro_categories ();
    for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
      tci = new QTreeWidgetItem (ti);
      tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second)));
      tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (std::string ("_macros_") + c->first)));
      tci->setData (0, Qt::FontRole, QVariant (f));
    }

    for (std::map <std::string, const lay::TechnologyComponent *>::const_iterator c = tc_by_name.begin (); c != tc_by_name.end (); ++c) {
      tci = new QTreeWidgetItem (ti);
      tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second->description ())));
      tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (c->first)));
      tci->setData (0, Qt::FontRole, QVariant (f));
    }

  }

}

void 
TechSetupDialog::update_tech (lay::Technology *t)
{
  if (t == mp_current_tech) {
    return;
  }

  mp_current_tech = t;

  clear_components ();

  if (t) {

    lay::TechnologyComponentEditor *tce_widget = new TechBaseEditorPage (this);
    tce_widget->setEnabled (t->is_persisted ());
    tce_widget->set_technology (t, 0);
    tc_stack->addWidget (tce_widget);
    m_component_editors.insert (std::make_pair (std::string ("_general"), tce_widget));

    const std::vector<std::pair<std::string, std::string> > &mc = lay::Application::instance ()->macro_categories ();
    for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
      tce_widget = new TechMacrosPage (this, c->first, c->second);
      tce_widget->setEnabled (t->is_persisted ());
      tce_widget->set_technology (t, 0);
      tc_stack->addWidget (tce_widget);
      m_component_editors.insert (std::make_pair (std::string ("_macros_") + c->first, tce_widget));
    }

    tce_widget = new TechLoadOptionsEditorPage (this);
    tce_widget->setEnabled (t->is_persisted ());
    tce_widget->set_technology (t, 0);
    tc_stack->addWidget (tce_widget);
    m_component_editors.insert (std::make_pair (std::string ("_load_options"), tce_widget));

    tce_widget = new TechSaveOptionsEditorPage (this);
    tce_widget->setEnabled (t->is_persisted ());
    tce_widget->set_technology (t, 0);
    tc_stack->addWidget (tce_widget);
    m_component_editors.insert (std::make_pair (std::string ("_save_options"), tce_widget));

    std::vector <std::string> tc_names = t->component_names ();
    for (std::vector <std::string>::const_iterator n = tc_names.begin (); n != tc_names.end (); ++n) {

      TechnologyComponent *tc = t->component_by_name (*n)->clone ();
      m_technology_components.insert (std::make_pair (*n, tc));

      tce_widget = tc->create_editor (this);
      if (tce_widget) {
        tce_widget->setEnabled (t->is_persisted ());
        tce_widget->set_technology (t, tc);
        tc_stack->addWidget (tce_widget);
        m_component_editors.insert (std::make_pair (tc->name (), tce_widget));
      }

    }

  }
}

void 
TechSetupDialog::update_tech_component ()
{
  std::string tc_name = selected_tech_component_name ();
  std::map <std::string, lay::TechnologyComponentEditor *>::const_iterator tce = m_component_editors.find (tc_name);
  if (tce != m_component_editors.end ()) {

    std::map <std::string, lay::TechnologyComponent *>::const_iterator tc = m_technology_components.find (tc_name);
    if (tc != m_technology_components.end ()) {
      mp_current_tech_component = tc->second;
    } else {
      mp_current_tech_component = 0;
    }

    tc_stack->setCurrentWidget (tce->second);
    mp_current_editor = tce->second;
    tce->second->setup ();

  } else {

    tc_stack->setCurrentIndex (0);
    mp_current_editor = 0;

  }
}

void
TechSetupDialog::select_tech (const lay::Technology &tech)
{
  //  unselect the previous technology
  update_tech (0); 

  //  find the item for the new technology
  QTreeWidgetItem *item = 0;
  for (int i = tech_tree->topLevelItemCount (); i > 0; --i) {
    item = tech_tree->topLevelItem (i - 1);
    if (item->data (0, Qt::UserRole).toString () == tl::to_qstring (tech.name ())) {
      break;
    }
  }

  tech_tree->setCurrentItem (item);
  
  update_tech (selected_tech ());
  update_tech_component ();
}

void
TechSetupDialog::accept ()
{
BEGIN_PROTECTED
  commit_tech_component ();
  QDialog::accept ();
END_PROTECTED
}

void
TechSetupDialog::current_tech_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous)
{
BEGIN_PROTECTED
  try {
    if (current) {
      commit_tech_component ();
      update_tech (selected_tech ());
      update_tech_component ();
    }
  } catch (...) {
    disconnect (tech_tree, SIGNAL (currentItemChanged (QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_tech_changed (QTreeWidgetItem *, QTreeWidgetItem *)));
    // TODO: this leaves current selected - any way to unselect it?
    tech_tree->setCurrentItem (previous);
    connect (tech_tree, SIGNAL (currentItemChanged (QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_tech_changed (QTreeWidgetItem *, QTreeWidgetItem *)));
    throw;
  }
END_PROTECTED
}

void
TechSetupDialog::commit_tech_component ()
{
  if (mp_current_editor) {
    mp_current_editor->commit ();
  }

  if (mp_current_tech && mp_current_tech_component && mp_current_tech->is_persisted ()) {

    mp_current_tech->set_component (mp_current_tech_component->clone ());

    //  because commit may have changed the description text, update the technology titles
    for (int i = tech_tree->topLevelItemCount (); i > 0; --i) {

      QTreeWidgetItem *item = tech_tree->topLevelItem (i - 1);

      lay::Technology *t = m_technologies.technology_by_name (tl::to_string (item->data (0, Qt::UserRole).toString ()));
      std::string d = t->name ();
      if (! d.empty () && ! t->description ().empty ()) {
        d += " - ";
      }
      d += t->description ();

      item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d)));

    }

  }
}

std::string 
TechSetupDialog::selected_tech_component_name ()
{
  QTreeWidgetItem *item = tech_tree->currentItem ();
  if (item) {
    QVariant d = item->data (0, Qt::UserRole + 1);
    if (d != QVariant ()) {
      return tl::to_string (d.toString ());
    }
  }

  return std::string ();
}

lay::Technology *
TechSetupDialog::selected_tech ()
{
  QTreeWidgetItem *item = tech_tree->currentItem ();
  while (item) {

    QVariant d = item->data (0, Qt::UserRole);
    if (d != QVariant ()) {
      std::string tn = tl::to_string (d.toString ());
      if (m_technologies.has_technology (tn)) {
        return m_technologies.technology_by_name (tn);
      }
    }

    //  try parent node.
    item = item->parent ();

  }

  return 0;
}

// ----------------------------------------------------------------
//  TechComponentSetupDialog implementation

TechComponentSetupDialog::TechComponentSetupDialog (QWidget *parent, Technology *tech, const std::string &component_name)
  : QDialog (parent),
    mp_tech (tech), mp_component (0), mp_editor (0)
{
  setObjectName (QString::fromUtf8 ("tech_component_setup_dialog"));

  Ui::TechComponentSetupDialog::setupUi (this);

  if (tech->name ().empty ()) {
    setWindowTitle (tl::to_qstring (tl::to_string (QObject::tr ("Edit Technology")) + " - " + tl::to_string (QObject::tr ("(Default)"))));
  } else {
    setWindowTitle (tl::to_qstring (tl::to_string (QObject::tr ("Edit Technology")) + " - " + tech->name ()));
  }

  const TechnologyComponent *component = tech->component_by_name (component_name);
  if (component) {

    mp_component = component->clone ();

    mp_editor = mp_component->create_editor (content_frame);
    if (mp_editor) {

      QVBoxLayout *layout = new QVBoxLayout (content_frame);
      layout->addWidget (mp_editor);
      layout->setMargin (0);
      content_frame->setLayout (layout);

      mp_editor->set_technology (tech, mp_component);
      mp_editor->setup ();

    }
  }
}

TechComponentSetupDialog::~TechComponentSetupDialog ()
{
  delete mp_component;
  mp_component = 0;
}

void 
TechComponentSetupDialog::accept ()
{
BEGIN_PROTECTED
  if (mp_editor && mp_tech && mp_component) {
    mp_editor->commit ();
    mp_tech->set_component (mp_component);
    mp_component = 0;
  }

  QDialog::accept ();
END_PROTECTED
}

}

