Merge branch 'salt' into controllers

This commit is contained in:
Matthias Koefferlein 2017-04-16 21:05:06 +02:00
commit 209b16f3ea
63 changed files with 9086 additions and 170 deletions

View File

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

View File

@ -0,0 +1,837 @@
<?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="7" 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="10" 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="4" column="2" colspan="3">
<widget class="QLineEdit" name="author"/>
</item>
<item row="8" column="4">
<widget class="QLabel" name="open_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;a href=&quot;%1&quot;&gt;Open link&lt;/a&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="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="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="12" 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="7" 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="14" 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="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="13" 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="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="3" column="2" colspan="3">
<widget class="QLineEdit" name="title"/>
</item>
<item row="9" 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="5" column="2" colspan="3">
<widget class="QLineEdit" name="author_contact"/>
</item>
<item row="6" column="2">
<widget class="QFrame" name="frame_5">
<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="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="13" 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="11" 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="0" column="3" colspan="2">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(use numeric versions like &quot;1.5&quot; or &quot;2.1.3&quot;)</string>
</property>
</widget>
</item>
<item row="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="11" 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="8" 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="6" column="3" colspan="2">
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>(license information like &quot;GPLv3&quot; or &quot;MIT&quot;)</string>
</property>
</widget>
</item>
<item row="8" 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>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>lay::AlertLogButton</class>
<extends>QToolButton</extends>
<header>layLogViewerDialog.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>version</tabstop>
<tabstop>title</tabstop>
<tabstop>author</tabstop>
<tabstop>author_contact</tabstop>
<tabstop>license</tabstop>
<tabstop>doc</tabstop>
<tabstop>doc_url</tabstop>
<tabstop>icon_config_button</tabstop>
<tabstop>icon_delete_button</tabstop>
<tabstop>screenshot_config_button</tabstop>
<tabstop>screenshot_delete_button</tabstop>
<tabstop>dependencies</tabstop>
<tabstop>add_dependency</tabstop>
<tabstop>remove_dependency</tabstop>
</tabstops>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SaltGrainPropertiesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>546</x>
<y>425</y>
</hint>
<hint type="destinationlabel">
<x>561</x>
<y>435</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SaltGrainPropertiesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>638</x>
<y>418</y>
</hint>
<hint type="destinationlabel">
<x>649</x>
<y>433</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

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

View File

@ -0,0 +1,798 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SaltManagerDialog</class>
<widget class="QDialog" name="SaltManagerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>692</width>
<height>440</height>
</rect>
</property>
<property name="windowTitle">
<string>Salt Package Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTabWidget" name="mode_tab">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Browse Installed Packages</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="rightMargin">
<number>4</number>
</property>
<item>
<widget class="QFrame" name="frame_2">
<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="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>2</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="QToolButton" name="create_button">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Create New Package</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>
<widget class="QToolButton" name="delete_button">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Uninstall Package</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>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="layResources.qrc">:/find.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="lay::DecoratedLineEdit" name="search_installed_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="list_stack">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_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="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>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_5">
<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="frame_3">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<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="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>343</width>
<height>207</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="1">
<spacer name="verticalSpacer">
<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="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="layResources.qrc">:/salt.png</pixmap>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>&lt;h1&gt;Salt Package Manager&lt;/h1&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>&lt;html&gt;&lt;body&gt;&lt;h4&gt;No packages are installed currently.&lt;/h4&gt;&lt;p&gt;Use&lt;br/&gt;
&lt;table&gt;
&lt;tr&gt;&lt;td&gt;The &quot;Install New Packages&quot; tab to install a package&lt;br/&gt;from an external repository&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;The &lt;a href=&quot;:add&quot;&gt;&lt;img src=&quot;:/add.png&quot;&gt;&lt;/a&gt; button to create a new package&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<spacer name="verticalSpacer_2">
<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>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="details_frame">
<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">
<property name="leftMargin">
<number>4</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="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="edit_button">
<property name="toolTip">
<string>Edit Package Details</string>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/edit.png</normaloff>:/edit.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="lay::SaltGrainDetailsTextWidget" name="details_text">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Install or Update Packages</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QSplitter" name="splitter_new">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="rightMargin">
<number>4</number>
</property>
<item>
<widget class="QFrame" name="frame_5">
<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>2</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="QToolButton" name="mark_button">
<property name="toolTip">
<string>Mark or unmark for installation</string>
</property>
<property name="text">
<string>Mark</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/marked_16.png</normaloff>:/marked_16.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>126</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="layResources.qrc">:/find.png</pixmap>
</property>
</widget>
</item>
<item>
<widget class="lay::DecoratedLineEdit" name="search_new_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</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="QVBoxLayout" name="verticalLayout_8">
<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="QListView" name="salt_mine_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>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="details_new_frame">
<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="QVBoxLayout" name="verticalLayout_10">
<property name="leftMargin">
<number>4</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="frame_4">
<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_6">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="layResources.qrc">
<normaloff>:/empty_16.png</normaloff>:/empty_16.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="lay::SaltGrainDetailsTextWidget" name="details_new_text">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<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_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="QPushButton" name="apply_button">
<property name="text">
<string>Apply</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="apply_label">
<property name="text">
<string>Set in code</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>563</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</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="actionUnmarkAll">
<property name="text">
<string>Unmark all</string>
</property>
</action>
<action name="actionShowMarkedOnly">
<property name="text">
<string>Show marked only</string>
</property>
</action>
<action name="actionShowAll">
<property name="text">
<string>Show all</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>lay::DecoratedLineEdit</class>
<extends>QLineEdit</extends>
<header>layWidgets.h</header>
</customwidget>
<customwidget>
<class>lay::SaltGrainDetailsTextWidget</class>
<extends>QTextBrowser</extends>
<header>laySaltGrainDetailsTextWidget.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mode_tab</tabstop>
<tabstop>search_installed_edit</tabstop>
<tabstop>salt_view</tabstop>
<tabstop>details_text</tabstop>
<tabstop>edit_button</tabstop>
<tabstop>search_new_edit</tabstop>
<tabstop>mark_button</tabstop>
<tabstop>salt_mine_view</tabstop>
<tabstop>details_new_text</tabstop>
<tabstop>toolButton</tabstop>
<tabstop>apply_button</tabstop>
<tabstop>scrollArea</tabstop>
</tabstops>
<resources>
<include location="layResources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>clicked(QAbstractButton*)</signal>
<receiver>SaltManagerDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>635</x>
<y>421</y>
</hint>
<hint type="destinationlabel">
<x>653</x>
<y>432</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

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

View File

@ -539,6 +539,10 @@ Class<lay::MainWindow> decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "MainWind
"@brief 'cm_technologies' action (bound to a menu)"
"\nThis method has been added in version 0.22."
) +
gsi::method ("cm_packages", &lay::MainWindow::cm_packages,
"@brief 'cm_packages' action (bound to a menu)"
"\nThis method has been added in version 0.25."
) +
gsi::method ("cm_open_too", &lay::MainWindow::cm_open_too,
"@brief 'cm_open_too' action (bound to a menu)"
) +

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

@ -47,7 +47,15 @@ HEADERS = \
layCommon.h \
layConfig.h \
layMacroController.h \
layTechnologyController.h
layTechnologyController.h \
laySalt.h \
laySaltGrain.h \
laySaltGrains.h \
laySaltManagerDialog.h \
laySaltGrainDetailsTextWidget.h \
laySaltGrainPropertiesDialog.h \
laySaltDownloadManager.h \
laySaltModel.h
FORMS = \
ClipDialog.ui \
@ -92,7 +100,11 @@ FORMS = \
XORToolDialog.ui \
TechLoadOptionsEditorPage.ui \
TechSaveOptionsEditorPage.ui \
MainConfigPage7.ui
MainConfigPage7.ui \
SaltManagerDialog.ui \
SaltGrainPropertiesDialog.ui \
SaltGrainTemplateSelectionDialog.ui \
SaltManagerInstallConfirmationDialog.ui
SOURCES = \
gsiDeclLayApplication.cc \
@ -136,13 +148,22 @@ SOURCES = \
layTextProgress.cc \
layVersion.cc \
layMacroController.cc \
layTechnologyController.cc
layTechnologyController.cc \
laySalt.cc \
laySaltGrain.cc \
laySaltGrains.cc \
laySaltManagerDialog.cc \
laySaltGrainDetailsTextWidget.cc \
laySaltGrainPropertiesDialog.cc \
laySaltDownloadManager.cc \
laySaltModel.cc
RESOURCES = layBuildInMacros.qrc \
layHelpResources.qrc \
layLayoutStatistics.qrc \
layMacroTemplates.qrc \
layResources.qrc \
laySaltTemplates.qrc
INCLUDEPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt
DEPENDPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt

View File

@ -27,6 +27,7 @@
#include <QMutexLocker>
#include <QTimer>
#include <QClipboard>
#include <QFrame>
#include <stdio.h>
@ -97,25 +98,30 @@ LogReceiver::begin ()
// -----------------------------------------------------------------
// LogFile implementation
LogFile::LogFile (size_t max_entries)
: m_error_receiver (this, 0, &LogFile::error),
m_warn_receiver (this, 0, &LogFile::warn),
m_log_receiver (this, 10, &LogFile::info),
m_info_receiver (this, 0, &LogFile::info),
LogFile::LogFile (size_t max_entries, bool register_global)
: m_error_receiver (this, 0, &LogFile::add_error),
m_warn_receiver (this, 0, &LogFile::add_warn),
m_log_receiver (this, 10, &LogFile::add_info),
m_info_receiver (this, 0, &LogFile::add_info),
m_max_entries (max_entries),
m_generation_id (0),
m_last_generation_id (0)
m_last_generation_id (0),
m_has_errors (false),
m_has_warnings (false),
m_last_attn (false)
{
connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ()));
m_timer.setSingleShot (true);
m_timer.setSingleShot (false);
m_timer.setInterval (100);
m_timer.start ();
tl::info.add (&m_info_receiver, false);
tl::log.add (&m_log_receiver, false);
tl::error.add (&m_error_receiver, false);
tl::warn.add (&m_warn_receiver, false);
if (register_global) {
tl::info.add (&m_info_receiver, false);
tl::log.add (&m_log_receiver, false);
tl::error.add (&m_error_receiver, false);
tl::warn.add (&m_warn_receiver, false);
}
}
void
@ -123,8 +129,23 @@ LogFile::clear ()
{
QMutexLocker locker (&m_lock);
m_messages.clear ();
++m_generation_id;
if (!m_messages.empty ()) {
m_messages.clear ();
m_has_errors = m_has_warnings = false;
++m_generation_id;
}
}
bool
LogFile::has_errors () const
{
return m_has_errors;
}
bool
LogFile::has_warnings () const
{
return m_has_warnings;
}
void
@ -159,9 +180,13 @@ void
LogFile::timeout ()
{
bool changed = false;
bool attn = false, last_attn = false;
m_lock.lock ();
if (m_generation_id != m_last_generation_id) {
attn = m_has_errors || m_has_warnings;
last_attn = m_last_attn;
m_last_attn = attn;
m_last_generation_id = m_generation_id;
changed = true;
}
@ -169,9 +194,10 @@ LogFile::timeout ()
if (changed) {
emit layoutChanged ();
if (last_attn != attn) {
emit attention_changed (attn);
}
}
m_timer.start ();
}
void
@ -183,6 +209,12 @@ LogFile::add (LogFileEntry::mode_type mode, const std::string &msg, bool continu
m_messages.pop_front ();
}
if (mode == LogFileEntry::Warning || mode == LogFileEntry::WarningContinued) {
m_has_warnings = true;
} else if (mode == LogFileEntry::Error || mode == LogFileEntry::ErrorContinued) {
m_has_errors = true;
}
m_messages.push_back (LogFileEntry (mode, msg, continued));
++m_generation_id;
@ -201,18 +233,25 @@ LogFile::data(const QModelIndex &index, int role) const
{
QMutexLocker locker (&m_lock);
if (role == Qt::DisplayRole) {
if (role == Qt::DecorationRole) {
if (index.row () < int (m_messages.size ()) && index.row () >= 0) {
LogFileEntry::mode_type mode = m_messages [index.row ()].mode ();
std::string message = m_messages [index.row ()].text ();
if (mode == LogFileEntry::Error) {
return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("ERROR: ")) + message));
return QIcon (QString::fromUtf8 (":/error_16.png"));
} else if (mode == LogFileEntry::Warning) {
return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("Warning: ")) + message));
return QIcon (QString::fromUtf8 (":/warn_16.png"));
} else if (mode == LogFileEntry::Info) {
return QIcon (QString::fromUtf8 (":/info_16.png"));
} else {
return QVariant (tl::to_qstring (message));
return QIcon (QString::fromUtf8 (":/empty_16.png"));
}
}
} else if (role == Qt::DisplayRole) {
if (index.row () < int (m_messages.size ()) && index.row () >= 0) {
return QVariant (tl::to_qstring (m_messages [index.row ()].text ()));
}
} else if (role == Qt::FontRole) {
@ -251,21 +290,36 @@ LogFile::data(const QModelIndex &index, int role) const
// -----------------------------------------------------------------
// LogViewerDialog implementation
LogViewerDialog::LogViewerDialog (QWidget *parent)
LogViewerDialog::LogViewerDialog (QWidget *parent, bool register_global, bool interactive)
: QDialog (parent),
m_file (50000) // TODO: make this variable ..
m_file (50000, register_global) // TODO: make this variable ..
{
setupUi (this);
// For non-global log views, the verbosity selector does not make sense
if (!register_global) {
verbosity_cbx->hide ();
verbosity_label->hide ();
} else {
verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10));
connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int)));
}
if (!interactive) {
clear_pb->hide ();
separator_pb->hide ();
copy_pb->hide ();
} else {
connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ()));
connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ()));
connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ()));
}
attn_frame->hide ();
log_view->setModel (&m_file);
verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10));
connect (&m_file, SIGNAL (layoutChanged ()), log_view, SLOT (scrollToBottom ()));
connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int)));
connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ()));
connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ()));
connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ()));
connect (&m_file, SIGNAL (attention_changed (bool)), attn_frame, SLOT (setVisible (bool)));
}
void
@ -274,5 +328,56 @@ LogViewerDialog::verbosity_changed (int index)
tl::verbosity (index * 10 + 1);
}
// -----------------------------------------------------------------
// AlertLog implementation
AlertLogButton::AlertLogButton (QWidget *parent)
: QToolButton (parent)
{
mp_logger = new LogViewerDialog (this, false, false);
hide ();
connect (&mp_logger->file (), SIGNAL (attention_changed (bool)), this, SLOT (attention_changed (bool)));
connect (this, SIGNAL (clicked ()), mp_logger, SLOT (exec ()));
}
void
AlertLogButton::attention_changed (bool attn)
{
setVisible (attn);
// as a special service, enlarge and color any surrounding frame red -
// this feature allows putting the alert button together with other entry fields into a frame and
// make this frame highlighted on error or warning.
QFrame *frame = dynamic_cast<QFrame *> (parent ());
if (frame) {
if (frame->layout ()) {
int l = 0, t = 0, r = 0, b = 0;
frame->layout ()->getContentsMargins (&l, &t, &r, &b);
if (attn) {
l += 3; t += 3; r += 2; b += 2;
} else {
l -= 3; t -= 3; r -= 2; b -= 2;
}
frame->layout ()->setContentsMargins (l, t, r, b);
}
if (attn) {
frame->setAutoFillBackground (true);
QPalette palette = frame->palette ();
palette.setColor (QPalette::Window, QColor (255, 160, 160));
frame->setPalette (palette);
} else {
frame->setAutoFillBackground (false);
frame->setPalette (QPalette ());
}
}
}
}

