Merge branch 'salt'

This commit is contained in:
Matthias Koefferlein 2017-05-07 23:55:22 +02:00
commit 60cfbd9c6f
142 changed files with 14394 additions and 1391 deletions

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -299,12 +299,25 @@ TextGenerator::default_generator ()
return fonts.empty () ? 0 : &fonts [0];
}
static std::vector<std::string> s_font_paths;
static std::vector<TextGenerator> s_fonts;
static bool s_fonts_loaded = false;
void
TextGenerator::set_font_paths (const std::vector<std::string> &paths)
{
s_font_paths = paths;
s_fonts.clear ();
s_fonts_loaded = false;
}
const std::vector<TextGenerator> &
TextGenerator::generators ()
{
static std::vector<TextGenerator> 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<std::string> system_path = tl::get_klayout_path ();
// scan for font files
for (std::vector<std::string>::const_iterator p = system_path.begin (); p != system_path.end (); ++p) {
for (std::vector<std::string>::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;
}
}

View File

@ -188,6 +188,12 @@ public:
*/
static const std::vector<TextGenerator> &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<std::string> &paths);
/**
* @brief Returns the font with the given name
* If no font with that name exsist, 0 is returned.

View File

@ -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

View File

@ -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<db::Layout *, int> m_referrers;

View File

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

View File

@ -453,7 +453,7 @@ public:
*/
const std::vector<PCellParameterDeclaration> &parameter_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<std::string, tl::Variant> &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;

5
src/db/dbResources.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/fonts">
<file>std_font.gds</file>
</qresource>
</RCC>

View File

@ -110,11 +110,25 @@ Class<db::Library> 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,

View File

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

View File

@ -223,6 +223,7 @@ InstPropertiesPage::update ()
const db::Cell &def_cell = def_layout->cell (def_cell_index);
std::pair<db::Library *, db::cell_index_type> 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 ();

View File

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

View File

@ -1,117 +1,213 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LogViewerDialog</class>
<widget class="QDialog" name="LogViewerDialog" >
<property name="geometry" >
<widget class="QDialog" name="LogViewerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>578</width>
<height>579</height>
<width>516</width>
<height>287</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string>Log Viewer</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<layout class="QGridLayout">
<property name="leftMargin">
<number>9</number>
</property>
<property name="spacing" >
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="3" >
<widget class="QPushButton" name="clear_pb" >
<property name="text" >
<string>Clear</string>
</property>
</widget>
</item>
<item row="0" column="4" >
<widget class="QPushButton" name="separator_pb" >
<property name="text" >
<string>Separator</string>
</property>
</widget>
</item>
<item row="0" column="5" >
<widget class="QPushButton" name="copy_pb" >
<property name="text" >
<string>Copy</string>
</property>
</widget>
</item>
<item row="0" column="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>101</width>
<height>22</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>Verbosity</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QComboBox" name="verbosity_cbx" >
<item row="0" column="1">
<widget class="QComboBox" name="verbosity_cbx">
<item>
<property name="text" >
<property name="text">
<string>Silent</string>
</property>
</item>
<item>
<property name="text" >
<property name="text">
<string>Information</string>
</property>
</item>
<item>
<property name="text" >
<property name="text">
<string>Details</string>
</property>
</item>
<item>
<property name="text" >
<property name="text">
<string>Verbose</string>
</property>
</item>
<item>
<property name="text" >
<property name="text">
<string>Noisy</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0" colspan="6" >
<widget class="QListView" name="log_view" >
<property name="resizeMode" >
<enum>QListView::Adjust</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool>
<item row="0" column="4">
<widget class="QPushButton" name="separator_pb">
<property name="text">
<string>Separator</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="6" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<item row="0" column="5">
<widget class="QPushButton" name="copy_pb">
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="clear_pb">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="verbosity_label">
<property name="text">
<string>Verbosity</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="6">
<widget class="QListView" name="log_view">
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Close</set>
<property name="sizeHint" stdset="0">
<size>
<width>101</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="6">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="attn_frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="layResources.qrc">:/warn_16.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>There are errors or warnings</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<tabstops>
<tabstop>verbosity_cbx</tabstop>
<tabstop>clear_pb</tabstop>
<tabstop>separator_pb</tabstop>
<tabstop>copy_pb</tabstop>
<tabstop>log_view</tabstop>
</tabstops>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
@ -119,11 +215,11 @@
<receiver>LogViewerDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
@ -135,11 +231,11 @@
<receiver>LogViewerDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>

View File

