Merge branch 'salt'
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ public:
|
|||
*/
|
||||
const std::vector<PCellParameterDeclaration> ¶meter_declarations () const
|
||||
{
|
||||
if (! m_has_parameter_declarations) {
|
||||
if (! m_has_parameter_declarations || ! wants_parameter_declaration_caching ()) {
|
||||
m_parameter_declarations = get_parameter_declarations ();
|
||||
m_has_parameter_declarations = true;
|
||||
}
|
||||
|
|
@ -495,6 +495,18 @@ public:
|
|||
*/
|
||||
pcell_parameters_type map_parameters (const std::map<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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/fonts">
|
||||
<file>std_font.gds</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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><a href="%1">Open link</a></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 "GPLv3" or "MIT")</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 "1.5" or "2.1.3")</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. "0.25")</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>
|
||||
|
|
@ -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 "group/package" 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>
|
||||
|
|
@ -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 "Ok" to install or update these packages or "Cancel" 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
) +
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
) +
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 149 B |
|
After Width: | Height: | Size: 674 B |
|
After Width: | Height: | Size: 284 B |
|
After Width: | Height: | Size: 358 B |
|
After Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 336 B |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 637 B |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> > ¯o_categories () const
|
||||
{
|
||||
return m_macro_categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a reference to the Ruby interpreter
|
||||
*/
|
||||
|
|
@ -265,11 +257,6 @@ public:
|
|||
return ! m_no_gui;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For debugging purposes: get a symbol name (a description actually) from an address
|
||||
*/
|
||||
static QString symbol_name_from_address (const QString &mod_name, size_t addr);
|
||||
|
||||
/**
|
||||
* @brief Reset config to global configuration
|
||||
*/
|
||||
|
|
@ -309,6 +296,7 @@ public:
|
|||
private:
|
||||
void shutdown ();
|
||||
void finish ();
|
||||
std::vector<std::string> scan_global_modules ();
|
||||
|
||||
enum file_type {
|
||||
layout_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");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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 ());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1083,7 +1083,7 @@ void MacroCollection::on_macro_changed (Macro *macro)
|
|||
}
|
||||
}
|
||||
|
||||
void MacroCollection::collect_used_nodes(std::set <Macro *> ¯os, std::set <MacroCollection *> ¯o_collections)
|
||||
void MacroCollection::collect_used_nodes (std::set <Macro *> ¯os, std::set <MacroCollection *> ¯o_collections)
|
||||
{
|
||||
for (MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) {
|
||||
macro_collections.insert (c->second);
|
||||
|
|
@ -1173,7 +1173,7 @@ MacroCollection::make_readonly (bool f)
|
|||
}
|
||||
|
||||
MacroCollection *
|
||||
MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly)
|
||||
MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly, bool force_create)
|
||||
{
|
||||
if (! path.empty () && path[0] == ':') {
|
||||
readonly = true;
|
||||
|
|
@ -1183,15 +1183,25 @@ MacroCollection::add_folder (const std::string &description, const std::string &
|
|||
|
||||
if (! file_info.exists ()) {
|
||||
|
||||
// Try to create the folder since it does not exist yet
|
||||
if (tl::verbosity () >= 20) {
|
||||
tl::log << "Folder does not exist yet - trying to create it: " << path;
|
||||
}
|
||||
if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) {
|
||||
if (tl::verbosity () >= 10) {
|
||||
tl::error << "Unable to create folder path: " << path;
|
||||
// Try to create the folder since it does not exist yet or skip that one
|
||||
if (! force_create) {
|
||||
|
||||
if (tl::verbosity () >= 20) {
|
||||
tl::log << "Folder does not exist - skipping: " << path;
|
||||
}
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
|
||||
if (tl::verbosity () >= 20) {
|
||||
tl::log << "Folder does not exist yet - trying to create it: " << path;
|
||||
}
|
||||
if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) {
|
||||
if (tl::verbosity () >= 10) {
|
||||
tl::error << "Unable to create folder path: " << path;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
file_info.refresh ();
|
||||
|
|
@ -1666,6 +1676,91 @@ MacroCollection &MacroCollection::root ()
|
|||
return ms_root;
|
||||
}
|
||||
|
||||
static bool sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (actual) {
|
||||
current->make_readonly (actual->is_readonly ());
|
||||
}
|
||||
|
||||
std::vector<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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 ()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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> > ¯o_categories () const
|
||||
{
|
||||
return m_macro_categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the singleton instance for this object
|
||||
*/
|
||||
|
|
@ -135,20 +159,78 @@ public:
|
|||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Update the menu with macros bound to a menu
|
||||
* @brief Updates the menu with macros bound to a menu
|
||||
*/
|
||||
void update_menu_with_macros ();
|
||||
void macro_collection_changed ();
|
||||
|
||||
/**
|
||||
* @brief Called when the technologies or the salt got changed
|
||||
*/
|
||||
void sync_with_external_sources ();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Called when the file watcher detects a change in the file system
|
||||
*/
|
||||
void file_watcher_triggered ();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief A structure describing an external macro location
|
||||
*/
|
||||
struct ExternalPathDescriptor
|
||||
{
|
||||
ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lay::MacroCollection::FolderType _type, bool _readonly)
|
||||
: path (_path), description (_description), cat (_cat), type (_type), readonly (_readonly)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
std::string path;
|
||||
std::string description;
|
||||
std::string cat;
|
||||
lay::MacroCollection::FolderType type;
|
||||
bool readonly;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A structure describing an internal macro location
|
||||
*/
|
||||
struct InternalPathDescriptor
|
||||
{
|
||||
InternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, bool _readonly)
|
||||
: path (_path), description (_description), cat (_cat), readonly (_readonly)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
std::string path;
|
||||
std::string description;
|
||||
std::string cat;
|
||||
bool readonly;
|
||||
};
|
||||
|
||||
lay::MacroEditorDialog *mp_macro_editor;
|
||||
lay::MainWindow *mp_mw;
|
||||
tl::DeferredMethod<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 ();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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\"/> ";
|
||||
}
|
||||
stream << "<img src=\":/folder_12.png\"/> <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\"/> ";
|
||||
}
|
||||
stream << "<img src=\":/file_12.png\"/> " << 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 <version> 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 <title> 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 <doc> 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 <author>, <authored-time> and <author-contact> 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 <license> 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 <doc-url> 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 << " " << 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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))));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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) < 1.0 um")
|
||||
</text>
|
||||
</klayout-macro>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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) && remove_const(:PCell)
|
||||
|
||||
# The PCell declaration
|
||||
# Each PCell must provide a declaration. It is recommended to use the PCell name as the class name.
|
||||
# TODO: change the class name
|
||||
class PCell < PCellDeclarationHelper
|
||||
|
||||
include RBA
|
||||
|
||||
def initialize
|
||||
|
||||
# Important: initialize the super class
|
||||
super
|
||||
|
||||
# declare the parameters
|
||||
# i.e. param(:l, TypeLayer, "Layer", :default => LayerInfo::new(1, 0))
|
||||
# param(:s, TypeShape, "", :default => DPoint::new(0, 0))
|
||||
|
||||
end
|
||||
|
||||
def display_text_impl
|
||||
# Provide a descriptive text for the cell
|
||||
"TODO: create description"
|
||||
end
|
||||
|
||||
def coerce_parameters_impl
|
||||
# TODO: use x to access parameter x and set_x to modify it's value
|
||||
end
|
||||
|
||||
def produce_impl
|
||||
# TODO: produce the cell content
|
||||
# i.e. cell.shapes(l_layer).insert(Polygon.new(...))
|
||||
end
|
||||
|
||||
# optional:
|
||||
# def can_create_from_shape_impl
|
||||
# TODO: determine if we have a shape that we can use to derive the
|
||||
# PCell parameters from and return true in that case
|
||||
# end
|
||||
#
|
||||
# optional:
|
||||
# def parameters_from_shape_impl
|
||||
# TODO: change parameters using set_x to reflect the parameter for the
|
||||
# given shape
|
||||
# end
|
||||
#
|
||||
# optional:
|
||||
# def transformation_from_shape_impl
|
||||
# TODO: return a RBA::Trans object for the initial transformation of
|
||||
# the instance
|
||||
# end
|
||||
|
||||
end
|
||||
|
||||
# TODO: add more PCell classes ..
|
||||
|
||||
# Register a new library with the PCells
|
||||
library = Library::new
|
||||
|
||||
# TODO: change the description
|
||||
library.description = "My PCell package"
|
||||
|
||||
# register the PCell declarations
|
||||
# TODO: change the names
|
||||
library.layout.register_pcell("PCell", PCell::new)
|
||||
|
||||
# TODO: register more PCell declarations ...
|
||||
|
||||
# register our new library with the name "PCellPackage"
|
||||
# TODO: change the library name
|
||||
library.register("PCellPackage")
|
||||
|
||||
end
|
||||
</text>
|
||||
</klayout-macro>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
||||