View File

@ -26,11 +26,13 @@
#include "ui_LogViewerDialog.h"
#include "tlLog.h"
#include "layCommon.h"
#include <QTimer>
#include <QMutex>
#include <QDialog>
#include <QAbstractListModel>
#include <QToolButton>
#include <deque>
#include <string>
@ -40,6 +42,9 @@ namespace lay
class LogFile;
/**
* @brief A helper class describing one log entry
*/
class LogFileEntry
{
public:
@ -70,7 +75,10 @@ private:
bool m_continued;
};
class LogReceiver
/**
* @brief The log receiver abstraction that connects a channel with the LogFile object
*/
class LAY_PUBLIC LogReceiver
: public tl::Channel
{
public:
@ -91,40 +99,110 @@ private:
QMutex m_lock;
};
class LogFile
/**
* @brief A log collection ("log file")
*
* The log collector collects warnings, errors and info messages
* and presents this collection as a QAbstractListModel view
* viewing inside a QTreeWidget or the LogViewerDialog.
*
* The log collector can either be used standalone or as a
* global receiver that will collect the global log
* messages.
*/
class LAY_PUBLIC LogFile
: public QAbstractListModel
{
Q_OBJECT
public:
LogFile (size_t max_entries);
void error (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued);
}
void info (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued);
}
void warn (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued);
}
/**
* @brief Constructs a log file receiver
* If "register_global" is true, the receiver will register itself as a global log receiver.
* Otherwise it's a private one that can be used with the "error", "warn" and "info" channels
* provided by the respective methods.
*/
LogFile (size_t max_entries, bool register_global = true);
/**
* @brief Implementation of the QAbstractItemModel interface
*/
int rowCount(const QModelIndex &parent) const;
/**
* @brief Implementation of the QAbstractItemModel interface
*/
QVariant data(const QModelIndex &index, int role) const;
private slots:
void timeout ();
/**
* @brief Gets a value indicating whether errors are present
*/
bool has_errors () const;
/**
* @brief Gets a value indicating whether warnings are present
*/
bool has_warnings () const;
public slots:
/**
* @brief Clears the log
*/
void clear ();
/**
* @brief Adds a separator
*/
void separator ();
/**
* @brief copies the contents to the clipboard
*/
void copy ();
public:
/**
* @brief Gets the error channel
*/
tl::Channel &error ()
{
return m_error_receiver;
}
/**
* @brief Gets the warning channel
*/
tl::Channel &warn ()
{
return m_warn_receiver;
}
/**
* @brief Gets the info channel
*/
tl::Channel &info ()
{
return m_info_receiver;
}
/**
* @brief Gets the log channel
*/
tl::Channel &log ()
{
return m_log_receiver;
}
private slots:
void timeout ();
signals:
/**
* @brief This signal is emitted if the log's attention state has changed
* Attention state is "true" if either errors or warnings are present.
*/
void attention_changed (bool f);
private:
QTimer m_timer;
mutable QMutex m_lock;
LogReceiver m_error_receiver;
@ -135,18 +213,63 @@ public:
size_t m_max_entries;
size_t m_generation_id;
size_t m_last_generation_id;
bool m_has_errors, m_has_warnings;
bool m_last_attn;
/**
* @brief Adds an error
*/
void add_error (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued);
}
/**
* @brief Adds a info message
*/
void add_info (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued);
}
/**
* @brief Adds a warning
*/
void add_warn (const std::string &msg, bool continued)
{
add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued);
}
/**
* @brief Adds anything
*/
void add (LogFileEntry::mode_type mode, const std::string &msg, bool continued);
};
class LogViewerDialog
/**
* @brief A dialog presenting the log file
*/
class LAY_PUBLIC LogViewerDialog
: public QDialog,
public Ui::LogViewerDialog
{
Q_OBJECT
public:
LogViewerDialog (QWidget *parent);
/**
* @brief The constructor
* If "register_global" is true, the log is registered globally
* and will receiver global log messages.
*/
LogViewerDialog (QWidget *parent, bool register_global = true, bool interactive = true);
/**
* @brief Gets the log file object
*/
LogFile &file ()
{
return m_file;
}
public slots:
void verbosity_changed (int l);
@ -155,7 +278,93 @@ private:
LogFile m_file;
};
/**
* @brief A tool button that collects logs and makes itself visible once attention is required
*/
class LAY_PUBLIC AlertLogButton
: public QToolButton
{
Q_OBJECT
public:
/**
* @brief Constructor
*/
AlertLogButton (QWidget *parent);
/**
* @brief Gets the error channel
*/
tl::Channel &error () const
{
return mp_logger->file ().error ();
}
/**
* @brief Gets the warn channel
*/
tl::Channel &warn () const
{
return mp_logger->file ().warn ();
}
/**
* @brief Gets the info channel
*/
tl::Channel &info () const
{
return mp_logger->file ().info ();
}
/**
* @brief Gets the log channel
*/
tl::Channel &log () const
{
return mp_logger->file ().log ();
}
/**
* @brief Gets the error status of the log
*/
bool has_errors () const
{
return mp_logger->file ().has_errors ();
}
/**
* @brief Gets the warning status of the log
*/
bool has_warnings () const
{
return mp_logger->file ().has_warnings ();
}
/**
* @brief Gets the attention status of the log
* (either warnings or errors are present)
*/
bool needs_attention () const
{
return has_errors () || has_warnings ();
}
public slots:
/**
* @brief Clears the log (and makes the button invisible)
*/
void clear ()
{
mp_logger->file ().clear ();
}
private slots:
void attention_changed (bool);
private:
LogViewerDialog *mp_logger;
};
}
#endif

View File

@ -85,8 +85,8 @@
#include "layLogViewerDialog.h"
#include "layLayerToolbox.h"
#include "laySettingsForm.h"
#include "laySettingsForm.h"
#include "layTechnologyController.h"
#include "laySaltManagerDialog.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),
@ -4509,6 +4510,13 @@ MainWindow::show_progress_bar (bool show)
}
}
void
MainWindow::cm_packages ()
{
lay::SaltManagerDialog dialog (this);
dialog.exec ();
}
void
MainWindow::cm_technologies ()
{

View File

@ -720,6 +720,7 @@ public slots:
void cm_macro_editor ();
void cm_new_drc_script ();
void cm_edit_drc_scripts ();
void cm_packages ();
void cm_technologies ();
void cm_open_too ();
void cm_open_new_view ();

View File

@ -113,8 +113,18 @@
<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>
</qresource>
<qresource prefix="/syntax" >
<qresource prefix="/syntax">
<file alias="ruby.xml">syntax/ruby.xml</file>
<file alias="python.xml">syntax/python.xml</file>
</qresource>

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

@ -0,0 +1,427 @@
/*
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 <QResource>
namespace lay
{
Salt::Salt ()
{
// .. nothing yet ..
}
Salt::Salt (const Salt &other)
: QObject ()
{
operator= (other);
}
Salt &Salt::operator= (const Salt &other)
{
if (this != &other) {
m_root = other.m_root;
invalidate ();
}
return *this;
}
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);
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) {
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) {
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 NameCompare
{
bool operator () (lay::SaltGrain *a, lay::SaltGrain *b) const
{
// TODO: UTF-8 support?
return a->name () < b->name ();
}
};
}
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));
}
// 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 (), NameCompare ());
}
}
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) {
SaltGrains::grain_iterator gnext = g;
++gnext;
collection.remove_grain (g, true);
res = true;
g = gnext;
}
}
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)
{
tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ()));
if (remove_from_collection (m_root, grain.name ())) {
tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ()));
// NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure
refresh ();
invalidate ();
return true;
} else {
tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ()));
return false;
}
}
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 ()) {
// 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 ());
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;
}
}

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

@ -0,0 +1,206 @@
/*
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);
signals:
/**
* @brief A signal triggered when one of the collections changed
*/
void collections_changed ();
private:
SaltGrains m_root;
std::vector<SaltGrain *> mp_flat_grains;
std::map<std::string, SaltGrain *> m_grains_by_name;
void validate ();
void invalidate ();
void add_collection_to_flat (lay::SaltGrains &gg);
};
}
#endif

View File