@ -0,0 +1,921 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SaltGrainPropertiesDialog</class>
<widget class="QDialog" name="SaltGrainPropertiesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>693</width>
<height>538</height>
</rect>
</property>
<property name="windowTitle">
<string>Package Properties</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="4" column="2" colspan="3">
<widget class="QLineEdit" name="author"/>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Description</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="10" column="2" colspan="3">
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(enter an URL to provide a documentation link)</string>
</property>
</widget>
</item>
<item row="3" column="2" colspan="3">
<widget class="QLineEdit" name="title"/>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_14">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Depends on</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="5" column="2" colspan="3">
<widget class="QLineEdit" name="author_contact"/>
</item>
<item row="9" column="4">
<widget class="QLabel" name="open_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;a href=&quot;%1&quot;&gt;Open link&lt;/a&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="15" column="2" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>32</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="2" colspan="3">
<widget class="QFrame" name="frame_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="2">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="remove_dependency">
<property name="toolTip">
<string>Delete dependency</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/clear.png</normaloff>:/clear.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="add_dependency">
<property name="toolTip">
<string>Add new dependency</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="3">
<widget class="QTreeWidget" name="dependencies">
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">Name</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>URL</string>
</property>
</column>
</widget>
</item>
<item row="0" column="1">
<widget class="lay::AlertLogButton" name="dependencies_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Title</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>API version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_8">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Author contact</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="2" colspan="3">
<widget class="QFrame" name="frame_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>2</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="1" column="6">
<widget class="QToolButton" name="screenshot_delete_button">
<property name="toolTip">
<string>Reset Screenshot</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/clear_edit.png</normaloff>:/clear_edit.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Icon
(64x64)</string>
</property>
</widget>
</item>
<item row="1" column="1" rowspan="4">
<widget class="QToolButton" name="icon_config_button">
<property name="toolTip">
<string>Select an Icon Image</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/salt_icon.png</normaloff>:/salt_icon.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
</widget>
</item>
<item row="1" column="5" rowspan="4">
<widget class="QToolButton" name="screenshot_config_button">
<property name="toolTip">
<string>Select a Screenshot Image</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
</widget>
</item>
<item row="1" column="7">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3" rowspan="4">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2" rowspan="3">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="icon_delete_button">
<property name="toolTip">
<string>Reset Icon</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/clear_edit.png</normaloff>:/clear_edit.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Showcase image
(max 1024x1024)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QFrame" name="frame_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="version">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="lay::AlertLogButton" name="version_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="3" colspan="2">
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(license information like &quot;GPLv3&quot; or &quot;MIT&quot;)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Version</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_9">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Images</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QFrame" name="frame_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="license">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="lay::AlertLogButton" name="license_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="11" column="2" colspan="3">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3" colspan="2">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(use numeric versions like &quot;1.5&quot; or &quot;2.1.3&quot;)</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_12">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>License</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Documentation</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="9" column="2" colspan="2">
<widget class="QFrame" name="frame_6">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="doc_url">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="lay::AlertLogButton" name="doc_url_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="13" column="2" colspan="3">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="2" colspan="3">
<widget class="QTextEdit" name="doc">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Author</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QFrame" name="frame_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="api_version"/>
</item>
<item>
<widget class="lay::AlertLogButton" name="api_version_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="7" column="3" colspan="2">
<widget class="QLabel" name="label_16">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(API version required - i.e. &quot;0.25&quot;)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>lay::AlertLogButton</class>
<extends>QToolButton</extends>
<header>layLogViewerDialog.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>version</tabstop>
<tabstop>title</tabstop>
<tabstop>author</tabstop>
<tabstop>author_contact</tabstop>
<tabstop>license</tabstop>
<tabstop>doc</tabstop>
<tabstop>doc_url</tabstop>
<tabstop>icon_config_button</tabstop>
<tabstop>icon_delete_button</tabstop>
<tabstop>screenshot_config_button</tabstop>
<tabstop>screenshot_delete_button</tabstop>
<tabstop>dependencies</tabstop>
<tabstop>add_dependency</tabstop>
<tabstop>remove_dependency</tabstop>
</tabstops>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SaltGrainPropertiesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>546</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>561</x>
<y>435</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SaltGrainPropertiesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>638</x>
<y>418</y>
</hint>
<hint type="destinationlabel">
<x>649</x>
<y>433</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,237 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SaltGrainTemplateSelectionDialog</class>
<widget class="QDialog" name="SaltGrainTemplateSelectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>401</width>
<height>499</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Package</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Template</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="salt_view">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="selectionRectVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(Pick a template with which to initialize your new package)</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Package Name</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="name_edit"/>
</item>
<item>
<widget class="lay::AlertLogButton" name="name_alert">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/warn.png</normaloff>:/warn.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>50</weight>
<italic>true</italic>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>(Choose a simple name composed of letters, digits and underscores. Use the notation &quot;group/package&quot; to create a package inside a group)</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
<action name="actionNew">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="text">
<string>New</string>
</property>
<property name="toolTip">
<string>New package</string>
</property>
</action>
<action name="actionDelete">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/clear.png</normaloff>:/clear.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete package</string>
</property>
</action>
<action name="actionImport">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/import.png</normaloff>:/import.png</iconset>
</property>
<property name="text">
<string>Import</string>
</property>
<property name="toolTip">
<string>Import package</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>lay::AlertLogButton</class>
<extends>QToolButton</extends>
<header>layLogViewerDialog.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SaltGrainTemplateSelectionDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>273</x>
<y>431</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>448</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SaltGrainTemplateSelectionDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>351</x>
<y>426</y>
</hint>
<hint type="destinationlabel">
<x>363</x>
<y>445</y>
</hint>
</hints>
</connection>
</connections>
</ui>

1144
src/lay/SaltManagerDialog.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SaltManagerInstallConfirmationDialog</class>
<widget class="QDialog" name="SaltManagerInstallConfirmationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>495</width>
<height>478</height>
</rect>
</property>
<property name="windowTitle">
<string>Ready for Installation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>The following packages are now ready for installation or update:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="list">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>Package</string>
</property>
</column>
<column>
<property name="text">
<string>Action</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>Download link</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Press &quot;Ok&quot; to install or update these packages or &quot;Cancel&quot; to abort.</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
<action name="actionNew">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="text">
<string>New</string>
</property>
<property name="toolTip">
<string>New package</string>
</property>
</action>
<action name="actionDelete">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/clear.png</normaloff>:/clear.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete package</string>
</property>
</action>
<action name="actionImport">
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/import.png</normaloff>:/import.png</iconset>
</property>
<property name="text">
<string>Import</string>
</property>
<property name="toolTip">
<string>Import package</string>
</property>
</action>
</widget>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SaltManagerInstallConfirmationDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>273</x>
<y>431</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>448</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SaltManagerInstallConfirmationDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>351</x>
<y>426</y>
</hint>
<hint type="destinationlabel">
<x>363</x>
<y>445</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -115,6 +115,9 @@
<iconset resource="layResources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -126,6 +129,9 @@
<iconset resource="layResources.qrc">
<normaloff>:/clear.png</normaloff>:/clear.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>

View File

@ -23,6 +23,7 @@
#include "layApplication.h"
#include "layMainWindow.h"
#include "laySignalHandler.h"
#include "gsiDecl.h"
#include "gsiQtExternals.h"
@ -68,7 +69,7 @@ Class<lay::Application> 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"
) +

View File

@ -539,6 +539,10 @@ Class<lay::MainWindow> 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)"
) +

BIN
src/lay/images/empty_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

BIN
src/lay/images/empty_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

BIN
src/lay/images/error_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

BIN
src/lay/images/file_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
src/lay/images/info_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/lay/images/salt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
src/lay/images/warn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

BIN
src/lay/images/warn_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

@ -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

View File

