diff --git a/src/db/db.pro b/src/db/db.pro index 18190dbba..b43b57f12 100644 --- a/src/db/db.pro +++ b/src/db/db.pro @@ -230,6 +230,9 @@ HEADERS = \ dbGlyphs.h \ dbCommon.h +RESOURCES = \ + dbResources.qrc + INCLUDEPATH += ../tl ../gsi DEPENDPATH += ../tl ../gsi LIBS += -L$$DESTDIR -lklayout_tl -lklayout_gsi diff --git a/src/db/dbCIFReader.cc b/src/db/dbCIFReader.cc index d70eedf38..6e47030b3 100644 --- a/src/db/dbCIFReader.cc +++ b/src/db/dbCIFReader.cc @@ -917,6 +917,8 @@ CIFReader::read_cell (db::Layout &layout, db::Cell &cell, double sf, int level) void CIFReader::do_read (db::Layout &layout) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + try { double sf = 0.01 / m_dbu; diff --git a/src/db/dbDXFReader.cc b/src/db/dbDXFReader.cc index 42ea9763e..86cb7a393 100644 --- a/src/db/dbDXFReader.cc +++ b/src/db/dbDXFReader.cc @@ -490,6 +490,8 @@ DXFReader::open_layer (db::Layout &layout, const std::string &n) void DXFReader::do_read (db::Layout &layout, db::cell_index_type top) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + m_new_layers.clear (); // create the zero layer - this is not mapped to GDS but can be specified in the layer mapping as diff --git a/src/db/dbGDS2ReaderBase.cc b/src/db/dbGDS2ReaderBase.cc index e36afd08c..3447cafe2 100644 --- a/src/db/dbGDS2ReaderBase.cc +++ b/src/db/dbGDS2ReaderBase.cc @@ -233,6 +233,8 @@ eq_y (const GDS2XY &a, const GDS2XY &b) void GDS2ReaderBase::do_read (db::Layout &layout) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + m_cellname = ""; m_libname = ""; diff --git a/src/db/dbGlyphs.cc b/src/db/dbGlyphs.cc index 4954e2bea..112caaa28 100644 --- a/src/db/dbGlyphs.cc +++ b/src/db/dbGlyphs.cc @@ -299,12 +299,25 @@ TextGenerator::default_generator () return fonts.empty () ? 0 : &fonts [0]; } + +static std::vector s_font_paths; +static std::vector s_fonts; +static bool s_fonts_loaded = false; + +void +TextGenerator::set_font_paths (const std::vector &paths) +{ + s_font_paths = paths; + s_fonts.clear (); + s_fonts_loaded = false; +} + const std::vector & TextGenerator::generators () { - static std::vector m_fonts; + if (! s_fonts_loaded) { - if (m_fonts.empty ()) { + s_fonts.clear (); const char *resources[] = { ":/fonts/std_font.gds" @@ -312,42 +325,47 @@ TextGenerator::generators () for (size_t i = 0 ; i < sizeof (resources) / sizeof (resources [0]); ++i) { try { - m_fonts.push_back (TextGenerator ()); - m_fonts.back ().load_from_resource (resources [i]); + tl::log << "Loading font from resource " << resources [i] << " .."; + s_fonts.push_back (TextGenerator ()); + s_fonts.back ().load_from_resource (resources [i]); } catch (tl::Exception &ex) { tl::error << ex.msg (); - m_fonts.pop_back (); + s_fonts.pop_back (); } } - std::vector system_path = tl::get_klayout_path (); - // scan for font files - for (std::vector::const_iterator p = system_path.begin (); p != system_path.end (); ++p) { + for (std::vector::const_iterator p = s_font_paths.begin (); p != s_font_paths.end (); ++p) { - QDir fp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("fonts")); + QDir fp = QDir (tl::to_qstring (*p)); + if (fp.exists ()) { - QStringList name_filters; - name_filters << QString::fromUtf8 ("*"); + QStringList name_filters; + name_filters << QString::fromUtf8 ("*"); - QStringList font_files = fp.entryList (name_filters, QDir::Files); - for (QStringList::const_iterator ff = font_files.begin (); ff != font_files.end (); ++ff) { + QStringList font_files = fp.entryList (name_filters, QDir::Files); + for (QStringList::const_iterator ff = font_files.begin (); ff != font_files.end (); ++ff) { + + try { + tl::log << "Loading font from " << tl::to_string (fp.filePath (*ff)) << " .."; + s_fonts.push_back (TextGenerator ()); + s_fonts.back ().load_from_file (tl::to_string (fp.filePath (*ff))); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + s_fonts.pop_back (); + } - try { - m_fonts.push_back (TextGenerator ()); - m_fonts.back ().load_from_file (tl::to_string (fp.filePath (*ff))); - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - m_fonts.pop_back (); } } } + s_fonts_loaded = true; + } - return m_fonts; + return s_fonts; } } diff --git a/src/db/dbGlyphs.h b/src/db/dbGlyphs.h index 18d9be721..73a6d3864 100644 --- a/src/db/dbGlyphs.h +++ b/src/db/dbGlyphs.h @@ -188,6 +188,12 @@ public: */ static const std::vector &generators (); + /** + * @brief Sets the search path for font files + * The given folders are scanned for font files. + */ + static void set_font_paths (const std::vector &paths); + /** * @brief Returns the font with the given name * If no font with that name exsist, 0 is returned. diff --git a/src/db/dbLayout.cc b/src/db/dbLayout.cc index 14e6bccf0..3efa3a70e 100644 --- a/src/db/dbLayout.cc +++ b/src/db/dbLayout.cc @@ -1271,6 +1271,8 @@ Layout::update () const void Layout::do_update () { + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (QObject::tr ("Sorting"))); + // establish a progress report since this operation can take some time. // HINT: because of some gcc bug, automatic destruction of the tl::Progress // object does not work. We overcome this problem by creating the object with new diff --git a/src/db/dbLibrary.h b/src/db/dbLibrary.h index 6819e7c36..3c2907123 100644 --- a/src/db/dbLibrary.h +++ b/src/db/dbLibrary.h @@ -101,6 +101,25 @@ public: m_name = name; } + /** + * @brief Gets the technology name this library is associated with + * + * If this attribute is non-empty, the library is selected only when the given technology is + * used for the layout. + */ + const std::string &get_technology () const + { + return m_technology; + } + + /** + * @brief Sets the technology name this library is associated with + */ + void set_technology (const std::string &t) + { + m_technology = t; + } + /** * @brief Getter for the description property */ @@ -153,6 +172,7 @@ public: private: std::string m_name; std::string m_description; + std::string m_technology; lib_id_type m_id; db::Layout m_layout; std::map m_referrers; diff --git a/src/db/dbOASISReader.cc b/src/db/dbOASISReader.cc index 1863db72b..0da801454 100644 --- a/src/db/dbOASISReader.cc +++ b/src/db/dbOASISReader.cc @@ -655,7 +655,7 @@ static const char magic_bytes[] = { "%SEMI-OASIS\015\012" }; void OASISReader::do_read (db::Layout &layout) { - tl::SelfTimer timer (tl::verbosity () >= 31, "File read"); + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); unsigned char r; char *mb; diff --git a/src/db/dbPCellDeclaration.h b/src/db/dbPCellDeclaration.h index ea669a50a..f957e1868 100644 --- a/src/db/dbPCellDeclaration.h +++ b/src/db/dbPCellDeclaration.h @@ -453,7 +453,7 @@ public: */ const std::vector ¶meter_declarations () const { - if (! m_has_parameter_declarations) { + if (! m_has_parameter_declarations || ! wants_parameter_declaration_caching ()) { m_parameter_declarations = get_parameter_declarations (); m_has_parameter_declarations = true; } @@ -495,6 +495,18 @@ public: */ pcell_parameters_type map_parameters (const std::map &named_parameters) const; +protected: + /** + * @brief Gets a value indicating whether the PCell wants caching of the parameter declarations + * + * Some PCells with a dynamic parameter definition may not want paramater declaration caching. These + * PCells can override this method and return false. + */ + virtual bool wants_parameter_declaration_caching () const + { + return true; + } + private: int m_ref_count; pcell_id_type m_id; diff --git a/src/db/dbResources.qrc b/src/db/dbResources.qrc new file mode 100644 index 000000000..92058dab4 --- /dev/null +++ b/src/db/dbResources.qrc @@ -0,0 +1,5 @@ + + + std_font.gds + + diff --git a/src/db/gsiDeclDbLibrary.cc b/src/db/gsiDeclDbLibrary.cc index 64b357813..14f67a857 100644 --- a/src/db/gsiDeclDbLibrary.cc +++ b/src/db/gsiDeclDbLibrary.cc @@ -110,11 +110,25 @@ Class decl_Library ("Library", gsi::method ("description", &db::Library::get_description, "@brief Returns the libraries' description text\n" ) + - gsi::method ("description=", &db::Library::set_description, + gsi::method ("description=", &db::Library::set_description, "@brief Sets the libraries' description text\n" "@args description\n" ) + - gsi::method ("layout_const", (const db::Layout &(db::Library::*)() const) &db::Library::layout, + gsi::method ("technology", &db::Library::get_technology, + "@brief Returns name of the technology the library is associated with\n" + "If this attribute is a non-empty string, this library is only offered for " + "selection if the current layout uses this technology.\n" + "\n" + "This attribute has been introduced in version 0.25." + ) + + gsi::method ("technology=", &db::Library::set_technology, + "@brief sets the name of the technology the library is associated with\n" + "@args technology\n" + "\n" + "See \\technology for details. " + "This attribute has been introduced in version 0.25." + ) + + gsi::method ("layout_const", (const db::Layout &(db::Library::*)() const) &db::Library::layout, "@brief The layout object where the cells reside that this library defines (const version)\n" ) + gsi::method ("layout", (db::Layout &(db::Library::*)()) &db::Library::layout, diff --git a/src/lib/std_font.gds b/src/db/std_font.gds similarity index 100% rename from src/lib/std_font.gds rename to src/db/std_font.gds diff --git a/src/edt/edtEditorOptionsPages.cc b/src/edt/edtEditorOptionsPages.cc index a0ac0308f..2e8fb8f9f 100644 --- a/src/edt/edtEditorOptionsPages.cc +++ b/src/edt/edtEditorOptionsPages.cc @@ -691,6 +691,11 @@ EditorOptionsInst::setup (lay::Plugin *root) m_cv_index = lay::LayoutView::current ()->active_cellview_index (); } mp_ui->lib_cbx->update_list (); + if (m_cv_index >= 0 && lay::LayoutView::current () && lay::LayoutView::current ()->cellview (m_cv_index).is_valid ()) { + mp_ui->lib_cbx->set_technology_filter (lay::LayoutView::current ()->cellview (m_cv_index)->tech_name (), true); + } else { + mp_ui->lib_cbx->set_technology_filter (std::string (), false); + } // cell name std::string s; diff --git a/src/edt/edtInstPropertiesPage.cc b/src/edt/edtInstPropertiesPage.cc index 7bc7b1465..380d45063 100644 --- a/src/edt/edtInstPropertiesPage.cc +++ b/src/edt/edtInstPropertiesPage.cc @@ -223,6 +223,7 @@ InstPropertiesPage::update () const db::Cell &def_cell = def_layout->cell (def_cell_index); std::pair dl = def_layout->defining_library (def_cell_index); + lib_cbx->set_technology_filter (cv->tech_name (), true); lib_cbx->set_current_library (dl.first); if (dl.first) { def_layout = &dl.first->layout (); diff --git a/src/edt/edtPlugin.cc b/src/edt/edtPlugin.cc index 1021c61c5..8e56b8e94 100644 --- a/src/edt/edtPlugin.cc +++ b/src/edt/edtPlugin.cc @@ -239,7 +239,7 @@ public: menu_entries.push_back (lay::MenuEntry ("edt::sel_convert_to_pcell", "convert_to_pcell:edit_mode", "edit_menu.selection_menu.end", tl::to_string (QObject::tr ("Convert To PCell")))); menu_entries.push_back (lay::MenuEntry ("edt::sel_convert_to_cell", "convert_to_cell:edit_mode", "edit_menu.selection_menu.end", tl::to_string (QObject::tr ("Convert To Static Cell")))); - menu_entries.push_back (lay::MenuEntry ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end", tl::to_string (QObject::tr ("Combine{Select background combination mode}")))); + menu_entries.push_back (lay::MenuEntry ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end_modes", tl::to_string (QObject::tr ("Combine{Select background combination mode}")))); } bool configure (const std::string &name, const std::string &value) diff --git a/src/lay/LogViewerDialog.ui b/src/lay/LogViewerDialog.ui index eeb86327f..889342ade 100644 --- a/src/lay/LogViewerDialog.ui +++ b/src/lay/LogViewerDialog.ui @@ -1,117 +1,213 @@ - + + LogViewerDialog - - + + 0 0 - 578 - 579 + 516 + 287 - + Log Viewer - - + + 9 - + + 9 + + + 9 + + + 9 + + 6 - - - - Clear - - - - - - - Separator - - - - - - - Copy - - - - - - - Qt::Horizontal - - - - 101 - 22 - - - - - - - - Verbosity - - - - - + + - + Silent - + Information - + Details - + Verbose - + Noisy - - - - QListView::Adjust - - - true + + + + Separator - - - + + + + Copy + + + + + + + Clear + + + + + + + Verbosity + + + + + + + + 16 + 16 + + + + false + + + QListView::Adjust + + + true + + + false + + + + + + Qt::Horizontal - - QDialogButtonBox::Close + + + 101 + 0 + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + :/warn_16.png + + + + + + + There are errors or warnings + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + - + + verbosity_cbx + clear_pb + separator_pb + copy_pb + log_view + + + + buttonBox @@ -119,11 +215,11 @@ LogViewerDialog accept() - + 248 254 - + 157 274 @@ -135,11 +231,11 @@ LogViewerDialog reject() - + 316 260 - + 286 274 diff --git a/src/lay/SaltGrainPropertiesDialog.ui b/src/lay/SaltGrainPropertiesDialog.ui new file mode 100644 index 000000000..f503a7832 --- /dev/null +++ b/src/lay/SaltGrainPropertiesDialog.ui @@ -0,0 +1,921 @@ + + + SaltGrainPropertiesDialog + + + + 0 + 0 + 693 + 538 + + + + Package Properties + + + + + + + 0 + 1 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 75 + true + + + + Description + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + 0 + 0 + + + + + true + + + + (enter an URL to provide a documentation link) + + + + + + + + + + + 75 + true + + + + Depends on + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + + + + 0 + 0 + + + + <a href="%1">Open link</a> + + + true + + + + + + + Qt::Vertical + + + + 20 + 32 + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Delete dependency + + + ... + + + + :/clear.png:/clear.png + + + true + + + + + + + Add new dependency + + + ... + + + + :/add.png:/add.png + + + true + + + + + + + QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + true + + + + Name + + + + + Version + + + + + URL + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + 75 + true + + + + Title + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + API version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Author contact + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + 6 + + + + + Reset Screenshot + + + ... + + + + :/clear_edit.png:/clear_edit.png + + + true + + + + + + + Icon +(64x64) + + + + + + + Select an Icon Image + + + ... + + + + :/salt_icon.png:/salt_icon.png + + + + 64 + 64 + + + + + + + + Select a Screenshot Image + + + ... + + + + :/add.png:/add.png + + + + 64 + 64 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Reset Icon + + + ... + + + + :/clear_edit.png:/clear_edit.png + + + true + + + + + + + Showcase image +(max 1024x1024) + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + true + + + + (license information like "GPLv3" or "MIT") + + + + + + + + 75 + true + + + + Version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Images + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + true + + + + (use numeric versions like "1.5" or "2.1.3") + + + + + + + + 75 + true + + + + License + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Documentation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 16777215 + 80 + + + + + + + + + 75 + true + + + + Author + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + true + + + + (API version required - i.e. "0.25") + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + lay::AlertLogButton + QToolButton +
layLogViewerDialog.h
+
+
+ + version + title + author + author_contact + license + doc + doc_url + icon_config_button + icon_delete_button + screenshot_config_button + screenshot_delete_button + dependencies + add_dependency + remove_dependency + + + + + + + buttonBox + accepted() + SaltGrainPropertiesDialog + accept() + + + 546 + 425 + + + 561 + 435 + + + + + buttonBox + rejected() + SaltGrainPropertiesDialog + reject() + + + 638 + 418 + + + 649 + 433 + + + + +
diff --git a/src/lay/SaltGrainTemplateSelectionDialog.ui b/src/lay/SaltGrainTemplateSelectionDialog.ui new file mode 100644 index 000000000..65406eb88 --- /dev/null +++ b/src/lay/SaltGrainTemplateSelectionDialog.ui @@ -0,0 +1,237 @@ + + + SaltGrainTemplateSelectionDialog + + + + 0 + 0 + 401 + 499 + + + + Create Package + + + + + + + 75 + true + + + + Template + + + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + true + + + + (Pick a template with which to initialize your new package) + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 75 + true + + + + Package Name + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + 50 + true + false + + + + (Choose a simple name composed of letters, digits and underscores. Use the notation "group/package" to create a package inside a group) + + + true + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + + :/import.png:/import.png + + + Import + + + Import package + + + + + + lay::AlertLogButton + QToolButton +
layLogViewerDialog.h
+
+
+ + + + + + buttonBox + accepted() + SaltGrainTemplateSelectionDialog + accept() + + + 273 + 431 + + + 276 + 448 + + + + + buttonBox + rejected() + SaltGrainTemplateSelectionDialog + reject() + + + 351 + 426 + + + 363 + 445 + + + + +
diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui new file mode 100644 index 000000000..a0d9944c6 --- /dev/null +++ b/src/lay/SaltManagerDialog.ui @@ -0,0 +1,1144 @@ + + + SaltManagerDialog + + + + 0 + 0 + 692 + 440 + + + + Salt Package Manager + + + + + + 2 + + + + Current Packages + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + + + 4 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::NoFocus + + + Uninstall Package + + + ... + + + + :/clear.png:/clear.png + + + true + + + + + + + Qt::Horizontal + + + + 50 + 0 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 537 + 284 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + :/salt.png + + + + + + + + 0 + 0 + + + + + 9 + 50 + false + + + + <h1>Salt Package Manager</h1> + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + <html><body><h4>No packages are installed currently.</h4><p>Use<br/> +<table> + <tr><td>The "Install New Packages" tab to install a package<br/>from an external repository</td></tr> + <tr></tr> + <tr><td>The <a href=":add"><img src=":/add.png"></a> button to create a new package</td></tr> +</table> +</body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + Edit Package Details + + + Edit + + + + :/edit.png:/edit.png + + + true + + + + + + + true + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Create Package + + + false + + + + + + + Qt::Horizontal + + + + 540 + 20 + + + + + + + + + + + + Update Packages + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + + + 4 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mark or unmark for installation + + + Mark + + + + :/marked_16.png:/marked_16.png + + + true + + + + + + + Qt::Horizontal + + + + 126 + 20 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + false + + + ... + + + + :/empty_16.png:/empty_16.png + + + true + + + + + + + + + + true + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Apply + + + false + + + false + + + + + + + Set in code + + + + + + + Qt::Horizontal + + + + 563 + 20 + + + + + + + + + + + + Install New Packages + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + + + 4 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mark or unmark for installation + + + Mark + + + + :/marked_16.png:/marked_16.png + + + true + + + + + + + Qt::Horizontal + + + + 126 + 20 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + false + + + ... + + + + :/empty_16.png:/empty_16.png + + + true + + + + + + + + + + true + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Apply + + + false + + + false + + + + + + + Set in code + + + + + + + Qt::Horizontal + + + + 563 + 20 + + + + + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + Unmark all + + + + + Show marked only + + + + + Show all + + + + + Refresh + + + Reload package repository + + + + + Show all + + + + + Show marked only + + + + + Unmark all + + + + + + lay::DecoratedLineEdit + QLineEdit +
layWidgets.h
+
+ + lay::SaltGrainDetailsTextWidget + QTextBrowser +
laySaltGrainDetailsTextWidget.h
+
+
+ + mode_tab + search_installed_edit + salt_view + details_text + edit_button + search_new_edit + mark_new_button + salt_mine_view_new + details_new_text + toolButton + apply_new_button + scrollArea + + + + + + + buttonBox + clicked(QAbstractButton*) + SaltManagerDialog + accept() + + + 635 + 421 + + + 653 + 432 + + + + +
diff --git a/src/lay/SaltManagerInstallConfirmationDialog.ui b/src/lay/SaltManagerInstallConfirmationDialog.ui new file mode 100644 index 000000000..d8dcf20e4 --- /dev/null +++ b/src/lay/SaltManagerInstallConfirmationDialog.ui @@ -0,0 +1,165 @@ + + + SaltManagerInstallConfirmationDialog + + + + 0 + 0 + 495 + 478 + + + + Ready for Installation + + + + + + The following packages are now ready for installation or update: + + + true + + + + + + + Qt::NoFocus + + + false + + + true + + + + Package + + + + + Action + + + + + Version + + + + + Download link + + + + + + + + Press "Ok" to install or update these packages or "Cancel" to abort. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + + :/import.png:/import.png + + + Import + + + Import package + + + + + + + + + buttonBox + accepted() + SaltManagerInstallConfirmationDialog + accept() + + + 273 + 431 + + + 276 + 448 + + + + + buttonBox + rejected() + SaltManagerInstallConfirmationDialog + reject() + + + 351 + 426 + + + 363 + 445 + + + + + diff --git a/src/lay/TechSetupDialog.ui b/src/lay/TechSetupDialog.ui index 490dc3654..56a85c4d9 100644 --- a/src/lay/TechSetupDialog.ui +++ b/src/lay/TechSetupDialog.ui @@ -115,6 +115,9 @@ :/add.png:/add.png
+ + true +
@@ -126,6 +129,9 @@ :/clear.png:/clear.png + + true + diff --git a/src/lay/gsiDeclLayApplication.cc b/src/lay/gsiDeclLayApplication.cc index 6c4321d24..e5c89415d 100644 --- a/src/lay/gsiDeclLayApplication.cc +++ b/src/lay/gsiDeclLayApplication.cc @@ -23,6 +23,7 @@ #include "layApplication.h" #include "layMainWindow.h" +#include "laySignalHandler.h" #include "gsiDecl.h" #include "gsiQtExternals.h" @@ -68,7 +69,7 @@ Class decl_Application (QT_EXTERNAL_BASE (QApplication) "Appli "method." ) + method ("crash_me", &crash_me, "@hide") + - method ("symname", &lay::Application::symbol_name_from_address, "@hide") + + method ("symname", &lay::get_symbol_name_from_address, "@hide") + method ("is_editable?", &lay::Application::is_editable, "@brief Returns true if the application is in editable mode\n" ) + diff --git a/src/lay/gsiDeclLayMainWindow.cc b/src/lay/gsiDeclLayMainWindow.cc index a64fad18d..77c8eb45e 100644 --- a/src/lay/gsiDeclLayMainWindow.cc +++ b/src/lay/gsiDeclLayMainWindow.cc @@ -539,6 +539,10 @@ Class decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "MainWind "@brief 'cm_technologies' action (bound to a menu)" "\nThis method has been added in version 0.22." ) + + gsi::method ("cm_packages", &lay::MainWindow::cm_packages, + "@brief 'cm_packages' action (bound to a menu)" + "\nThis method has been added in version 0.25." + ) + gsi::method ("cm_open_too", &lay::MainWindow::cm_open_too, "@brief 'cm_open_too' action (bound to a menu)" ) + diff --git a/src/lay/images/empty_12.png b/src/lay/images/empty_12.png new file mode 100644 index 000000000..989ee8a3f Binary files /dev/null and b/src/lay/images/empty_12.png differ diff --git a/src/lay/images/empty_16.png b/src/lay/images/empty_16.png new file mode 100644 index 000000000..10b1f1be6 Binary files /dev/null and b/src/lay/images/empty_16.png differ diff --git a/src/lay/images/error_16.png b/src/lay/images/error_16.png new file mode 100644 index 000000000..c9dad1310 Binary files /dev/null and b/src/lay/images/error_16.png differ diff --git a/src/lay/images/file_12.png b/src/lay/images/file_12.png new file mode 100644 index 000000000..5113c3262 Binary files /dev/null and b/src/lay/images/file_12.png differ diff --git a/src/lay/images/folder_12.png b/src/lay/images/folder_12.png new file mode 100644 index 000000000..99459e601 Binary files /dev/null and b/src/lay/images/folder_12.png differ diff --git a/src/lay/images/info_16.png b/src/lay/images/info_16.png new file mode 100644 index 000000000..d1fa36644 Binary files /dev/null and b/src/lay/images/info_16.png differ diff --git a/src/lay/images/marked_16.png b/src/lay/images/marked_16.png new file mode 100644 index 000000000..777a625a7 Binary files /dev/null and b/src/lay/images/marked_16.png differ diff --git a/src/lay/images/marked_24.png b/src/lay/images/marked_24.png new file mode 100644 index 000000000..cf50321a8 Binary files /dev/null and b/src/lay/images/marked_24.png differ diff --git a/src/lay/images/marked_64.png b/src/lay/images/marked_64.png new file mode 100644 index 000000000..02c23d603 Binary files /dev/null and b/src/lay/images/marked_64.png differ diff --git a/src/lay/images/salt.png b/src/lay/images/salt.png new file mode 100644 index 000000000..181409a28 Binary files /dev/null and b/src/lay/images/salt.png differ diff --git a/src/lay/images/salt_icon.png b/src/lay/images/salt_icon.png new file mode 100644 index 000000000..ec11c7ac0 Binary files /dev/null and b/src/lay/images/salt_icon.png differ diff --git a/src/lay/images/warn.png b/src/lay/images/warn.png new file mode 100644 index 000000000..adea92d4f Binary files /dev/null and b/src/lay/images/warn.png differ diff --git a/src/lay/images/warn_16.png b/src/lay/images/warn_16.png new file mode 100644 index 000000000..357216719 Binary files /dev/null and b/src/lay/images/warn_16.png differ diff --git a/src/lay/lay.pro b/src/lay/lay.pro index bd89c9af8..079155bfd 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -41,13 +41,25 @@ HEADERS = \ laySelectCellViewForm.h \ laySession.h \ laySettingsForm.h \ - layTechnologySelector.h \ layTechSetupDialog.h \ layTextProgress.h \ layVersion.h \ layCommon.h \ layConfig.h \ - layMacroController.h + layMacroController.h \ + layTechnologyController.h \ + laySalt.h \ + laySaltGrain.h \ + laySaltGrains.h \ + laySaltManagerDialog.h \ + laySaltGrainDetailsTextWidget.h \ + laySaltGrainPropertiesDialog.h \ + laySaltDownloadManager.h \ + laySaltModel.h \ + laySaltController.h \ + laySignalHandler.h \ + layLibraryController.h \ + layFontController.h FORMS = \ ClipDialog.ui \ @@ -92,7 +104,11 @@ FORMS = \ XORToolDialog.ui \ TechLoadOptionsEditorPage.ui \ TechSaveOptionsEditorPage.ui \ - MainConfigPage7.ui + MainConfigPage7.ui \ + SaltManagerDialog.ui \ + SaltGrainPropertiesDialog.ui \ + SaltGrainTemplateSelectionDialog.ui \ + SaltManagerInstallConfirmationDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -132,17 +148,30 @@ SOURCES = \ laySelectCellViewForm.cc \ laySession.cc \ laySettingsForm.cc \ - layTechnologySelector.cc \ layTechSetupDialog.cc \ layTextProgress.cc \ layVersion.cc \ - layMacroController.cc + layMacroController.cc \ + layTechnologyController.cc \ + laySalt.cc \ + laySaltGrain.cc \ + laySaltGrains.cc \ + laySaltManagerDialog.cc \ + laySaltGrainDetailsTextWidget.cc \ + laySaltGrainPropertiesDialog.cc \ + laySaltDownloadManager.cc \ + laySaltModel.cc \ + laySaltController.cc \ + laySignalHandler.cc \ + layLibraryController.cc \ + layFontController.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ layLayoutStatistics.qrc \ layMacroTemplates.qrc \ layResources.qrc \ + laySaltTemplates.qrc INCLUDEPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt DEPENDPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index c74bb0f4c..896f28e49 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -22,18 +22,21 @@ #include "layApplication.h" +#include "laySignalHandler.h" #include "laybasicConfig.h" #include "layConfig.h" #include "layMainWindow.h" #include "layMacroEditorDialog.h" #include "layVersion.h" #include "layMacro.h" -#include "layCrashMessage.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 "gtf.h" #include "gsiDecl.h" #include "gsiInterpreter.h" @@ -55,32 +58,20 @@ #include #include -#include #include #include #include #include -#ifdef _WIN32 -# include -# include -# include -// get rid of these - we have std::min/max .. -# ifdef min -# undef min -# endif -# ifdef max -# undef max -# endif -#else -# include -# include -#endif - #include #include #include -#include + +#ifdef _WIN32 +# include +#else +# include +#endif namespace lay { @@ -90,6 +81,9 @@ namespace lay 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 ()) { @@ -132,6 +126,9 @@ static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) 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 ()) { @@ -147,6 +144,9 @@ static void ui_exception_handler_std (const std::exception &ex, QWidget *parent) 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 ()) { @@ -163,292 +163,6 @@ static void ui_exception_handler_def (QWidget *parent) static Application *ms_instance = 0; -#if defined(WIN32) - -static QString -addr2symname (DWORD64 addr) -{ - const int max_symbol_length = 255; - - SYMBOL_INFO *symbol = (SYMBOL_INFO *) calloc (sizeof (SYMBOL_INFO) + (max_symbol_length + 1) * sizeof (char), 1); - symbol->MaxNameLen = max_symbol_length; - symbol->SizeOfStruct = sizeof (SYMBOL_INFO); - - HANDLE process = GetCurrentProcess (); - - QString sym_name; - DWORD64 d; - bool has_symbol = false; - DWORD64 disp = addr; - if (SymFromAddr(process, addr, &d, symbol)) { - // Symbols taken from the export table seem to be unreliable - skip these - // and report the module name + offset. - if (! (symbol->Flags & SYMFLAG_EXPORT)) { - sym_name = QString::fromLocal8Bit (symbol->Name); - disp = d; - has_symbol = true; - } - } - - // find the module name from the module base address - - HMODULE modules[1024]; - DWORD modules_size = 0; - if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { - modules_size = 0; - } - - QString mod_name; - for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { - TCHAR mn[MAX_PATH]; - if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { - MODULEINFO mi; - if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { - if ((DWORD64) mi.lpBaseOfDll <= addr && (DWORD64) mi.lpBaseOfDll + mi.SizeOfImage > addr) { - mod_name = QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName (); - if (! has_symbol) { - disp -= (DWORD64) mi.lpBaseOfDll; - } - break; - } - } - } - } - - if (! mod_name.isNull ()) { - mod_name = QString::fromUtf8 ("(") + mod_name + QString::fromUtf8 (") "); - } - - free (symbol); - - return QString::fromUtf8 ("0x%1 - %2%3+%4"). - arg (addr, 0, 16). - arg (mod_name). - arg (sym_name). - arg (disp); -} - -static QString -get_symbol_name_from_address (const QString &mod_name, size_t addr) -{ - HANDLE process = GetCurrentProcess (); - - DWORD64 mod_base = 0; - if (! mod_name.isEmpty ()) { - - // find the module name from the module base address - HMODULE modules[1024]; - DWORD modules_size = 0; - if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { - modules_size = 0; - } - - for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { - TCHAR mn[MAX_PATH]; - if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { - if (mod_name == QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName ()) { - MODULEINFO mi; - if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { - mod_base = (DWORD64) mi.lpBaseOfDll; - } - } - } - } - - if (mod_base == 0) { - throw tl::Exception (tl::to_string (QObject::tr ("Unknown module name: ") + mod_name)); - } - - } - - SymInitialize (process, NULL, TRUE); - QString res = addr2symname (mod_base + (DWORD64) addr); - SymCleanup (process); - - return res; -} - -LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) -{ - HANDLE process = GetCurrentProcess (); - SymInitialize (process, NULL, TRUE); - - QString text; - text += QObject::tr ("Exception code: 0x%1\n").arg (pExceptionInfo->ExceptionRecord->ExceptionCode, 0, 16); - text += QObject::tr ("Program Version: ") + - QString::fromUtf8 (lay::Version::name ()) + - QString::fromUtf8 (" ") + - QString::fromUtf8 (lay::Version::version ()) + - QString::fromUtf8 (" (") + - QString::fromUtf8 (lay::Version::subversion ()) + - QString::fromUtf8 (")"); -#if defined(_WIN64) - text += QString::fromUtf8 (" AMD64"); -#else - text += QString::fromUtf8 (" x86"); -#endif - text += QString::fromUtf8 ("\n"); - text += QObject::tr ("\nBacktrace:\n"); - - CONTEXT context_record = *pExceptionInfo->ContextRecord; - - // Initialize stack walking. - STACKFRAME64 stack_frame; - memset(&stack_frame, 0, sizeof(stack_frame)); - -#if defined(_WIN64) - int machine_type = IMAGE_FILE_MACHINE_AMD64; - stack_frame.AddrPC.Offset = context_record.Rip; - stack_frame.AddrFrame.Offset = context_record.Rbp; - stack_frame.AddrStack.Offset = context_record.Rsp; -#else - int machine_type = IMAGE_FILE_MACHINE_I386; - stack_frame.AddrPC.Offset = context_record.Eip; - stack_frame.AddrFrame.Offset = context_record.Ebp; - stack_frame.AddrStack.Offset = context_record.Esp; -#endif - stack_frame.AddrPC.Mode = AddrModeFlat; - stack_frame.AddrFrame.Mode = AddrModeFlat; - stack_frame.AddrStack.Mode = AddrModeFlat; - - while (StackWalk64 (machine_type, - GetCurrentProcess(), - GetCurrentThread(), - &stack_frame, - &context_record, - NULL, - &SymFunctionTableAccess64, - &SymGetModuleBase64, - NULL)) { - text += addr2symname (stack_frame.AddrPC.Offset); - text += QString::fromUtf8 ("\n"); - } - - SymCleanup (process); - - // YES! I! KNOW! - // In a signal handler you shall not do fancy stuff (in particular not - // open dialogs) nor shall you throw exceptions! But that scheme appears to - // be working since in most cases the signal is raised from our code (hence - // from our stack frames) and everything is better than just showing - // the "application stopped working" dialog. - // Isn't it? - - CrashMessage msg (0, true, text); - if (! msg.exec ()) { - // terminate unconditionally - return EXCEPTION_EXECUTE_HANDLER; - } else { - throw tl::CancelException (); - } -} - -static void handle_signal (int signo) -{ - signal (signo, handle_signal); - int user_base = (1 << 29); - RaiseException(signo + user_base, 0, 0, NULL); -} - -static void install_signal_handlers () -{ - // disable any signal handlers that Ruby might have installed. - signal (SIGSEGV, SIG_DFL); - signal (SIGILL, SIG_DFL); - signal (SIGFPE, SIG_DFL); - - signal (SIGABRT, handle_signal); - -#if 0 - // TODO: not available to MinGW - linking against msvc100 would help - // but then the app crashes. - _set_abort_behavior( 0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT ); -#endif - - SetUnhandledExceptionFilter(ExceptionHandler); -} - -#else - -QString get_symbol_name_from_address (const QString &, size_t) -{ - return QString::fromUtf8 ("n/a"); -} - -void signal_handler (int signo, siginfo_t *si, void *) -{ - void *array [100]; - - bool can_resume = (signo != SIGILL); - - size_t nptrs = backtrace (array, sizeof (array) / sizeof (array[0])); - - QString text; - text += QObject::tr ("Signal number: %1\n").arg (signo); - text += QObject::tr ("Address: 0x%1\n").arg ((size_t) si->si_addr, 0, 16); - text += QObject::tr ("Program Version: ") + - QString::fromUtf8 (lay::Version::name ()) + - QString::fromUtf8 (" ") + - QString::fromUtf8 (lay::Version::version ()) + - QString::fromUtf8 (" (") + - QString::fromUtf8 (lay::Version::subversion ()) + - QString::fromUtf8 (")"); - text += QString::fromUtf8 ("\n"); - text += QObject::tr ("Backtrace:\n"); - - char **symbols = backtrace_symbols (array, nptrs); - if (symbols == NULL) { - text += QObject::tr ("-- Unable to obtain stack trace --"); - } else { - for (size_t i = 2; i < nptrs; i++) { - text += QString::fromUtf8 (symbols [i]) + QString::fromUtf8 ("\n"); - } - } - free(symbols); - - // YES! I! KNOW! - // In a signal handler you shall not do fancy stuff (in particular not - // open dialogs) nor shall you throw exceptions! But that scheme appears to - // be working since in most cases the signal is raised from our code (hence - // from our stack frames) and everything is better than just core dumping. - // Isn't it? - - CrashMessage msg (0, can_resume, text); - if (! msg.exec ()) { - - _exit (signo); - - } else { - - sigset_t x; - sigemptyset (&x); - sigaddset(&x, signo); - sigprocmask(SIG_UNBLOCK, &x, NULL); - - throw tl::CancelException (); - - } -} - -static void install_signal_handlers () -{ - struct sigaction act; - act.sa_sigaction = signal_handler; - sigemptyset (&act.sa_mask); - act.sa_flags = SA_SIGINFO; -#if !defined(__APPLE__) - act.sa_restorer = 0; -#endif - - sigaction (SIGSEGV, &act, NULL); - sigaction (SIGILL, &act, NULL); - sigaction (SIGFPE, &act, NULL); - sigaction (SIGABRT, &act, NULL); - sigaction (SIGBUS, &act, NULL); -} - -#endif - static void load_plugin (const std::string &pp) { #ifdef _WIN32 @@ -577,47 +291,6 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } - // try to locate a global rbainit file and rbm modules - std::vector global_modules; - std::set modules; - - // try to locate a global plugins - for (std::vector ::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 ()) { - global_modules.push_back (m); - modules.insert (m); - } - } - } - - } - // try to locate the global plugins for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { @@ -888,52 +561,6 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) mp_ruby_interpreter = new rba::RubyInterpreter (); mp_python_interpreter = new pya::PythonInterpreter (); - if (! m_no_gui) { - // Install the signal handlers after the interpreters, so we can be sure we - // installed our handler. - install_signal_handlers (); - } - - if (! m_no_macros) { - // Add the global ruby modules as the first ones. - m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); - } - - // Scan built-in macros - // These macros are always taken, even if there are no macros requested (they are required to - // fully form the API). - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-macros", "macros", true); - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-pymacros", "pymacros", true); - - m_macro_categories.push_back (std::pair ("macros", tl::to_string (QObject::tr ("Ruby")))); - m_macro_categories.push_back (std::pair ("pymacros", tl::to_string (QObject::tr ("Python")))); - m_macro_categories.push_back (std::pair ("drc", tl::to_string (QObject::tr ("DRC")))); - - // Scan for macros and set interpreter path - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - - for (size_t c = 0; c < m_macro_categories.size (); ++c) { - - std::string mp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (tl::to_qstring (m_macro_categories [c].first))); - - // don't scan if macros are disabled - if (! m_no_macros) { - if (p == m_klayout_path.begin ()) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Local")), mp, m_macro_categories [c].first, false); - } else if (m_klayout_path.size () == 2) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Global")), mp, m_macro_categories [c].first, true); - } else { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Global")) + " - " + *p, mp, m_macro_categories [c].first, true); - } - } - - ruby_interpreter ().add_path (mp); - python_interpreter ().add_path (mp); - - } - - } - // Read some configuration values that we need early bool editable_from_config = false; @@ -965,95 +592,94 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } } catch (...) { } - try { - std::string s; - cfg.config_get (cfg_technologies, s); - lay::Technologies tt; - if (! s.empty ()) { - tt.load_from_xml (s); - } - *lay::Technologies::instance () = tt; - } catch (tl::Exception &ex) { - tl::warn << tl::to_string (QObject::tr ("Unable to restore technologies: ")) << ex.msg (); + } + + if (! m_no_gui) { + // 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 ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + sc->add_path (*p); } + sc->set_salt_mine_url (tl::salt_mine_url ()); + } - // auto-import technologies - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + if (tc) { - QDir inst_path_dir (tl::to_qstring (*p)); - if (! inst_path_dir.cd (QString::fromUtf8 ("tech"))) { - continue; + // auto-import technologies + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + tc->add_path (*p); } - QStringList name_filters; - name_filters << QString::fromUtf8 ("*.lyt"); + // import technologies from the command line + for (std::vector > >::iterator f = m_files.begin (); f != m_files.end (); ++f) { - QStringList lyt_files; - - QDirIterator di (inst_path_dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (di.hasNext ()) { - lyt_files << di.next (); - } - - lyt_files.sort (); - - for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { - - try { + if (f->first == layout_file_with_tech_file) { if (tl::verbosity () >= 20) { - tl::info << "Auto-importing technology from " << tl::to_string (*lf); + tl::info << "Importing technology from " << f->second.second; } lay::Technology t; - t.load (tl::to_string (*lf)); - t.set_persisted (false); // don't save that one in the configuration - lay::Technologies::instance ()->add (new lay::Technology (t)); + t.load (f->second.second); + + tc->add_temp_tech (t); + + f->first = layout_file_with_tech; + f->second.second = t.name (); - } catch (tl::Exception &ex) { - tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); } } + tc->load (); + } - // import technologies from the command line - for (std::vector > >::iterator f = m_files.begin (); f != m_files.end (); ++f) { + if (mc) { - if (f->first == layout_file_with_tech_file) { + mc->enable_implicit_macros (! m_no_macros); - if (tl::verbosity () >= 20) { - tl::info << "Importing technology from " << f->second.second; + if (! m_no_macros) { + + // Add the global ruby modules as the first ones. + // TODO: this is a deprecated feature. + std::vector global_modules = scan_global_modules (); + m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); + + for (std::vector ::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); + } } - lay::Technology t; - t.load (f->second.second); - t.set_persisted (false); // don't save that one in the configuration - lay::Technologies::instance ()->add (new lay::Technology (t)); - - f->first = layout_file_with_tech; - f->second.second = t.name (); + // Install the custom folders + for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != 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 + mc->load (); - // Install the custom folders - if (! m_no_macros) { - for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->first, p->second, false); - // TODO: put somewhere else: - ruby_interpreter ().add_path (p->first); - python_interpreter ().add_path (p->first); - } } - // Add locations defined by the technologies - sync_tech_macro_locations (); - // 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 @@ -1103,8 +729,14 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } // initialize the plugins for the first time + if (tl::verbosity () >= 20) { + tl::info << "Initializing plugins:"; + } for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { lay::PluginDeclaration *pd = const_cast (&*cls); + if (tl::verbosity () >= 20) { + tl::info << " " << cls.current_name () << " [" << cls.current_position () << "]"; + } pd->initialize (mp_plugin_root); } @@ -1138,10 +770,56 @@ Application::~Application () shutdown (); } -QString -Application::symbol_name_from_address (const QString &mod_name, size_t addr) +std::vector +Application::scan_global_modules () { - return get_symbol_name_from_address (mod_name, addr); + // 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 global_modules; + std::set modules; + + // try to locate a global plugins + for (std::vector ::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 (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; } bool @@ -1179,9 +857,6 @@ Application::finish () if (mp_plugin_root && m_write_config_file) { - // save the technology setup in the configuration - mp_plugin_root->config_set (cfg_technologies, lay::Technologies::instance ()->to_xml ()); - 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; @@ -1380,48 +1055,6 @@ Application::run () } - // scan for libraries - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - - QDir lp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("libraries")); - - QStringList name_filters; - name_filters << QString::fromUtf8 ("*"); - - QStringList libs = lp.entryList (name_filters, QDir::Files); - for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { - - std::string filename = tl::to_string (*im); - - try { - - std::auto_ptr lib (new db::Library ()); - lib->set_description (filename); - lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); - - tl::log << "Reading library '" << filename << "'"; - tl::InputStream stream (tl::to_string (lp.filePath (*im))); - db::Reader reader (stream); - reader.read (lib->layout ()); - - // Use the libname if there is one - for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { - if (m->name == "libname" && ! m->value.empty ()) { - lib->set_name (m->value); - break; - } - } - - db::LibraryManager::instance ().register_lib (lib.release ()); - - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } - - } - - } - // run all autorun macros lay::MacroCollection::root ().autorun (); @@ -1658,22 +1291,7 @@ void Application::set_config (const std::string &name, const std::string &value) { if (mp_plugin_root) { - - if (name == cfg_technologies) { - - // HACK: cfg_technologies is not a real configuration parameter currently. Hence we emulate that - // behavior. But currently this is the only way to access technology data indirectly from a script. - // Note that this method will set only the technologies accessible through the configuration parameter. - // I.e. the ones not auto-imported. - // TODO: rework this one. This is only half-hearted. - if (! value.empty ()) { - lay::Technologies::instance ()->load_from_xml (value); - } - - } else { - mp_plugin_root->config_set (name, value); - } - + mp_plugin_root->config_set (name, value); } } @@ -1689,16 +1307,7 @@ std::string Application::get_config (const std::string &name) const { if (mp_plugin_root) { - if (name == cfg_technologies) { - // HACK: cfg_technologies is not a real configuration parameter currently. Hence we emulate that - // behavior. But currently this is the only way to access technology data indirectly from a script. - // Note that this method will return only the technologies accessible through the configuration parameter. - // I.e. the ones not auto-imported. - // TODO: rework this one. - return lay::Technologies::instance ()->to_xml (); - } else { - return mp_plugin_root->config_get (name); - } + return mp_plugin_root->config_get (name); } else { return std::string (); } @@ -1722,120 +1331,5 @@ Application::special_app_flag (const std::string &name) return (env && *env); } -std::vector -Application::sync_tech_macro_locations () -{ - if (m_no_macros) { - return std::vector (); - } - - std::set > tech_macro_paths; - std::map, std::string> tech_names_by_path; - - // Add additional places where the technologies define some macros - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - - if (t->base_path ().empty ()) { - continue; - } - - for (size_t c = 0; c < m_macro_categories.size (); ++c) { - - QDir base_dir (tl::to_qstring (t->base_path ())); - if (base_dir.exists ()) { - - QDir macro_dir (base_dir.filePath (tl::to_qstring (m_macro_categories [c].first))); - if (macro_dir.exists ()) { - - std::string mp = tl::to_string (macro_dir.path ()); - std::pair cp (m_macro_categories [c].first, mp); - tech_macro_paths.insert (cp); - std::string &tn = tech_names_by_path [cp]; - if (! tn.empty ()) { - tn += ","; - } - tn += t->name (); - - } - - } - - } - - } - - // delete macro collections which are no longer required or update description - std::vector folders_to_delete; - std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; - - lay::MacroCollection *root = &lay::MacroCollection::root (); - - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - - std::pair cp (m->second->category (), m->second->path ()); - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { - - if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { - // no longer used - folders_to_delete.push_back (m->second); - } else { - // used: update description if required - std::string desc = desc_prefix + tech_names_by_path [cp]; - m->second->set_description (desc); - } - - } - - } - - for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { - if (tl::verbosity () >= 20) { - tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; - } - root->erase (*m); - } - - // store new paths - m_tech_macro_paths = tech_macro_paths; - - // add new folders - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { - std::pair cp (m->second->category (), m->second->path ()); - tech_macro_paths.erase (cp); - } - } - - std::vector new_folders; - - for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { - - const std::string &tn = tech_names_by_path [*p]; - - // TODO: is it wise to make it writeable? - if (tl::verbosity () >= 20) { - tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; - } - - // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in - // a non-tech context. - // In that case, the add_folder method will return 0. - lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); - if (mc) { - - mc->set_virtual_mode (lay::MacroCollection::TechFolder); - new_folders.push_back (mc); - - // TODO: put somewhere else: - ruby_interpreter ().add_path (p->second); - python_interpreter ().add_path (p->second); - - } - - } - - return new_folders; -} - } diff --git a/src/lay/layApplication.h b/src/lay/layApplication.h index bdc319f7b..3186c7c56 100644 --- a/src/lay/layApplication.h +++ b/src/lay/layApplication.h @@ -202,14 +202,6 @@ public: */ bool special_app_flag (const std::string &name); - /** - * @brief Obtain the list of macro categories - */ - const std::vector< std::pair > ¯o_categories () const - { - return m_macro_categories; - } - /** * @brief Return a reference to the Ruby interpreter */ @@ -265,11 +257,6 @@ public: return ! m_no_gui; } - /** - * @brief For debugging purposes: get a symbol name (a description actually) from an address - */ - static QString symbol_name_from_address (const QString &mod_name, size_t addr); - /** * @brief Reset config to global configuration */ @@ -309,6 +296,7 @@ public: private: void shutdown (); void finish (); + std::vector scan_global_modules (); enum file_type { layout_file, diff --git a/src/lay/layFontController.cc b/src/lay/layFontController.cc new file mode 100644 index 000000000..de36f4bca --- /dev/null +++ b/src/lay/layFontController.cc @@ -0,0 +1,180 @@ + +/* + + 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 "layFontController.h" +#include "layApplication.h" +#include "laySaltController.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "dbGlyphs.h" +#include "tlLog.h" + +#include + +namespace lay +{ + +FontController::FontController () + : m_file_watcher (0), + dm_sync_dirs (this, &FontController::sync_dirs) +{ +} + +void +FontController::initialize (lay::PluginRoot * /*root*/) +{ + // NOTE: we initialize the dirs in the stage once to have them available for the autorun + // macros. We'll do that later again in order to pull in the dirs from the packages. + sync_dirs (); +} + +void +FontController::initialized (lay::PluginRoot * /*root*/) +{ + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + sync_dirs (); +} + +void +FontController::uninitialize (lay::PluginRoot * /*root*/) +{ + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +FontController::get_options (std::vector < std::pair > & /*options*/) const +{ + // .. nothing yet .. +} + +void +FontController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +FontController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +FontController::config_finalize() +{ + // .. nothing yet .. +} + +bool +FontController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +void +FontController::sync_dirs () +{ + if (! m_file_watcher) { + return; + } + + m_file_watcher->clear (); + m_file_watcher->enable (false); + + std::vector paths = lay::Application::instance ()->klayout_path (); + + // add the salt grains as potential sources for library definitions + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back ((*g)->path ()); + } + } + + // scan for font directories + + std::vector font_paths; + + for (std::vector ::const_iterator p = paths.begin (); p != paths.end (); ++p) { + QDir fp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("fonts")); + if (fp.exists ()) { + m_file_watcher->add_file (tl::to_string (fp.absolutePath ())); + font_paths.push_back (tl::to_string (fp.absolutePath ())); + } + } + + db::TextGenerator::set_font_paths (font_paths); + + m_file_watcher->enable (true); +} + +void +FontController::sync_with_external_sources () +{ + tl::log << tl::to_string (tr ("Package updates - updating fonts")); + dm_sync_dirs (); +} + +void +FontController::file_watcher_triggered () +{ + tl::log << tl::to_string (tr ("Detected file system change in fonts - updating")); + dm_sync_dirs (); +} + +FontController * +FontController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + FontController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the library controller +static tl::RegisteredClass font_controller_decl (new lay::FontController (), 160, "FontController"); + +} + diff --git a/src/lay/layFontController.h b/src/lay/layFontController.h new file mode 100644 index 000000000..5c1e48e28 --- /dev/null +++ b/src/lay/layFontController.h @@ -0,0 +1,131 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_layFontController +#define HDR_layFontController + +#include "layCommon.h" +#include "layPlugin.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" + +#include +#include + +#include + +namespace lay +{ + +class FontManagerDialog; +class MainWindow; + +/** + * @brief A controller for the fonts + * + * This object is a singleton that acts as a controller + * for the font management for the Glyphs. The controller is responsible + * to managing the fonts and notifying library consumers + * of changes. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class FontController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + FontController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Gets the singleton instance for this object + */ + static FontController *instance (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + + /** + * @brief Called when the salt (packages) has changed + */ + void sync_with_external_sources (); + +private: + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_dirs; + + void sync_dirs (); +}; + +} + +#endif + diff --git a/src/lay/layLibraryController.cc b/src/lay/layLibraryController.cc new file mode 100644 index 000000000..31906445c --- /dev/null +++ b/src/lay/layLibraryController.cc @@ -0,0 +1,282 @@ + +/* + + 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 "layLibraryController.h" +#include "layTechnology.h" +#include "layApplication.h" +#include "laySaltController.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "layQtTools.h" +#include "dbLibraryManager.h" +#include "dbLibrary.h" +#include "dbReader.h" +#include "tlLog.h" +#include "tlStream.h" + +#include + +namespace lay +{ + +LibraryController::LibraryController () + : m_file_watcher (0), + dm_sync_files (this, &LibraryController::sync_files) +{ +} + +void +LibraryController::initialize (lay::PluginRoot * /*root*/) +{ + // NOTE: we initialize the libraries in the stage once to have them available for the autorun + // macros. We'll do that later again in order to pull in the libraries from the packages. + sync_files (); +} + +void +LibraryController::initialized (lay::PluginRoot * /*root*/) +{ + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + sync_files (); +} + +void +LibraryController::uninitialize (lay::PluginRoot * /*root*/) +{ + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +LibraryController::get_options (std::vector < std::pair > & /*options*/) const +{ + // .. nothing yet .. +} + +void +LibraryController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +LibraryController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +LibraryController::config_finalize() +{ + // .. nothing yet .. +} + +bool +LibraryController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +void +LibraryController::sync_files () +{ + if (! m_file_watcher) { + return; + } + + m_file_watcher->clear (); + m_file_watcher->enable (false); + + std::map > new_lib_files; + + // build a list of paths vs. technology + std::vector > paths; + + std::vector klayout_path = lay::Application::instance ()->klayout_path (); + for (std::vector::const_iterator p = klayout_path.begin (); p != klayout_path.end (); ++p) { + paths.push_back (std::make_pair (*p, std::string ())); + } + + // add the salt grains as potential sources for library definitions + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back (std::make_pair ((*g)->path (), std::string ())); + } + } + + // add the technologies as potential sources for library definitions + + for (lay::Technologies::iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->base_path ().empty ()) { + paths.push_back (std::make_pair (t->base_path (), t->name ())); + } + } + + // scan for libraries + + for (std::vector >::const_iterator p = paths.begin (); p != paths.end (); ++p) { + + QDir lp = QDir (tl::to_qstring (p->first)).filePath (tl::to_qstring ("libraries")); + if (lp.exists ()) { + + m_file_watcher->add_file (tl::to_string (lp.absolutePath ())); + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*"); + + QStringList libs = lp.entryList (name_filters, QDir::Files); + for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { + + std::string filename = tl::to_string (*im); + std::string lib_path = tl::to_string (lp.absoluteFilePath (*im)); + + try { + + QFileInfo fi (tl::to_qstring (lib_path)); + + bool needs_load = false; + std::map >::iterator ll = m_lib_files.find (lib_path); + if (ll == m_lib_files.end ()) { + needs_load = true; + } else { + if (fi.lastModified () > ll->second.second) { + needs_load = true; + } else { + new_lib_files.insert (*ll); + } + } + + if (needs_load) { + + std::auto_ptr lib (new db::Library ()); + lib->set_description (filename); + lib->set_technology (p->second); + lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); + + tl::log << "Reading library '" << lib_path << "'"; + tl::InputStream stream (lib_path); + db::Reader reader (stream); + reader.read (lib->layout ()); + + // Use the libname if there is one + for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { + if (m->name == "libname" && ! m->value.empty ()) { + lib->set_name (m->value); + break; + } + } + + tl::log << "Registering as '" << lib->get_name () << "' for tech '" << lib->get_technology () << "'"; + new_lib_files.insert (std::make_pair (lib_path, std::make_pair (lib->get_name (), fi.lastModified ()))); + + db::LibraryManager::instance ().register_lib (lib.release ()); + + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } + + } + + } + + } + + m_file_watcher->enable (true); + + // remove libraries which are no longer present + + std::set new_names; + + for (std::map >::const_iterator lf = new_lib_files.begin (); lf != new_lib_files.end (); ++lf) { + new_names.insert (lf->second.first); + } + + for (std::map >::const_iterator lf = m_lib_files.begin (); lf != m_lib_files.end (); ++lf) { + if (new_names.find (lf->second.first) == new_names.end ()) { + try { + std::pair li = db::LibraryManager::instance ().lib_by_name (lf->second.first); + if (li.first) { + db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (li.second)); + } + } catch (...) { + } + } + } + + // establish the new libraries + + m_lib_files = new_lib_files; +} + +void +LibraryController::sync_with_external_sources () +{ + tl::log << tl::to_string (tr ("Package updates - updating libraries")); + dm_sync_files (); +} + +void +LibraryController::file_watcher_triggered () +{ + tl::log << tl::to_string (tr ("Detected file system change in libraries - updating")); + dm_sync_files (); +} + +LibraryController * +LibraryController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + LibraryController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the library controller +static tl::RegisteredClass library_controller_decl (new lay::LibraryController (), 150, "LibraryController"); + +} + diff --git a/src/lay/layLibraryController.h b/src/lay/layLibraryController.h new file mode 100644 index 000000000..1792bd1ef --- /dev/null +++ b/src/lay/layLibraryController.h @@ -0,0 +1,132 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_layLibraryController +#define HDR_layLibraryController + +#include "layCommon.h" +#include "layPlugin.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" + +#include +#include + +#include + +namespace lay +{ + +class LibraryManagerDialog; +class MainWindow; + +/** + * @brief A controller for the libraries + * + * This object is a singleton that acts as a controller + * for the library management. The controller is responsible + * to managing the libraries and notifying library consumers + * of changes. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class LibraryController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + LibraryController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Gets the singleton instance for this object + */ + static LibraryController *instance (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + + /** + * @brief Called when the salt (packages) has changed + */ + void sync_with_external_sources (); + +private: + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_files; + std::map > m_lib_files; + + void sync_files (); +}; + +} + +#endif + diff --git a/src/lay/layLogViewerDialog.cc b/src/lay/layLogViewerDialog.cc index f6be30d4a..b1a5a8172 100644 --- a/src/lay/layLogViewerDialog.cc +++ b/src/lay/layLogViewerDialog.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -97,25 +98,30 @@ LogReceiver::begin () // ----------------------------------------------------------------- // LogFile implementation -LogFile::LogFile (size_t max_entries) - : m_error_receiver (this, 0, &LogFile::error), - m_warn_receiver (this, 0, &LogFile::warn), - m_log_receiver (this, 10, &LogFile::info), - m_info_receiver (this, 0, &LogFile::info), +LogFile::LogFile (size_t max_entries, bool register_global) + : m_error_receiver (this, 0, &LogFile::add_error), + m_warn_receiver (this, 0, &LogFile::add_warn), + m_log_receiver (this, 10, &LogFile::add_info), + m_info_receiver (this, 0, &LogFile::add_info), m_max_entries (max_entries), m_generation_id (0), - m_last_generation_id (0) + m_last_generation_id (0), + m_has_errors (false), + m_has_warnings (false), + m_last_attn (false) { connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ())); - m_timer.setSingleShot (true); + m_timer.setSingleShot (false); m_timer.setInterval (100); m_timer.start (); - tl::info.add (&m_info_receiver, false); - tl::log.add (&m_log_receiver, false); - tl::error.add (&m_error_receiver, false); - tl::warn.add (&m_warn_receiver, false); + if (register_global) { + tl::info.add (&m_info_receiver, false); + tl::log.add (&m_log_receiver, false); + tl::error.add (&m_error_receiver, false); + tl::warn.add (&m_warn_receiver, false); + } } void @@ -123,8 +129,23 @@ LogFile::clear () { QMutexLocker locker (&m_lock); - m_messages.clear (); - ++m_generation_id; + if (!m_messages.empty ()) { + m_messages.clear (); + m_has_errors = m_has_warnings = false; + ++m_generation_id; + } +} + +bool +LogFile::has_errors () const +{ + return m_has_errors; +} + +bool +LogFile::has_warnings () const +{ + return m_has_warnings; } void @@ -159,9 +180,13 @@ void LogFile::timeout () { bool changed = false; + bool attn = false, last_attn = false; m_lock.lock (); if (m_generation_id != m_last_generation_id) { + attn = m_has_errors || m_has_warnings; + last_attn = m_last_attn; + m_last_attn = attn; m_last_generation_id = m_generation_id; changed = true; } @@ -169,9 +194,10 @@ LogFile::timeout () if (changed) { emit layoutChanged (); + if (last_attn != attn) { + emit attention_changed (attn); + } } - - m_timer.start (); } void @@ -183,6 +209,12 @@ LogFile::add (LogFileEntry::mode_type mode, const std::string &msg, bool continu m_messages.pop_front (); } + if (mode == LogFileEntry::Warning || mode == LogFileEntry::WarningContinued) { + m_has_warnings = true; + } else if (mode == LogFileEntry::Error || mode == LogFileEntry::ErrorContinued) { + m_has_errors = true; + } + m_messages.push_back (LogFileEntry (mode, msg, continued)); ++m_generation_id; @@ -201,18 +233,25 @@ LogFile::data(const QModelIndex &index, int role) const { QMutexLocker locker (&m_lock); - if (role == Qt::DisplayRole) { + if (role == Qt::DecorationRole) { if (index.row () < int (m_messages.size ()) && index.row () >= 0) { LogFileEntry::mode_type mode = m_messages [index.row ()].mode (); - std::string message = m_messages [index.row ()].text (); if (mode == LogFileEntry::Error) { - return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("ERROR: ")) + message)); + return QIcon (QString::fromUtf8 (":/error_16.png")); } else if (mode == LogFileEntry::Warning) { - return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("Warning: ")) + message)); + return QIcon (QString::fromUtf8 (":/warn_16.png")); + } else if (mode == LogFileEntry::Info) { + return QIcon (QString::fromUtf8 (":/info_16.png")); } else { - return QVariant (tl::to_qstring (message)); + return QIcon (QString::fromUtf8 (":/empty_16.png")); } + } + + } else if (role == Qt::DisplayRole) { + + if (index.row () < int (m_messages.size ()) && index.row () >= 0) { + return QVariant (tl::to_qstring (m_messages [index.row ()].text ())); } } else if (role == Qt::FontRole) { @@ -251,21 +290,36 @@ LogFile::data(const QModelIndex &index, int role) const // ----------------------------------------------------------------- // LogViewerDialog implementation -LogViewerDialog::LogViewerDialog (QWidget *parent) +LogViewerDialog::LogViewerDialog (QWidget *parent, bool register_global, bool interactive) : QDialog (parent), - m_file (50000) // TODO: make this variable .. + m_file (50000, register_global) // TODO: make this variable .. { setupUi (this); + // For non-global log views, the verbosity selector does not make sense + if (!register_global) { + verbosity_cbx->hide (); + verbosity_label->hide (); + } else { + verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10)); + connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int))); + } + + if (!interactive) { + clear_pb->hide (); + separator_pb->hide (); + copy_pb->hide (); + } else { + connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ())); + connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ())); + connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ())); + } + + attn_frame->hide (); log_view->setModel (&m_file); - verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10)); - connect (&m_file, SIGNAL (layoutChanged ()), log_view, SLOT (scrollToBottom ())); - connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int))); - connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ())); - connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ())); - connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ())); + connect (&m_file, SIGNAL (attention_changed (bool)), attn_frame, SLOT (setVisible (bool))); } void @@ -274,5 +328,56 @@ LogViewerDialog::verbosity_changed (int index) tl::verbosity (index * 10 + 1); } +// ----------------------------------------------------------------- +// AlertLog implementation + +AlertLogButton::AlertLogButton (QWidget *parent) + : QToolButton (parent) +{ + mp_logger = new LogViewerDialog (this, false, false); + hide (); + connect (&mp_logger->file (), SIGNAL (attention_changed (bool)), this, SLOT (attention_changed (bool))); + connect (this, SIGNAL (clicked ()), mp_logger, SLOT (exec ())); +} + +void +AlertLogButton::attention_changed (bool attn) +{ + setVisible (attn); + + // as a special service, enlarge and color any surrounding frame red - + // this feature allows putting the alert button together with other entry fields into a frame and + // make this frame highlighted on error or warning. + QFrame *frame = dynamic_cast (parent ()); + if (frame) { + + if (frame->layout ()) { + int l = 0, t = 0, r = 0, b = 0; + frame->layout ()->getContentsMargins (&l, &t, &r, &b); + if (attn) { + l += 3; t += 3; r += 2; b += 2; + } else { + l -= 3; t -= 3; r -= 2; b -= 2; + } + frame->layout ()->setContentsMargins (l, t, r, b); + } + + if (attn) { + + frame->setAutoFillBackground (true); + QPalette palette = frame->palette (); + palette.setColor (QPalette::Window, QColor (255, 160, 160)); + frame->setPalette (palette); + + } else { + + frame->setAutoFillBackground (false); + frame->setPalette (QPalette ()); + + } + + } +} + } diff --git a/src/lay/layLogViewerDialog.h b/src/lay/layLogViewerDialog.h index e2d132146..c31eca626 100644 --- a/src/lay/layLogViewerDialog.h +++ b/src/lay/layLogViewerDialog.h @@ -26,11 +26,13 @@ #include "ui_LogViewerDialog.h" #include "tlLog.h" +#include "layCommon.h" #include #include #include #include +#include #include #include @@ -40,6 +42,9 @@ namespace lay class LogFile; +/** + * @brief A helper class describing one log entry + */ class LogFileEntry { public: @@ -70,7 +75,10 @@ private: bool m_continued; }; -class LogReceiver +/** + * @brief The log receiver abstraction that connects a channel with the LogFile object + */ +class LAY_PUBLIC LogReceiver : public tl::Channel { public: @@ -91,40 +99,110 @@ private: QMutex m_lock; }; -class LogFile +/** + * @brief A log collection ("log file") + * + * The log collector collects warnings, errors and info messages + * and presents this collection as a QAbstractListModel view + * viewing inside a QTreeWidget or the LogViewerDialog. + * + * The log collector can either be used standalone or as a + * global receiver that will collect the global log + * messages. + */ +class LAY_PUBLIC LogFile : public QAbstractListModel { Q_OBJECT public: - LogFile (size_t max_entries); - - void error (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued); - } - - void info (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued); - } - - void warn (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued); - } + /** + * @brief Constructs a log file receiver + * If "register_global" is true, the receiver will register itself as a global log receiver. + * Otherwise it's a private one that can be used with the "error", "warn" and "info" channels + * provided by the respective methods. + */ + LogFile (size_t max_entries, bool register_global = true); + /** + * @brief Implementation of the QAbstractItemModel interface + */ int rowCount(const QModelIndex &parent) const; + /** + * @brief Implementation of the QAbstractItemModel interface + */ QVariant data(const QModelIndex &index, int role) const; -private slots: - void timeout (); + /** + * @brief Gets a value indicating whether errors are present + */ + bool has_errors () const; + + /** + * @brief Gets a value indicating whether warnings are present + */ + bool has_warnings () const; + +public slots: + /** + * @brief Clears the log + */ void clear (); + + /** + * @brief Adds a separator + */ void separator (); + + /** + * @brief copies the contents to the clipboard + */ void copy (); -public: + /** + * @brief Gets the error channel + */ + tl::Channel &error () + { + return m_error_receiver; + } + + /** + * @brief Gets the warning channel + */ + tl::Channel &warn () + { + return m_warn_receiver; + } + + /** + * @brief Gets the info channel + */ + tl::Channel &info () + { + return m_info_receiver; + } + + /** + * @brief Gets the log channel + */ + tl::Channel &log () + { + return m_log_receiver; + } + +private slots: + void timeout (); + +signals: + /** + * @brief This signal is emitted if the log's attention state has changed + * Attention state is "true" if either errors or warnings are present. + */ + void attention_changed (bool f); + +private: QTimer m_timer; mutable QMutex m_lock; LogReceiver m_error_receiver; @@ -135,18 +213,63 @@ public: size_t m_max_entries; size_t m_generation_id; size_t m_last_generation_id; + bool m_has_errors, m_has_warnings; + bool m_last_attn; + /** + * @brief Adds an error + */ + void add_error (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued); + } + + /** + * @brief Adds a info message + */ + void add_info (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued); + } + + /** + * @brief Adds a warning + */ + void add_warn (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued); + } + + /** + * @brief Adds anything + */ void add (LogFileEntry::mode_type mode, const std::string &msg, bool continued); }; -class LogViewerDialog +/** + * @brief A dialog presenting the log file + */ +class LAY_PUBLIC LogViewerDialog : public QDialog, public Ui::LogViewerDialog { Q_OBJECT public: - LogViewerDialog (QWidget *parent); + /** + * @brief The constructor + * If "register_global" is true, the log is registered globally + * and will receiver global log messages. + */ + LogViewerDialog (QWidget *parent, bool register_global = true, bool interactive = true); + + /** + * @brief Gets the log file object + */ + LogFile &file () + { + return m_file; + } public slots: void verbosity_changed (int l); @@ -155,7 +278,93 @@ private: LogFile m_file; }; +/** + * @brief A tool button that collects logs and makes itself visible once attention is required + */ +class LAY_PUBLIC AlertLogButton + : public QToolButton +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + AlertLogButton (QWidget *parent); + + /** + * @brief Gets the error channel + */ + tl::Channel &error () const + { + return mp_logger->file ().error (); + } + + /** + * @brief Gets the warn channel + */ + tl::Channel &warn () const + { + return mp_logger->file ().warn (); + } + + /** + * @brief Gets the info channel + */ + tl::Channel &info () const + { + return mp_logger->file ().info (); + } + + /** + * @brief Gets the log channel + */ + tl::Channel &log () const + { + return mp_logger->file ().log (); + } + + /** + * @brief Gets the error status of the log + */ + bool has_errors () const + { + return mp_logger->file ().has_errors (); + } + + /** + * @brief Gets the warning status of the log + */ + bool has_warnings () const + { + return mp_logger->file ().has_warnings (); + } + + /** + * @brief Gets the attention status of the log + * (either warnings or errors are present) + */ + bool needs_attention () const + { + return has_errors () || has_warnings (); + } + +public slots: + /** + * @brief Clears the log (and makes the button invisible) + */ + void clear () + { + mp_logger->file ().clear (); + } + +private slots: + void attention_changed (bool); + +private: + LogViewerDialog *mp_logger; +}; + } #endif - diff --git a/src/lay/layMacro.cc b/src/lay/layMacro.cc index 848fea380..7c180cdaf 100644 --- a/src/lay/layMacro.cc +++ b/src/lay/layMacro.cc @@ -1083,7 +1083,7 @@ void MacroCollection::on_macro_changed (Macro *macro) } } -void MacroCollection::collect_used_nodes(std::set ¯os, std::set ¯o_collections) +void MacroCollection::collect_used_nodes (std::set ¯os, std::set ¯o_collections) { for (MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) { macro_collections.insert (c->second); @@ -1173,7 +1173,7 @@ MacroCollection::make_readonly (bool f) } MacroCollection * -MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly) +MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly, bool force_create) { if (! path.empty () && path[0] == ':') { readonly = true; @@ -1183,15 +1183,25 @@ MacroCollection::add_folder (const std::string &description, const std::string & if (! file_info.exists ()) { - // Try to create the folder since it does not exist yet - if (tl::verbosity () >= 20) { - tl::log << "Folder does not exist yet - trying to create it: " << path; - } - if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) { - if (tl::verbosity () >= 10) { - tl::error << "Unable to create folder path: " << path; + // Try to create the folder since it does not exist yet or skip that one + if (! force_create) { + + if (tl::verbosity () >= 20) { + tl::log << "Folder does not exist - skipping: " << path; } return 0; + + } else { + + if (tl::verbosity () >= 20) { + tl::log << "Folder does not exist yet - trying to create it: " << path; + } + if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) { + if (tl::verbosity () >= 10) { + tl::error << "Unable to create folder path: " << path; + } + return 0; + } } file_info.refresh (); @@ -1666,6 +1676,91 @@ MacroCollection &MacroCollection::root () return ms_root; } +static bool sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual) +{ + bool ret = false; + + if (actual) { + current->make_readonly (actual->is_readonly ()); + } + + std::vector folders_to_delete; + + for (lay::MacroCollection::child_iterator m = current->begin_children (); m != current->end_children (); ++m) { + lay::MacroCollection *cm = actual ? actual->folder_by_name (m->first) : 0; + if (! cm) { + folders_to_delete.push_back (m->second); + } + } + + if (actual) { + for (lay::MacroCollection::child_iterator m = actual->begin_children (); m != actual->end_children (); ++m) { + lay::MacroCollection *cm = current->folder_by_name (m->first); + if (! cm) { + cm = current->create_folder (m->first.c_str (), false); + ret = true; + } + if (sync_macros(cm, m->second)) { + ret = true; + } + } + } + + // delete folders which do no longer exist + for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { + ret = true; + sync_macros (*m, 0); + current->erase (*m); + } + + std::vector macros_to_delete; + + for (lay::MacroCollection::iterator m = current->begin (); m != current->end (); ++m) { + lay::Macro *cm = actual ? actual->macro_by_name (m->first, m->second->format ()) : 0; + if (! cm) { + macros_to_delete.push_back (m->second); + } + } + + if (actual) { + for (lay::MacroCollection::iterator m = actual->begin (); m != actual->end (); ++m) { + lay::Macro *cm = current->macro_by_name (m->first, m->second->format ()); + if (cm) { + if (*cm != *m->second) { + cm->assign (*m->second); + } + cm->set_readonly (m->second->is_readonly ()); + } else { + cm = current->create (m->first.c_str (), m->second->format ()); + cm->assign (*m->second); + cm->set_readonly (m->second->is_readonly ()); + ret = true; + } + } + } + + // erase macros from collection which are no longer used + for (std::vector::const_iterator m = macros_to_delete.begin (); m != macros_to_delete.end (); ++m) { + current->erase (*m); + ret = true; + } + + return ret; +} + +void MacroCollection::reload () +{ + // create a new collection and synchronize + + lay::MacroCollection new_collection; + for (lay::MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) { + new_collection.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly (), false /* don't force to create */); + } + + // and synchronize current with the actual one + sync_macros (this, &new_collection); +} + static bool has_autorun_for (const lay::MacroCollection &collection, bool early) { for (lay::MacroCollection::const_child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { diff --git a/src/lay/layMacro.h b/src/lay/layMacro.h index 63114f6b9..46b7c6982 100644 --- a/src/lay/layMacro.h +++ b/src/lay/layMacro.h @@ -643,10 +643,11 @@ public: /** * @brief Some constants for virtual_mode */ - enum { + enum FolderType { NotVirtual = 0, ProjectFolder = 1, - TechFolder = 2 + TechFolder = 2, + SaltFolder = 3 }; /** @@ -665,8 +666,11 @@ public: * @brief Add a folder (will also scan the folder) * * @return A pointer to the new collection if successful + * + * If force_create is true (the default), the folder will be created if it does not + * exist yet. On error, 0 is returned. */ - MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly); + MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly, bool force_create = true); /** * @brief Gets the category tag of the collection @@ -997,6 +1001,13 @@ public: */ void rescan (); + /** + * @brief Reloads the macro collection + * + * This method is similar to rescan, but it will also remove folders and macros. + */ + void reload (); + /** * @brief Gets the root of the macro hierarchy corresponding to the configuration space */ diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 93f8e5920..e9a086ed0 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -21,6 +21,8 @@ */ #include "layMacroController.h" +#include "layTechnologyController.h" +#include "laySaltController.h" #include "layMacroEditorDialog.h" #include "layMacroInterpreter.h" #include "layMainWindow.h" @@ -36,11 +38,41 @@ namespace lay { MacroController::MacroController () - : mp_macro_editor (0), mp_mw (0), - dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros) + : mp_macro_editor (0), mp_mw (0), m_no_implicit_macros (false), m_file_watcher (0), + dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros), + dm_do_sync_with_external_sources (this, &MacroController::do_sync_with_external_sources), + dm_sync_file_watcher (this, &MacroController::sync_file_watcher), + dm_sync_files (this, &MacroController::sync_files) { - connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); +} + +void +MacroController::load () +{ + // Scan built-in macros + // These macros are always taken, even if there are no macros requested (they are required to + // fully form the API). + lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-macros", "macros", true); + lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-pymacros", "pymacros", true); + + // TODO: consider adding "drc" dynamically and allow more dynamic categories + m_macro_categories.push_back (std::pair ("macros", tl::to_string (QObject::tr ("Ruby")))); + m_macro_categories.push_back (std::pair ("pymacros", tl::to_string (QObject::tr ("Python")))); + m_macro_categories.push_back (std::pair ("drc", tl::to_string (QObject::tr ("DRC")))); + + // Scan for macros and set interpreter path + for (std::vector ::const_iterator p = m_internal_paths.begin (); p != m_internal_paths.end (); ++p) { + + for (size_t c = 0; c < m_macro_categories.size (); ++c) { + if (p->cat.empty () || p->cat == m_macro_categories [c].first) { + std::string mp = tl::to_string (QDir (tl::to_qstring (p->path)).absoluteFilePath (tl::to_qstring (m_macro_categories [c].first))); + lay::MacroCollection::root ().add_folder (p->description, mp, m_macro_categories [c].first, p->readonly); + } + } + + } } void @@ -52,19 +84,49 @@ MacroController::initialized (lay::PluginRoot *root) mp_macro_editor->setModal (false); } - connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); + if (lay::TechnologyController::instance ()) { + connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (macro_collection_changed ())); + connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (sync_with_external_sources ())); + } + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + // synchronize the macro collection with all external sources + sync_implicit_macros (false); // update the menus with the macro menu bindings as late as possible (now we // can be sure that the menus are created propertly) - do_update_menu_with_macros (); + macro_collection_changed (); } void MacroController::uninitialize (lay::PluginRoot * /*root*/) { - disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); + if (lay::TechnologyController::instance ()) { + disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (macro_collection_changed ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (sync_with_external_sources ())); + } + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } delete mp_macro_editor; mp_macro_editor = 0; @@ -193,9 +255,6 @@ MacroController::drop_url (const std::string &path_or_url) macro->save (); - // refresh macro editor to show new macro plus to install the menus - refresh (); - } } else { @@ -224,11 +283,195 @@ MacroController::show_editor (const std::string &cat, bool force_add) } void -MacroController::refresh () +MacroController::enable_implicit_macros (bool enable) { - if (mp_macro_editor) { - mp_macro_editor->refresh (); + m_no_implicit_macros = !enable; +} + +void +MacroController::sync_implicit_macros (bool ask_before_autorun) +{ + if (m_no_implicit_macros) { + return; } + + std::vector external_paths; + + // Add additional places where the technologies define some macros + + std::map > tech_names_by_path; + std::map > grain_names_by_path; + std::set readonly_paths; + + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->base_path ().empty ()) { + QDir base_dir (tl::to_qstring (t->base_path ())); + if (base_dir.exists ()) { + std::string path = tl::to_string (base_dir.absolutePath ()); + tech_names_by_path [path].push_back (t->name ()); + if (t->is_readonly ()) { + readonly_paths.insert (path); + } + if (! t->grain_name ().empty ()) { + grain_names_by_path [path].push_back (t->grain_name ()); + } + } + } + } + + for (std::map >::const_iterator t = tech_names_by_path.begin (); t != tech_names_by_path.end (); ++t) { + + for (size_t c = 0; c < macro_categories ().size (); ++c) { + + QDir base_dir (tl::to_qstring (t->first)); + QDir macro_dir (base_dir.filePath (tl::to_qstring (macro_categories () [c].first))); + if (macro_dir.exists ()) { + + std::string description; + if (t->second.size () == 1) { + description = tl::to_string (tr ("Technology %1").arg (tl::to_qstring (t->second.front ()))); + } else { + description = tl::to_string (tr ("Technologies %1").arg (tl::to_qstring (tl::join (t->second, ",")))); + } + + std::map >::const_iterator gn = grain_names_by_path.find (t->first); + if (gn != grain_names_by_path.end ()) { + description += " - "; + if (gn->second.size () == 1) { + description += tl::to_string (tr ("Package %1").arg (tl::to_qstring (gn->second.front ()))); + } else { + description += tl::to_string (tr ("Packages %1").arg (tl::to_qstring (tl::join (gn->second, ",")))); + } + } + + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::TechFolder, readonly_paths.find (t->first) != readonly_paths.end ())); + + } + + } + + } + + // Add additional places where the salt defines macros + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + + lay::Salt &salt = sc->salt (); + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + + const lay::SaltGrain *g = *i; + + for (size_t c = 0; c < macro_categories ().size (); ++c) { + + QDir base_dir (tl::to_qstring (g->path ())); + QDir macro_dir (base_dir.filePath (tl::to_qstring (macro_categories () [c].first))); + if (macro_dir.exists ()) { + + std::string description = tl::to_string (tr ("Package %1").arg (tl::to_qstring (g->name ()))); + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::SaltFolder, g->is_readonly ())); + + } + + } + + } + + } + + // delete macro collections which are no longer required or update description + + std::vector folders_to_delete; + + // determine the paths that will be in use + std::map new_folders_by_path; + for (std::vector::const_iterator p = external_paths.begin (); p != external_paths.end (); ++p) { + new_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + } + + // determine the paths currently in use + std::map prev_folders_by_path; + for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { + prev_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + } + + lay::MacroCollection *root = &lay::MacroCollection::root (); + + for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder || + m->second->virtual_mode () == lay::MacroCollection::SaltFolder) { + std::map::const_iterator u = new_folders_by_path.find (m->second->path ()); + if (u == new_folders_by_path.end ()) { + // no longer used + folders_to_delete.push_back (m->second); + } else { + m->second->set_description (u->second->description); + } + } + } + + for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { + if (tl::verbosity () >= 20) { + tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; + } + root->erase (*m); + } + + // store new paths + m_external_paths = external_paths; + + // add new folders + + std::vector new_folders; + + for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { + + if (prev_folders_by_path.find (p->path) != prev_folders_by_path.end ()) { + continue; + } + + if (tl::verbosity () >= 20) { + tl::info << "Adding macro folder " << p->path << ", category '" << p->cat << "' for '" << p->description << "'"; + } + + // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in + // a non-tech context. + // In that case, the add_folder method will return 0. + + // TODO: is it wise to make this writeable? + lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (p->description, p->path, p->cat, p->readonly); + if (mc) { + mc->set_virtual_mode (p->type); + new_folders.push_back (mc); + } + + } + + { + + // This prevents the message dialog below to issue deferred methods + tl::NoDeferredMethods silent; + + bool has_autorun = false; + for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end () && ! has_autorun; ++m) { + has_autorun = (*m)->has_autorun (); + } + + if (has_autorun) { + if (! ask_before_autorun || QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end (); ++m) { + (*m)->autorun (); + } + } + } + + } +} + +void +MacroController::add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly) +{ + m_internal_paths.push_back (InternalPathDescriptor (path, description, category, readonly)); } void @@ -247,7 +490,7 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int if (! tech || c->second->virtual_mode () != lay::MacroCollection::TechFolder) { consider = true; } else { - const std::vector > &mc = lay::Application::instance ()->macro_categories (); + const std::vector > &mc = macro_categories (); for (std::vector >::const_iterator cc = mc.begin (); cc != mc.end () && !consider; ++cc) { consider = (c->second->path () == tl::to_string (QDir (tl::to_qstring (tech->base_path ())).filePath (tl::to_qstring (cc->first)))); } @@ -323,11 +566,28 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int } void -MacroController::update_menu_with_macros () +MacroController::sync_with_external_sources () +{ + dm_do_sync_with_external_sources (); +} + +void +MacroController::do_sync_with_external_sources () +{ + try { + sync_implicit_macros (true); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } +} + +void +MacroController::macro_collection_changed () { // empty action to macro table now we know it's invalid m_action_to_macro.clear (); dm_do_update_menu_with_macros (); + dm_sync_file_watcher (); } void @@ -337,11 +597,9 @@ MacroController::do_update_menu_with_macros () return; } - // TODO: implement this by asking the technology manager for the active technology const lay::Technology *tech = 0; - if (mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { - std::string active_tech = mp_mw->current_view ()->active_cellview ()->tech_name (); - tech = lay::Technologies::instance ()->technology_by_name (active_tech); + if (lay::TechnologyController::instance ()) { + tech = lay::TechnologyController::instance ()->active_technology (); } std::vector > key_bindings = unpack_key_binding (mp_mw->config_get (cfg_key_bindings)); @@ -376,6 +634,39 @@ MacroController::do_update_menu_with_macros () } } +void +MacroController::file_watcher_triggered () +{ + dm_sync_files (); +} + +static void +add_collections_to_file_watcher (const lay::MacroCollection &collection, tl::FileSystemWatcher *watcher) +{ + for (lay::MacroCollection::const_child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { + if (! c->second->path ().empty () && c->second->path ()[0] != ':') { + watcher->add_file (c->second->path ()); + add_collections_to_file_watcher (*c->second, watcher); + } + } +} + +void +MacroController::sync_file_watcher () +{ + m_file_watcher->clear (); + m_file_watcher->enable (false); + add_collections_to_file_watcher (lay::MacroCollection::root (), m_file_watcher); + m_file_watcher->enable (true); +} + +void +MacroController::sync_files () +{ + tl::log << tl::to_string (tr ("Detected file system change in macro folders - updating")); + lay::MacroCollection::root ().reload (); +} + MacroController * MacroController::instance () { diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 08f996c2d..e245bf643 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -29,6 +29,7 @@ #include "layMacro.h" #include "tlObject.h" #include "tlDeferredExecution.h" +#include "tlFileSystemWatcher.h" #include #include @@ -103,6 +104,14 @@ public: */ virtual void drop_url (const std::string &path_or_url); + /** + * @brief Enables or disables implicit macros + * If implicit macros are enabled, the macro tree contains the macros defined within the technologies + * and other implicit sources. + * This flag needs to be set initially and before the technology tree is updated. + */ + void enable_implicit_macros (bool enable); + /** * @brief Shows the macro editor * @@ -113,9 +122,16 @@ public: void show_editor (const std::string &cat = std::string (), bool force_add = false); /** - * @brief Reloads all macros from the paths registered + * @brief Adds a search path to the macros + * After adding the paths, "load" needs to be called to actually load the macros. */ - void refresh (); + void add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly); + + /** + * @brief Loads the macros from the predefined paths + * This method will also establish the macro categories. + */ + void load (); /** * @brief Adds a temporary macro @@ -128,6 +144,14 @@ public: */ void add_temp_macro (lay::Macro *m); + /** + * @brief Obtain the list of macro categories + */ + const std::vector< std::pair > ¯o_categories () const + { + return m_macro_categories; + } + /** * @brief Gets the singleton instance for this object */ @@ -135,20 +159,78 @@ public: public slots: /** - * @brief Update the menu with macros bound to a menu + * @brief Updates the menu with macros bound to a menu */ - void update_menu_with_macros (); + void macro_collection_changed (); + + /** + * @brief Called when the technologies or the salt got changed + */ + void sync_with_external_sources (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); private: + /** + * @brief A structure describing an external macro location + */ + struct ExternalPathDescriptor + { + ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lay::MacroCollection::FolderType _type, bool _readonly) + : path (_path), description (_description), cat (_cat), type (_type), readonly (_readonly) + { + // .. nothing yet .. + } + + std::string path; + std::string description; + std::string cat; + lay::MacroCollection::FolderType type; + bool readonly; + }; + + /** + * @brief A structure describing an internal macro location + */ + struct InternalPathDescriptor + { + InternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, bool _readonly) + : path (_path), description (_description), cat (_cat), readonly (_readonly) + { + // .. nothing yet .. + } + + std::string path; + std::string description; + std::string cat; + bool readonly; + }; + lay::MacroEditorDialog *mp_macro_editor; lay::MainWindow *mp_mw; - tl::DeferredMethod dm_do_update_menu_with_macros; + bool m_no_implicit_macros; std::vector m_macro_actions; std::map m_action_to_macro; lay::MacroCollection m_temp_macros; + std::vector< std::pair > m_macro_categories; + std::vector m_internal_paths; + std::vector m_external_paths; + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_do_update_menu_with_macros; + tl::DeferredMethod dm_do_sync_with_external_sources; + tl::DeferredMethod dm_sync_file_watcher; + tl::DeferredMethod dm_sync_files; + void sync_implicit_macros (bool ask_before_autorun); void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); void do_update_menu_with_macros (); + void do_sync_with_external_sources (); + void sync_file_watcher (); + void sync_files (); }; } diff --git a/src/lay/layMacroEditorDialog.cc b/src/lay/layMacroEditorDialog.cc index 1f40e9b52..3702b2592 100644 --- a/src/lay/layMacroEditorDialog.cc +++ b/src/lay/layMacroEditorDialog.cc @@ -22,6 +22,7 @@ #include "ui_MacroTemplateSelectionDialog.h" +#include "layMacroController.h" #include "layMacroEditorTree.h" #include "layMacroEditorDialog.h" #include "layMacroEditorSetupDialog.h" @@ -252,7 +253,8 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection m_font_size (0), m_edit_trace_index (-1), m_add_edit_trace_enabled (true), - dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher) + dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher), + dm_update_ui_to_run_mode (this, &MacroEditorDialog::do_update_ui_to_run_mode) { // Makes this dialog receive events while progress bars are on - this way we can set breakpoints // during execution of a macro even if anything lengthy is running. @@ -262,8 +264,10 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *))); connect (mp_root, SIGNAL (macro_deleted (Macro *)), this, SLOT (macro_deleted (Macro *))); + connect (mp_root, SIGNAL (macro_collection_deleted (MacroCollection *)), this, SLOT (macro_collection_deleted (MacroCollection *))); + connect (mp_root, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed (MacroCollection *))); - m_categories = lay::Application::instance ()->macro_categories (); + m_categories = lay::MacroController::instance ()->macro_categories (); treeTab->clear (); @@ -389,7 +393,6 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection watchList->addAction (actionDeleteWatches); watchList->addAction (actionClearWatches); - connect (actionAddWatch, SIGNAL (triggered ()), this, SLOT (add_watch ())); connect (actionAddWatch, SIGNAL (triggered ()), this, SLOT (add_watch ())); connect (actionEditWatch, SIGNAL (triggered ()), this, SLOT (edit_watch ())); connect (actionDeleteWatches, SIGNAL (triggered ()), this, SLOT (del_watches ())); @@ -1023,7 +1026,7 @@ MacroEditorDialog::add_edit_trace (bool compress) } MacroEditorPage *page = dynamic_cast (tabWidget->currentWidget ()); - if (! page) { + if (! page || ! page->macro ()) { return; } @@ -1533,6 +1536,34 @@ MacroEditorDialog::commit () } } +void +MacroEditorDialog::macro_collection_deleted (lay::MacroCollection *collection) +{ + // close the tab pages related to the collection we want to delete + std::set used_macros; + std::set used_collections; + collection->collect_used_nodes (used_macros, used_collections); + + for (std::set ::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) { + + if (mp_run_macro == *mc) { + mp_run_macro = 0; + } + + std::map ::iterator p = m_tab_widgets.find (*mc); + if (p != m_tab_widgets.end ()) { + // disable the macro on the page - we'll ask for updates when the file + // watcher becomes active. So long, the macro is "zombie". + p->second->connect_macro (0); + m_tab_widgets.erase (p); + } + + } + + refresh_file_watcher (); + update_ui_to_run_mode (); +} + void MacroEditorDialog::macro_deleted (lay::Macro *macro) { @@ -1542,15 +1573,20 @@ MacroEditorDialog::macro_deleted (lay::Macro *macro) std::map ::iterator page = m_tab_widgets.find (macro); if (page != m_tab_widgets.end ()) { + // disable the macro on the page - we'll ask for updates when the file + // watcher becomes active. So long, the macro is "zombie". page->second->connect_macro (0); - tabWidget->blockSignals (true); // blockSignals prevents a reentrant call into set_current of the tree - tabWidget->removeTab (tabWidget->indexOf (page->second)); - tabWidget->blockSignals (false); - delete page->second; m_tab_widgets.erase (page); } refresh_file_watcher (); + update_ui_to_run_mode (); +} + +void +MacroEditorDialog::macro_collection_changed (lay::MacroCollection * /*collection*/) +{ + refresh_file_watcher (); } void @@ -1593,7 +1629,7 @@ MacroEditorDialog::current_tab_changed (int index) update_ui_to_run_mode (); } -lay::Macro *MacroEditorDialog::create_macro_here(const char *prefix) +lay::Macro *MacroEditorDialog::create_macro_here (const char *prefix) { lay::MacroEditorTree *mt = current_macro_tree (); MacroCollection *collection = mt->current_macro_collection (); @@ -1923,11 +1959,14 @@ MacroEditorDialog::setup_button_clicked () m_save_all_on_run = data.save_all_on_run; - for (std::map::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) { - f->second->set_ntab (m_ntab); - f->second->set_nindent (m_nindent); - f->second->apply_attributes (); - f->second->set_font (m_font_family, m_font_size); + for (int i = 0; i < tabWidget->count (); ++i) { + MacroEditorPage *page = dynamic_cast (tabWidget->widget (i)); + if (page) { + page->set_ntab (m_ntab); + page->set_nindent (m_nindent); + page->apply_attributes (); + page->set_font (m_font_family, m_font_size); + } } // write configuration @@ -2059,14 +2098,16 @@ BEGIN_PROTECTED } MacroEditorPage *page = dynamic_cast (tabWidget->widget (index)); - if (! page || ! page->macro ()) { + if (! page) { delete tabWidget->currentWidget (); return; } - std::map ::iterator p = m_tab_widgets.find (page->macro ()); - if (p != m_tab_widgets.end ()) { - m_tab_widgets.erase (p); + for (std::map ::iterator p = m_tab_widgets.begin (); p != m_tab_widgets.end (); ++p) { + if (p->second == page) { + m_tab_widgets.erase (p); + break; + } } page->connect_macro (0); @@ -2343,7 +2384,7 @@ MacroEditorDialog::file_changed (const QString &path) { m_changed_files.push_back (path); - // Wait a little to let more to allow for more reload requests to collect + // Wait a little to allow for more reload requests to collect m_file_changed_timer->setInterval (300); m_file_changed_timer->start (); } @@ -2388,79 +2429,6 @@ MacroEditorDialog::sync_file_watcher (lay::MacroCollection * /*collection*/) #endif } -bool -MacroEditorDialog::sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual) -{ - bool ret = false; - - if (actual) { - current->make_readonly (actual->is_readonly ()); - } - - std::vector folders_to_delete; - - for (lay::MacroCollection::child_iterator m = current->begin_children (); m != current->end_children (); ++m) { - lay::MacroCollection *cm = actual ? actual->folder_by_name (m->first) : 0; - if (! cm) { - folders_to_delete.push_back (m->second); - } - } - - if (actual) { - for (lay::MacroCollection::child_iterator m = actual->begin_children (); m != actual->end_children (); ++m) { - lay::MacroCollection *cm = current->folder_by_name (m->first); - if (! cm) { - cm = current->create_folder (m->first.c_str (), false); - ret = true; - } - if (sync_macros(cm, m->second)) { - ret = true; - } - } - } - - // delete folders which do no longer exist - for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { - ret = true; - sync_macros (*m, 0); - current->erase (*m); - } - - std::vector macros_to_delete; - - for (lay::MacroCollection::iterator m = current->begin (); m != current->end (); ++m) { - lay::Macro *cm = actual ? actual->macro_by_name (m->first, m->second->format ()) : 0; - if (! cm) { - macros_to_delete.push_back (m->second); - } - } - - if (actual) { - for (lay::MacroCollection::iterator m = actual->begin (); m != actual->end (); ++m) { - lay::Macro *cm = current->macro_by_name (m->first, m->second->format ()); - if (cm) { - if (*cm != *m->second) { - cm->assign (*m->second); - } - cm->set_readonly (m->second->is_readonly ()); - } else { - cm = current->create (m->first.c_str (), m->second->format ()); - cm->assign (*m->second); - cm->set_readonly (m->second->is_readonly ()); - ret = true; - } - } - } - - // erase macros from collection which are no longer used - for (std::vector::const_iterator m = macros_to_delete.begin (); m != macros_to_delete.end (); ++m) { - current->erase (*m); - ret = true; - } - - return ret; -} - void MacroEditorDialog::refresh_file_watcher () { @@ -2488,18 +2456,13 @@ void MacroEditorDialog::reload_macros () { m_file_watcher->clear (); - - lay::MacroCollection new_root; - - // create a new root - for (lay::MacroCollection::child_iterator c = mp_root->begin_children (); c != mp_root->end_children (); ++c) { - new_root.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly ()); + try { + mp_root->reload (); + refresh_file_watcher (); + } catch (...) { + refresh_file_watcher (); + throw; } - - // and synchronize current with the actual one - sync_macros (mp_root, &new_root); - - refresh_file_watcher (); } void @@ -2593,30 +2556,13 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove that location"))); } - // close the tab pages related to the collection we want to delete - std::set used_macros; - std::set used_collections; - collection->collect_used_nodes (used_macros, used_collections); - - for (std::set ::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) { - - std::map ::iterator p = m_tab_widgets.find (*mc); - if (p != m_tab_widgets.end ()) { - p->second->connect_macro (0); - delete p->second; - m_tab_widgets.erase (p); - } - - } - - // actually remove the collection + // actually remove the collection (update is done through the + // macro_collection_deleted signal handler). mp_root->erase (collection); // save the new paths set_custom_paths (paths); - refresh_file_watcher (); - END_PROTECTED } @@ -3044,18 +2990,26 @@ MacroEditorDialog::leave_breakpoint_mode () void MacroEditorDialog::update_ui_to_run_mode () +{ + dm_update_ui_to_run_mode (); +} + +void +MacroEditorDialog::do_update_ui_to_run_mode () { double alpha = 0.95; MacroEditorPage *page = dynamic_cast (tabWidget->currentWidget ()); dbgOn->setEnabled (! m_in_exec); - runButton->setEnabled ((! m_in_exec && (mp_run_macro || (page && page->macro ()->interpreter () != lay::Macro::None))) || m_in_breakpoint); - runThisButton->setEnabled ((! m_in_exec && page && page->macro ()->interpreter () != lay::Macro::None) || m_in_breakpoint); + runButton->setEnabled ((! m_in_exec && (mp_run_macro || (page && page->macro () && page->macro ()->interpreter () != lay::Macro::None))) || m_in_breakpoint); + runThisButton->setEnabled ((! m_in_exec && page && page->macro () && page->macro ()->interpreter () != lay::Macro::None) || m_in_breakpoint); singleStepButton->setEnabled (! m_in_exec || m_in_breakpoint); nextStepButton->setEnabled (! m_in_exec || m_in_breakpoint); stopButton->setEnabled (m_in_exec); pauseButton->setEnabled (m_in_exec && ! m_in_breakpoint); + breakpointButton->setEnabled (page && page->macro ()); + clearBreakpointsButton->setEnabled (page && page->macro ()); for (std::vector::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) { (*mt)->setEditTriggers (m_in_exec ? QAbstractItemView::NoEditTriggers : QAbstractItemView::SelectedClicked); diff --git a/src/lay/layMacroEditorDialog.h b/src/lay/layMacroEditorDialog.h index 476baa492..b89615caf 100644 --- a/src/lay/layMacroEditorDialog.h +++ b/src/lay/layMacroEditorDialog.h @@ -192,6 +192,8 @@ private slots: void help_requested (const QString &s); void macro_changed (Macro *macro); void macro_deleted (Macro *macro); + void macro_collection_deleted (MacroCollection *collection); + void macro_collection_changed (MacroCollection *collection); void add_watch (); void edit_watch (); void del_watches (); @@ -248,10 +250,10 @@ private: void run (int stop_stack_depth, lay::Macro *macro); lay::Macro *current_run_macro (); void update_ui_to_run_mode (); + void do_update_ui_to_run_mode (); void set_run_macro (lay::Macro *m); void apply_search (bool if_needed); void process_events (QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents); - bool sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual); void sync_file_watcher (lay::MacroCollection *current); void do_refresh_file_watcher (); void refresh_file_watcher (); @@ -309,6 +311,7 @@ private: QTimer *m_file_changed_timer; std::vector m_changed_files, m_removed_files; tl::DeferredMethod dm_refresh_file_watcher; + tl::DeferredMethod dm_update_ui_to_run_mode; }; } diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index ca633aaa5..6da478457 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -85,8 +85,8 @@ #include "layLogViewerDialog.h" #include "layLayerToolbox.h" #include "laySettingsForm.h" -#include "laySettingsForm.h" -#include "layTechSetupDialog.h" +#include "layTechnologyController.h" +#include "laySaltController.h" #include "layTipDialog.h" #include "laySelectCellViewForm.h" #include "layLayoutPropertiesForm.h" @@ -928,6 +928,7 @@ MainWindow::init_menu () }; MenuLayoutEntry tools_menu [] = { + MenuLayoutEntry ("packages", tl::to_string (QObject::tr ("Manage Packages")), SLOT (cm_packages ())), MenuLayoutEntry ("technologies", tl::to_string (QObject::tr ("Manage Technologies")), SLOT (cm_technologies ())), MenuLayoutEntry::separator ("verification_group"), MenuLayoutEntry ("drc", tl::to_string (QObject::tr ("DRC")), drc_menu), @@ -997,6 +998,11 @@ MainWindow::init_menu () } + // Add a hook for inserting new items after the modes + Action end_modes; + end_modes.set_separator (true); + mp_menu->insert_item ("@toolbar.end", "end_modes", end_modes); + // make the plugins create their menu items for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { // TODO: get rid of the const_cast hack @@ -2602,7 +2608,6 @@ MainWindow::cm_cancel () { BEGIN_PROTECTED cancel (); - select_mode (lay::LayoutView::default_mode ()); END_PROTECTED } @@ -2612,8 +2617,6 @@ MainWindow::cm_cancel () void MainWindow::cancel () { - BEGIN_PROTECTED - // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. if (m_manager.transacting ()) { @@ -2624,7 +2627,7 @@ MainWindow::cancel () (*vp)->cancel (); } - END_PROTECTED + select_mode (lay::LayoutView::default_mode ()); } void @@ -4504,32 +4507,21 @@ MainWindow::show_progress_bar (bool show) } } +void +MainWindow::cm_packages () +{ + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + sc->show_editor (); + } +} + void MainWindow::cm_technologies () { - lay::TechSetupDialog dialog (this); - if (dialog.exec ()) { - - std::vector nm = lay::Application::instance ()->sync_tech_macro_locations (); - - bool has_autorun = false; - for (std::vector::const_iterator m = nm.begin (); m != nm.end () && ! has_autorun; ++m) { - has_autorun = (*m)->has_autorun (); - } - - if (has_autorun && QMessageBox::question (this, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with technologies now are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::const_iterator m = nm.begin (); m != nm.end (); ++m) { - (*m)->autorun (); - } - } - - // because the macro-tech association might have changed, do this: - // TODO: let the macro controller monitor the technologies. - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - mc->update_menu_with_macros (); - } - + lay::TechnologyController *tc = lay::TechnologyController::instance (); + if (tc) { + tc->show_editor (); } } diff --git a/src/lay/layMainWindow.h b/src/lay/layMainWindow.h index 55bb7adb0..715e47f40 100644 --- a/src/lay/layMainWindow.h +++ b/src/lay/layMainWindow.h @@ -720,6 +720,7 @@ public slots: void cm_macro_editor (); void cm_new_drc_script (); void cm_edit_drc_scripts (); + void cm_packages (); void cm_technologies (); void cm_open_too (); void cm_open_new_view (); diff --git a/src/lay/layResources.qrc b/src/lay/layResources.qrc index 3c2e3bdd4..6d7bbd6b0 100644 --- a/src/lay/layResources.qrc +++ b/src/lay/layResources.qrc @@ -113,8 +113,21 @@ images/upup.png images/waived.png images/yellow_flag.png + images/salt.png + images/salt_icon.png + images/warn.png + images/warn_16.png + images/empty_16.png + images/error_16.png + images/info_16.png + images/marked_24.png + images/marked_64.png + images/marked_16.png + images/folder_12.png + images/file_12.png + images/empty_12.png - + syntax/ruby.xml syntax/python.xml diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc new file mode 100644 index 000000000..7ef818c5b --- /dev/null +++ b/src/lay/laySalt.cc @@ -0,0 +1,499 @@ + +/* + + 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 "laySalt.h" +#include "tlString.h" +#include "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" +#include "tlWebDAV.h" + +#include +#include +#include +#include + +namespace lay +{ + +Salt::Salt () +{ + // .. nothing yet .. +} + +Salt::Salt (const Salt &other) + : QObject () +{ + operator= (other); +} + +Salt &Salt::operator= (const Salt &other) +{ + if (this != &other) { + emit collections_about_to_change (); + m_root = other.m_root; + invalidate (); + } + return *this; +} + +SaltGrains & +Salt::root () +{ + return m_root; +} + +Salt::flat_iterator +Salt::begin_flat () +{ + validate (); + return mp_flat_grains.begin (); +} + +Salt::flat_iterator +Salt::end_flat () +{ + validate (); + return mp_flat_grains.end (); +} + +SaltGrain * +Salt::grain_by_name (const std::string &name) +{ + validate (); + std::map::const_iterator g = m_grains_by_name.find (name); + if (g != m_grains_by_name.end ()) { + return g->second; + } else { + return 0; + } +} + +void +Salt::add_location (const std::string &path) +{ + tl_assert (! path.empty ()); + + if (path[0] != ':') { + // do nothing if the collection is already there + QFileInfo fi (tl::to_qstring (path)); + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + return; + } + } + } + + lay::SaltGrains gg = lay::SaltGrains::from_path (path); + emit collections_about_to_change (); + m_root.add_collection (gg); + invalidate (); +} + +void +Salt::remove_location (const std::string &path) +{ + QFileInfo fi (tl::to_qstring (path)); + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + emit collections_about_to_change (); + m_root.remove_collection (g, false); + invalidate (); + return; + } + } +} + +void +Salt::refresh () +{ + lay::SaltGrains new_root; + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + new_root.add_collection (lay::SaltGrains::from_path (g->path ())); + } + if (new_root != m_root) { + emit collections_about_to_change (); + m_root = new_root; + invalidate (); + } +} + +void +Salt::add_collection_to_flat (SaltGrains &gg) +{ + for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { + // TODO: get rid of the const cast - would require a non-const grain iterator + mp_flat_grains.push_back (const_cast (g.operator-> ())); + } + for (lay::SaltGrains::collection_iterator g = gg.begin_collections (); g != gg.end_collections (); ++g) { + // TODO: get rid of the const cast - would require a non-const grain collection iterator + add_collection_to_flat (const_cast (*g)); + } +} + +namespace { + +struct NameAndTopoIndexCompare +{ + NameAndTopoIndexCompare (const std::map &topo_index) + : mp_topo_index (&topo_index) + { + // .. nothing yet .. + } + + bool operator () (lay::SaltGrain *a, lay::SaltGrain *b) const + { + std::map::const_iterator ti_a = mp_topo_index->find (a->name ()); + std::map::const_iterator ti_b = mp_topo_index->find (b->name ()); + + // Reverse sorting by topological index as highest priority + if (ti_a != mp_topo_index->end () && ti_b != mp_topo_index->end ()) { + if (ti_a->second != ti_b->second) { + return ti_a->second > ti_b->second; + } + } + + // TODO: UTF-8 support? + return a->name () < b->name (); + } + +private: + const std::map *mp_topo_index; +}; + +} + +void +Salt::validate () +{ + if (mp_flat_grains.empty ()) { + + add_collection_to_flat (m_root); + + m_grains_by_name.clear (); + for (std::vector::const_iterator i = mp_flat_grains.begin (); i != mp_flat_grains.end (); ++i) { + m_grains_by_name.insert (std::make_pair ((*i)->name (), *i)); + } + + // Compute a set of topological indexes. Packages which serve depedencies of other packages have a higher + // topological index. Later we sort the packages by descending topo index to ensure the packages which are + // input to others come first. + + std::map topological_index; + for (std::map::const_iterator g = m_grains_by_name.begin (); g != m_grains_by_name.end (); ++g) { + topological_index.insert (std::make_pair (g->first, 0)); + } + + // NOTE: we allow max. 10 levels of dependencies. That should be sufficient. Limiting the levels of dependencies prevents + // infinite recursion due to faulty recursive dependencies. + for (int n = 0; n < 10; ++n) { + + bool any_updated = false; + + for (std::map::const_iterator g = m_grains_by_name.begin (); g != m_grains_by_name.end (); ++g) { + int index = topological_index [g->first]; + for (std::vector::const_iterator d = g->second->dependencies ().begin (); d != g->second->dependencies ().end (); ++d) { + std::map::iterator ti = topological_index.find (d->name); + if (ti != topological_index.end () && ti->second < index + 1) { + ti->second = index + 1; + any_updated = true; + } + } + } + + if (! any_updated) { + break; + } + + } + + // NOTE: we intentionally sort after the name list has been built - this way + // the first entry will win in the name to grain map. + std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameAndTopoIndexCompare (topological_index)); + + } +} + +void +Salt::invalidate () +{ + mp_flat_grains.clear (); + emit collections_changed (); +} + + +static +bool remove_from_collection (SaltGrains &collection, const std::string &name) +{ + bool res = false; + + for (SaltGrains::grain_iterator g = collection.begin_grains (); g != collection.end_grains (); ++g) { + if (g->name () == name) { + res = collection.remove_grain (g, true); + break; + } + } + + for (SaltGrains::collection_iterator gg = collection.begin_collections (); gg != collection.end_collections (); ++gg) { + // TODO: remove this const_cast + if (remove_from_collection (const_cast (*gg), name)) { + res = true; + } + } + + return res; +} + +bool +Salt::remove_grain (const SaltGrain &grain) +{ + emit collections_about_to_change (); + + tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ())); + bool res = remove_from_collection (m_root, grain.name ()); + if (res) { + tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ())); + } else { + tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ())); + } + + invalidate (); + return res; +} + +namespace +{ + +/** + * @brief A helper class required because directory traversal is not supported by QResource directly + * This class supports resource file trees and extraction of a tree from the latter + */ +class ResourceDir + : public QResource +{ +public: + using QResource::isFile; + + /** + * @brief Constructor + * Creates a resource representing a resource tree. + */ + ResourceDir (const QString &path) + : QResource (path) + { + // .. nothing yet .. + } + + /** + * @brief Writes the resource tree to the target directory + * Returns false on error - see log in this case. + */ + bool copy_to (const QDir &target) + { + if (isDir ()) { + + QStringList templ_dir = children (); + for (QStringList::const_iterator t = templ_dir.begin (); t != templ_dir.end (); ++t) { + + ResourceDir child_res (fileName () + QString::fromUtf8 ("/") + *t); + if (child_res.isFile ()) { + + QFile file (target.absoluteFilePath (*t)); + if (! file.open (QIODevice::WriteOnly)) { + tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (target.absoluteFilePath (*t)); + return false; + } + + QByteArray data; + if (child_res.isCompressed ()) { + data = qUncompress ((const unsigned char *)child_res.data (), (int)child_res.size ()); + } else { + data = QByteArray ((const char *)child_res.data (), (int)child_res.size ()); + } + + file.write (data); + + file.close (); + + } else { + + QFileInfo child_dir (target.absoluteFilePath (*t)); + if (! child_dir.exists ()) { + if (! target.mkdir (*t)) { + tl::error << QObject::tr ("Unable to create target directory: %1").arg (child_dir.path ()); + return false; + } + } else if (! child_dir.isDir ()) { + tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (child_dir.path ()); + return false; + } + + if (! child_res.copy_to (QDir (target.absoluteFilePath (*t)))) { + return false; + } + + } + + } + + } + + return true; + } +}; + +} + +bool +Salt::create_grain (const SaltGrain &templ, SaltGrain &target) +{ + tl_assert (!m_root.is_empty ()); + + const SaltGrains *coll = m_root.begin_collections ().operator-> (); + + if (target.name ().empty ()) { + target.set_name (templ.name ()); + } + + if (target.path ().empty ()) { + lay::SaltGrain *g = grain_by_name (target.name ()); + if (g) { + target.set_path (g->path ()); + } + } + + std::string path = target.path (); + if (! path.empty ()) { + coll = 0; + for (SaltGrains::collection_iterator gg = m_root.begin_collections (); gg != m_root.end_collections (); ++gg) { + if (tl::is_parent_path (tl::to_qstring (gg->path ()), tl::to_qstring (path))) { + coll = gg.operator-> (); + break; + } + } + tl_assert (coll != 0); + } + + tl::info << QObject::tr ("Installing package '%1' ..").arg (tl::to_qstring (target.name ())); + + QDir target_dir (tl::to_qstring (coll->path ())); + + try { + + // change down to the desired target location and create the directory structure while doing so + std::vector name_parts = tl::split (target.name (), "/"); + for (std::vector::const_iterator n = name_parts.begin (); n != name_parts.end (); ++n) { + + QFileInfo subdir (target_dir.filePath (tl::to_qstring (*n))); + if (subdir.exists () && ! subdir.isDir ()) { + throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package - is already a file").arg (subdir.path ()))); + } else if (! subdir.exists ()) { + if (! target_dir.mkdir (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package").arg (subdir.path ()))); + } + if (! target_dir.cd (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); + } + } else { + if (! target_dir.cd (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); + } + } + + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + return false; + } + + bool res = true; + + std::string target_name = target.name (); + target = templ; + target.set_path (tl::to_string (target_dir.absolutePath ())); + target.set_name (target_name); + + if (! templ.path ().empty ()) { + + if (templ.path ()[0] != ':') { + + // if the template represents an actual folder, use the files from there + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (templ.path (), target.path ()); + + } else { + + // if the template represents a resource path, use the files from there + tl::info << QObject::tr ("Installing package from resource '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = ResourceDir (tl::to_qstring (templ.path ())).copy_to (QDir (tl::to_qstring (target.path ()))); + + } + + } else if (! templ.url ().empty ()) { + + if (templ.url ().find ("http:") == 0 || templ.url ().find ("https:") == 0) { + + // otherwise download from the URL + tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); + res = tl::WebDAVObject::download (templ.url (), target.path ()); + + } else { + + // or copy from a file path for "file" URL's + std::string src = templ.url (); + if (src.find ("file:") == 0) { + QUrl url (tl::to_qstring (src)); + src = tl::to_string (QFileInfo (url.toLocalFile ()).absoluteFilePath ()); + } + + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (src)).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (src, target.path ()); + + } + + target.set_url (templ.url ()); + + } + + if (res) { + + tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); + target.set_installed_time (QDateTime::currentDateTime ()); + target.save (); + + // NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure + refresh (); + + } else { + + tl::warn << QObject::tr ("Failed to install package '%1' - removing files ..").arg (tl::to_qstring (target.name ())); + if (! tl::rm_dir_recursive (target.path ())) { + tl::warn << QObject::tr ("Failed to remove files").arg (tl::to_qstring (target.name ())); + } + + } + + return res; +} + +} diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h new file mode 100644 index 000000000..aef3da0ac --- /dev/null +++ b/src/lay/laySalt.h @@ -0,0 +1,218 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySalt +#define HDR_laySalt + +#include "layCommon.h" +#include "laySaltGrain.h" +#include "laySaltGrains.h" + +#include + +#include + +namespace lay +{ + +/** + * @brief The global salt (package manager) object + * This object can be configured to represent a couple of locations. + * It will provide a collection of grains for these locations. + */ +class LAY_PUBLIC Salt + : public QObject +{ +Q_OBJECT + +public: + typedef SaltGrains::collection_iterator iterator; + typedef std::vector::const_iterator flat_iterator; + + /** + * @brief Default constructor + */ + Salt (); + + /** + * @brief Copy constructor + */ + Salt (const Salt &other); + + /** + * @brief assignment + */ + Salt &operator= (const Salt &other); + + /** + * @brief Adds the given location to the ones the package manager uses + * Adding a location will scan the folder and make the contents available + * as a new collection. + */ + void add_location (const std::string &path); + + /** + * @brief Removes a given location + * This will remove the collection from the package locations. + */ + void remove_location (const std::string &path); + + /** + * @brief Refreshes the collections + * This method rescans all registered locations. + */ + void refresh (); + + /** + * @brief Iterates the collections (begin) + */ + iterator begin () const + { + return m_root.begin_collections (); + } + + /** + * @brief Iterates the collections (end) + */ + iterator end () const + { + return m_root.end_collections (); + } + + /** + * @brief Returns a value indicating whether the collection is empty + */ + bool is_empty () const + { + return m_root.is_empty (); + } + + /** + * @brief A flat iterator of (sorted) grains (begin) + */ + flat_iterator begin_flat (); + + /** + * @brief A flat iterator of (sorted) grains (end) + */ + flat_iterator end_flat (); + + /** + * @brief Gets the grain with the given name + */ + SaltGrain *grain_by_name (const std::string &name); + + /** + * @brief Gets the grain with the given name (const version) + */ + const SaltGrain *grain_by_name (const std::string &name) const + { + return const_cast (this)->grain_by_name (name); + } + + /** + * @brief Loads the salt from a "salt mine" file + */ + void load (const std::string &p) + { + m_root.load (p); + } + + /** + * @brief Loads the salt from a "salt mine" stream + */ + void load (tl::InputStream &s) + { + m_root.load (s); + } + + /** + * @brief Saves the salt to a "salt mine" file + * This feature is provided for debugging purposes mainly. + */ + void save (const std::string &p) + { + m_root.save (p); + } + + /** + * @brief Removes a grain from the salt + * + * This operation will remove the grain with the given name from the salt and delete all files and directories related to it. + * If multiple grains with the same name exist, they will all be removed. + * + * Returns true, if the package could be removed successfully. + */ + bool remove_grain (const SaltGrain &grain); + + /** + * @brief Creates a new grain from a template + * + * This method will create a folder for a grain with the given path and download or copy + * all files related to this grain. It will copy the download URL from the template into the + * new grain, so updates will come from the original location. + * + * If the target's name is not set, it will be taken from the template. + * If the target's path is not set and a grain with the given name already exists in + * the package, the path is taken from that grain. + * If no target path is set and no grain with this name exists yet, a new path will + * be constructed using the first location in the salt. + * + * The target grain will be updated with the installation information. If the target grain + * contains an installation path prior to the installation, this path will be used for the + * installation of the grain files. + * + * Returns true, if the package could be created successfully. + */ + bool create_grain (const SaltGrain &templ, SaltGrain &target); + + /** + * @brief Gets the root collection + * + * This method is provided for test purposes mainly. + */ + SaltGrains &root (); + +signals: + /** + * @brief A signal triggered before one of the collections changed + */ + void collections_about_to_change (); + + /** + * @brief A signal triggered when one of the collections changed + */ + void collections_changed (); + +private: + SaltGrains m_root; + std::vector mp_flat_grains; + std::map m_grains_by_name; + + void validate (); + void invalidate (); + void add_collection_to_flat (lay::SaltGrains &gg); +}; + +} + +#endif diff --git a/src/lay/laySaltController.cc b/src/lay/laySaltController.cc new file mode 100644 index 000000000..98e5e0174 --- /dev/null +++ b/src/lay/laySaltController.cc @@ -0,0 +1,215 @@ + +/* + + 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 "laySaltController.h" +#include "laySaltManagerDialog.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "layQtTools.h" +#include "tlLog.h" + +#include + +namespace lay +{ + +static const std::string cfg_salt_manager_window_state ("salt-manager-window-state"); + +SaltController::SaltController () + : mp_salt_dialog (0), mp_mw (0), m_file_watcher (0), + dm_sync_file_watcher (this, &SaltController::sync_file_watcher), + dm_sync_files (this, &SaltController::sync_files) +{ +} + +void +SaltController::initialized (lay::PluginRoot *root) +{ + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + mp_mw = dynamic_cast (root); + + connect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); +} + +void +SaltController::uninitialize (lay::PluginRoot * /*root*/) +{ + disconnect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); + + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + + delete mp_salt_dialog; + mp_salt_dialog = 0; + mp_mw = 0; +} + +void +SaltController::get_options (std::vector < std::pair > &options) const +{ + options.push_back (std::pair (cfg_salt_manager_window_state, "")); +} + +void +SaltController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +SaltController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +SaltController::config_finalize() +{ + // .. nothing yet .. +} + +bool +SaltController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +bool +SaltController::accepts_drop (const std::string & /*path_or_url*/) const +{ + // .. nothing yet .. + return false; +} + +void +SaltController::drop_url (const std::string & /*path_or_url*/) +{ + // .. nothing yet .. +} + +void +SaltController::show_editor () +{ + if (mp_mw && !mp_salt_dialog) { + + mp_salt_dialog = new lay::SaltManagerDialog (mp_mw, &m_salt, m_salt_mine_url); + + } + + if (mp_salt_dialog) { + + if (mp_mw) { + std::string s = mp_mw->config_get (cfg_salt_manager_window_state); + if (! s.empty ()) { + lay::restore_dialog_state (mp_salt_dialog, s); + } + } + + // while running the dialog, don't watch file events - that would interfere with + // the changes applied by the dialog itself. + m_file_watcher->enable (false); + mp_salt_dialog->exec (); + m_file_watcher->enable (true); + + if (mp_mw) { + mp_mw->config_set (cfg_salt_manager_window_state, lay::save_dialog_state (mp_salt_dialog)); + } + + sync_file_watcher (); + + } +} + +void +SaltController::sync_file_watcher () +{ + m_file_watcher->clear (); + m_file_watcher->enable (false); + for (lay::Salt::flat_iterator g = m_salt.begin_flat (); g != m_salt.end_flat (); ++g) { + m_file_watcher->add_file ((*g)->path ()); + } + m_file_watcher->enable (true); +} + +void +SaltController::sync_files () +{ + tl::log << tl::to_string (tr ("Detected file system change in packages - updating")); + emit salt_changed (); +} + +void +SaltController::add_path (const std::string &path) +{ + try { + + std::string tp = tl::to_string (QDir (tl::to_qstring (path)).filePath (QString::fromUtf8 ("salt"))); + + tl::log << tl::to_string (tr ("Scanning %1 for packages").arg (tl::to_qstring (tp))); + m_salt.add_location (tp); + + dm_sync_file_watcher (); + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } +} + +void +SaltController::file_watcher_triggered () +{ + dm_sync_files (); +} + +void +SaltController::set_salt_mine_url (const std::string &url) +{ + m_salt_mine_url = url; +} + +SaltController * +SaltController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + SaltController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the salt controller +static tl::RegisteredClass salt_controller_decl (new lay::SaltController (), 100, "SaltController"); + +} + diff --git a/src/lay/laySaltController.h b/src/lay/laySaltController.h new file mode 100644 index 000000000..e1687f667 --- /dev/null +++ b/src/lay/laySaltController.h @@ -0,0 +1,178 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_laySaltController +#define HDR_laySaltController + +#include "layCommon.h" +#include "layPlugin.h" +#include "laySalt.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" + +#include +#include + +#include + +namespace lay +{ + +class SaltManagerDialog; +class MainWindow; + +/** + * @brief A controller for the salt package manager + * + * This object is a singleton that acts as a controller + * for the package management. The controller is responsible + * to managing the packages and notifying package consumers + * of changes. + * + * It interacts with the SaltManagerDialog which basically + * is the view for the packages. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class SaltController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + SaltController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool accepts_drop (const std::string &path_or_url) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void drop_url (const std::string &path_or_url); + + /** + * @brief Shows the package editor + */ + void show_editor (); + + /** + * @brief Adds a search path to the package manager + */ + void add_path (const std::string &path); + + /** + * @brief Specifies the salt mine (package repository) URL + */ + void set_salt_mine_url (const std::string &url); + + /** + * @brief Gets the salt + */ + lay::Salt &salt () + { + return m_salt; + } + + /** + * @brief Gets the salt (const version) + */ + const lay::Salt &salt () const + { + return m_salt; + } + + /** + * @brief Gets the singleton instance for this object + */ + static SaltController *instance (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + +signals: + /** + * @brief This signal is emitted if the salt changed + */ + void salt_changed (); + +private: + lay::SaltManagerDialog *mp_salt_dialog; + lay::MainWindow *mp_mw; + std::string m_salt_mine_url; + lay::Salt m_salt, m_salt_mine; + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_file_watcher; + tl::DeferredMethod dm_sync_files; + + void sync_file_watcher (); + void sync_files (); +}; + +} + +#endif + diff --git a/src/lay/laySaltDownloadManager.cc b/src/lay/laySaltDownloadManager.cc new file mode 100644 index 000000000..08956c7f6 --- /dev/null +++ b/src/lay/laySaltDownloadManager.cc @@ -0,0 +1,243 @@ + +/* + + 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 "laySaltDownloadManager.h" +#include "laySalt.h" +#include "tlFileUtils.h" +#include "tlWebDAV.h" + +#include "ui_SaltManagerInstallConfirmationDialog.h" + +#include +#include + +namespace lay +{ + +// ---------------------------------------------------------------------------------- + +class ConfirmationDialog + : public QDialog, private Ui::SaltManagerInstallConfirmationDialog +{ +public: + ConfirmationDialog (QWidget *parent) + : QDialog (parent) + { + Ui::SaltManagerInstallConfirmationDialog::setupUi (this); + } + + void add_info (const std::string &name, bool update, const std::string &version, const std::string &url) + { + QTreeWidgetItem *item = new QTreeWidgetItem (list); + + item->setFlags (item->flags () & ~Qt::ItemIsSelectable); + + item->setText (0, tl::to_qstring (name)); + item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL")); + item->setText (2, tl::to_qstring (version)); + item->setText (3, tl::to_qstring (url)); + + for (int column = 0; column < list->colorCount (); ++column) { + item->setData (column, Qt::ForegroundRole, update ? Qt::blue : Qt::black); + } + } +}; + +// ---------------------------------------------------------------------------------- + +SaltDownloadManager::SaltDownloadManager () +{ + // .. nothing yet .. +} + +void +SaltDownloadManager::register_download (const std::string &name, const std::string &url, const std::string &version) +{ + m_registry.insert (std::make_pair (name, Descriptor (url, version))); +} + +void +SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Salt &salt_mine) +{ + tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Computing package dependencies .."))); + + std::map registry; + + // remove those registered entries which don't need to be updated + + registry = m_registry; + for (std::map::const_iterator p = registry.begin (); p != registry.end (); ++p) { + const SaltGrain *g = salt.grain_by_name (p->first); + if (g && SaltGrain::compare_versions (p->second.version, g->version ()) == 0 && p->second.url == g->url ()) { + m_registry.erase (p->first); + } + } + + // add further entries as derived from the dependencies + + while (needs_iteration ()) { + + fetch_missing (salt_mine, progress); + + registry = m_registry; + for (std::map::const_iterator p = registry.begin (); p != registry.end (); ++p) { + + for (std::vector::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) { + + std::map::iterator r = m_registry.find (d->name); + if (r != m_registry.end ()) { + + if (SaltGrain::compare_versions (r->second.version, d->version) < 0) { + + // Grain is present, but too old -> update version and reload in the next iteration + r->second.downloaded = false; + r->second.version = d->version; + r->second.url = d->url; + r->second.downloaded = false; + + } + + } else { + + const SaltGrain *g = salt.grain_by_name (d->name); + if (g) { + + // Grain is installed already, but too old -> register for update + if (SaltGrain::compare_versions (g->version (), d->version) < 0) { + register_download (d->name, d->url, d->version); + } + + } else { + register_download (d->name, d->url, d->version); + } + + } + + } + + } + + } +} + +bool +SaltDownloadManager::needs_iteration () +{ + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + if (! p->second.downloaded) { + return true; + } + } + return false; +} + +void +SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress) +{ + for (std::map::iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + + if (! p->second.downloaded) { + + ++progress; + + // If no URL is given, utilize the salt mine to fetch it + if (p->second.url.empty ()) { + const lay::SaltGrain *g = salt_mine.grain_by_name (p->first); + if (SaltGrain::compare_versions (g->version (), p->second.version) < 0) { + throw tl::Exception (tl::to_string (QObject::tr ("Package '%1': package in repository is too old (%2) to satisfy requirements (%3)").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (p->second.version)))); + } + p->second.version = g->version (); + p->second.url = g->url (); + } + + try { + p->second.grain = SaltGrain::from_url (p->second.url); + } catch (tl::Exception &ex) { + throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (ex.msg ())))); + } + + p->second.downloaded = true; + + } + + } +} + +bool +SaltDownloadManager::show_confirmation_dialog (QWidget *parent, const lay::Salt &salt) +{ + // Stop with a warning if there is nothing to do + if (m_registry.empty()) { + QMessageBox::warning (parent, tr ("Nothing to do"), tr ("No packages need update or are marked for installation")); + return false; + } + + lay::ConfirmationDialog dialog (parent); + + // First the packages to update + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + const lay::SaltGrain *g = salt.grain_by_name (p->first); + if (g) { + // \342\206\222 is UTF-8 "right arrow" + dialog.add_info (p->first, true, g->version () + " \342\206\222 " + p->second.version, p->second.url); + } + } + + // Then the packages to install + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + const lay::SaltGrain *g = salt.grain_by_name (p->first); + if (!g) { + dialog.add_info (p->first, false, p->second.version, p->second.url); + } + } + + return dialog.exec (); +} + +bool +SaltDownloadManager::execute (lay::Salt &salt) +{ + bool result = true; + + tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading packages")), m_registry.size (), 1); + + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + + lay::SaltGrain target; + target.set_name (p->first); + lay::SaltGrain *g = salt.grain_by_name (p->first); + if (g) { + target.set_path (g->path ()); + } + + if (! salt.create_grain (p->second.grain, target)) { + result = false; + } + + ++progress; + + } + + return result; +} + +} diff --git a/src/lay/laySaltDownloadManager.h b/src/lay/laySaltDownloadManager.h new file mode 100644 index 000000000..89fd74620 --- /dev/null +++ b/src/lay/laySaltDownloadManager.h @@ -0,0 +1,117 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltDownloadManager +#define HDR_laySaltDownloadManager + +#include "layCommon.h" +#include "laySaltGrain.h" +#include "tlProgress.h" + +#include +#include +#include + +namespace lay +{ + +class Salt; + +/** + * @brief The download manager + * + * This class is responsible for handling the downloads for + * grains. The basic sequence is: + * + "register_download" (multiple times) to register the packages intended for download + * + "compute_dependencies" to determine all related packages + * + (optional) "show_confirmation_dialog" + * + "execute" to actually execute the downloads + */ +class LAY_PUBLIC SaltDownloadManager + : public QObject +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + SaltDownloadManager (); + + /** + * @brief Registers an URL (with version) for download in the given target directory + * + * The target directory can be empty. In this case, the downloader will pick an approriate one. + */ + void register_download (const std::string &name, const std::string &url, const std::string &version); + + /** + * @brief Computes the dependencies after all required packages have been registered + * + * This method will compute the dependencies. Packages not present in the list of + * packages ("salt" argument), will be scheduled for download too. Dependency packages + * are looked up in "salt_mine" if no download URL is given. + */ + void compute_dependencies (const lay::Salt &salt, const Salt &salt_mine); + + /** + * @brief Presents a dialog showing the packages scheduled for download + * + * This method requires all dependencies to be computed. It will return false + * if the dialog is not confirmed. + * + * "salt" needs to be the currently installed packages so the dialog can + * indicate which packages will be updated. + */ + bool show_confirmation_dialog (QWidget *parent, const lay::Salt &salt); + + /** + * @brief Actually execute the downloads + * + * This method will return false if anything goes wrong. + * Failed packages will be removed entirely after they have been listed in + * an error dialog. + */ + bool execute (lay::Salt &salt); + +private: + struct Descriptor + { + Descriptor (const std::string &_url, const std::string &_version) + : url (_url), version (_version), downloaded (false) + { } + + std::string url; + std::string version; + bool downloaded; + lay::SaltGrain grain; + }; + + std::map m_registry; + + bool needs_iteration (); + void fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress); +}; + +} + +#endif diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc new file mode 100644 index 000000000..d1a93d396 --- /dev/null +++ b/src/lay/laySaltGrain.cc @@ -0,0 +1,465 @@ + +/* + + 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 "laySaltGrain.h" +#include "tlString.h" +#include "tlXMLParser.h" +#include "tlHttpStream.h" + +#include +#include +#include +#include +#include + +namespace lay +{ + +static const std::string grain_filename = "grain.xml"; + +SaltGrain::SaltGrain () +{ + // .. nothing yet .. +} + +bool +SaltGrain::operator== (const SaltGrain &other) const +{ + return m_name == other.m_name && + m_path == other.m_path && + m_version == other.m_version && + m_api_version == other.m_api_version && + m_url == other.m_url && + m_title == other.m_title && + m_doc == other.m_doc && + m_doc_url == other.m_doc_url && + m_icon == other.m_icon && + m_screenshot == other.m_screenshot && + m_dependencies == other.m_dependencies && + m_author == other.m_author && + m_author_contact == other.m_author_contact && + m_license == other.m_license && + m_authored_time == other.m_authored_time && + m_installed_time == other.m_installed_time; +} + +void +SaltGrain::set_name (const std::string &n) +{ + m_name = n; +} + +void +SaltGrain::set_version (const std::string &v) +{ + m_version = v; +} + +void +SaltGrain::set_api_version (const std::string &v) +{ + m_api_version = v; +} + +void +SaltGrain::set_path (const std::string &p) +{ + m_path = p; +} + +void +SaltGrain::set_url (const std::string &u) +{ + m_url = u; +} + +void +SaltGrain::set_title (const std::string &t) +{ + m_title = t; +} + +void +SaltGrain::set_doc (const std::string &t) +{ + m_doc = t; +} + +void +SaltGrain::set_doc_url (const std::string &u) +{ + m_doc_url = u; +} + +std::string +SaltGrain::eff_doc_url () const +{ + if (m_doc_url.empty ()) { + return std::string (); + } + + QUrl url (tl::to_qstring (m_doc_url)); + if (! url.scheme ().isEmpty ()) { + return m_doc_url; + } + + if (! path ().empty ()) { + + // if the URL is a relative URL, make it absolute relative to the grain's installation directory + QFileInfo fi (url.toLocalFile ()); + if (! fi.isAbsolute ()) { + url = QUrl::fromLocalFile (QDir (tl::to_qstring (path ())).absoluteFilePath (fi.filePath ())); + } + url.setScheme (tl::to_qstring ("file")); + return tl::to_string (url.toString ()); + + } else { + + // base the documentation URL on the download URL + QUrl eff_url = QUrl (tl::to_qstring (m_url)); + eff_url.setPath (eff_url.path () + QString::fromUtf8 ("/") + url.path ()); + return tl::to_string (eff_url.toString ()); + + } +} + +void +SaltGrain::set_author (const std::string &a) +{ + m_author = a; +} + +void +SaltGrain::set_author_contact (const std::string &a) +{ + m_author_contact = a; +} + +void +SaltGrain::set_license (const std::string &l) +{ + m_license = l; +} + +void +SaltGrain::set_authored_time (const QDateTime &t) +{ + m_authored_time = t; +} + +void +SaltGrain::set_installed_time (const QDateTime &t) +{ + m_installed_time = t; +} + +void +SaltGrain::set_screenshot (const QImage &i) +{ + m_screenshot = i; +} + +void +SaltGrain::set_icon (const QImage &i) +{ + m_icon = i; +} + +int +SaltGrain::compare_versions (const std::string &v1, const std::string &v2) +{ + tl::Extractor ex1 (v1.c_str ()); + tl::Extractor ex2 (v2.c_str ()); + + while (true) { + + if (ex1.at_end () && ex2.at_end ()) { + return 0; + } + + int n1 = 0, n2 = 0; + if (! ex1.at_end ()) { + ex1.try_read (n1); + } + if (! ex2.at_end ()) { + ex2.try_read (n2); + } + + if (n1 != n2) { + return n1 < n2 ? -1 : 1; + } + + while (! ex1.at_end ()) { + char c = *ex1; + ++ex1; + if (c == '.') { + break; + } + } + + while (! ex2.at_end ()) { + char c = *ex2; + ++ex2; + if (c == '.') { + break; + } + } + + } +} + +std::string +SaltGrain::spec_url (const std::string &url) +{ + std::string res = url; + if (! res.empty()) { + // TODO: use system path separator unless this is a URL + if (res [res.size () - 1] != '/') { + res += "/"; + } + res += grain_filename; + } + return res; +} + +bool +SaltGrain::valid_name (const std::string &n) +{ + std::string res; + + tl::Extractor ex (n); + + std::string s; + if (! ex.try_read_word (s, "_")) { + return false; + } + res += s; + + while (! ex.at_end ()) { + if (! ex.test ("/")) { + return false; + } + if (! ex.try_read_word (s, "_")) { + return false; + } + res += "/"; + res += s; + } + + // this captures the cases where the extractor skips blanks + // TODO: the extractor should have a "non-blank-skipping" mode + return res == n; +} + +bool +SaltGrain::valid_version (const std::string &v) +{ + tl::Extractor ex (v.c_str ()); + + while (! ex.at_end ()) { + int n = 0; + if (! ex.try_read (n)) { + return false; + } + if (! ex.at_end ()) { + if (*ex != '.') { + return false; + } else { + ++ex; + } + } + } + + return true; +} + +struct TimeConverter +{ + std::string to_string (const QDateTime &time) const + { + if (time.isNull ()) { + return std::string (); + } else { + return tl::to_string (time.toString (Qt::ISODate)); + } + } + + void from_string (const std::string &time, QDateTime &res) const + { + if (time.empty ()) { + res = QDateTime (); + } else { + res = QDateTime::fromString (tl::to_qstring (time), Qt::ISODate); + } + } +}; + +struct ImageConverter +{ + std::string to_string (const QImage &image) const + { + if (image.isNull ()) { + return std::string (); + } else { + QBuffer buffer; + buffer.open (QIODevice::WriteOnly); + image.save (&buffer, "PNG"); + buffer.close (); + return buffer.buffer ().toBase64 ().constData (); + } + } + + void from_string (const std::string &image, QImage &res) const + { + if (image.empty ()) { + res = QImage (); + } else { + res = QImage::fromData (QByteArray::fromBase64 (QByteArray (image.c_str (), image.size ()))); + } + } +}; + +static tl::XMLElementList s_xml_elements = + tl::make_member (&SaltGrain::name, &SaltGrain::set_name, "name") + + tl::make_member (&SaltGrain::version, &SaltGrain::set_version, "version") + + tl::make_member (&SaltGrain::api_version, &SaltGrain::set_api_version, "api-version") + + tl::make_member (&SaltGrain::title, &SaltGrain::set_title, "title") + + tl::make_member (&SaltGrain::doc, &SaltGrain::set_doc, "doc") + + tl::make_member (&SaltGrain::doc_url, &SaltGrain::set_doc_url, "doc-url") + + tl::make_member (&SaltGrain::url, &SaltGrain::set_url, "url") + + tl::make_member (&SaltGrain::license, &SaltGrain::set_license, "license") + + tl::make_member (&SaltGrain::author, &SaltGrain::set_author, "author") + + tl::make_member (&SaltGrain::author_contact, &SaltGrain::set_author_contact, "author-contact") + + tl::make_member (&SaltGrain::authored_time, &SaltGrain::set_authored_time, "authored-time", TimeConverter ()) + + tl::make_member (&SaltGrain::installed_time, &SaltGrain::set_installed_time, "installed-time", TimeConverter ()) + + tl::make_member (&SaltGrain::icon, &SaltGrain::set_icon, "icon", ImageConverter ()) + + tl::make_member (&SaltGrain::screenshot, &SaltGrain::set_screenshot, "screenshot", ImageConverter ()) + + tl::make_element (&SaltGrain::begin_dependencies, &SaltGrain::end_dependencies, &SaltGrain::add_dependency, "depends", + tl::make_member (&SaltGrain::Dependency::name, "name") + + tl::make_member (&SaltGrain::Dependency::url, "url") + + tl::make_member (&SaltGrain::Dependency::version, "version") + ); + +static tl::XMLStruct s_xml_struct ("salt-grain", s_xml_elements); + +tl::XMLElementList & +SaltGrain::xml_struct () +{ + return s_xml_elements; +} + +bool +SaltGrain::is_readonly () const +{ + // A grain is readonly if the directory is not writable or there is a download URL + // (this means the grain has been installed from an URL). + return !QFileInfo (tl::to_qstring (path ())).isWritable () || !m_url.empty (); +} + +void +SaltGrain::load (const std::string &p) +{ + tl_assert (!p.empty ()); + + if (p[0] != ':') { + + tl::XMLFileSource source (p); + s_xml_struct.parse (source, *this); + + } else { + + QResource res (tl::to_qstring (p)); + QByteArray data; + if (res.isCompressed ()) { + data = qUncompress ((const unsigned char *)res.data (), (int)res.size ()); + } else { + data = QByteArray ((const char *)res.data (), (int)res.size ()); + } + + std::string str_data (data.constData (), data.size ()); + tl::XMLStringSource source (str_data); + s_xml_struct.parse (source, *this); + + } +} + +void +SaltGrain::load (tl::InputStream &p) +{ + tl::XMLStreamSource source (p); + s_xml_struct.parse (source, *this); +} + +void +SaltGrain::save () const +{ + save (tl::to_string (QDir (tl::to_qstring (path ())).filePath (tl::to_qstring (grain_filename)))); +} + +void +SaltGrain::save (const std::string &p) const +{ + tl::OutputStream os (p, tl::OutputStream::OM_Plain); + s_xml_struct.write (os, *this); +} + +SaltGrain +SaltGrain::from_path (const std::string &path) +{ + QDir dir (tl::to_qstring (path)); + + SaltGrain g; + g.load (tl::to_string (dir.filePath (tl::to_qstring (grain_filename)))); + g.set_path (tl::to_string (dir.absolutePath ())); + return g; +} + +SaltGrain +SaltGrain::from_url (const std::string &url) +{ + if (url.empty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("No download link available"))); + } + + tl::InputStream stream (SaltGrain::spec_url (url)); + + SaltGrain g; + g.load (stream); + g.set_url (url); + return g; +} + +bool +SaltGrain::is_grain (const std::string &path) +{ + tl_assert (! path.empty ()); + + if (path[0] != ':') { + QDir dir (tl::to_qstring (path)); + QString gf = dir.filePath (tl::to_qstring (grain_filename)); + return QFileInfo (gf).exists (); + } else { + return QResource (tl::to_qstring (path + "/" + grain_filename)).isValid (); + } +} + +} diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h new file mode 100644 index 000000000..a8c7bff8c --- /dev/null +++ b/src/lay/laySaltGrain.h @@ -0,0 +1,446 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltGrain +#define HDR_laySaltGrain + +#include "layCommon.h" +#include "tlObject.h" +#include "tlStream.h" +#include "tlXMLParser.h" + +#include +#include + +namespace lay +{ + +/** + * @brief This class represents on grain of salt + * "One grain of salt" is one package. + */ +class LAY_PUBLIC SaltGrain + : public tl::Object +{ +public: + /** + * @brief A descriptor for one dependency + * A dependency can be specified either through a name (see name property) + * or a download URL. If download URL are specified, they have precedence + * over names. + * The version is the minimum required version. If empty, any version is + * allowed to resolve this dependency. + */ + struct Dependency + { + std::string name; + std::string url; + std::string version; + + bool operator== (const Dependency &other) const + { + return name == other.name && url == other.url && version == other.version; + } + }; + + /** + * @brief Constructor + */ + SaltGrain (); + + /** + * @brief Equality + */ + bool operator== (const SaltGrain &other) const; + + /** + * @brief Inequality + */ + bool operator!= (const SaltGrain &other) const + { + return !operator== (other); + } + + /** + * @brief Gets the name of the grain + * + * The name is either a plain name (a word) or a path into a collection. + * Name paths are formed using the "/" separator. "mypackage" is a plain name, + * while "mycollection/mypackage" is a package within a collection. Collections + * can be used to group packages. Names are case sensitive in general, but + * names differing only in case should be avoided. + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the name of the grain + */ + void set_name (const std::string &p); + + /** + * @brief Gets the title of the grain + * + * The title is a brief description that is shown in the title of the + * package manager. + */ + const std::string &title () const + { + return m_title; + } + + /** + * @brief Sets the title of the grain + */ + void set_title (const std::string &t); + + /** + * @brief Gets the documentation text of the grain + * + * The documentation text is a brief description. + */ + const std::string &doc () const + { + return m_doc; + } + + /** + * @brief Sets the documentation text of the grain + */ + void set_doc (const std::string &t); + + /** + * @brief Gets the documentation URL of the grain + * + * The documentation URL provides a detailed documentation. + */ + const std::string &doc_url () const + { + return m_doc_url; + } + + /** + * @brief Sets the documentation URL of the grain + */ + void set_doc_url (const std::string &u); + + /** + * @brief Gets the effective documentation URL + * + * The effective documentation URL is formed from the installation path + * and the documentation URL if the latter is a relative one. + */ + std::string eff_doc_url () const; + + /** + * @brief Gets the version of the grain + * + * A version string is of the form "x.y..." where x, y and other version + * components are integer numbers. + */ + const std::string &version () const + { + return m_version; + } + + /** + * @brief Sets the version of the grain + */ + void set_version (const std::string &v); + + /** + * @brief Gets the API version of the grain + * + * The API version is the KLayout version required to run the grain's macros. + * A version string is of the form "x.y..." where x, y and other version + * components are integer numbers. + */ + const std::string &api_version () const + { + return m_api_version; + } + + /** + * @brief Sets the API version of the grain + */ + void set_api_version (const std::string &v); + + /** + * @brief Gets the author of the grain + */ + const std::string &author () const + { + return m_author; + } + + /** + * @brief Sets the author of the grain + */ + void set_author (const std::string &a); + + /** + * @brief Gets the author's contact + */ + const std::string &author_contact () const + { + return m_author_contact; + } + + /** + * @brief Sets the author's contact + */ + void set_author_contact (const std::string &a); + + /** + * @brief Gets the license of the grain + */ + const std::string &license () const + { + return m_license; + } + + /** + * @brief Sets the license of the grain + */ + void set_license (const std::string &l); + + /** + * @brief Gets the release date and/or time of the grain + */ + const QDateTime &authored_time () const + { + return m_authored_time; + } + + /** + * @brief Sets the release date and/or time + */ + void set_authored_time (const QDateTime &t); + + /** + * @brief Gets the installation date and/or time of the grain + */ + const QDateTime &installed_time () const + { + return m_installed_time; + } + + /** + * @brief Sets the installation date and/or time + */ + void set_installed_time (const QDateTime &t); + + /** + * @brief Gets the icon image for the grain. + * The preferred image size is 64x64 pixels. + * The image may be null image. In this case, a default image is used. + */ + const QImage &icon () const + { + return m_icon; + } + + /** + * @brief Sets icon image + */ + void set_icon (const QImage &i); + + /** + * @brief Gets a screenshot image for documentation. + * The image may be null image. In this case, no screenshot is shown. + */ + const QImage &screenshot () const + { + return m_screenshot; + } + + /** + * @brief Sets screenshot image + */ + void set_screenshot (const QImage &i); + + /** + * @brief Gets the absolute file path of the installed grain + * This is the file path to the grain folder. + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Sets the absolute file path of the installed grain + */ + void set_path (const std::string &p); + + /** + * @brief Gets the download URL + * The download URL is the place from which the grain was installed originally. + */ + const std::string &url () const + { + return m_url; + } + + /** + * @brief Sets the download URL + */ + void set_url (const std::string &u); + + /** + * @brief Gets the dependencies of the grain + * Grains this grain depends on are installed automatically when the grain + * is installed. + */ + const std::vector &dependencies () const + { + return m_dependencies; + } + + /** + * @brief Gets the dependencies of the grain (non-const) + */ + std::vector &dependencies () + { + return m_dependencies; + } + + /** + * @brief Dependency iterator (begin) + */ + std::vector::const_iterator begin_dependencies () const + { + return m_dependencies.begin (); + } + + /** + * @brief Dependency iterator (end) + */ + std::vector::const_iterator end_dependencies () const + { + return m_dependencies.end (); + } + + /** + * @brief Adds a dependency + */ + void add_dependency (const Dependency &dep) + { + m_dependencies.push_back (dep); + } + + /** + * @brief Returns true, if the collection is read-only + */ + bool is_readonly () const; + + /** + * @brief Loads the data from a given file + * This method will *not* set the path. + */ + void load (const std::string &file_path); + + /** + * @brief Loads the data from a given stream + */ + void load (tl::InputStream &stream); + + /** + * @brief Saves the data to the path inside the grain folder given by the "path" property + */ + void save () const; + + /** + * @brief Saves the data to the given file + */ + void save (const std::string &file_path) const; + + /** + * @brief Gets the XML structure representing a grain + */ + static tl::XMLElementList &xml_struct (); + + /** + * @brief Compares two version strings + * Returns -1 if v1 < v2, 0 if v1 == v2 and 1 if v1 > v2. + * Malformed versions are read gracefully. Letters and non-digits are skipped. + * Missing numbers are read as 0. Hence "1.0 == 1" for example. + */ + static int compare_versions (const std::string &v1, const std::string &v2); + + /** + * @brief Gets a value indicating whether the given version string is a valid version + */ + static bool valid_version (const std::string &v); + + /** + * @brief Checks whether the given string is a valid name + */ + static bool valid_name (const std::string &n); + + /** + * @brief Detects a grain from the given directory + * This method will return a grain constructed from the given directory. + * The data is read from "path/grain.xml". This method will throw an + * exception if an error occurs during reading. + */ + static SaltGrain from_path (const std::string &path); + + /** + * @brief Loads the grain from the given URL + * This method will return a grain constructed from the downloaded data. + * The data is read from "URL/grain.xml". This method will throw an + * exception if an error occurs during reading. + */ + static SaltGrain from_url (const std::string &url); + + /** + * @brief Forms the spec file download URL from a given download URL + */ + static std::string spec_url (const std::string &url); + + /** + * @brief Returns a value indicating whether the given path represents is a grain + */ + static bool is_grain (const std::string &path); + +private: + std::string m_name; + std::string m_version; + std::string m_api_version; + std::string m_path; + std::string m_url; + std::string m_title; + std::string m_doc, m_doc_url; + std::string m_author; + std::string m_author_contact; + std::string m_license; + QDateTime m_authored_time, m_installed_time; + QImage m_icon, m_screenshot; + std::vector m_dependencies; +}; + +} + +#endif diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc new file mode 100644 index 000000000..59ea4f260 --- /dev/null +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -0,0 +1,300 @@ + +/* + + 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 "laySaltGrainDetailsTextWidget.h" +#include "laySaltGrain.h" +#include "tlString.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace lay +{ + +SaltGrainDetailsTextWidget::SaltGrainDetailsTextWidget (QWidget *w) + : QTextBrowser (w), mp_grain (0) +{ + setOpenLinks (false); + setOpenExternalLinks (false); + connect (this, SIGNAL (anchorClicked (const QUrl &)), this, SLOT (open_link (const QUrl &))); +} + +void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g) +{ + mp_grain = g; + setHtml (details_text ()); +} + +void +SaltGrainDetailsTextWidget::open_link (const QUrl &url) +{ + QDesktopServices::openUrl (url); +} + +QVariant +SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) +{ + if (url.path () == QString::fromUtf8 ("/icon")) { + + int icon_dim = 64; + + if (!mp_grain || mp_grain->icon ().isNull ()) { + + return QImage (":/salt_icon.png"); + + } else { + + QImage img = mp_grain->icon (); + if (img.width () != icon_dim || img.height () != icon_dim) { + + img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); + final_img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&final_img); + painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); + + return final_img; + + } else { + return img; + } + + } + + } else if (url.path () == QString::fromUtf8 ("/screenshot")) { + + QImage s = mp_grain->screenshot ().convertToFormat (QImage::Format_ARGB32_Premultiplied); + + QImage smask (s.size (), QImage::Format_ARGB32_Premultiplied); + smask.fill (QColor (0, 0, 0, 0)); + { + int border = 0; + int radius = 6; + + QPainter painter (&smask); + + painter.setRenderHint (QPainter::Antialiasing); + + painter.setCompositionMode (QPainter::CompositionMode_Source); + for (int b = border; b > 0; --b) { + QPen pen (QColor (255, 255, 255, ((border - b + 1) * 255) / border)); + pen.setWidth (b * 2 + 1); + painter.setBrush (Qt::NoBrush); + painter.setPen (pen); + painter.drawRoundedRect (QRectF (border, border, s.width () - 2 * border, s.height () - 2 * border), radius, radius, Qt::AbsoluteSize); + } + + painter.setPen (Qt::white); + painter.setBrush (Qt::white); + painter.drawRoundedRect (QRectF (border, border, s.width () - 2 * border, s.height () - 2 * border), radius, radius, Qt::AbsoluteSize); + } + + { + QPainter painter (&s); + painter.setCompositionMode (QPainter::CompositionMode_DestinationIn); + painter.drawImage (0, 0, smask); + } + + return s; + + } else { + return QTextBrowser::loadResource (type, url); + } +} + +static void produce_listing (QTextStream &stream, QDir dir, int level) +{ + for (int i = 0; i < level + 1; ++i) { + stream << "  "; + } + stream << "  "; + if (level > 0) { + stream << tl::escaped_to_html (tl::to_string (dir.dirName ())).c_str (); + } else { + stream << tl::escaped_to_html (tl::to_string (dir.absolutePath ())).c_str (); + } + stream << "
\n"; + + level += 1; + + QStringList entries = dir.entryList (QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + QFileInfo fi (dir.filePath (*e)); + if (fi.isDir ()) { + produce_listing (stream, QDir (fi.filePath ()), level); + } else { + for (int i = 0; i < level + 1; ++i) { + stream << "  "; + } + stream << "  " << tl::escaped_to_html (tl::to_string (*e)).c_str () << "
\n"; + } + + } +} + +QString +SaltGrainDetailsTextWidget::details_text () +{ + SaltGrain *g = mp_grain; + if (! g) { + return QString (); + } + + QBuffer buffer; + buffer.open (QIODevice::WriteOnly); + QTextStream stream (&buffer); + stream.setCodec ("UTF-8"); + + stream << ""; + + stream << ""; + stream << ""; + + stream << "
"; + stream << "

"; + stream << tl::to_qstring (tl::escaped_to_html (g->name ())) << " " << tl::to_qstring (tl::escaped_to_html (g->version ())); + stream << "

"; + if (! g->title ().empty()) { + stream << "

" << tl::to_qstring (tl::escaped_to_html (g->title ())) << "

"; + } + + if (g->version ().empty ()) { + stream << "

"; + stream << QObject::tr ("This package does not have a version. " + "Use the <version> element of the specification file or edit the package properties to provide a version."); + stream << "

"; + } + + if (g->title ().empty ()) { + stream << "

"; + stream << QObject::tr ("This package does not have a title. " + "Use the <title> element of the specification file or edit the package properties to provide a title."); + stream << "

"; + } + + stream << "


"; + if (! g->doc ().empty ()) { + stream << tl::to_qstring (tl::escaped_to_html (g->doc ())); + } else { + stream << ""; + stream << QObject::tr ("This package does not have a description. " + "Use the <doc> element of the specification file or edit the package properties to provide a description."); + stream << ""; + } + stream << "

"; + + stream << "

"; + if (! g->author ().empty ()) { + stream << "" << QObject::tr ("Author") << ": " << tl::to_qstring (tl::escaped_to_html (g->author ())) << " "; + if (! g->author_contact ().empty ()) { + stream << "(" << tl::to_qstring (tl::escaped_to_html (g->author_contact ())) << ")"; + } + if (!g->authored_time ().isNull ()) { + stream << "
"; + stream << "" << QObject::tr ("Released") << ": " << g->authored_time ().date ().toString (Qt::ISODate); + } + } else { + stream << ""; + stream << QObject::tr ("This package does not have a author information. " + "Use the <author>, <authored-time> and <author-contact> elements of the specification file or edit the package properties to provide authoring information."); + stream << ""; + } + stream << "

"; + + stream << "

"; + if (! g->license ().empty ()) { + stream << "" << QObject::tr ("License") << ": " << tl::to_qstring (tl::escaped_to_html (g->license ())) << " "; + } else { + stream << ""; + stream << QObject::tr ("This package does not have license information. " + "Use the <license> elements of the specification file or edit the package properties to provide license information."); + stream << ""; + } + stream << "

"; + + stream << "

"; + if (! g->api_version ().empty ()) { + stream << "" << QObject::tr ("API version") << ": " << tl::to_qstring (tl::escaped_to_html (g->api_version ())) << " "; + } + stream << "

"; + + stream << "

"; + if (! g->doc_url ().empty ()) { + stream << "" << QObject::tr ("Documentation link") << ": eff_doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->eff_doc_url ())) << ""; + } else { + stream << ""; + stream << QObject::tr ("This package does not have a documentation link. " + "Use the <doc-url> element of the specification file or edit the package properties to provide a link."); + stream << ""; + } + stream << "

"; + + if (! g->screenshot ().isNull ()) { + stream << "
"; + stream << "

" << QObject::tr ("Screenshot") << "

"; + } + + stream << "
"; + stream << "

" << QObject::tr ("Installation") << "

"; + + if (! g->url ().empty ()) { + stream << "

" << QObject::tr ("Download URL: ") << "" << tl::to_qstring (tl::escaped_to_html (g->url ())) << "

"; + } + if (! g->installed_time ().isNull ()) { + stream << "

" << QObject::tr ("Installed: ") << "" << g->installed_time ().toString () << "

"; + } + if (! g->dependencies ().empty ()) { + stream << "

" << QObject::tr ("Depends on: ") << "
"; + for (std::vector::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) { + stream << "    " << tl::to_qstring (tl::escaped_to_html (d->name)) << " "; + stream << tl::to_qstring (tl::escaped_to_html (d->version)); + if (! d->url.empty ()) { + stream << " - "; + stream << "[" << tl::to_qstring (tl::escaped_to_html (d->url)) << "]
"; + } + } + stream << "

"; + } + + if (! g->path ().empty ()) { + stream << "

" << QObject::tr ("Installed files: ") << "

"; + produce_listing (stream, QDir (tl::to_qstring (g->path ())), 0); + stream << "

"; + } + + stream << "
"; + + stream << ""; + + stream.flush (); + + return QString::fromUtf8 (buffer.buffer()); +} + +} diff --git a/src/lay/laySaltGrainDetailsTextWidget.h b/src/lay/laySaltGrainDetailsTextWidget.h new file mode 100644 index 000000000..6ba47f144 --- /dev/null +++ b/src/lay/laySaltGrainDetailsTextWidget.h @@ -0,0 +1,66 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltGrainDetailsTextWidget +#define HDR_laySaltGrainDetailsTextWidget + +#include + +namespace lay +{ + +class SaltGrain; + +/** + * @brief A specialisation of QTextBrowser that displays the details of the salt grain + */ +class SaltGrainDetailsTextWidget + : public QTextBrowser +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltGrainDetailsTextWidget (QWidget *w); + + /** + * @brief Sets the grain whose details are to be shown + */ + void set_grain (SaltGrain *g); + +protected: + virtual QVariant loadResource (int type, const QUrl &url); + +private slots: + void open_link (const QUrl &url); + +private: + lay::SaltGrain *mp_grain; + + QString details_text (); +}; + +} + +#endif diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc new file mode 100644 index 000000000..5743ea1c1 --- /dev/null +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -0,0 +1,643 @@ + +/* + + 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 "laySaltGrainPropertiesDialog.h" +#include "laySalt.h" +#include "tlString.h" +#include "tlExceptions.h" +#include "tlHttpStream.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace lay +{ + +// ---------------------------------------------------------------------------------------------------- + +/** + * @brief A delegate for editing a field of the dependency list + */ +class SaltGrainEditDelegate + : public QItemDelegate +{ +public: + SaltGrainEditDelegate (QWidget *parent) + : QItemDelegate (parent) + { + // .. nothing yet .. + } + + QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const + { + QLineEdit *editor = new QLineEdit (parent); + editor->setFrame (false); + editor->setTextMargins (2, 0, 2, 0); + return editor; + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const + { + editor->setGeometry(option.rect); + } + + void setEditorData (QWidget *widget, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + editor->setText (index.model ()->data (index, Qt::UserRole).toString ()); + } + } + + void setModelData (QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + model->setData (index, QVariant (editor->text ()), Qt::UserRole); + } + } + + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const + { + QSize sz = option.fontMetrics.size (Qt::TextSingleLine, QString::fromUtf8 ("M")); + sz += QSize (0, 8); + return sz; + } +}; + +/** + * @brief A delegate for editing a field of the dependency list + */ +class SaltGrainNameEditDelegate + : public SaltGrainEditDelegate +{ +public: + SaltGrainNameEditDelegate (QWidget *parent, Salt *salt) + : SaltGrainEditDelegate (parent), mp_completer (0) + { + QStringList names; + for (lay::Salt::flat_iterator i = salt->begin_flat (); i != salt->end_flat (); ++i) { + names << tl::to_qstring ((*i)->name ()); + } + mp_completer = new QCompleter (names, this); + } + + QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QWidget *editor = SaltGrainEditDelegate::createEditor (parent, option, index); + QLineEdit *line_edit = dynamic_cast (editor); + if (line_edit) { + line_edit->setCompleter (mp_completer); + } + return editor; + } + +public: + QCompleter *mp_completer; +}; + +// ---------------------------------------------------------------------------------------------------- +// SaltGrainPropertiesDialog implementation + +SaltGrainPropertiesDialog::SaltGrainPropertiesDialog (QWidget *parent) + : QDialog (parent), mp_salt (0), m_update_enabled (true) +{ + Ui::SaltGrainPropertiesDialog::setupUi (this); + + m_title = windowTitle (); + m_open_label = open_label->text (); + + connect (icon_delete_button, SIGNAL (clicked ()), this, SLOT (reset_icon ())); + connect (icon_config_button, SIGNAL (clicked ()), this, SLOT (set_icon ())); + connect (screenshot_delete_button, SIGNAL (clicked ()), this, SLOT (reset_screenshot ())); + connect (screenshot_config_button, SIGNAL (clicked ()), this, SLOT (set_screenshot ())); + connect (doc_url, SIGNAL (textChanged (const QString &)), this, SLOT (url_changed (const QString &))); + connect (add_dependency, SIGNAL (clicked ()), this, SLOT (add_dependency_clicked ())); + connect (remove_dependency, SIGNAL (clicked ()), this, SLOT (remove_dependency_clicked ())); + connect (dependencies, SIGNAL (itemChanged (QTreeWidgetItem *, int)), this, SLOT (dependency_changed (QTreeWidgetItem *, int))); + + dependencies->setItemDelegateForColumn (1, new SaltGrainEditDelegate (dependencies)); + dependencies->setItemDelegateForColumn (2, new SaltGrainEditDelegate (dependencies)); + + url_changed (QString ()); +} + +void +SaltGrainPropertiesDialog::update_controls () +{ + setWindowTitle (m_title + tl::to_qstring (" - " + m_grain.name ())); + license_alert->clear (); + version_alert->clear (); + doc_url_alert->clear (); + dependencies_alert->clear (); + + version->setText (tl::to_qstring (m_grain.version ())); + api_version->setText (tl::to_qstring (m_grain.api_version ())); + title->setText (tl::to_qstring (m_grain.title ())); + author->setText (tl::to_qstring (m_grain.author ())); + author_contact->setText (tl::to_qstring (m_grain.author_contact ())); + doc->setText (tl::to_qstring (m_grain.doc ())); + doc_url->setText (tl::to_qstring (m_grain.doc_url ())); + license->setText (tl::to_qstring (m_grain.license ())); + + dependencies->clear (); + for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { + + QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); + item->setFlags (item->flags () | Qt::ItemIsEditable); + + item->setData (0, Qt::UserRole, tl::to_qstring (d->name)); + dependency_changed (item, 0); + item->setData (1, Qt::UserRole, tl::to_qstring (d->version)); + dependency_changed (item, 1); + item->setData (2, Qt::UserRole, tl::to_qstring (d->url)); + dependency_changed (item, 2); + + dependencies->addTopLevelItem (item); + + } + + update_icon (); + update_screenshot (); +} + +void +SaltGrainPropertiesDialog::update_icon () +{ + if (m_grain.icon ().isNull ()) { + icon_config_button->setIcon (QIcon (":/salt_icon.png")); + } else { + QImage img = m_grain.icon (); + if (img.width () == icon_config_button->iconSize ().width ()) { + icon_config_button->setIcon (QIcon (QPixmap::fromImage (img))); + } else { + icon_config_button->setIcon (QIcon (QPixmap::fromImage (img.scaled (icon_config_button->iconSize (), Qt::KeepAspectRatio, Qt::SmoothTransformation)))); + } + } +} + +void +SaltGrainPropertiesDialog::update_screenshot () +{ + if (m_grain.screenshot ().isNull ()) { + screenshot_config_button->setIcon (QIcon (":/add.png")); + } else { + QImage img = m_grain.screenshot (); + if (img.width () == screenshot_config_button->iconSize ().width ()) { + screenshot_config_button->setIcon (QIcon (QPixmap::fromImage (img))); + } else { + screenshot_config_button->setIcon (QIcon (QPixmap::fromImage (img.scaled (screenshot_config_button->iconSize (), Qt::KeepAspectRatio, Qt::SmoothTransformation)))); + } + } +} + +void +SaltGrainPropertiesDialog::update_data () +{ + m_grain.set_version (tl::to_string (version->text ())); + m_grain.set_api_version (tl::to_string (api_version->text ())); + m_grain.set_title (tl::to_string (title->text ())); + m_grain.set_author (tl::to_string (author->text ())); + m_grain.set_author_contact (tl::to_string (author_contact->text ())); + m_grain.set_doc (tl::to_string (doc->toPlainText ())); + m_grain.set_doc_url (tl::to_string (doc_url->text ())); + m_grain.set_license (tl::to_string (license->text ())); + + m_grain.dependencies ().clear (); + for (int i = 0; i < dependencies->topLevelItemCount (); ++i) { + + QTreeWidgetItem *item = dependencies->topLevelItem (i); + QString name = item->data (0, Qt::UserRole).toString ().simplified (); + QString version = item->data (1, Qt::UserRole).toString ().simplified (); + QString url = item->data (2, Qt::UserRole).toString ().simplified (); + + if (! name.isEmpty ()) { + lay::SaltGrain::Dependency dep = lay::SaltGrain::Dependency (); + dep.name = tl::to_string (name); + dep.version = tl::to_string (version); + dep.url = tl::to_string (url); + m_grain.dependencies ().push_back (dep); + } + + } +} + +void +SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column) +{ + if (! m_update_enabled) { + return; + } + m_update_enabled = false; + + std::string name = tl::to_string (item->data (0, Qt::UserRole).toString ().simplified ()); + SaltGrain *g = mp_salt->grain_by_name (name); + + if (column == 0 && mp_salt) { + + item->setData (0, Qt::EditRole, tl::to_qstring (name)); + + // set URL and version for known grains + if (name == m_grain.name ()) { + + item->setData (1, Qt::UserRole, QString ()); + item->setData (2, Qt::UserRole, QString ()); + // placeholder texts: + item->setData (1, Qt::EditRole, QString ()); + item->setData (2, Qt::EditRole, tr ("(must not depend on itself)")); + + } else { + + if (g) { + item->setData (1, Qt::UserRole, tl::to_qstring (g->version ())); + item->setData (2, Qt::UserRole, QString ()); + // placeholder texts: + item->setData (1, Qt::EditRole, tl::to_qstring (g->version ())); + if (! g->url ().empty ()) { + item->setData (2, Qt::EditRole, tl::to_qstring ("(" + g->url () + ")")); + } else { + item->setData (2, Qt::EditRole, tr ("(from repository)")); + } + } else { + item->setData (1, Qt::UserRole, QString ()); + item->setData (2, Qt::UserRole, QString ()); + // placeholder texts: + item->setData (1, Qt::EditRole, QString ()); + item->setData (2, Qt::EditRole, tr ("(from repository)")); + } + + } + + } else if (column == 1) { + + QString text = item->data (column, Qt::UserRole).toString (); + if (! text.isEmpty ()) { + item->setData (1, Qt::EditRole, text); + } else if (g) { + item->setData (1, Qt::EditRole, tl::to_qstring (g->version ())); + } + + } else if (column == 2) { + + QString text = item->data (column, Qt::UserRole).toString (); + if (! text.isEmpty ()) { + item->setData (2, Qt::EditRole, text); + } else if (g) { + if (! g->url ().empty ()) { + item->setData (2, Qt::EditRole, tl::to_qstring ("(" + g->url () + ")")); + } else { + item->setData (2, Qt::EditRole, tr ("(from repository)")); + } + } + + } + + m_update_enabled = true; +} + +void +SaltGrainPropertiesDialog::url_changed (const QString &url) +{ + // inserts the URL into the label + m_grain.set_doc_url (tl::to_string (url)); + open_label->setText (m_open_label.arg (tl::to_qstring (m_grain.eff_doc_url ()))); + open_label->setEnabled (! url.isEmpty ()); +} + +void +SaltGrainPropertiesDialog::set_icon () +{ +BEGIN_PROTECTED + + const int max_dim = 256; + + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Icon Image File"), m_image_dir, tr ("Images (*.png *.jpg);;All Files (*)")); + if (! fileName.isNull ()) { + + bool ok = true; + QImage img = QImage (fileName); + if (img.width () > max_dim || img.height () > max_dim) { + if (QMessageBox::warning (this, tr ("Image Too Big"), + tr ("Icon image too big - must be %1x%2 pixels max, but is %3x%4.\n\nScale image?").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + ok = false; + } else { + img = img.scaled (max_dim, max_dim, Qt::KeepAspectRatio); + } + } + + if (ok) { + m_grain.set_icon (img); + m_image_dir = QFileInfo (fileName).path (); + update_icon (); + } + + } + +END_PROTECTED +} + +void +SaltGrainPropertiesDialog::reset_icon () +{ + m_grain.set_icon (QImage ()); + update_icon (); +} + +void +SaltGrainPropertiesDialog::set_screenshot () +{ +BEGIN_PROTECTED + + const int max_dim = 1024; + + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Showcase Image File"), m_image_dir, tr ("Images (*.png *.jpg);;All Files (*)")); + if (! fileName.isNull ()) { + + bool ok = true; + QImage img = QImage (fileName); + if (img.width () > max_dim || img.height () > max_dim) { + if (QMessageBox::warning (this, tr ("Image Too Big"), + tr ("Showcase image too big - must be %1x%2 pixels max, but is %3x%4.\n\nScale image?").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + ok = false; + } else { + img = img.scaled (max_dim, max_dim, Qt::KeepAspectRatio); + } + } + + if (ok) { + m_grain.set_screenshot (img); + m_image_dir = QFileInfo (fileName).path (); + update_screenshot (); + } + + } + +END_PROTECTED +} + +void +SaltGrainPropertiesDialog::reset_screenshot () +{ + m_grain.set_screenshot (QImage ()); + update_screenshot (); +} + +void +SaltGrainPropertiesDialog::add_dependency_clicked () +{ + QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); + item->setFlags (item->flags () | Qt::ItemIsEditable); + dependencies->addTopLevelItem (item); + dependencies->setCurrentItem (dependencies->topLevelItem (dependencies->topLevelItemCount () - 1)); +} + +void +SaltGrainPropertiesDialog::remove_dependency_clicked () +{ + int index = dependencies->indexOfTopLevelItem (dependencies->currentItem ()); + if (index >= 0 && index < dependencies->topLevelItemCount ()) { + delete dependencies->topLevelItem (index); + } +} + +namespace +{ + +class DependencyGraph +{ +public: + DependencyGraph (Salt *salt) + { + for (lay::Salt::flat_iterator i = salt->begin_flat (); i != salt->end_flat (); ++i) { + m_name_to_grain.insert (std::make_pair ((*i)->name (), *i)); + } + } + + bool is_valid_name (const std::string &name) const + { + return m_name_to_grain.find (name) != m_name_to_grain.end (); + } + + const lay::SaltGrain *grain_for_name (const std::string &name) const + { + std::map ::const_iterator n = m_name_to_grain.find (name); + if (n != m_name_to_grain.end ()) { + return n->second; + } else { + return 0; + } + } + + void check_circular (const lay::SaltGrain *current, const lay::SaltGrain *new_dep) + { + std::vector path; + path.push_back (current); + check_circular_follow (new_dep, path); + } + +private: + std::map m_name_to_grain; + + void check_circular_follow (const lay::SaltGrain *current, std::vector &path) + { + if (! current) { + return; + } + + path.push_back (current); + + for (std::vector ::const_iterator p = path.begin (); p != path.end () - 1; ++p) { + if (*p == current) { + circular_reference_error (path); + } + } + + for (std::vector::const_iterator d = current->dependencies ().begin (); d != current->dependencies ().end (); ++d) { + check_circular_follow (grain_for_name (d->name), path); + } + + path.pop_back (); + } + + void circular_reference_error (std::vector &path) + { + std::string msg = tl::to_string (QObject::tr ("The following path forms a circular dependency: ")); + for (std::vector ::const_iterator p = path.begin (); p != path.end (); ++p) { + if (p != path.begin ()) { + msg += "->"; + } + msg += (*p)->name (); + } + throw tl::Exception (msg); + } +}; + +} + +void +SaltGrainPropertiesDialog::accept () +{ + update_data (); + + // Perform some checks + + // license + license_alert->clear (); + if (m_grain.license ().empty ()) { + license_alert->warn () << tr ("License field is empty. Please consider specifying a license model.") << tl::endl + << tr ("A license model tells users whether and how to use the source code of the package."); + } + + // version + version_alert->clear (); + if (m_grain.version ().empty ()) { + version_alert->warn () << tr ("Version field is empty. Please consider specifying a version number.") << tl::endl + << tr ("Versions help the system to apply upgrades if required."); + } else if (! SaltGrain::valid_version (m_grain.version ())) { + version_alert->error () << tr ("'%1' is not a valid version string. A version string needs to be numeric (like '1.2.3' or '4.5'').").arg (tl::to_qstring (m_grain.version ())); + } + + // API version + api_version_alert->clear (); + if (! m_grain.api_version ().empty () && ! SaltGrain::valid_version (m_grain.api_version ())) { + api_version_alert->error () << tr ("'%1' is not a valid API version string. An API version string needs to be numeric (like '0.25'').").arg (tl::to_qstring (m_grain.api_version ())); + } + + // doc URL + doc_url_alert->clear (); + if (! m_grain.doc_url ().empty ()) { + try { + tl::InputStream stream (m_grain.eff_doc_url ()); + if (! stream.get (1)) { + throw tl::Exception (tl::to_string (tr ("Empty document"))); + } + } catch (tl::Exception &ex) { + doc_url_alert->error () << tr ("Attempt to read documentation URL failed. Error details follow.") << tl::endl + << tr ("URL: ") << m_grain.doc_url () << tl::endl + << tr ("Message: ") << ex.msg (); + } + } + + // dependencies + dependencies_alert->clear (); + DependencyGraph dep (mp_salt); + std::set dep_seen; + for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { + + if (! SaltGrain::valid_name (d->name)) { + dependencies_alert->error () << tr ("'%1' is not a valid package name").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("Valid package names are words (letters, digits, underscores).") << tl::endl + << tr ("Package groups can be specified in the form 'group/package'."); + continue; + } + + if (dep_seen.find (d->name) != dep_seen.end ()) { + dependencies_alert->error () << tr ("Duplicate dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("A package cannot be dependent on the same package twice. Remove on entry."); + continue; + } + dep_seen.insert (d->name); + + if (dep.is_valid_name (d->name)) { + try { + dep.check_circular (dep.grain_for_name (m_grain.name ()), dep.grain_for_name (d->name)); + } catch (tl::Exception &ex) { + dependencies_alert->error () << ex.msg () << tl::endl + << tr ("Circular dependency means, a package is eventually depending on itself."); + } + } + + if (d->version.empty ()) { + dependencies_alert->warn () << tr ("No version specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("Please consider giving a version here. Versions help deciding whether a package needs to be updated.") << tl::endl + << tr ("If the dependency package has a version itself, the version is automatically set to it's current version."); + } + + if (!d->url.empty ()) { + SaltGrain gdep; + try { + gdep = SaltGrain::from_url (d->url); + if (gdep.name () != d->name) { + dependencies_alert->error () << tr ("Package name obtained from download URL is not the expected name.") << tl::endl + << tr ("Downloaded name: ") << gdep.name () << tl::endl + << tr ("Expected name: ") << d->name; + } + } catch (tl::Exception &ex) { + dependencies_alert->error () << tr ("Attempt to test-download package from URL failed. Error details follow.") << tl::endl + << tr ("URL: ") << d->url << tl::endl + << tr ("Message: ") << ex.msg (); + } + } + + } + + if (!license_alert->needs_attention () && + !doc_url_alert->needs_attention () && + !dependencies_alert->needs_attention () && + !version_alert->needs_attention () && + !api_version_alert->needs_attention ()) { + QDialog::accept (); + } else { + if (QMessageBox::warning (this, tr ("Issues Encountered"), + tr ("Some issues have been found when inspecting the package details.\nThe respective fields are marked with warning icons.\n\nIgnore these issues and commit the package details?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + QDialog::accept (); + } + } +} + +bool +SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) +{ + m_grain = *grain; + mp_salt = salt; + + dependencies->setItemDelegateForColumn (0, new SaltGrainNameEditDelegate (dependencies, mp_salt)); + + update_controls (); + + bool res = exec (); + if (res && *grain != m_grain) { + *grain = m_grain; + // save modified grain + grain->save (); + } + + delete dependencies->itemDelegateForColumn (0); + dependencies->setItemDelegateForColumn (0, 0); + + mp_salt = 0; + return res; +} + +} diff --git a/src/lay/laySaltGrainPropertiesDialog.h b/src/lay/laySaltGrainPropertiesDialog.h new file mode 100644 index 000000000..cb2b62285 --- /dev/null +++ b/src/lay/laySaltGrainPropertiesDialog.h @@ -0,0 +1,96 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltGrainPropertiesDialog +#define HDR_laySaltGrainPropertiesDialog + +#include "laySaltGrain.h" + +#include + +#include "ui_SaltGrainPropertiesDialog.h" + +namespace lay +{ + +class Salt; + +/** + * @brief The dialog for managing the Salt ("Packages") + */ +class SaltGrainPropertiesDialog + : public QDialog, private Ui::SaltGrainPropertiesDialog +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltGrainPropertiesDialog (QWidget *parent); + + /** + * @brief Executes the dialog for the given grain + * If the dialog is committed with "Ok", the new data is written into + * the grain provided and "true" is returned. Otherwise, "false" is + * returned and the object remains unchanged. + */ + bool exec_dialog (lay::SaltGrain *grain, lay::Salt *salt); + + /** + * @brief Gets the current package index + */ + lay::Salt *salt () + { + return mp_salt; + } + +private slots: + void reset_icon (); + void set_icon (); + void reset_screenshot (); + void set_screenshot (); + void url_changed (const QString &url); + void add_dependency_clicked (); + void remove_dependency_clicked (); + void dependency_changed (QTreeWidgetItem *item, int column); + +protected: + void accept (); + +private: + lay::SaltGrain m_grain; + lay::Salt *mp_salt; + QString m_title; + QString m_open_label; + QString m_image_dir; + bool m_update_enabled; + + void update_controls (); + void update_data (); + void update_icon (); + void update_screenshot (); +}; + +} + +#endif diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc new file mode 100644 index 000000000..221861340 --- /dev/null +++ b/src/lay/laySaltGrains.cc @@ -0,0 +1,264 @@ + +/* + + 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 "laySaltGrains.h" +#include "tlString.h" +#include "tlFileUtils.h" + +#include +#include +#include + +namespace lay +{ + +SaltGrains::SaltGrains () +{ + // .. nothing yet .. +} + +bool +SaltGrains::operator== (const SaltGrains &other) const +{ + return m_name == other.m_name && + m_path == other.m_path && + m_title == other.m_title && + m_collections == other.m_collections && + m_grains == other.m_grains; +} + +void +SaltGrains::set_name (const std::string &n) +{ + m_name = n; +} + +void +SaltGrains::set_title (const std::string &t) +{ + m_title = t; +} + +void +SaltGrains::set_path (const std::string &p) +{ + m_path = p; +} + +void +SaltGrains::add_collection (const SaltGrains &collection) +{ + m_collections.push_back (collection); +} + +bool +SaltGrains::remove_collection (collection_iterator iter, bool with_files) +{ + // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required + for (collections_type::iterator i = m_collections.begin (); i != m_collections.end (); ++i) { + if (i == iter) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) { + return false; + } + m_collections.erase (i); + return true; + } + } + + return false; +} + +void +SaltGrains::add_grain (const SaltGrain &grain) +{ + m_grains.push_back (grain); +} + +bool +SaltGrains::remove_grain (grain_iterator iter, bool with_files) +{ + // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required + for (grains_type::iterator i = m_grains.begin (); i != m_grains.end (); ++i) { + if (i == iter) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) { + return false; + } + m_grains.erase (i); + return true; + } + } + + return false; +} + +bool +SaltGrains::is_empty () const +{ + if (! m_grains.empty ()) { + return false; + } + for (collections_type::const_iterator i = m_collections.begin (); i != m_collections.end (); ++i) { + if (!i->is_empty ()) { + return false; + } + } + return true; +} + +bool +SaltGrains::is_readonly () const +{ + return QFileInfo (tl::to_qstring (path ())).isWritable (); +} + +namespace +{ + +/** + * @brief A helper class required because directory traversal is not supported by QResource directly + */ +class OpenResource + : public QResource +{ +public: + using QResource::isDir; + using QResource::isFile; + using QResource::children; + + OpenResource (const QString &path) + : QResource (path) + { + // .. nothing yet .. + } +}; + +} + +SaltGrains +SaltGrains::from_path (const std::string &path, const std::string &prefix) +{ + tl_assert (! path.empty ()); + + SaltGrains grains; + grains.set_path (path); + + if (path[0] != ':') { + + QDir dir (tl::to_qstring (path)); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + std::string new_prefix = prefix; + if (! new_prefix.empty ()) { + new_prefix += "/"; + } + new_prefix += tl::to_string (*e); + + std::string epath = tl::to_string (dir.absoluteFilePath (*e)); + if (SaltGrain::is_grain (epath)) { + try { + SaltGrain g (SaltGrain::from_path (epath)); + g.set_name (new_prefix); + grains.add_grain (g); + } catch (...) { + // ignore errors (TODO: what to do here?) + } + } else if (QFileInfo (tl::to_qstring (epath)).isDir ()) { + SaltGrains c = SaltGrains::from_path (epath, new_prefix); + c.set_name (new_prefix); + if (! c.is_empty ()) { + grains.add_collection (c); + } + } + + } + + } else { + + OpenResource resource (tl::to_qstring (path)); + if (resource.isDir ()) { + + QStringList templ_dir = resource.children (); + for (QStringList::const_iterator t = templ_dir.begin (); t != templ_dir.end (); ++t) { + + std::string new_prefix = prefix; + if (! new_prefix.empty ()) { + new_prefix += "/"; + } + new_prefix += tl::to_string (*t); + + std::string epath = path + "/" + tl::to_string (*t); + + if (SaltGrain::is_grain (epath)) { + try { + SaltGrain g (SaltGrain::from_path (epath)); + g.set_name (new_prefix); + grains.add_grain (g); + } catch (...) { + // ignore errors (TODO: what to do here?) + } + } else if (OpenResource (tl::to_qstring (epath)).isDir ()) { + SaltGrains c = SaltGrains::from_path (epath, new_prefix); + c.set_name (new_prefix); + if (! c.is_empty ()) { + grains.add_collection (c); + } + } + + } + + } + + } + + return grains; +} + +static tl::XMLElementList s_group_struct = + tl::make_member (&SaltGrains::name, &SaltGrains::set_name, "name") + + tl::make_element (&SaltGrains::begin_collections, &SaltGrains::end_collections, &SaltGrains::add_collection, "group", &s_group_struct) + + tl::make_element (&SaltGrains::begin_grains, &SaltGrains::end_grains, &SaltGrains::add_grain, "salt-grain", SaltGrain::xml_struct ()); + +static tl::XMLStruct s_xml_struct ("salt-mine", s_group_struct); + +void +SaltGrains::load (const std::string &p) +{ + tl::XMLFileSource source (p); + s_xml_struct.parse (source, *this); +} + +void +SaltGrains::load (tl::InputStream &p) +{ + tl::XMLStreamSource source (p); + s_xml_struct.parse (source, *this); +} + +void +SaltGrains::save (const std::string &p) const +{ + tl::OutputStream os (p, tl::OutputStream::OM_Plain); + s_xml_struct.write (os, *this); +} + +} diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h new file mode 100644 index 000000000..2dc524a5a --- /dev/null +++ b/src/lay/laySaltGrains.h @@ -0,0 +1,210 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltGrains +#define HDR_laySaltGrains + +#include "layCommon.h" +#include "laySaltGrain.h" + +#include + +namespace lay +{ + +/** + * @brief A class representing a collection of grains (packages) + * A collection can have child collections and grains (leafs). + */ +class LAY_PUBLIC SaltGrains +{ +public: + typedef std::list collections_type; + typedef collections_type::const_iterator collection_iterator; + typedef std::list grains_type; + typedef grains_type::const_iterator grain_iterator; + + /** + * @brief Constructor: creates an empty collection + */ + SaltGrains (); + + /** + * @brief Equality + */ + bool operator== (const SaltGrains &other) const; + + /** + * @brief Inequality + */ + bool operator!= (const SaltGrains &other) const + { + return !operator== (other); + } + + /** + * @brief Gets the name of the grain collection + * + * The name is either a plain name (a word) or a path into a collection. + * Name paths are formed using the "/" separator. "mycollection" is a plain name, + * while "mycollection/subcollection" is a collection within a collection. + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the name of the grain collection + */ + void set_name (const std::string &p); + + /** + * @brief Gets the title of the grain collection + * + * The title is a brief description that is shown in the title of the + * package manager. + */ + const std::string &title () const + { + return m_title; + } + + /** + * @brief Sets the title of the grain collection + */ + void set_title (const std::string &t); + + /** + * @brief Gets the absolute file path of the installed grain + * This is the file path to the grain folder. + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Sets the absolute file path of the installed grain + */ + void set_path (const std::string &p); + + /** + * @brief Gets the collections which are members of this collection (begin iterator) + */ + collection_iterator begin_collections () const + { + return m_collections.begin (); + } + + /** + * @brief Gets the collections which are members of this collection (end iterator) + */ + collection_iterator end_collections () const + { + return m_collections.end (); + } + + /** + * @brief Adds a collection to this collection + */ + void add_collection (const SaltGrains &collection); + + /** + * @brief Removes the collection given by the collection iterator + * If "with_files" is true, also the folder and all sub-folders will be removed + * @return true, if the remove was successful. + */ + bool remove_collection (collection_iterator iter, bool with_files = false); + + /** + * @brief Gets the grains (leaf nodes) which are members of this collection (begin iterator) + */ + grain_iterator begin_grains () const + { + return m_grains.begin (); + } + + /** + * @brief Gets the grains (leaf nodes) which are members of this collection (end iterator) + */ + grain_iterator end_grains () const + { + return m_grains.end (); + } + + /** + * @brief Adds a grain to this collection + */ + void add_grain (const SaltGrain &grain); + + /** + * @brief Removes the grain given by the grain iterator + * If "with_files" is true, also the files and the folder will be removed. + * @return true, if the remove was successful. + */ + bool remove_grain (grain_iterator iter, bool with_files = false); + + /** + * @brief Gets a value indicating whether the collection is empty + */ + bool is_empty () const; + + /** + * @brief Returns true, if the collection is read-only + */ + bool is_readonly () const; + + /** + * @brief Loads the grain collection from the given path + */ + void load (const std::string &p); + + /** + * @brief Loads the grain collection from the given input stream + */ + void load (tl::InputStream &p); + + /** + * @brief Saves the grain collection to the given file + */ + void save (const std::string &p) const; + + /** + * @brief Scan grains from a given path + * This will scan the grains found within this path and return a collection containing + * the grains from this path. + * Sub-collections are created from folders which contain grains or sub-collections. + */ + static SaltGrains from_path (const std::string &path, const std::string &pfx = std::string ()); + +private: + std::string m_name; + std::string m_title; + std::string m_path; + collections_type m_collections; + grains_type m_grains; +}; + +} + +#endif diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc new file mode 100644 index 000000000..d71be4edb --- /dev/null +++ b/src/lay/laySaltManagerDialog.cc @@ -0,0 +1,870 @@ + +/* + + 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 "laySaltManagerDialog.h" +#include "laySaltModel.h" +#include "laySaltGrainPropertiesDialog.h" +#include "laySaltDownloadManager.h" +#include "laySalt.h" +#include "layVersion.h" +#include "ui_SaltGrainTemplateSelectionDialog.h" +#include "tlString.h" +#include "tlExceptions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lay +{ + +// -------------------------------------------------------------------------------------- + +/** + * @brief A tiny dialog to select a template and a name for the grain + */ +class SaltGrainTemplateSelectionDialog + : public QDialog, private Ui::SaltGrainTemplateSelectionDialog +{ +public: + SaltGrainTemplateSelectionDialog (QWidget *parent, lay::Salt *salt) + : QDialog (parent), mp_salt (salt) + { + Ui::SaltGrainTemplateSelectionDialog::setupUi (this); + + m_salt_templates.add_location (":/salt_templates"); + salt_view->setModel (new SaltModel (this, &m_salt_templates)); + salt_view->setItemDelegate (new SaltItemDelegate (this)); + salt_view->setCurrentIndex (salt_view->model ()->index (0, 0, QModelIndex ())); + } + + lay::SaltGrain templ () const + { + SaltModel *model = dynamic_cast (salt_view->model ()); + tl_assert (model != 0); + + SaltGrain *g = model->grain_from_index (salt_view->currentIndex ()); + tl_assert (g != 0); + + return *g; + } + + std::string name () const + { + return tl::to_string (name_edit->text ()); + } + + void accept () + { + name_alert->clear (); + std::string name = tl::to_string (name_edit->text ().simplified ()); + if (name.empty ()) { + name_alert->error () << tr ("Name must not be empty"); + } else if (! SaltGrain::valid_name (name)) { + name_alert->error () << tr ("Name is not valid (must be composed of letters, digits or underscores.\nGroups and names need to be separated with slashes."); + } else { + + // check, if this name does not exist yet + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + if ((*g)->name () == name) { + name_alert->error () << tr ("A package with this name already exists"); + return; + } + } + + QDialog::accept (); + + } + } + +private: + lay::Salt m_salt_templates; + lay::Salt *mp_salt; +}; + +// -------------------------------------------------------------------------------------- +// SaltManager implementation + +SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, const std::string &salt_mine_url) + : QDialog (parent), + m_salt_mine_url (salt_mine_url), + dm_update_models (this, &SaltManagerDialog::update_models), m_current_tab (-1) +{ + Ui::SaltManagerDialog::setupUi (this); + mp_properties_dialog = new lay::SaltGrainPropertiesDialog (this); + + connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); + connect (create_button, SIGNAL (clicked ()), this, SLOT (create_grain ())); + connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); + connect (apply_new_button, SIGNAL (clicked ()), this, SLOT (apply ())); + connect (apply_update_button, SIGNAL (clicked ()), this, SLOT (apply ())); + + mp_salt = salt; + + try { + if (! m_salt_mine_url.empty ()) { + tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); + m_salt_mine.load (m_salt_mine_url); + } + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } + + SaltModel *model = new SaltModel (this, mp_salt); + salt_view->setModel (model); + salt_view->setItemDelegate (new SaltItemDelegate (this)); + + SaltModel *mine_model; + + // This model will show only the grains of mp_salt_mine which are not present in mp_salt yet. + mine_model = new SaltModel (this, &m_salt_mine, mp_salt, true); + salt_mine_view_new->setModel (mine_model); + salt_mine_view_new->setItemDelegate (new SaltItemDelegate (this)); + + // This model will show only the grains of mp_salt_mine which are present in mp_salt already. + mine_model = new SaltModel (this, &m_salt_mine, mp_salt, false); + salt_mine_view_update->setModel (mine_model); + salt_mine_view_update->setItemDelegate (new SaltItemDelegate (this)); + + mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0); + + connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); + m_current_tab = mode_tab->currentIndex (); + + connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); + connect (mp_salt, SIGNAL (collections_about_to_change ()), this, SLOT (salt_about_to_change ())); + connect (&m_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); + connect (&m_salt_mine, SIGNAL (collections_about_to_change ()), this, SLOT (salt_mine_about_to_change ())); + + update_models (); + + connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + connect (salt_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (edit_properties ())); + connect (salt_mine_view_new->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_new_current_changed ()), Qt::QueuedConnection); + connect (salt_mine_view_update->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_update_current_changed ()), Qt::QueuedConnection); + connect (salt_mine_view_new, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); + connect (salt_mine_view_update, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); + + search_installed_edit->set_clear_button_enabled (true); + search_new_edit->set_clear_button_enabled (true); + search_update_edit->set_clear_button_enabled (true); + connect (search_installed_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + connect (search_new_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + connect (search_update_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + + connect (mark_new_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); + connect (mark_update_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); + + QAction *a; + + salt_mine_view_new->addAction (actionUnmarkAllNew); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_new->addAction (a); + salt_mine_view_new->addAction (actionShowMarkedOnlyNew); + salt_mine_view_new->addAction (actionShowAllNew); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_new->addAction (a); + salt_mine_view_new->addAction (actionRefresh); + salt_mine_view_new->setContextMenuPolicy (Qt::ActionsContextMenu); + + salt_mine_view_update->addAction (actionUnmarkAllUpdate); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_update->addAction (a); + salt_mine_view_update->addAction (actionShowMarkedOnlyUpdate); + salt_mine_view_update->addAction (actionShowAllUpdate); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_update->addAction (a); + salt_mine_view_update->addAction (actionRefresh); + salt_mine_view_update->setContextMenuPolicy (Qt::ActionsContextMenu); + + connect (actionUnmarkAllNew, SIGNAL (triggered ()), this, SLOT (unmark_all_new ())); + connect (actionShowMarkedOnlyNew, SIGNAL (triggered ()), this, SLOT (show_marked_only_new ())); + connect (actionShowAllNew, SIGNAL (triggered ()), this, SLOT (show_all_new ())); + connect (actionUnmarkAllUpdate, SIGNAL (triggered ()), this, SLOT (unmark_all_update ())); + connect (actionShowMarkedOnlyUpdate, SIGNAL (triggered ()), this, SLOT (show_marked_only_update ())); + connect (actionShowAllUpdate, SIGNAL (triggered ()), this, SLOT (show_all_update ())); + connect (actionRefresh, SIGNAL (triggered ()), this, SLOT (refresh ())); +} + +void +SaltManagerDialog::mode_changed () +{ + // commits edits: + setFocus (Qt::NoFocusReason); + + QList sizes; + if (m_current_tab == 0) { + sizes = splitter->sizes (); + } else if (m_current_tab == 1) { + sizes = splitter_update->sizes (); + } else if (m_current_tab == 2) { + sizes = splitter_new->sizes (); + } + + // keeps the splitters in sync + if (!sizes.empty ()) { + splitter_new->setSizes (sizes); + splitter_update->setSizes (sizes); + splitter->setSizes (sizes); + } + + if (mode_tab->currentIndex () >= 1) { + show_all_new (); + show_all_update (); + } + + m_current_tab = mode_tab->currentIndex (); +} + +void +SaltManagerDialog::show_all_new () +{ + search_new_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + salt_mine_view_new->setRowHidden (i, false); + } + } +} + +void +SaltManagerDialog::show_all_update () +{ + search_update_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + salt_mine_view_update->setRowHidden (i, false); + } + } +} + +void +SaltManagerDialog::show_marked_only_new () +{ + search_new_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + if (! model) { + return; + } + + salt_mine_view_new->setCurrentIndex (QModelIndex ()); + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ())); + salt_mine_view_new->setRowHidden (i, !(g && model->is_marked (g->name ()))); + mine_new_current_changed (); + } +} + +void +SaltManagerDialog::show_marked_only_update () +{ + search_update_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (! model) { + return; + } + + salt_mine_view_update->setCurrentIndex (QModelIndex ()); + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ())); + salt_mine_view_update->setRowHidden (i, !(g && model->is_marked (g->name ()))); + mine_update_current_changed (); + } +} + +void +SaltManagerDialog::unmark_all_new () +{ + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + if (model) { + model->clear_marked (); + show_all_new (); + update_apply_state (); + } +} + +void +SaltManagerDialog::unmark_all_update () +{ + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + model->clear_marked (); + show_all_update (); + update_apply_state (); + } +} + +void +SaltManagerDialog::search_text_changed (const QString &text) +{ + QListView *view = 0; + if (sender () == search_installed_edit) { + view = salt_view; + } else if (sender () == search_new_edit) { + view = salt_mine_view_new; + } else if (sender () == search_update_edit) { + view = salt_mine_view_update; + } else { + return; + } + + SaltModel *model = dynamic_cast (view->model ()); + if (! model) { + return; + } + + if (text.isEmpty ()) { + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + view->setRowHidden (i, false); + } + + } else { + + QRegExp re (text, Qt::CaseInsensitive); + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + bool hidden = (!g || re.indexIn (tl::to_qstring (g->name ())) < 0); + view->setRowHidden (i, hidden); + } + + } +} + +void +SaltManagerDialog::mark_clicked () +{ + QListView *view; + if (sender () == salt_mine_view_new || sender () == mark_new_button) { + view = salt_mine_view_new; + } else { + view = salt_mine_view_update; + } + + SaltModel *model = dynamic_cast (view->model ()); + if (! model) { + return; + } + + SaltGrain *g = model->grain_from_index (view->currentIndex ()); + if (g) { + model->set_marked (g->name (), ! model->is_marked (g->name ())); + update_apply_state (); + } +} + +void +SaltManagerDialog::update_apply_state () +{ + SaltModel *model; + + model = dynamic_cast (salt_mine_view_new->model ()); + if (model) { + + int marked = 0; + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + marked += 1; + } + } + + apply_new_button->setEnabled (marked > 0); + if (marked == 0) { + apply_label_new->setText (QString ()); + } else if (marked == 1) { + apply_label_new->setText (tr ("One package selected")); + } else if (marked > 1) { + apply_label_new->setText (tr ("%1 packages selected").arg (marked)); + } + + } + + model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + + int marked = 0; + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + marked += 1; + } + } + + apply_update_button->setEnabled (marked > 0); + if (marked == 0) { + apply_label_update->setText (QString ()); + } else if (marked == 1) { + apply_label_update->setText (tr ("One package selected")); + } else if (marked > 1) { + apply_label_update->setText (tr ("%1 packages selected").arg (marked)); + } + + } +} + +void +SaltManagerDialog::apply () +{ +BEGIN_PROTECTED + + bool update = (sender () == apply_update_button); + + lay::SaltDownloadManager manager; + + bool any = false; + + // fetch all marked grains and register for download + SaltModel *model; + if (update) { + model = dynamic_cast (salt_mine_view_update->model ()); + } else { + model = dynamic_cast (salt_mine_view_new->model ()); + } + + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + manager.register_download (g->name (), g->url (), g->version ()); + any = true; + } + } + } + + if (! any) { + if (update) { + throw tl::Exception (tl::to_string (tr ("No packages marked for update"))); + } else { + throw tl::Exception (tl::to_string (tr ("No packages marked for installation"))); + } + } + + manager.compute_dependencies (*mp_salt, m_salt_mine); + + if (manager.show_confirmation_dialog (this, *mp_salt)) { + if (update) { + unmark_all_update (); + } else { + unmark_all_new (); + } + manager.execute (*mp_salt); + } + +END_PROTECTED +} + +void +SaltManagerDialog::edit_properties () +{ + SaltGrain *g = current_grain (); + if (g) { + if (g->is_readonly ()) { + QMessageBox::critical (this, tr ("Package is not Editable"), + tr ("This package cannot be edited.\n\nEither you don't have write permissions on the directory or the package was installed from a repository.")); + } else if (mp_properties_dialog->exec_dialog (g, mp_salt)) { + current_changed (); + } + } +} + +void +SaltManagerDialog::create_grain () +{ +BEGIN_PROTECTED + + SaltGrainTemplateSelectionDialog temp_dialog (this, mp_salt); + if (temp_dialog.exec ()) { + + SaltGrain target; + target.set_name (temp_dialog.name ()); + + if (mp_salt->create_grain (temp_dialog.templ (), target)) { + + // select the new one + SaltModel *model = dynamic_cast (salt_view->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && g->name () == target.name ()) { + salt_view->setCurrentIndex (index); + break; + } + } + + } + + } else { + throw tl::Exception (tl::to_string (tr ("Initialization of new package failed - see log window (File/Log Viewer) for details"))); + } + + } + +END_PROTECTED +} + +void +SaltManagerDialog::delete_grain () +{ +BEGIN_PROTECTED + + SaltGrain *g = current_grain (); + if (! g) { + throw tl::Exception (tl::to_string (tr ("No package selected to delete"))); + } + + if (QMessageBox::question (this, tr ("Delete Package"), tr ("Are you sure to delete package '%1'?").arg (tl::to_qstring (g->name ())), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + mp_salt->remove_grain (*g); + } + +END_PROTECTED +} + +void +SaltManagerDialog::salt_about_to_change () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + tl_assert (model != 0); + model->begin_update (); +} + +void +SaltManagerDialog::salt_changed () +{ + dm_update_models (); +} + +void +SaltManagerDialog::salt_mine_about_to_change () +{ + SaltModel *model; + + model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (model != 0); + model->begin_update (); + + model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (model != 0); + model->begin_update (); +} + +void +SaltManagerDialog::refresh () +{ +BEGIN_PROTECTED + + if (! m_salt_mine_url.empty ()) { + + tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); + + lay::Salt new_mine; + new_mine.load (m_salt_mine_url); + m_salt_mine = new_mine; + + salt_mine_changed (); + + } + +END_PROTECTED +} + +void +SaltManagerDialog::salt_mine_changed () +{ + dm_update_models (); +} + +void +SaltManagerDialog::update_models () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + tl_assert (model != 0); + + model->clear_messages (); + + // Establish a message saying that an update is available + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + SaltGrain *gm = m_salt_mine.grain_by_name ((*g)->name ()); + if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("An update to version %1 is available").arg (tl::to_qstring (gm->version ())))); + } + } + + model->update (); + + if (mp_salt->is_empty ()) { + + list_stack->setCurrentIndex (1); + details_frame->hide (); + + } else { + + list_stack->setCurrentIndex (0); + details_frame->show (); + + // select the first grain + if (model->rowCount (QModelIndex ()) > 0) { + salt_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); + } + + } + + SaltModel *mine_model; + + mine_model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (mine_model != 0); + + mine_model->clear_order (); + mine_model->clear_messages (); + mine_model->enable_all (); + + bool has_warning = false; + + // Establish a message saying that an update is available + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + SaltGrain *gm = m_salt_mine.grain_by_name ((*g)->name ()); + if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + has_warning = true; + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("The installed version is outdated (%1)").arg (tl::to_qstring ((*g)->version ())))); + mine_model->set_order ((*g)->name (), -1); + } else if (gm) { + mine_model->set_message ((*g)->name (), SaltModel::None, tl::to_string (tr ("This package is up to date"))); + mine_model->set_order ((*g)->name (), 1); + mine_model->set_enabled ((*g)->name (), false); + } + } + + // Establish a message indicating whether the API version does not match + for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { + if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + mine_model->set_enabled ((*g)->name (), false); + } + } + + if (has_warning) { + mode_tab->setTabIcon (1, QIcon (":/warn_16.png")); + } else { + mode_tab->setTabIcon (1, QIcon ()); + } + + mine_model->update (); + + // select the first grain + if (mine_model->rowCount (QModelIndex ()) > 0) { + salt_mine_view_update->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); + } + + mine_model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (mine_model != 0); + + mine_model->clear_order (); + mine_model->clear_messages (); + mine_model->enable_all (); + + // Establish a message indicating whether the API version does not match + for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { + if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + mine_model->set_enabled ((*g)->name (), false); + } + } + + mine_model->update (); + + // select the first grain + if (mine_model->rowCount (QModelIndex ()) > 0) { + salt_mine_view_new->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); + } + + mine_new_current_changed (); + mine_update_current_changed (); + current_changed (); + update_apply_state (); +} + +void +SaltManagerDialog::current_changed () +{ + SaltGrain *g = current_grain (); + details_text->set_grain (g); + if (!g) { + details_frame->setEnabled (false); + delete_button->setEnabled (false); + } else { + details_frame->setEnabled (true); + delete_button->setEnabled (true); + edit_button->setEnabled (! g->is_readonly ()); + } +} + +lay::SaltGrain * +SaltManagerDialog::current_grain () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; +} + +void +SaltManagerDialog::mine_update_current_changed () +{ +BEGIN_PROTECTED + + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (model != 0); + SaltGrain *g = model->grain_from_index (salt_mine_view_update->currentIndex ()); + + details_update_frame->setEnabled (g != 0); + + SaltGrain *remote_grain = get_remote_grain_info (g, details_update_text); + m_remote_update_grain.reset (remote_grain); + +END_PROTECTED +} + +void +SaltManagerDialog::mine_new_current_changed () +{ +BEGIN_PROTECTED + + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (model != 0); + SaltGrain *g = model->grain_from_index (salt_mine_view_new->currentIndex ()); + + details_new_frame->setEnabled (g != 0); + + SaltGrain *remote_grain = get_remote_grain_info (g, details_new_text); + m_remote_new_grain.reset (remote_grain); + +END_PROTECTED +} + +lay::SaltGrain * +SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details) +{ + if (! g) { + return 0; + } + + std::auto_ptr remote_grain; + remote_grain.reset (0); + + // Download actual grain definition file + try { + + if (g->url ().empty ()) { + throw tl::Exception (tl::to_string (tr ("No download link available"))); + } + + QString html = tr ( + "" + "" + "" + "

Fetching Package Definition ...

" + "

URL: %1

" + "
" + "" + "" + ) + .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))); + + details->setHtml (html); + + QApplication::processEvents (QEventLoop::ExcludeUserInputEvents); + + tl::InputStream stream (SaltGrain::spec_url (g->url ())); + + remote_grain.reset (new SaltGrain ()); + remote_grain->load (stream); + remote_grain->set_url (g->url ()); + + if (g->name () != remote_grain->name ()) { + throw tl::Exception (tl::to_string (tr ("Name mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->name ())).arg (tl::to_qstring (remote_grain->name ())))); + } + if (SaltGrain::compare_versions (g->version (), remote_grain->version ()) != 0) { + throw tl::Exception (tl::to_string (tr ("Version mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (remote_grain->version ())))); + } + + details->set_grain (remote_grain.get ()); + + } catch (tl::Exception &ex) { + + remote_grain.reset (0); + + QString html = tr ( + "" + "" + "" + "

Error Fetching Package Definition

" + "

URL: %1

" + "

Error: %2

" + "
" + "" + "" + ) + .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))) + .arg (tl::to_qstring (tl::escaped_to_html (ex.msg ()))); + + details->setHtml (html); + + } + + return remote_grain.release (); +} + +} diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h new file mode 100644 index 000000000..88493198d --- /dev/null +++ b/src/lay/laySaltManagerDialog.h @@ -0,0 +1,176 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltManagerDialog +#define HDR_laySaltManagerDialog + +#include "ui_SaltManagerDialog.h" +#include "laySalt.h" +#include "tlDeferredExecution.h" + +#include +#include + +namespace lay +{ + +class SaltGrainPropertiesDialog; + +/** + * @brief The dialog for managing the Salt ("Packages") + */ +class SaltManagerDialog + : public QDialog, private Ui::SaltManagerDialog +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltManagerDialog (QWidget *parent, lay::Salt *salt, const std::string &salt_mine_url); + +private slots: + /** + * @brief Called when the list of packages (grains) is about to change + */ + void salt_about_to_change (); + + /** + * @brief Called when the list of packages (grains) has changed + */ + void salt_changed (); + + /** + * @brief Called when the repository (salt mine) is about to change + */ + void salt_mine_about_to_change (); + + /** + * @brief Called when the repository (salt mine) has changed + */ + void salt_mine_changed (); + + /** + * @brief Called when the currently selected package (grain) has changed + */ + void current_changed (); + + /** + * @brief Called when the currently selected package from the update page has changed + */ + void mine_update_current_changed (); + + /** + * @brief Called when the currently selected package from the new installation page has changed + */ + void mine_new_current_changed (); + + /** + * @brief Called when the "edit" button is pressed + */ + void edit_properties (); + + /** + * @brief Called when the "mark" button is pressed + */ + void mark_clicked (); + + /** + * @brief Called when the "edit" button is pressed + */ + void create_grain (); + + /** + * @brief Called when the "delete" button is pressed + */ + void delete_grain (); + + /** + * @brief Called when the mode tab changed + */ + void mode_changed (); + + /** + * @brief Called when the "apply" button is clicked + */ + void apply (); + + /** + * @brief Called when one search text changed + */ + void search_text_changed (const QString &text); + + /** + * @brief Called to show the marked items only (new packages tab) + */ + void show_marked_only_new (); + + /** + * @brief Called to show all items again (new packages tab) + */ + void show_all_new (); + + /** + * @brief Called to unmark all items (new packages tab) + */ + void unmark_all_new (); + + /** + * @brief Called to show the marked items only (update packages tab) + */ + void show_marked_only_update (); + + /** + * @brief Called to show all items again (update packages tab) + */ + void show_all_update (); + + /** + * @brief Called to unmark all items (update packages tab) + */ + void unmark_all_update (); + + /** + * @brief Reloads the salt mine + */ + void refresh (); + +private: + Salt *mp_salt; + Salt m_salt_mine; + std::string m_salt_mine_url; + std::auto_ptr m_remote_update_grain; + std::auto_ptr m_remote_new_grain; + SaltGrainPropertiesDialog *mp_properties_dialog; + tl::DeferredMethod dm_update_models; + int m_current_tab; + + SaltGrain *current_grain (); + void update_models (); + void update_apply_state (); + SaltGrain *get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details); +}; + +} + +#endif diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc new file mode 100644 index 000000000..078582186 --- /dev/null +++ b/src/lay/laySaltModel.cc @@ -0,0 +1,433 @@ + +/* + + 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 "laySaltModel.h" +#include "laySalt.h" + +#include +#include +#include +#include +#include + +namespace lay +{ + +// -------------------------------------------------------------------------------------- + +SaltItemDelegate::SaltItemDelegate (QObject *parent) + : QStyledItemDelegate (parent) +{ + // .. nothing yet .. +} + +void +SaltItemDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItemV4 optionV4 = option; + initStyleOption (&optionV4, index); + + bool is_enabled = (optionV4.state & QStyle::State_Enabled); + optionV4.state |= QStyle::State_Enabled; + + QStyle *style = optionV4.widget ? optionV4.widget->style () : QApplication::style (); + + QTextDocument doc; + doc.setHtml (optionV4.text); + + optionV4.text = QString (); + style->drawControl (QStyle::CE_ItemViewItem, &optionV4, painter); + + QAbstractTextDocumentLayout::PaintContext ctx; + + if (optionV4.state & QStyle::State_Selected) { + ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Active, QPalette::HighlightedText)); + } else if (! is_enabled) { + ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Disabled, QPalette::Text)); + } + + QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, &optionV4); + painter->save (); + painter->translate (textRect.topLeft ()); + painter->setClipRect (textRect.translated (-textRect.topLeft ())); + doc.documentLayout()->draw (painter, ctx); + painter->restore (); +} + +QSize +SaltItemDelegate::sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const int textWidth = 500; + + QStyleOptionViewItemV4 optionV4 = option; + initStyleOption (&optionV4, index); + + const QListView *view = dynamic_cast (optionV4.widget); + QSize icon_size (0, 0); + if (view) { + icon_size = view->iconSize (); + } + + QTextDocument doc; + doc.setHtml (optionV4.text); + doc.setTextWidth (textWidth); + return QSize (textWidth + icon_size.width () + 6, std::max (icon_size.height () + 12, int (doc.size ().height ()))); +} + +// -------------------------------------------------------------------------------------- + +SaltModel::SaltModel (QObject *parent, lay::Salt *salt, lay::Salt *salt_filtered, bool salt_exclude) + : QAbstractItemModel (parent), mp_salt (salt), + mp_salt_filtered (salt_filtered), m_salt_exclude (salt_exclude), + m_in_update (false) +{ + create_ordered_list (); +} + +Qt::ItemFlags +SaltModel::flags (const QModelIndex &index) const +{ + Qt::ItemFlags f = QAbstractItemModel::flags (index); + + const lay::SaltGrain *g = grain_from_index (index); + if (g && ! is_enabled (g->name ())) { + f &= ~Qt::ItemIsSelectable; + f &= ~Qt::ItemIsEnabled; + } + + return f; +} + +QVariant +SaltModel::data (const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + + const lay::SaltGrain *g = grain_from_index (index); + if (!g) { + return QVariant (); + } + + std::string text = ""; + text += "

"; + text += tl::escaped_to_html (g->name ()); + if (!g->version ().empty ()) { + text += " "; + text += tl::escaped_to_html (g->version ()); + } + if (!g->title ().empty ()) { + text += " - "; + text += tl::escaped_to_html (g->title ()); + } + text += "

"; + if (!g->doc ().empty ()) { + text += "

"; + text += tl::escaped_to_html (g->doc ()); + text += "

"; + } + + std::map >::const_iterator m = m_messages.find (g->name ()); + if (m != m_messages.end ()) { + if (m->second.first == Warning || m->second.first == Error) { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } else if (m->second.first == Info) { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } else { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } + } + + text += ""; + + return tl::to_qstring (text); + + } else if (role == Qt::DecorationRole) { + + int icon_dim = 64; + + const lay::SaltGrain *g = grain_from_index (index); + if (!g) { + return QVariant (); + } + + QImage img; + if (g->icon ().isNull ()) { + img = QImage (":/salt_icon.png"); + } else { + img = g->icon (); + } + + if (img.width () != icon_dim || img.height () != icon_dim) { + + QImage scaled = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + img = QImage (icon_dim, icon_dim, QImage::Format_ARGB32); + img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&img); + painter.drawImage ((icon_dim - scaled.width ()) / 2, (icon_dim - scaled.height ()) / 2, scaled); + + } + + if (m_marked.find (g->name ()) != m_marked.end ()) { + QPainter painter (&img); + QImage warn (":/marked_64.png"); + painter.drawImage (0, 0, warn); + } + + std::map >::const_iterator m = m_messages.find (g->name ()); + if (m != m_messages.end ()) { + if (m->second.first == Warning) { + QPainter painter (&img); + QImage warn (":/warn_16.png"); + painter.drawImage (0, 0, warn); + } else if (m->second.first == Error) { + QPainter painter (&img); + QImage warn (":/error_16.png"); + painter.drawImage (0, 0, warn); + } else if (m->second.first == Info) { + QPainter painter (&img); + QImage warn (":/info_16.png"); + painter.drawImage (0, 0, warn); + } + } + + return QPixmap::fromImage (img); + + } else { + return QVariant (); + } +} + +QModelIndex +SaltModel::index (int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid ()) { + return QModelIndex (); + } else { + return createIndex (row, column); + } +} + +QModelIndex +SaltModel::parent (const QModelIndex & /*index*/) const +{ + return QModelIndex (); +} + +int +SaltModel::columnCount(const QModelIndex & /*parent*/) const +{ + return 1; +} + +int +SaltModel::rowCount (const QModelIndex &parent) const +{ + if (parent.isValid ()) { + return 0; + } else { + return int (m_ordered_grains.size ()); + } +} + +SaltGrain * +SaltModel::grain_from_index (const QModelIndex &index) const +{ + if (index.isValid () && index.row () >= 0 && index.row () < int (m_ordered_grains.size ())) { + return m_ordered_grains [index.row ()]; + } else { + return 0; + } +} + +bool +SaltModel::is_marked (const std::string &name) const +{ + return m_marked.find (name) != m_marked.end (); +} + +bool +SaltModel::is_enabled (const std::string &name) const +{ + return m_disabled.find (name) == m_disabled.end (); +} + +void +SaltModel::set_marked (const std::string &name, bool marked) +{ + if (marked != is_marked (name)) { + if (! marked) { + m_marked.erase (name); + } else { + m_marked.insert (name); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::clear_marked () +{ + if (! m_marked.empty ()) { + m_marked.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::set_enabled (const std::string &name, bool enabled) +{ + if (enabled != is_enabled (name)) { + if (enabled) { + m_disabled.erase (name); + } else { + m_disabled.insert (name); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::enable_all () +{ + if (! m_disabled.empty ()) { + m_disabled.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::clear_order () +{ + m_display_order.clear (); +} + +void +SaltModel::reset_order (const std::string &name) +{ + m_display_order.erase (name); +} + +void +SaltModel::set_order (const std::string &name, int order) +{ + m_display_order[name] = order; +} + +void +SaltModel::set_message (const std::string &name, Severity severity, const std::string &message) +{ + bool needs_update = false; + if (message.empty ()) { + if (m_messages.find (name) != m_messages.end ()) { + m_messages.erase (name); + needs_update = true; + } + } else { + std::map >::iterator m = m_messages.find (name); + if (m == m_messages.end () || m->second.second != message || m->second.first != severity) { + m_messages.insert (std::make_pair (name, std::make_pair (severity, message))); + needs_update = true; + } + } + + if (needs_update) { + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::clear_messages () +{ + if (! m_messages.empty ()) { + m_messages.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::begin_update () +{ + if (! m_in_update) { + m_ordered_grains.clear (); + beginResetModel (); + m_in_update = true; + } +} + +void +SaltModel::update () +{ + begin_update (); + create_ordered_list (); + endResetModel (); + m_in_update = false; +} + +void +SaltModel::create_ordered_list () +{ + m_ordered_grains.clear (); + + if (m_display_order.empty ()) { + + for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + // filter the grains by looking them up in the reference salt + if (mp_salt_filtered && (mp_salt_filtered->grain_by_name ((*i)->name ()) != 0) == m_salt_exclude) { + continue; + } + m_ordered_grains.push_back (*i); + } + + } else { + + int min_order = m_display_order.begin ()->second; + int max_order = min_order; + min_order = std::min (min_order, 0); + max_order = std::max (max_order, 0); + + for (std::map::const_iterator i = m_display_order.begin (); i != m_display_order.end (); ++i) { + min_order = std::min (min_order, i->second); + max_order = std::max (max_order, i->second); + } + + for (int o = min_order; o <= max_order; ++o) { + for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + // filter the grains by looking them up in the reference salt + if (mp_salt_filtered && (mp_salt_filtered->grain_by_name ((*i)->name ()) != 0) == m_salt_exclude) { + continue; + } + std::map::const_iterator d = m_display_order.find ((*i)->name ()); + int oi = 0; + if (d != m_display_order.end ()) { + oi = d->second; + } + if (oi == o) { + m_ordered_grains.push_back (*i); + } + } + } + + } +} + +} diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h new file mode 100644 index 000000000..ea6f02a2a --- /dev/null +++ b/src/lay/laySaltModel.h @@ -0,0 +1,204 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltModel +#define HDR_laySaltModel + +#include "layCommon.h" + +#include +#include +#include +#include +#include +#include + +namespace lay +{ + +class Salt; +class SaltGrain; + +/** + * @brief A model representing the salt grains for a QListView + */ +class SaltModel + : public QAbstractItemModel +{ +Q_OBJECT + +public: + /** + * @brief An enum describing the severity of a message + */ + enum Severity + { + None = 0, + Info = 1, + Warning = 2, + Error = 3 + }; + + /** + * @brief Constructor + */ + SaltModel (QObject *parent, lay::Salt *salt, Salt *salt_filtered = 0, bool salt_exclude = false); + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QVariant data (const QModelIndex &index, int role) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + Qt::ItemFlags flags (const QModelIndex &index) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QModelIndex index (int row, int column, const QModelIndex &parent) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QModelIndex parent (const QModelIndex & /*index*/) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + int columnCount(const QModelIndex & /*parent*/) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + int rowCount (const QModelIndex &parent) const; + + /** + * @brief Gets the grain pointer from a model index + */ + SaltGrain *grain_from_index (const QModelIndex &index) const; + + /** + * @brief Marks the model as "under construction" + * This method can be called (multiple times) before update to mark the model + * as being under construction. update() will end this state. + */ + void begin_update (); + + /** + * @brief Updates the model + * Needs to be called when the salt has changed. + */ + void update (); + + /** + * @brief Sets or resets the "marked" flag on the grain with the given name + */ + void set_marked (const std::string &name, bool marked); + + /** + * @brief Clears the marked state of all grains + */ + void clear_marked (); + + /** + * @brief Enables or disables the grain with the given name + */ + void set_enabled (const std::string &name, bool enabled); + + /** + * @brief Enables all grains + */ + void enable_all (); + + /** + * @brief Installs a message on the grain with the given name + * Installing an empty message basically removes the message. + */ + void set_message (const std::string &name, Severity severity, const std::string &message); + + /** + * @brief Removes a message + */ + void reset_message (const std::string &name) + { + set_message (name, None, std::string ()); + } + + /** + * @brief Clears all messages + */ + void clear_messages (); + + /** + * @brief Sets the display order + * Specifying a display order for a name will make the grain appear + * before or after other grains. + * "update" needs to be called before the order becomes active. + * Non-assigned items are considered to have order (0). + */ + void set_order (const std::string &name, int order); + + /** + * @brief Resets any display order + */ + void reset_order (const std::string &name); + + /** + * @brief Resets all display order specs + */ + void clear_order (); + +public: + lay::Salt *mp_salt, *mp_salt_filtered; + bool m_salt_exclude; + std::set m_marked; + std::set m_disabled; + std::map > m_messages; + std::map m_display_order; + std::vector m_ordered_grains; + bool m_in_update; + + bool is_marked (const std::string &name) const; + bool is_enabled (const std::string &name) const; + void create_ordered_list (); +}; + +// -------------------------------------------------------------------------------------- + +/** + * @brief A delegate displaying the summary of a grain + */ +class SaltItemDelegate + : public QStyledItemDelegate +{ +public: + SaltItemDelegate (QObject *parent); + + void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +} + +#endif diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc new file mode 100644 index 000000000..1f3918944 --- /dev/null +++ b/src/lay/laySaltTemplates.qrc @@ -0,0 +1,112 @@ + + + + + + + salt_templates/font/grain.xml + + + salt_templates/font/fonts/new_font.gds + + + salt_templates/font/doc/readme.html + + + + + salt_templates/lib/grain.xml + + + salt_templates/lib/libraries/new_lib.gds + + + salt_templates/lib/doc/readme.html + + + + + salt_templates/pcell_lib/grain.xml + + + salt_templates/pcell_lib/macros/pcell.lym + + + salt_templates/pcell_lib/doc/readme.html + + + + + salt_templates/macro/grain.xml + + + salt_templates/macro/macros/new_macro.lym + + + salt_templates/macro/doc/readme.html + + + + + salt_templates/ruby_lib/grain.xml + + + salt_templates/ruby_lib/macros/new_macro.lym + + + salt_templates/ruby_lib/doc/readme.html + + + + + salt_templates/pymacro/grain.xml + + + salt_templates/pymacro/pymacros/new_macro.lym + + + salt_templates/pymacro/doc/readme.html + + + + + salt_templates/python_lib/grain.xml + + + salt_templates/python_lib/pymacros/new_macro.lym + + + salt_templates/python_lib/doc/readme.html + + + + + salt_templates/drc/grain.xml + + + salt_templates/drc/drc/new_drc.lym + + + salt_templates/drc/doc/readme.html + + + + + salt_templates/tech/grain.xml + + + salt_templates/tech/tech/tech.lyt + + + + + + + + + + + salt_templates/tech/doc/readme.html + + + diff --git a/src/lay/laySignalHandler.cc b/src/lay/laySignalHandler.cc new file mode 100644 index 000000000..9b65614c2 --- /dev/null +++ b/src/lay/laySignalHandler.cc @@ -0,0 +1,336 @@ + +/* + + 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 "laySignalHandler.h" +#include "layCrashMessage.h" +#include "layVersion.h" +#include "tlException.h" + +#ifdef _WIN32 +# include +# include +# include +// get rid of these - we have std::min/max .. +# ifdef min +# undef min +# endif +# ifdef max +# undef max +# endif +#else +# include +# include +# include +#endif + +#include + +namespace lay +{ + +#if defined(WIN32) + +static QString +addr2symname (DWORD64 addr) +{ + const int max_symbol_length = 255; + + SYMBOL_INFO *symbol = (SYMBOL_INFO *) calloc (sizeof (SYMBOL_INFO) + (max_symbol_length + 1) * sizeof (char), 1); + symbol->MaxNameLen = max_symbol_length; + symbol->SizeOfStruct = sizeof (SYMBOL_INFO); + + HANDLE process = GetCurrentProcess (); + + QString sym_name; + DWORD64 d; + bool has_symbol = false; + DWORD64 disp = addr; + if (SymFromAddr(process, addr, &d, symbol)) { + // Symbols taken from the export table seem to be unreliable - skip these + // and report the module name + offset. + if (! (symbol->Flags & SYMFLAG_EXPORT)) { + sym_name = QString::fromLocal8Bit (symbol->Name); + disp = d; + has_symbol = true; + } + } + + // find the module name from the module base address + + HMODULE modules[1024]; + DWORD modules_size = 0; + if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { + modules_size = 0; + } + + QString mod_name; + for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { + TCHAR mn[MAX_PATH]; + if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { + MODULEINFO mi; + if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { + if ((DWORD64) mi.lpBaseOfDll <= addr && (DWORD64) mi.lpBaseOfDll + mi.SizeOfImage > addr) { + mod_name = QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName (); + if (! has_symbol) { + disp -= (DWORD64) mi.lpBaseOfDll; + } + break; + } + } + } + } + + if (! mod_name.isNull ()) { + mod_name = QString::fromUtf8 ("(") + mod_name + QString::fromUtf8 (") "); + } + + free (symbol); + + return QString::fromUtf8 ("0x%1 - %2%3+%4"). + arg (addr, 0, 16). + arg (mod_name). + arg (sym_name). + arg (disp); +} + +QString +get_symbol_name_from_address (const QString &mod_name, size_t addr) +{ + HANDLE process = GetCurrentProcess (); + + DWORD64 mod_base = 0; + if (! mod_name.isEmpty ()) { + + // find the module name from the module base address + HMODULE modules[1024]; + DWORD modules_size = 0; + if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { + modules_size = 0; + } + + for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { + TCHAR mn[MAX_PATH]; + if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { + if (mod_name == QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName ()) { + MODULEINFO mi; + if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { + mod_base = (DWORD64) mi.lpBaseOfDll; + } + } + } + } + + if (mod_base == 0) { + throw tl::Exception (tl::to_string (QObject::tr ("Unknown module name: ") + mod_name)); + } + + } + + SymInitialize (process, NULL, TRUE); + QString res = addr2symname (mod_base + (DWORD64) addr); + SymCleanup (process); + + return res; +} + +LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + HANDLE process = GetCurrentProcess (); + SymInitialize (process, NULL, TRUE); + + QString text; + text += QObject::tr ("Exception code: 0x%1\n").arg (pExceptionInfo->ExceptionRecord->ExceptionCode, 0, 16); + text += QObject::tr ("Program Version: ") + + QString::fromUtf8 (lay::Version::name ()) + + QString::fromUtf8 (" ") + + QString::fromUtf8 (lay::Version::version ()) + + QString::fromUtf8 (" (") + + QString::fromUtf8 (lay::Version::subversion ()) + + QString::fromUtf8 (")"); +#if defined(_WIN64) + text += QString::fromUtf8 (" AMD64"); +#else + text += QString::fromUtf8 (" x86"); +#endif + text += QString::fromUtf8 ("\n"); + text += QObject::tr ("\nBacktrace:\n"); + + CONTEXT context_record = *pExceptionInfo->ContextRecord; + + // Initialize stack walking. + STACKFRAME64 stack_frame; + memset(&stack_frame, 0, sizeof(stack_frame)); + +#if defined(_WIN64) + int machine_type = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = context_record.Rip; + stack_frame.AddrFrame.Offset = context_record.Rbp; + stack_frame.AddrStack.Offset = context_record.Rsp; +#else + int machine_type = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = context_record.Eip; + stack_frame.AddrFrame.Offset = context_record.Ebp; + stack_frame.AddrStack.Offset = context_record.Esp; +#endif + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + + while (StackWalk64 (machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &stack_frame, + &context_record, + NULL, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + NULL)) { + text += addr2symname (stack_frame.AddrPC.Offset); + text += QString::fromUtf8 ("\n"); + } + + SymCleanup (process); + + // YES! I! KNOW! + // In a signal handler you shall not do fancy stuff (in particular not + // open dialogs) nor shall you throw exceptions! But that scheme appears to + // be working since in most cases the signal is raised from our code (hence + // from our stack frames) and everything is better than just showing + // the "application stopped working" dialog. + // Isn't it? + + CrashMessage msg (0, true, text); + if (! msg.exec ()) { + // terminate unconditionally + return EXCEPTION_EXECUTE_HANDLER; + } else { + throw tl::CancelException (); + } +} + +static void handle_signal (int signo) +{ + signal (signo, handle_signal); + int user_base = (1 << 29); + RaiseException(signo + user_base, 0, 0, NULL); +} + +static void install_signal_handlers () +{ + // disable any signal handlers that Ruby might have installed. + signal (SIGSEGV, SIG_DFL); + signal (SIGILL, SIG_DFL); + signal (SIGFPE, SIG_DFL); + + signal (SIGABRT, handle_signal); + +#if 0 + // TODO: not available to MinGW - linking against msvc100 would help + // but then the app crashes. + _set_abort_behavior( 0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT ); +#endif + + SetUnhandledExceptionFilter(ExceptionHandler); +} + +#else + +QString get_symbol_name_from_address (const QString &, size_t) +{ + return QString::fromUtf8 ("n/a"); +} + +void signal_handler (int signo, siginfo_t *si, void *) +{ + void *array [100]; + + bool can_resume = (signo != SIGILL); + + size_t nptrs = backtrace (array, sizeof (array) / sizeof (array[0])); + + QString text; + text += QObject::tr ("Signal number: %1\n").arg (signo); + text += QObject::tr ("Address: 0x%1\n").arg ((size_t) si->si_addr, 0, 16); + text += QObject::tr ("Program Version: ") + + QString::fromUtf8 (lay::Version::name ()) + + QString::fromUtf8 (" ") + + QString::fromUtf8 (lay::Version::version ()) + + QString::fromUtf8 (" (") + + QString::fromUtf8 (lay::Version::subversion ()) + + QString::fromUtf8 (")"); + text += QString::fromUtf8 ("\n"); + text += QObject::tr ("Backtrace:\n"); + + char **symbols = backtrace_symbols (array, nptrs); + if (symbols == NULL) { + text += QObject::tr ("-- Unable to obtain stack trace --"); + } else { + for (size_t i = 2; i < nptrs; i++) { + text += QString::fromUtf8 (symbols [i]) + QString::fromUtf8 ("\n"); + } + } + free(symbols); + + // YES! I! KNOW! + // In a signal handler you shall not do fancy stuff (in particular not + // open dialogs) nor shall you throw exceptions! But that scheme appears to + // be working since in most cases the signal is raised from our code (hence + // from our stack frames) and everything is better than just core dumping. + // Isn't it? + + CrashMessage msg (0, can_resume, text); + if (! msg.exec ()) { + + _exit (signo); + + } else { + + sigset_t x; + sigemptyset (&x); + sigaddset(&x, signo); + sigprocmask(SIG_UNBLOCK, &x, NULL); + + throw tl::CancelException (); + + } +} + +void install_signal_handlers () +{ + struct sigaction act; + act.sa_sigaction = signal_handler; + sigemptyset (&act.sa_mask); + act.sa_flags = SA_SIGINFO; +#if !defined(__APPLE__) + act.sa_restorer = 0; +#endif + + sigaction (SIGSEGV, &act, NULL); + sigaction (SIGILL, &act, NULL); + sigaction (SIGFPE, &act, NULL); + sigaction (SIGABRT, &act, NULL); + sigaction (SIGBUS, &act, NULL); +} + +#endif + +} diff --git a/src/lay/layTechnologySelector.h b/src/lay/laySignalHandler.h similarity index 70% rename from src/lay/layTechnologySelector.h rename to src/lay/laySignalHandler.h index 0d6c8b1ad..9b9c528f9 100644 --- a/src/lay/layTechnologySelector.h +++ b/src/lay/laySignalHandler.h @@ -20,16 +20,24 @@ */ +#ifndef HDR_laySignalHandler +#define HDR_laySignalHandler -#ifndef HDR_layTechnologySelector -#define HDR_layTechnologySelector +#include namespace lay { - // no exposes classes. Everything is inside the plugin declaration singleton. +/** + * @brief Installs global signal handlers for SIGSEGV and similar + */ +void install_signal_handlers (); + +/** + * @brief For debugging purposes: get the symbol name from a memory address + */ +QString get_symbol_name_from_address (const QString &mod_name, size_t addr); } #endif - diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index aa392fa67..edddde5df 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -31,6 +31,8 @@ #include "layApplication.h" #include "layMacroEditorTree.h" #include "layMacro.h" +#include "layMacroController.h" +#include "layTechnologyController.h" #include "tlAssert.h" #include "tlStream.h" #include "dbStream.h" @@ -47,6 +49,28 @@ namespace lay { +// ---------------------------------------------------------------- + +static std::string +title_for_technology (const lay::Technology *t) +{ + std::string d; + if (t->name ().empty ()) { + d = t->description (); + } else { + d += t->name (); + if (! t->grain_name ().empty ()) { + d += " "; + d += tl::to_string (QObject::tr ("[Package %1]").arg (tl::to_qstring (t->grain_name ()))); + } + if (! t->description ().empty ()) { + d += " - "; + d += t->description (); + } + } + return d; +} + // ---------------------------------------------------------------- // TechBaseEditorPage implementation @@ -463,7 +487,7 @@ TechMacrosPage::commit () 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) + : QDialog (parent), mp_current_tech (0), mp_current_editor (0), mp_current_tech_component (0), m_current_tech_changed_enabled (true) { setObjectName (QString::fromUtf8 ("tech_setup_dialog")); @@ -479,29 +503,29 @@ TechSetupDialog::TechSetupDialog (QWidget *parent) 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 ())); + QAction *refresh_action = new QAction (QObject::tr ("Refresh"), this); + connect (refresh_action, SIGNAL (triggered ()), this, SLOT (refresh_clicked ())); + + QAction *separator; tech_tree->addAction (add_action); tech_tree->addAction (delete_action); tech_tree->addAction (rename_action); - QAction *separator = new QAction (this); + separator = new QAction (this); separator->setSeparator (true); tech_tree->addAction (separator); tech_tree->addAction (import_action); tech_tree->addAction (export_action); + separator = new QAction (this); + separator->setSeparator (true); + tech_tree->addAction (separator); + tech_tree->addAction (refresh_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 ("To get started with the technology manager, read the documentation provided: About Technology Management.")), - "tech-manager-basic-tips"); - td.exec_dialog (); - s_first_show = false; - } } TechSetupDialog::~TechSetupDialog () @@ -527,23 +551,96 @@ TechSetupDialog::clear_components () mp_current_tech_component = 0; } -int -TechSetupDialog::exec () +void +TechSetupDialog::refresh_clicked () +{ + m_current_tech_changed_enabled = false; + +BEGIN_PROTECTED + + commit_tech_component (); + update_tech (0); + + std::string tech_name; + if (selected_tech ()) { + tech_name = selected_tech ()->name (); + } + + // Save the expanded state of the items + std::set expanded_techs; + for (int i = 0; i < tech_tree->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + if (item && item->isExpanded ()) { + QVariant d = item->data (0, Qt::UserRole); + if (d != QVariant ()) { + expanded_techs.insert (tl::to_string (d.toString ())); + } + } + } + + lay::TechnologyController::instance ()->rescan (m_technologies); + + update_tech_tree (); + + QTreeWidgetItem *new_item = 0; + + for (int i = 0; i < tech_tree->topLevelItemCount () && !new_item; ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + QVariant d = item->data (0, Qt::UserRole); + if (d != QVariant () && tech_name == tl::to_string (d.toString ())) { + new_item = item; + } + } + + tech_tree->setCurrentItem (new_item); + + // restore the expanded state + for (int i = 0; i < tech_tree->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + QVariant d = item->data (0, Qt::UserRole); + bool expand = (d != QVariant () && expanded_techs.find (tl::to_string (d.toString ())) != expanded_techs.end ()); + item->setExpanded (expand); + } + + update_tech (selected_tech ()); + update_tech_component (); + +END_PROTECTED + + m_current_tech_changed_enabled = true; +} + +void +TechSetupDialog::update () { - m_technologies = *lay::Technologies ().instance (); update_tech_tree (); tech_tree->setCurrentItem (tech_tree->topLevelItem (0)); update_tech (selected_tech ()); +} + +int +TechSetupDialog::exec (lay::Technologies &technologies) +{ + if (s_first_show) { + TipDialog td (this, + tl::to_string (QObject::tr ("To get started with the technology manager, read the documentation provided: About Technology Management.")), + "tech-manager-basic-tips"); + td.exec_dialog (); + s_first_show = false; + } + + m_technologies = technologies; + update (); + tc_stack->setMinimumSize (tc_stack->sizeHint ()); int ret = QDialog::exec (); if (ret) { - *lay::Technologies ().instance () = m_technologies; + technologies = m_technologies; } // clean up update_tech (0); - clear_components (); m_technologies = lay::Technologies (); update_tech_tree (); @@ -583,7 +680,21 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists"))); } + QDir root = QDir (tl::to_qstring (lay::TechnologyController::instance ()->default_root ())); + QDir tech_dir (root.filePath (tn)); + if (tech_dir.exists ()) { + if (QMessageBox::question (this, QObject::tr ("Creating Technology"), + QObject::tr ("A target folder with path '%1' already exists\nUse this directory for the new technology?").arg (tech_dir.path ()), + QMessageBox::No | QMessageBox::Yes) == QMessageBox::No) { + throw tl::CancelException (); + } + } + lay::Technology *nt = new lay::Technology (*t); + + nt->set_tech_file_path (tl::to_string (tech_dir.absoluteFilePath (tn + QString::fromUtf8 (".lyt")))); + nt->set_default_base_path (tl::to_string (tech_dir.absolutePath ())); + nt->set_persisted (false); nt->set_name (tl::to_string (tn)); nt->set_description (std::string ()); m_technologies.add (nt); @@ -610,8 +721,8 @@ BEGIN_PROTECTED 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 (t->is_readonly ()) { + throw tl::Exception (tl::to_string (QObject::tr ("This technology is read-only and cannot be deleted"))); } if (QMessageBox::question (this, QObject::tr ("Deleting Technology"), @@ -623,6 +734,7 @@ BEGIN_PROTECTED if (i->name () == t->name ()) { m_technologies.remove (i->name ()); + update_tech_tree (); select_tech (*m_technologies.technology_by_name (std::string ())); @@ -652,8 +764,8 @@ BEGIN_PROTECTED 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"))); + if (t->is_readonly ()) { + throw tl::Exception (tl::to_string (QObject::tr ("This technology is read-only and cannot be renamed"))); } bool ok = false; @@ -670,10 +782,21 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists"))); } - t->set_name (tl::to_string (tn)); + if (t->name () != tl::to_string (tn)) { - update_tech_tree (); - select_tech (*t); + t->set_name (tl::to_string (tn)); + + if (! t->is_persisted () && ! t->tech_file_path().empty ()) { + TipDialog td (this, + tl::to_string (QObject::tr ("Renaming of a technology will neither rename the technology file or the folder the file is stored in.
The file or folder needs to be renamed manually.")), + "tech-manager-rename-tip"); + td.exec_dialog (); + } + + update_tech_tree (); + select_tech (*t); + + } } @@ -740,19 +863,15 @@ TechSetupDialog::update_tech_tree () for (std::map ::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 (); + f.setItalic (t->second->is_readonly ()); QTreeWidgetItem *ti = new QTreeWidgetItem (tech_tree); - ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d))); + ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (title_for_technology (t->second)))); ti->setData (0, Qt::UserRole, QVariant (tl::to_qstring (t->first))); ti->setData (0, Qt::FontRole, QVariant (f)); + if (! t->second->tech_file_path ().empty ()) { + ti->setData (0, Qt::ToolTipRole, QVariant (tl::to_qstring (t->second->tech_file_path ()))); + } std::vector tc_names = t->second->component_names (); std::map tc_by_name; @@ -775,12 +894,14 @@ TechSetupDialog::update_tech_tree () tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring ("_save_options"))); tci->setData (0, Qt::FontRole, QVariant (f)); - const std::vector > &mc = lay::Application::instance ()->macro_categories (); - for (std::vector >::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)); + if (lay::MacroController::instance ()) { + const std::vector > &mc = lay::MacroController::instance ()->macro_categories (); + for (std::vector >::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 ::const_iterator c = tc_by_name.begin (); c != tc_by_name.end (); ++c) { @@ -791,7 +912,6 @@ TechSetupDialog::update_tech_tree () } } - } void @@ -808,28 +928,30 @@ TechSetupDialog::update_tech (lay::Technology *t) if (t) { lay::TechnologyComponentEditor *tce_widget = new TechBaseEditorPage (this); - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); 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 > &mc = lay::Application::instance ()->macro_categories (); - for (std::vector >::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)); + if (lay::MacroController::instance ()) { + const std::vector > &mc = lay::MacroController::instance ()->macro_categories (); + for (std::vector >::const_iterator c = mc.begin (); c != mc.end (); ++c) { + tce_widget = new TechMacrosPage (this, c->first, c->second); + tce_widget->setEnabled (!t->is_readonly ()); + 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->setEnabled (!t->is_readonly ()); 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->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, 0); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (std::string ("_save_options"), tce_widget)); @@ -842,7 +964,7 @@ TechSetupDialog::update_tech (lay::Technology *t) tce_widget = tc->create_editor (this); if (tce_widget) { - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, tc); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (tc->name (), tce_widget)); @@ -912,6 +1034,10 @@ END_PROTECTED void TechSetupDialog::current_tech_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous) { + if (! m_current_tech_changed_enabled) { + return; + } + BEGIN_PROTECTED try { if (current) { @@ -936,9 +1062,11 @@ TechSetupDialog::commit_tech_component () mp_current_editor->commit (); } - if (mp_current_tech && mp_current_tech_component && mp_current_tech->is_persisted ()) { + if (mp_current_tech && !mp_current_tech->is_readonly ()) { - mp_current_tech->set_component (mp_current_tech_component->clone ()); + if (mp_current_tech_component) { + 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) { @@ -946,13 +1074,7 @@ TechSetupDialog::commit_tech_component () 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))); + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (title_for_technology (t)))); } diff --git a/src/lay/layTechSetupDialog.h b/src/lay/layTechSetupDialog.h index 1fddac714..c4c503899 100644 --- a/src/lay/layTechSetupDialog.h +++ b/src/lay/layTechSetupDialog.h @@ -128,7 +128,7 @@ public: TechSetupDialog (QWidget *parent); ~TechSetupDialog (); - int exec (); + int exec (lay::Technologies &technologies); protected slots: void current_tech_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous); @@ -137,6 +137,7 @@ protected slots: void rename_clicked (); void import_clicked (); void export_clicked (); + void refresh_clicked (); private: void update_tech_tree (); @@ -148,6 +149,7 @@ private: std::string selected_tech_component_name (); void commit_tech_component (); void clear_components (); + void update (); lay::Technologies m_technologies; lay::Technology *mp_current_tech; @@ -155,6 +157,7 @@ private: std::map m_technology_components; lay::TechnologyComponentEditor *mp_current_editor; lay::TechnologyComponent *mp_current_tech_component; + bool m_current_tech_changed_enabled; }; class LAY_PUBLIC TechComponentSetupDialog diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc new file mode 100644 index 000000000..94d6b0fec --- /dev/null +++ b/src/lay/layTechnologyController.cc @@ -0,0 +1,594 @@ + +/* + + 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 "layTechnologyController.h" +#include "layTechSetupDialog.h" +#include "layMainWindow.h" +#include "layApplication.h" +#include "laySaltController.h" +#include "layConfig.h" +#include "layQtTools.h" +#include "laybasicConfig.h" + +#include +#include +#include +#include + +namespace lay +{ + +static const std::string cfg_tech_editor_window_state ("tech-editor-window-state"); + +std::string tech_string_from_name (const std::string &tn) +{ + if (tn.empty ()) { + return tl::to_string (QObject::tr ("(Default)")); + } else { + return tn; + } +} + +TechnologyController::TechnologyController () + : PluginDeclaration (), mp_editor (0), mp_mw (0), mp_active_technology (0) +{ + m_current_technology_updated = false; + m_technologies_configured = false; +} + +TechnologyController * +TechnologyController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + TechnologyController *tc = dynamic_cast (cls.operator-> ()); + if (tc) { + return tc; + } + } + return 0; +} + +void +TechnologyController::initialize (lay::PluginRoot *root) +{ + mp_mw = dynamic_cast (root); + if (mp_mw) { + mp_editor = new lay::TechSetupDialog (mp_mw); + mp_editor->setModal (false); + } +} + +void +TechnologyController::initialized (lay::PluginRoot * /*root*/) +{ + update_menu (); + connect_events (); + + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +TechnologyController::uninitialize (lay::PluginRoot * /*root*/) +{ + m_tech_actions.clear (); + tl::Object::detach_from_all_events (); + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +TechnologyController::get_options (std::vector < std::pair > &options) const +{ + options.push_back (std::pair (cfg_initial_technology, "")); + options.push_back (std::pair (cfg_tech_editor_window_state, "")); +} + +void +TechnologyController::get_menu_entries (std::vector &menu_entries) const +{ + lay::PluginDeclaration::get_menu_entries (menu_entries); + menu_entries.push_back (lay::MenuEntry ("technology_selector:apply_technology", "technology_selector:tech_selector_group", "@toolbar.end", tl::to_string (QObject::tr ("Technology<:techs.png>{Select technology (click to apply)}")))); +} + +void +TechnologyController::connect_events () +{ + // NOTE: the whole concept is a but strange here: the goal is to + // connect to the current view's active_cellview_changed event and + // the active cellview's technology_changed event. We could register + // events tracking the current view and active cellview which detach + // and attach event handlers. This is more tedious than doing this: + // we detach and re-attach the events whenever something changes. + // The event system supports this case, hence we do so. + + tl::Object::detach_from_all_events (); + + lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologyController::technology_changed); + lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologyController::technologies_changed); + + if (mp_mw) { + + // NOTE: the "real" call needs to come before the re-connect handler because + // the latter will remove the update call + mp_mw->current_view_changed_event.add (this, &TechnologyController::update_active_technology); + mp_mw->current_view_changed_event.add (this, &TechnologyController::connect_events); + + if (mp_mw->current_view ()) { + + // NOTE: the "real" call needs to come before the re-connect handler because + // the latter will remove the update call + mp_mw->current_view ()->active_cellview_changed_event.add (this, &TechnologyController::update_active_technology); + mp_mw->current_view ()->active_cellview_changed_event.add (this, &TechnologyController::connect_events); + + if (mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { + mp_mw->current_view ()->active_cellview ()->technology_changed_event.add (this, &TechnologyController::update_active_technology); + } + + } + + } +} + +lay::Technology * +TechnologyController::active_technology () const +{ + return mp_active_technology; +} + +void +TechnologyController::update_active_technology () +{ + lay::Technology *active_tech = 0; + if (mp_mw && mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { + + std::string tn = mp_mw->current_view ()->active_cellview ()->tech_name (); + if (lay::Technologies::instance ()->has_technology (tn)) { + active_tech = lay::Technologies::instance ()->technology_by_name (tn); + } + + } + + if (mp_active_technology != active_tech) { + + mp_active_technology = active_tech; + + if (mp_mw) { + if (active_tech) { + mp_mw->tech_message (tech_string_from_name (active_tech->name ())); + } else { + mp_mw->tech_message (std::string ()); + } + } + + emit active_technology_changed (); + + } + +#if 0 + // Hint with this implementation, the current technology follows the current layout. + // Although that's a nice way to display the current technology, it's pretty confusing + lay::PluginRoot *pr = mp_mw; + if (pr) { + pr->config_set (cfg_initial_technology, active_tech); + } +#endif +} + +void +TechnologyController::technologies_changed () +{ + // update the configuration to reflect the persisted technologies + lay::PluginRoot *pr = mp_mw; + if (pr) { + pr->config_set (cfg_technologies, lay::Technologies::instance ()->to_xml ()); + } + + update_menu (); + emit technologies_edited (); +} + +void +TechnologyController::technology_changed (lay::Technology *) +{ + update_menu (); +} + +bool +TechnologyController::configure (const std::string &name, const std::string &value) +{ + if (name == cfg_initial_technology) { + + if (value != m_current_technology) { + m_current_technology = value; + m_current_technology_updated = true; + } + + } else if (name == cfg_tech_editor_window_state) { + + lay::restore_dialog_state (mp_editor, value); + + } else if (name == cfg_technologies) { + + if (! value.empty ()) { + + try { + lay::Technologies new_tech = *lay::Technologies::instance (); + new_tech.load_from_xml (value); + replace_technologies (new_tech); + m_technologies_configured = true; + } catch (...) { + } + + } + + } + return false; +} + +void +TechnologyController::config_finalize () +{ + if (m_current_technology_updated) { + update_current_technology (); + m_current_technology_updated = false; + } + + if (m_technologies_configured) { + update_menu (); + emit technologies_edited (); + m_technologies_configured = false; + } +} + +bool +TechnologyController::menu_activated (const std::string &symbol) const +{ + if (symbol == "technology_selector:apply_technology") { + if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { + // Cancels the current modes - changing the technology may make libraries unavailable + // for example. + if (mp_mw) { + mp_mw->cancel (); + } + lay::LayoutView::current ()->active_cellview ()->apply_technology (m_current_technology); + } + return true; + } else { + return lay::PluginDeclaration::menu_activated (symbol); + } +} + +void +TechnologyController::update_current_technology () +{ + lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); + if (! pr) { + return; + } + + std::string title = tech_string_from_name (m_current_technology); + + std::vector menu_entries = pr->menu ()->group ("tech_selector_group"); + for (std::vector::const_iterator m = menu_entries.begin (); m != menu_entries.end (); ++m) { + lay::Action action = pr->menu ()->action (*m); + action.set_title (title); + } + + std::map tech_by_name; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + tech_by_name.insert (std::make_pair (t->name (), t.operator-> ())); + } + + int it = 0; + for (std::map::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t, ++it) { + m_tech_actions[it].set_checked (t->second->name () == m_current_technology); + } +} + +void +TechnologyController::update_menu () +{ + lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); + if (! pr) { + return; + } + + if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { + m_current_technology = lay::LayoutView::current ()->active_cellview ()->tech_name (); + } + + if (! lay::Technologies::instance()->has_technology (m_current_technology)) { + m_current_technology = std::string (); + } + + std::string title = tech_string_from_name (m_current_technology); + + size_t ntech = 0; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + ++ntech; + } + + std::vector tech_group = pr->menu ()->group ("tech_selector_group"); + + for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { + lay::Action action = pr->menu ()->action (*t); + action.set_title (title); + action.set_enabled (ntech > 1); + std::vector items = pr->menu ()->items (*t); + for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { + pr->menu ()->delete_item (*i); + } + } + + m_tech_actions.clear (); + + std::map tech_by_name; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + tech_by_name.insert (std::make_pair (t->name (), t.operator-> ())); + } + + int it = 0; + for (std::map::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t, ++it) { + + std::string title = tech_string_from_name (t->first); + + m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->first)); + m_tech_actions.back ().set_title (title); // setting the title here avoids interpretation of '(...)' etc. + m_tech_actions.back ().set_checkable (true); + m_tech_actions.back ().set_checked (t->first == m_current_technology); + for (std::vector::const_iterator tg = tech_group.begin (); tg != tech_group.end (); ++tg) { + pr->menu ()->insert_item (*tg + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); + } + + } + + update_active_technology (); +} + +void +TechnologyController::replace_technologies (const lay::Technologies &technologies) +{ + bool has_active_tech = (mp_active_technology != 0); + std::string active_tech_name; + if (mp_active_technology) { + active_tech_name = mp_active_technology->name (); + } + + lay::Technologies ().instance ()->begin_updates (); + *lay::Technologies ().instance () = technologies; + lay::Technologies ().instance ()->end_updates_no_event (); + + if (has_active_tech) { + mp_active_technology = lay::Technologies::instance ()->technology_by_name (active_tech_name); + } +} + +void +TechnologyController::show_editor () +{ + lay::Technologies new_tech = *lay::Technologies ().instance (); + + if (mp_editor && mp_editor->exec (new_tech)) { + + std::string err_msg; + + // determine the technology files that need to be deleted and delete them + std::set files_before; + for (lay::Technologies::const_iterator t = new_tech.begin (); t != new_tech.end (); ++t) { + if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { + files_before.insert (t->tech_file_path ()); + } + } + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->tech_file_path ().empty () && ! t->is_persisted () && files_before.find (t->tech_file_path ()) == files_before.end ()) { + // TODO: issue an error if files could not be removed + QFile (tl::to_qstring (t->tech_file_path ())).remove (); + } + } + + replace_technologies (new_tech); + + // save the technologies that need to be saved + // TODO: save only the ones that really need saving + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + + if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { + + // create the tech folder if required + + try { + + QDir dir = QFileInfo (tl::to_qstring (t->tech_file_path ())).absoluteDir (); + QStringList to_create; + while (! dir.isRoot() && ! dir.exists ()) { + to_create << dir.dirName (); + dir = QFileInfo (dir.path ()).absoluteDir (); + } + + while (! to_create.empty ()) { + if (! dir.mkdir (to_create.back ())) { + throw tl::CancelException (); + } + if (! dir.cd (to_create.back ())) { + throw tl::CancelException (); + } + to_create.pop_back (); + } + + t->save (t->tech_file_path ()); + + } catch (...) { + if (! err_msg.empty ()) { + err_msg += "\n"; + } + err_msg += t->tech_file_path (); + } + + } + + } + + if (! err_msg.empty ()) { + QMessageBox::critical (mp_mw, QObject::tr ("Error Saving Technology Files"), + QObject::tr ("The following files could not be saved:\n\n") + tl::to_qstring (err_msg), + QMessageBox::Ok); + } + + technologies_changed (); + + } + + if (mp_mw) { + mp_mw->config_set (cfg_tech_editor_window_state, lay::save_dialog_state (mp_editor)); + } +} + +const std::string & +TechnologyController::default_root () +{ + tl_assert (!m_paths.empty ()); + return m_paths.front (); +} + +void +TechnologyController::load () +{ + rescan (*lay::Technologies::instance ()); +} + +void +TechnologyController::sync_with_external_sources () +{ + rescan (*lay::Technologies::instance ()); +} + +void +TechnologyController::rescan (lay::Technologies &technologies) +{ + lay::Technologies current = technologies; + + // start with all persisted technologies (at least "default") + technologies.clear (); + for (lay::Technologies::const_iterator t = current.begin (); t != current.end (); ++t) { + if (t->is_persisted ()) { + technologies.add (new lay::Technology (*t)); + } + } + + std::vector paths = m_paths; + std::set readonly_paths; + std::map grain_names; + + // add the salt grains as potential sources for tech definitions + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back ((*g)->path ()); + grain_names.insert (std::make_pair ((*g)->path (), (*g)->name ())); + if ((*g)->is_readonly ()) { + readonly_paths.insert ((*g)->path ()); + } + } + } + + for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { + + QDir dir (tl::to_qstring (*p)); + if (! dir.exists ()) { + continue; + } + + bool readonly = (readonly_paths.find (*p) != readonly_paths.end ()); + + std::string grain_name; + std::map::const_iterator gn = grain_names.find (*p); + if (gn != grain_names.end ()) { + grain_name = gn->second; + } + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*.lyt"); + + QStringList lyt_files; + + QDirIterator di (dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (di.hasNext ()) { + lyt_files << di.next (); + } + + lyt_files.sort (); + + for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { + + try { + + if (tl::verbosity () >= 20) { + tl::info << "Auto-importing technology from " << tl::to_string (*lf); + } + + lay::Technology t; + t.load (tl::to_string (*lf)); + t.set_persisted (false); // don't save that one in the configuration + t.set_readonly (readonly || ! QFileInfo (dir.filePath (*lf)).isWritable ()); + t.set_grain_name (grain_name); + technologies.add (new lay::Technology (t)); + + } catch (tl::Exception &ex) { + tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); + } + + } + + } + + for (std::vector::const_iterator t = m_temp_tech.begin (); t != m_temp_tech.end (); ++t) { + + lay::Technology *tech = new lay::Technology (*t); + tech->set_persisted (false); // don't save that one in the configuration + tech->set_tech_file_path (std::string ()); // don't save to a file either + tech->set_readonly (true); // don't edit + technologies.add (tech); + + } +} + +void +TechnologyController::add_temp_tech (const lay::Technology &t) +{ + m_temp_tech.push_back (t); +} + +void +TechnologyController::add_path (const std::string &p) +{ + std::string tp = tl::to_string (QDir (tl::to_qstring (p)).filePath (QString::fromUtf8 ("tech"))); + m_paths.push_back (tp); +} + +static tl::RegisteredClass config_decl (new TechnologyController (), 110, "TechnologyController"); + +} diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h new file mode 100644 index 000000000..1845f08f1 --- /dev/null +++ b/src/lay/layTechnologyController.h @@ -0,0 +1,154 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_layTechnologyController +#define HDR_layTechnologyController + +#include "layCommon.h" +#include "layPlugin.h" +#include "layTechnology.h" +#include "layAbstractMenu.h" + +#include + +namespace lay +{ + +class TechSetupDialog; +class MainWindow; +class MacroCollection; + +/** + * @brief A "controller" for the technologies + * The main task of the controller is to establish and manage the + * list of technologies and to manage the active technology. + */ +class LAY_PUBLIC TechnologyController + : public PluginDeclaration, + public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + TechnologyController (); + + void initialize (lay::PluginRoot *root); + void initialized (lay::PluginRoot *root); + void uninitialize (lay::PluginRoot *root); + + void get_options (std::vector < std::pair > &options) const; + void get_menu_entries (std::vector &menu_entries) const; + + void show_editor (); + + /** + * @brief Gets the active technology object or 0 if none is active + * The active technology is the one the current cellview uses + */ + lay::Technology *active_technology () const; + + /** + * @brief Adds a path as a search path for technologies + * "load" needs to be called after search paths have been added. + */ + void add_path (const std::string &path); + + /** + * @brief Adds a temporary technology + * Temporary technologies are additional technologies which are added to the list of technologies + * but are not persisted or editable. + * "load" needs to be called after temp technologies have been added. + */ + void add_temp_tech (const lay::Technology &t); + + /** + * @brief Updates the given technology collection with the technologies from the search path and the temp technologies + */ + void rescan (lay::Technologies &technologies); + + /** + * @brief Loads the global list of technologies + */ + void load (); + + /** + * @brief Gets the default root folder + * The default root is the first one of the paths added with add_path. + */ + const std::string &default_root (); + + /** + * @brief Gets the singleton instance of the controller + */ + static TechnologyController *instance (); + +signals: + /** + * @brief This signal is emitted if the active technology has changed + */ + void active_technology_changed (); + + /** + * @brief This signal is emitted if the technology list has been edited + * This signal is emitted if either the list or one technology has been + * edited. It indicates the need for reflecting changes in the technology + * setup. + */ + void technologies_edited (); + +private slots: + /** + * @brief Called when the salt got changed + */ + void sync_with_external_sources (); + +private: + tl::stable_vector m_tech_actions; + std::string m_current_technology; + bool m_current_technology_updated; + bool m_technologies_configured; + lay::TechSetupDialog *mp_editor; + lay::MainWindow *mp_mw; + std::vector m_paths; + std::vector m_temp_tech; + lay::Technology *mp_active_technology; + + void update_active_technology (); + void connect_events (); + void technologies_changed (); + void technology_changed (lay::Technology *); + bool configure (const std::string &name, const std::string &value); + void config_finalize (); + bool menu_activated (const std::string &symbol) const; + void update_current_technology (); + void update_menu (); + void replace_technologies (const lay::Technologies &technologies); +}; + +} + +#endif + diff --git a/src/lay/layTechnologySelector.cc b/src/lay/layTechnologySelector.cc deleted file mode 100644 index e894c1f11..000000000 --- a/src/lay/layTechnologySelector.cc +++ /dev/null @@ -1,256 +0,0 @@ - -/* - - 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 "layTechnologySelector.h" -#include "layPlugin.h" -#include "laybasicConfig.h" -#include "layMainWindow.h" -#include "layMacroController.h" -#include "layTechnology.h" -#include "laybasicConfig.h" -#include "tlDeferredExecution.h" - -namespace lay -{ - -std::string tech_string_from_name (const std::string &tn) -{ - if (tn.empty ()) { - return tl::to_string (QObject::tr ("(Default)")); - } else { - return tn; - } -} - -class LAY_PUBLIC TechnologySelector - : public PluginDeclaration, - public tl::Object -{ -public: - TechnologySelector () - : PluginDeclaration () - { - m_current_technology_updated = false; - } - - void initialize (lay::PluginRoot * /*root*/) - { - // don't initialize in the -z case (no gui) - if (! lay::MainWindow::instance ()) { - return; - } - - update_menu (); - update_after_change (); - } - - void uninitialize (lay::PluginRoot * /*root*/) - { - m_tech_actions.clear (); - tl::Object::detach_from_all_events (); - } - - void get_options (std::vector < std::pair > &options) const - { - options.push_back (std::pair (cfg_initial_technology, "")); - } - - void get_menu_entries (std::vector &menu_entries) const - { - lay::PluginDeclaration::get_menu_entries (menu_entries); - menu_entries.push_back (lay::MenuEntry ("technology_selector:apply_technology", "technology_selector:tech_selector_group", "@toolbar.end", tl::to_string (QObject::tr ("Technology<:techs.png>{Select technology (click to apply)}")))); - } - -private: - tl::stable_vector m_tech_actions; - std::string m_current_technology; - std::string m_active_technology; - bool m_current_technology_updated; - - void update_after_change () - { - // re-attach all events - tl::Object::detach_from_all_events (); - - lay::MainWindow::instance ()->current_view_changed_event.add (this, &TechnologySelector::update_after_change); - lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologySelector::technology_changed); - lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologySelector::technologies_changed); - - if (lay::LayoutView::current ()) { - lay::LayoutView::current ()->active_cellview_changed_event.add (this, &TechnologySelector::update_after_change); - } - - std::string active_tech; - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview_index () >= 0 && lay::LayoutView::current ()->active_cellview_index () <= int (lay::LayoutView::current ()->cellviews ())) { - lay::LayoutView::current ()->active_cellview ()->technology_changed_event.add (this, &TechnologySelector::update_after_change); - active_tech = lay::LayoutView::current ()->active_cellview ()->tech_name (); - } - - if (m_active_technology != active_tech) { - - m_active_technology = active_tech; - - lay::MainWindow *mw = lay::MainWindow::instance (); - if (mw) { - mw->tech_message (tech_string_from_name (active_tech)); - } - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - // TODO: let the macro controller monitor the active technology - // need to do this since macros may be bound to the new technology - mc->update_menu_with_macros (); - } - - } - -#if 0 - // Hint with this implementation, the current technology follows the current layout. - // Although that's a nice way to display the current technology, it's pretty confusing - lay::PluginRoot *pr = lay::PluginRoot::instance (); - if (pr) { - pr->config_set (cfg_initial_technology, active_tech); - pr->config_finalize (); - } -#endif - } - - void technologies_changed () - { - // delay actual update of menu so we can compress multiple events - update_menu (); - } - - void technology_changed (lay::Technology *) - { - // delay actual update of menu so we can compress multiple events - update_menu (); - } - - bool configure (const std::string &name, const std::string &value) - { - if (name == cfg_initial_technology) { - - if (value != m_current_technology) { - m_current_technology = value; - m_current_technology_updated = true; - } - - } - return false; - } - - void config_finalize () - { - if (m_current_technology_updated) { - update_current_technology (); - m_current_technology_updated = false; - } - } - - bool menu_activated (const std::string &symbol) const - { - if (symbol == "technology_selector:apply_technology") { - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { - lay::LayoutView::current ()->active_cellview ()->apply_technology (m_current_technology); - } - return true; - } else { - return lay::PluginDeclaration::menu_activated (symbol); - } - } - - void update_current_technology () - { - lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); - if (! pr) { - return; - } - - std::string title = tech_string_from_name (m_current_technology); - - std::vector menu_entries = pr->menu ()->group ("tech_selector_group"); - for (std::vector::const_iterator m = menu_entries.begin (); m != menu_entries.end (); ++m) { - lay::Action action = pr->menu ()->action (*m); - action.set_title (title); - } - - size_t it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end () && it < m_tech_actions.size (); ++t, ++it) { - m_tech_actions[it].set_checked (t->name () == m_current_technology); - } - } - - void update_menu () - { - lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); - if (! pr) { - return; - } - - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { - m_current_technology = lay::LayoutView::current ()->active_cellview ()->tech_name (); - } - - std::string title = tech_string_from_name (m_current_technology); - - size_t ntech = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - ++ntech; - } - - std::vector tech_group = pr->menu ()->group ("tech_selector_group"); - - for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { - lay::Action action = pr->menu ()->action (*t); - action.set_title (title); - action.set_visible (ntech > 1); - std::vector items = pr->menu ()->items (*t); - for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { - pr->menu ()->delete_item (*i); - } - } - - m_tech_actions.clear (); - - int it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t, ++it) { - - std::string title = tech_string_from_name (t->name ()); - - m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->name ())); - m_tech_actions.back ().set_title (title); // setting the title here avoids interpretation of '(...)' etc. - m_tech_actions.back ().set_checkable (true); - m_tech_actions.back ().set_checked (t->name () == m_current_technology); - for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { - pr->menu ()->insert_item (*t + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); - } - - } - } -}; - -static tl::RegisteredClass config_decl (new TechnologySelector (), 9000, "TechnologySelector"); - -} - - diff --git a/src/lay/salt_templates/drc/doc/readme.html b/src/lay/salt_templates/drc/doc/readme.html new file mode 100644 index 000000000..919634a60 --- /dev/null +++ b/src/lay/salt_templates/drc/doc/readme.html @@ -0,0 +1,45 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop DRC scripts within +your macro package. When the package was initialized, a "drc" folder with a single +DRC script has been created. You will find this folder in the macro editor +in the DRC category under the name you have given the package. The name of the sample DRC script is "new_drc". +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/drc/drc/new_drc.lym b/src/lay/salt_templates/drc/drc/new_drc.lym new file mode 100644 index 000000000..29e11c907 --- /dev/null +++ b/src/lay/salt_templates/drc/drc/new_drc.lym @@ -0,0 +1,22 @@ + + + The New DRC Script + drc + + + false + false + + false + + + dsl + drc-dsl-xml + # This is the new DRC script created with the sample DRC package + +report("Sample DRC") + +l1 = input(1, 0) +l1.width(1.0.um).output("Width (Layer 1) < 1.0 um") + + diff --git a/src/lay/salt_templates/drc/grain.xml b/src/lay/salt_templates/drc/grain.xml new file mode 100644 index 000000000..d04b8791f --- /dev/null +++ b/src/lay/salt_templates/drc/grain.xml @@ -0,0 +1,16 @@ + + + drc + 0.0 + DRC template + This template provides a template for a DRC script + doc/readme.html + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/font/doc/readme.html b/src/lay/salt_templates/font/doc/readme.html new file mode 100644 index 000000000..e38e9765a --- /dev/null +++ b/src/lay/salt_templates/font/doc/readme.html @@ -0,0 +1,43 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Change the name of the font layout file to a unique name
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
+ +

+Edit the font's layout to define the glyphs. Don't forget to update the metadata +in the "COMMENT" cell. The metadata helps the PCell to compute properties +of the font. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/font/fonts/new_font.gds b/src/lay/salt_templates/font/fonts/new_font.gds new file mode 100644 index 000000000..c790edfbb Binary files /dev/null and b/src/lay/salt_templates/font/fonts/new_font.gds differ diff --git a/src/lay/salt_templates/font/grain.xml b/src/lay/salt_templates/font/grain.xml new file mode 100644 index 000000000..a4cb1fba0 --- /dev/null +++ b/src/lay/salt_templates/font/grain.xml @@ -0,0 +1,16 @@ + + + font + 0.0 + Font package + This template provides a font for the Basic.TEXT PCell. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/lib/doc/readme.html b/src/lay/salt_templates/lib/doc/readme.html new file mode 100644 index 000000000..fbc16f403 --- /dev/null +++ b/src/lay/salt_templates/lib/doc/readme.html @@ -0,0 +1,40 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop libraries within +your library package. When the package was initialized, a "libraries" folder with a single +sample library has been created. +In the package details you will find the local path to your package and the library +layout file under "Installation". +You can use any versioning system to manage your files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/lib/grain.xml b/src/lay/salt_templates/lib/grain.xml new file mode 100644 index 000000000..f39000459 --- /dev/null +++ b/src/lay/salt_templates/lib/grain.xml @@ -0,0 +1,16 @@ + + + lib + 0.0 + Static Library + This template provides a static library + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/lib/libraries/new_lib.gds b/src/lay/salt_templates/lib/libraries/new_lib.gds new file mode 100644 index 000000000..0b91bfa39 Binary files /dev/null and b/src/lay/salt_templates/lib/libraries/new_lib.gds differ diff --git a/src/lay/salt_templates/macro/doc/readme.html b/src/lay/salt_templates/macro/doc/readme.html new file mode 100644 index 000000000..03d1cd383 --- /dev/null +++ b/src/lay/salt_templates/macro/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop macros within +your macro package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Ruby and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/macro/grain.xml b/src/lay/salt_templates/macro/grain.xml new file mode 100644 index 000000000..6d304c519 --- /dev/null +++ b/src/lay/salt_templates/macro/grain.xml @@ -0,0 +1,16 @@ + + + macro + 0.0 + Ruby Macro + This template provides a Ruby macro. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/macro/macros/new_macro.lym b/src/lay/salt_templates/macro/macros/new_macro.lym new file mode 100644 index 000000000..2aec7a1c6 --- /dev/null +++ b/src/lay/salt_templates/macro/macros/new_macro.lym @@ -0,0 +1,23 @@ + + + The New Macro + + + false + false + + false + + + ruby + + # This is the new macro created with the sample macro package + +RBA::MessageBox::info("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", the will be loaded before this package +# and their modules and classes will be available. + + diff --git a/src/lay/salt_templates/pcell_lib/doc/readme.html b/src/lay/salt_templates/pcell_lib/doc/readme.html new file mode 100644 index 000000000..b004d843e --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop PCell macros within +your PCell package. When the package was initialized, a "macros" folder with a single +sample PCell has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "pcell". +You can add more script code, Ruby and other files there or modify the sample script. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/pcell_lib/grain.xml b/src/lay/salt_templates/pcell_lib/grain.xml new file mode 100644 index 000000000..46fc05777 --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/grain.xml @@ -0,0 +1,16 @@ + + + pcell_lib + 0.0 + PCell library + This template provides a PCell library implemented in Ruby + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/pcell_lib/macros/pcell.lym b/src/lay/salt_templates/pcell_lib/macros/pcell.lym new file mode 100644 index 000000000..8965f06c3 --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/macros/pcell.lym @@ -0,0 +1,99 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # PCell template + +# It is recommended to put PCell code into namespaces. +# TODO: change the module name +module PCellPackageModule + + include RBA + + # Remove any definition of our classes (this helps when + # reexecuting this code after a change has been applied) + # TODO: adjust the names + PCellPackageModule.constants.member?(:PCell) && remove_const(:PCell) + + # The PCell declaration + # Each PCell must provide a declaration. It is recommended to use the PCell name as the class name. + # TODO: change the class name + class PCell < PCellDeclarationHelper + + include RBA + + def initialize + + # Important: initialize the super class + super + + # declare the parameters + # i.e. param(:l, TypeLayer, "Layer", :default => LayerInfo::new(1, 0)) + # param(:s, TypeShape, "", :default => DPoint::new(0, 0)) + + end + + def display_text_impl + # Provide a descriptive text for the cell + "TODO: create description" + end + + def coerce_parameters_impl + # TODO: use x to access parameter x and set_x to modify it's value + end + + def produce_impl + # TODO: produce the cell content + # i.e. cell.shapes(l_layer).insert(Polygon.new(...)) + end + + # optional: + # def can_create_from_shape_impl + # TODO: determine if we have a shape that we can use to derive the + # PCell parameters from and return true in that case + # end + # + # optional: + # def parameters_from_shape_impl + # TODO: change parameters using set_x to reflect the parameter for the + # given shape + # end + # + # optional: + # def transformation_from_shape_impl + # TODO: return a RBA::Trans object for the initial transformation of + # the instance + # end + + end + + # TODO: add more PCell classes .. + + # Register a new library with the PCells + library = Library::new + + # TODO: change the description + library.description = "My PCell package" + + # register the PCell declarations + # TODO: change the names + library.layout.register_pcell("PCell", PCell::new) + + # TODO: register more PCell declarations ... + + # register our new library with the name "PCellPackage" + # TODO: change the library name + library.register("PCellPackage") + +end + + diff --git a/src/lay/salt_templates/pymacro/doc/readme.html b/src/lay/salt_templates/pymacro/doc/readme.html new file mode 100644 index 000000000..112d34f93 --- /dev/null +++ b/src/lay/salt_templates/pymacro/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop macros within +your macro package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Python and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/pymacro/grain.xml b/src/lay/salt_templates/pymacro/grain.xml new file mode 100644 index 000000000..b3f00281f --- /dev/null +++ b/src/lay/salt_templates/pymacro/grain.xml @@ -0,0 +1,16 @@ + + + pymacro + 0.0 + Python Macro + This template provides a Python macro. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym new file mode 100644 index 000000000..6afdbbd78 --- /dev/null +++ b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym @@ -0,0 +1,23 @@ + + + The New Macro + + + false + false + + false + + + python + + # This is the new macro created with the sample macro package + +pya.MessageBox.info("Information", "This is the new macro created with the sample macro package", pya.MessageBox.Ok) + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/python_lib/doc/readme.html b/src/lay/salt_templates/python_lib/doc/readme.html new file mode 100644 index 000000000..e50eac64e --- /dev/null +++ b/src/lay/salt_templates/python_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "pymacros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Python and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/python_lib/grain.xml b/src/lay/salt_templates/python_lib/grain.xml new file mode 100644 index 000000000..a4c917c3f --- /dev/null +++ b/src/lay/salt_templates/python_lib/grain.xml @@ -0,0 +1,16 @@ + + + python_lib + 0.0 + Python Library + This template provides a Python library. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/python_lib/pymacros/new_macro.lym b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym new file mode 100644 index 000000000..14ff7b3bb --- /dev/null +++ b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym @@ -0,0 +1,27 @@ + + + The New Macro + + + false + true + + false + + + python + + # This is the new macro created with the sample Python library package + +class NewSampleLibraryClass(object): + + def __init__(self): + # TODO: add your code here + pass + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/ruby_lib/doc/readme.html b/src/lay/salt_templates/ruby_lib/doc/readme.html new file mode 100644 index 000000000..60b4a1503 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Ruby and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/ruby_lib/grain.xml b/src/lay/salt_templates/ruby_lib/grain.xml new file mode 100644 index 000000000..b2276c062 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/grain.xml @@ -0,0 +1,16 @@ + + + ruby_lib + 0.0 + Ruby Library + This template provides a Ruby library. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/ruby_lib/macros/new_macro.lym b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym new file mode 100644 index 000000000..2c7aae58b --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym @@ -0,0 +1,29 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # This is the new macro created with the sample Ruby library package + +class NewSampleLibraryClass + + def initialize + # TODO: add your code here + end + +end + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/tech/doc/readme.html b/src/lay/salt_templates/tech/doc/readme.html new file mode 100644 index 000000000..30adefd4c --- /dev/null +++ b/src/lay/salt_templates/tech/doc/readme.html @@ -0,0 +1,50 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Complete the technology definition
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to edit the technology definition. +When the package was initialized, a folder hierarchy consisting of a tech +folder containing a technology definition file and empty "libraries", "macros", +"pymacros" and "drc" folders was created. You can use these empty folders +to put your own files there. The technology definition can be edited in the +technology manager where you can find the technology as "new_tech" under the +name you had given the package. Initially, the technology definition is +without any particular definitions. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/tech/grain.xml b/src/lay/salt_templates/tech/grain.xml new file mode 100644 index 000000000..06aa9fad4 --- /dev/null +++ b/src/lay/salt_templates/tech/grain.xml @@ -0,0 +1,16 @@ + + + tech + 0.0 + Technology + This template provides a technology + doc/readme.html + + GPLv3 + + + + + iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAF/wAABf8ByXatVgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABPTSURBVHic5Vt5bFzHef/NzLv2vT0e9+Byl4dOkpYVUWds2ZEPuZJdF5ERAxacFHECFEWL1v4jQoKgaBuUBtoYRZGmSFIUSZOmjZOmRZqjtlMrVizZchJHjmXJFEVKlsWQtEiJ4qE9375zpn/s4ZVMypK1tIF2gMG+fTv75vf95vu++eabecD/80Le7w6//OUvm6ZppkKhUEKW5TCAihBivlAozE9OTs4PDg7y9xPPshOwb9+++Nq1az/e3d39UVVVb1VVNc4YA+ccuq6jXC5DVVVwzuF5XslxnKOu6+6fmJj4/mOPPTax3PiWjYDBwUEaBMGf9fb2/nkqlTJ0XYeiKCiVSohGoxgaGsL999+Pw4cPo7e3F9PT01BVFfF4HJVKBZZl+bOzs/88Njb2ucHBQWu5cErL9eDp6enP9vf3/41pmpBlGYqiQAiBmZkZrFu3Dv39/dB1HQ899BAsy4KmaQiCAIQQBEEAz/Mk0zT/JBKJGAA+vVw4l40A3/fvKRaLKBQKUBQFrutC0zRMTExgdnYWnHNYllVvi0uXLuHo0aPYsWMHXNeF7/soFArI5/P3LBdGYBkJiEajJBaLYcWKFTh//jw8z4PrutixYwcqlQp+9KMfYcWKFYjFYnjppZewa9cubNmyBblcDowxzM/P49SpU9A0TSwXRmAZCahUKvbq1atx8OBB6LqOqakptLe3Ix6PIxqNYuXKlWCMoVwuY+PGjZiamkImk0E4HMbIyAhOnTqF1atXo1QqLZv9A8urAeqJEyfwwAMPoFKp4ODBg5ifn8fU1BR83wfnb892hBAwxhAKhbB69Wps2LAB3d3dOHbsGEKhUGi5MALLNAs8+uijCVmWj95///0rLl68iIMHD8IwDPT29qKrqwuO4zRIqAuvaRrS6TROnTqFY8eOIRaLYePGjRgbG+NdXV1fe/HFFx//1re+tdBqrC0n4Atf+ML6devWPTcyMpKNRqPQNA07duzA0NAQLly4gImJCczMzKBSqaBSqUDTNCiKglQqhdtuuw1bt24FpRTDw8M4e/YsIpEIXNfFqlWrpo4cOXLvl770pZFW4m25CWQymW8WCoVsPp9HR0cHdu/ejWeeeQZHjhwBpRSJRAIDAwMwTROKoiAIAliWhYWFBQwPD+OFF15AX18fNmzYgFQqhRdffBGrVq1CoVDo7Orq+iaA21uJt+UEuK67oqOjA5lMBkePHsXTTz+NzZs3495770U4HIZlWSiXyyiXy5ifn4eu69A0Db29vVi/fj2i0SgOHz6Mb3/72yCEIJPJwDRNxGIxnD59urPVeFtOwPDw8E91Xf/Dvr4+mKaJc+fOYXp6GsePH8fFixfheV4jMKKUQggBz/PgeR4AIB6PI5FIoL29HZ2dnejp6YGiKDh06BAcx/mZEEKudVU3X04I8d8r3pb7gNHR0e8dOHDgwVdffVVdu3Yt7enpQSwWw9zcHHK5HAqFAsrlcsMRAoAkSdA0DYZhwDRNbNq0CWvWrEGlUsHc3BwsyxIrV64MOjs73wqFQkMAwgAMAKoQ4gVK6eDjjz9eei8LqZYSMDg4SB9++OEziUQiUi6XU9/97ndx6NAhzM/PC845iUajSKVSiMViMAwDslwdTNd14XkeJEnCwMAA9uzZg2g0irm5ORQKBTiOAyEEGGMCgC9JEsLhMI/FYjyZTLpCiGNCiL94/fXXf7Nt2zbvAyPgmWee2bt9+/Z/SiQSCQAolUrI5/NwXTf3yiuvmCdPnsTU1BRmZmZQKpXgui4AQFEUxGIx7NixA4888ggmJydRKBQAAIwxBEGAIAgghICqqgiFQpAkCYQQzMzMCEmSvF27dp0B8F+U0i8SQtxrxdxSH8A5752bm3Pa2tpAKUU4HIZhGJbrus7u3buxc+dOBEEA3/cbn47jAAA0TUM2m8WFCxdQLBZx4sQJcM4Rj8eRTCahqioYY7Btu2FCtf8S3/eV1157bc3WrVsfBPB3AD4YAgzDWJVMJrPnzp072d3dvZ4QYhFCiqqqplVVfUd73/eRy+WgKEpjVBVFwcTEBDZs2ADLslCpVBqawhiD7/uglMI0TYTDYdi2jUOHDmHDhg0ghFQAXJcJtJQAXdc7hRBoa2tbXywWTxNCYrqudzDGFu9ckhCNRhvfhRDo7OzELbfcgueffx6MMXR0dDRiBiFEI3YolUoYHx/HwsIC7rzzTm/z5s2BEKJECLmuxVNLCVBVNSuEACEEtm0bnPN0sVgsAFhgjBUJIYEsyypjLGEYRnudmNpUWJAkiSiKoq9fv54Ui0Xa0dEBy7Jg23ZjNel5HlRVRVdXFwYGBuA4jjBN0wcgAJSvF3NLCZAkKcE5B6UUnHOXEAJFUSKMsQilFIyxxtxfLBbztm0v+L4PRVHijLEYAESj0YaDC4fDSKVSCIVC8H0flmU1pk5CCCRJQhAEjf4JIaXrxtwq4QEQxliUEAJCCIQQXv26Bu7tTqu2HpVlOVoul4ksy+Cc1/OCkKT3BotzXqaUXpcJ0PfU0yJlcHAwo+u6UReac+4vJvxi95rLUvevoXywJpBKpQbC4TBr0oAAuLrwzRpSL5Te0Jhcd/KkZRoQDoe76rZbI0C8m/DNv9XrDRJgo6oJ11xaRoBhGGkADSGEqOJYzA8speaSJN0IAYJS+sGZgK7rPcDiAjdfL6X+9ZRYnYA6gUKId9R6qV2TenqNc165XgJbRkAkEunmnIMxVjeBRvTzbuqvqipkWW6EuiMjI+8Q9ir94uWXX9bS6bScTqej2Wz2unC3zAQURcnWY4CagBS4uvCUUkQikcamyPHjxzE6OgrDMJBOp6EoymX+4UrTqe8tqKpKwuGwdPbs2b989tlnX37qqadWXSvulhEgy3J7PclZAyq/28jXIj9MTExgeHgYK1euRH9/P7q7u5HP5/HWW281gh/GGFRVha7rkGUZrutibGwMo6OjSCQS4Jyjq6sr3NfX92HG2K9/8IMf/P214G6JCezbty+kaVpbPQwGACGEspTwNTsljDEMDQ0hFAph3bp1l7Vbt24dOOcoFAo4c+YMbNsG5xxCCMiyjGg0ivb2dpimeVk+YcWKFUgkEu0jIyN//OSTT97yyU9+8k5CyJKJkpYQkE6n+2KxmHaFA1SWGnkhBKGU4tixY0gmk0gkEosSxRiDaZowTfMyp+j7fmNtUPcVruvCcRycP38eXV1duPXWW3XTNLd//etfPy6E2LQUCS0hIJFI3NS8uVnzBWqzQE3zfGPkk8kkkskkrmz3bqUeKteFbw6jLcvC1NQUstksbrrpJuY4zs1f+cpXXgLwkcWe1RIfEIlEOpoDmSAIQCkNXSlU3UGOj49DVVUkk8l3OLlrrZIkXVbrs08QBHAcBzMzMyCE4Oabb2Z9fX2bnnjiia8uGwGapnU0gwuCIJAkSbtSeMYY8X0fs7Oz6OnpWUw7rip0c9s6CYyxy2pdG1zXxczMDBRFwZYtW3Rd1z++b9++tctCAGOsEQUSQuA4TrlpOqxvfxFKKUZHR7FmzZrGf68m4FIkNMDXltj1WluGN6rjOLh48SIURcF9992X1HX9e8tCgKZpWSFEYwSDILCbs0D1kfJ9H77vIxQKXZe6L0VMnYR6v/Xr+mwBALZtI5/PQ9M0JBKJNY899thlMUJLCAiFQl3NMQDnvJGUrDs+QgjOnj2Lq0Vq1zryVzz7HeazVBS5c+fOhBDiH5rvtYIAoqpqIwhqzgZd6QTL5TJisdi7P/AqQi/VvvlzqdLW1gZZlvub790wAYODg5lIJNIIgmoa0MjMNoO6ltj+vZTmGOHdiqZpibvvvlurf7/hOCCVSg3EYjGpOQoE4AOXq6ht2++a6grgw0YZJeRRQA45LKBICyiKAmxU4MODEAISZChChS4MhFkEIRjQiA5JKBAeAYW8pDZ0dXUZiqLcAeBASwiIRCKdsiw38vUAIITgdQB171woFCDLcmOUOALk6RzO0XGcoaM4Tl7Fq+QI8kEOiidDCWRIgQQpkEA5BRUURNTCbCIgCEdAOXwawGc+vIgHt82DQlRscrdiwN+MNd5NyLrdMNw2UF51yplMJsQY29ISAoQQ5MCBA58ol8sNz14nAGioPwGq3pgQgktsBmfYCI6wX+BZ+hQqXhkhK4SQq8H0wuhwE1A9FaqvQCIyGGGghIGCgtR28gQEpovTKFQKSKpJqCEZUAFHdmDLDsaVNzCqDKFiVCBMgt3O/fiwczvWODchEo2Ac95Rl+FGNUANh8ObXdeFYRjviPmBt3N8jDG8qQzjNelX+KH0fYRtA2HbQNzJwLB1GI4OlWoApyCgMJU2KFSFRGVIRAIFAyHVZx1+6yBeOf8yVhpr8Ufdn8dfj3we96y9B8LjcBwbLrdRVq1q1Sy8oD6HZ6I/xn3uR9ET7kUggnxLCHj66acz/f39JqUUjuMgEolc9nszIZqm4Vfzh7Cf/gQxK4pIJYyIbSBsh6ELHZoUwrHp4zg9fwqfXvWneGP+FDZlNqFD66oSUdMGANiRBngAXHLm8bWxL2JjejN8T+D58edwe3Inznuz6El2QfVUKH7VlBhn+Hnof5At9yBvVRpHcG+IAM7578fjcWpZFoIggG3bUBQFnHPWZA4AgEQigWBUQAokyPXqy5ADGRKTIREFa9v6EFVMDFuvIhPuRFLrgCFFoFEdIapDYzoICMx4Av3mh2BzC5XAgs0tlIMC7uq+B+WghNWRVSAE8Llc7cOv9if5MuZ+uyCcgvbTlhAQj8fvIYTUhUa5XIamaaCUSkBj6Ss454RSimQpDVfyatWFI7tQAgcsqNp5woijM9IFlWrQaAgeHBT9HGxioURk0JoGcBHAFz484cDlDhxuw+UO2sMp2DwMm1dgBxZc6sCRXbiS2+jT+K3hDQ0NXWwJAeFwuHd+fh7xeLxx7i+fzwNAqjkqq2vDyqAPHxrrxZs9k1gI5+FKHhzZgWHbMBwbdqBBpWUoVIVMFMi07girPoA2aVWAAIEI4AsPHnfhCRdujQiH2Chr1mV+wJEdrBnvxPQZ+3yzDDdEgGVZ/1ksFj+XTqcb8XdtKZxwHOeyRYosy9h5y24cf/YFyDMCfgdwvn0B5+LnEXK1avU0qK4KzVeheDJktyo8IwzkillAgIOLKgke8+BILhzVhS07sBUblVqN2Dp6zqWhTAtM718Q8yPuZ1tGwHe+852v7t279zNBEEj17Wvf931N0yTOOYIgaByGlCSpmgCx1+DFof8APUmRSnSgJ7kSNK6hYrq4FCvgXOI8KKcNu2VcAlskDuC1OCCoxwGsalodxRQS+Si0hTj4nIPCxTmMzx4D9304r3UunDk5+cOWEfCNb3xjcuvWrS+YprlrYGAAnHPYtu0yxiSgegBCCAHHcaCqKiRJwsd27cXp/34FQ+p+zMxOY3ZhBpQxUEZBKUOnZiAajkEN6ZA0BVRhgEQABoDUtn0EQHwB4XFwN4BXcWFbZRRLOZStaUwFb4HXjtXwwAf3A/hvxETpgv9XV8pww5Hg/v37n+zt7b27WCxKkUgEQgheX5I2b1LUfcSqVauwzdyN8fxR5NiFqvCUgjIKQhl8z0O5nAchFIQSgJCG6leLgBD1dBiH4AKCc4hGHiAADzh4EIAHAQTnCMoEldf1M789PvWPV+Jf/OjGdZSOjo7TkUhkl2EY3ZlMBpVKxVNVVWleHNVTVbXECDYNbMHwwXGMk6MIuFcFH3CIGuhG9QMEfnUUA79WPR+B74H71c/6d7/xe/U3HgSAEBABUHwuUXrzlzN9WOTs0A0TMD4+7kejUWvbtm0fMwyDUkq5qqqNafBKEoBqVPjhgdvwxqELmNaGEXC/obIiCBA0jWDg+zUiriDBqwvfJHjtJFm9CA4Un0vYCyOV7XbZO7cY/hsmAABGRkZO3nHHHR8BsKa9vZ3IstzYFVqMBM45DMPALQO34+SBc1gIjSGg1RW0EE0qfaVGNNdaG1G1h3dgCsoEuZ+Z5fwp/+6FmdKxpbC3hAAAiEQib27fvv3TbW1tTFVVslSis75zLISAruu4766P4s2fz8MOyigpsy3B4r0VEgsH9MnJX+dWVkrOVd88axkBJ06cOPfAAw9skyTppsnJSVQqlcZ7AEuluepbXr+76/cgzbbh/PEiXK0IT35vL4n4OYbiIdO6dJT96/nh3C5cw5G5lhEAAJTSVyKRyJ5EIjEVDoedQqGgjo2NSVNTU7AsC4SQeqjciBIdx4EkSVh/84fwsd0Po3hCRm40ACoS3FABYFc//stdAv+NGPzXE+7CL5SnJl+dv608b//4WjG3/LD0nj17PhGLxR7JZDJ3ZrNZo7Ozc76/v38iFouZxWIxk8/nQ5xztLW1IZlMigsXLpBisQhKKZLJJDKZjDAMwx8eHqb/9u//Qt+cGYHFc8QOLPjERsADwJME5RIUEULa6BGP/sFn7LvuuutvDx8+/MWdO3de18nxZXtxcu/evYplWbfKsvw7mUxmVzabvSWbzcqdnZ3zq1evPmcYhmnbtilJUpBOp+ds2760sLCQyOfzqy3LoqlUiicSiYqu60KWZUop5QCaj90QABBCVACc5pw/wRjbT66yEbpYed/eHd6zZ49OKb09FovtymQyuzKZzOZsNks3btyIXC6HXC6Xz+Vyv8nlcr+sVCqvtbW1/fJTn/qUA6DT9/12QsgKQkgngBSqx+V1AJ4Q4ieMsZ8DcK5XeOADeHm6Xh588MF2APc89NBDci6Xe/nRRx99c7F2QgiCt3FeeS1QfWHifX3h+v9U+V+qL5hjVn7twwAAAABJRU5ErkJggg== + + diff --git a/src/lay/salt_templates/tech/tech/tech.lyt b/src/lay/salt_templates/tech/tech/tech.lyt new file mode 100644 index 000000000..ac78bda13 --- /dev/null +++ b/src/lay/salt_templates/tech/tech/tech.lyt @@ -0,0 +1,9 @@ + + + new_tech + + 0.001 + + + true + diff --git a/src/laybasic/layAbstractMenu.cc b/src/laybasic/layAbstractMenu.cc index fac446f05..6e5fd7c32 100644 --- a/src/laybasic/layAbstractMenu.cc +++ b/src/laybasic/layAbstractMenu.cc @@ -635,7 +635,6 @@ ConfigureAction::triggered () } m_pr->config_set (m_cname, m_cvalue); - m_pr->config_end (); } void @@ -874,7 +873,6 @@ AbstractMenu::build (QToolBar *t, std::list &items) } } - } QMenu * @@ -1123,14 +1121,17 @@ AbstractMenu::find_item (const std::string &path) return std::make_pair ((AbstractMenuItem *) 0, m_root.children.end ()); } - } else if (extr.test ("begin")) { - return std::make_pair (parent, parent->children.begin ()); - } else if (extr.test ("end")) { - return std::make_pair (parent, parent->children.end ()); } else { std::string n; extr.read (n, ".+"); + + if (n == "begin") { + return std::make_pair (parent, parent->children.begin ()); + } else if (n == "end") { + return std::make_pair (parent, parent->children.end ()); + } + std::string name (parent->name ()); if (! name.empty ()) { name += "."; diff --git a/src/laybasic/layCellView.cc b/src/laybasic/layCellView.cc index 86e4bd46d..0736fdb6f 100644 --- a/src/laybasic/layCellView.cc +++ b/src/laybasic/layCellView.cc @@ -217,7 +217,11 @@ void LayoutHandle::set_tech_name (const std::string &tn) { if (tn != m_tech_name) { - m_tech_name = tn; + if (lay::Technologies::instance ()->has_technology (tn)) { + m_tech_name = tn; + } else { + m_tech_name = std::string (); + } technology_changed_event (); } } diff --git a/src/laybasic/layLayoutView.cc b/src/laybasic/layLayoutView.cc index 06c674f3c..afeeaef13 100644 --- a/src/laybasic/layLayoutView.cc +++ b/src/laybasic/layLayoutView.cc @@ -3028,10 +3028,11 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption try { + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading"))); + // load the file { tl::log << tl::to_string (QObject::tr ("Loading file: ")) << filename << tl::to_string (QObject::tr (" with technology: ")) << technology; - tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading"))); lmap = cv->load (options, technology); } @@ -3039,7 +3040,6 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption // implicitly at some other time. This may throw an exception // if the operation was cancelled. { - tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Sorting"))); cv->layout ().update (); } @@ -3124,6 +3124,9 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption } update_content (); + // this event may not be generated otherwise: + active_cellview_changed (cv_index); + return cv_index; } diff --git a/src/laybasic/layPlugin.cc b/src/laybasic/layPlugin.cc index 76bdebaaa..15660e123 100644 --- a/src/laybasic/layPlugin.cc +++ b/src/laybasic/layPlugin.cc @@ -196,7 +196,7 @@ PluginDeclaration::init_menu () m_mouse_mode_action.qaction ()->setData (QVariant (id ())); menu.insert_item ("edit_menu.mode_menu.end", name, m_mouse_mode_action); - menu.insert_item ("@toolbar.end", name, m_mouse_mode_action); + menu.insert_item ("@toolbar.end_modes", name, m_mouse_mode_action); gtf::action_connect (m_mouse_mode_action.qaction (), SIGNAL (triggered ()), this, SLOT (mode_triggered ())); diff --git a/src/laybasic/layQtTools.cc b/src/laybasic/layQtTools.cc index 6d395e8e9..0ed979770 100644 --- a/src/laybasic/layQtTools.cc +++ b/src/laybasic/layQtTools.cc @@ -103,6 +103,10 @@ save_dialog_state (QWidget *w) void restore_dialog_state (QWidget *dialog, const std::string &s) { + if (! dialog) { + return; + } + tl::Extractor ex (s.c_str ()); while (! ex.at_end ()) { diff --git a/src/laybasic/layTechnology.cc b/src/laybasic/layTechnology.cc index 9750e2995..9e4c15bcb 100644 --- a/src/laybasic/layTechnology.cc +++ b/src/laybasic/layTechnology.cc @@ -38,6 +38,8 @@ namespace lay Technologies::Technologies () { m_technologies.push_back (new Technology (std::string (""), "(Default)")); + m_changed = false; + m_in_update = false; } Technologies::Technologies (const Technologies &other) @@ -128,7 +130,7 @@ Technologies::add (Technology *technology) technology->technology_changed_with_sender_event.add (this, &Technologies::technology_changed); } - technologies_changed_event (); + technologies_changed (); } void @@ -137,12 +139,58 @@ Technologies::remove (const std::string &name) for (tl::stable_vector::iterator t = m_technologies.begin (); t != m_technologies.end (); ++t) { if (t->name () == name) { m_technologies.erase (t); - technologies_changed_event (); + technologies_changed (); break; } } } +void +Technologies::clear () +{ + if (! m_technologies.empty ()) { + m_technologies.clear (); + technologies_changed (); + } +} + +void +Technologies::technologies_changed () +{ + if (m_in_update) { + m_changed = true; + } else { + technologies_changed_event (); + } +} + +void +Technologies::begin_updates () +{ + tl_assert (! m_in_update); + m_in_update = true; + m_changed = false; +} + +void +Technologies::end_updates () +{ + if (m_in_update) { + m_in_update = false; + if (m_changed) { + m_changed = false; + technologies_changed (); + } + } +} + +void +Technologies::end_updates_no_event () +{ + m_in_update = false; + m_changed = false; +} + bool Technologies::has_technology (const std::string &name) const { @@ -171,13 +219,13 @@ Technologies::technology_by_name (const std::string &name) // Technology implementation Technology::Technology () - : m_name (), m_description (), m_dbu (0.001), m_persisted (true) + : m_name (), m_description (), m_dbu (0.001), m_persisted (true), m_readonly (false) { init (); } Technology::Technology (const std::string &name, const std::string &description) - : m_name (name), m_description (description), m_dbu (0.001), m_persisted (true) + : m_name (name), m_description (description), m_dbu (0.001), m_persisted (true), m_readonly (false) { init (); } @@ -204,11 +252,12 @@ Technology::~Technology () Technology::Technology (const Technology &d) : tl::Object (), - m_name (d.m_name), m_description (d.m_description), m_dbu (d.m_dbu), + m_name (d.m_name), m_description (d.m_description), m_grain_name (d.m_grain_name), m_dbu (d.m_dbu), m_explicit_base_path (d.m_explicit_base_path), m_default_base_path (d.m_default_base_path), m_load_layout_options (d.m_load_layout_options), m_save_layout_options (d.m_save_layout_options), - m_lyp_path (d.m_lyp_path), m_add_other_layers (d.m_add_other_layers), m_persisted (d.m_persisted) + m_lyp_path (d.m_lyp_path), m_add_other_layers (d.m_add_other_layers), m_persisted (d.m_persisted), + m_readonly (d.m_readonly), m_lyt_file (d.m_lyt_file) { for (std::vector ::const_iterator c = d.m_components.begin (); c != d.m_components.end (); ++c) { m_components.push_back ((*c)->clone ()); @@ -221,6 +270,7 @@ Technology &Technology::operator= (const Technology &d) m_name = d.m_name; m_description = d.m_description; + m_grain_name = d.m_grain_name; m_dbu = d.m_dbu; m_default_base_path = d.m_default_base_path; m_explicit_base_path = d.m_explicit_base_path; @@ -229,6 +279,8 @@ Technology &Technology::operator= (const Technology &d) m_lyp_path = d.m_lyp_path; m_add_other_layers = d.m_add_other_layers; m_persisted = d.m_persisted; + m_readonly = d.m_readonly; + m_lyt_file = d.m_lyt_file; for (std::vector ::const_iterator c = m_components.begin (); c != m_components.end (); ++c) { delete *c; @@ -350,7 +402,10 @@ Technology::load (const std::string &fn) xml_struct.parse (source, *this); // use the tech file's path as the default base path - set_default_base_path (tl::to_string (QFileInfo (tl::to_qstring (fn)).absoluteDir ().path ())); + std::string lyt_file = tl::to_string (QFileInfo (tl::to_qstring (fn)).absoluteDir ().path ()); + set_default_base_path (lyt_file); + + set_tech_file_path (fn); } void diff --git a/src/laybasic/layTechnology.h b/src/laybasic/layTechnology.h index 82f4fd028..ff28f3eaf 100644 --- a/src/laybasic/layTechnology.h +++ b/src/laybasic/layTechnology.h @@ -133,6 +133,30 @@ public: */ void remove (const std::string &name); + /** + * @brief Clears the list of technologies + */ + void clear (); + + /** + * @brief Begins a bulk operation + * This method will disable "technologies_changed" events until (later) end_updates () is called. + */ + void begin_updates (); + + /** + * @brief Ends a bulk operation + */ + void end_updates (); + + /** + * @brief Ends a bulk operation + * This version does not send an technologies_changed event but just cancels the bulk + * operation. begin_updates/end_updates_no_event is essentially equivalent to blocking + * signals. + */ + void end_updates_no_event (); + /** * @brief Checks, if a technology with the given name exists */ @@ -191,8 +215,15 @@ protected: technology_changed_event (t); } + /** + * @brief Sends the technologies_changed event + */ + void technologies_changed (); + private: tl::stable_vector m_technologies; + bool m_changed; + bool m_in_update; }; /** @@ -249,6 +280,24 @@ public: } } + /** + * @brief Sets the package source + * + * This attribute indicates that this technology was contributed by a package + */ + void set_grain_name (const std::string &g) + { + m_grain_name = g; + } + + /** + * @brief Gets the package source + */ + const std::string &grain_name () const + { + return m_grain_name; + } + /** * @brief Gets the base path * @@ -308,6 +357,23 @@ public: } } + /** + * @brief Gets the path of the tech file if the technology was loaded from a tech file + */ + const std::string &tech_file_path () const + { + return m_lyt_file; + } + + /** + * @brief Sets the path of the tech file + * This method is intended for internal use only. + */ + void set_tech_file_path (const std::string &lyt_file) + { + m_lyt_file = lyt_file; + } + /** * @brief Gets the description */ @@ -497,6 +563,24 @@ public: m_persisted = f; } + /** + * @brief Returns a flag indicating whether the technology is readonly + * + * If the flag is false, the technology can be edited. Otherwise it's locked for editing. + */ + bool is_readonly () const + { + return m_readonly; + } + + /** + * @brief Sets a flag indicating whether the technology is readonly + */ + void set_readonly (bool f) + { + m_readonly = f; + } + /** * @brief An event indicating that the technology has changed */ @@ -509,14 +593,18 @@ public: private: std::string m_name, m_description; + std::string m_grain_name; double m_dbu; std::string m_explicit_base_path, m_default_base_path; db::LoadLayoutOptions m_load_layout_options; db::SaveLayoutOptions m_save_layout_options; std::string m_lyp_path; + std::string m_lyt_path; bool m_add_other_layers; std::vector m_components; bool m_persisted; + bool m_readonly; + std::string m_lyt_file; void init (); diff --git a/src/laybasic/layWidgets.cc b/src/laybasic/layWidgets.cc index 4b255e8a1..b1e7b2136 100644 --- a/src/laybasic/layWidgets.cc +++ b/src/laybasic/layWidgets.cc @@ -230,7 +230,8 @@ struct CellViewSelectionComboBoxPrivateData const lay::LayoutView *layout_view; }; -CellViewSelectionComboBox::CellViewSelectionComboBox (QWidget * /*parent*/) +CellViewSelectionComboBox::CellViewSelectionComboBox (QWidget *parent) + : QComboBox (parent) { mp_private = new CellViewSelectionComboBoxPrivateData (); mp_private->layout_view = 0; @@ -299,7 +300,8 @@ struct LayerSelectionComboBoxPrivateData int cv_index; }; -LayerSelectionComboBox::LayerSelectionComboBox (QWidget * /*parent*/) +LayerSelectionComboBox::LayerSelectionComboBox (QWidget *parent) + : QComboBox (parent) { mp_private = new LayerSelectionComboBoxPrivateData (); mp_private->no_layer_available = false; @@ -569,11 +571,22 @@ LayerSelectionComboBox::current_layer_props () const // ------------------------------------------------------------- // LibrarySelectionComboBox implementation -LibrarySelectionComboBox::LibrarySelectionComboBox (QWidget * /*parent*/) +LibrarySelectionComboBox::LibrarySelectionComboBox (QWidget *parent) + : QComboBox (parent), m_tech_set (false) { update_list (); } +void +LibrarySelectionComboBox::set_technology_filter (const std::string &tech, bool enabled) +{ + if (m_tech != tech || m_tech_set != enabled) { + m_tech = tech; + m_tech_set = enabled; + update_list (); + } +} + void LibrarySelectionComboBox::update_list () { @@ -585,12 +598,23 @@ LibrarySelectionComboBox::update_list () addItem (QObject::tr ("Local (no library)"), QVariant ()); for (db::LibraryManager::iterator l = db::LibraryManager::instance ().begin (); l != db::LibraryManager::instance ().end (); ++l) { + db::Library *lib = db::LibraryManager::instance ().lib (l->second); - if (! lib->get_description ().empty ()) { - addItem (tl::to_qstring (lib->get_name () + " - " + lib->get_description ()), QVariant ((unsigned int) lib->get_id ())); - } else { - addItem (tl::to_qstring (lib->get_name ()), QVariant ((unsigned int) lib->get_id ())); + if (! m_tech_set || lib->get_technology ().empty () || m_tech == lib->get_technology ()) { + + std::string item_text = lib->get_name (); + if (! lib->get_description ().empty ()) { + item_text += " - " + lib->get_description (); + } + if (m_tech_set && !lib->get_technology ().empty ()) { + item_text += " "; + item_text += tl::to_string (QObject::tr ("[Technology %1]").arg (tl::to_qstring (lib->get_technology ()))); + } + + addItem (tl::to_qstring (item_text), QVariant ((unsigned int) lib->get_id ())); + } + } set_current_library (lib); diff --git a/src/laybasic/layWidgets.h b/src/laybasic/layWidgets.h index 001a70a57..08019f82b 100644 --- a/src/laybasic/layWidgets.h +++ b/src/laybasic/layWidgets.h @@ -147,6 +147,19 @@ public: * @brief Update the list of libraries */ void update_list (); + + /** + * @brief Sets the technology filter + * + * If a technology filter is set, only the libraries associated with the given + * technology are shown. If enable is false, the technology name is ignored and + * all libraries are shown. + */ + void set_technology_filter (const std::string &tech, bool enable); + +private: + std::string m_tech; + bool m_tech_set; }; /** diff --git a/src/laybasic/rdbMarkerBrowserPage.cc b/src/laybasic/rdbMarkerBrowserPage.cc index e31087072..f14e57338 100644 --- a/src/laybasic/rdbMarkerBrowserPage.cc +++ b/src/laybasic/rdbMarkerBrowserPage.cc @@ -1820,24 +1820,6 @@ MarkerBrowserPage::set_max_marker_count (size_t max_marker_count) } } -static void -escape_to_html (std::string &out, const std::string &in) -{ - for (const char *cp = in.c_str (); *cp; ++cp) { - if (*cp == '<') { - out += "<"; - } else if (*cp == '>') { - out += ">"; - } else if (*cp == '&') { - out += "&"; - } else if (*cp == '\n') { - out += "
"; - } else { - out += *cp; - } - } -} - void MarkerBrowserPage::enable_updates (bool f) { @@ -1955,13 +1937,13 @@ MarkerBrowserPage::update_info_text () if (category && n_category == 1 && ! category->description ().empty ()) { info += "

"; - escape_to_html (info, category->description ()); + tl::escape_to_html (info, category->description ()); info += "

"; } if (! m_error_text.empty ()) { info += "

"; - escape_to_html (info, m_error_text); + tl::escape_to_html (info, m_error_text); info += "

"; } @@ -1978,7 +1960,7 @@ MarkerBrowserPage::update_info_text () if (v->tag_id () != 0) { const rdb::Tag &tag = mp_database->tags ().tag (v->tag_id ()); info += ""; - escape_to_html (info, tag.name ()); + tl::escape_to_html (info, tag.name ()); info += ":
"; } @@ -1989,7 +1971,7 @@ MarkerBrowserPage::update_info_text () value_string = std::string (value_string.begin (), value_string.begin () + max_length) + "..."; } - escape_to_html (info, value_string); + tl::escape_to_html (info, value_string); info += "
"; diff --git a/src/lib/libBasicText.h b/src/lib/libBasicText.h index b0ef3dfa0..8f36f4662 100644 --- a/src/lib/libBasicText.h +++ b/src/lib/libBasicText.h @@ -83,6 +83,17 @@ public: */ virtual std::vector get_parameter_declarations () const; +protected: + /** + * @brief Returns a value indicating that this PCell wants to update it's parameter declarations dynamically + * + * This is be required because the fonts can be updated dynamically when new packages are installed. + */ + virtual bool wants_parameter_declaration_caching () const + { + return false; + } + public: int get_font_index (const db::pcell_parameters_type ¶meters) const; }; diff --git a/src/lib/libResources.qrc b/src/lib/libResources.qrc index d98df72d0..03585ec03 100644 --- a/src/lib/libResources.qrc +++ b/src/lib/libResources.qrc @@ -1,5 +1,2 @@ - - std_font.gds - diff --git a/src/tl/tl.pro b/src/tl/tl.pro index c59dc2ec9..8018eb796 100644 --- a/src/tl/tl.pro +++ b/src/tl/tl.pro @@ -40,7 +40,9 @@ SOURCES = \ tlVariant.cc \ tlXMLParser.cc \ tlXMLWriter.cc \ - tlFileSystemWatcher.cc + tlFileSystemWatcher.cc \ + tlFileUtils.cc \ + tlWebDAV.cc HEADERS = \ tlAlgorithm.h \ @@ -84,7 +86,9 @@ HEADERS = \ tlFileSystemWatcher.h \ tlCommon.h \ tlMath.h \ - tlCpp.h + tlCpp.h \ + tlFileUtils.h \ + tlWebDAV.h INCLUDEPATH = DEPENDPATH = diff --git a/src/tl/tlClassRegistry.h b/src/tl/tlClassRegistry.h index b03eebfe0..059d76200 100644 --- a/src/tl/tlClassRegistry.h +++ b/src/tl/tlClassRegistry.h @@ -163,6 +163,11 @@ public: return mp_pos->m_name; } + int current_position () const + { + return mp_pos->m_position; + } + X &operator* () const { return *(mp_pos->mp_object); diff --git a/src/tl/tlDeferredExecution.h b/src/tl/tlDeferredExecution.h index 2a8c9537f..7e1136cee 100644 --- a/src/tl/tlDeferredExecution.h +++ b/src/tl/tlDeferredExecution.h @@ -140,6 +140,40 @@ private: void do_execute (); }; +/** + * @brief A protected region that ensures that deferred methods are not executed + * + * This class employs the RAII pattern to block a region of code for execution of + * deferred methods. This is useful to protect message boxes against having a side + * effects of issuing deferred method calls: + * + * @code + * { + * tl::NoDeferredMethods block; + * QMessageBox::warning (...); + * } + * @endcode + */ +class TL_PUBLIC NoDeferredMethods +{ +public: + /** + * @brief Constructor + */ + NoDeferredMethods () + { + DeferredMethodScheduler::enable (false); + } + + /** + * @brief Destructor + */ + ~NoDeferredMethods () + { + DeferredMethodScheduler::enable (true); + } +}; + /** * @brief Deferred execution of a const method * diff --git a/src/tl/tlFileSystemWatcher.cc b/src/tl/tlFileSystemWatcher.cc index e415b1473..9a54a86f4 100644 --- a/src/tl/tlFileSystemWatcher.cc +++ b/src/tl/tlFileSystemWatcher.cc @@ -82,11 +82,13 @@ FileSystemWatcher::add_file (const std::string &path) QDateTime time; QFileInfo fi (tl::to_qstring (path)); - if (fi.exists ()) { - size = size_t (fi.size ()); - time = fi.lastModified (); + if (! fi.exists () || ! fi.isReadable ()) { + return; } + size = size_t (fi.size ()); + time = fi.lastModified (); + std::map::iterator i = m_files.find (path); if (i != m_files.end ()) { i->second.refcount += 1; diff --git a/src/tl/tlFileUtils.cc b/src/tl/tlFileUtils.cc new file mode 100644 index 000000000..9ae545118 --- /dev/null +++ b/src/tl/tlFileUtils.cc @@ -0,0 +1,140 @@ + +/* + + 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 "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" + +#include +#include + +namespace tl +{ + +bool +is_parent_path (const QString &parent, const QString &path) +{ + QFileInfo parent_info (parent); + QFileInfo path_info (path); + + while (parent_info != path_info) { + path_info = path_info.path (); + if (path_info.isRoot ()) { + return false; + } + } + + return true; +} + +bool +rm_dir_recursive (const QString &path) +{ + QDir dir (path); + + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + QFileInfo fi (dir.absoluteFilePath (*e)); + if (fi.isDir ()) { + if (! rm_dir_recursive (fi.filePath ())) { + return false; + } + } else if (fi.isFile ()) { + if (! dir.remove (*e)) { + tl::error << QObject::tr ("Unable to remove file: %1").arg (dir.absoluteFilePath (*e)); + return false; + } + } + } + + QString name = dir.dirName (); + if (dir.cdUp ()) { + if (! dir.rmdir (name)) { + tl::error << QObject::tr ("Unable to remove directory: %1").arg (dir.absoluteFilePath (name)); + return false; + } + } + + return true; +} + +bool +cp_dir_recursive (const QString &source, const QString &target) +{ + QDir dir (source); + QDir dir_target (target); + + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + QFileInfo fi (dir.absoluteFilePath (*e)); + QFileInfo fi_target (dir_target.absoluteFilePath (*e)); + + if (fi.isDir ()) { + + // Copy subdirectory + if (! fi_target.exists ()) { + if (! dir_target.mkdir (*e)) { + tl::error << QObject::tr ("Unable to create target directory: %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + } else if (! fi_target.isDir ()) { + tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + if (! cp_dir_recursive (fi.filePath (), fi_target.filePath ())) { + return false; + } + + // TODO: leave symlinks symlinks? How to copy symlinks with Qt? + } else if (fi.isFile ()) { + + QFile file (fi.filePath ()); + QFile file_target (fi_target.filePath ()); + + if (! file.open (QIODevice::ReadOnly)) { + tl::error << QObject::tr ("Unable to open source file for reading: %1").arg (fi.filePath ()); + return false; + } + if (! file_target.open (QIODevice::WriteOnly)) { + tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (fi_target.filePath ()); + return false; + } + + size_t chunk_size = 64 * 1024; + + while (! file.atEnd ()) { + QByteArray data = file.read (chunk_size); + file_target.write (data); + } + + file.close (); + file_target.close (); + + } + + } + + return true; +} + +} diff --git a/src/tl/tlFileUtils.h b/src/tl/tlFileUtils.h new file mode 100644 index 000000000..be25bd843 --- /dev/null +++ b/src/tl/tlFileUtils.h @@ -0,0 +1,80 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_tlFileUtils +#define HDR_tlFileUtils + +#include "tlCommon.h" +#include "tlString.h" +#include + +namespace tl +{ + +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path + */ +bool TL_PUBLIC is_parent_path (const QString &parent, const QString &path); + +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path (version with std::string) + */ +inline bool TL_PUBLIC is_parent_path (const std::string &parent, const std::string &path) +{ + return is_parent_path (tl::to_qstring (parent), tl::to_qstring (path)); +} + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories + * @return True, if successful. false otherwise. + */ +bool TL_PUBLIC rm_dir_recursive (const QString &path); + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC rm_dir_recursive (const std::string &path) +{ + return rm_dir_recursive (tl::to_qstring (path)); +} + +/** + * @brief Recursively copies a given directory to a target directory + * Both target and source directories need to exist. New directories are created in the target + * directory if required. + * @return True, if successful. false otherwise. + */ +bool TL_PUBLIC cp_dir_recursive (const QString &source, const QString &target); + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC cp_dir_recursive (const std::string &source, const std::string &target) +{ + return cp_dir_recursive (tl::to_qstring (source), tl::to_qstring (target)); +} + +} + +#endif diff --git a/src/tl/tlHttpStream.cc b/src/tl/tlHttpStream.cc index a709586e8..d91bfb905 100644 --- a/src/tl/tlHttpStream.cc +++ b/src/tl/tlHttpStream.cc @@ -78,7 +78,7 @@ public: static QNetworkAccessManager *s_network_manager (0); InputHttpStream::InputHttpStream (const std::string &url) - : m_url (url) + : m_url (url), m_request ("GET"), mp_buffer (0) { if (! s_network_manager) { s_network_manager = new QNetworkAccessManager(0); @@ -87,7 +87,6 @@ InputHttpStream::InputHttpStream (const std::string &url) connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *))); connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), this, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *))); connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), this, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *))); - s_network_manager->get (QNetworkRequest (QUrl (tl::to_qstring (url)))); mp_reply = 0; } @@ -97,6 +96,30 @@ InputHttpStream::~InputHttpStream () mp_reply = 0; } +void +InputHttpStream::set_request (const char *r) +{ + m_request = QByteArray (r); +} + +void +InputHttpStream::set_data (const char *data) +{ + m_data = QByteArray (data); +} + +void +InputHttpStream::set_data (const char *data, size_t n) +{ + m_data = QByteArray (data, int (n)); +} + +void +InputHttpStream::add_header (const std::string &name, const std::string &value) +{ + m_headers.insert (std::make_pair (name, value)); +} + void InputHttpStream::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth) { @@ -117,18 +140,39 @@ InputHttpStream::finished (QNetworkReply *reply) QVariant redirect_target = reply->attribute (QNetworkRequest::RedirectionTargetAttribute); if (reply->error () == QNetworkReply::NoError && ! redirect_target.isNull ()) { m_url = tl::to_string (redirect_target.toString ()); - s_network_manager->get (QNetworkRequest (QUrl (redirect_target.toString ()))); + issue_request (QUrl (redirect_target.toString ())); delete reply; } else { mp_reply = reply; } } +void +InputHttpStream::issue_request (const QUrl &url) +{ + delete mp_buffer; + mp_buffer = 0; + + QNetworkRequest request (url); + for (std::map::const_iterator h = m_headers.begin (); h != m_headers.end (); ++h) { + request.setRawHeader (QByteArray (h->first.c_str ()), QByteArray (h->second.c_str ())); + } + if (m_data.isEmpty ()) { + s_network_manager->sendCustomRequest (request, m_request); + } else { + mp_buffer = new QBuffer (&m_data); + s_network_manager->sendCustomRequest (request, m_request, mp_buffer); + } +} + size_t InputHttpStream::read (char *b, size_t n) { + if (mp_reply == 0) { + issue_request (QUrl (tl::to_qstring (m_url))); + } while (mp_reply == 0) { - QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); + QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents, 100); } if (mp_reply->error () != QNetworkReply::NoError) { @@ -160,4 +204,3 @@ InputHttpStream::filename () const } } - diff --git a/src/tl/tlHttpStream.h b/src/tl/tlHttpStream.h index 5a2da4d8a..acd3b8635 100644 --- a/src/tl/tlHttpStream.h +++ b/src/tl/tlHttpStream.h @@ -27,6 +27,8 @@ #include "tlStream.h" #include +#include +#include class QNetworkAccessManager; class QNetworkReply; @@ -68,14 +70,38 @@ public: */ virtual ~InputHttpStream (); + /** + * @brief Sets the request verb + * The default verb is "GET" + */ + void set_request (const char *r); + + /** + * @brief Sets data to be sent with the request + * If data is given, it is sent along with the request. + * This version takes a null-terminated string. + */ + void set_data (const char *data); + + /** + * @brief Sets data to be sent with the request + * If data is given, it is sent along with the request. + * This version takes a data plus length. + */ + void set_data (const char *data, size_t n); + + /** + * @brief Sets a header field + */ + void add_header (const std::string &name, const std::string &value); + /** * @brief Read from the stream - * * Implements the basic read method. */ virtual size_t read (char *b, size_t n); - virtual void reset (); + virtual void reset (); virtual std::string source () const { @@ -89,14 +115,20 @@ public: virtual std::string filename () const; -private: - std::string m_url; - QNetworkReply *mp_reply; - private slots: void finished (QNetworkReply *); void authenticationRequired (QNetworkReply *, QAuthenticator *); void proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *); + +private: + std::string m_url; + QNetworkReply *mp_reply; + QByteArray m_request; + QByteArray m_data; + QBuffer *mp_buffer; + std::map m_headers; + + void issue_request (const QUrl &url); }; } diff --git a/src/tl/tlString.cc b/src/tl/tlString.cc index 15aa9d5ef..22d05402f 100644 --- a/src/tl/tlString.cc +++ b/src/tl/tlString.cc @@ -413,6 +413,32 @@ tl::to_word_or_quoted_string (const std::string &s, const char *non_term) } } +void +tl::escape_to_html (std::string &out, const std::string &in, bool replace_newlines) +{ + for (const char *cp = in.c_str (); *cp; ++cp) { + if (*cp == '<') { + out += "<"; + } else if (*cp == '>') { + out += ">"; + } else if (*cp == '&') { + out += "&"; + } else if (replace_newlines && *cp == '\n') { + out += "
"; + } else { + out += *cp; + } + } +} + +std::string +tl::escaped_to_html (const std::string &in, bool replace_newlines) +{ + std::string s; + escape_to_html (s, in, replace_newlines); + return s; +} + void tl::from_string (const std::string &s, const char * &result) { diff --git a/src/tl/tlString.h b/src/tl/tlString.h index 5c9aaf630..6840a7fba 100644 --- a/src/tl/tlString.h +++ b/src/tl/tlString.h @@ -326,6 +326,18 @@ TL_PUBLIC int edit_distance (const std::string &a, const std::string &b); */ TL_PUBLIC std::string to_word_or_quoted_string (const std::string &s, const char *non_term = "_.$"); +/** + * @brief Escapes HTML (or XML) characters from in and adds the result to out + * If "replace_newlines" is true, "\n" will be replaced by "
". + */ +TL_PUBLIC void escape_to_html (std::string &out, const std::string &in, bool replace_newlines = true); + +/** + * @brief Escapes HTML (or XML) characters from in and returns the resulting string + * If "replace_newlines" is true, "\n" will be replaced by "
". + */ +TL_PUBLIC std::string escaped_to_html (const std::string &in, bool replace_newlines = true); + /** * @brief Set the number of digits resolution for a micron display */ diff --git a/src/tl/tlSystemPaths.cc b/src/tl/tlSystemPaths.cc index d1847b853..53a9c5615 100644 --- a/src/tl/tlSystemPaths.cc +++ b/src/tl/tlSystemPaths.cc @@ -164,5 +164,27 @@ get_klayout_path () } } +std::string +salt_mine_url () +{ + const std::string default_url ("https://www.klayout.org/salt.mine"); + +#ifdef _WIN32 + wchar_t *env = _wgetenv (L"KLAYOUT_SALT_MINE"); + if (env) { + return tl::to_string (QString ((const QChar *) env))); + } else { + return default_url; + } +#else + char *env = getenv ("KLAYOUT_SALT_MINE"); + if (env) { + return (tl::system_to_string (env)); + } else { + return default_url; + } +#endif +} + } diff --git a/src/tl/tlSystemPaths.h b/src/tl/tlSystemPaths.h index 16187c8a3..84b83bf86 100644 --- a/src/tl/tlSystemPaths.h +++ b/src/tl/tlSystemPaths.h @@ -66,6 +66,11 @@ TL_PUBLIC void set_klayout_path (const std::vector &path); */ TL_PUBLIC void reset_klayout_path (); +/** + * @brief Gets the package manager URL + */ +TL_PUBLIC std::string salt_mine_url (); + } #endif diff --git a/src/tl/tlWebDAV.cc b/src/tl/tlWebDAV.cc new file mode 100644 index 000000000..04706e78e --- /dev/null +++ b/src/tl/tlWebDAV.cc @@ -0,0 +1,320 @@ + +/* + + 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 "tlWebDAV.h" +#include "tlXMLParser.h" +#include "tlHttpStream.h" +#include "tlStream.h" +#include "tlInternational.h" +#include "tlProgress.h" +#include "tlLog.h" + +#include +#include + +namespace tl +{ + +// --------------------------------------------------------------- +// WebDAVCollection implementation + +WebDAVObject::WebDAVObject () +{ + // .. nothing yet .. +} + +namespace +{ + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct ResourceType +{ + ResourceType () : is_collection (false) { } + + const std::string &collection () const + { + static std::string empty; + return empty; + } + + void set_collection (const std::string &) + { + is_collection = true; + } + + bool is_collection; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct Prop +{ + ResourceType resourcetype; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct PropStat +{ + std::string status; + Prop prop; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct Response +{ + std::string href; + PropStat propstat; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct MultiStatus +{ + typedef std::list container; + typedef container::const_iterator iterator; + + iterator begin () const { return responses.begin (); } + iterator end () const { return responses.end (); } + void add (const Response &r) { responses.push_back (r); } + + container responses; +}; + +} + +tl::XMLStruct xml_struct ("multistatus", + tl::make_element (&MultiStatus::begin, &MultiStatus::end, &MultiStatus::add, "response", + tl::make_member (&Response::href, "href") + + tl::make_element (&Response::propstat, "propstat", + tl::make_member (&PropStat::status, "status") + + tl::make_element (&PropStat::prop, "prop", + tl::make_element (&Prop::resourcetype, "resourcetype", + tl::make_member (&ResourceType::collection, &ResourceType::set_collection, "collection") + ) + ) + ) + ) +); + +static std::string item_name (const QString &path1, const QString &path2) +{ + QStringList sl1 = path1.split (QChar ('/')); + if (! sl1.empty () && sl1.back ().isEmpty ()) { + sl1.pop_back (); + } + + QStringList sl2 = path2.split (QChar ('/')); + if (! sl2.empty () && sl2.back ().isEmpty ()) { + sl2.pop_back (); + } + + int i = 0; + for ( ; i < sl1.length () && i < sl2.length (); ++i) { + if (sl1 [i] != sl2 [i]) { + throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection item of %2").arg (path2).arg (path1))); + } + } + if (i == sl2.length ()) { + return std::string (); + } else if (i + 1 == sl2.length ()) { + return tl::to_string (sl2[i]); + } else { + throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection sub-item of %2").arg (path2).arg (path1))); + } +} + +void +WebDAVObject::read (const std::string &url, int depth) +{ + QUrl base_url = QUrl (tl::to_qstring (url)); + + tl::InputHttpStream http (url); + http.add_header ("User-Agent", "SVN"); + http.add_header ("Depth", tl::to_string (depth)); + http.set_request ("PROPFIND"); + http.set_data (""); + + MultiStatus multistatus; + tl::InputStream stream (http); + tl::XMLStreamSource source (stream); + xml_struct.parse (source, multistatus); + + // TODO: check status .. + + m_items.clear (); + for (MultiStatus::iterator r = multistatus.begin (); r != multistatus.end (); ++r) { + + bool is_collection = r->propstat.prop.resourcetype.is_collection; + QUrl item_url = base_url.resolved (QUrl (tl::to_qstring (r->href))); + + std::string n = item_name (base_url.path (), item_url.path ()); + std::string item_url_string = tl::to_string (item_url.toString ()); + + if (! n.empty ()) { + m_items.push_back (WebDAVItem (is_collection, item_url_string, n)); + } else { + m_is_collection = is_collection; + m_url = item_url_string; + } + + } +} + +namespace +{ + +struct DownloadItem +{ + DownloadItem (const std::string &u, const std::string &p) + { + url = u; + path = p; + } + + std::string url; + std::string path; +}; + +} + +static +void fetch_download_items (const std::string &url, const std::string &target, std::list &items, tl::AbsoluteProgress &progress) +{ + ++progress; + + WebDAVObject object; + object.read (url, 1); + + if (object.is_collection ()) { + + QDir dir (tl::to_qstring (target)); + if (! dir.exists ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: target directory '%1' does not exists").arg (dir.path ()))); + } + + for (WebDAVObject::iterator i = object.begin (); i != object.end (); ++i) { + + QFileInfo new_item (dir.absoluteFilePath (tl::to_qstring (i->name ()))); + + if (i->is_collection ()) { + + if (! new_item.exists ()) { + if (! dir.mkdir (tl::to_qstring (i->name ()))) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1'").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + } else if (! new_item.isDir ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - is already a file").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } else if (! new_item.isWritable ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + + fetch_download_items (i->url (), tl::to_string (new_item.filePath ()), items, progress); + + } else { + + if (new_item.exists () && ! new_item.isWritable ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: file is '%2' in '%1' - already exists, but no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + + items.push_back (DownloadItem (i->url (), tl::to_string (dir.absoluteFilePath (tl::to_qstring (i->name ()))))); + + } + } + + } else { + items.push_back (DownloadItem (url, target)); + } +} + +bool +WebDAVObject::download (const std::string &url, const std::string &target) +{ + std::list items; + + try { + + tl::info << QObject::tr ("Fetching file structure from ") << url; + tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Fetching directory structure from %1").arg (tl::to_qstring (url)))); + fetch_download_items (url, target, items, progress); + + } catch (tl::Exception &ex) { + tl::error << QObject::tr ("Error downloading file structure from '") << url << "':" << tl::endl << ex.msg (); + return false; + } + + bool has_errors = false; + + { + tl::info << tl::to_string (QObject::tr ("Downloading %1 file(s) now ..").arg (items.size ())); + + tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading file(s) from %1").arg (tl::to_qstring (url))), items.size (), 1); + + for (std::list::const_iterator i = items.begin (); i != items.end (); ++i) { + + tl::info << QObject::tr ("Downloading '%1' to '%2' ..").arg (tl::to_qstring (i->url)).arg (tl::to_qstring (i->path)); + + try { + + tl::InputHttpStream http (i->url); + + QFile file (tl::to_qstring (i->path)); + if (! file.open (QIODevice::WriteOnly)) { + has_errors = true; + tl::error << QObject::tr ("Unable to open file '%1' for writing").arg (tl::to_qstring (i->path)); + } + + const size_t chunk = 65536; + char b[chunk]; + size_t read; + while ((read = http.read (b, sizeof (b))) > 0) { + if (! file.write (b, read)) { + tl::error << QObject::tr ("Unable to write %2 bytes file '%1'").arg (tl::to_qstring (i->path)).arg (int (read)); + has_errors = true; + break; + } + } + + file.close (); + + } catch (tl::Exception &ex) { + tl::error << QObject::tr ("Error downloading file from '") << i->url << "':" << tl::endl << ex.msg (); + has_errors = true; + } + + ++progress; + + } + } + + return ! has_errors; +} + +} diff --git a/src/tl/tlWebDAV.h b/src/tl/tlWebDAV.h new file mode 100644 index 000000000..9e82ffb8e --- /dev/null +++ b/src/tl/tlWebDAV.h @@ -0,0 +1,152 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_tlWebDAV +#define HDR_tlWebDAV + +#include "tlCommon.h" +#include +#include + +namespace tl +{ + +/** + * @brief Represents an item in a WebDAV collection + */ +class TL_PUBLIC WebDAVItem +{ +public: + /** + * @brief Default constructor + */ + WebDAVItem () + : m_is_collection (false) + { + // .. nothing yet .. + } + + /** + * @brief Constructor + */ + WebDAVItem (bool is_collection, const std::string &url, const std::string &name) + : m_is_collection (is_collection), m_url (url), m_name (name) + { + // .. nothing yet .. + } + + /** + * @brief Gets a value indicating whether this item is a collection + * If false, it's a file. + */ + bool is_collection () const + { + return m_is_collection; + } + + /** + * @brief Gets the URL of this item + */ + const std::string &url () const + { + return m_url; + } + + /** + * @brief Gets the name of this item + * The name is only valid for sub-items. + */ + const std::string &name () const + { + return m_name; + } + +protected: + bool m_is_collection; + std::string m_url; + std::string m_name; +}; + +/** + * @brief Represents an object from a WebDAV URL + * This object can be a file or collection + */ +class TL_PUBLIC WebDAVObject + : public WebDAVItem +{ +public: + typedef std::vector container; + typedef container::const_iterator iterator; + + /** + * @brief Open a stream with the given URL + */ + WebDAVObject (); + + /** + * @brief Populates the collection from the given URL + * The depth value can be 0 (self only) or 1 (self + collection members). + */ + void read (const std::string &url, int depth); + + /** + * @brief Gets the items of this collection (begin iterator) + */ + iterator begin () const + { + return m_items.begin (); + } + + /** + * @brief Gets the items of this collection (begin iterator) + */ + iterator end () const + { + return m_items.end (); + } + + /** + * @brief Downloads the collection or file with the given URL + * + * This method will download the WebDAV object from url to the file path + * given in "target". + * + * For file download, the target must be the path of the target file. + * For collection download, the target must be a directory path. In this + * case, the target directory must exist already. + * + * Sub-directories are created if required. + * + * This method throws an exception if the directory structure could + * not be obtained or downloading of one file failed. + */ + static bool download (const std::string &url, const std::string &target); + +private: + container m_items; +}; + +} + +#endif + diff --git a/src/tl/tlXMLParser.h b/src/tl/tlXMLParser.h index 2760df1f0..6a1ae3015 100644 --- a/src/tl/tlXMLParser.h +++ b/src/tl/tlXMLParser.h @@ -671,12 +671,12 @@ public: return m_name; } - bool check_name (const std::string &, const std::string &, const std::string &qname) const + bool check_name (const std::string & /*uri*/, const std::string &lname, const std::string & /*qname*/) const { if (m_name == "*") { return true; } else { - return m_name == qname; // no namespace currently + return m_name == lname; // no namespace currently } } diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc new file mode 100644 index 000000000..7c5e6eb81 --- /dev/null +++ b/src/unit_tests/laySalt.cc @@ -0,0 +1,401 @@ + +/* + + 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 "laySaltGrain.h" +#include "laySaltGrains.h" +#include "laySalt.h" +#include "tlFileUtils.h" +#include "utHead.h" + +#include +#include + +static std::string grains_to_string (const lay::SaltGrains &gg) +{ + std::string res; + res += "["; + bool first = true; + for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { + if (! first) { + res += ","; + } + first = false; + res += g->name (); + } + for (lay::SaltGrains::collection_iterator gc = gg.begin_collections (); gc != gg.end_collections (); ++gc) { + if (! first) { + res += ","; + } + first = false; + res += gc->name (); + res += grains_to_string (*gc); + } + res += "]"; + return res; +} + +static std::string salt_to_string (lay::Salt &salt) +{ + std::string res; + res += "["; + bool first = true; + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + if (! first) { + res += ","; + } + first = false; + res += (*i)->name (); + } + res += "]"; + return res; +} + +TEST (1) +{ + std::string tmp0 = tmp_file ("tmp0"); + + lay::SaltGrain g; + g.save (tmp0); + EXPECT_EQ (g.authored_time ().isNull (), true); + EXPECT_EQ (g.installed_time ().isNull (), true); + + lay::SaltGrain g0; + g0.load (tmp0); + EXPECT_EQ (g0.authored_time ().isNull (), true); + EXPECT_EQ (g0.installed_time ().isNull (), true); + EXPECT_EQ (g == g0, true); + + std::string tmp = tmp_file (); + + g.set_name ("abc"); + EXPECT_EQ (g.name (), "abc"); + g.set_url ("xyz"); + EXPECT_EQ (g.url (), "xyz"); + g.set_version ("1.0"); + EXPECT_EQ (g.version (), "1.0"); + g.set_path ("a/b"); + EXPECT_EQ (g.path (), "a/b"); + g.set_title ("title"); + EXPECT_EQ (g.title (), "title"); + g.set_doc ("doc"); + EXPECT_EQ (g.doc (), "doc"); + g.set_doc_url ("doc-url"); + EXPECT_EQ (g.doc_url (), "doc-url"); + g.set_author ("me"); + EXPECT_EQ (g.author (), "me"); + g.set_author_contact ("ac"); + EXPECT_EQ (g.author_contact (), "ac"); + g.set_license ("free"); + EXPECT_EQ (g.license (), "free"); + g.set_authored_time (QDateTime ()); + EXPECT_EQ (g.authored_time ().isNull (), true); + g.set_authored_time (QDateTime::fromMSecsSinceEpoch (1000000000)); + EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.authored_time ()), 1000000000); + g.set_installed_time (QDateTime ()); + EXPECT_EQ (g.installed_time ().isNull (), true); + g.set_installed_time (QDateTime::fromMSecsSinceEpoch (2000000000)); + EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.installed_time ()), 2000000000); + + g.add_dependency (lay::SaltGrain::Dependency ()); + g.dependencies ().back ().name = "depname"; + g.dependencies ().back ().url = "depurl"; + g.dependencies ().back ().version = "0.0"; + EXPECT_EQ (int (g.dependencies ().size ()), 1); + + lay::SaltGrain gg; + EXPECT_EQ (g == gg, false); + EXPECT_EQ (g == g, true); + EXPECT_EQ (g != gg, true); + EXPECT_EQ (g != g, false); + + gg = g; + EXPECT_EQ (g == gg, true); + + gg.set_doc ("blabla"); + EXPECT_EQ (g == gg, false); + + EXPECT_EQ (g == gg, false); + g.save (tmp); + + EXPECT_EQ (g == gg, false); + + gg = lay::SaltGrain (); + gg.load (tmp); + gg.set_path (g.path ()); // path is not set by load(file) + EXPECT_EQ (int (gg.dependencies ().size ()), 1); + EXPECT_EQ (g == gg, true); + + gg.add_dependency (lay::SaltGrain::Dependency ()); + EXPECT_EQ (g == gg, false); + gg.set_path (tl::to_string (QFileInfo (tl::to_qstring (tmp)).absolutePath ())); + gg.save (); + + g = lay::SaltGrain::from_path (gg.path ()); + EXPECT_EQ (g == gg, true); +} + +TEST (2) +{ + EXPECT_EQ (lay::SaltGrain::valid_version (""), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("1"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("1.2"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("\t1 . 2.\n3"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("x"), false); + EXPECT_EQ (lay::SaltGrain::valid_version ("1.2x"), false); + EXPECT_EQ (lay::SaltGrain::valid_name (""), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x1"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x1 "), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x$1"), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x/y"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x_y"), true); + EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", ""), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("2", "1"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "2.0"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "1.0"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.0", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1a", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.a.1", "1.0.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.1.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "991"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "990"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("991", "990"), 1); +} + +TEST (3) +{ + const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); + + lay::SaltGrain g; + g.set_name ("x"); + + QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ()); + QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a"))); + QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b"))); + QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c"))); + QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u"))); + QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); + QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); + + lay::SaltGrains gg; + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + + tmp_dir.mkdir (dir_a.dirName ()); + tmp_dir.mkdir (dir_b.dirName ()); + tmp_dir.mkdir (dir_c.dirName ()); + dir_c.mkdir (dir_cu.dirName ()); + dir_c.mkdir (dir_cc.dirName ()); + dir_cc.mkdir (dir_ccv.dirName ()); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ())); + + g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file))); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), false); + EXPECT_EQ (grains_to_string (gg), "[a]"); + EXPECT_EQ (gg.begin_grains ()->path (), tl::to_string (dir_a.absolutePath ())); + + g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file))); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), false); + EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); + EXPECT_EQ (gg.begin_collections ()->path (), tl::to_string (dir_c.absolutePath ())); + + std::string gg_path = tmp_file ("gg.tmp"); + gg.save (gg_path); + + lay::SaltGrains ggg; + ggg.load (gg_path); + EXPECT_EQ (grains_to_string (ggg), "[a,b,c[c/u,c/c[c/c/v]]]"); + // NOTE: The path is not set, so this will fail: + // EXPECT_EQ (gg == ggg, true); + + gg.remove_grain (gg.begin_grains (), false); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); + gg.remove_grain (gg.begin_grains (), true); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg.remove_collection (gg.begin_collections (), false); + EXPECT_EQ (grains_to_string (gg), "[b]"); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg.remove_collection (gg.begin_collections (), true); + EXPECT_EQ (grains_to_string (gg), "[b]"); + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b]"); +} + +TEST (4) +{ + const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); + + // That's just preparation ... + + lay::SaltGrain g; + g.set_name ("x"); + + QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ()); + QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a"))); + QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b"))); + QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c"))); + QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u"))); + QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); + QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); + + lay::SaltGrains gg; + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + + tmp_dir.mkdir (dir_a.dirName ()); + tmp_dir.mkdir (dir_b.dirName ()); + tmp_dir.mkdir (dir_c.dirName ()); + dir_c.mkdir (dir_cu.dirName ()); + dir_c.mkdir (dir_cc.dirName ()); + dir_cc.mkdir (dir_ccv.dirName ()); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ())); + + g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file))); + + // That's the main test part + + lay::Salt salt; + EXPECT_EQ (salt.is_empty (), true); + + QSignalSpy spy (&salt, SIGNAL (collections_changed ())); + EXPECT_EQ (salt_to_string (salt), "[]"); + + spy.clear (); + salt.add_location (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (salt.is_empty (), false); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]"); + + spy.clear (); + salt.add_location (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (spy.count (), 0); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]"); + + spy.clear (); + salt.add_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u,c/v,u]"); + + lay::Salt salt_copy = salt; + (const_cast (*salt_copy.begin ())).remove_grain (salt_copy.begin ()->begin_grains (), true); + + spy.clear (); + salt.refresh (); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u,c/v,u]"); + + spy.clear (); + salt.remove_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); + + spy.clear (); + // location already removed + salt.remove_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 0); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); + + EXPECT_EQ (salt.grain_by_name ("x"), 0); + EXPECT_EQ (salt.grain_by_name ("b")->name (), "b"); + EXPECT_EQ (salt.grain_by_name ("c/c/v")->name (), "c/c/v"); +} + +TEST (5) +{ + lay::SaltGrains grains; + + lay::SaltGrain g1; + g1.set_name ("g1"); + lay::SaltGrain::Dependency dep; + dep.name = "g2"; + g1.dependencies ().push_back (dep); + dep.name = "g3"; + g1.dependencies ().push_back (dep); + grains.add_grain (g1); + + lay::SaltGrains g34; + + lay::SaltGrain g3; + g3.set_name ("g3"); + g34.add_grain (g3); + + lay::SaltGrain g4; + g4.set_name ("g4"); + g34.add_grain (g4); + + grains.add_collection (g34); + + lay::SaltGrain g2; + g2.set_name ("g2"); + dep.name = "g3"; + g2.dependencies ().push_back (dep); + grains.add_grain (g2); + + lay::Salt salt; + salt.root ().add_collection (grains); + + std::vector names; + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + names.push_back ((*i)->name ()); + } + + EXPECT_EQ (tl::join (names, ","), "g3,g2,g1,g4"); +} diff --git a/src/unit_tests/tlFileUtils.cc b/src/unit_tests/tlFileUtils.cc new file mode 100644 index 000000000..7eb3032a8 --- /dev/null +++ b/src/unit_tests/tlFileUtils.cc @@ -0,0 +1,155 @@ + +/* + + 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 "tlFileUtils.h" +#include "utHead.h" + +#include +#include +#include + +TEST (1) +{ + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home/matthias"), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home"), true); + EXPECT_EQ (tl::is_parent_path (std::string (""), ""), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/opt/klayout"), "/home/matthias"), false); + EXPECT_EQ (tl::is_parent_path (std::string ("/home/klayout"), "/home/matthias"), false); +} + +TEST (2) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + EXPECT_EQ (tl::rm_dir_recursive (tl::to_string (adir.absolutePath ())), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + EXPECT_EQ (b1dir.exists (), false); + EXPECT_EQ (b2dir.exists (), false); + EXPECT_EQ (QFileInfo (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))).exists (), false); +} + +TEST (3) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("a"))); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world II!\n"); + file.close (); + } + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("acopy"))); + tmp_dir.mkdir (QString::fromUtf8 ("acopy")); + + tl::cp_dir_recursive (tmp_dir.absoluteFilePath (QString::fromUtf8 ("a")), tmp_dir.absoluteFilePath (QString::fromUtf8 ("acopy"))); + + QDir acopydir = tmp_dir; + EXPECT_EQ (acopydir.cd (QString::fromUtf8 ("acopy")), true); + EXPECT_EQ (acopydir.exists (), true); + + QDir b1copydir = acopydir; + EXPECT_EQ (b1copydir.cd (QString::fromUtf8 ("b1")), true); + EXPECT_EQ (b1copydir.exists (), true); + + QDir b2copydir = acopydir; + EXPECT_EQ (b2copydir.cd (QString::fromUtf8 ("b2")), true); + EXPECT_EQ (b2copydir.exists (), true); + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("x"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world!\n"); + file.close (); + } + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("y"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world II!\n"); + file.close (); + } +} diff --git a/src/unit_tests/tlHttpStream.cc b/src/unit_tests/tlHttpStream.cc new file mode 100644 index 000000000..2c493de12 --- /dev/null +++ b/src/unit_tests/tlHttpStream.cc @@ -0,0 +1,75 @@ + +/* + + 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 "tlHttpStream.h" +#include "utHead.h" + +static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); +static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1"); + +TEST(1) +{ + tl::InputHttpStream stream (test_url1); + + char b[100]; + size_t n = stream.read (b, sizeof (b)); + std::string res (b, n); + EXPECT_EQ (res, "hello, world.\n"); +} + +TEST(2) +{ + tl::InputHttpStream stream (test_url2); + stream.add_header ("User-Agent", "SVN"); + stream.add_header ("Depth", "1"); + stream.set_request ("PROPFIND"); + stream.set_data (""); + + char b[10000]; + size_t n = stream.read (b, sizeof (b)); + std::string res (b, n); + + EXPECT_EQ (res, + "\n" + "\n" + "\n" + "/svn-public/klayout-resources/trunk/testdata/dir1/\n" + "\n" + "\n" + "\n" + "\n" + "HTTP/1.1 200 OK\n" + "\n" + "\n" + "\n" + "/svn-public/klayout-resources/trunk/testdata/dir1/text\n" + "\n" + "\n" + "\n" + "\n" + "HTTP/1.1 200 OK\n" + "\n" + "\n" + "\n" + ); +} diff --git a/src/unit_tests/tlString.cc b/src/unit_tests/tlString.cc index 9c6c3fd19..6b0ad38ac 100644 --- a/src/unit_tests/tlString.cc +++ b/src/unit_tests/tlString.cc @@ -423,3 +423,20 @@ TEST(10) EXPECT_EQ (unescape_string (escape_string ("'a\n\003")), "'a\n\003"); } +TEST(11) +{ + std::string s; + tl::escape_to_html (s, "x"); + EXPECT_EQ (s, "x"); + tl::escape_to_html (s, "<&>"); + EXPECT_EQ (s, "x<&>"); + s = std::string (); + tl::escape_to_html (s, "a\nb"); + EXPECT_EQ (s, "a
b"); + s = std::string (); + tl::escape_to_html (s, "a\nb", false); + EXPECT_EQ (s, "a\nb"); + EXPECT_EQ (tl::escaped_to_html ("x<&>"), "x<&>"); + EXPECT_EQ (tl::escaped_to_html ("a\nb"), "a
b"); + EXPECT_EQ (tl::escaped_to_html ("a\nb", false), "a\nb"); +} diff --git a/src/unit_tests/tlWebDAV.cc b/src/unit_tests/tlWebDAV.cc new file mode 100644 index 000000000..bba9764f8 --- /dev/null +++ b/src/unit_tests/tlWebDAV.cc @@ -0,0 +1,129 @@ + +/* + + 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 "tlWebDAV.h" +#include "utHead.h" + +#include + +static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata"); +static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + +static std::string collection2string (const tl::WebDAVObject &coll) +{ + std::string s; + for (tl::WebDAVObject::iterator c = coll.begin (); c != coll.end (); ++c) { + if (!s.empty ()) { + s += "\n"; + } + if (c->is_collection ()) { + s += "[dir] "; + } + s += c->name (); + s += " "; + s += c->url (); + } + return s; +} + +TEST(1) +{ + tl::WebDAVObject collection; + collection.read (test_url1, 1); + + EXPECT_EQ (collection.is_collection (), true); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/"); + + EXPECT_EQ (collection2string (collection), + "[dir] dir1 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1/\n" + "[dir] dir2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir2/\n" + "text http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text\n" + "text2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text2" + ); +} + +TEST(2) +{ + tl::WebDAVObject collection; + collection.read (test_url1, 0); + + EXPECT_EQ (collection.is_collection (), true); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(3) +{ + tl::WebDAVObject collection; + collection.read (test_url2, 1); + + EXPECT_EQ (collection.is_collection (), false); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(4) +{ + tl::WebDAVObject collection; + collection.read (test_url2, 0); + + EXPECT_EQ (collection.is_collection (), false); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(5) +{ + tl::WebDAVObject collection; + + QDir tmp_dir (tl::to_qstring (tmp_file ("tmp"))); + EXPECT_EQ (tmp_dir.exists (), false); + + tmp_dir.cdUp (); + tmp_dir.mkdir (tl::to_qstring ("tmp")); + tmp_dir.cd (tl::to_qstring ("tmp")); + + bool res = collection.download (test_url1, tl::to_string (tmp_dir.absolutePath ())); + EXPECT_EQ (res, true); + + QDir dir1 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir1"))); + QDir dir2 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir2"))); + QDir dir21 (dir2.absoluteFilePath (QString::fromUtf8 ("dir21"))); + EXPECT_EQ (dir1.exists (), true); + EXPECT_EQ (dir2.exists (), true); + EXPECT_EQ (dir21.exists (), true); + + QByteArray ba; + + QFile text1 (dir1.absoluteFilePath (QString::fromUtf8 ("text"))); + text1.open (QIODevice::ReadOnly); + ba = text1.read (10000); + EXPECT_EQ (ba.constData (), "A text.\n"); + text1.close (); + + QFile text21 (dir21.absoluteFilePath (QString::fromUtf8 ("text"))); + text21.open (QIODevice::ReadOnly); + ba = text21.read (10000); + EXPECT_EQ (ba.constData (), "A text II.I.\n"); + text21.close (); +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index ffe60f38f..7693c5677 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -96,7 +96,11 @@ SOURCES = \ tlXMLParser.cc \ gsiTest.cc \ tlFileSystemWatcher.cc \ - tlMath.cc + tlMath.cc \ + laySalt.cc \ + tlFileUtils.cc \ + tlHttpStream.cc \ + tlWebDAV.cc # main components: SOURCES += \ diff --git a/src/ut/utMain.cc b/src/ut/utMain.cc index 310d6a0ab..b5ca6d446 100644 --- a/src/ut/utMain.cc +++ b/src/ut/utMain.cc @@ -27,6 +27,7 @@ #include "tlStaticObjects.h" #include "tlTimer.h" #include "tlSystemPaths.h" +#include "tlFileUtils.h" #include "layApplication.h" #include "gsiExpression.h" #include "gsiExternalMain.h" @@ -519,8 +520,12 @@ bool TestBase::do_test (const std::string & /*mode*/) { // Ensures the test temp directory is present QDir dir (testtmp ()); + QDir tmpdir (dir.absoluteFilePath (tl::to_qstring (m_testdir))); + if (tmpdir.exists () && ! tl::rm_dir_recursive (tmpdir.absolutePath ())) { + throw tl::Exception ("Unable to clean temporary dir: " + tl::to_string (tmpdir.absolutePath ())); + } if (! dir.mkpath (tl::to_qstring (m_testdir))) { - throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (dir.filePath (tl::to_qstring (m_test)))); + throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (tmpdir.absolutePath ())); } dir.cd (tl::to_qstring (m_testdir));