@ -0,0 +1,243 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "laySaltDownloadManager.h"
#include "laySalt.h"
#include "tlFileUtils.h"
#include "tlWebDAV.h"
#include "ui_SaltManagerInstallConfirmationDialog.h"
#include <QTreeWidgetItem>
#include <QMessageBox>
namespace lay
{
// ----------------------------------------------------------------------------------
class ConfirmationDialog
: public QDialog, private Ui::SaltManagerInstallConfirmationDialog
{
public:
ConfirmationDialog (QWidget *parent)
: QDialog (parent)
{
Ui::SaltManagerInstallConfirmationDialog::setupUi (this);
}
void add_info (const std::string &name, bool update, const std::string &version, const std::string &url)
{
QTreeWidgetItem *item = new QTreeWidgetItem (list);
item->setFlags (item->flags () & ~Qt::ItemIsSelectable);
item->setText (0, tl::to_qstring (name));
item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL"));
item->setText (2, tl::to_qstring (version));
item->setText (3, tl::to_qstring (url));
for (int column = 0; column < list->colorCount (); ++column) {
item->setData (column, Qt::ForegroundRole, update ? Qt::blue : Qt::black);
}
}
};
// ----------------------------------------------------------------------------------
SaltDownloadManager::SaltDownloadManager ()
{
// .. nothing yet ..
}
void
SaltDownloadManager::register_download (const std::string &name, const std::string &url, const std::string &version)
{
m_registry.insert (std::make_pair (name, Descriptor (url, version)));
}
void
SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Salt &salt_mine)
{
tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Computing package dependencies ..")));
std::map<std::string, Descriptor> registry;
// remove those registered entries which don't need to be updated
registry = m_registry;
for (std::map<std::string, Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
const SaltGrain *g = salt.grain_by_name (p->first);
if (g && SaltGrain::compare_versions (p->second.version, g->version ()) == 0 && p->second.url == g->url ()) {
m_registry.erase (p->first);
}
}
// add further entries as derived from the dependencies
while (needs_iteration ()) {
fetch_missing (salt_mine, progress);
registry = m_registry;
for (std::map<std::string, Descriptor>::const_iterator p = registry.begin (); p != registry.end (); ++p) {
for (std::vector<SaltGrain::Dependency>::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) {
std::map<std::string, Descriptor>::iterator r = m_registry.find (d->name);
if (r != m_registry.end ()) {
if (SaltGrain::compare_versions (r->second.version, d->version) < 0) {
// Grain is present, but too old -> update version and reload in the next iteration
r->second.downloaded = false;
r->second.version = d->version;
r->second.url = d->url;
r->second.downloaded = false;
}
} else {
const SaltGrain *g = salt.grain_by_name (d->name);
if (g) {
// Grain is installed already, but too old -> register for update
if (SaltGrain::compare_versions (g->version (), d->version) < 0) {
register_download (d->name, d->url, d->version);
}
} else {
register_download (d->name, d->url, d->version);
}
}
}
}
}
}
bool
SaltDownloadManager::needs_iteration ()
{
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (! p->second.downloaded) {
return true;
}
}
return false;
}
void
SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress)
{
for (std::map<std::string, Descriptor>::iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
if (! p->second.downloaded) {
++progress;
// If no URL is given, utilize the salt mine to fetch it
if (p->second.url.empty ()) {
const lay::SaltGrain *g = salt_mine.grain_by_name (p->first);
if (SaltGrain::compare_versions (g->version (), p->second.version) < 0) {
throw tl::Exception (tl::to_string (QObject::tr ("Package '%1': package in repository is too old (%2) to satisfy requirements (%3)").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (p->second.version))));
}
p->second.version = g->version ();
p->second.url = g->url ();
}
try {
p->second.grain = SaltGrain::from_url (p->second.url);
} catch (tl::Exception &ex) {
throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (ex.msg ()))));
}
p->second.downloaded = true;
}
}
}
bool
SaltDownloadManager::show_confirmation_dialog (QWidget *parent, const lay::Salt &salt)
{
// Stop with a warning if there is nothing to do
if (m_registry.empty()) {
QMessageBox::warning (parent, tr ("Nothing to do"), tr ("No packages need update or are marked for installation"));
return false;
}
lay::ConfirmationDialog dialog (parent);
// First the packages to update
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->first);
if (g) {
// \342\206\222 is UTF-8 "right arrow"
dialog.add_info (p->first, true, g->version () + " \342\206\222 " + p->second.version, p->second.url);
}
}
// Then the packages to install
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->first);
if (!g) {
dialog.add_info (p->first, false, p->second.version, p->second.url);
}
}
return dialog.exec ();
}
bool
SaltDownloadManager::execute (lay::Salt &salt)
{
bool result = true;
tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading packages")), m_registry.size (), 1);
for (std::map<std::string, Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
lay::SaltGrain target;
target.set_name (p->first);
lay::SaltGrain *g = salt.grain_by_name (p->first);
if (g) {
target.set_path (g->path ());
}
if (! salt.create_grain (p->second.grain, target)) {
result = false;
}
++progress;
}
return result;
}
}

View File

@ -0,0 +1,117 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_laySaltDownloadManager
#define HDR_laySaltDownloadManager
#include "layCommon.h"
#include "laySaltGrain.h"
#include "tlProgress.h"
#include <QObject>
#include <string>
#include <map>
namespace lay
{
class Salt;
/**
* @brief The download manager
*
* This class is responsible for handling the downloads for
* grains. The basic sequence is:
* + "register_download" (multiple times) to register the packages intended for download
* + "compute_dependencies" to determine all related packages
* + (optional) "show_confirmation_dialog"
* + "execute" to actually execute the downloads
*/
class LAY_PUBLIC SaltDownloadManager
: public QObject
{
Q_OBJECT
public:
/**
* @brief Default constructor
*/
SaltDownloadManager ();
/**
* @brief Registers an URL (with version) for download in the given target directory
*
* The target directory can be empty. In this case, the downloader will pick an approriate one.
*/
void register_download (const std::string &name, const std::string &url, const std::string &version);
/**
* @brief Computes the dependencies after all required packages have been registered
*
* This method will compute the dependencies. Packages not present in the list of
* packages ("salt" argument), will be scheduled for download too. Dependency packages
* are looked up in "salt_mine" if no download URL is given.
*/
void compute_dependencies (const lay::Salt &salt, const Salt &salt_mine);
/**
* @brief Presents a dialog showing the packages scheduled for download
*
* This method requires all dependencies to be computed. It will return false
* if the dialog is not confirmed.
*
* "salt" needs to be the currently installed packages so the dialog can
* indicate which packages will be updated.
*/
bool show_confirmation_dialog (QWidget *parent, const lay::Salt &salt);
/**
* @brief Actually execute the downloads
*
* This method will return false if anything goes wrong.
* Failed packages will be removed entirely after they have been listed in
* an error dialog.
*/
bool execute (lay::Salt &salt);
private:
struct Descriptor
{
Descriptor (const std::string &_url, const std::string &_version)
: url (_url), version (_version), downloaded (false)
{ }
std::string url;
std::string version;
bool downloaded;
lay::SaltGrain grain;
};
std::map<std::string, Descriptor> m_registry;
bool needs_iteration ();
void fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress);
};
}
#endif

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

@ -0,0 +1,422 @@
/*
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>
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_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_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;
}
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()) {
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::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
{
return !QFileInfo (tl::to_qstring (path ())).isWritable ();
}
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::InputHttpStream http (SaltGrain::spec_url (url));
tl::InputStream stream (http);
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 ();
}
}
}

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

@ -0,0 +1,420 @@
/*
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 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 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_path;
std::string m_url;
std::string m_title;
std::string m_doc, m_doc_url;
std::string m_author;
std::string m_author_contact;
std::string m_license;
QDateTime m_authored_time, m_installed_time;
QImage m_icon, m_screenshot;
std::vector<Dependency> m_dependencies;
};
}
#endif

View File

@ -0,0 +1,247 @@
/*
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>
namespace lay
{
SaltGrainDetailsTextWidget::SaltGrainDetailsTextWidget (QWidget *w)
: QTextBrowser (w), mp_grain (0)
{
// .. nothing yet ..
}
void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g)
{
mp_grain = g;
setHtml (details_text ());
}
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);
}
}
QString
SaltGrainDetailsTextWidget::details_text ()
{
SaltGrain *g = mp_grain;
if (! g) {
return QString ();
}
QBuffer buffer;
buffer.open (QIODevice::WriteOnly);
QTextStream stream (&buffer);
stream.setCodec ("UTF-8");
stream << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/></head><body>";
stream << "<table cellpadding=\"6\"><tr>";
stream << "<td><img src=\":/icon\" width=\"64\" height=\"64\"/></td>";
stream << "<td>";
stream << "<h1>";
stream << tl::to_qstring (tl::escaped_to_html (g->name ())) << " " << tl::to_qstring (tl::escaped_to_html (g->version ()));
stream << "</h1>";
if (! g->title ().empty()) {
stream << "<h3>" << tl::to_qstring (tl::escaped_to_html (g->title ())) << "</h3>";
}
if (g->version ().empty ()) {
stream << "<p><i><font color='gray'>";
stream << QObject::tr ("This package does not have a version. "
"Use the &lt;version&gt; element of the specification file or edit the package properties to provide a version.");
stream << "</font></i></p>";
}
if (g->title ().empty ()) {
stream << "<p><i><font color='gray'>";
stream << QObject::tr ("This package does not have a title. "
"Use the &lt;title&gt; element of the specification file or edit the package properties to provide a title.");
stream << "</font></i></p>";
}
stream << "<p><br/>";
if (! g->doc ().empty ()) {
stream << tl::to_qstring (tl::escaped_to_html (g->doc ()));
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a description. "
"Use the &lt;doc&gt; element of the specification file or edit the package properties to provide a description.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->author ().empty ()) {
stream << "<b>" << QObject::tr ("Author") << ":</b> " << tl::to_qstring (tl::escaped_to_html (g->author ())) << " ";
if (! g->author_contact ().empty ()) {
stream << "(" << tl::to_qstring (tl::escaped_to_html (g->author_contact ())) << ")";
}
if (!g->authored_time ().isNull ()) {
stream << "<br/>";
stream << "<b>" << QObject::tr ("Released") << ":</b> " << g->authored_time ().date ().toString (Qt::ISODate);
}
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a author information. "
"Use the &lt;author&gt;, &lt;authored-time&gt; and &lt;author-contact&gt; elements of the specification file or edit the package properties to provide authoring information.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->license ().empty ()) {
stream << "<b>" << QObject::tr ("License") << ":</b> " << tl::to_qstring (tl::escaped_to_html (g->license ())) << " ";
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have license information. "
"Use the &lt;license&gt; elements of the specification file or edit the package properties to provide license information.");
stream << "</font></i>";
}
stream << "</p>";
stream << "<p>";
if (! g->doc_url ().empty ()) {
stream << "<b>" << QObject::tr ("Documentation link") << ":</b> <a href=\"" << tl::to_qstring (g->doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->doc_url ())) << "</a>";
} else {
stream << "<i><font color='gray'>";
stream << QObject::tr ("This package does not have a documentation link. "
"Use the &lt;doc-url&gt; element of the specification file or edit the package properties to provide a link.");
stream << "</font></i>";
}
stream << "</p>";
if (! g->screenshot ().isNull ()) {
stream << "<br/>";
stream << "<h3>" << QObject::tr ("Screenshot") << "</h3><p><img src=\":/screenshot\"/></p>";
}
stream << "<br/>";
stream << "<h3>" << QObject::tr ("Installation") << "</h3>";
stream << "<p><b>" << QObject::tr ("Installation path: ") << "</b>" << tl::to_qstring (tl::escaped_to_html (g->path ())) << "</p>";
if (! g->url ().empty ()) {
stream << "<p><b>" << QObject::tr ("Download URL: ") << "</b>" << tl::to_qstring (tl::escaped_to_html (g->url ())) << "</p>";
}
if (! g->installed_time ().isNull ()) {
stream << "<p><b>" << QObject::tr ("Installed: ") << "</b>" << g->installed_time ().toString () << "</p>";
}
if (! g->dependencies ().empty ()) {
stream << "<p><b>" << QObject::tr ("Depends on: ") << "</b><br/>";
for (std::vector<lay::SaltGrain::Dependency>::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) {
stream << "&nbsp;&nbsp;&nbsp;&nbsp;" << tl::to_qstring (tl::escaped_to_html (d->name)) << " ";
stream << tl::to_qstring (tl::escaped_to_html (d->version));
if (! d->url.empty ()) {
stream << " - ";
stream << "[" << tl::to_qstring (tl::escaped_to_html (d->url)) << "]<br/>";
}
}
stream << "</p>";
}
stream << "</td></tr></table>";
stream << "</body></html>";
stream.flush ();
return QString::fromUtf8 (buffer.buffer());
}
}

View File

@ -0,0 +1,61 @@
/*
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
{
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:
lay::SaltGrain *mp_grain;
QString details_text ();
};
}
#endif

View File

@ -0,0 +1,632 @@
/*
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 ()));
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_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
open_label->setText (m_open_label.arg (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 ()));
}
// doc URL
doc_url_alert->clear ();
if (! m_grain.doc_url ().empty ()) {
tl::InputHttpStream stream (m_grain.doc_url ());
try {
char b;
stream.read (&b, 1);
} 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 ()) {
QDialog::accept ();
} else {
if (QMessageBox::warning (this, tr ("Issues Encountered"),
tr ("Some issues have been found when inspecting the package details.\nThe respective fields are marked with warning icons.\n\nIgnore these issues and commit the package details?"),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
QDialog::accept ();
}
}
}
bool
SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt)
{
m_grain = *grain;
mp_salt = salt;
dependencies->setItemDelegateForColumn (0, new SaltGrainNameEditDelegate (dependencies, mp_salt));
update_controls ();
bool res = exec ();
if (res && *grain != m_grain) {
*grain = m_grain;
// save modified grain
grain->save ();
}
delete dependencies->itemDelegateForColumn (0);
dependencies->setItemDelegateForColumn (0, 0);
mp_salt = 0;
return res;
}
}

View File

@ -0,0 +1,96 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_laySaltGrainPropertiesDialog
#define HDR_laySaltGrainPropertiesDialog
#include "laySaltGrain.h"
#include <QDialog>
#include "ui_SaltGrainPropertiesDialog.h"
namespace lay
{
class Salt;
/**
* @brief The dialog for managing the Salt ("Packages")
*/
class SaltGrainPropertiesDialog
: public QDialog, private Ui::SaltGrainPropertiesDialog
{
Q_OBJECT
public:
/**
* @brief Constructor
*/
SaltGrainPropertiesDialog (QWidget *parent);
/**
* @brief Executes the dialog for the given grain
* If the dialog is committed with "Ok", the new data is written into
* the grain provided and "true" is returned. Otherwise, "false" is
* returned and the object remains unchanged.
*/
bool exec_dialog (lay::SaltGrain *grain, lay::Salt *salt);
/**
* @brief Gets the current package index
*/
lay::Salt *salt ()
{
return mp_salt;
}
private slots:
void reset_icon ();
void set_icon ();
void reset_screenshot ();
void set_screenshot ();
void url_changed (const QString &url);
void add_dependency_clicked ();
void remove_dependency_clicked ();
void dependency_changed (QTreeWidgetItem *item, int column);
protected:
void accept ();
private:
lay::SaltGrain m_grain;
lay::Salt *mp_salt;
QString m_title;
QString m_open_label;
QString m_image_dir;
bool m_update_enabled;
void update_controls ();
void update_data ();
void update_icon ();
void update_screenshot ();
};
}
#endif

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