@ -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 <QIcon>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QFile>
#include <QAction>
#include <QMessageBox>
#ifdef _WIN32
# include <windows.h>
# include <DbgHelp.h>
# include <Psapi.h>
// get rid of these - we have std::min/max ..
# ifdef min
# undef min
# endif
# ifdef max
# undef max
# endif
#else
# include <dlfcn.h>
# include <execinfo.h>
#endif
#include <iostream>
#include <memory>
#include <algorithm>
#include <signal.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <dlfcn.h>
#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<std::string> global_modules;
std::set<std::string> modules;
// try to locate a global plugins
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
#if 0
// deprecated functionality
QFileInfo rbainit_file (tl::to_qstring (*p), QString::fromUtf8 ("rbainit"));
if (rbainit_file.exists () && rbainit_file.isReadable ()) {
std::string m = tl::to_string (rbainit_file.absoluteFilePath ());
if (modules.find (m) == modules.end ()) {
global_modules.push_back (m);
modules.insert (m);
}
}
#endif
QDir inst_path_dir (tl::to_qstring (*p));
QStringList name_filters;
name_filters << QString::fromUtf8 ("*.rbm");
name_filters << QString::fromUtf8 ("*.pym");
QStringList inst_modules = inst_path_dir.entryList (name_filters);
inst_modules.sort ();
for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
QFileInfo rbm_file (tl::to_qstring (*p), *im);
if (rbm_file.exists () && rbm_file.isReadable ()) {
std::string m = tl::to_string (rbm_file.absoluteFilePath ());
if (modules.find (m) == modules.end ()) {
global_modules.push_back (m);
modules.insert (m);
}
}
}
}
// try to locate the global plugins
for (std::vector <std::string>::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<std::string, std::string> ("macros", tl::to_string (QObject::tr ("Ruby"))));
m_macro_categories.push_back (std::pair<std::string, std::string> ("pymacros", tl::to_string (QObject::tr ("Python"))));
m_macro_categories.push_back (std::pair<std::string, std::string> ("drc", tl::to_string (QObject::tr ("DRC"))));
// Scan for macros and set interpreter path
for (std::vector <std::string>::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 <std::string>::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 <std::string>::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 <std::string>::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 <std::pair<file_type, std::pair<std::string, std::string> > >::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 <std::pair<file_type, std::pair<std::string, std::string> > >::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<std::string> global_modules = scan_global_modules ();
m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ());
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
if (p == m_klayout_path.begin ()) {
mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false);
} else if (m_klayout_path.size () == 2) {
mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true);
} else {
mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true);
}
}
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 <std::pair<std::string, std::string> >::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 <std::pair<std::string, std::string> >::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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
if (tl::verbosity () >= 20) {
tl::info << " " << cls.current_name () << " [" << cls.current_position () << "]";
}
pd->initialize (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<std::string>
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<std::string> global_modules;
std::set<std::string> modules;
// try to locate a global plugins
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
#if 0
// deprecated functionality
QFileInfo rbainit_file (tl::to_qstring (*p), QString::fromUtf8 ("rbainit"));
if (rbainit_file.exists () && rbainit_file.isReadable ()) {
std::string m = tl::to_string (rbainit_file.absoluteFilePath ());
if (modules.find (m) == modules.end ()) {
global_modules.push_back (m);
modules.insert (m);
}
}
#endif
QDir inst_path_dir (tl::to_qstring (*p));
QStringList name_filters;
name_filters << QString::fromUtf8 ("*.rbm");
name_filters << QString::fromUtf8 ("*.pym");
QStringList inst_modules = inst_path_dir.entryList (name_filters);
inst_modules.sort ();
for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) {
QFileInfo rbm_file (tl::to_qstring (*p), *im);
if (rbm_file.exists () && rbm_file.isReadable ()) {
std::string m = tl::to_string (rbm_file.absoluteFilePath ());
if (modules.find (m) == modules.end ()) {
tl::warn << tl::to_string (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 <std::string>::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<db::Library> 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<lay::MacroCollection *>
Application::sync_tech_macro_locations ()
{
if (m_no_macros) {
return std::vector<lay::MacroCollection *> ();
}
std::set<std::pair<std::string, std::string> > tech_macro_paths;
std::map<std::pair<std::string, std::string>, 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<std::string, std::string> 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<lay::MacroCollection *> 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<std::string, std::string> 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<lay::MacroCollection *>::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<std::string, std::string> cp (m->second->category (), m->second->path ());
tech_macro_paths.erase (cp);
}
}
std::vector<lay::MacroCollection *> new_folders;
for (std::set<std::pair<std::string, std::string> >::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;
}
}

View File

@ -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<std::string, std::string> > &macro_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<std::string> scan_global_modules ();
enum file_type {
layout_file,

View File

@ -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 <QDir>
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<std::string, std::string> > & /*options*/) const
{
// .. nothing yet ..
}
void
FontController::get_menu_entries (std::vector<lay::MenuEntry> & /*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<std::string> 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<std::string> font_paths;
for (std::vector <std::string>::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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
FontController *sc = dynamic_cast <FontController *> (cls.operator-> ());
if (sc) {
return sc;
}
}
return 0;
}
// The singleton instance of the library controller
static tl::RegisteredClass<lay::PluginDeclaration> font_controller_decl (new lay::FontController (), 160, "FontController");
}

131
src/lay/layFontController.h Normal file
View File

@ -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 <vector>
#include <string>
#include <QObject>
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<std::string, std::string> > &options) const;
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
void get_menu_entries (std::vector<lay::MenuEntry> &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<FontController> dm_sync_dirs;
void sync_dirs ();
};
}
#endif

View File

@ -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 <QDir>
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<std::string, std::string> > & /*options*/) const
{
// .. nothing yet ..
}
void
LibraryController::get_menu_entries (std::vector<lay::MenuEntry> & /*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<std::string, std::pair<std::string, QDateTime> > new_lib_files;
// build a list of paths vs. technology
std::vector<std::pair<std::string, std::string> > paths;
std::vector<std::string> klayout_path = lay::Application::instance ()->klayout_path ();
for (std::vector<std::string>::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 <std::pair<std::string, std::string> >::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<std::string, std::pair<std::string, QDateTime> >::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<db::Library> 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<std::string> new_names;
for (std::map<std::string, std::pair<std::string, QDateTime> >::const_iterator lf = new_lib_files.begin (); lf != new_lib_files.end (); ++lf) {
new_names.insert (lf->second.first);
}
for (std::map<std::string, std::pair<std::string, QDateTime> >::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<bool, db::lib_id_type> 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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
LibraryController *sc = dynamic_cast <LibraryController *> (cls.operator-> ());
if (sc) {
return sc;
}
}
return 0;
}
// The singleton instance of the library controller
static tl::RegisteredClass<lay::PluginDeclaration> library_controller_decl (new lay::LibraryController (), 150, "LibraryController");
}

View File

@ -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 <vector>
#include <string>
#include <QObject>
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<std::string, std::string> > &options) const;
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
void get_menu_entries (std::vector<lay::MenuEntry> &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<LibraryController> dm_sync_files;
std::map<std::string, std::pair<std::string, QDateTime> > m_lib_files;
void sync_files ();
};
}
#endif

View File

@ -27,6 +27,7 @@
#include <QMutexLocker>
#include <QTimer>
#include <QClipboard>
#include <QFrame>
#include <stdio.h>
@ -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<QFrame *> (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 ());
}
}
}
}

View File

@ -26,11 +26,13 @@
#include "ui_LogViewerDialog.h"
#include "tlLog.h"
#include "layCommon.h"
#include <QTimer>
#include <QMutex>
#include <QDialog>
#include <QAbstractListModel>
#include <QToolButton>
#include <deque>
#include <string>
@ -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

View File

@ -1083,7 +1083,7 @@ void MacroCollection::on_macro_changed (Macro *macro)
}
}
void MacroCollection::collect_used_nodes(std::set <Macro *> &macros, std::set <MacroCollection *> &macro_collections)
void MacroCollection::collect_used_nodes (std::set <Macro *> &macros, std::set <MacroCollection *> &macro_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<lay::MacroCollection *> 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<lay::MacroCollection *>::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) {
ret = true;
sync_macros (*m, 0);
current->erase (*m);
}
std::vector<lay::Macro *> 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<lay::Macro *>::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) {

View File

@ -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
*/

View File

@ -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<std::string, std::string> ("macros", tl::to_string (QObject::tr ("Ruby"))));
m_macro_categories.push_back (std::pair<std::string, std::string> ("pymacros", tl::to_string (QObject::tr ("Python"))));
m_macro_categories.push_back (std::pair<std::string, std::string> ("drc", tl::to_string (QObject::tr ("DRC"))));
// Scan for macros and set interpreter path
for (std::vector <InternalPathDescriptor>::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<ExternalPathDescriptor> external_paths;
// Add additional places where the technologies define some macros
std::map<std::string, std::vector<std::string> > tech_names_by_path;
std::map<std::string, std::vector<std::string> > grain_names_by_path;
std::set<std::string> 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<std::string, std::vector<std::string> >::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<std::string, std::vector<std::string> >::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<lay::MacroCollection *> folders_to_delete;
// determine the paths that will be in use
std::map<std::string, const ExternalPathDescriptor *> new_folders_by_path;
for (std::vector<ExternalPathDescriptor>::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<std::string, const ExternalPathDescriptor *> prev_folders_by_path;
for (std::vector<ExternalPathDescriptor>::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<std::string, const ExternalPathDescriptor *>::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<lay::MacroCollection *>::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<lay::MacroCollection *> new_folders;
for (std::vector<ExternalPathDescriptor>::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<lay::MacroCollection *>::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<lay::MacroCollection *>::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<std::pair<std::string, std::string> > &mc = lay::Application::instance ()->macro_categories ();
const std::vector<std::pair<std::string, std::string> > &mc = macro_categories ();
for (std::vector<std::pair<std::string, std::string> >::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<std::pair<std::string, std::string> > 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 ()
{

View File

@ -29,6 +29,7 @@
#include "layMacro.h"
#include "tlObject.h"
#include "tlDeferredExecution.h"
#include "tlFileSystemWatcher.h"
#include <string>
#include <map>
@ -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<std::string, std::string> > &macro_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<MacroController> dm_do_update_menu_with_macros;
bool m_no_implicit_macros;
std::vector<lay::Action> m_macro_actions;
std::map<QAction *, lay::Macro *> m_action_to_macro;
lay::MacroCollection m_temp_macros;
std::vector< std::pair<std::string, std::string> > m_macro_categories;
std::vector<InternalPathDescriptor> m_internal_paths;
std::vector<ExternalPathDescriptor> m_external_paths;
tl::FileSystemWatcher *m_file_watcher;
tl::DeferredMethod<MacroController> dm_do_update_menu_with_macros;
tl::DeferredMethod<MacroController> dm_do_sync_with_external_sources;
tl::DeferredMethod<MacroController> dm_sync_file_watcher;
tl::DeferredMethod<MacroController> dm_sync_files;
void sync_implicit_macros (bool ask_before_autorun);
void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set<std::string> &groups, const lay::Technology *tech, std::vector<std::pair<std::string, std::string> > *key_bindings);
void do_update_menu_with_macros ();
void do_sync_with_external_sources ();
void sync_file_watcher ();
void sync_files ();
};
}

View File

@ -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<MacroEditorPage *> (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 <lay::Macro *> used_macros;
std::set <lay::MacroCollection *> used_collections;
collection->collect_used_nodes (used_macros, used_collections);
for (std::set <lay::Macro *>::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) {
if (mp_run_macro == *mc) {
mp_run_macro = 0;
}
std::map <Macro *, MacroEditorPage *>::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 <Macro *, MacroEditorPage *>::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<Macro *, MacroEditorPage *>::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<MacroEditorPage *> (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<MacroEditorPage *> (tabWidget->widget (index));
if (! page || ! page->macro ()) {
if (! page) {
delete tabWidget->currentWidget ();
return;
}
std::map <Macro *, MacroEditorPage *>::iterator p = m_tab_widgets.find (page->macro ());
if (p != m_tab_widgets.end ()) {
m_tab_widgets.erase (p);
for (std::map <Macro *, MacroEditorPage *>::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<lay::MacroCollection *> 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<lay::MacroCollection *>::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) {
ret = true;
sync_macros (*m, 0);
current->erase (*m);
}
std::vector<lay::Macro *> 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<lay::Macro *>::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 <lay::Macro *> used_macros;
std::set <lay::MacroCollection *> used_collections;
collection->collect_used_nodes (used_macros, used_collections);
for (std::set <lay::Macro *>::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) {
std::map <Macro *, MacroEditorPage *>::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<MacroEditorPage *> (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<lay::MacroEditorTree *>::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) {
(*mt)->setEditTriggers (m_in_exec ? QAbstractItemView::NoEditTriggers : QAbstractItemView::SelectedClicked);

View File

@ -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<QString> m_changed_files, m_removed_files;
tl::DeferredMethod<MacroEditorDialog> dm_refresh_file_watcher;
tl::DeferredMethod<MacroEditorDialog> dm_update_ui_to_run_mode;
};
}

View File

@ -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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::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<lay::MacroCollection *> nm = lay::Application::instance ()->sync_tech_macro_locations ();
bool has_autorun = false;
for (std::vector<lay::MacroCollection *>::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<lay::MacroCollection *>::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 ();
}
}

View File

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

View File

@ -113,8 +113,21 @@
<file alias="upup.png">images/upup.png</file>
<file alias="waived.png">images/waived.png</file>
<file alias="yellow_flag.png">images/yellow_flag.png</file>
<file alias="salt.png">images/salt.png</file>
<file alias="salt_icon.png">images/salt_icon.png</file>
<file alias="warn.png">images/warn.png</file>
<file alias="warn_16.png">images/warn_16.png</file>
<file alias="empty_16.png">images/empty_16.png</file>
<file alias="error_16.png">images/error_16.png</file>
<file alias="info_16.png">images/info_16.png</file>
<file alias="marked_24.png">images/marked_24.png</file>
<file alias="marked_64.png">images/marked_64.png</file>
<file alias="marked_16.png">images/marked_16.png</file>
<file alias="folder_12.png">images/folder_12.png</file>
<file alias="file_12.png">images/file_12.png</file>
<file alias="empty_12.png">images/empty_12.png</file>
</qresource>
<qresource prefix="/syntax" >
<qresource prefix="/syntax">
<file alias="ruby.xml">syntax/ruby.xml</file>
<file alias="python.xml">syntax/python.xml</file>
</qresource>

499
src/lay/laySalt.cc Normal file
View File

@ -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 <QFileInfo>
#include <QDir>
#include <QUrl>
#include <QResource>
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<std::string, SaltGrain *>::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 <SaltGrain *> (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 <SaltGrains &> (*g));
}
}
namespace {
struct NameAndTopoIndexCompare
{
NameAndTopoIndexCompare (const std::map<std::string, int> &topo_index)
: mp_topo_index (&topo_index)
{
// .. nothing yet ..
}
bool operator () (lay::SaltGrain *a, lay::SaltGrain *b) const
{
std::map<std::string, int>::const_iterator ti_a = mp_topo_index->find (a->name ());
std::map<std::string, int>::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<std::string, int> *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<SaltGrain *>::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<std::string, int> topological_index;
for (std::map<std::string, SaltGrain *>::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<std::string, SaltGrain *>::const_iterator g = m_grains_by_name.begin (); g != m_grains_by_name.end (); ++g) {
int index = topological_index [g->first];
for (std::vector<SaltGrain::Dependency>::const_iterator d = g->second->dependencies ().begin (); d != g->second->dependencies ().end (); ++d) {
std::map<std::string, int>::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 <SaltGrains &> (*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<std::string> name_parts = tl::split (target.name (), "/");
for (std::vector<std::string>::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;
}
}

218
src/lay/laySalt.h Normal file
View File

@ -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 <QObject>
#include <map>
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<SaltGrain *>::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<Salt *> (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<SaltGrain *> mp_flat_grains;
std::map<std::string, SaltGrain *> m_grains_by_name;
void validate ();
void invalidate ();
void add_collection_to_flat (lay::SaltGrains &gg);
};
}
#endif

View File

@ -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 <QDir>
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 <lay::MainWindow *> (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<std::string, std::string> > &options) const
{
options.push_back (std::pair<std::string, std::string> (cfg_salt_manager_window_state, ""));
}
void
SaltController::get_menu_entries (std::vector<lay::MenuEntry> & /*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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
SaltController *sc = dynamic_cast <SaltController *> (cls.operator-> ());
if (sc) {
return sc;
}
}
return 0;
}
// The singleton instance of the salt controller
static tl::RegisteredClass<lay::PluginDeclaration> salt_controller_decl (new lay::SaltController (), 100, "SaltController");
}

178
src/lay/laySaltController.h Normal file
View File

@ -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 <vector>
#include <string>
#include <QObject>
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<std::string, std::string> > &options) const;
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
void get_menu_entries (std::vector<lay::MenuEntry> &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<SaltController> dm_sync_file_watcher;
tl::DeferredMethod<SaltController> dm_sync_files;
void sync_file_watcher ();
void sync_files ();
};
}
#endif

View File

@ -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 <QTreeWidgetItem>
#include <QMessageBox>
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<std::string, Descriptor> registry;
// remove those registered entries which don't need to be updated
registry = m_registry;
for (std::map<std::string, Descriptor>::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<std::string, Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
for (std::vector<SaltGrain::Dependency>::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) {
std::map<std::string, Descriptor>::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<std::string, Descriptor>::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<std::string, Descriptor>::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<std::string, Descriptor>::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<std::string, Descriptor>::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<std::string, Descriptor>::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;
}
}

View File

@ -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 <QObject>
#include <string>
#include <map>
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<std::string, Descriptor> m_registry;
bool needs_iteration ();
void fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress);
};
}
#endif

465
src/lay/laySaltGrain.cc Normal file
View File

@ -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 <QDir>
#include <QFileInfo>
#include <QBuffer>
#include <QResource>
#include <QUrl>
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<lay::SaltGrain> 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 ();
}
}
}

446
src/lay/laySaltGrain.h Normal file
View File

@ -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 <QTime>
#include <QImage>
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<Dependency> &dependencies () const
{
return m_dependencies;
}
/**
* @brief Gets the dependencies of the grain (non-const)
*/
std::vector<Dependency> &dependencies ()
{
return m_dependencies;
}
/**
* @brief Dependency iterator (begin)
*/
std::vector<Dependency>::const_iterator begin_dependencies () const
{
return m_dependencies.begin ();
}
/**
* @brief Dependency iterator (end)
*/
std::vector<Dependency>::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<Dependency> m_dependencies;
};
}
#endif

View File

@ -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 <QTextStream>
#include <QBuffer>
#include <QIcon>
#include <QPainter>
#include <QDir>
#include <QFileInfo>
#include <QDesktopServices>
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 << "<img src=\":/empty_12.png\"/>&nbsp;&nbsp;";
}
stream << "<img src=\":/folder_12.png\"/>&nbsp;&nbsp;<i>";
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 << "</i><br/>\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 << "<img src=\":/empty_12.png\"/>&nbsp;&nbsp;";
}
stream << "<img src=\":/file_12.png\"/>&nbsp;&nbsp;" << tl::escaped_to_html (tl::to_string (*e)).c_str () << "<br/>\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 << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/></head><body>";
stream << "<table cellpadding=\"6\"><tr>";
stream << "<td><img src=\":/icon\" width=\"64\" height=\"64\"/></td>";
stream << "<td>";
stream << "<h1>";
stream << tl::to_qstring (tl::escaped_to_html (g->name ())) << " " << tl::to_qstring (tl::escaped_to_html (g->version ()));
stream << "</h1>";
if (! g->title ().empty()) {
stream << "<h3>" << tl::to_qstring (tl::escaped_to_html (g->title ())) << "</h3>";
}
if (g->version ().empty ()) {
stream << "<p><i><font color='gray'>";
stream << QObject::tr ("This package does not have a version. "
"Use the &lt;version&gt; element of the specification file or edit the package properties to provide a version.");
stream << "</font></i></p>";
}
if (g->title ().empty ()) {
stream << "<p><i><font color='gray'>";
stream << QObject::tr ("This package does not have a title. "
"Use the &lt;title&gt; element of the specification file or edit the package properties to provide a title.");
stream << "</font></i></p>";
}
stream << "<p><br/>";
if (! g->doc ().empty ()) {
stream << tl::to_qstring (tl::escaped_to_html (g->doc ()));
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a description. "
"Use the &lt;doc&gt; element of the specification file or edit the package properties to provide a description.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->author ().empty ()) {
stream << "<b>" << QObject::tr ("Author") << ":</b> " << 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 << "<br/>";
stream << "<b>" << QObject::tr ("Released") << ":</b> " << g->authored_time ().date ().toString (Qt::ISODate);
}
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a author information. "
"Use the &lt;author&gt;, &lt;authored-time&gt; and &lt;author-contact&gt; elements of the specification file or edit the package properties to provide authoring information.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->license ().empty ()) {
stream << "<b>" << QObject::tr ("License") << ":</b> " << tl::to_qstring (tl::escaped_to_html (g->license ())) << " ";
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have license information. "
"Use the &lt;license&gt; elements of the specification file or edit the package properties to provide license information.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->api_version ().empty ()) {
stream << "<b>" << QObject::tr ("API version") << ":</b> " << tl::to_qstring (tl::escaped_to_html (g->api_version ())) << " ";
}
stream << "</p>";
stream << "<p>";
if (! g->doc_url ().empty ()) {
stream << "<b>" << QObject::tr ("Documentation link") << ":</b> <a href=\"" << tl::to_qstring (g->eff_doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->eff_doc_url ())) << "</a>";
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a documentation link. "
"Use the &lt;doc-url&gt; element of the specification file or edit the package properties to provide a link.");
stream << "</font></i>";
}
stream << "</p>";
if (! g->screenshot ().isNull ()) {
stream << "<br/>";
stream << "<h3>" << QObject::tr ("Screenshot") << "</h3><p><img src=\":/screenshot\"/></p>";
}
stream << "<br/>";
stream << "<h3>" << QObject::tr ("Installation") << "</h3>";
if (! g->url ().empty ()) {
stream << "<p><b>" << QObject::tr ("Download URL: ") << "</b>" << tl::to_qstring (tl::escaped_to_html (g->url ())) << "</p>";
}
if (! g->installed_time ().isNull ()) {
stream << "<p><b>" << QObject::tr ("Installed: ") << "</b>" << g->installed_time ().toString () << "</p>";
}
if (! g->dependencies ().empty ()) {
stream << "<p><b>" << QObject::tr ("Depends on: ") << "</b><br/>";
for (std::vector<lay::SaltGrain::Dependency>::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) {
stream << "&nbsp;&nbsp;&nbsp;&nbsp;" << 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)) << "]<br/>";
}
}
stream << "</p>";
}
if (! g->path ().empty ()) {
stream << "<p><b>" << QObject::tr ("Installed files: ") << "</b></p><p>";
produce_listing (stream, QDir (tl::to_qstring (g->path ())), 0);
stream << "</p>";
}
stream << "</td></tr></table>";
stream << "</body></html>";
stream.flush ();
return QString::fromUtf8 (buffer.buffer());
}
}

View File

@ -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 <QTextBrowser>
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

View File

@ -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 <QFileDialog>
#include <QFileInfo>
#include <QTreeWidgetItem>
#include <QItemDelegate>
#include <QPainter>
#include <QCompleter>
#include <QMessageBox>
#include <memory>
#include <map>
#include <set>
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<QLineEdit *> (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<QLineEdit *> (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<QLineEdit *> (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<SaltGrain::Dependency>::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 <std::string, const lay::SaltGrain *>::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 <const lay::SaltGrain *> path;
path.push_back (current);
check_circular_follow (new_dep, path);
}
private:
std::map <std::string, const lay::SaltGrain *> m_name_to_grain;
void check_circular_follow (const lay::SaltGrain *current, std::vector <const lay::SaltGrain *> &path)
{
if (! current) {
return;
}
path.push_back (current);
for (std::vector <const lay::SaltGrain *>::const_iterator p = path.begin (); p != path.end () - 1; ++p) {
if (*p == current) {
circular_reference_error (path);
}
}
for (std::vector<SaltGrain::Dependency>::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 <const lay::SaltGrain *> &path)
{
std::string msg = tl::to_string (QObject::tr ("The following path forms a circular dependency: "));
for (std::vector <const lay::SaltGrain *>::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 <std::string> dep_seen;
for (std::vector<SaltGrain::Dependency>::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;
}
}

View File

@ -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 <QDialog>
#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

264
src/lay/laySaltGrains.cc Normal file
View File

@ -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 <QDir>
#include <QFileInfo>
#include <QResource>
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<lay::SaltGrains> 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);
}
}

210
src/lay/laySaltGrains.h Normal file
View File

@ -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 <list>
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<SaltGrains> collections_type;
typedef collections_type::const_iterator collection_iterator;
typedef std::list<SaltGrain> 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

View File

@ -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 <QTextDocument>
#include <QPainter>
#include <QDir>
#include <QTextStream>
#include <QBuffer>
#include <QResource>
#include <QMessageBox>
#include <QAbstractItemModel>
#include <QStyledItemDelegate>
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<SaltModel *> (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<int> 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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (salt_mine_view_update->model ());
} else {
model = dynamic_cast <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (salt_mine_view_new->model ());
tl_assert (model != 0);
model->begin_update ();
model = dynamic_cast <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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 <SaltModel *> (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<lay::SaltGrain> 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 (
"<html>"
"<body>"
"<font color=\"#c0c0c0\">"
"<h2>Fetching Package Definition ...</h2>"
"<p><b>URL</b>: %1</p>"
"</font>"
"</body>"
"</html>"
)
.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 (
"<html>"
"<body>"
"<font color=\"#ff0000\">"
"<h2>Error Fetching Package Definition</h2>"
"<p><b>URL</b>: %1</p>"
"<p><b>Error</b>: %2</p>"
"</font>"
"</body>"
"</html>"
)
.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 ();
}
}

View File

@ -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 <QDialog>
#include <memory>
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<SaltGrain> m_remote_update_grain;
std::auto_ptr<SaltGrain> m_remote_new_grain;
SaltGrainPropertiesDialog *mp_properties_dialog;
tl::DeferredMethod<SaltManagerDialog> 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

433
src/lay/laySaltModel.cc Normal file
View File

@ -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 <QIcon>
#include <QPainter>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QListView>
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<const QListView *> (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 = "<html><body>";
text += "<h4>";
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 += "</h4>";
if (!g->doc ().empty ()) {
text += "<p>";
text += tl::escaped_to_html (g->doc ());
text += "</p>";
}
std::map<std::string, std::pair<Severity, std::string> >::const_iterator m = m_messages.find (g->name ());
if (m != m_messages.end ()) {
if (m->second.first == Warning || m->second.first == Error) {
text += "<p><font color=\"#ff0000\"><b>" + tl::escaped_to_html (m->second.second) + "</b></font></p>";
} else if (m->second.first == Info) {
text += "<p><font color=\"#c0c0c0\">" + tl::escaped_to_html (m->second.second) + "</font></p>";
} else {
text += "<p>" + tl::escaped_to_html (m->second.second) + "</p>";
}
}
text += "</body></html>";
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<std::string, std::pair<Severity, std::string> >::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<std::string, std::pair<Severity, std::string> >::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<std::string, int>::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<std::string, int>::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);
}
}
}
}
}
}