@ -0,0 +1,264 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "laySaltGrains.h"
#include "tlString.h"
#include "tlFileUtils.h"
#include <QDir>
#include <QFileInfo>
#include <QResource>
namespace lay
{
SaltGrains::SaltGrains ()
{
// .. nothing yet ..
}
bool
SaltGrains::operator== (const SaltGrains &other) const
{
return m_name == other.m_name &&
m_path == other.m_path &&
m_title == other.m_title &&
m_collections == other.m_collections &&
m_grains == other.m_grains;
}
void
SaltGrains::set_name (const std::string &n)
{
m_name = n;
}
void
SaltGrains::set_title (const std::string &t)
{
m_title = t;
}
void
SaltGrains::set_path (const std::string &p)
{
m_path = p;
}
void
SaltGrains::add_collection (const SaltGrains &collection)
{
m_collections.push_back (collection);
}
bool
SaltGrains::remove_collection (collection_iterator iter, bool with_files)
{
// NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required
for (collections_type::iterator i = m_collections.begin (); i != m_collections.end (); ++i) {
if (i == iter) {
if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) {
return false;
}
m_collections.erase (i);
return true;
}
}
return false;
}
void
SaltGrains::add_grain (const SaltGrain &grain)
{
m_grains.push_back (grain);
}
bool
SaltGrains::remove_grain (grain_iterator iter, bool with_files)
{
// NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required
for (grains_type::iterator i = m_grains.begin (); i != m_grains.end (); ++i) {
if (i == iter) {
if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) {
return false;
}
m_grains.erase (i);
return true;
}
}
return false;
}
bool
SaltGrains::is_empty () const
{
if (! m_grains.empty ()) {
return false;
}
for (collections_type::const_iterator i = m_collections.begin (); i != m_collections.end (); ++i) {
if (!i->is_empty ()) {
return false;
}
}
return true;
}
bool
SaltGrains::is_readonly () const
{
return QFileInfo (tl::to_qstring (path ())).isWritable ();
}
namespace
{
/**
* @brief A helper class required because directory traversal is not supported by QResource directly
*/
class OpenResource
: public QResource
{
public:
using QResource::isDir;
using QResource::isFile;
using QResource::children;
OpenResource (const QString &path)
: QResource (path)
{
// .. nothing yet ..
}
};
}
SaltGrains
SaltGrains::from_path (const std::string &path, const std::string &prefix)
{
tl_assert (! path.empty ());
SaltGrains grains;
grains.set_path (path);
if (path[0] != ':') {
QDir dir (tl::to_qstring (path));
QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name);
for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) {
std::string new_prefix = prefix;
if (! new_prefix.empty ()) {
new_prefix += "/";
}
new_prefix += tl::to_string (*e);
std::string epath = tl::to_string (dir.absoluteFilePath (*e));
if (SaltGrain::is_grain (epath)) {
try {
SaltGrain g (SaltGrain::from_path (epath));
g.set_name (new_prefix);
grains.add_grain (g);
} catch (...) {
// ignore errors (TODO: what to do here?)
}
} else if (QFileInfo (tl::to_qstring (epath)).isDir ()) {
SaltGrains c = SaltGrains::from_path (epath, new_prefix);
c.set_name (new_prefix);
if (! c.is_empty ()) {
grains.add_collection (c);
}
}
}
} else {
OpenResource resource (tl::to_qstring (path));
if (resource.isDir ()) {
QStringList templ_dir = resource.children ();
for (QStringList::const_iterator t = templ_dir.begin (); t != templ_dir.end (); ++t) {
std::string new_prefix = prefix;
if (! new_prefix.empty ()) {
new_prefix += "/";
}
new_prefix += tl::to_string (*t);
std::string epath = path + "/" + tl::to_string (*t);
if (SaltGrain::is_grain (epath)) {
try {
SaltGrain g (SaltGrain::from_path (epath));
g.set_name (new_prefix);
grains.add_grain (g);
} catch (...) {
// ignore errors (TODO: what to do here?)
}
} else if (OpenResource (tl::to_qstring (epath)).isDir ()) {
SaltGrains c = SaltGrains::from_path (epath, new_prefix);
c.set_name (new_prefix);
if (! c.is_empty ()) {
grains.add_collection (c);
}
}
}
}
}
return grains;
}
static tl::XMLElementList s_group_struct =
tl::make_member (&SaltGrains::name, &SaltGrains::set_name, "name") +
tl::make_element (&SaltGrains::begin_collections, &SaltGrains::end_collections, &SaltGrains::add_collection, "group", &s_group_struct) +
tl::make_element (&SaltGrains::begin_grains, &SaltGrains::end_grains, &SaltGrains::add_grain, "salt-grain", SaltGrain::xml_struct ());
static tl::XMLStruct<lay::SaltGrains> s_xml_struct ("salt-mine", s_group_struct);
void
SaltGrains::load (const std::string &p)
{
tl::XMLFileSource source (p);
s_xml_struct.parse (source, *this);
}
void
SaltGrains::load (tl::InputStream &p)
{
tl::XMLStreamSource source (p);
s_xml_struct.parse (source, *this);
}
void
SaltGrains::save (const std::string &p) const
{
tl::OutputStream os (p, tl::OutputStream::OM_Plain);
s_xml_struct.write (os, *this);
}
}

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

@ -0,0 +1,210 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_laySaltGrains
#define HDR_laySaltGrains
#include "layCommon.h"
#include "laySaltGrain.h"
#include <list>
namespace lay
{
/**
* @brief A class representing a collection of grains (packages)
* A collection can have child collections and grains (leafs).
*/
class LAY_PUBLIC SaltGrains
{
public:
typedef std::list<SaltGrains> collections_type;
typedef collections_type::const_iterator collection_iterator;
typedef std::list<SaltGrain> grains_type;
typedef grains_type::const_iterator grain_iterator;
/**
* @brief Constructor: creates an empty collection
*/
SaltGrains ();
/**
* @brief Equality
*/
bool operator== (const SaltGrains &other) const;
/**
* @brief Inequality
*/
bool operator!= (const SaltGrains &other) const
{
return !operator== (other);
}
/**
* @brief Gets the name of the grain collection
*
* The name is either a plain name (a word) or a path into a collection.
* Name paths are formed using the "/" separator. "mycollection" is a plain name,
* while "mycollection/subcollection" is a collection within a collection.
*/
const std::string &name () const
{
return m_name;
}
/**
* @brief Sets the name of the grain collection
*/
void set_name (const std::string &p);
/**
* @brief Gets the title of the grain collection
*
* The title is a brief description that is shown in the title of the
* package manager.
*/
const std::string &title () const
{
return m_title;
}
/**
* @brief Sets the title of the grain collection
*/
void set_title (const std::string &t);
/**
* @brief Gets the absolute file path of the installed grain
* This is the file path to the grain folder.
*/
const std::string &path () const
{
return m_path;
}
/**
* @brief Sets the absolute file path of the installed grain
*/
void set_path (const std::string &p);
/**
* @brief Gets the collections which are members of this collection (begin iterator)
*/
collection_iterator begin_collections () const
{
return m_collections.begin ();
}
/**
* @brief Gets the collections which are members of this collection (end iterator)
*/
collection_iterator end_collections () const
{
return m_collections.end ();
}
/**
* @brief Adds a collection to this collection
*/
void add_collection (const SaltGrains &collection);
/**
* @brief Removes the collection given by the collection iterator
* If "with_files" is true, also the folder and all sub-folders will be removed
* @return true, if the remove was successful.
*/
bool remove_collection (collection_iterator iter, bool with_files = false);
/**
* @brief Gets the grains (leaf nodes) which are members of this collection (begin iterator)
*/
grain_iterator begin_grains () const
{
return m_grains.begin ();
}
/**
* @brief Gets the grains (leaf nodes) which are members of this collection (end iterator)
*/
grain_iterator end_grains () const
{
return m_grains.end ();
}
/**
* @brief Adds a grain to this collection
*/
void add_grain (const SaltGrain &grain);
/**
* @brief Removes the grain given by the grain iterator
* If "with_files" is true, also the files and the folder will be removed.
* @return true, if the remove was successful.
*/
bool remove_grain (grain_iterator iter, bool with_files = false);
/**
* @brief Gets a value indicating whether the collection is empty
*/
bool is_empty () const;
/**
* @brief Returns true, if the collection is read-only
*/
bool is_readonly () const;
/**
* @brief Loads the grain collection from the given path
*/
void load (const std::string &p);
/**
* @brief Loads the grain collection from the given input stream
*/
void load (tl::InputStream &p);
/**
* @brief Saves the grain collection to the given file
*/
void save (const std::string &p) const;
/**
* @brief Scan grains from a given path
* This will scan the grains found within this path and return a collection containing
* the grains from this path.
* Sub-collections are created from folders which contain grains or sub-collections.
*/
static SaltGrains from_path (const std::string &path, const std::string &pfx = std::string ());
private:
std::string m_name;
std::string m_title;
std::string m_path;
collections_type m_collections;
grains_type m_grains;
};
}
#endif

View File