204
src/lay/laySaltModel.h Normal file
View File

@ -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 <QObject>
#include <QAbstractItemModel>
#include <QStyledItemDelegate>
#include <string>
#include <set>
#include <map>
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<std::string> m_marked;
std::set<std::string> m_disabled;
std::map<std::string, std::pair<Severity, std::string> > m_messages;
std::map<std::string, int> m_display_order;
std::vector<SaltGrain *> 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

View File

@ -0,0 +1,112 @@
<RCC>
<qresource prefix="/salt_templates">
</qresource>
<!-- font template -->
<qresource prefix="/salt_templates/font">
<file alias="grain.xml">salt_templates/font/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/font/fonts">
<file alias="new_font.gds">salt_templates/font/fonts/new_font.gds</file>
</qresource>
<qresource prefix="/salt_templates/font/doc">
<file alias="readme.html">salt_templates/font/doc/readme.html</file>
</qresource>
<!-- lib template -->
<qresource prefix="/salt_templates/lib">
<file alias="grain.xml">salt_templates/lib/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/lib/libraries">
<file alias="new_lib.gds">salt_templates/lib/libraries/new_lib.gds</file>
</qresource>
<qresource prefix="/salt_templates/lib/doc">
<file alias="readme.html">salt_templates/lib/doc/readme.html</file>
</qresource>
<!-- pcell_lib template -->
<qresource prefix="/salt_templates/pcell_lib">
<file alias="grain.xml">salt_templates/pcell_lib/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/pcell_lib/macros">
<file alias="pcell.lym">salt_templates/pcell_lib/macros/pcell.lym</file>
</qresource>
<qresource prefix="/salt_templates/pcell_lib/doc">
<file alias="readme.html">salt_templates/pcell_lib/doc/readme.html</file>
</qresource>
<!-- macro template -->
<qresource prefix="/salt_templates/macro">
<file alias="grain.xml">salt_templates/macro/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/macro/macros">
<file alias="new_macro.lym">salt_templates/macro/macros/new_macro.lym</file>
</qresource>
<qresource prefix="/salt_templates/macro/doc">
<file alias="readme.html">salt_templates/macro/doc/readme.html</file>
</qresource>
<!-- ruby library template -->
<qresource prefix="/salt_templates/ruby_lib">
<file alias="grain.xml">salt_templates/ruby_lib/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/ruby_lib/macros">
<file alias="new_macro.lym">salt_templates/ruby_lib/macros/new_macro.lym</file>
</qresource>
<qresource prefix="/salt_templates/ruby_lib/doc">
<file alias="readme.html">salt_templates/ruby_lib/doc/readme.html</file>
</qresource>
<!-- pymacro template -->
<qresource prefix="/salt_templates/pymacro">
<file alias="grain.xml">salt_templates/pymacro/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/pymacro/pymacros">
<file alias="new_macro.lym">salt_templates/pymacro/pymacros/new_macro.lym</file>
</qresource>
<qresource prefix="/salt_templates/pymacro/doc">
<file alias="readme.html">salt_templates/pymacro/doc/readme.html</file>
</qresource>
<!-- python library template -->
<qresource prefix="/salt_templates/python_lib">
<file alias="grain.xml">salt_templates/python_lib/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/python_lib/pymacros">
<file alias="new_macro.lym">salt_templates/python_lib/pymacros/new_macro.lym</file>
</qresource>
<qresource prefix="/salt_templates/python_lib/doc">
<file alias="readme.html">salt_templates/python_lib/doc/readme.html</file>
</qresource>
<!-- drc template -->
<qresource prefix="/salt_templates/drc">
<file alias="grain.xml">salt_templates/drc/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/drc/drc">
<file alias="new_drc.lym">salt_templates/drc/drc/new_drc.lym</file>
</qresource>
<qresource prefix="/salt_templates/drc/doc">
<file alias="readme.html">salt_templates/drc/doc/readme.html</file>
</qresource>
<!-- tech template -->
<qresource prefix="/salt_templates/tech">
<file alias="grain.xml">salt_templates/tech/grain.xml</file>
</qresource>
<qresource prefix="/salt_templates/tech/tech">
<file alias="tech.lyt">salt_templates/tech/tech/tech.lyt</file>
</qresource>
<qresource prefix="/salt_templates/tech/tech/libraries">
</qresource>
<qresource prefix="/salt_templates/tech/tech/macros">
</qresource>
<qresource prefix="/salt_templates/tech/tech/pymacros">
</qresource>
<qresource prefix="/salt_templates/tech/tech/drc">
</qresource>
<qresource prefix="/salt_templates/tech/doc">
<file alias="readme.html">salt_templates/tech/doc/readme.html</file>
</qresource>
</RCC>