@ -0,0 +1,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 "laySaltManagerDialog.h"
#include "laySaltModel.h"
#include "laySaltGrainPropertiesDialog.h"
#include "laySaltDownloadManager.h"
#include "laySalt.h"
#include "ui_SaltGrainTemplateSelectionDialog.h"
#include "tlString.h"
#include "tlExceptions.h"
#include "tlHttpStream.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
// @@@
lay::Salt salt;
static bool salt_initialized = false;
void make_salt ()
{
if (!salt_initialized) {
salt_initialized = true;
salt.add_location (tl::to_string (QDir::homePath () + QString::fromUtf8("/.klayout/salt")));
}
}
lay::Salt *get_salt ()
{
salt = lay::Salt (); salt_initialized = false;
make_salt ();
return &salt;
}
// @@@
// @@@
lay::Salt salt_mine;
void make_salt_mine ()
{
salt_mine = lay::Salt ();
salt_mine.load ("/home/matthias/salt.mine");
}
lay::Salt *get_salt_mine ()
{
make_salt_mine();
return &salt_mine;
}
// @@@
SaltManagerDialog::SaltManagerDialog (QWidget *parent)
: QDialog (parent),
m_current_changed_enabled (true), dm_update_models (this, &SaltManagerDialog::update_models)
{
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_button, SIGNAL (clicked ()), this, SLOT (apply ()));
mp_salt = get_salt ();
mp_salt_mine = get_salt_mine ();
SaltModel *model = new SaltModel (this, mp_salt);
salt_view->setModel (model);
salt_view->setItemDelegate (new SaltItemDelegate (this));
SaltModel *mine_model = new SaltModel (this, mp_salt_mine);
salt_mine_view->setModel (mine_model);
salt_mine_view->setItemDelegate (new SaltItemDelegate (this));
mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0);
connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ()));
connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ()));
connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ()));
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->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ()), Qt::QueuedConnection);
connect (salt_mine_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ()));
search_installed_edit->set_clear_button_enabled (true);
search_new_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 (mark_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ()));
salt_mine_view->addAction (actionUnmarkAll);
QAction *a = new QAction (this);
a->setSeparator (true);
salt_mine_view->addAction (a);
salt_mine_view->addAction (actionShowMarkedOnly);
salt_mine_view->addAction (actionShowAll);
salt_mine_view->setContextMenuPolicy (Qt::ActionsContextMenu);
connect (actionUnmarkAll, SIGNAL (triggered ()), this, SLOT (unmark_all ()));
connect (actionShowMarkedOnly, SIGNAL (triggered ()), this, SLOT (show_marked_only ()));
connect (actionShowAll, SIGNAL (triggered ()), this, SLOT (show_all ()));
}
void
SaltManagerDialog::mode_changed ()
{
// keeps the splitters in sync
if (mode_tab->currentIndex () == 1) {
splitter_new->setSizes (splitter->sizes ());
show_all ();
} else if (mode_tab->currentIndex () == 0) {
splitter->setSizes (splitter_new->sizes ());
}
}
void
SaltManagerDialog::show_all ()
{
search_new_edit->clear ();
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
if (! model) {
return;
}
for (int i = model->rowCount (QModelIndex ()); i > 0; ) {
--i;
salt_mine_view->setRowHidden (i, false);
}
}
void
SaltManagerDialog::show_marked_only ()
{
search_new_edit->clear ();
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
if (! model) {
return;
}
for (int i = model->rowCount (QModelIndex ()); i > 0; ) {
--i;
SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ()));
salt_mine_view->setRowHidden (i, !(g && model->is_marked (g->name ())));
}
}
void
SaltManagerDialog::unmark_all ()
{
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
if (model) {
model->clear_marked ();
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;
} 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 ()
{
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
if (! model) {
return;
}
SaltGrain *g = mine_current_grain ();
if (! g) {
return;
}
model->set_marked (g->name (), !model->is_marked (g->name ()));
update_apply_state ();
}
void
SaltManagerDialog::update_apply_state ()
{
int marked = 0;
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
if (! model) {
return;
}
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_button->setEnabled (marked > 0);
if (marked == 0) {
apply_label->setText (QString ());
} else if (marked == 1) {
apply_label->setText (tr ("One package selected"));
} else if (marked > 1) {
apply_label->setText (tr ("%1 packages selected").arg (marked));
}
}
void
SaltManagerDialog::apply ()
{
BEGIN_PROTECTED
lay::SaltDownloadManager manager;
bool any = false;
// fetch all marked grains and register for download
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_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 && model->is_marked (g->name ())) {
manager.register_download (g->name (), g->url (), g->version ());
any = true;
}
}
}
if (! any) {
throw tl::Exception (tl::to_string (tr ("No packages marked for installation or update")));
}
manager.compute_dependencies (*mp_salt, *mp_salt_mine);
if (manager.show_confirmation_dialog (this, *mp_salt)) {
unmark_all ();
manager.execute (*mp_salt);
}
END_PROTECTED
}
void
SaltManagerDialog::edit_properties ()
{
SaltGrain *g = current_grain ();
if (g) {
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_changed ()
{
dm_update_models ();
}
void
SaltManagerDialog::salt_mine_changed ()
{
dm_update_models ();
}
void
SaltManagerDialog::update_models ()
{
SaltModel *model = dynamic_cast <SaltModel *> (salt_view->model ());
tl_assert (model != 0);
// NOTE: the disabling of the event handler prevents us from
// letting the model connect to the salt's signal directly.
m_current_changed_enabled = false;
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 = mp_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 ();
m_current_changed_enabled = true;
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 = dynamic_cast <SaltModel *> (salt_mine_view->model ());
tl_assert (mine_model != 0);
// NOTE: the disabling of the event handler prevents us from
// letting the model connect to the salt's signal directly.
m_current_changed_enabled = false;
mine_model->clear_order ();
mine_model->clear_messages ();
mine_model->enable_all ();
// 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 = mp_salt_mine->grain_by_name ((*g)->name ());
if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) {
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 already installed and up to date")));
mine_model->set_order ((*g)->name (), 1);
mine_model->set_enabled ((*g)->name (), false);
}
}
mine_model->update ();
m_current_changed_enabled = true;
// select the first grain
if (mine_model->rowCount (QModelIndex ()) > 0) {
salt_mine_view->setCurrentIndex (mine_model->index (0, 0, QModelIndex ()));
}
mine_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_current_changed ()
{
BEGIN_PROTECTED
SaltGrain *g = mine_current_grain ();
details_new_frame->setEnabled (g != 0);
if (! g) {
details_new_text->set_grain (0);
return;
}
m_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 text = 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_new_text->setHtml (text);
QApplication::processEvents (QEventLoop::ExcludeUserInputEvents);
tl::InputHttpStream http (SaltGrain::spec_url (g->url ()));
tl::InputStream stream (http);
m_remote_grain.reset (new SaltGrain ());
m_remote_grain->load (stream);
m_remote_grain->set_url (g->url ());
if (g->name () != m_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 (m_remote_grain->name ()))));
}
if (SaltGrain::compare_versions (g->version (), m_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 (m_remote_grain->version ()))));
}
details_new_text->set_grain (m_remote_grain.get ());
} catch (tl::Exception &ex) {
m_remote_grain.reset (0);
QString text = 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_new_text->setHtml (text);
}
END_PROTECTED
}
lay::SaltGrain *
SaltManagerDialog::mine_current_grain ()
{
SaltModel *model = dynamic_cast <SaltModel *> (salt_mine_view->model ());
return model ? model->grain_from_index (salt_mine_view->currentIndex ()) : 0;
}
}

View File

@ -0,0 +1,139 @@
/*
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 "tlDeferredExecution.h"
#include <QDialog>
#include <memory>
namespace lay
{
class Salt;
class SaltGrain;
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);
private slots:
/**
* @brief Called when the list of packages (grains) has changed
*/
void salt_changed ();
/**
* @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 salt mine has changed
*/
void mine_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
*/
void show_marked_only ();
/**
* @brief Called to show all items again
*/
void show_all ();
/**
* @brief Called to unmark all items
*/
void unmark_all ();
private:
Salt *mp_salt, *mp_salt_mine;
std::auto_ptr<SaltGrain> m_remote_grain;
bool m_current_changed_enabled;
SaltGrainPropertiesDialog *mp_properties_dialog;
tl::DeferredMethod<SaltManagerDialog> dm_update_models;
SaltGrain *current_grain ();
SaltGrain *mine_current_grain ();
void update_models ();
void update_apply_state ();
};
}
#endif

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

@ -0,0 +1,411 @@
/*
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)
: QAbstractItemModel (parent), mp_salt (salt)
{
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, m_ordered_grains [row]);
}
}
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 ()) {
return static_cast<SaltGrain *> (index.internalPointer ());
} 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::update ()
{
create_ordered_list ();
reset ();
}
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) {
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) {
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);
}
}
}
}
}
}

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

@ -0,0 +1,195 @@
/*
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);
/**
* @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 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;
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 is_marked (const std::string &name) const;
bool is_enabled (const std::string &name) const;
void create_ordered_list ();
};
// --------------------------------------------------------------------------------------
/**
* @brief A delegate displaying the summary of a grain
*/
class SaltItemDelegate
: public QStyledItemDelegate
{
public:
SaltItemDelegate (QObject *parent);
void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
}
#endif

View File

@ -0,0 +1,47 @@
<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">
</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">
</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">
</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">
</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">
</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">
</qresource>
</RCC>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>font</name>
<version>0.0</version>
<title>Font package</title>
<doc>This template provides a font for the Basic.TEXT PCell</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>lib</name>
<version>0.0</version>
<title>Static Library</title>
<doc>This template provides a static library</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>macro</name>
<version>0.0</version>
<title>Ruby Macro</title>
<doc>This template provides a Ruby macro</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>macro</name>
<version>0.0</version>
<title>PCell library</title>
<doc>This template provides a PCell library implemented in Ruby</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>macro</name>
<version>0.0</version>
<title>Python Macro</title>
<doc>This template provides a Python macro</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>macro</name>
<version>0.0</version>
<title>Technology</title>
<doc>This template provides a technology</doc>
<doc-url/>
<url/>
<license>GPLv3</license>
<author/>
<author-contact/>
<authored-time/>
<installed-time/>
<icon/>
<screenshot/>
</salt-grain>

View File

@ -1820,24 +1820,6 @@ MarkerBrowserPage::set_max_marker_count (size_t max_marker_count)
}
}
static void
escape_to_html (std::string &out, const std::string &in)
{
for (const char *cp = in.c_str (); *cp; ++cp) {
if (*cp == '<') {
out += "&lt;";
} else if (*cp == '>') {
out += "&gt;";
} else if (*cp == '&') {
out += "&amp;";
} else if (*cp == '\n') {
out += "<br/>";
} else {
out += *cp;
}
}
}
void
MarkerBrowserPage::enable_updates (bool f)
{
@ -1955,13 +1937,13 @@ MarkerBrowserPage::update_info_text ()
if (category && n_category == 1 && ! category->description ().empty ()) {
info += "<p style=\"color:blue; font-weight: bold\">";
escape_to_html (info, category->description ());
tl::escape_to_html (info, category->description ());
info += "</p>";
}
if (! m_error_text.empty ()) {
info += "<p style=\"color:red; font-weight: bold\">";
escape_to_html (info, m_error_text);
tl::escape_to_html (info, m_error_text);
info += "</p>";
}
@ -1978,7 +1960,7 @@ MarkerBrowserPage::update_info_text ()
if (v->tag_id () != 0) {
const rdb::Tag &tag = mp_database->tags ().tag (v->tag_id ());
info += "<b>";
escape_to_html (info, tag.name ());
tl::escape_to_html (info, tag.name ());
info += ":</br> ";
}
@ -1989,7 +1971,7 @@ MarkerBrowserPage::update_info_text ()
value_string = std::string (value_string.begin (), value_string.begin () + max_length) + "...";
}
escape_to_html (info, value_string);
tl::escape_to_html (info, value_string);
info += "<br/>";

View File

@ -40,7 +40,9 @@ SOURCES = \
tlVariant.cc \
tlXMLParser.cc \
tlXMLWriter.cc \
tlFileSystemWatcher.cc
tlFileSystemWatcher.cc \
tlFileUtils.cc \
tlWebDAV.cc
HEADERS = \
tlAlgorithm.h \
@ -84,7 +86,9 @@ HEADERS = \
tlFileSystemWatcher.h \
tlCommon.h \
tlMath.h \
tlCpp.h
tlCpp.h \
tlFileUtils.h \
tlWebDAV.h
INCLUDEPATH =
DEPENDPATH =

140
src/tl/tlFileUtils.cc Normal file
View File

@ -0,0 +1,140 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlFileUtils.h"
#include "tlLog.h"
#include "tlInternational.h"
#include <QDir>
#include <QFileInfo>
namespace tl
{
bool
is_parent_path (const QString &parent, const QString &path)
{
QFileInfo parent_info (parent);
QFileInfo path_info (path);
while (parent_info != path_info) {
path_info = path_info.path ();
if (path_info.isRoot ()) {
return false;
}
}
return true;
}
bool
rm_dir_recursive (const QString &path)
{
QDir dir (path);
QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) {
QFileInfo fi (dir.absoluteFilePath (*e));
if (fi.isDir ()) {
if (! rm_dir_recursive (fi.filePath ())) {
return false;
}
} else if (fi.isFile ()) {
if (! dir.remove (*e)) {
tl::error << QObject::tr ("Unable to remove file: %1").arg (dir.absoluteFilePath (*e));
return false;
}
}
}
QString name = dir.dirName ();
if (dir.cdUp ()) {
if (! dir.rmdir (name)) {
tl::error << QObject::tr ("Unable to remove directory: %1").arg (dir.absoluteFilePath (name));
return false;
}
}
return true;
}
bool
cp_dir_recursive (const QString &source, const QString &target)
{
QDir dir (source);
QDir dir_target (target);
QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) {
QFileInfo fi (dir.absoluteFilePath (*e));
QFileInfo fi_target (dir_target.absoluteFilePath (*e));
if (fi.isDir ()) {
// Copy subdirectory
if (! fi_target.exists ()) {
if (! dir_target.mkdir (*e)) {
tl::error << QObject::tr ("Unable to create target directory: %1").arg (dir_target.absoluteFilePath (*e));
return false;
}
} else if (! fi_target.isDir ()) {
tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (dir_target.absoluteFilePath (*e));
return false;
}
if (! cp_dir_recursive (fi.filePath (), fi_target.filePath ())) {
return false;
}
// TODO: leave symlinks symlinks? How to copy symlinks with Qt?
} else if (fi.isFile ()) {
QFile file (fi.filePath ());
QFile file_target (fi_target.filePath ());
if (! file.open (QIODevice::ReadOnly)) {
tl::error << QObject::tr ("Unable to open source file for reading: %1").arg (fi.filePath ());
return false;
}
if (! file_target.open (QIODevice::WriteOnly)) {
tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (fi_target.filePath ());
return false;
}
size_t chunk_size = 64 * 1024;
while (! file.atEnd ()) {
QByteArray data = file.read (chunk_size);
file_target.write (data);
}
file.close ();
file_target.close ();
}
}
return true;
}
}

80
src/tl/tlFileUtils.h Normal file
View File

@ -0,0 +1,80 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlFileUtils
#define HDR_tlFileUtils
#include "tlCommon.h"
#include "tlString.h"
#include <QString>
namespace tl
{
/**
* @brief Returns a value indicating whether the parent path is a parent directory of the path
*/
bool TL_PUBLIC is_parent_path (const QString &parent, const QString &path);
/**
* @brief Returns a value indicating whether the parent path is a parent directory of the path (version with std::string)
*/
inline bool TL_PUBLIC is_parent_path (const std::string &parent, const std::string &path)
{
return is_parent_path (tl::to_qstring (parent), tl::to_qstring (path));
}
/**
* @brief Recursively remove the given directory, the files from that directory and all sub-directories
* @return True, if successful. false otherwise.
*/
bool TL_PUBLIC rm_dir_recursive (const QString &path);
/**
* @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string)
* @return True, if successful. false otherwise.
*/
inline bool TL_PUBLIC rm_dir_recursive (const std::string &path)
{
return rm_dir_recursive (tl::to_qstring (path));
}
/**
* @brief Recursively copies a given directory to a target directory
* Both target and source directories need to exist. New directories are created in the target
* directory if required.
* @return True, if successful. false otherwise.
*/
bool TL_PUBLIC cp_dir_recursive (const QString &source, const QString &target);
/**
* @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string)
* @return True, if successful. false otherwise.
*/
inline bool TL_PUBLIC cp_dir_recursive (const std::string &source, const std::string &target)
{
return cp_dir_recursive (tl::to_qstring (source), tl::to_qstring (target));
}
}
#endif

View File

@ -78,7 +78,7 @@ public:
static QNetworkAccessManager *s_network_manager (0);
InputHttpStream::InputHttpStream (const std::string &url)
: m_url (url)
: m_url (url), m_request ("GET"), mp_buffer (0)
{
if (! s_network_manager) {
s_network_manager = new QNetworkAccessManager(0);
@ -87,7 +87,6 @@ InputHttpStream::InputHttpStream (const std::string &url)
connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *)));
connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), this, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *)));
connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), this, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)));
s_network_manager->get (QNetworkRequest (QUrl (tl::to_qstring (url))));
mp_reply = 0;
}
@ -97,6 +96,30 @@ InputHttpStream::~InputHttpStream ()
mp_reply = 0;
}
void
InputHttpStream::set_request (const char *r)
{
m_request = QByteArray (r);
}
void
InputHttpStream::set_data (const char *data)
{
m_data = QByteArray (data);
}
void
InputHttpStream::set_data (const char *data, size_t n)
{
m_data = QByteArray (data, int (n));
}
void
InputHttpStream::add_header (const std::string &name, const std::string &value)
{
m_headers.insert (std::make_pair (name, value));
}
void
InputHttpStream::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth)
{
@ -117,18 +140,39 @@ InputHttpStream::finished (QNetworkReply *reply)
QVariant redirect_target = reply->attribute (QNetworkRequest::RedirectionTargetAttribute);
if (reply->error () == QNetworkReply::NoError && ! redirect_target.isNull ()) {
m_url = tl::to_string (redirect_target.toString ());
s_network_manager->get (QNetworkRequest (QUrl (redirect_target.toString ())));
issue_request (QUrl (redirect_target.toString ()));
delete reply;
} else {
mp_reply = reply;
}
}
void
InputHttpStream::issue_request (const QUrl &url)
{
delete mp_buffer;
mp_buffer = 0;
QNetworkRequest request (url);
for (std::map<std::string, std::string>::const_iterator h = m_headers.begin (); h != m_headers.end (); ++h) {
request.setRawHeader (QByteArray (h->first.c_str ()), QByteArray (h->second.c_str ()));
}
if (m_data.isEmpty ()) {
s_network_manager->sendCustomRequest (request, m_request);
} else {
mp_buffer = new QBuffer (&m_data);
s_network_manager->sendCustomRequest (request, m_request, mp_buffer);
}
}
size_t
InputHttpStream::read (char *b, size_t n)
{
if (mp_reply == 0) {
issue_request (QUrl (tl::to_qstring (m_url)));
}
while (mp_reply == 0) {
QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents);
QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents, 100);
}
if (mp_reply->error () != QNetworkReply::NoError) {
@ -160,4 +204,3 @@ InputHttpStream::filename () const
}
}

View File

@ -27,6 +27,8 @@
#include "tlStream.h"
#include <QObject>
#include <QBuffer>
#include <QByteArray>
class QNetworkAccessManager;
class QNetworkReply;
@ -68,14 +70,38 @@ public:
*/
virtual ~InputHttpStream ();
/**
* @brief Sets the request verb
* The default verb is "GET"
*/
void set_request (const char *r);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a null-terminated string.
*/
void set_data (const char *data);
/**
* @brief Sets data to be sent with the request
* If data is given, it is sent along with the request.
* This version takes a data plus length.
*/
void set_data (const char *data, size_t n);
/**
* @brief Sets a header field
*/
void add_header (const std::string &name, const std::string &value);
/**
* @brief Read from the stream
*
* Implements the basic read method.
*/
virtual size_t read (char *b, size_t n);
virtual void reset ();
virtual void reset ();
virtual std::string source () const
{
@ -89,14 +115,20 @@ public:
virtual std::string filename () const;
private:
std::string m_url;
QNetworkReply *mp_reply;
private slots:
void finished (QNetworkReply *);
void authenticationRequired (QNetworkReply *, QAuthenticator *);
void proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *);
private:
std::string m_url;
QNetworkReply *mp_reply;
QByteArray m_request;
QByteArray m_data;
QBuffer *mp_buffer;
std::map<std::string, std::string> m_headers;
void issue_request (const QUrl &url);
};
}

View File

@ -413,6 +413,46 @@ tl::to_word_or_quoted_string (const std::string &s, const char *non_term)
}
}
void
tl::escape_to_html (std::string &out, const std::string &in, bool replace_newlines)
{
for (const char *cp = in.c_str (); *cp; ++cp) {
if (*cp == '<') {
out += "&lt;";
} else if (*cp == '>') {
out += "&gt;";
} else if (*cp == '&') {
out += "&amp;";
} else if (replace_newlines && *cp == '\n') {
out += "<br/>";
} else {
out += *cp;
}
}
}
std::string
tl::escaped_to_html (const std::string &in, bool replace_newlines)
{
std::string s;
escape_to_html (s, in, replace_newlines);
return s;
}
void
tl::from_string (const std::string &s, const char * &result)
{

View File

@ -326,6 +326,18 @@ TL_PUBLIC int edit_distance (const std::string &a, const std::string &b);
*/
TL_PUBLIC std::string to_word_or_quoted_string (const std::string &s, const char *non_term = "_.$");
/**
* @brief Escapes HTML (or XML) characters from in and adds the result to out
* If "replace_newlines" is true, "\n" will be replaced by "<br/>".
*/
TL_PUBLIC void escape_to_html (std::string &out, const std::string &in, bool replace_newlines = true);
/**
* @brief Escapes HTML (or XML) characters from in and returns the resulting string
* If "replace_newlines" is true, "\n" will be replaced by "<br/>".
*/
TL_PUBLIC std::string escaped_to_html (const std::string &in, bool replace_newlines = true);
/**
* @brief Set the number of digits resolution for a micron display
*/

320
src/tl/tlWebDAV.cc Normal file
View File

@ -0,0 +1,320 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlWebDAV.h"
#include "tlXMLParser.h"
#include "tlHttpStream.h"
#include "tlStream.h"
#include "tlInternational.h"
#include "tlProgress.h"
#include "tlLog.h"
#include <QUrl>
#include <QDir>
namespace tl
{
// ---------------------------------------------------------------
// WebDAVCollection implementation
WebDAVObject::WebDAVObject ()
{
// .. nothing yet ..
}
namespace
{
/**
* @brief A dummy "DOM" for the WebDAV reply
*/
struct ResourceType
{
ResourceType () : is_collection (false) { }
const std::string &collection () const
{
static std::string empty;
return empty;
}
void set_collection (const std::string &)
{
is_collection = true;
}
bool is_collection;
};
/**
* @brief A dummy "DOM" for the WebDAV reply
*/
struct Prop
{
ResourceType resourcetype;
};
/**
* @brief A dummy "DOM" for the WebDAV reply
*/
struct PropStat
{
std::string status;
Prop prop;
};
/**
* @brief A dummy "DOM" for the WebDAV reply
*/
struct Response
{
std::string href;
PropStat propstat;
};
/**
* @brief A dummy "DOM" for the WebDAV reply
*/
struct MultiStatus
{
typedef std::list<Response> container;
typedef container::const_iterator iterator;
iterator begin () const { return responses.begin (); }
iterator end () const { return responses.end (); }
void add (const Response &r) { responses.push_back (r); }
container responses;
};
}
tl::XMLStruct<MultiStatus> xml_struct ("multistatus",
tl::make_element (&MultiStatus::begin, &MultiStatus::end, &MultiStatus::add, "response",
tl::make_member (&Response::href, "href") +
tl::make_element (&Response::propstat, "propstat",
tl::make_member (&PropStat::status, "status") +
tl::make_element (&PropStat::prop, "prop",
tl::make_element (&Prop::resourcetype, "resourcetype",
tl::make_member (&ResourceType::collection, &ResourceType::set_collection, "collection")
)
)
)
)
);
static std::string item_name (const QString &path1, const QString &path2)
{
QStringList sl1 = path1.split (QChar ('/'));
if (! sl1.empty () && sl1.back ().isEmpty ()) {
sl1.pop_back ();
}
QStringList sl2 = path2.split (QChar ('/'));
if (! sl2.empty () && sl2.back ().isEmpty ()) {
sl2.pop_back ();
}
int i = 0;
for ( ; i < sl1.length () && i < sl2.length (); ++i) {
if (sl1 [i] != sl2 [i]) {
throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection item of %2").arg (path2).arg (path1)));
}
}
if (i == sl2.length ()) {
return std::string ();
} else if (i + 1 == sl2.length ()) {
return tl::to_string (sl2[i]);
} else {
throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection sub-item of %2").arg (path2).arg (path1)));
}
}
void
WebDAVObject::read (const std::string &url, int depth)
{
QUrl base_url = QUrl (tl::to_qstring (url));
tl::InputHttpStream http (url);
http.add_header ("User-Agent", "SVN");
http.add_header ("Depth", tl::to_string (depth));
http.set_request ("PROPFIND");
http.set_data ("<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><prop><resourcetype xmlns=\"DAV:\"/></prop></propfind>");
MultiStatus multistatus;
tl::InputStream stream (http);
tl::XMLStreamSource source (stream);
xml_struct.parse (source, multistatus);
// TODO: check status ..
m_items.clear ();
for (MultiStatus::iterator r = multistatus.begin (); r != multistatus.end (); ++r) {
bool is_collection = r->propstat.prop.resourcetype.is_collection;
QUrl item_url = base_url.resolved (QUrl (tl::to_qstring (r->href)));
std::string n = item_name (base_url.path (), item_url.path ());
std::string item_url_string = tl::to_string (item_url.toString ());
if (! n.empty ()) {
m_items.push_back (WebDAVItem (is_collection, item_url_string, n));
} else {
m_is_collection = is_collection;
m_url = item_url_string;
}
}
}
namespace
{
struct DownloadItem
{
DownloadItem (const std::string &u, const std::string &p)
{
url = u;
path = p;
}
std::string url;
std::string path;
};
}
static
void fetch_download_items (const std::string &url, const std::string &target, std::list<DownloadItem> &items, tl::AbsoluteProgress &progress)
{
++progress;
WebDAVObject object;
object.read (url, 1);
if (object.is_collection ()) {
QDir dir (tl::to_qstring (target));
if (! dir.exists ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Download failed: target directory '%1' does not exists").arg (dir.path ())));
}
for (WebDAVObject::iterator i = object.begin (); i != object.end (); ++i) {
QFileInfo new_item (dir.absoluteFilePath (tl::to_qstring (i->name ())));
if (i->is_collection ()) {
if (! new_item.exists ()) {
if (! dir.mkdir (tl::to_qstring (i->name ()))) {
throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1'").arg (dir.path ()).arg (tl::to_qstring (i->name ()))));
}
} else if (! new_item.isDir ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - is already a file").arg (dir.path ()).arg (tl::to_qstring (i->name ()))));
} else if (! new_item.isWritable ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ()))));
}
fetch_download_items (i->url (), tl::to_string (new_item.filePath ()), items, progress);
} else {
if (new_item.exists () && ! new_item.isWritable ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Download failed: file is '%2' in '%1' - already exists, but no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ()))));
}
items.push_back (DownloadItem (i->url (), tl::to_string (dir.absoluteFilePath (tl::to_qstring (i->name ())))));
}
}
} else {
items.push_back (DownloadItem (url, target));
}
}
bool
WebDAVObject::download (const std::string &url, const std::string &target)
{
std::list<DownloadItem> items;
try {
tl::info << QObject::tr ("Fetching file structure from ") << url;
tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Fetching directory structure from %1").arg (tl::to_qstring (url))));
fetch_download_items (url, target, items, progress);
} catch (tl::Exception &ex) {
tl::error << QObject::tr ("Error downloading file structure from '") << url << "':" << tl::endl << ex.msg ();
return false;
}
bool has_errors = false;
{
tl::info << tl::to_string (QObject::tr ("Downloading %1 file(s) now ..").arg (items.size ()));
tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading file(s) from %1").arg (tl::to_qstring (url))), items.size (), 1);
for (std::list<DownloadItem>::const_iterator i = items.begin (); i != items.end (); ++i) {
tl::info << QObject::tr ("Downloading '%1' to '%2' ..").arg (tl::to_qstring (i->url)).arg (tl::to_qstring (i->path));
try {
tl::InputHttpStream http (i->url);
QFile file (tl::to_qstring (i->path));
if (! file.open (QIODevice::WriteOnly)) {
has_errors = true;
tl::error << QObject::tr ("Unable to open file '%1' for writing").arg (tl::to_qstring (i->path));
}
const size_t chunk = 65536;
char b[chunk];
size_t read;
while ((read = http.read (b, sizeof (b))) > 0) {
if (! file.write (b, read)) {
tl::error << QObject::tr ("Unable to write %2 bytes file '%1'").arg (tl::to_qstring (i->path)).arg (int (read));
has_errors = true;
break;
}
}
file.close ();
} catch (tl::Exception &ex) {
tl::error << QObject::tr ("Error downloading file from '") << i->url << "':" << tl::endl << ex.msg ();
has_errors = true;
}
++progress;
}
}
return ! has_errors;
}
}