336
src/lay/laySignalHandler.cc Normal file
View File

@ -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 <windows.h>
# include <DbgHelp.h>
# include <Psapi.h>
// get rid of these - we have std::min/max ..
# ifdef min
# undef min
# endif
# ifdef max
# undef max
# endif
#else
# include <dlfcn.h>
# include <execinfo.h>
# include <unistd.h>
#endif
#include <signal.h>
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
}

View File

@ -20,16 +20,24 @@
*/
#ifndef HDR_laySignalHandler
#define HDR_laySignalHandler
#ifndef HDR_layTechnologySelector
#define HDR_layTechnologySelector
#include <QString>
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

View File

@ -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 ("<html><body>To get started with the technology manager, read the documentation provided: <a href=\"int:/about/technology_manager.xml\">About Technology Management</a>.</body></html>")),
"tech-manager-basic-tips");
td.exec_dialog ();
s_first_show = false;
}
}
TechSetupDialog::~TechSetupDialog ()
@ -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<std::string> 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 ("<html><body>To get started with the technology manager, read the documentation provided: <a href=\"int:/about/technology_manager.xml\">About Technology Management</a>.</body></html>")),
"tech-manager-basic-tips");
td.exec_dialog ();
s_first_show = false;
}
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 ("<html><body>Renaming of a technology will neither rename the technology file or the folder the file is stored in.<br/>The file or folder needs to be renamed manually.</body></html>")),
"tech-manager-rename-tip");
td.exec_dialog ();
}
update_tech_tree ();
select_tech (*t);
}
}
@ -740,19 +863,15 @@ TechSetupDialog::update_tech_tree ()
for (std::map <std::string, const lay::Technology *>::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t) {
QFont f (tech_tree->font ());
f.setItalic (! t->second->is_persisted ());
std::string d;
d += t->first;
if (! d.empty () && ! t->second->description ().empty ()) {
d += " - ";
}
d += t->second->description ();
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 <std::string> tc_names = t->second->component_names ();
std::map <std::string, const lay::TechnologyComponent *> 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<std::pair<std::string, std::string> > &mc = lay::Application::instance ()->macro_categories ();
for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
tci = new QTreeWidgetItem (ti);
tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second)));
tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (std::string ("_macros_") + c->first)));
tci->setData (0, Qt::FontRole, QVariant (f));
if (lay::MacroController::instance ()) {
const std::vector<std::pair<std::string, std::string> > &mc = lay::MacroController::instance ()->macro_categories ();
for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
tci = new QTreeWidgetItem (ti);
tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second)));
tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (std::string ("_macros_") + c->first)));
tci->setData (0, Qt::FontRole, QVariant (f));
}
}
for (std::map <std::string, const lay::TechnologyComponent *>::const_iterator c = tc_by_name.begin (); c != tc_by_name.end (); ++c) {
@ -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<std::pair<std::string, std::string> > &mc = lay::Application::instance ()->macro_categories ();
for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
tce_widget = new TechMacrosPage (this, c->first, c->second);
tce_widget->setEnabled (t->is_persisted ());
tce_widget->set_technology (t, 0);
tc_stack->addWidget (tce_widget);
m_component_editors.insert (std::make_pair (std::string ("_macros_") + c->first, tce_widget));
if (lay::MacroController::instance ()) {
const std::vector<std::pair<std::string, std::string> > &mc = lay::MacroController::instance ()->macro_categories ();
for (std::vector<std::pair<std::string, std::string> >::const_iterator c = mc.begin (); c != mc.end (); ++c) {
tce_widget = new TechMacrosPage (this, c->first, c->second);
tce_widget->setEnabled (!t->is_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))));
}