152
src/tl/tlWebDAV.h Normal file
View File

@ -0,0 +1,152 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlWebDAV
#define HDR_tlWebDAV
#include "tlCommon.h"
#include <string>
#include <vector>
namespace tl
{
/**
* @brief Represents an item in a WebDAV collection
*/
class TL_PUBLIC WebDAVItem
{
public:
/**
* @brief Default constructor
*/
WebDAVItem ()
: m_is_collection (false)
{
// .. nothing yet ..
}
/**
* @brief Constructor
*/
WebDAVItem (bool is_collection, const std::string &url, const std::string &name)
: m_is_collection (is_collection), m_url (url), m_name (name)
{
// .. nothing yet ..
}
/**
* @brief Gets a value indicating whether this item is a collection
* If false, it's a file.
*/
bool is_collection () const
{
return m_is_collection;
}
/**
* @brief Gets the URL of this item
*/
const std::string &url () const
{
return m_url;
}
/**
* @brief Gets the name of this item
* The name is only valid for sub-items.
*/
const std::string &name () const
{
return m_name;
}
protected:
bool m_is_collection;
std::string m_url;
std::string m_name;
};
/**
* @brief Represents an object from a WebDAV URL
* This object can be a file or collection
*/
class TL_PUBLIC WebDAVObject
: public WebDAVItem
{
public:
typedef std::vector<WebDAVItem> container;
typedef container::const_iterator iterator;
/**
* @brief Open a stream with the given URL
*/
WebDAVObject ();
/**
* @brief Populates the collection from the given URL
* The depth value can be 0 (self only) or 1 (self + collection members).
*/
void read (const std::string &url, int depth);
/**
* @brief Gets the items of this collection (begin iterator)
*/
iterator begin () const
{
return m_items.begin ();
}
/**
* @brief Gets the items of this collection (begin iterator)
*/
iterator end () const
{
return m_items.end ();
}
/**
* @brief Downloads the collection or file with the given URL
*
* This method will download the WebDAV object from url to the file path
* given in "target".
*
* For file download, the target must be the path of the target file.
* For collection download, the target must be a directory path. In this
* case, the target directory must exist already.
*
* Sub-directories are created if required.
*
* This method throws an exception if the directory structure could
* not be obtained or downloading of one file failed.
*/
static bool download (const std::string &url, const std::string &target);
private:
container m_items;
};
}
#endif

View File

@ -671,12 +671,12 @@ public:
return m_name;
}
bool check_name (const std::string &, const std::string &, const std::string &qname) const
bool check_name (const std::string & /*uri*/, const std::string &lname, const std::string & /*qname*/) const
{
if (m_name == "*") {
return true;
} else {
return m_name == qname; // no namespace currently
return m_name == lname; // no namespace currently
}
}

359
src/unit_tests/laySalt.cc Normal file
View File

@ -0,0 +1,359 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "laySaltGrain.h"
#include "laySaltGrains.h"
#include "laySalt.h"
#include "tlFileUtils.h"
#include "utHead.h"
#include <QDir>
#include <QSignalSpy>
static std::string grains_to_string (const lay::SaltGrains &gg)
{
std::string res;
res += "[";
bool first = true;
for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) {
if (! first) {
res += ",";
}
first = false;
res += g->name ();
}
for (lay::SaltGrains::collection_iterator gc = gg.begin_collections (); gc != gg.end_collections (); ++gc) {
if (! first) {
res += ",";
}
first = false;
res += gc->name ();
res += grains_to_string (*gc);
}
res += "]";
return res;
}
static std::string salt_to_string (lay::Salt &salt)
{
std::string res;
res += "[";
bool first = true;
for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) {
if (! first) {
res += ",";
}
first = false;
res += (*i)->name ();
}
res += "]";
return res;
}
TEST (1)
{
std::string tmp0 = tmp_file ("tmp0");
lay::SaltGrain g;
g.save (tmp0);
EXPECT_EQ (g.authored_time ().isNull (), true);
EXPECT_EQ (g.installed_time ().isNull (), true);
lay::SaltGrain g0;
g0.load (tmp0);
EXPECT_EQ (g0.authored_time ().isNull (), true);
EXPECT_EQ (g0.installed_time ().isNull (), true);
EXPECT_EQ (g == g0, true);
std::string tmp = tmp_file ();
g.set_name ("abc");
EXPECT_EQ (g.name (), "abc");
g.set_url ("xyz");
EXPECT_EQ (g.url (), "xyz");
g.set_version ("1.0");
EXPECT_EQ (g.version (), "1.0");
g.set_path ("a/b");
EXPECT_EQ (g.path (), "a/b");
g.set_title ("title");
EXPECT_EQ (g.title (), "title");
g.set_doc ("doc");
EXPECT_EQ (g.doc (), "doc");
g.set_doc_url ("doc-url");
EXPECT_EQ (g.doc_url (), "doc-url");
g.set_author ("me");
EXPECT_EQ (g.author (), "me");
g.set_author_contact ("ac");
EXPECT_EQ (g.author_contact (), "ac");
g.set_license ("free");
EXPECT_EQ (g.license (), "free");
g.set_authored_time (QDateTime ());
EXPECT_EQ (g.authored_time ().isNull (), true);
g.set_authored_time (QDateTime::fromMSecsSinceEpoch (1000000000));
EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.authored_time ()), 1000000000);
g.set_installed_time (QDateTime ());
EXPECT_EQ (g.installed_time ().isNull (), true);
g.set_installed_time (QDateTime::fromMSecsSinceEpoch (2000000000));
EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.installed_time ()), 2000000000);
g.add_dependency (lay::SaltGrain::Dependency ());
g.dependencies ().back ().name = "depname";
g.dependencies ().back ().url = "depurl";
g.dependencies ().back ().version = "0.0";
EXPECT_EQ (int (g.dependencies ().size ()), 1);
lay::SaltGrain gg;
EXPECT_EQ (g == gg, false);
EXPECT_EQ (g == g, true);
EXPECT_EQ (g != gg, true);
EXPECT_EQ (g != g, false);
gg = g;
EXPECT_EQ (g == gg, true);
gg.set_doc ("blabla");
EXPECT_EQ (g == gg, false);
EXPECT_EQ (g == gg, false);
g.save (tmp);
EXPECT_EQ (g == gg, false);
gg = lay::SaltGrain ();
gg.load (tmp);
gg.set_path (g.path ()); // path is not set by load(file)
EXPECT_EQ (int (gg.dependencies ().size ()), 1);
EXPECT_EQ (g == gg, true);
gg.add_dependency (lay::SaltGrain::Dependency ());
EXPECT_EQ (g == gg, false);
gg.set_path (tl::to_string (QFileInfo (tl::to_qstring (tmp)).absolutePath ()));
gg.save ();
g = lay::SaltGrain::from_path (gg.path ());
EXPECT_EQ (g == gg, true);
}
TEST (2)
{
EXPECT_EQ (lay::SaltGrain::valid_version (""), true);
EXPECT_EQ (lay::SaltGrain::valid_version ("1"), true);
EXPECT_EQ (lay::SaltGrain::valid_version ("1.2"), true);
EXPECT_EQ (lay::SaltGrain::valid_version ("\t1 . 2.\n3"), true);
EXPECT_EQ (lay::SaltGrain::valid_version ("x"), false);
EXPECT_EQ (lay::SaltGrain::valid_version ("1.2x"), false);
EXPECT_EQ (lay::SaltGrain::valid_name (""), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x"), true);
EXPECT_EQ (lay::SaltGrain::valid_name ("x1"), true);
EXPECT_EQ (lay::SaltGrain::valid_name ("x1 "), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x$1"), false);
EXPECT_EQ (lay::SaltGrain::valid_name ("x/y"), true);
EXPECT_EQ (lay::SaltGrain::valid_name ("x_y"), true);
EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1", ""), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("2", "1"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "2.0"), -1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "1.0"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1", "1.0"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0.0"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.0", "1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1a", "1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.a.1", "1.0.1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.0"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.0"), 1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.1.1"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "991"), -1);
EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "990"), 0);
EXPECT_EQ (lay::SaltGrain::compare_versions ("991", "990"), 1);
}
TEST (3)
{
const QString grain_spec_file = QString::fromUtf8 ("grain.xml");
lay::SaltGrain g;
g.set_name ("x");
QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ());
QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a")));
QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b")));
QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c")));
QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u")));
QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c")));
QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v")));
lay::SaltGrains gg;
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), true);
EXPECT_EQ (grains_to_string (gg), "[]");
tmp_dir.mkdir (dir_a.dirName ());
tmp_dir.mkdir (dir_b.dirName ());
tmp_dir.mkdir (dir_c.dirName ());
dir_c.mkdir (dir_cu.dirName ());
dir_c.mkdir (dir_cc.dirName ());
dir_cc.mkdir (dir_ccv.dirName ());
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), true);
EXPECT_EQ (grains_to_string (gg), "[]");
EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ()));
g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file)));
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), false);
EXPECT_EQ (grains_to_string (gg), "[a]");
EXPECT_EQ (gg.begin_grains ()->path (), tl::to_string (dir_a.absolutePath ()));
g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file)));
g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file)));
g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file)));
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), false);
EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]");
EXPECT_EQ (gg.begin_collections ()->path (), tl::to_string (dir_c.absolutePath ()));
std::string gg_path = tmp_file ("gg.tmp");
gg.save (gg_path);
lay::SaltGrains ggg;
ggg.load (gg_path);
EXPECT_EQ (grains_to_string (ggg), "[a,b,c[c/u,c/c[c/c/v]]]");
// NOTE: The path is not set, so this will fail:
// EXPECT_EQ (gg == ggg, true);
gg.remove_grain (gg.begin_grains (), false);
EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]");
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]");
gg.remove_grain (gg.begin_grains (), true);
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]");
gg.remove_collection (gg.begin_collections (), false);
EXPECT_EQ (grains_to_string (gg), "[b]");
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]");
gg.remove_collection (gg.begin_collections (), true);
EXPECT_EQ (grains_to_string (gg), "[b]");
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (grains_to_string (gg), "[b]");
}
TEST (4)
{
const QString grain_spec_file = QString::fromUtf8 ("grain.xml");
// That's just preparation ...
lay::SaltGrain g;
g.set_name ("x");
QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ());
QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a")));
QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b")));
QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c")));
QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u")));
QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c")));
QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v")));
lay::SaltGrains gg;
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), true);
EXPECT_EQ (grains_to_string (gg), "[]");
tmp_dir.mkdir (dir_a.dirName ());
tmp_dir.mkdir (dir_b.dirName ());
tmp_dir.mkdir (dir_c.dirName ());
dir_c.mkdir (dir_cu.dirName ());
dir_c.mkdir (dir_cc.dirName ());
dir_cc.mkdir (dir_ccv.dirName ());
gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (gg.is_empty (), true);
EXPECT_EQ (grains_to_string (gg), "[]");
EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ()));
g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file)));
g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file)));
g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file)));
g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file)));
// That's the main test part
lay::Salt salt;
EXPECT_EQ (salt.is_empty (), true);
QSignalSpy spy (&salt, SIGNAL (collections_changed ()));
EXPECT_EQ (salt_to_string (salt), "[]");
spy.clear ();
salt.add_location (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (salt.is_empty (), false);
EXPECT_EQ (spy.count (), 1);
EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]");
spy.clear ();
salt.add_location (tl::to_string (tmp_dir.path ()));
EXPECT_EQ (spy.count (), 0);
EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]");
spy.clear ();
salt.add_location (tl::to_string (dir_c.path ()));
EXPECT_EQ (spy.count (), 1);
EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u,c/v,u]");
lay::Salt salt_copy = salt;
(const_cast<lay::SaltGrains &> (*salt_copy.begin ())).remove_grain (salt_copy.begin ()->begin_grains (), true);
spy.clear ();
salt.refresh ();
EXPECT_EQ (spy.count (), 1);
EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u,c/v,u]");
spy.clear ();
salt.remove_location (tl::to_string (dir_c.path ()));
EXPECT_EQ (spy.count (), 1);
EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]");
spy.clear ();
// location already removed
salt.remove_location (tl::to_string (dir_c.path ()));
EXPECT_EQ (spy.count (), 0);
EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]");
EXPECT_EQ (salt.grain_by_name ("x"), 0);
EXPECT_EQ (salt.grain_by_name ("b")->name (), "b");
EXPECT_EQ (salt.grain_by_name ("c/c/v")->name (), "c/c/v");
}

View File

@ -0,0 +1,155 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlFileUtils.h"
#include "utHead.h"
#include <QDir>
#include <QFileInfo>
#include <QFile>
TEST (1)
{
EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home/matthias"), true);
EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home"), true);
EXPECT_EQ (tl::is_parent_path (std::string (""), ""), true);
EXPECT_EQ (tl::is_parent_path (std::string ("/opt/klayout"), "/home/matthias"), false);
EXPECT_EQ (tl::is_parent_path (std::string ("/home/klayout"), "/home/matthias"), false);
}
TEST (2)
{
QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir ();
tmp_dir.mkdir (QString::fromUtf8 ("a"));
QDir adir = tmp_dir;
adir.cd (QString::fromUtf8 ("a"));
EXPECT_EQ (adir.exists (), true);
EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true);
EXPECT_EQ (adir.exists (), false);
tmp_dir.mkdir (QString::fromUtf8 ("a"));
EXPECT_EQ (adir.exists (), true);
EXPECT_EQ (tl::rm_dir_recursive (tl::to_string (adir.absolutePath ())), true);
EXPECT_EQ (adir.exists (), false);
tmp_dir.mkdir (QString::fromUtf8 ("a"));
EXPECT_EQ (adir.exists (), true);
adir.mkdir (QString::fromUtf8 ("b1"));
QDir b1dir = adir;
b1dir.cd (QString::fromUtf8 ("b1"));
adir.mkdir (QString::fromUtf8 ("b2"));
QDir b2dir = adir;
b2dir.cd (QString::fromUtf8 ("b2"));
{
QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x")));
file.open (QIODevice::WriteOnly);
file.write ("hello, world!\n");
file.close ();
}
{
QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y")));
file.open (QIODevice::WriteOnly);
file.write ("hello, world!\n");
file.close ();
}
EXPECT_EQ (adir.exists (), true);
EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true);
EXPECT_EQ (adir.exists (), false);
EXPECT_EQ (b1dir.exists (), false);
EXPECT_EQ (b2dir.exists (), false);
EXPECT_EQ (QFileInfo (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))).exists (), false);
}
TEST (3)
{
QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir ();
tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("a")));
tmp_dir.mkdir (QString::fromUtf8 ("a"));
QDir adir = tmp_dir;
adir.cd (QString::fromUtf8 ("a"));
adir.mkdir (QString::fromUtf8 ("b1"));
QDir b1dir = adir;
b1dir.cd (QString::fromUtf8 ("b1"));
adir.mkdir (QString::fromUtf8 ("b2"));
QDir b2dir = adir;
b2dir.cd (QString::fromUtf8 ("b2"));
{
QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x")));
file.open (QIODevice::WriteOnly);
file.write ("hello, world!\n");
file.close ();
}
{
QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y")));
file.open (QIODevice::WriteOnly);
file.write ("hello, world II!\n");
file.close ();
}
tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("acopy")));
tmp_dir.mkdir (QString::fromUtf8 ("acopy"));
tl::cp_dir_recursive (tmp_dir.absoluteFilePath (QString::fromUtf8 ("a")), tmp_dir.absoluteFilePath (QString::fromUtf8 ("acopy")));
QDir acopydir = tmp_dir;
EXPECT_EQ (acopydir.cd (QString::fromUtf8 ("acopy")), true);
EXPECT_EQ (acopydir.exists (), true);
QDir b1copydir = acopydir;
EXPECT_EQ (b1copydir.cd (QString::fromUtf8 ("b1")), true);
EXPECT_EQ (b1copydir.exists (), true);
QDir b2copydir = acopydir;
EXPECT_EQ (b2copydir.cd (QString::fromUtf8 ("b2")), true);
EXPECT_EQ (b2copydir.exists (), true);
{
QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("x")));
EXPECT_EQ (file.exists (), true);
file.open (QIODevice::ReadOnly);
EXPECT_EQ (file.readAll ().constData (), "hello, world!\n");
file.close ();
}
{
QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("y")));
EXPECT_EQ (file.exists (), true);
file.open (QIODevice::ReadOnly);
EXPECT_EQ (file.readAll ().constData (), "hello, world II!\n");
file.close ();
}
}

View File

@ -0,0 +1,75 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlHttpStream.h"
#include "utHead.h"
static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text");
static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1");
TEST(1)
{
tl::InputHttpStream stream (test_url1);
char b[100];
size_t n = stream.read (b, sizeof (b));
std::string res (b, n);
EXPECT_EQ (res, "hello, world.\n");
}
TEST(2)
{
tl::InputHttpStream stream (test_url2);
stream.add_header ("User-Agent", "SVN");
stream.add_header ("Depth", "1");
stream.set_request ("PROPFIND");
stream.set_data ("<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><prop><resourcetype xmlns=\"DAV:\"/></prop></propfind>");
char b[10000];
size_t n = stream.read (b, sizeof (b));
std::string res (b, n);
EXPECT_EQ (res,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"DAV:\">\n"
"<D:response xmlns:lp1=\"DAV:\">\n"
"<D:href>/svn-public/klayout-resources/trunk/testdata/dir1/</D:href>\n"
"<D:propstat>\n"
"<D:prop>\n"
"<lp1:resourcetype><D:collection/></lp1:resourcetype>\n"
"</D:prop>\n"
"<D:status>HTTP/1.1 200 OK</D:status>\n"
"</D:propstat>\n"
"</D:response>\n"
"<D:response xmlns:lp1=\"DAV:\">\n"
"<D:href>/svn-public/klayout-resources/trunk/testdata/dir1/text</D:href>\n"
"<D:propstat>\n"
"<D:prop>\n"
"<lp1:resourcetype/>\n"
"</D:prop>\n"
"<D:status>HTTP/1.1 200 OK</D:status>\n"
"</D:propstat>\n"
"</D:response>\n"
"</D:multistatus>\n"
);
}

View File

@ -423,3 +423,20 @@ TEST(10)
EXPECT_EQ (unescape_string (escape_string ("'a\n\003")), "'a\n\003");
}
TEST(11)
{
std::string s;
tl::escape_to_html (s, "x");
EXPECT_EQ (s, "x");
tl::escape_to_html (s, "<&>");
EXPECT_EQ (s, "x&lt;&amp;&gt;");
s = std::string ();
tl::escape_to_html (s, "a\nb");
EXPECT_EQ (s, "a<br/>b");
s = std::string ();
tl::escape_to_html (s, "a\nb", false);
EXPECT_EQ (s, "a\nb");
EXPECT_EQ (tl::escaped_to_html ("x<&>"), "x&lt;&amp;&gt;");
EXPECT_EQ (tl::escaped_to_html ("a\nb"), "a<br/>b");
EXPECT_EQ (tl::escaped_to_html ("a\nb", false), "a\nb");
}

129
src/unit_tests/tlWebDAV.cc Normal file
View File

@ -0,0 +1,129 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlWebDAV.h"
#include "utHead.h"
#include <QDir>
static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata");
static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text");
static std::string collection2string (const tl::WebDAVObject &coll)
{
std::string s;
for (tl::WebDAVObject::iterator c = coll.begin (); c != coll.end (); ++c) {
if (!s.empty ()) {
s += "\n";
}
if (c->is_collection ()) {
s += "[dir] ";
}
s += c->name ();
s += " ";
s += c->url ();
}
return s;
}
TEST(1)
{
tl::WebDAVObject collection;
collection.read (test_url1, 1);
EXPECT_EQ (collection.is_collection (), true);
EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/");
EXPECT_EQ (collection2string (collection),
"[dir] dir1 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1/\n"
"[dir] dir2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir2/\n"
"text http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text\n"
"text2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text2"
);
}
TEST(2)
{
tl::WebDAVObject collection;
collection.read (test_url1, 0);
EXPECT_EQ (collection.is_collection (), true);
EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/");
EXPECT_EQ (collection2string (collection), "");
}
TEST(3)
{
tl::WebDAVObject collection;
collection.read (test_url2, 1);
EXPECT_EQ (collection.is_collection (), false);
EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text");
EXPECT_EQ (collection2string (collection), "");
}
TEST(4)
{
tl::WebDAVObject collection;
collection.read (test_url2, 0);
EXPECT_EQ (collection.is_collection (), false);
EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text");
EXPECT_EQ (collection2string (collection), "");
}
TEST(5)
{
tl::WebDAVObject collection;
QDir tmp_dir (tl::to_qstring (tmp_file ("tmp")));
EXPECT_EQ (tmp_dir.exists (), false);
tmp_dir.cdUp ();
tmp_dir.mkdir (tl::to_qstring ("tmp"));
tmp_dir.cd (tl::to_qstring ("tmp"));
bool res = collection.download (test_url1, tl::to_string (tmp_dir.absolutePath ()));
EXPECT_EQ (res, true);
QDir dir1 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir1")));
QDir dir2 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir2")));
QDir dir21 (dir2.absoluteFilePath (QString::fromUtf8 ("dir21")));
EXPECT_EQ (dir1.exists (), true);
EXPECT_EQ (dir2.exists (), true);
EXPECT_EQ (dir21.exists (), true);
QByteArray ba;
QFile text1 (dir1.absoluteFilePath (QString::fromUtf8 ("text")));
text1.open (QIODevice::ReadOnly);
ba = text1.read (10000);
EXPECT_EQ (ba.constData (), "A text.\n");
text1.close ();
QFile text21 (dir21.absoluteFilePath (QString::fromUtf8 ("text")));
text21.open (QIODevice::ReadOnly);
ba = text21.read (10000);
EXPECT_EQ (ba.constData (), "A text II.I.\n");
text21.close ();
}

View File

@ -96,7 +96,11 @@ SOURCES = \
tlXMLParser.cc \
gsiTest.cc \
tlFileSystemWatcher.cc \
tlMath.cc
tlMath.cc \
laySalt.cc \
tlFileUtils.cc \
tlHttpStream.cc \
tlWebDAV.cc
# main components:
SOURCES += \

View File

@ -27,6 +27,7 @@
#include "tlStaticObjects.h"
#include "tlTimer.h"
#include "tlSystemPaths.h"
#include "tlFileUtils.h"
#include "layApplication.h"
#include "gsiExpression.h"
#include "gsiExternalMain.h"
@ -519,8 +520,12 @@ bool TestBase::do_test (const std::string & /*mode*/)
{
// Ensures the test temp directory is present
QDir dir (testtmp ());
QDir tmpdir (dir.absoluteFilePath (tl::to_qstring (m_testdir)));
if (tmpdir.exists () && ! tl::rm_dir_recursive (tmpdir.absolutePath ())) {
throw tl::Exception ("Unable to clean temporary dir: " + tl::to_string (tmpdir.absolutePath ()));
}
if (! dir.mkpath (tl::to_qstring (m_testdir))) {
throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (dir.filePath (tl::to_qstring (m_test))));
throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (tmpdir.absolutePath ()));
}
dir.cd (tl::to_qstring (m_testdir));