View File

@ -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 <std::string, lay::TechnologyComponent *> m_technology_components;
lay::TechnologyComponentEditor *mp_current_editor;
lay::TechnologyComponent *mp_current_tech_component;
bool m_current_tech_changed_enabled;
};
class LAY_PUBLIC TechComponentSetupDialog

View File

@ -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 <QMessageBox>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
TechnologyController *tc = dynamic_cast <TechnologyController *> (cls.operator-> ());
if (tc) {
return tc;
}
}
return 0;
}
void
TechnologyController::initialize (lay::PluginRoot *root)
{
mp_mw = dynamic_cast <lay::MainWindow *> (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<std::string, std::string> > &options) const
{
options.push_back (std::pair<std::string, std::string> (cfg_initial_technology, ""));
options.push_back (std::pair<std::string, std::string> (cfg_tech_editor_window_state, ""));
}
void
TechnologyController::get_menu_entries (std::vector<lay::MenuEntry> &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<std::string> menu_entries = pr->menu ()->group ("tech_selector_group");
for (std::vector<std::string>::const_iterator m = menu_entries.begin (); m != menu_entries.end (); ++m) {
lay::Action action = pr->menu ()->action (*m);
action.set_title (title);
}
std::map<std::string, const lay::Technology *> 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<std::string, const lay::Technology *>::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<std::string> tech_group = pr->menu ()->group ("tech_selector_group");
for (std::vector<std::string>::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<std::string> items = pr->menu ()->items (*t);
for (std::vector<std::string>::const_iterator i = items.begin (); i != items.end (); ++i) {
pr->menu ()->delete_item (*i);
}
}
m_tech_actions.clear ();
std::map<std::string, const lay::Technology *> 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<std::string, const lay::Technology *>::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<std::string>::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<std::string> 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<std::string> paths = m_paths;
std::set<std::string> readonly_paths;
std::map<std::string, std::string> 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<std::string>::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<std::string, std::string>::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<lay::Technology>::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<lay::PluginDeclaration> config_decl (new TechnologyController (), 110, "TechnologyController");
}

View File

@ -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 <vector>
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<std::string, std::string> > &options) const;
void get_menu_entries (std::vector<lay::MenuEntry> &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 <lay::Action> 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<std::string> m_paths;
std::vector<lay::Technology> 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

View File

@ -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<std::string, std::string> > &options) const
{
options.push_back (std::pair<std::string, std::string> (cfg_initial_technology, ""));
}
void get_menu_entries (std::vector<lay::MenuEntry> &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 <lay::Action> 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<std::string> menu_entries = pr->menu ()->group ("tech_selector_group");
for (std::vector<std::string>::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<std::string> tech_group = pr->menu ()->group ("tech_selector_group");
for (std::vector<std::string>::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<std::string> items = pr->menu ()->items (*t);
for (std::vector<std::string>::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<std::string>::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<lay::PluginDeclaration> config_decl (new TechnologySelector (), 9000, "TechnologySelector");
}

View File

@ -0,0 +1,45 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
<li>Specify dependencies if you plan to use items from other packages</li>
</ul>
<p>
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".
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>The New DRC Script</description>
<category>drc</category>
<prolog/>
<epilog/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<menu-path/>
<interpreter>dsl</interpreter>
<dsl-interpreter-name>drc-dsl-xml</dsl-interpreter-name>
<text># 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) &lt; 1.0 um")
</text>
</klayout-macro>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>drc</name>
<version>0.0</version>
<title>DRC template</title>
<doc>This template provides a template for a DRC script</doc>
<doc-url>doc/readme.html</doc-url>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,43 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Change the name of the font layout file to a unique name</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,46 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
<li>Specify dependencies if you plan to use items from other packages</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>The New Macro</description>
<prolog/>
<epilog/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<menu-path/>
<interpreter>ruby</interpreter>
<dsl-interpreter-name/>
<text># 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.
</text>
</klayout-macro>

View File

@ -0,0 +1,46 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
<li>Specify dependencies if you plan to use items from other packages</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>The New Macro</description>
<prolog/>
<epilog/>
<autorun>false</autorun>
<autorun-early>true</autorun-early>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<menu-path/>
<interpreter>ruby</interpreter>
<dsl-interpreter-name/>
<text># 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) &amp;&amp; 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 &lt; PCellDeclarationHelper
include RBA
def initialize
# Important: initialize the super class
super
# declare the parameters
# i.e. param(:l, TypeLayer, "Layer", :default =&gt; LayerInfo::new(1, 0))
# param(:s, TypeShape, "", :default =&gt; 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
</text>
</klayout-macro>

View File

@ -0,0 +1,46 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
<li>Specify dependencies if you plan to use items from other packages</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>The New Macro</description>
<prolog/>
<epilog/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<menu-path/>
<interpreter>python</interpreter>
<dsl-interpreter-name/>
<text># 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.
</text>
</klayout-macro>

View File

@ -0,0 +1,46 @@
<html>
<body>
<h1>Your new Package</h1>
<p>
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.
</p>
<p>
Here is what you should do:
</p>
<ul>
<li>Enter your author details</li>
<li>Choose a license model</li>
<li>Provide an icon and optionally a screenshot image</li>
<li>Specify dependencies if you plan to use items from other packages</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More