Quellcode durchsuchen

#2 #3 add new FormLauncher, FormProductEdit with full functionality

Dalibor Votruba vor 1 Jahr
Ursprung
Commit
4fd9e30c1f

+ 16 - 0
App.config

@@ -43,6 +43,22 @@
             <setting name="CleanupOutputDir" serializeAs="String">
                 <value>True</value>
             </setting>
+            <setting name="PredefinedVariants" serializeAs="String">
+                <value>Foto 10x15cm=150;Foto 15x23cm=120;Foto 20x30cm=100;Magnetka 10x13cm=25,Obraz na plátně 30x45cm=250,Hrnek s potiskem=70
+</value>
+            </setting>
+            <setting name="PredefinedCategories" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="ImportTplUrl" serializeAs="String">
+                <value>https://www.studiou.cz/wp-content/uploads/{YYYY}/{MM}/{file}{ext}</value>
+            </setting>
+            <setting name="ImportTplShortDesc" serializeAs="String">
+                <value>Forografie {file} ve formátu {variant}.</value>
+            </setting>
+            <setting name="ImportTplDesc" serializeAs="String">
+                <value>{category} ve formátu {variant}.</value>
+            </setting>
         </qdr.app.studiou.orders2printpack.Properties.AppSettings>
     </userSettings>
 </configuration>

+ 290 - 0
Design.Designer.cs

@@ -0,0 +1,290 @@
+namespace qdr.app.studiou.orders2printpack
+{
+    partial class Design
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            detProduct = new FlowLayoutPanel();
+            label1 = new Label();
+            tbdetId = new TextBox();
+            label2 = new Label();
+            tbdetName = new TextBox();
+            label3 = new Label();
+            tbdetSku = new TextBox();
+            label4 = new Label();
+            tbdetShortDescription = new TextBox();
+            label5 = new Label();
+            tbdetDescription = new TextBox();
+            label6 = new Label();
+            cbdetVariants = new ComboBox();
+            label7 = new Label();
+            tbdetParent = new ComboBox();
+            label8 = new Label();
+            tbdetPrice = new NumericUpDown();
+            label9 = new Label();
+            cbdetCategories = new ComboBox();
+            label10 = new Label();
+            tbdetUri = new TextBox();
+            detProduct.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)tbdetPrice).BeginInit();
+            SuspendLayout();
+            // 
+            // detProduct
+            // 
+            detProduct.Controls.Add(label1);
+            detProduct.Controls.Add(tbdetId);
+            detProduct.Controls.Add(label7);
+            detProduct.Controls.Add(tbdetParent);
+            detProduct.Controls.Add(label2);
+            detProduct.Controls.Add(tbdetName);
+            detProduct.Controls.Add(label3);
+            detProduct.Controls.Add(tbdetSku);
+            detProduct.Controls.Add(label4);
+            detProduct.Controls.Add(tbdetShortDescription);
+            detProduct.Controls.Add(label5);
+            detProduct.Controls.Add(tbdetDescription);
+            detProduct.Controls.Add(label6);
+            detProduct.Controls.Add(cbdetVariants);
+            detProduct.Controls.Add(label8);
+            detProduct.Controls.Add(tbdetPrice);
+            detProduct.Controls.Add(label9);
+            detProduct.Controls.Add(cbdetCategories);
+            detProduct.Controls.Add(label10);
+            detProduct.Controls.Add(tbdetUri);
+            detProduct.FlowDirection = FlowDirection.TopDown;
+            detProduct.Location = new Point(163, 6);
+            detProduct.Name = "detProduct";
+            detProduct.Size = new Size(474, 438);
+            detProduct.TabIndex = 2;
+            // 
+            // label1
+            // 
+            label1.AutoSize = true;
+            label1.Location = new Point(3, 0);
+            label1.Name = "label1";
+            label1.Size = new Size(17, 15);
+            label1.TabIndex = 0;
+            label1.Text = "Id";
+            // 
+            // tbdetId
+            // 
+            tbdetId.Location = new Point(3, 18);
+            tbdetId.Name = "tbdetId";
+            tbdetId.ReadOnly = true;
+            tbdetId.Size = new Size(203, 23);
+            tbdetId.TabIndex = 1;
+            // 
+            // label2
+            // 
+            label2.AutoSize = true;
+            label2.Location = new Point(3, 88);
+            label2.Name = "label2";
+            label2.Size = new Size(42, 15);
+            label2.TabIndex = 2;
+            label2.Text = "Jméno";
+            // 
+            // tbdetName
+            // 
+            tbdetName.Location = new Point(3, 106);
+            tbdetName.Name = "tbdetName";
+            tbdetName.Size = new Size(203, 23);
+            tbdetName.TabIndex = 3;
+            // 
+            // label3
+            // 
+            label3.AutoSize = true;
+            label3.Location = new Point(3, 132);
+            label3.Name = "label3";
+            label3.Size = new Size(125, 15);
+            label3.TabIndex = 4;
+            label3.Text = "Katalogové číslo (SKU)";
+            // 
+            // tbdetSku
+            // 
+            tbdetSku.Location = new Point(3, 150);
+            tbdetSku.Name = "tbdetSku";
+            tbdetSku.Size = new Size(203, 23);
+            tbdetSku.TabIndex = 5;
+            // 
+            // label4
+            // 
+            label4.AutoSize = true;
+            label4.Location = new Point(3, 176);
+            label4.Name = "label4";
+            label4.Size = new Size(72, 15);
+            label4.TabIndex = 6;
+            label4.Text = "Krátký popis";
+            // 
+            // tbdetShortDescription
+            // 
+            tbdetShortDescription.Location = new Point(3, 194);
+            tbdetShortDescription.Name = "tbdetShortDescription";
+            tbdetShortDescription.Size = new Size(203, 23);
+            tbdetShortDescription.TabIndex = 7;
+            // 
+            // label5
+            // 
+            label5.AutoSize = true;
+            label5.Location = new Point(3, 220);
+            label5.Name = "label5";
+            label5.Size = new Size(36, 15);
+            label5.TabIndex = 8;
+            label5.Text = "Popis";
+            // 
+            // tbdetDescription
+            // 
+            tbdetDescription.Location = new Point(3, 238);
+            tbdetDescription.Multiline = true;
+            tbdetDescription.Name = "tbdetDescription";
+            tbdetDescription.Size = new Size(203, 91);
+            tbdetDescription.TabIndex = 9;
+            // 
+            // label6
+            // 
+            label6.AutoSize = true;
+            label6.Location = new Point(3, 332);
+            label6.Name = "label6";
+            label6.Size = new Size(49, 15);
+            label6.TabIndex = 10;
+            label6.Text = "Varianta";
+            // 
+            // cbdetVariants
+            // 
+            cbdetVariants.FormattingEnabled = true;
+            cbdetVariants.Location = new Point(3, 350);
+            cbdetVariants.Name = "cbdetVariants";
+            cbdetVariants.Size = new Size(203, 23);
+            cbdetVariants.TabIndex = 11;
+            // 
+            // label7
+            // 
+            label7.AutoSize = true;
+            label7.Location = new Point(3, 44);
+            label7.Name = "label7";
+            label7.Size = new Size(101, 15);
+            label7.TabIndex = 12;
+            label7.Text = "Nadřazený / rodič";
+            // 
+            // tbdetParent
+            // 
+            tbdetParent.FormattingEnabled = true;
+            tbdetParent.Location = new Point(3, 62);
+            tbdetParent.Name = "tbdetParent";
+            tbdetParent.Size = new Size(203, 23);
+            tbdetParent.TabIndex = 13;
+            // 
+            // label8
+            // 
+            label8.AutoSize = true;
+            label8.Location = new Point(3, 376);
+            label8.Name = "label8";
+            label8.Size = new Size(34, 15);
+            label8.TabIndex = 14;
+            label8.Text = "Cena";
+            // 
+            // tbdetPrice
+            // 
+            tbdetPrice.DecimalPlaces = 2;
+            detProduct.SetFlowBreak(tbdetPrice, true);
+            tbdetPrice.Location = new Point(3, 394);
+            tbdetPrice.Name = "tbdetPrice";
+            tbdetPrice.Size = new Size(203, 23);
+            tbdetPrice.TabIndex = 16;
+            tbdetPrice.ThousandsSeparator = true;
+            // 
+            // label9
+            // 
+            label9.AutoSize = true;
+            label9.Location = new Point(212, 0);
+            label9.Name = "label9";
+            label9.Size = new Size(57, 15);
+            label9.TabIndex = 17;
+            label9.Text = "Kategorie";
+            // 
+            // cbdetCategories
+            // 
+            cbdetCategories.FormattingEnabled = true;
+            cbdetCategories.Location = new Point(212, 18);
+            cbdetCategories.Name = "cbdetCategories";
+            cbdetCategories.Size = new Size(203, 23);
+            cbdetCategories.TabIndex = 18;
+            // 
+            // label10
+            // 
+            label10.AutoSize = true;
+            label10.Location = new Point(212, 44);
+            label10.Name = "label10";
+            label10.Size = new Size(68, 15);
+            label10.TabIndex = 19;
+            label10.Text = "Obrázek Uri";
+            // 
+            // tbdetUri
+            // 
+            tbdetUri.Location = new Point(212, 62);
+            tbdetUri.Name = "tbdetUri";
+            tbdetUri.Size = new Size(203, 23);
+            tbdetUri.TabIndex = 20;
+            // 
+            // Design
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(800, 450);
+            Controls.Add(detProduct);
+            Name = "Design";
+            Text = "Design";
+            detProduct.ResumeLayout(false);
+            detProduct.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)tbdetPrice).EndInit();
+            ResumeLayout(false);
+        }
+
+        #endregion
+
+        private FlowLayoutPanel detProduct;
+        private Label label1;
+        private TextBox tbdetId;
+        private Label label2;
+        private TextBox tbdetName;
+        private Label label3;
+        private TextBox tbdetSku;
+        private Label label4;
+        private TextBox tbdetShortDescription;
+        private Label label5;
+        private TextBox tbdetDescription;
+        private Label label7;
+        private ComboBox tbdetParent;
+        private Label label6;
+        private ComboBox cbdetVariants;
+        private Label label8;
+        private NumericUpDown tbdetPrice;
+        private Label label9;
+        private ComboBox cbdetCategories;
+        private Label label10;
+        private TextBox tbdetUri;
+    }
+}

+ 20 - 0
Design.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace qdr.app.studiou.orders2printpack
+{
+    public partial class Design : Form
+    {
+        public Design()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 120 - 0
Design.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 234 - 0
Extensions/CsvParserUtility.cs

@@ -0,0 +1,234 @@
+using System.Text;
+using System.Reflection;
+using System.Globalization;
+
+namespace qdr.app.studiou.orders2printpack.Extensions
+{
+    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+    public class CsvColumnAttribute : Attribute
+    {
+        public string Name { get; }
+        public int Index { get; set; }
+
+        public CsvColumnAttribute(string name)
+        {
+            Name = name;
+            Index = -1; // Default to -1 to indicate that index is not set
+        }
+    }
+
+    public class CsvParser
+    {
+        public static IEnumerable<TClass> Parse<TClass>(string filePath, bool hasHeaderRow = true, string delimiter = ",")
+            where TClass : new()
+        {
+            using (var reader = new StreamReader(filePath))
+            {
+                var properties = typeof(TClass).GetProperties()
+                    .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
+                    .ToList();
+
+                string[]? headers = null;
+                if (hasHeaderRow)
+                {
+                    var headerLine = reader.ReadLine();
+                    headers = ParseCsvLine(headerLine, delimiter);
+                }
+
+                while (!reader.EndOfStream)
+                {
+                    var line = reader.ReadLine();
+                    var values = ParseCsvLine(line, delimiter);
+
+                    if (values == null || values.Length == 0)
+                        continue;
+
+                    var item = new TClass();
+
+                    foreach (var prop in properties)
+                    {
+                        var attr = prop.GetCustomAttribute<CsvColumnAttribute>();
+                        int? index = attr?.Index;
+
+                        if ((index == null || index == -1) && headers != null)
+                        {
+                            index = Array.IndexOf(headers, attr?.Name);
+                        }
+
+                        if (index != null && index != -1 && index < values.Length)
+                        {
+                            SetPropertyValue(item, prop, values[index.Value]);
+                        }
+                    }
+
+                    yield return item;
+                }
+            }
+        }
+
+        public static void Save<TClass>(IEnumerable<TClass> data, string filePath, bool includeHeader = true, string delimiter = ",")
+        {
+            var properties = typeof(TClass).GetProperties()
+                .Where(p => p.GetCustomAttribute<CsvColumnAttribute>() != null)
+                .OrderBy(p => p?.GetCustomAttribute<CsvColumnAttribute>()?.Index)
+                .ToList();
+
+            using (var writer = new StreamWriter(filePath, false, Encoding.UTF8))
+            {
+                // Write header
+                if (includeHeader)
+                {
+                    var headerLine = string.Join(delimiter.ToString(), properties.Select(p => QuoteField(p?.GetCustomAttribute<CsvColumnAttribute>()?.Name)));
+                    writer.WriteLine(headerLine);
+                }
+
+                // Write data
+                foreach (var item in data)
+                {
+                    var line = new StringBuilder();
+                    foreach (var prop in properties)
+                    {
+                        if (line.Length > 0)
+                            line.Append(delimiter);
+
+                        var value = prop.GetValue(item);
+                        line.Append(QuoteField(FormatValue(value)));
+                    }
+                    writer.WriteLine(line.ToString());
+                }
+            }
+        }
+
+        /// <summary>
+        /// Parses a single CSV line, handling quoted values and delimiters within quotes.
+        /// </summary>
+        /// <param name="line">The CSV line to parse.</param>
+        /// <param name="delimiter">The delimiter used in the CSV line.</param>
+        /// <returns>An array of field values.</returns>
+        private static string[]? ParseCsvLine(string? line, string delimiter)
+        {
+            if (string.IsNullOrEmpty(line))
+                return null;
+
+            var result = new List<string>();
+            var currentField = new StringBuilder();
+            bool inQuotes = false;
+
+            for (int i = 0; i < line.Length; i++)
+            {
+                if (inQuotes)
+                {
+                    if (line[i] == '"' && (i + 1 == line.Length || line[i + 1] != '"'))
+                    {
+                        // End of quoted field
+                        inQuotes = false;
+                    }
+                    else if (line[i] == '"' && i + 1 < line.Length && line[i + 1] == '"')
+                    {
+                        // Escaped quote
+                        currentField.Append('"');
+                        i++; // Skip next quote
+                    }
+                    else
+                    {
+                        currentField.Append(line[i]);
+                    }
+                }
+                else
+                {
+                    if (line[i] == '"')
+                    {
+                        inQuotes = true;
+                    }
+                    else if (line.Substring(i).StartsWith(delimiter))
+                    {
+                        result.Add(currentField.ToString().Trim());
+                        currentField.Clear();
+                        i += delimiter.Length - 1; // Skip the rest of the delimiter
+                    }
+                    else
+                    {
+                        currentField.Append(line[i]);
+                    }
+                }
+            }
+
+            result.Add(currentField.ToString().Trim());
+            return result.ToArray();
+        }
+
+        private static void SetPropertyValue(object obj, PropertyInfo prop, string value)
+        {
+            if (string.IsNullOrWhiteSpace(value))
+                return;
+
+            try
+            {
+                object convertedValue;
+                if (prop.PropertyType == typeof(string))
+                {
+                    convertedValue = value;
+                }
+                else if (prop.PropertyType == typeof(int))
+                {
+                    convertedValue = int.Parse(value);
+                }
+                else if (prop.PropertyType == typeof(double))
+                {
+                    convertedValue = double.Parse(value, CultureInfo.InvariantCulture);
+                }
+                else if (prop.PropertyType == typeof(DateTime))
+                {
+                    convertedValue = DateTime.Parse(value);
+                }
+                else if (prop.PropertyType == typeof(bool))
+                {
+                    convertedValue = bool.Parse(value);
+                }
+                else
+                {
+                    convertedValue = Convert.ChangeType(value, prop.PropertyType);
+                }
+
+                prop.SetValue(obj, convertedValue);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error setting property {prop.Name}: {ex.Message}");
+            }
+        }
+
+        private static string? FormatValue(object? value)
+        {
+            if (value == null)
+                return string.Empty;
+
+            if (value is DateTime dateTime)
+                return dateTime.ToString("yyyy-MM-dd HH:mm:ss");
+
+            if (value is IFormattable formattable)
+                return formattable.ToString(null, CultureInfo.InvariantCulture);
+
+            return value?.ToString();
+        }
+
+        private static string EscapeField(string? field)
+        {
+            if (string.IsNullOrEmpty(field))
+                return string.Empty;
+
+            bool needsQuoting = field.Contains(',') || field.Contains('"') || field.Contains('\n');
+            if (!needsQuoting)
+                return field;
+
+            return QuoteField(field);
+        }
+        private static string QuoteField(string? field)
+        {
+            if (string.IsNullOrEmpty(field))
+                return "\"\"";
+
+            return $"\"{field.Replace("\"", "\"\"")}\"";
+        }
+    }
+}

+ 15 - 0
Extensions/ExtendedParametrizedString.cs

@@ -0,0 +1,15 @@
+using Quadarax.Foundation.Core.Value;
+
+namespace qdr.app.studiou.orders2printpack.Extensions
+{
+    public class ExtendedParametrizedString : ParameterizedString
+    {
+        public ExtendedParametrizedString(string initialString,  IDictionary<string,string> parameters, string parameterPrefix = "", string parameterPostfix = "") : base(initialString, parameterPrefix, parameterPostfix)
+        {
+            foreach (var parameter in parameters)
+            {
+                AddParameter(parameter.Key, parameter.Value);
+            }
+        }
+    }
+}

+ 134 - 0
Extensions/ListViewExtension.cs

@@ -0,0 +1,134 @@
+using qdr.app.studiou.orders2printpack.ProductStorage;
+using System.Collections;
+using System.Reflection;
+
+namespace qdr.app.studiou.orders2printpack.Extensions
+{
+
+    public static class ListViewExtensions
+    {
+        public static void BindData<TClass>(this ListView listView, IEnumerable<TClass> data, bool enableSorting = true, Action<ListViewItem,TClass>? itemCustomProcessCallback = null)
+            where TClass : class, IIdentifierGetter
+        {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+
+
+            listView.BeginUpdate();
+            listView.Items.Clear();
+
+            // Get properties of the class
+            var properties = typeof(TClass).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+            // Add data to the ListView
+            foreach (var item in data)
+                AddItemInternal(listView, properties, item, itemCustomProcessCallback: itemCustomProcessCallback);
+
+
+            // Auto-resize columns
+            listView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
+
+            if (enableSorting)
+            {
+                EnableSorting(listView);
+            }
+            listView.EndUpdate();
+
+        }
+
+        public static ListViewItem AddItem<TClass>(this ListView listView, TClass item, bool selectItem = false)
+            where TClass : class, IIdentifierGetter
+        {
+            if (item == null)
+                throw new ArgumentNullException(nameof(item));
+            // Get properties of the class
+            var properties = typeof(TClass).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+
+            var lvItem = AddItemInternal<TClass>(listView, properties, item);
+            if (selectItem)
+                lvItem.Selected = true;
+            return lvItem;
+        }
+
+
+        private static ListViewItem AddItemInternal<TClass>(ListView listView, PropertyInfo[] properties, TClass item, bool selectItem = false, Action<ListViewItem, TClass>? itemCustomProcessCallback = null)
+            where TClass : class, IIdentifierGetter
+        {
+            var row = new string[listView.Columns.Count];
+            foreach (var column in listView.Columns)
+            {
+                var header = column as ColumnHeader;
+                if (header == null)
+                    continue;
+
+                var property = properties.FirstOrDefault(p => string.Equals(p.Name, header.Tag?.ToString(), StringComparison.CurrentCultureIgnoreCase));
+                if (property == null)
+                    continue;
+
+                var value = property.GetValue(item);
+                var ordinal = header.DisplayIndex;
+                row[ordinal] = value?.ToString() ?? "";
+            }
+
+            var lvItem = new ListViewItem(row);
+            lvItem.Tag = item.GetIdentifier();
+            if (itemCustomProcessCallback != null)
+                itemCustomProcessCallback(lvItem, item);
+            listView.Items.Add(lvItem);
+            return lvItem;
+        }
+        public static void SaveSelection(this ListView listView)
+        {
+            listView.Tag = listView.SelectedItems.Cast<ListViewItem>().Select(x => x.Tag?.ToString()).ToArray();
+        }
+
+        public static void RestoreSelection(this ListView listView)
+        {
+            if (listView.Tag == null)
+                return;
+            var identifiers = (string[])listView.Tag;
+            foreach (var identifier in identifiers)
+            {
+                var item = listView.Items.Cast<ListViewItem>().FirstOrDefault(x => string.Equals(x.Tag?.ToString(), identifier));
+                if (item != null)
+                    item.Selected = true;
+            }
+            listView.Tag = null;
+        }
+
+        private static void EnableSorting(ListView listView)
+        {
+            listView.ListViewItemSorter = new ListViewItemComparer();
+            listView.ColumnClick += (sender, e) =>
+            {
+                var sorter = (ListViewItemComparer)listView.ListViewItemSorter;
+                sorter.Column = e.Column;
+                sorter.Order = sorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
+                listView.Sort();
+            };
+        }
+
+        private class ListViewItemComparer : IComparer
+        {
+            public int Column { get; set; }
+            public SortOrder Order { get; set; }
+
+            public ListViewItemComparer()
+            {
+                Column = 0;
+                Order = SortOrder.Ascending;
+            }
+
+            public int Compare(object? x, object? y)
+            {
+                var xobj = x as ListViewItem;
+                var yobj = y as ListViewItem;
+
+                int compareResult = string.Compare(xobj?.SubItems[Column].Text, yobj?.SubItems[Column].Text);
+                return Order == SortOrder.Ascending ? compareResult : -compareResult;
+            }
+        }
+    }
+}
+

+ 78 - 56
FormMain.Designer.cs → FormBatch.Designer.cs

@@ -1,6 +1,6 @@
 namespace qdr.app.studiou.orders2printpack
 {
-    partial class FormMain
+    partial class FormBatch
     {
         /// <summary>
         ///  Required designer variable.
@@ -28,7 +28,7 @@
         /// </summary>
         private void InitializeComponent()
         {
-            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormBatch));
             pictureBox1 = new PictureBox();
             panel1 = new Panel();
             splitContainer1 = new SplitContainer();
@@ -89,9 +89,11 @@
             // pictureBox1
             // 
             pictureBox1.Image = Properties.Resources.logo;
-            pictureBox1.Location = new Point(21, 33);
+            pictureBox1.Location = new Point(18, 25);
+            pictureBox1.Margin = new Padding(3, 2, 3, 2);
             pictureBox1.Name = "pictureBox1";
-            pictureBox1.Size = new Size(209, 112);
+            pictureBox1.Size = new Size(183, 112);
+            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
             pictureBox1.TabIndex = 0;
             pictureBox1.TabStop = false;
             // 
@@ -101,14 +103,16 @@
             panel1.Controls.Add(panel2);
             panel1.Dock = DockStyle.Top;
             panel1.Location = new Point(0, 0);
+            panel1.Margin = new Padding(3, 2, 3, 2);
             panel1.Name = "panel1";
-            panel1.Size = new Size(895, 173);
+            panel1.Size = new Size(838, 161);
             panel1.TabIndex = 1;
             // 
             // splitContainer1
             // 
             splitContainer1.Dock = DockStyle.Fill;
-            splitContainer1.Location = new Point(250, 0);
+            splitContainer1.Location = new Point(219, 0);
+            splitContainer1.Margin = new Padding(3, 2, 3, 2);
             splitContainer1.Name = "splitContainer1";
             // 
             // splitContainer1.Panel1
@@ -125,66 +129,70 @@
             splitContainer1.Panel2.Controls.Add(butOutputDir);
             splitContainer1.Panel2.Controls.Add(butOpenSource);
             splitContainer1.Panel2.Controls.Add(butOpenOrdersFile);
-            splitContainer1.Size = new Size(645, 173);
-            splitContainer1.SplitterDistance = 562;
+            splitContainer1.Size = new Size(619, 161);
+            splitContainer1.SplitterDistance = 539;
             splitContainer1.TabIndex = 1;
             // 
             // tbOutputDir
             // 
-            tbOutputDir.Location = new Point(11, 140);
+            tbOutputDir.Location = new Point(6, 123);
+            tbOutputDir.Margin = new Padding(3, 2, 3, 2);
             tbOutputDir.Name = "tbOutputDir";
             tbOutputDir.ReadOnly = true;
-            tbOutputDir.Size = new Size(443, 27);
+            tbOutputDir.Size = new Size(388, 23);
             tbOutputDir.TabIndex = 5;
             // 
             // label3
             // 
             label3.AutoSize = true;
-            label3.Location = new Point(11, 117);
+            label3.Location = new Point(6, 105);
             label3.Name = "label3";
-            label3.Size = new Size(106, 20);
+            label3.Size = new Size(85, 15);
             label3.TabIndex = 4;
             label3.Text = "Složka výstupu";
             // 
             // tbSourceDir
             // 
-            tbSourceDir.Location = new Point(11, 86);
+            tbSourceDir.Location = new Point(6, 80);
+            tbSourceDir.Margin = new Padding(3, 2, 3, 2);
             tbSourceDir.Name = "tbSourceDir";
             tbSourceDir.ReadOnly = true;
-            tbSourceDir.Size = new Size(443, 27);
+            tbSourceDir.Size = new Size(388, 23);
             tbSourceDir.TabIndex = 3;
             // 
             // label2
             // 
             label2.AutoSize = true;
-            label2.Location = new Point(11, 63);
+            label2.Location = new Point(6, 63);
             label2.Name = "label2";
-            label2.Size = new Size(195, 20);
+            label2.Size = new Size(153, 15);
             label2.TabIndex = 2;
             label2.Text = "Složka zdrojových forografií";
             // 
             // tbOrdersSourceFile
             // 
-            tbOrdersSourceFile.Location = new Point(11, 33);
+            tbOrdersSourceFile.Location = new Point(6, 38);
+            tbOrdersSourceFile.Margin = new Padding(3, 2, 3, 2);
             tbOrdersSourceFile.Name = "tbOrdersSourceFile";
             tbOrdersSourceFile.ReadOnly = true;
-            tbOrdersSourceFile.Size = new Size(443, 27);
+            tbOrdersSourceFile.Size = new Size(388, 23);
             tbOrdersSourceFile.TabIndex = 1;
             // 
             // label1
             // 
             label1.AutoSize = true;
-            label1.Location = new Point(11, 10);
+            label1.Location = new Point(6, 21);
             label1.Name = "label1";
-            label1.Size = new Size(172, 20);
+            label1.Size = new Size(138, 15);
             label1.TabIndex = 0;
             label1.Text = "Soubor objenávek (*.csv)";
             // 
             // butOutputDir
             // 
-            butOutputDir.Location = new Point(8, 140);
+            butOutputDir.Location = new Point(9, 120);
+            butOutputDir.Margin = new Padding(3, 2, 3, 2);
             butOutputDir.Name = "butOutputDir";
-            butOutputDir.Size = new Size(43, 23);
+            butOutputDir.Size = new Size(38, 26);
             butOutputDir.TabIndex = 2;
             butOutputDir.Text = ",,,";
             butOutputDir.UseVisualStyleBackColor = true;
@@ -192,9 +200,10 @@
             // 
             // butOpenSource
             // 
-            butOpenSource.Location = new Point(8, 88);
+            butOpenSource.Location = new Point(9, 80);
+            butOpenSource.Margin = new Padding(3, 2, 3, 2);
             butOpenSource.Name = "butOpenSource";
-            butOpenSource.Size = new Size(43, 23);
+            butOpenSource.Size = new Size(38, 23);
             butOpenSource.TabIndex = 1;
             butOpenSource.Text = ",,,";
             butOpenSource.UseVisualStyleBackColor = true;
@@ -202,9 +211,10 @@
             // 
             // butOpenOrdersFile
             // 
-            butOpenOrdersFile.Location = new Point(10, 37);
+            butOpenOrdersFile.Location = new Point(9, 38);
+            butOpenOrdersFile.Margin = new Padding(3, 2, 3, 2);
             butOpenOrdersFile.Name = "butOpenOrdersFile";
-            butOpenOrdersFile.Size = new Size(43, 23);
+            butOpenOrdersFile.Size = new Size(38, 23);
             butOpenOrdersFile.TabIndex = 0;
             butOpenOrdersFile.Text = ",,,";
             butOpenOrdersFile.UseVisualStyleBackColor = true;
@@ -215,63 +225,67 @@
             panel2.Controls.Add(pictureBox1);
             panel2.Dock = DockStyle.Left;
             panel2.Location = new Point(0, 0);
+            panel2.Margin = new Padding(3, 2, 3, 2);
             panel2.Name = "panel2";
-            panel2.Size = new Size(250, 173);
+            panel2.Size = new Size(219, 161);
             panel2.TabIndex = 0;
             // 
             // ssMain
             // 
             ssMain.ImageScalingSize = new Size(20, 20);
             ssMain.Items.AddRange(new ToolStripItem[] { ssProgress, tspOrdersCount, tsbFiles, tsbSources, tsbNotFound });
-            ssMain.Location = new Point(0, 377);
+            ssMain.Location = new Point(0, 356);
             ssMain.Name = "ssMain";
-            ssMain.Size = new Size(895, 26);
+            ssMain.Padding = new Padding(1, 0, 12, 0);
+            ssMain.Size = new Size(838, 22);
             ssMain.TabIndex = 2;
             ssMain.Text = "statusStrip1";
             // 
             // ssProgress
             // 
             ssProgress.Name = "ssProgress";
-            ssProgress.Size = new Size(100, 18);
+            ssProgress.Size = new Size(88, 16);
             // 
             // tspOrdersCount
             // 
             tspOrdersCount.Name = "tspOrdersCount";
-            tspOrdersCount.Size = new Size(91, 20);
+            tspOrdersCount.Size = new Size(73, 17);
             tspOrdersCount.Text = "Objednávek:";
             // 
             // tsbFiles
             // 
             tsbFiles.Name = "tsbFiles";
-            tsbFiles.Size = new Size(68, 20);
+            tsbFiles.Size = new Size(55, 17);
             tsbFiles.Text = "Souborů:";
             // 
             // tsbSources
             // 
             tsbSources.Name = "tsbSources";
-            tsbSources.Size = new Size(56, 20);
+            tsbSources.Size = new Size(45, 17);
             tsbSources.Text = "Zdrojů:";
             // 
             // tsbNotFound
             // 
             tsbNotFound.BackColor = Color.LightCoral;
             tsbNotFound.Name = "tsbNotFound";
-            tsbNotFound.Size = new Size(91, 20);
+            tsbNotFound.Size = new Size(72, 17);
             tsbNotFound.Text = "Nenalezeno:";
             // 
             // panel3
             // 
             panel3.Controls.Add(splitContainer2);
             panel3.Dock = DockStyle.Fill;
-            panel3.Location = new Point(0, 173);
+            panel3.Location = new Point(0, 161);
+            panel3.Margin = new Padding(3, 2, 3, 2);
             panel3.Name = "panel3";
-            panel3.Size = new Size(895, 204);
+            panel3.Size = new Size(838, 195);
             panel3.TabIndex = 3;
             // 
             // splitContainer2
             // 
             splitContainer2.Dock = DockStyle.Fill;
             splitContainer2.Location = new Point(0, 0);
+            splitContainer2.Margin = new Padding(3, 2, 3, 2);
             splitContainer2.Name = "splitContainer2";
             splitContainer2.Orientation = Orientation.Horizontal;
             // 
@@ -282,17 +296,19 @@
             // splitContainer2.Panel2
             // 
             splitContainer2.Panel2.Controls.Add(splitContainer3);
-            splitContainer2.Size = new Size(895, 204);
-            splitContainer2.SplitterDistance = 32;
+            splitContainer2.Size = new Size(838, 195);
+            splitContainer2.SplitterDistance = 30;
+            splitContainer2.SplitterWidth = 3;
             splitContainer2.TabIndex = 0;
             // 
             // tsMain
             // 
+            tsMain.Dock = DockStyle.None;
             tsMain.ImageScalingSize = new Size(20, 20);
             tsMain.Items.AddRange(new ToolStripItem[] { tsbCheck, tsbDo, tsbProtocol, toolStripSeparator1, tsbAddSourceDir, toolStripSeparator2, tsbSettings });
             tsMain.Location = new Point(0, 0);
             tsMain.Name = "tsMain";
-            tsMain.Size = new Size(895, 27);
+            tsMain.Size = new Size(350, 25);
             tsMain.TabIndex = 0;
             tsMain.Text = "toolStrip1";
             // 
@@ -303,7 +319,7 @@
             tsbCheck.Image = (Image)resources.GetObject("tsbCheck.Image");
             tsbCheck.ImageTransparentColor = Color.Magenta;
             tsbCheck.Name = "tsbCheck";
-            tsbCheck.Size = new Size(85, 24);
+            tsbCheck.Size = new Size(68, 22);
             tsbCheck.Text = "1. Kontrola";
             tsbCheck.Click += tsbCheck_Click;
             // 
@@ -314,7 +330,7 @@
             tsbDo.Image = (Image)resources.GetObject("tsbDo.Image");
             tsbDo.ImageTransparentColor = Color.Magenta;
             tsbDo.Name = "tsbDo";
-            tsbDo.Size = new Size(66, 24);
+            tsbDo.Size = new Size(53, 22);
             tsbDo.Text = "2. Spusť";
             tsbDo.Click += tsbDo_Click;
             // 
@@ -325,14 +341,14 @@
             tsbProtocol.Image = (Image)resources.GetObject("tsbProtocol.Image");
             tsbProtocol.ImageTransparentColor = Color.Magenta;
             tsbProtocol.Name = "tsbProtocol";
-            tsbProtocol.Size = new Size(80, 24);
+            tsbProtocol.Size = new Size(65, 22);
             tsbProtocol.Text = "3.Protokol";
             tsbProtocol.Click += tsbProtocol_Click;
             // 
             // toolStripSeparator1
             // 
             toolStripSeparator1.Name = "toolStripSeparator1";
-            toolStripSeparator1.Size = new Size(6, 27);
+            toolStripSeparator1.Size = new Size(6, 25);
             // 
             // tsbAddSourceDir
             // 
@@ -340,14 +356,14 @@
             tsbAddSourceDir.Image = (Image)resources.GetObject("tsbAddSourceDir.Image");
             tsbAddSourceDir.ImageTransparentColor = Color.Magenta;
             tsbAddSourceDir.Name = "tsbAddSourceDir";
-            tsbAddSourceDir.Size = new Size(96, 24);
+            tsbAddSourceDir.Size = new Size(77, 22);
             tsbAddSourceDir.Text = "Přidej složku";
             tsbAddSourceDir.Click += tsbAddSourceDir_Click;
             // 
             // toolStripSeparator2
             // 
             toolStripSeparator2.Name = "toolStripSeparator2";
-            toolStripSeparator2.Size = new Size(6, 27);
+            toolStripSeparator2.Size = new Size(6, 25);
             // 
             // tsbSettings
             // 
@@ -356,7 +372,7 @@
             tsbSettings.Image = (Image)resources.GetObject("tsbSettings.Image");
             tsbSettings.ImageTransparentColor = Color.Magenta;
             tsbSettings.Name = "tsbSettings";
-            tsbSettings.Size = new Size(78, 24);
+            tsbSettings.Size = new Size(63, 22);
             tsbSettings.Text = "Nastavení";
             tsbSettings.Click += tsbSettings_Click;
             // 
@@ -364,6 +380,7 @@
             // 
             splitContainer3.Dock = DockStyle.Fill;
             splitContainer3.Location = new Point(0, 0);
+            splitContainer3.Margin = new Padding(3, 2, 3, 2);
             splitContainer3.Name = "splitContainer3";
             // 
             // splitContainer3.Panel1
@@ -374,17 +391,19 @@
             // 
             splitContainer3.Panel2.Controls.Add(lbNotMapped);
             splitContainer3.Panel2.Controls.Add(toolStrip1);
-            splitContainer3.Size = new Size(895, 168);
-            splitContainer3.SplitterDistance = 592;
+            splitContainer3.Size = new Size(838, 162);
+            splitContainer3.SplitterDistance = 554;
             splitContainer3.TabIndex = 1;
             // 
             // lbLog
             // 
             lbLog.Dock = DockStyle.Fill;
             lbLog.FormattingEnabled = true;
+            lbLog.ItemHeight = 15;
             lbLog.Location = new Point(0, 0);
+            lbLog.Margin = new Padding(3, 2, 3, 2);
             lbLog.Name = "lbLog";
-            lbLog.Size = new Size(592, 168);
+            lbLog.Size = new Size(554, 162);
             lbLog.TabIndex = 0;
             // 
             // lbNotMapped
@@ -392,10 +411,12 @@
             lbNotMapped.Dock = DockStyle.Fill;
             lbNotMapped.FormattingEnabled = true;
             lbNotMapped.HorizontalScrollbar = true;
+            lbNotMapped.ItemHeight = 15;
             lbNotMapped.Location = new Point(0, 25);
+            lbNotMapped.Margin = new Padding(3, 2, 3, 2);
             lbNotMapped.Name = "lbNotMapped";
             lbNotMapped.ScrollAlwaysVisible = true;
-            lbNotMapped.Size = new Size(299, 143);
+            lbNotMapped.Size = new Size(280, 137);
             lbNotMapped.TabIndex = 1;
             lbNotMapped.MouseDoubleClick += lbNotMapped_MouseDoubleClick;
             // 
@@ -405,14 +426,14 @@
             toolStrip1.Items.AddRange(new ToolStripItem[] { toolStripLabel1 });
             toolStrip1.Location = new Point(0, 0);
             toolStrip1.Name = "toolStrip1";
-            toolStrip1.Size = new Size(299, 25);
+            toolStrip1.Size = new Size(280, 25);
             toolStrip1.TabIndex = 0;
             toolStrip1.Text = "toolStrip1";
             // 
             // toolStripLabel1
             // 
             toolStripLabel1.Name = "toolStripLabel1";
-            toolStripLabel1.Size = new Size(146, 22);
+            toolStripLabel1.Size = new Size(114, 22);
             toolStripLabel1.Text = "Nepřiřazené položky";
             // 
             // dlgOpenFile
@@ -420,16 +441,17 @@
             dlgOpenFile.DefaultExt = "*.csv";
             dlgOpenFile.Filter = "CSV files|*.csv";
             // 
-            // FormMain
+            // FormBatch
             // 
-            AutoScaleDimensions = new SizeF(8F, 20F);
+            AutoScaleDimensions = new SizeF(7F, 15F);
             AutoScaleMode = AutoScaleMode.Font;
-            ClientSize = new Size(895, 403);
+            ClientSize = new Size(838, 378);
             Controls.Add(panel3);
             Controls.Add(ssMain);
             Controls.Add(panel1);
             Icon = (Icon)resources.GetObject("$this.Icon");
-            Name = "FormMain";
+            Margin = new Padding(3, 2, 3, 2);
+            Name = "FormBatch";
             Text = "Form1";
             Load += FormMain_Load;
             ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();

+ 2 - 2
FormMain.cs → FormBatch.cs

@@ -8,7 +8,7 @@ using System.IO.Abstractions;
 
 namespace qdr.app.studiou.orders2printpack
 {
-    public partial class FormMain : Form
+    public partial class FormBatch : Form
     {
         #region *** Constants ***
         private const string CS_COL_SOURCE_PATH = "LocalPath";
@@ -32,7 +32,7 @@ namespace qdr.app.studiou.orders2printpack
         #endregion
 
         #region *** Constructor ***
-        public FormMain()
+        public FormBatch()
         {
             InitializeComponent();
             _outputPath = string.Empty;

+ 6 - 6
FormMain.resx → FormBatch.resx

@@ -127,7 +127,7 @@
   <data name="tsbCheck.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
-        YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
         6CYmNoMYJJ0NBiFFIoIytOuf0+TeXP3yde+iyS+3OcP53Z4y3/dJJ4HAsiwyTVMp6BQCBIZhKAWdEcHV
         vSlBmeB82NFy1KLluEWOPRC5MoHdMWhazwi4RJlALgf4EuT6BI+5kCsTrGddUY658E+QvyXYHq9UnRyC
         U87f4aUApcXhnrI9Jzg/laQKFntXlHM+lSQK5psL5fvbp/JvJLGCQqmSWM5JkiCT84igXGtSrruKLQ0T
@@ -138,7 +138,7 @@
   <data name="tsbDo.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
-        YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
         6CYmNoMYJJ0NBiFFIoIytOuf0+TeXP3yde+iyS+3OcP53Z4y3/dJJ4HAsiwyTVMp6BQCBIZhKAWdEcHV
         vSlBmeB82NFy1KLluEWOPRC5MoHdMWhazwi4RJlALgf4EuT6BI+5kCsTrGddUY658E+QvyXYHq9UnRyC
         U87f4aUApcXhnrI9Jzg/laQKFntXlHM+lSQK5psL5fvbp/JvJLGCQqmSWM5JkiCT84igXGtSrruKLQ0T
@@ -149,7 +149,7 @@
   <data name="tsbProtocol.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
-        YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
         6CYmNoMYJJ0NBiFFIoIytOuf0+TeXP3yde+iyS+3OcP53Z4y3/dJJ4HAsiwyTVMp6BQCBIZhKAWdEcHV
         vSlBmeB82NFy1KLluEWOPRC5MoHdMWhazwi4RJlALgf4EuT6BI+5kCsTrGddUY658E+QvyXYHq9UnRyC
         U87f4aUApcXhnrI9Jzg/laQKFntXlHM+lSQK5psL5fvbp/JvJLGCQqmSWM5JkiCT84igXGtSrruKLQ0T
@@ -160,7 +160,7 @@
   <data name="tsbAddSourceDir.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
-        YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
         6CYmNoMYJJ0NBiFFIoIytOuf0+TeXP3yde+iyS+3OcP53Z4y3/dJJ4HAsiwyTVMp6BQCBIZhKAWdEcHV
         vSlBmeB82NFy1KLluEWOPRC5MoHdMWhazwi4RJlALgf4EuT6BI+5kCsTrGddUY658E+QvyXYHq9UnRyC
         U87f4aUApcXhnrI9Jzg/laQKFntXlHM+lSQK5psL5fvbp/JvJLGCQqmSWM5JkiCT84igXGtSrruKLQ0T
@@ -171,7 +171,7 @@
   <data name="tsbSettings.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
-        YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEKSURBVEhL3ZG9DsFQHMXvczDZvIOtXsHObuhqkViI3Quw
         6CYmNoMYJJ0NBiFFIoIytOuf0+TeXP3yde+iyS+3OcP53Z4y3/dJJ4HAsiwyTVMp6BQCBIZhKAWdEcHV
         vSlBmeB82NFy1KLluEWOPRC5MoHdMWhazwi4RJlALgf4EuT6BI+5kCsTrGddUY658E+QvyXYHq9UnRyC
         U87f4aUApcXhnrI9Jzg/laQKFntXlHM+lSQK5psL5fvbp/JvJLGCQqmSWM5JkiCT84igXGtSrruKLQ0T
@@ -189,7 +189,7 @@
     <value>399, 17</value>
   </metadata>
   <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>69</value>
+    <value>59</value>
   </metadata>
   <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>

+ 95 - 0
FormLauncher.Designer.cs

@@ -0,0 +1,95 @@
+namespace qdr.app.studiou.orders2printpack
+{
+    partial class FormLauncher
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormLauncher));
+            pictureBox1 = new PictureBox();
+            butFormBatch = new Button();
+            butProductEdit = new Button();
+            ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
+            SuspendLayout();
+            // 
+            // pictureBox1
+            // 
+            pictureBox1.Image = Properties.Resources.logo;
+            pictureBox1.Location = new Point(10, 9);
+            pictureBox1.Margin = new Padding(3, 2, 3, 2);
+            pictureBox1.Name = "pictureBox1";
+            pictureBox1.Size = new Size(183, 84);
+            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
+            pictureBox1.TabIndex = 1;
+            pictureBox1.TabStop = false;
+            // 
+            // butFormBatch
+            // 
+            butFormBatch.Location = new Point(209, 9);
+            butFormBatch.Name = "butFormBatch";
+            butFormBatch.Size = new Size(253, 27);
+            butFormBatch.TabIndex = 2;
+            butFormBatch.Text = "Příprava dávky pro Tiskovou službu";
+            butFormBatch.UseVisualStyleBackColor = true;
+            butFormBatch.Click += butFormBatch_Click;
+            // 
+            // butProductEdit
+            // 
+            butProductEdit.Location = new Point(209, 42);
+            butProductEdit.Name = "butProductEdit";
+            butProductEdit.Size = new Size(253, 27);
+            butProductEdit.TabIndex = 3;
+            butProductEdit.Text = "Příprava Produktů";
+            butProductEdit.UseVisualStyleBackColor = true;
+            butProductEdit.Click += butProductEdit_Click;
+            // 
+            // FormLauncher
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(478, 104);
+            Controls.Add(butProductEdit);
+            Controls.Add(butFormBatch);
+            Controls.Add(pictureBox1);
+            FormBorderStyle = FormBorderStyle.FixedSingle;
+            Icon = (Icon)resources.GetObject("$this.Icon");
+            Margin = new Padding(3, 2, 3, 2);
+            MaximizeBox = false;
+            Name = "FormLauncher";
+            StartPosition = FormStartPosition.CenterScreen;
+            Text = "FormLauncher";
+            Load += FormLauncher_Load;
+            ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
+            ResumeLayout(false);
+        }
+
+        #endregion
+
+        private PictureBox pictureBox1;
+        private Button butFormBatch;
+        private Button butProductEdit;
+    }
+}

+ 37 - 0
FormLauncher.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace qdr.app.studiou.orders2printpack
+{
+    public partial class FormLauncher : Form
+    {
+        public FormLauncher()
+        {
+            InitializeComponent();
+        }
+
+        private void FormLauncher_Load(object sender, EventArgs e)
+        {
+            Text = $"{Application.ProductName} v{Application.ProductVersion}";
+        }
+
+        private void butFormBatch_Click(object sender, EventArgs e)
+        {
+            var form = new FormBatch();
+            form.Show(this);
+        }
+
+        private void butProductEdit_Click(object sender, EventArgs e)
+        {
+            var form = new FormProductEdit();
+            form.Show(this);
+        }
+    }
+}

+ 216 - 0
FormLauncher.resx

@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQ
+        AADDDgAAww4AAAAAAAAAAAAAjIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/iYmJ/6Ghof/Y2Nj/u7u7/46Ojv/Gxsb/yMjI/42N
+        jf+ysrL/1tbW/6Wlpf+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Kior/lJSU/+zs7P/29vb/pqam/+np
+        6f/s7Oz/mpqa//Dw8P/z8/P/mZmZ/4qKiv+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+JiYn/ysrK//7+
+        /v/Hx8f/5+fn/+vr6/++vr7//////8/Pz/+Ghob/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJ
+        if+jo6P/+fn5/+np6f/t7e3/8fHx/+3t7f/39/f/oKCg/4mJif+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4qKiv/a2tr///////z8/P/+/v7//////9nZ2f+MjIz/i4uL/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+MjIz/jIyM/4yM
+        jP+Li4v/ioqK/4qKiv+JiYn/iIiI/9DQ0P//////////////////////0tLS/4qKiv+JiYn/iYmJ/4mJ
+        if+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4yM
+        jP+MjIz/ioqK/5iYmP+kpKT/pKSk/6Kiov+urq7/9vb2///////////////////////z8/P/ra2t/6Ki
+        ov+lpaX/paWl/5qamv+Kior/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+Li4v/jIyM/4mJif+xsbH/8/Pz//n5+f/4+Pj/+Pj4//n5+f//////////////////////////////
+        ///5+fn/+Pj4//j4+P/5+fn/9PT0/7e3t/+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/ioqK/9jY2P/////////////////////////////////r6+v/1NTU/9TU
+        1P/q6ur//v7+////////////////////////////2tra/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/39/f////////////////////////////1dXV/5ub
+        m/+Ghob/h4eH/5mZmf/U1NT////////////////////////////e3t7/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi//e3t7//////////////////////+Pj
+        4/+Pj4//mJiY/7+/v/+/v7//mJiY/42Njf/n5+f//////////////////////97e3v+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+MjIz/i4uL/+Dg4P//////////////
+        ///+/v7/ubm5/5GRkf/g4OD////////////j4+P/lJSU/7Kysv/+/v7/////////////////39/f/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4yMjP+Kior/ycnJ/+7u
+        7v/r6+v/7e3t/+Li4v+UlJT/vLy8///////////////////////CwsL/kJCQ/9vb2//u7u7/7Ozs/+3t
+        7f/Ly8v/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/jIyM/4yM
+        jP+MjIz/j4+P/42Njf+Pj4//i4uL/4aGhv/R0dH//////////////////////9XV1f+JiYn/i4uL/4+P
+        j/+Ojo7/jo6O/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uL
+        i/+MjIz/i4uL/76+vv/d3d3/2tra/9zc3P/Q0ND/jY2N/8bGxv//////////////////////zMzM/42N
+        jf/Kysr/3d3d/9vb2//d3d3/wMDA/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+Li4v/39/f//////////////////v7+/+rq6v/nZ2d//Ly8v////////////X1
+        9f+jo6P/o6Oj//n5+f/////////////////f39//jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi//e3t7//////////////////////9HR0f+Ghob/tbW1/+fn
+        5//m5ub/uLi4/4SEhP/Pz8////////Hx8f/Jycn/7u7u/+Dg4P+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/9/f3///////////////////////9/f3/7u7
+        u/+Ghob/jo6O/42Njf+EhIT/ubm5//n5+f//////0dHR/3t7e/++vr7/39/f/42Njf+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/3d3d////////////////////
+        ////////+fn5/9XV1f+zs7P/s7Oz/9PT0//39/f////////////f39//i4uL/9LS0v/f39//jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJif/Dw8P//Pz8//39
+        /f/9/f3//f39//7+/v////////////r6+v/6+vr////////////+/v7//f39//r6+v/r6+v/9/f3/8jI
+        yP+Kior/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/5KS
+        kv+zs7P/u7u7/7u7u/+7u7v/y8vL//z8/P//////////////////////+vr6/8nJyf+5ubn/vb29/8DA
+        wP+2trb/lJSU/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/i4uL/4qKiv+JiYn/iYmJ/4iIiP+Tk5P/6enp//r6+v/4+Pj/+Pj4//r6+v/g4OD/kJCQ/4iI
+        iP+Kior/ioqK/4qKiv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+ZmZn/o6Oj/6CgoP+hoaH/o6Oj/5iY
+        mP+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4qKiv+Kior/ioqK/4qK
+        iv+Kior/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAA
+        AAAABAAAww4AAMMOAAAAAAAAAAAAAIyMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4qKiv+JiYn/iYmJ/4qK
+        iv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4aGhv9/f3//fX19/319
+        ff9/f3//hoaG/4qKiv+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJif+JiYn//////7m5
+        uf+1tbX//////42Njf+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+Ghob/dXV1//Dw
+        8P/u7u7/8PDw//Dw8P90dHT/hoaG/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4qKiv+FhYX/enp6/2tr
+        a/+YmJj///////////+ampr/ampq/3p6ev+FhYX/ioqK/4yMjP+MjIz/jIyM/4uLi/+FhYX/hoaG/9jY
+        2P/Q0ND/9PT0////////////8vLy/9DQ0P/Z2dn/iYmJ/4aGhv+Li4v/jIyM/4yMjP+Kior/gYGB/7S0
+        tP////////////////+cnJz/m5ub/////////////////7S0tP+BgYH/ioqK/4yMjP+MjIz/ioqK/4CA
+        gP+1tbX///////////+Pj4//yMjI/8rKyv+Ojo7///////////+1tbX/gICA/4qKiv+MjIz/jIyM/4qK
+        iv+BgYH/iIiI/7Kysv+np6f/ioqK////////////jo6O/6Wlpf+zs7P/ioqK/4KCgv+Kior/jIyM/4yM
+        jP+Kior/gYGB/6enp///////+vr6/4KCgv///////////4aGhv/8/Pz//////6ysrP+CgoL/ioqK/4yM
+        jP+MjIz/ioqK/4CAgP+zs7P///////////+vr6//ioqK/4qKiv+xsbH//////7Kysv+9vb3/gYGB/4qK
+        iv+MjIz/jIyM/4uLi/+Dg4P/q6ur/////////////////+fn5//n5+f////////////g4OD/tra2/4SE
+        hP+Li4v/jIyM/4yMjP+Li4v/iIiI/39/f/+VlZX/jY2N/9vb2////////////9XV1f+RkZH/np6e/4OD
+        g/+IiIj/i4uL/4yMjP+MjIz/jIyM/4uLi/+IiIj/hISE/39/f/98fHz/hISE/4SEhP99fX3/f39//4WF
+        hf+JiYn/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4uLi/+Kior/iIiI/4aGhv+Ghob/iIiI/4qK
+        iv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/i4uL/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+</value>
+  </data>
+</root>

+ 959 - 0
FormProductEdit.Designer.cs

@@ -0,0 +1,959 @@
+namespace qdr.app.studiou.orders2printpack
+{
+    partial class FormProductEdit
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormProductEdit));
+            ssMain = new StatusStrip();
+            tsslblOutput = new ToolStripStatusLabel();
+            tsslblProducts = new ToolStripStatusLabel();
+            tsslblVariants = new ToolStripStatusLabel();
+            tsMain = new ToolStrip();
+            tbbutNew = new ToolStripButton();
+            tbbutOpen = new ToolStripButton();
+            tbbutSave = new ToolStripButton();
+            toolStripSeparator1 = new ToolStripSeparator();
+            tbbutImport = new ToolStripButton();
+            splitContainer1 = new SplitContainer();
+            lvProducts = new ListView();
+            colprdCat = new ColumnHeader();
+            colprdType = new ColumnHeader();
+            colprdPrior = new ColumnHeader();
+            colprdName = new ColumnHeader();
+            colprdVariant = new ColumnHeader();
+            colprdPrice = new ColumnHeader();
+            colprdUri = new ColumnHeader();
+            colprdDescription = new ColumnHeader();
+            tsProducts = new ToolStrip();
+            toolStripLabel4 = new ToolStripLabel();
+            toolStripSeparator10 = new ToolStripSeparator();
+            tbbutProdNew = new ToolStripButton();
+            tbbutProdVarNew = new ToolStripButton();
+            tbbutProdDelete = new ToolStripButton();
+            toolStripSeparator2 = new ToolStripSeparator();
+            tbbutProdClone = new ToolStripButton();
+            toolStripSeparator3 = new ToolStripSeparator();
+            toolStripLabel1 = new ToolStripLabel();
+            tbbutProduct = new ToolStripButton();
+            tbbutVariant = new ToolStripButton();
+            splitContainer2 = new SplitContainer();
+            splitContainer3 = new SplitContainer();
+            lvVariants = new ListView();
+            colvarName = new ColumnHeader();
+            colvarPrice = new ColumnHeader();
+            tsVariants = new ToolStrip();
+            toolStripLabel2 = new ToolStripLabel();
+            toolStripSeparator6 = new ToolStripSeparator();
+            tbbutVarNew = new ToolStripButton();
+            tbbutVarDelete = new ToolStripButton();
+            toolStripSeparator7 = new ToolStripSeparator();
+            tbbutVarApply = new ToolStripButton();
+            lvCategories = new ListView();
+            colcatName = new ColumnHeader();
+            tsCategories = new ToolStrip();
+            toolStripLabel3 = new ToolStripLabel();
+            toolStripSeparator8 = new ToolStripSeparator();
+            tbbutCatNew = new ToolStripButton();
+            tbbutCatDelete = new ToolStripButton();
+            toolStripSeparator9 = new ToolStripSeparator();
+            detProduct = new FlowLayoutPanel();
+            ltbdetId = new Label();
+            tbdetId = new TextBox();
+            lcbdetParent = new Label();
+            cbdetParent = new ComboBox();
+            ltbdetName = new Label();
+            tbdetName = new TextBox();
+            ltbdetSku = new Label();
+            tbdetSku = new TextBox();
+            ltbdetShortDescription = new Label();
+            tbdetShortDescription = new TextBox();
+            ltbdetDescription = new Label();
+            tbdetDescription = new TextBox();
+            ltbdetPrice = new Label();
+            tbdetPrice = new NumericUpDown();
+            lcbdetCategories = new Label();
+            cbdetCategories = new ComboBox();
+            ltbdetUri = new Label();
+            tbdetUri = new TextBox();
+            tsProductDetail = new ToolStrip();
+            tblbCaption = new ToolStripLabel();
+            toolStripSeparator5 = new ToolStripSeparator();
+            tbbutDetSave = new ToolStripButton();
+            toolStripSeparator4 = new ToolStripSeparator();
+            tbbutDetCopy = new ToolStripButton();
+            tbbutDetPaste = new ToolStripButton();
+            dlgOpenFile = new OpenFileDialog();
+            dlgOpenDir = new FolderBrowserDialog();
+            colprdSku = new ColumnHeader();
+            ssMain.SuspendLayout();
+            tsMain.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
+            splitContainer1.Panel1.SuspendLayout();
+            splitContainer1.Panel2.SuspendLayout();
+            splitContainer1.SuspendLayout();
+            tsProducts.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)splitContainer2).BeginInit();
+            splitContainer2.Panel1.SuspendLayout();
+            splitContainer2.Panel2.SuspendLayout();
+            splitContainer2.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)splitContainer3).BeginInit();
+            splitContainer3.Panel1.SuspendLayout();
+            splitContainer3.Panel2.SuspendLayout();
+            splitContainer3.SuspendLayout();
+            tsVariants.SuspendLayout();
+            tsCategories.SuspendLayout();
+            detProduct.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)tbdetPrice).BeginInit();
+            tsProductDetail.SuspendLayout();
+            SuspendLayout();
+            // 
+            // ssMain
+            // 
+            ssMain.Items.AddRange(new ToolStripItem[] { tsslblOutput, tsslblProducts, tsslblVariants });
+            ssMain.Location = new Point(0, 428);
+            ssMain.Name = "ssMain";
+            ssMain.Size = new Size(1117, 22);
+            ssMain.TabIndex = 0;
+            ssMain.Text = "statusStrip1";
+            // 
+            // tsslblOutput
+            // 
+            tsslblOutput.Name = "tsslblOutput";
+            tsslblOutput.Size = new Size(66, 17);
+            tsslblOutput.Text = "Output File";
+            // 
+            // tsslblProducts
+            // 
+            tsslblProducts.Name = "tsslblProducts";
+            tsslblProducts.Size = new Size(54, 17);
+            tsslblProducts.Text = "Products";
+            // 
+            // tsslblVariants
+            // 
+            tsslblVariants.Name = "tsslblVariants";
+            tsslblVariants.Size = new Size(48, 17);
+            tsslblVariants.Text = "Variants";
+            // 
+            // tsMain
+            // 
+            tsMain.Items.AddRange(new ToolStripItem[] { tbbutNew, tbbutOpen, tbbutSave, toolStripSeparator1, tbbutImport });
+            tsMain.Location = new Point(0, 0);
+            tsMain.Name = "tsMain";
+            tsMain.Size = new Size(1117, 25);
+            tsMain.TabIndex = 1;
+            tsMain.Text = "toolStrip1";
+            // 
+            // tbbutNew
+            // 
+            tbbutNew.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutNew.Image = (Image)resources.GetObject("tbbutNew.Image");
+            tbbutNew.ImageTransparentColor = Color.Magenta;
+            tbbutNew.Name = "tbbutNew";
+            tbbutNew.Size = new Size(39, 22);
+            tbbutNew.Text = "Nový";
+            tbbutNew.Click += tbbutNew_Click;
+            // 
+            // tbbutOpen
+            // 
+            tbbutOpen.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutOpen.Image = (Image)resources.GetObject("tbbutOpen.Image");
+            tbbutOpen.ImageTransparentColor = Color.Magenta;
+            tbbutOpen.Name = "tbbutOpen";
+            tbbutOpen.Size = new Size(47, 22);
+            tbbutOpen.Text = "Otevřít";
+            tbbutOpen.Click += tbbutOpen_Click;
+            // 
+            // tbbutSave
+            // 
+            tbbutSave.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutSave.Image = (Image)resources.GetObject("tbbutSave.Image");
+            tbbutSave.ImageTransparentColor = Color.Magenta;
+            tbbutSave.Name = "tbbutSave";
+            tbbutSave.Size = new Size(41, 22);
+            tbbutSave.Text = "Uložit";
+            tbbutSave.Click += tbbutSave_Click;
+            // 
+            // toolStripSeparator1
+            // 
+            toolStripSeparator1.Name = "toolStripSeparator1";
+            toolStripSeparator1.Size = new Size(6, 25);
+            // 
+            // tbbutImport
+            // 
+            tbbutImport.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutImport.Image = (Image)resources.GetObject("tbbutImport.Image");
+            tbbutImport.ImageTransparentColor = Color.Magenta;
+            tbbutImport.Name = "tbbutImport";
+            tbbutImport.Size = new Size(47, 22);
+            tbbutImport.Text = "Import";
+            tbbutImport.Click += tbbutImport_Click;
+            // 
+            // splitContainer1
+            // 
+            splitContainer1.Dock = DockStyle.Fill;
+            splitContainer1.Location = new Point(0, 25);
+            splitContainer1.Name = "splitContainer1";
+            // 
+            // splitContainer1.Panel1
+            // 
+            splitContainer1.Panel1.Controls.Add(lvProducts);
+            splitContainer1.Panel1.Controls.Add(tsProducts);
+            // 
+            // splitContainer1.Panel2
+            // 
+            splitContainer1.Panel2.Controls.Add(splitContainer2);
+            splitContainer1.Size = new Size(1117, 403);
+            splitContainer1.SplitterDistance = 580;
+            splitContainer1.TabIndex = 2;
+            // 
+            // lvProducts
+            // 
+            lvProducts.AllowColumnReorder = true;
+            lvProducts.Columns.AddRange(new ColumnHeader[] { colprdCat, colprdType, colprdPrior, colprdSku, colprdName, colprdVariant, colprdPrice, colprdUri, colprdDescription });
+            lvProducts.Dock = DockStyle.Fill;
+            lvProducts.FullRowSelect = true;
+            lvProducts.GridLines = true;
+            lvProducts.Location = new Point(0, 25);
+            lvProducts.Name = "lvProducts";
+            lvProducts.Size = new Size(580, 378);
+            lvProducts.TabIndex = 1;
+            lvProducts.UseCompatibleStateImageBehavior = false;
+            lvProducts.View = View.Details;
+            lvProducts.ItemSelectionChanged += lvProducts_ItemSelectionChanged;
+            // 
+            // colprdCat
+            // 
+            colprdCat.Tag = "Category";
+            colprdCat.Text = "Kategorie";
+            colprdCat.Width = 120;
+            // 
+            // colprdType
+            // 
+            colprdType.Tag = "TypeAsString";
+            colprdType.Text = "Typ";
+            // 
+            // colprdPrior
+            // 
+            colprdPrior.Tag = "ParentName";
+            colprdPrior.Text = "Nadřazený";
+            // 
+            // colprdName
+            // 
+            colprdName.Tag = "ProductName";
+            colprdName.Text = "Jméno";
+            colprdName.Width = 120;
+            // 
+            // colprdVariant
+            // 
+            colprdVariant.Tag = "VariantName";
+            colprdVariant.Text = "Varianta";
+            // 
+            // colprdPrice
+            // 
+            colprdPrice.Tag = "Price";
+            colprdPrice.Text = "Cena";
+            // 
+            // colprdUri
+            // 
+            colprdUri.Tag = "Uri";
+            colprdUri.Text = "Uri";
+            colprdUri.Width = 260;
+            // 
+            // colprdDescription
+            // 
+            colprdDescription.Tag = "ShortDescription";
+            colprdDescription.Text = "Popis";
+            // 
+            // tsProducts
+            // 
+            tsProducts.Items.AddRange(new ToolStripItem[] { toolStripLabel4, toolStripSeparator10, tbbutProdNew, tbbutProdVarNew, tbbutProdDelete, toolStripSeparator2, tbbutProdClone, toolStripSeparator3, toolStripLabel1, tbbutProduct, tbbutVariant });
+            tsProducts.Location = new Point(0, 0);
+            tsProducts.Name = "tsProducts";
+            tsProducts.Size = new Size(580, 25);
+            tsProducts.TabIndex = 0;
+            tsProducts.Text = "toolStrip3";
+            // 
+            // toolStripLabel4
+            // 
+            toolStripLabel4.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
+            toolStripLabel4.Name = "toolStripLabel4";
+            toolStripLabel4.Size = new Size(58, 22);
+            toolStripLabel4.Text = "Produkty";
+            // 
+            // toolStripSeparator10
+            // 
+            toolStripSeparator10.Name = "toolStripSeparator10";
+            toolStripSeparator10.Size = new Size(6, 25);
+            // 
+            // tbbutProdNew
+            // 
+            tbbutProdNew.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutProdNew.Image = (Image)resources.GetObject("tbbutProdNew.Image");
+            tbbutProdNew.ImageTransparentColor = Color.Magenta;
+            tbbutProdNew.Name = "tbbutProdNew";
+            tbbutProdNew.Size = new Size(86, 22);
+            tbbutProdNew.Text = "Přidej Produkt";
+            tbbutProdNew.Click += tbbutProdNew_Click;
+            // 
+            // tbbutProdVarNew
+            // 
+            tbbutProdVarNew.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutProdVarNew.Image = (Image)resources.GetObject("tbbutProdVarNew.Image");
+            tbbutProdVarNew.ImageTransparentColor = Color.Magenta;
+            tbbutProdVarNew.Name = "tbbutProdVarNew";
+            tbbutProdVarNew.Size = new Size(87, 22);
+            tbbutProdVarNew.Text = "Přidej Variantu";
+            tbbutProdVarNew.Click += tbbutProdVarNew_Click;
+            // 
+            // tbbutProdDelete
+            // 
+            tbbutProdDelete.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutProdDelete.Image = (Image)resources.GetObject("tbbutProdDelete.Image");
+            tbbutProdDelete.ImageTransparentColor = Color.Magenta;
+            tbbutProdDelete.Name = "tbbutProdDelete";
+            tbbutProdDelete.Size = new Size(39, 22);
+            tbbutProdDelete.Text = "Smaž";
+            tbbutProdDelete.Click += tbbutProdDelete_Click;
+            // 
+            // toolStripSeparator2
+            // 
+            toolStripSeparator2.Name = "toolStripSeparator2";
+            toolStripSeparator2.Size = new Size(6, 25);
+            // 
+            // tbbutProdClone
+            // 
+            tbbutProdClone.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutProdClone.Image = (Image)resources.GetObject("tbbutProdClone.Image");
+            tbbutProdClone.ImageTransparentColor = Color.Magenta;
+            tbbutProdClone.Name = "tbbutProdClone";
+            tbbutProdClone.Size = new Size(45, 22);
+            tbbutProdClone.Text = "Klonuj";
+            tbbutProdClone.Click += tbbutProdClone_Click;
+            // 
+            // toolStripSeparator3
+            // 
+            toolStripSeparator3.Name = "toolStripSeparator3";
+            toolStripSeparator3.Size = new Size(6, 25);
+            // 
+            // toolStripLabel1
+            // 
+            toolStripLabel1.Name = "toolStripLabel1";
+            toolStripLabel1.Size = new Size(30, 22);
+            toolStripLabel1.Text = "Filtr:";
+            // 
+            // tbbutProduct
+            // 
+            tbbutProduct.Checked = true;
+            tbbutProduct.CheckState = CheckState.Checked;
+            tbbutProduct.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutProduct.Image = (Image)resources.GetObject("tbbutProduct.Image");
+            tbbutProduct.ImageTransparentColor = Color.Magenta;
+            tbbutProduct.Name = "tbbutProduct";
+            tbbutProduct.Size = new Size(59, 22);
+            tbbutProduct.Text = "Produkty";
+            tbbutProduct.Click += tbbutProduct_Click;
+            // 
+            // tbbutVariant
+            // 
+            tbbutVariant.Checked = true;
+            tbbutVariant.CheckState = CheckState.Checked;
+            tbbutVariant.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutVariant.Image = (Image)resources.GetObject("tbbutVariant.Image");
+            tbbutVariant.ImageTransparentColor = Color.Magenta;
+            tbbutVariant.Name = "tbbutVariant";
+            tbbutVariant.Size = new Size(53, 22);
+            tbbutVariant.Text = "Varianty";
+            tbbutVariant.Click += tbbutVariant_Click;
+            // 
+            // splitContainer2
+            // 
+            splitContainer2.Dock = DockStyle.Fill;
+            splitContainer2.Location = new Point(0, 0);
+            splitContainer2.Name = "splitContainer2";
+            splitContainer2.Orientation = Orientation.Horizontal;
+            // 
+            // splitContainer2.Panel1
+            // 
+            splitContainer2.Panel1.Controls.Add(splitContainer3);
+            // 
+            // splitContainer2.Panel2
+            // 
+            splitContainer2.Panel2.Controls.Add(detProduct);
+            splitContainer2.Panel2.Controls.Add(tsProductDetail);
+            splitContainer2.Size = new Size(533, 403);
+            splitContainer2.SplitterDistance = 124;
+            splitContainer2.TabIndex = 0;
+            // 
+            // splitContainer3
+            // 
+            splitContainer3.Dock = DockStyle.Fill;
+            splitContainer3.Location = new Point(0, 0);
+            splitContainer3.Name = "splitContainer3";
+            // 
+            // splitContainer3.Panel1
+            // 
+            splitContainer3.Panel1.Controls.Add(lvVariants);
+            splitContainer3.Panel1.Controls.Add(tsVariants);
+            // 
+            // splitContainer3.Panel2
+            // 
+            splitContainer3.Panel2.Controls.Add(lvCategories);
+            splitContainer3.Panel2.Controls.Add(tsCategories);
+            splitContainer3.Size = new Size(533, 124);
+            splitContainer3.SplitterDistance = 267;
+            splitContainer3.TabIndex = 0;
+            // 
+            // lvVariants
+            // 
+            lvVariants.AllowColumnReorder = true;
+            lvVariants.Columns.AddRange(new ColumnHeader[] { colvarName, colvarPrice });
+            lvVariants.Dock = DockStyle.Fill;
+            lvVariants.FullRowSelect = true;
+            lvVariants.GridLines = true;
+            lvVariants.Location = new Point(0, 25);
+            lvVariants.Name = "lvVariants";
+            lvVariants.Size = new Size(267, 99);
+            lvVariants.TabIndex = 2;
+            lvVariants.UseCompatibleStateImageBehavior = false;
+            lvVariants.View = View.Details;
+            lvVariants.ItemSelectionChanged += lvVariants_ItemSelectionChanged;
+            // 
+            // colvarName
+            // 
+            colvarName.Tag = "Name";
+            colvarName.Text = "Jméno";
+            colvarName.Width = 120;
+            // 
+            // colvarPrice
+            // 
+            colvarPrice.Tag = "Price";
+            colvarPrice.Text = "Cena";
+            // 
+            // tsVariants
+            // 
+            tsVariants.Items.AddRange(new ToolStripItem[] { toolStripLabel2, toolStripSeparator6, tbbutVarNew, tbbutVarDelete, toolStripSeparator7, tbbutVarApply });
+            tsVariants.Location = new Point(0, 0);
+            tsVariants.Name = "tsVariants";
+            tsVariants.Size = new Size(267, 25);
+            tsVariants.TabIndex = 0;
+            tsVariants.Text = "toolStrip1";
+            // 
+            // toolStripLabel2
+            // 
+            toolStripLabel2.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
+            toolStripLabel2.Name = "toolStripLabel2";
+            toolStripLabel2.Size = new Size(52, 22);
+            toolStripLabel2.Text = "Varianty";
+            // 
+            // toolStripSeparator6
+            // 
+            toolStripSeparator6.Name = "toolStripSeparator6";
+            toolStripSeparator6.Size = new Size(6, 25);
+            // 
+            // tbbutVarNew
+            // 
+            tbbutVarNew.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutVarNew.Image = (Image)resources.GetObject("tbbutVarNew.Image");
+            tbbutVarNew.ImageTransparentColor = Color.Magenta;
+            tbbutVarNew.Name = "tbbutVarNew";
+            tbbutVarNew.Size = new Size(41, 22);
+            tbbutVarNew.Text = "Přidej";
+            tbbutVarNew.Click += tbbutVarNew_Click;
+            // 
+            // tbbutVarDelete
+            // 
+            tbbutVarDelete.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutVarDelete.Image = (Image)resources.GetObject("tbbutVarDelete.Image");
+            tbbutVarDelete.ImageTransparentColor = Color.Magenta;
+            tbbutVarDelete.Name = "tbbutVarDelete";
+            tbbutVarDelete.Size = new Size(39, 22);
+            tbbutVarDelete.Text = "Smaž";
+            tbbutVarDelete.Click += tbbutVarDelete_Click;
+            // 
+            // toolStripSeparator7
+            // 
+            toolStripSeparator7.Name = "toolStripSeparator7";
+            toolStripSeparator7.Size = new Size(6, 25);
+            // 
+            // tbbutVarApply
+            // 
+            tbbutVarApply.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutVarApply.Image = (Image)resources.GetObject("tbbutVarApply.Image");
+            tbbutVarApply.ImageTransparentColor = Color.Magenta;
+            tbbutVarApply.Name = "tbbutVarApply";
+            tbbutVarApply.Size = new Size(48, 22);
+            tbbutVarApply.Text = "Aplikuj";
+            tbbutVarApply.Click += tbbutVarApply_Click;
+            // 
+            // lvCategories
+            // 
+            lvCategories.AllowColumnReorder = true;
+            lvCategories.Columns.AddRange(new ColumnHeader[] { colcatName });
+            lvCategories.Dock = DockStyle.Fill;
+            lvCategories.FullRowSelect = true;
+            lvCategories.GridLines = true;
+            lvCategories.Location = new Point(0, 25);
+            lvCategories.Name = "lvCategories";
+            lvCategories.Size = new Size(262, 99);
+            lvCategories.TabIndex = 3;
+            lvCategories.UseCompatibleStateImageBehavior = false;
+            lvCategories.View = View.Details;
+            lvCategories.ItemSelectionChanged += lvCategories_ItemSelectionChanged;
+            // 
+            // colcatName
+            // 
+            colcatName.Tag = "Name";
+            colcatName.Text = "Jméno";
+            colcatName.Width = 120;
+            // 
+            // tsCategories
+            // 
+            tsCategories.Items.AddRange(new ToolStripItem[] { toolStripLabel3, toolStripSeparator8, tbbutCatNew, tbbutCatDelete, toolStripSeparator9 });
+            tsCategories.Location = new Point(0, 0);
+            tsCategories.Name = "tsCategories";
+            tsCategories.Size = new Size(262, 25);
+            tsCategories.TabIndex = 0;
+            tsCategories.Text = "toolStrip1";
+            // 
+            // toolStripLabel3
+            // 
+            toolStripLabel3.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
+            toolStripLabel3.Name = "toolStripLabel3";
+            toolStripLabel3.Size = new Size(62, 22);
+            toolStripLabel3.Text = "Kategorie";
+            // 
+            // toolStripSeparator8
+            // 
+            toolStripSeparator8.Name = "toolStripSeparator8";
+            toolStripSeparator8.Size = new Size(6, 25);
+            // 
+            // tbbutCatNew
+            // 
+            tbbutCatNew.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutCatNew.Image = (Image)resources.GetObject("tbbutCatNew.Image");
+            tbbutCatNew.ImageTransparentColor = Color.Magenta;
+            tbbutCatNew.Name = "tbbutCatNew";
+            tbbutCatNew.Size = new Size(41, 22);
+            tbbutCatNew.Text = "Přidej";
+            tbbutCatNew.Click += tbbutCatNew_Click;
+            // 
+            // tbbutCatDelete
+            // 
+            tbbutCatDelete.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutCatDelete.Image = (Image)resources.GetObject("tbbutCatDelete.Image");
+            tbbutCatDelete.ImageTransparentColor = Color.Magenta;
+            tbbutCatDelete.Name = "tbbutCatDelete";
+            tbbutCatDelete.Size = new Size(39, 22);
+            tbbutCatDelete.Text = "Smaž";
+            tbbutCatDelete.Click += tbbutCatDelete_Click;
+            // 
+            // toolStripSeparator9
+            // 
+            toolStripSeparator9.Name = "toolStripSeparator9";
+            toolStripSeparator9.Size = new Size(6, 25);
+            // 
+            // detProduct
+            // 
+            detProduct.AutoScroll = true;
+            detProduct.Controls.Add(ltbdetId);
+            detProduct.Controls.Add(tbdetId);
+            detProduct.Controls.Add(lcbdetParent);
+            detProduct.Controls.Add(cbdetParent);
+            detProduct.Controls.Add(ltbdetName);
+            detProduct.Controls.Add(tbdetName);
+            detProduct.Controls.Add(ltbdetSku);
+            detProduct.Controls.Add(tbdetSku);
+            detProduct.Controls.Add(ltbdetShortDescription);
+            detProduct.Controls.Add(tbdetShortDescription);
+            detProduct.Controls.Add(ltbdetDescription);
+            detProduct.Controls.Add(tbdetDescription);
+            detProduct.Controls.Add(ltbdetPrice);
+            detProduct.Controls.Add(tbdetPrice);
+            detProduct.Controls.Add(lcbdetCategories);
+            detProduct.Controls.Add(cbdetCategories);
+            detProduct.Controls.Add(ltbdetUri);
+            detProduct.Controls.Add(tbdetUri);
+            detProduct.Dock = DockStyle.Fill;
+            detProduct.FlowDirection = FlowDirection.TopDown;
+            detProduct.Location = new Point(0, 25);
+            detProduct.Name = "detProduct";
+            detProduct.Size = new Size(533, 250);
+            detProduct.TabIndex = 3;
+            // 
+            // ltbdetId
+            // 
+            ltbdetId.AutoSize = true;
+            ltbdetId.Location = new Point(3, 0);
+            ltbdetId.Name = "ltbdetId";
+            ltbdetId.Size = new Size(17, 15);
+            ltbdetId.TabIndex = 0;
+            ltbdetId.Text = "Id";
+            // 
+            // tbdetId
+            // 
+            tbdetId.Location = new Point(3, 18);
+            tbdetId.Name = "tbdetId";
+            tbdetId.ReadOnly = true;
+            tbdetId.Size = new Size(203, 23);
+            tbdetId.TabIndex = 1;
+            // 
+            // lcbdetParent
+            // 
+            lcbdetParent.AutoSize = true;
+            lcbdetParent.Location = new Point(3, 44);
+            lcbdetParent.Name = "lcbdetParent";
+            lcbdetParent.Size = new Size(101, 15);
+            lcbdetParent.TabIndex = 12;
+            lcbdetParent.Text = "Nadřazený / rodič";
+            // 
+            // cbdetParent
+            // 
+            cbdetParent.DropDownStyle = ComboBoxStyle.DropDownList;
+            cbdetParent.FormattingEnabled = true;
+            cbdetParent.Location = new Point(3, 62);
+            cbdetParent.Name = "cbdetParent";
+            cbdetParent.Size = new Size(203, 23);
+            cbdetParent.TabIndex = 13;
+            // 
+            // ltbdetName
+            // 
+            ltbdetName.AutoSize = true;
+            ltbdetName.Location = new Point(3, 88);
+            ltbdetName.Name = "ltbdetName";
+            ltbdetName.Size = new Size(42, 15);
+            ltbdetName.TabIndex = 2;
+            ltbdetName.Text = "Jméno";
+            // 
+            // tbdetName
+            // 
+            tbdetName.Location = new Point(3, 106);
+            tbdetName.Name = "tbdetName";
+            tbdetName.Size = new Size(203, 23);
+            tbdetName.TabIndex = 3;
+            // 
+            // ltbdetSku
+            // 
+            ltbdetSku.AutoSize = true;
+            ltbdetSku.Location = new Point(3, 132);
+            ltbdetSku.Name = "ltbdetSku";
+            ltbdetSku.Size = new Size(125, 15);
+            ltbdetSku.TabIndex = 4;
+            ltbdetSku.Text = "Katalogové číslo (SKU)";
+            // 
+            // tbdetSku
+            // 
+            tbdetSku.Location = new Point(3, 150);
+            tbdetSku.Name = "tbdetSku";
+            tbdetSku.Size = new Size(203, 23);
+            tbdetSku.TabIndex = 5;
+            // 
+            // ltbdetShortDescription
+            // 
+            ltbdetShortDescription.AutoSize = true;
+            ltbdetShortDescription.Location = new Point(3, 176);
+            ltbdetShortDescription.Name = "ltbdetShortDescription";
+            ltbdetShortDescription.Size = new Size(72, 15);
+            ltbdetShortDescription.TabIndex = 6;
+            ltbdetShortDescription.Text = "Krátký popis";
+            // 
+            // tbdetShortDescription
+            // 
+            tbdetShortDescription.Location = new Point(3, 194);
+            tbdetShortDescription.Name = "tbdetShortDescription";
+            tbdetShortDescription.Size = new Size(203, 23);
+            tbdetShortDescription.TabIndex = 7;
+            // 
+            // ltbdetDescription
+            // 
+            ltbdetDescription.AutoSize = true;
+            ltbdetDescription.Location = new Point(3, 220);
+            ltbdetDescription.Name = "ltbdetDescription";
+            ltbdetDescription.Size = new Size(36, 15);
+            ltbdetDescription.TabIndex = 8;
+            ltbdetDescription.Text = "Popis";
+            // 
+            // tbdetDescription
+            // 
+            tbdetDescription.Location = new Point(212, 3);
+            tbdetDescription.Multiline = true;
+            tbdetDescription.Name = "tbdetDescription";
+            tbdetDescription.Size = new Size(203, 91);
+            tbdetDescription.TabIndex = 9;
+            // 
+            // ltbdetPrice
+            // 
+            ltbdetPrice.AutoSize = true;
+            ltbdetPrice.Location = new Point(212, 97);
+            ltbdetPrice.Name = "ltbdetPrice";
+            ltbdetPrice.Size = new Size(34, 15);
+            ltbdetPrice.TabIndex = 14;
+            ltbdetPrice.Text = "Cena";
+            // 
+            // tbdetPrice
+            // 
+            tbdetPrice.DecimalPlaces = 2;
+            detProduct.SetFlowBreak(tbdetPrice, true);
+            tbdetPrice.Location = new Point(212, 115);
+            tbdetPrice.Maximum = new decimal(new int[] { 100000, 0, 0, 0 });
+            tbdetPrice.Name = "tbdetPrice";
+            tbdetPrice.Size = new Size(203, 23);
+            tbdetPrice.TabIndex = 16;
+            tbdetPrice.ThousandsSeparator = true;
+            // 
+            // lcbdetCategories
+            // 
+            lcbdetCategories.AutoSize = true;
+            lcbdetCategories.Location = new Point(421, 0);
+            lcbdetCategories.Name = "lcbdetCategories";
+            lcbdetCategories.Size = new Size(57, 15);
+            lcbdetCategories.TabIndex = 17;
+            lcbdetCategories.Text = "Kategorie";
+            // 
+            // cbdetCategories
+            // 
+            cbdetCategories.DropDownStyle = ComboBoxStyle.DropDownList;
+            cbdetCategories.FormattingEnabled = true;
+            cbdetCategories.Location = new Point(421, 18);
+            cbdetCategories.Name = "cbdetCategories";
+            cbdetCategories.Size = new Size(203, 23);
+            cbdetCategories.TabIndex = 18;
+            // 
+            // ltbdetUri
+            // 
+            ltbdetUri.AutoSize = true;
+            ltbdetUri.Location = new Point(421, 44);
+            ltbdetUri.Name = "ltbdetUri";
+            ltbdetUri.Size = new Size(68, 15);
+            ltbdetUri.TabIndex = 19;
+            ltbdetUri.Text = "Obrázek Uri";
+            // 
+            // tbdetUri
+            // 
+            tbdetUri.Location = new Point(421, 62);
+            tbdetUri.Name = "tbdetUri";
+            tbdetUri.Size = new Size(203, 23);
+            tbdetUri.TabIndex = 20;
+            // 
+            // tsProductDetail
+            // 
+            tsProductDetail.Items.AddRange(new ToolStripItem[] { tblbCaption, toolStripSeparator5, tbbutDetSave, toolStripSeparator4, tbbutDetCopy, tbbutDetPaste });
+            tsProductDetail.Location = new Point(0, 0);
+            tsProductDetail.Name = "tsProductDetail";
+            tsProductDetail.Size = new Size(533, 25);
+            tsProductDetail.TabIndex = 0;
+            tsProductDetail.Text = "toolStrip2";
+            // 
+            // tblbCaption
+            // 
+            tblbCaption.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
+            tblbCaption.Name = "tblbCaption";
+            tblbCaption.Size = new Size(85, 22);
+            tblbCaption.Text = "Detail Caption";
+            // 
+            // toolStripSeparator5
+            // 
+            toolStripSeparator5.Name = "toolStripSeparator5";
+            toolStripSeparator5.Size = new Size(6, 25);
+            // 
+            // tbbutDetSave
+            // 
+            tbbutDetSave.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutDetSave.Image = (Image)resources.GetObject("tbbutDetSave.Image");
+            tbbutDetSave.ImageTransparentColor = Color.Magenta;
+            tbbutDetSave.Name = "tbbutDetSave";
+            tbbutDetSave.Size = new Size(34, 22);
+            tbbutDetSave.Text = "Ulož";
+            tbbutDetSave.Click += tbbutDetSave_Click;
+            // 
+            // toolStripSeparator4
+            // 
+            toolStripSeparator4.Name = "toolStripSeparator4";
+            toolStripSeparator4.Size = new Size(6, 25);
+            // 
+            // tbbutDetCopy
+            // 
+            tbbutDetCopy.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutDetCopy.Image = (Image)resources.GetObject("tbbutDetCopy.Image");
+            tbbutDetCopy.ImageTransparentColor = Color.Magenta;
+            tbbutDetCopy.Name = "tbbutDetCopy";
+            tbbutDetCopy.Size = new Size(49, 22);
+            tbbutDetCopy.Text = "Kopíruj";
+            tbbutDetCopy.Click += tbbutDetCopy_Click;
+            // 
+            // tbbutDetPaste
+            // 
+            tbbutDetPaste.DisplayStyle = ToolStripItemDisplayStyle.Text;
+            tbbutDetPaste.Image = (Image)resources.GetObject("tbbutDetPaste.Image");
+            tbbutDetPaste.ImageTransparentColor = Color.Magenta;
+            tbbutDetPaste.Name = "tbbutDetPaste";
+            tbbutDetPaste.Size = new Size(33, 22);
+            tbbutDetPaste.Text = "Vlož";
+            tbbutDetPaste.Click += tbbutDetPaste_Click;
+            // 
+            // dlgOpenFile
+            // 
+            dlgOpenFile.CheckFileExists = false;
+            dlgOpenFile.DefaultExt = "*.csv";
+            dlgOpenFile.Filter = "CSV files|*.csv";
+            // 
+            // colprdSku
+            // 
+            colprdSku.Tag = "Sku";
+            colprdSku.Text = "SKU";
+            // 
+            // FormProductEdit
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(1117, 450);
+            Controls.Add(splitContainer1);
+            Controls.Add(tsMain);
+            Controls.Add(ssMain);
+            Icon = (Icon)resources.GetObject("$this.Icon");
+            Name = "FormProductEdit";
+            Text = "FormProductEdit";
+            ssMain.ResumeLayout(false);
+            ssMain.PerformLayout();
+            tsMain.ResumeLayout(false);
+            tsMain.PerformLayout();
+            splitContainer1.Panel1.ResumeLayout(false);
+            splitContainer1.Panel1.PerformLayout();
+            splitContainer1.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit();
+            splitContainer1.ResumeLayout(false);
+            tsProducts.ResumeLayout(false);
+            tsProducts.PerformLayout();
+            splitContainer2.Panel1.ResumeLayout(false);
+            splitContainer2.Panel2.ResumeLayout(false);
+            splitContainer2.Panel2.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)splitContainer2).EndInit();
+            splitContainer2.ResumeLayout(false);
+            splitContainer3.Panel1.ResumeLayout(false);
+            splitContainer3.Panel1.PerformLayout();
+            splitContainer3.Panel2.ResumeLayout(false);
+            splitContainer3.Panel2.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)splitContainer3).EndInit();
+            splitContainer3.ResumeLayout(false);
+            tsVariants.ResumeLayout(false);
+            tsVariants.PerformLayout();
+            tsCategories.ResumeLayout(false);
+            tsCategories.PerformLayout();
+            detProduct.ResumeLayout(false);
+            detProduct.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)tbdetPrice).EndInit();
+            tsProductDetail.ResumeLayout(false);
+            tsProductDetail.PerformLayout();
+            ResumeLayout(false);
+            PerformLayout();
+        }
+
+        #endregion
+
+        private StatusStrip ssMain;
+        private ToolStrip tsMain;
+        private SplitContainer splitContainer1;
+        private ListView lvProducts;
+        private ToolStrip tsProducts;
+        private SplitContainer splitContainer2;
+        private ListView lvVariants;
+        private ToolStrip tsVariants;
+        private ToolStrip tsProductDetail;
+        private ToolStripButton tbbutNew;
+        private ToolStripButton tbbutOpen;
+        private ToolStripButton tbbutSave;
+        private ToolStripSeparator toolStripSeparator1;
+        private ToolStripButton tbbutImport;
+        private ToolStripButton tbbutProdNew;
+        private ToolStripButton tbbutProdDelete;
+        private ToolStripSeparator toolStripSeparator2;
+        private ToolStripButton tbbutProdClone;
+        private ToolStripSeparator toolStripSeparator3;
+        private ToolStripLabel toolStripLabel1;
+        private ToolStripButton tbbutDetSave;
+        private ToolStripSeparator toolStripSeparator4;
+        private ToolStripButton tbbutDetCopy;
+        private ToolStripButton tbbutDetPaste;
+        private ToolStripButton tbbutVarNew;
+        private ToolStripButton tbbutVarDelete;
+        private ToolStripSeparator toolStripSeparator5;
+        private ToolStripLabel tblbCaption;
+        private ToolStripStatusLabel tsslblOutput;
+        private ToolStripStatusLabel tsslblProducts;
+        private ColumnHeader colprdName;
+        private ColumnHeader colprdVariant;
+        private ColumnHeader colprdPrice;
+        private ColumnHeader colprdUri;
+        private ColumnHeader colprdDescription;
+        private ColumnHeader colvarName;
+        private ColumnHeader colvarPrice;
+        private ToolStripLabel toolStripLabel2;
+        private ToolStripSeparator toolStripSeparator6;
+        private ToolStripSeparator toolStripSeparator7;
+        private ToolStripButton tbbutVarApply;
+        private ToolStripLabel toolStripLabel4;
+        private ToolStripSeparator toolStripSeparator10;
+        private SplitContainer splitContainer3;
+        private ListView lvCategories;
+        private ColumnHeader colcatName;
+        private ToolStrip tsCategories;
+        private ToolStripLabel toolStripLabel3;
+        private ToolStripSeparator toolStripSeparator8;
+        private ToolStripButton tbbutCatNew;
+        private ToolStripButton tbbutCatDelete;
+        private ToolStripSeparator toolStripSeparator9;
+        private ColumnHeader colprdCat;
+        private ToolStripStatusLabel tsslblVariants;
+        private ColumnHeader colprdType;
+        private OpenFileDialog dlgOpenFile;
+        private ToolStripButton tbbutProduct;
+        private ToolStripButton tbbutVariant;
+        private ColumnHeader colprdPrior;
+        private FlowLayoutPanel detProduct;
+        private Label ltbdetId;
+        private TextBox tbdetId;
+        private Label lcbdetParent;
+        private ComboBox cbdetParent;
+        private Label ltbdetName;
+        private TextBox tbdetName;
+        private Label ltbdetSku;
+        private TextBox tbdetSku;
+        private Label ltbdetShortDescription;
+        private TextBox tbdetShortDescription;
+        private Label ltbdetDescription;
+        private TextBox tbdetDescription;
+        private Label ltbdetPrice;
+        private NumericUpDown tbdetPrice;
+        private Label lcbdetCategories;
+        private ComboBox cbdetCategories;
+        private Label ltbdetUri;
+        private TextBox tbdetUri;
+        private ToolStripButton tbbutProdVarNew;
+        private FolderBrowserDialog dlgOpenDir;
+        private ColumnHeader colprdSku;
+    }
+}

+ 808 - 0
FormProductEdit.cs

@@ -0,0 +1,808 @@
+using qdr.app.studiou.orders2printpack.Extensions;
+using qdr.app.studiou.orders2printpack.ProductStorage;
+using qdr.app.studiou.orders2printpack.Properties;
+using Quadarax.Foundation.Core.IO;
+using Quadarax.Foundation.Core.Value;
+using Quadarax.Foundation.Core.Value.Generators;
+using System.ComponentModel;
+using System.Data;
+
+namespace qdr.app.studiou.orders2printpack
+{
+    public partial class FormProductEdit : Form
+    {
+
+        #region *** Enumerations ***
+        public enum DetailType
+        {
+            Product,
+            Variant,
+            Category
+        }
+        #endregion
+
+        #region *** Private Fields ***
+        private ProductStorage.ProductStorage _storage = new ProductStorage.ProductStorage();
+        private DetailType _detailType = DetailType.Product;
+        private ProductDto? _currentDetailProduct;
+        private VariantDto? _currentDetailVariant;
+        private CategoryDto? _currentDetailCategory;
+        private TinyHash _hashGenerator = new TinyHash(10);
+        private ProductDto _productClipboard = new ProductDto();
+        private VariantDto _variantClipboard = new VariantDto();
+        private CategoryDto _categoryClipboard = new CategoryDto();
+        #endregion
+
+
+        #region *** Constructor ***
+        public FormProductEdit()
+        {
+            InitializeComponent();
+            _storage.AppendVariantsFromString(AppSettings.Default.PredefinedVariants);
+            _storage.AppendCategoriesFromString(AppSettings.Default.PredefinedCategories);
+            tsslblOutput.Text = "Soubor nezadán";
+            RefreshTools();
+        }
+        #endregion
+        #region *** Form Handlers ***
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            AppSettings.Default.PredefinedVariants = _storage.GetVariantsAsString();
+            AppSettings.Default.PredefinedCategories = _storage.GetCategoriesAsString();
+            AppSettings.Default.Save();
+            base.OnClosing(e);
+        }
+        #endregion
+
+        #region *** Toolbar Handlers ***
+        private void tbbutSave_Click(object sender, EventArgs e)
+        {
+            SaveProductFile(tsslblOutput.Text);
+            MessageBox.Show($"Data ({_storage.Products.Count()} položek) byla uložena do souboru '{tsslblOutput.Text}'", "Uloženo", MessageBoxButtons.OK, MessageBoxIcon.Information);
+        }
+        private void tbbutNew_Click(object sender, EventArgs e)
+        {
+            _storage = new ProductStorage.ProductStorage();
+            _storage.AppendVariantsFromString(AppSettings.Default.PredefinedVariants);
+            _storage.AppendCategoriesFromString(AppSettings.Default.PredefinedCategories);
+            _currentDetailProduct = null;
+            ClearDetail();
+            RefreshViews();
+            RefreshTools();
+        }
+        private void tbbutOpen_Click(object sender, EventArgs e)
+        {
+            if (dlgOpenFile.ShowDialog() == DialogResult.OK)
+                OpenProductFile(dlgOpenFile.FileName);
+        }
+        private void tbbutProduct_Click(object sender, EventArgs e)
+        {
+            tbbutProduct.Checked = !tbbutProduct.Checked;
+            RefreshProductList();
+
+        }
+
+        private void tbbutVariant_Click(object sender, EventArgs e)
+        {
+            tbbutVariant.Checked = !tbbutVariant.Checked;
+            RefreshProductList();
+        }
+
+        private void tbbutProdNew_Click(object sender, EventArgs e)
+        {
+            AddNewProduct();
+            RefreshTools();
+        }
+
+
+
+        private void tbbutProdVarNew_Click(object sender, EventArgs e)
+        {
+            AddNewProductVariant();
+            RefreshTools();
+        }
+
+        private void tbbutDetSave_Click(object sender, EventArgs e)
+        {
+            switch (_detailType)
+            {
+                case DetailType.Product:
+                    SaveDetail(_currentDetailProduct);
+                    RefreshProductList();
+                    break;
+                case DetailType.Variant:
+                    SaveDetail(_currentDetailVariant);
+                    RefreshVariantList();
+                    RefreshProductList();
+                    break;
+                case DetailType.Category:
+                    SaveDetail(_currentDetailCategory);
+                    RefreshCategoryList();
+                    break;
+            }
+        }
+
+        private void tbbutProdDelete_Click(object sender, EventArgs e)
+        {
+            var selected = GetSelectedProduct();
+            if (selected == null)
+                return;
+
+            if (!ConfirmMessage($"Opravdu chcete smazat {selected.Length} vybraných produktů?"))
+                return;
+
+            _storage.RemoveProduct(selected.Select(x => x.GetIdentifier()).ToArray());
+            RefreshProductList();
+            RefreshTools();
+        }
+
+        private void tbbutProdClone_Click(object sender, EventArgs e)
+        {
+            var selected = GetSelectedProduct()?.FirstOrDefault();
+            if (selected == null)
+                return;
+            CloneProduct(selected);
+            RefreshTools();
+        }
+
+        private void tbbutDetCopy_Click(object sender, EventArgs e)
+        {
+            switch (_detailType)
+            {
+                case DetailType.Product:
+                    if (_currentDetailProduct == null)
+                        return;
+                    _currentDetailProduct.CopyTo(_productClipboard);
+                    break;
+                case DetailType.Variant:
+                    if (_currentDetailVariant == null)
+                        return;
+                    _currentDetailVariant.CopyTo(_variantClipboard);
+                    break;
+                case DetailType.Category:
+                    if (_currentDetailCategory == null)
+                        return;
+                    _currentDetailCategory.CopyTo(_categoryClipboard);
+                    break;
+            }
+
+
+        }
+
+        private void tbbutDetPaste_Click(object sender, EventArgs e)
+        {
+
+            switch (_detailType)
+            {
+                case DetailType.Product:
+                    if (_currentDetailProduct == null)
+                        return;
+                    _productClipboard.Id = _currentDetailProduct.Id;
+                    ShowDetail(_productClipboard);
+                    break;
+                case DetailType.Variant:
+                    if (_currentDetailVariant == null)
+                        return;
+                    ShowDetail(_variantClipboard);
+                    break;
+                case DetailType.Category:
+                    if (_currentDetailCategory == null)
+                        return;
+                    ShowDetail(_categoryClipboard);
+                    break;
+            }
+        }
+
+        private void tbbutVarNew_Click(object sender, EventArgs e)
+        {
+            AddNewVariant();
+        }
+
+        private void tbbutVarDelete_Click(object sender, EventArgs e)
+        {
+            var selected = GetSelectedVariants();
+            if (selected == null)
+                return;
+
+            if (!ConfirmMessage($"Opravdu chcete smazat {selected.Length} vybraných variant?"))
+                return;
+
+            _storage.RemoveVariants(selected.Select(x => x.GetIdentifier()).ToArray());
+            RefreshVariantList();
+        }
+
+        private void tbbutVarApply_Click(object sender, EventArgs e)
+        {
+            ApplyProductVariant();
+            RefreshProductList();
+        }
+
+
+        private void tbbutCatNew_Click(object sender, EventArgs e)
+        {
+            AddNewCategory();
+        }
+
+        private void tbbutCatDelete_Click(object sender, EventArgs e)
+        {
+            var selected = GetSelectedCategories();
+            if (selected == null)
+                return;
+
+            if (!ConfirmMessage($"Opravdu chcete smazat {selected.Length} vybraných kategorií?"))
+                return;
+
+            _storage.RemoveCategories(selected.Select(x => x.GetIdentifier()).ToArray());
+            RefreshCategoryList();
+        }
+
+        private void tbbutCatApply_Click(object sender, EventArgs e)
+        {
+
+        }
+
+        private void tbbutImport_Click(object sender, EventArgs e)
+        {
+            var dlg = new dlgImport();
+            if (dlg.ShowDialog() == DialogResult.OK)
+            {
+                ImportProducts(dlg.ImportDir, dlg.UrlPrefix, dlg.ShortDescription, dlg.Description, dlg.UploadDate);
+            }
+            RefreshViews();
+            RefreshTools();
+        }
+        #endregion
+
+
+        #region *** Refreshes ***
+        private void RefreshTools()
+        {
+            tsslblProducts.Text = $"Produkty: {_storage.Products.Count(x => !x.IsVariant)}";
+            tsslblVariants.Text = $"Varianty: {_storage.Products.Count(x => x.IsVariant)}";
+        }
+        private void RefreshViews()
+        {
+            RefreshProductList();
+            RefreshCategoryList();
+            RefreshVariantList();
+
+        }
+
+        private void RefreshProductList()
+        {
+            BlockErrorHandled(() =>
+            {
+                lvProducts.SaveSelection();
+                lvProducts.BindData(_storage.GetProductsView(tbbutProduct.Checked, tbbutVariant.Checked), itemCustomProcessCallback: (lvItem, data) =>
+                {
+                    if (!data.IsValid)
+                        lvItem.BackColor = Color.IndianRed;
+                    
+                });
+                lvProducts.RestoreSelection();
+            });
+
+        }
+        private void RefreshCategoryList()
+        {
+            BlockErrorHandled(() =>
+            {
+                lvCategories.SaveSelection();
+                lvCategories.BindData(_storage.Categories);
+                lvCategories.RestoreSelection();
+            });
+        }
+        private void RefreshVariantList()
+        {
+            BlockErrorHandled(() =>
+            {
+                lvVariants.SaveSelection();
+                lvVariants.BindData(_storage.Variants);
+                lvVariants.RestoreSelection();
+            });
+        }
+        #endregion
+
+        #region *** ListView Handlers ***
+        private void lvProducts_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
+        {
+            if (e.Item != null)
+            {
+                var product = _storage.GetProductByIdentifier(e.Item.Tag?.ToString());
+                ShowDetail(product);
+                _currentDetailProduct = product;
+            }
+        }
+        private void lvVariants_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
+        {
+            if (e.Item != null)
+            {
+                var variant = _storage.GetVariantByIdentifier(e.Item.Tag?.ToString());
+                ShowDetail(variant);
+                _currentDetailVariant = variant;
+            }
+        }
+        private void lvCategories_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
+        {
+            if (e.Item != null)
+            {
+                var category = _storage.GetCategoryByIdentifier(e.Item.Tag?.ToString());
+                ShowDetail(category);
+                _currentDetailCategory = category;
+            }
+        }
+        #endregion
+
+        #region *** Operations ***
+        private void OpenProductFile(string fileName)
+        {
+            BlockErrorHandled(() =>
+            {
+                tsslblOutput.Text = fileName;
+                if (!File.Exists(fileName))
+                    return;
+
+                _storage.Load(fileName, ",");
+                RefreshTools();
+                RefreshViews();
+            });
+        }
+
+        private void SaveProductFile(string? fileName)
+        {
+            BlockErrorHandled(() =>
+            {
+                if (fileName == null)
+                    return;
+                tsslblOutput.Text = fileName;
+
+                _storage.Save(fileName, ",");
+                RefreshTools();
+            });
+        }
+
+
+        private void ClearDetail()
+        {
+            BlockErrorHandled(() =>
+            {
+                tbdetId.Text = string.Empty;
+                tbdetName.Text = string.Empty;
+                tbdetSku.Text = string.Empty;
+                tbdetShortDescription.Text = string.Empty;
+                tbdetDescription.Text = string.Empty;
+
+                cbdetCategories.BeginUpdate();
+                cbdetCategories.Items.Clear();
+                foreach (var category in _storage.Categories)
+                    cbdetCategories.Items.Add(category.Name);
+                cbdetCategories.EndUpdate();
+
+                tbdetPrice.Value = 0;
+                tbdetUri.Text = string.Empty;
+
+                cbdetParent.BeginUpdate();
+                cbdetParent.Items.Clear();
+                foreach (var product in _storage.GetProductsView(true, false).Select(x => x.Sku).Order())
+                    cbdetParent.Items.Add(product!);
+                cbdetParent.EndUpdate();
+            });
+        }
+
+        private void VisibleAllDetail(bool visible)
+        {
+            BlockErrorHandled(() =>
+            {
+                tbdetId.Visible = visible;
+                ltbdetId.Visible = visible;
+                tbdetName.Visible = visible;
+                ltbdetName.Visible = visible;
+                tbdetSku.Visible = visible;
+                ltbdetSku.Visible = visible;
+                tbdetShortDescription.Visible = visible;
+                ltbdetShortDescription.Visible = visible;
+                tbdetDescription.Visible = visible;
+                ltbdetDescription.Visible = visible;
+                tbdetPrice.Visible = visible;
+                ltbdetPrice.Visible = visible;
+                tbdetUri.Visible = visible;
+                ltbdetUri.Visible = visible;
+                cbdetCategories.Visible = visible;
+                lcbdetCategories.Visible = visible;
+                cbdetParent.Visible = visible;
+                lcbdetParent.Visible = visible;
+            });
+        }
+
+        private void ShowDetail(ProductDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                _detailType = DetailType.Product;
+                detProduct.Visible = false;
+                ClearDetail();
+                if (data == null)
+                {
+                    detProduct.Visible = true;
+                    return;
+                }
+                var isVariant = data.IsVariant;
+                tblbCaption.Text = isVariant ? "Varianta produktu" : "Produkt";
+
+                VisibleAllDetail(true);
+                if (isVariant)
+                {
+                    // variant
+                    cbdetCategories.Visible = false;
+                    lcbdetCategories.Visible = false;
+                }
+                else
+                {
+                    // head product
+                    cbdetParent.Visible = false;
+                    lcbdetParent.Visible = false;
+                    tbdetPrice.Visible = false;
+                    ltbdetPrice.Visible = false;
+                    cbdetCategories.SelectedIndex = cbdetCategories.FindStringExact(data.Category);
+                }
+
+
+                tbdetId.Text = data.Id.ToString();
+                tbdetName.Text = data.ProductName;
+                tbdetSku.Text = data.Sku;
+                tbdetShortDescription.Text = data.ShortDescription;
+                tbdetDescription.Text = data.Description;
+
+                tbdetPrice.Value = data.Price;
+                tbdetUri.Text = data.Uri;
+
+                cbdetParent.SelectedIndex = cbdetParent.FindStringExact(data.ParentName);
+                detProduct.Visible = true;
+            });
+        }
+
+        private void ShowDetail(VariantDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                _detailType = DetailType.Variant;
+                tblbCaption.Text = "Varianta";
+                detProduct.Visible = false;
+                ClearDetail();
+                if (data == null)
+                {
+                    detProduct.Visible = true;
+                    return;
+                }
+
+
+                VisibleAllDetail(false);
+                ltbdetName.Visible = true;
+                tbdetName.Visible = true;
+                ltbdetPrice.Visible = true;
+                tbdetPrice.Visible = true;
+
+
+
+                tbdetName.Text = data.Name;
+                tbdetPrice.Value = data.Price;
+
+                detProduct.Visible = true;
+            });
+        }
+
+        private void ShowDetail(CategoryDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                _detailType = DetailType.Category;
+                tblbCaption.Text = "Kategorie";
+                detProduct.Visible = false;
+                ClearDetail();
+                if (data == null)
+                {
+                    detProduct.Visible = true;
+                    return;
+                }
+
+
+
+                VisibleAllDetail(false);
+                ltbdetName.Visible = true;
+                tbdetName.Visible = true;
+
+                tbdetName.Text = data.Name;
+
+                detProduct.Visible = true;
+            });
+        }
+
+        private void SaveDetail(ProductDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                if (data == null)
+                    return;
+
+                data.ProductName = tbdetName.Text;
+                if (!string.Equals(data.Sku,tbdetSku.Text,StringComparison.CurrentCultureIgnoreCase)){
+                    if (_storage.Products.Any(x => string.Equals(x.Sku, tbdetSku.Text, StringComparison.CurrentCultureIgnoreCase)))
+                        throw new Exception($"Produkt se zadaným SKU '{tbdetSku.Text}' již existuje!");
+                    if (!data.IsVariant)
+                    {
+                        foreach(ProductDto item in _storage.Products.Where(x=>x.IsVariant && string.Equals(x.ParentName, data.Sku, StringComparison.CurrentCultureIgnoreCase)))
+                        {
+                            item.ParentName = tbdetSku.Text;
+                        }
+                    }
+                }
+                data.Sku = tbdetSku.Text;
+
+                data.ShortDescription = tbdetShortDescription.Text;
+                data.Description = tbdetDescription.Text;
+
+                data.Price = tbdetPrice.Value;
+                data.Uri = tbdetUri.Text;
+                data.ParentName = cbdetParent.SelectedItem?.ToString() ?? string.Empty;
+                data.Category = cbdetCategories.SelectedItem?.ToString() ?? string.Empty;
+                _storage.CheckProductVariantValidity();
+            });
+        }
+        private void SaveDetail(VariantDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                if (data == null)
+                    return;
+
+                // update products
+
+                var productsToUpdate = _storage.Products.Where(x => x.IsVariant && string.Equals(x.VariantName, data.Name, StringComparison.InvariantCultureIgnoreCase));
+                foreach (var product in productsToUpdate)
+                {
+                    product.VariantName = tbdetName.Text;
+                    product.Price = tbdetPrice.Value;
+                }
+                data.Name = tbdetName.Text;
+                data.Price = tbdetPrice.Value;
+            });
+        }
+
+        private void SaveDetail(CategoryDto? data)
+        {
+            BlockErrorHandled(() =>
+            {
+                if (data == null)
+                    return;
+
+                // update products
+                if (!string.Equals(data.Name, tbdetName.Text, StringComparison.InvariantCultureIgnoreCase))
+                {
+
+                    var productsToUpdate = _storage.Products.Where(x => x.IsVariant && string.Equals(x.Category, data.Name, StringComparison.InvariantCultureIgnoreCase));
+                    foreach (var product in productsToUpdate)
+                    {
+                        product.Category = tbdetName.Text;
+                    }
+                    data.Name = tbdetName.Text;
+                }
+            });
+        }
+        private void AddNewVariant()
+        {
+            BlockErrorHandled(() =>
+            {
+                var item = _storage.AddNewVariant();
+                lvVariants.AddItem(item, true);
+            });
+        }
+        private void AddNewCategory()
+        {
+            BlockErrorHandled(() =>
+            {
+                var item = _storage.AddNewCategory();
+                lvCategories.AddItem(item, true);
+            });
+        }
+
+        private void AddNewProduct()
+        {
+            BlockErrorHandled(() =>
+            {
+                var item = _storage.AddNewProduct();
+                item.ProductName = _hashGenerator.NewId();
+                item.Sku = item.ProductName;
+                lvProducts.AddItem(item, true);
+            });
+        }
+        private void AddNewProductVariant()
+        {
+            BlockErrorHandled(() =>
+            {
+                var item = _storage.AddNewProductVariant(GetSelectedProduct()?.FirstOrDefault()?.ParentName);
+                item.ProductName = _hashGenerator.NewId();
+                item.Sku = item.ProductName;
+                lvProducts.AddItem(item, true);
+            });
+        }
+
+        private void CloneProduct(ProductDto item)
+        {
+            BlockErrorHandled(() =>
+            {
+                var newItem = item.IsVariant ? _storage.AddNewProductVariant(item.ParentName) : _storage.AddNewProduct();
+                item.CopyTo(newItem);
+                lvProducts.AddItem(newItem, true);
+            });
+        }
+
+        private void ApplyProductVariant()
+        {
+            BlockErrorHandled(() =>
+            {
+                var variants = GetSelectedVariants();
+                if (variants == null)
+                    return;
+
+                var totalCnt = 0;
+                foreach (var variant in variants)
+                {
+                    var productsToUpdate = _storage.Products.Where(x => x.IsVariant && string.Equals(x.VariantName, variant.Name, StringComparison.InvariantCultureIgnoreCase));
+                    totalCnt += productsToUpdate.Count();
+                    foreach (var product in productsToUpdate)
+                    {
+                        product.Price = variant.Price;
+                    }
+                }
+                MessageBox.Show($"Bylo upraveno {totalCnt} produktů", "Hotovo", MessageBoxButtons.OK, MessageBoxIcon.Information);
+            });
+        }
+
+        private ProductDto[]? GetSelectedProduct()
+        {
+
+            if (lvProducts.SelectedItems.Count == 0)
+                return null;
+            var result = new List<ProductDto>();
+            BlockErrorHandled(() =>
+            {
+                foreach (ListViewItem item in lvProducts.SelectedItems)
+                {
+                    var product = _storage.GetProductByIdentifier(item.Tag?.ToString());
+                    if (product != null)
+                        result.Add(product);
+                }
+            });
+            return result.ToArray();
+
+
+        }
+
+        private VariantDto[]? GetSelectedVariants()
+        {
+            if (lvVariants.SelectedItems.Count == 0)
+                return null;
+            var result = new List<VariantDto>();
+            BlockErrorHandled(() =>
+            {
+                foreach (ListViewItem item in lvVariants.SelectedItems)
+                {
+                    var variant = _storage.GetVariantByIdentifier(item.Tag?.ToString());
+                    if (variant != null)
+                        result.Add(variant);
+                }
+            });
+            return result.ToArray();
+        }
+
+        private CategoryDto[]? GetSelectedCategories()
+        {
+            if (lvCategories.SelectedItems.Count == 0)
+                return null;
+            var result = new List<CategoryDto>();
+            foreach (ListViewItem item in lvCategories.SelectedItems)
+            {
+                var category = _storage.GetCategoryByIdentifier(item.Tag?.ToString());
+                if (category != null)
+                    result.Add(category);
+            }
+            return result.ToArray();
+        }
+
+        private void ImportProducts(string selectedPath, string urlPrefix, string shortDescription, string description, DateTime uploadDate)
+        {
+
+            BlockErrorHandled(() =>
+            {
+                if (!Directory.Exists(selectedPath))
+                    throw new Exception($"Složka '{selectedPath}' neexistuje.");
+
+                // search for files
+                var fsrch = new FileSearch(selectedPath, true);
+                var filesCache = new List<string>();
+
+                var files = fsrch.Search(new[] {AppSettings.Default.SourceSearchPattern , "*" });
+                if (files.Length == 0)
+                    throw new Exception($"Nenalezeny žádné soubory pro zpracování ve složce '{selectedPath}'.");
+                // construct list of files
+                foreach (var file in files)
+                    if (!filesCache.Any(x => x == file))
+                        filesCache.Add(file);
+
+                var tplParams = new Dictionary<string, string>
+            {
+                { "DD", uploadDate.Day.ToString("##") },
+                { "MM", uploadDate.Month.ToString("##") },
+                { "YY", uploadDate.Year.ToString().Substring(2,2) },
+                { "YYYY", uploadDate.Year.ToString() },
+                { "file", "file" },
+                { "ext", "ext" },
+                { "category", "category" },
+                { "variant", "variant" }
+            };
+
+                foreach (var file in filesCache)
+                {
+                    var product = new ProductDto();
+                    var pathParts = file.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
+                    if (pathParts.Length < 3) continue;
+                    var indexLast = pathParts.Length - 1;
+                    var sku = pathParts[indexLast];
+                    var variant = pathParts[indexLast - 1];
+                    var category = pathParts[indexLast - 2];
+
+                    tplParams["file"] = Path.GetFileNameWithoutExtension(pathParts[indexLast]);
+                    tplParams["ext"] = Path.GetExtension(pathParts[indexLast]);
+                    tplParams["variant"] = variant;
+                    tplParams["category"] = category;
+
+                    var parentDto = _storage.GetProductByName(Path.GetFileNameWithoutExtension(pathParts[indexLast]));
+                    if (parentDto == null)
+                    {
+                        // product has not parent, create new product
+                        parentDto = _storage.AddNewProduct();
+                        parentDto.ProductName = Path.GetFileNameWithoutExtension(pathParts[indexLast]);
+                        parentDto.Sku = sku + "_" + _hashGenerator.NewId();
+                        parentDto.Category = category;
+                        parentDto.ShortDescription = new ExtendedParametrizedString(shortDescription, tplParams, "{", "}").ToString();
+                        parentDto.Description = new ExtendedParametrizedString(description, tplParams, "{", "}").ToString();
+                        parentDto.Uri = new ExtendedParametrizedString(urlPrefix, tplParams, "{", "}").ToString();
+                    }
+
+                    product = _storage.AddNewProductVariant(parentDto.Sku);
+                    product.Uri = new ExtendedParametrizedString(urlPrefix, tplParams, "{", "}").ToString();
+                    product.ShortDescription = new ExtendedParametrizedString(shortDescription, tplParams, "{", "}").ToString();
+                    product.Description = new ExtendedParametrizedString(description, tplParams, "{", "}").ToString();
+                    product.ProductName = Path.GetFileNameWithoutExtension(pathParts[indexLast]) + " - " + variant;
+                    product.Sku = sku + "_" + _hashGenerator.NewId();
+                    product.Price = 0;
+                    product.VariantName = variant;
+                }
+                _storage.CheckProductVariantValidity();
+            });
+        }
+
+        private bool ConfirmMessage(string message)
+        {
+            return MessageBox.Show(message, "Potvrzení", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
+        }
+
+
+        private void BlockErrorHandled(Action block)
+        {
+            try
+            {
+                block();
+            }
+            catch (Exception ex)
+            {
+                // Log("Error: " + ex.Message);
+                MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                RefreshTools();
+            }
+        }
+        #endregion
+
+    }
+
+}

+ 405 - 0
FormProductEdit.resx

@@ -0,0 +1,405 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="ssMain.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+  <metadata name="tsMain.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>118, 17</value>
+  </metadata>
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <data name="tbbutNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutOpen.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutSave.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutImport.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <metadata name="tsProducts.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>462, 17</value>
+  </metadata>
+  <data name="tbbutProdNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutProdVarNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutProdDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutProdClone.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutProduct.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutVariant.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <metadata name="tsVariants.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>218, 17</value>
+  </metadata>
+  <data name="tbbutVarNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutVarDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutVarApply.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <metadata name="tsCategories.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>586, 1</value>
+  </metadata>
+  <data name="tbbutCatNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutCatDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <metadata name="tsProductDetail.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>340, 17</value>
+  </metadata>
+  <data name="tbbutDetSave.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutDetCopy.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <data name="tbbutDetPaste.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+        YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb
+        J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj
+        CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN
+        AAAAAElFTkSuQmCC
+</value>
+  </data>
+  <metadata name="dlgOpenFile.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>723, 23</value>
+  </metadata>
+  <metadata name="dlgOpenDir.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>399, 17</value>
+  </metadata>
+  <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>58</value>
+  </metadata>
+  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+    <value>
+        AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQ
+        AADDDgAAww4AAAAAAAAAAAAAjIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/iYmJ/6Ghof/Y2Nj/u7u7/46Ojv/Gxsb/yMjI/42N
+        jf+ysrL/1tbW/6Wlpf+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Kior/lJSU/+zs7P/29vb/pqam/+np
+        6f/s7Oz/mpqa//Dw8P/z8/P/mZmZ/4qKiv+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+JiYn/ysrK//7+
+        /v/Hx8f/5+fn/+vr6/++vr7//////8/Pz/+Ghob/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJ
+        if+jo6P/+fn5/+np6f/t7e3/8fHx/+3t7f/39/f/oKCg/4mJif+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4qKiv/a2tr///////z8/P/+/v7//////9nZ2f+MjIz/i4uL/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+MjIz/jIyM/4yM
+        jP+Li4v/ioqK/4qKiv+JiYn/iIiI/9DQ0P//////////////////////0tLS/4qKiv+JiYn/iYmJ/4mJ
+        if+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4yM
+        jP+MjIz/ioqK/5iYmP+kpKT/pKSk/6Kiov+urq7/9vb2///////////////////////z8/P/ra2t/6Ki
+        ov+lpaX/paWl/5qamv+Kior/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+Li4v/jIyM/4mJif+xsbH/8/Pz//n5+f/4+Pj/+Pj4//n5+f//////////////////////////////
+        ///5+fn/+Pj4//j4+P/5+fn/9PT0/7e3t/+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/ioqK/9jY2P/////////////////////////////////r6+v/1NTU/9TU
+        1P/q6ur//v7+////////////////////////////2tra/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/39/f////////////////////////////1dXV/5ub
+        m/+Ghob/h4eH/5mZmf/U1NT////////////////////////////e3t7/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi//e3t7//////////////////////+Pj
+        4/+Pj4//mJiY/7+/v/+/v7//mJiY/42Njf/n5+f//////////////////////97e3v+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+MjIz/i4uL/+Dg4P//////////////
+        ///+/v7/ubm5/5GRkf/g4OD////////////j4+P/lJSU/7Kysv/+/v7/////////////////39/f/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4yMjP+Kior/ycnJ/+7u
+        7v/r6+v/7e3t/+Li4v+UlJT/vLy8///////////////////////CwsL/kJCQ/9vb2//u7u7/7Ozs/+3t
+        7f/Ly8v/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/jIyM/4yM
+        jP+MjIz/j4+P/42Njf+Pj4//i4uL/4aGhv/R0dH//////////////////////9XV1f+JiYn/i4uL/4+P
+        j/+Ojo7/jo6O/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uL
+        i/+MjIz/i4uL/76+vv/d3d3/2tra/9zc3P/Q0ND/jY2N/8bGxv//////////////////////zMzM/42N
+        jf/Kysr/3d3d/9vb2//d3d3/wMDA/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+Li4v/39/f//////////////////v7+/+rq6v/nZ2d//Ly8v////////////X1
+        9f+jo6P/o6Oj//n5+f/////////////////f39//jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi//e3t7//////////////////////9HR0f+Ghob/tbW1/+fn
+        5//m5ub/uLi4/4SEhP/Pz8////////Hx8f/Jycn/7u7u/+Dg4P+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/9/f3///////////////////////9/f3/7u7
+        u/+Ghob/jo6O/42Njf+EhIT/ubm5//n5+f//////0dHR/3t7e/++vr7/39/f/42Njf+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/3d3d////////////////////
+        ////////+fn5/9XV1f+zs7P/s7Oz/9PT0//39/f////////////f39//i4uL/9LS0v/f39//jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJif/Dw8P//Pz8//39
+        /f/9/f3//f39//7+/v////////////r6+v/6+vr////////////+/v7//f39//r6+v/r6+v/9/f3/8jI
+        yP+Kior/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/5KS
+        kv+zs7P/u7u7/7u7u/+7u7v/y8vL//z8/P//////////////////////+vr6/8nJyf+5ubn/vb29/8DA
+        wP+2trb/lJSU/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/i4uL/4qKiv+JiYn/iYmJ/4iIiP+Tk5P/6enp//r6+v/4+Pj/+Pj4//r6+v/g4OD/kJCQ/4iI
+        iP+Kior/ioqK/4qKiv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+ZmZn/o6Oj/6CgoP+hoaH/o6Oj/5iY
+        mP+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4qKiv+Kior/ioqK/4qK
+        iv+Kior/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAA
+        AAAABAAAww4AAMMOAAAAAAAAAAAAAIyMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4qKiv+JiYn/iYmJ/4qK
+        iv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4aGhv9/f3//fX19/319
+        ff9/f3//hoaG/4qKiv+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4mJif+JiYn//////7m5
+        uf+1tbX//////42Njf+JiYn/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4uLi/+Ghob/dXV1//Dw
+        8P/u7u7/8PDw//Dw8P90dHT/hoaG/4uLi/+MjIz/jIyM/4yMjP+MjIz/jIyM/4qKiv+FhYX/enp6/2tr
+        a/+YmJj///////////+ampr/ampq/3p6ev+FhYX/ioqK/4yMjP+MjIz/jIyM/4uLi/+FhYX/hoaG/9jY
+        2P/Q0ND/9PT0////////////8vLy/9DQ0P/Z2dn/iYmJ/4aGhv+Li4v/jIyM/4yMjP+Kior/gYGB/7S0
+        tP////////////////+cnJz/m5ub/////////////////7S0tP+BgYH/ioqK/4yMjP+MjIz/ioqK/4CA
+        gP+1tbX///////////+Pj4//yMjI/8rKyv+Ojo7///////////+1tbX/gICA/4qKiv+MjIz/jIyM/4qK
+        iv+BgYH/iIiI/7Kysv+np6f/ioqK////////////jo6O/6Wlpf+zs7P/ioqK/4KCgv+Kior/jIyM/4yM
+        jP+Kior/gYGB/6enp///////+vr6/4KCgv///////////4aGhv/8/Pz//////6ysrP+CgoL/ioqK/4yM
+        jP+MjIz/ioqK/4CAgP+zs7P///////////+vr6//ioqK/4qKiv+xsbH//////7Kysv+9vb3/gYGB/4qK
+        iv+MjIz/jIyM/4uLi/+Dg4P/q6ur/////////////////+fn5//n5+f////////////g4OD/tra2/4SE
+        hP+Li4v/jIyM/4yMjP+Li4v/iIiI/39/f/+VlZX/jY2N/9vb2////////////9XV1f+RkZH/np6e/4OD
+        g/+IiIj/i4uL/4yMjP+MjIz/jIyM/4uLi/+IiIj/hISE/39/f/98fHz/hISE/4SEhP99fX3/f39//4WF
+        hf+JiYn/i4uL/4yMjP+MjIz/jIyM/4yMjP+MjIz/i4uL/4uLi/+Kior/iIiI/4aGhv+Ghob/iIiI/4qK
+        iv+Li4v/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP+Li4v/i4uL/4yM
+        jP+MjIz/jIyM/4yMjP+MjIz/jIyM/4yMjP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+</value>
+  </data>
+</root>

+ 29 - 0
ProductStorage/CategoryDto.cs

@@ -0,0 +1,29 @@
+namespace qdr.app.studiou.orders2printpack.ProductStorage
+{
+    public class CategoryDto : IIdentifierGetter
+    {
+        public string Name { get; set; } = string.Empty;
+
+        public void CopyTo(CategoryDto destination)
+        {
+            destination.Name = Name;
+        }
+        public string GetIdentifier()
+        {
+            if (string.IsNullOrWhiteSpace(Name))
+                return string.Empty;
+            
+            return Name.ToLower();
+        }
+
+        public override string ToString()
+        {
+            return Name;
+        }
+
+        bool IEquatable<IIdentifierGetter>.Equals(IIdentifierGetter? other)
+        {
+            return other != null && other.GetIdentifier() == GetIdentifier();
+        }
+    }
+}

+ 7 - 0
ProductStorage/IIdentifierDto.cs

@@ -0,0 +1,7 @@
+namespace qdr.app.studiou.orders2printpack.ProductStorage
+{
+    public interface IIdentifierGetter : IEquatable<IIdentifierGetter>
+    {
+        string GetIdentifier();
+    }
+}

+ 88 - 0
ProductStorage/ProductDto.cs

@@ -0,0 +1,88 @@
+
+using qdr.app.studiou.orders2printpack.Extensions;
+using System.Security.Policy;
+using System.Text;
+
+namespace qdr.app.studiou.orders2printpack.ProductStorage
+{
+    public class ProductDto : IIdentifierGetter
+    {
+        public const string CS_ITEM_TYPE_VARIANT = "variation";
+        public const string CS_ITEM_TYPE_PRODUCT = "variable";
+
+        [CsvColumn("ID")]
+        public int Id { get; set; }
+
+        [CsvColumn("Typ")]
+        public string Type { get; set; } = string.Empty;
+
+        [CsvColumn("Katalogové číslo")]
+        public string Sku { get; set; } = string.Empty;
+        
+        [CsvColumn("Jméno")]
+        public string ProductName { get; set; } = string.Empty;
+
+        [CsvColumn("Nadřazené")]
+        public string ParentName { get; set; } = string.Empty;
+
+        [CsvColumn("Krátký popis")]
+        public string ShortDescription { get; set; } = string.Empty;
+        [CsvColumn("Popis")]
+        public string Description { get; set; } = string.Empty;
+
+        [CsvColumn("Název 1 vlastnosti")]
+        public string VariantType { get; set; } = string.Empty;
+
+        [CsvColumn("Hodnota(y) 1 vlastnosti")]
+        public string VariantName { get; set; } = string.Empty;
+
+        [CsvColumn("Kategorie")]
+        public string Category { get; set; } = string.Empty;
+        
+        [CsvColumn("Obrázky")]
+        public string Uri { get; set; } = string.Empty;
+        
+        [CsvColumn("Běžná cena")]
+        public decimal Price { get; set; }
+
+        public bool IsParentValid {get;set;}
+        public bool IsValid => ((IsVariant && IsParentValid) || !IsVariant) && ((IsVariant && Price > 0) || !IsVariant) && !string.IsNullOrEmpty(Sku) && !string.IsNullOrEmpty(Uri) && ((!IsVariant && !string.IsNullOrEmpty(Category)) || IsVariant) && ((IsVariant && !string.IsNullOrEmpty(VariantName)) || !IsVariant);
+
+        public bool IsVariant => string.Equals(Type, CS_ITEM_TYPE_VARIANT, StringComparison.InvariantCultureIgnoreCase);
+        public string VariantAndPrice => $"{VariantName}-{Price}";
+
+        public string TypeAsString => IsVariant ? "Varianta" : "Produkt";
+
+        public string GetIdentifier()
+        {
+            var sb = new StringBuilder();
+            sb.Append(Id).Append("_");
+            sb.Append(ProductName);
+            return sb.ToString().ToLower();
+        }
+
+        public void CopyTo(ProductDto destination, bool includeId = false)
+        {
+            if (includeId)
+                destination.Id = Id;
+            destination.Type = Type;
+            destination.Sku = Sku;
+            destination.ProductName = ProductName;
+            destination.ParentName = ParentName;
+            destination.ShortDescription = ShortDescription;
+            destination.Description = Description;
+            destination.VariantType = VariantType;
+            destination.VariantName = VariantName;
+            destination.Category = Category;
+            destination.Uri = Uri;
+            destination.Price = Price;
+        }
+
+        bool IEquatable<IIdentifierGetter>.Equals(IIdentifierGetter? other)
+        {
+            return other != null && other.GetIdentifier() == GetIdentifier();
+        }
+
+
+    }
+}

+ 225 - 0
ProductStorage/ProductStorage.cs

@@ -0,0 +1,225 @@
+
+using qdr.app.studiou.orders2printpack.Extensions;
+
+namespace qdr.app.studiou.orders2printpack.ProductStorage
+{
+    public class ProductStorage
+    {
+        private IList<ProductDto> _rawCollection = new List<ProductDto>();
+        private IList<VariantDto> _variants = new List<VariantDto>();
+        private IList<CategoryDto> _categories = new List<CategoryDto>();
+        public IEnumerable<ProductDto> Products => _rawCollection.ToArray();
+        public IEnumerable<VariantDto> Variants => GetVariants().ToArray();
+        public IEnumerable<CategoryDto> Categories => GetCategories().ToArray();
+
+
+        public void Load(string filePath, string separator)
+        {
+            _rawCollection = CsvParser.Parse<ProductDto>(filePath, delimiter: separator).ToList();
+
+            CheckProductVariantValidity();
+        }
+
+        public void Save(string filePath, string separator)
+        {
+            CsvParser.Save<ProductDto>(_rawCollection.OrderBy(x=>x.IsVariant).ToArray(), filePath, delimiter:separator);
+        }
+
+        public void CheckProductVariantValidity()
+        {
+            foreach (var product in _rawCollection.Where(x => x.IsVariant))
+            {
+                product.IsParentValid = _rawCollection.Any(x => string.Equals(x.Sku, product.ParentName, StringComparison.InvariantCultureIgnoreCase));
+            }
+        }
+
+        public ProductDto? GetProductByIdentifier(string? identifier)
+        {
+            if (string.IsNullOrWhiteSpace(identifier))
+                return null;
+            return _rawCollection.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+         public ProductDto? GetProductBySku(string sku)
+        {
+            if (string.IsNullOrWhiteSpace(sku))
+                return null;
+            return _rawCollection.FirstOrDefault(x => !x.IsVariant && string.Equals(x.Sku, sku, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        public ProductDto? GetProductByName(string productName)
+        {
+            if (string.IsNullOrWhiteSpace(productName))
+                return null;
+            return _rawCollection.FirstOrDefault(x => !x.IsVariant && string.Equals(x.ProductName, productName, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        public VariantDto? GetVariantByIdentifier(string? identifier)
+        {
+            if (identifier==null)
+                return null;
+            return Variants.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        public CategoryDto? GetCategoryByIdentifier(string? identifier)
+        {
+            if (identifier==null)
+                return null;
+            return Categories.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        public void AppendVariantsFromString(string variantsString)
+        {
+            var variants = variantsString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+            foreach (var variant in variants)
+            {
+                var variantParts = variant.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
+                if (variantParts.Length != 2)
+                    continue;
+
+                var variantName = variantParts[0];
+                var variantPrice = decimal.Parse(variantParts[1]);
+
+                if (_variants.Any(x => string.Equals(x.Name,variantName, StringComparison.InvariantCultureIgnoreCase)))
+                    continue;
+
+                _variants.Add(new VariantDto()
+                {
+                    Name = variantName,
+                    Price = variantPrice
+                });
+            }
+        }
+
+        public void AppendCategoriesFromString(string categoriesString)
+        {
+            var categories = categoriesString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+            foreach (var category in categories)
+            {
+                if (_categories.Any(x=>string.Equals(x.Name ,categoriesString, StringComparison.InvariantCultureIgnoreCase)))
+
+                _categories.Add(new CategoryDto() { Name = category });
+            }
+        }
+
+        public ProductDto AddNewProduct()
+        {
+            var item = new ProductDto() { Type = ProductDto.CS_ITEM_TYPE_PRODUCT };
+            _rawCollection.Add(item);
+            return item;
+        }
+
+        public ProductDto AddNewProductVariant(string? parentSku)
+        {
+            if (!string.IsNullOrWhiteSpace(parentSku))
+                if (!_rawCollection.Any(x => string.Equals(x.Sku, parentSku, StringComparison.InvariantCultureIgnoreCase)))
+                    throw new Exception($"Parent Product SKU '{parentSku}' not found!");
+
+            var item = new ProductDto() {Type = ProductDto.CS_ITEM_TYPE_VARIANT, ParentName = parentSku};
+
+            _rawCollection.Add(item);
+            return item;
+        }
+
+        public VariantDto AddNewVariant()
+        {
+            var item = new VariantDto() { };
+            _variants.Add(item);
+            return item;
+        }
+
+        public CategoryDto AddNewCategory()
+        {
+            var item = new CategoryDto() { };
+            _categories.Add(item);
+            return item;
+        }
+
+        public void RemoveProduct(string[]? identifiers)
+        {
+            if (identifiers == null || identifiers.Length==0)
+                return;
+
+            foreach (var identifier in identifiers)
+            {
+                var itemToRemove = _rawCollection.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+                if (itemToRemove != null)
+                    _rawCollection.Remove(itemToRemove);
+            }
+        }
+
+        public void RemoveVariants(string[]? identifiers)
+        {
+            if (identifiers == null || identifiers.Length==0)
+                return;
+
+            foreach (var identifier in identifiers)
+            {
+                var itemToRemove = _variants.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+                if (itemToRemove != null)
+                    _variants.Remove(itemToRemove);
+            }
+        }
+
+        public void RemoveCategories(string[]? identifiers)
+        {
+            if (identifiers == null || identifiers.Length==0)
+                return;
+
+            foreach (var identifier in identifiers)
+            {
+                var itemToRemove = _categories.FirstOrDefault(x => string.Equals(x.GetIdentifier(), identifier, StringComparison.InvariantCultureIgnoreCase));
+                if (itemToRemove != null)
+                    _categories.Remove(itemToRemove);
+            }
+        }
+
+        public string GetVariantsAsString()
+        {
+            return string.Join(";", _variants.Select(x => x.ToString()));
+        }
+
+        public string GetCategoriesAsString()
+        {
+            return string.Join(";", _categories.Select(x => x.ToString()));
+        }
+
+
+        public IEnumerable<ProductDto> GetProductsView(bool includeProduct, bool includeVariants)
+        {
+            return _rawCollection.Where(x => x.IsVariant == includeVariants || !x.IsVariant == includeProduct).ToArray();
+        }
+
+        private IEnumerable<VariantDto> GetVariants()
+        {
+            var productVariants = _rawCollection.Where(x => x.IsVariant).DistinctBy(x => x.VariantAndPrice).Select(x => new VariantDto() { Name = x.VariantName, Price = x.Price });
+            foreach (var variant in productVariants)
+            {
+                if (!_variants.Any(x => x.Name == variant.Name))
+                {
+                    _variants.Add(variant);
+                }
+                else
+                {
+                    if (_variants.First(x => x.Name == variant.Name).Price != variant.Price)
+                        _variants.First(x => x.Name == variant.Name).Price = variant.Price;
+
+                }
+            }
+            return _variants;
+        }
+
+        private IEnumerable<CategoryDto> GetCategories()
+        {
+            var productCategories = _rawCollection.Where(x=>!x.IsVariant).DistinctBy(x=>x.Category).Select(x => new CategoryDto() { Name = x.Category });
+            foreach (var category in productCategories)
+            {
+                if (!_categories.Any(x => x.Name == category.Name))
+                {
+                    _categories.Add(category);
+                }
+            }
+            return _categories;
+        }
+    }
+}

+ 38 - 0
ProductStorage/VariantDto.cs

@@ -0,0 +1,38 @@
+
+using Microsoft.VisualBasic;
+
+namespace qdr.app.studiou.orders2printpack.ProductStorage
+{
+    public class VariantDto : IIdentifierGetter
+    {
+        public string Name { get; set; } = string.Empty;
+        public decimal Price { get; set; } = 0;
+
+
+        public void CopyTo(VariantDto destination)
+        {
+            destination.Name = Name;
+            destination.Price = Price;
+        }
+        public string GetIdentifier()
+        {
+            if (string.IsNullOrWhiteSpace(Name))
+                return string.Empty;
+            
+            return Name.ToLower();
+        }
+
+        public override string ToString()
+        {
+              if (string.IsNullOrWhiteSpace(Name))
+                return string.Empty;
+            return Name.ToLower();
+        }
+
+        bool IEquatable<IIdentifierGetter>.Equals(IIdentifierGetter? other)
+        {
+            return other != null && other.GetIdentifier() == GetIdentifier();
+        }
+    }
+    
+}

+ 1 - 1
Program.cs

@@ -11,7 +11,7 @@ namespace qdr.app.studiou.orders2printpack
             // To customize application configuration such as set high DPI settings or default font,
             // see https://aka.ms/applicationconfiguration.
             ApplicationConfiguration.Initialize();
-            Application.Run(new FormMain());
+            Application.Run(new FormLauncher());
         }
     }
 }

+ 61 - 0
Properties/AppSettings.Designer.cs

@@ -166,5 +166,66 @@ namespace qdr.app.studiou.orders2printpack.Properties {
                 this["CleanupOutputDir"] = value;
             }
         }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("Foto 10x15cm=150;Foto 15x23cm=120;Foto 20x30cm=100;Magnetka 10x13cm=25,Obraz na p" +
+            "látně 30x45cm=250,Hrnek s potiskem=70\r\n")]
+        public string PredefinedVariants {
+            get {
+                return ((string)(this["PredefinedVariants"]));
+            }
+            set {
+                this["PredefinedVariants"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string PredefinedCategories {
+            get {
+                return ((string)(this["PredefinedCategories"]));
+            }
+            set {
+                this["PredefinedCategories"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("https://www.studiou.cz/wp-content/uploads/{YYYY}/{MM}/{file}{ext}")]
+        public string ImportTplUrl {
+            get {
+                return ((string)(this["ImportTplUrl"]));
+            }
+            set {
+                this["ImportTplUrl"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("Forografie {file} ve formátu {variant}.")]
+        public string ImportTplShortDesc {
+            get {
+                return ((string)(this["ImportTplShortDesc"]));
+            }
+            set {
+                this["ImportTplShortDesc"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("{category} ve formátu {variant}.")]
+        public string ImportTplDesc {
+            get {
+                return ((string)(this["ImportTplDesc"]));
+            }
+            set {
+                this["ImportTplDesc"] = value;
+            }
+        }
     }
 }

+ 16 - 0
Properties/AppSettings.settings

@@ -38,5 +38,21 @@
     <Setting Name="CleanupOutputDir" Type="System.Boolean" Scope="User">
       <Value Profile="(Default)">True</Value>
     </Setting>
+    <Setting Name="PredefinedVariants" Type="System.String" Scope="User">
+      <Value Profile="(Default)">Foto 10x15cm=150;Foto 15x23cm=120;Foto 20x30cm=100;Magnetka 10x13cm=25,Obraz na plátně 30x45cm=250,Hrnek s potiskem=70
+</Value>
+    </Setting>
+    <Setting Name="PredefinedCategories" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="ImportTplUrl" Type="System.String" Scope="User">
+      <Value Profile="(Default)">https://www.studiou.cz/wp-content/uploads/{YYYY}/{MM}/{file}{ext}</Value>
+    </Setting>
+    <Setting Name="ImportTplShortDesc" Type="System.String" Scope="User">
+      <Value Profile="(Default)">Forografie {file} ve formátu {variant}.</Value>
+    </Setting>
+    <Setting Name="ImportTplDesc" Type="System.String" Scope="User">
+      <Value Profile="(Default)">{category} ve formátu {variant}.</Value>
+    </Setting>
   </Settings>
 </SettingsFile>

+ 4 - 0
README.md

@@ -4,6 +4,10 @@ Proprietální řešení zpracování WooCommerce objednávek pro online tiskovo
 
 ## Changelog
 
+### 1.1.0 (2024-10-16)
+- Přidán launch screen
+- Přidána obrazovka Editace produktů s podporou importu ze struktury
+
 ### 1.0.2 (2024-10-05)
 - Úprava mapování na sloupečky CSV, nastavení defaultů
 - Přidáno nastavení pro promazání výstupního adresáře, včetně nastavení

+ 231 - 0
dlgImport.Designer.cs

@@ -0,0 +1,231 @@
+namespace qdr.app.studiou.orders2printpack
+{
+    partial class dlgImport
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(dlgImport));
+            label1 = new Label();
+            tbImportDir = new TextBox();
+            butOpenDir = new Button();
+            label2 = new Label();
+            dtUploadDate = new DateTimePicker();
+            label3 = new Label();
+            tbUrlPrefix = new TextBox();
+            tbShortDescription = new TextBox();
+            label4 = new Label();
+            tbDescription = new TextBox();
+            label5 = new Label();
+            butImport = new Button();
+            butCancel = new Button();
+            label6 = new Label();
+            textBox3 = new TextBox();
+            dlgOpenDir = new FolderBrowserDialog();
+            SuspendLayout();
+            // 
+            // label1
+            // 
+            label1.AutoSize = true;
+            label1.Location = new Point(12, 9);
+            label1.Name = "label1";
+            label1.Size = new Size(232, 15);
+            label1.TabIndex = 0;
+            label1.Text = "Importní adresář (Kategorie\\Varianta\\*.jpg)";
+            // 
+            // tbImportDir
+            // 
+            tbImportDir.Location = new Point(12, 27);
+            tbImportDir.Name = "tbImportDir";
+            tbImportDir.ReadOnly = true;
+            tbImportDir.Size = new Size(305, 23);
+            tbImportDir.TabIndex = 1;
+            // 
+            // butOpenDir
+            // 
+            butOpenDir.Location = new Point(323, 27);
+            butOpenDir.Name = "butOpenDir";
+            butOpenDir.Size = new Size(37, 23);
+            butOpenDir.TabIndex = 2;
+            butOpenDir.Text = "...";
+            butOpenDir.UseVisualStyleBackColor = true;
+            butOpenDir.Click += butOpenDir_Click;
+            // 
+            // label2
+            // 
+            label2.AutoSize = true;
+            label2.Location = new Point(12, 53);
+            label2.Name = "label2";
+            label2.Size = new Size(183, 15);
+            label2.TabIndex = 3;
+            label2.Text = "Datum uploadu fotografií na web";
+            // 
+            // dtUploadDate
+            // 
+            dtUploadDate.Format = DateTimePickerFormat.Short;
+            dtUploadDate.Location = new Point(12, 71);
+            dtUploadDate.Name = "dtUploadDate";
+            dtUploadDate.Size = new Size(305, 23);
+            dtUploadDate.TabIndex = 4;
+            // 
+            // label3
+            // 
+            label3.AutoSize = true;
+            label3.Location = new Point(12, 97);
+            label3.Name = "label3";
+            label3.Size = new Size(66, 15);
+            label3.TabIndex = 5;
+            label3.Text = "Url šablona";
+            // 
+            // tbUrlPrefix
+            // 
+            tbUrlPrefix.Location = new Point(15, 114);
+            tbUrlPrefix.Name = "tbUrlPrefix";
+            tbUrlPrefix.Size = new Size(302, 23);
+            tbUrlPrefix.TabIndex = 6;
+            // 
+            // tbShortDescription
+            // 
+            tbShortDescription.Location = new Point(15, 158);
+            tbShortDescription.Name = "tbShortDescription";
+            tbShortDescription.Size = new Size(302, 23);
+            tbShortDescription.TabIndex = 8;
+            // 
+            // label4
+            // 
+            label4.AutoSize = true;
+            label4.Location = new Point(12, 141);
+            label4.Name = "label4";
+            label4.Size = new Size(116, 15);
+            label4.TabIndex = 7;
+            label4.Text = "Krátký popis šablona";
+            // 
+            // tbDescription
+            // 
+            tbDescription.Location = new Point(15, 204);
+            tbDescription.Multiline = true;
+            tbDescription.Name = "tbDescription";
+            tbDescription.Size = new Size(302, 88);
+            tbDescription.TabIndex = 10;
+            // 
+            // label5
+            // 
+            label5.AutoSize = true;
+            label5.Location = new Point(12, 187);
+            label5.Name = "label5";
+            label5.Size = new Size(121, 15);
+            label5.TabIndex = 9;
+            label5.Text = "Dlouhý popis šablona";
+            // 
+            // butImport
+            // 
+            butImport.Location = new Point(15, 307);
+            butImport.Name = "butImport";
+            butImport.Size = new Size(75, 23);
+            butImport.TabIndex = 11;
+            butImport.Text = "Import";
+            butImport.UseVisualStyleBackColor = true;
+            butImport.Click += butImport_Click;
+            // 
+            // butCancel
+            // 
+            butCancel.Location = new Point(564, 307);
+            butCancel.Name = "butCancel";
+            butCancel.Size = new Size(75, 23);
+            butCancel.TabIndex = 12;
+            butCancel.Text = "Zrušit";
+            butCancel.UseVisualStyleBackColor = true;
+            butCancel.Click += button2_Click;
+            // 
+            // label6
+            // 
+            label6.AutoSize = true;
+            label6.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
+            label6.Location = new Point(366, 27);
+            label6.Name = "label6";
+            label6.Size = new Size(95, 15);
+            label6.TabIndex = 13;
+            label6.Text = "Značky / příklad";
+            // 
+            // textBox3
+            // 
+            textBox3.Location = new Point(373, 46);
+            textBox3.Multiline = true;
+            textBox3.Name = "textBox3";
+            textBox3.ReadOnly = true;
+            textBox3.Size = new Size(266, 250);
+            textBox3.TabIndex = 14;
+            textBox3.Text = resources.GetString("textBox3.Text");
+            // 
+            // dlgImport
+            // 
+            AutoScaleDimensions = new SizeF(7F, 15F);
+            AutoScaleMode = AutoScaleMode.Font;
+            ClientSize = new Size(656, 343);
+            Controls.Add(textBox3);
+            Controls.Add(label6);
+            Controls.Add(butCancel);
+            Controls.Add(butImport);
+            Controls.Add(tbDescription);
+            Controls.Add(label5);
+            Controls.Add(tbShortDescription);
+            Controls.Add(label4);
+            Controls.Add(tbUrlPrefix);
+            Controls.Add(label3);
+            Controls.Add(dtUploadDate);
+            Controls.Add(label2);
+            Controls.Add(butOpenDir);
+            Controls.Add(tbImportDir);
+            Controls.Add(label1);
+            FormBorderStyle = FormBorderStyle.FixedDialog;
+            MaximizeBox = false;
+            MinimizeBox = false;
+            Name = "dlgImport";
+            Text = "Import produktů z adresářové struktury";
+            ResumeLayout(false);
+            PerformLayout();
+        }
+
+        #endregion
+
+        private Label label1;
+        private TextBox tbImportDir;
+        private Button butOpenDir;
+        private Label label2;
+        private DateTimePicker dtUploadDate;
+        private Label label3;
+        private TextBox tbUrlPrefix;
+        private TextBox tbShortDescription;
+        private Label label4;
+        private TextBox tbDescription;
+        private Label label5;
+        private Button butImport;
+        private Button butCancel;
+        private Label label6;
+        private TextBox textBox3;
+        private FolderBrowserDialog dlgOpenDir;
+    }
+}

+ 47 - 0
dlgImport.cs

@@ -0,0 +1,47 @@
+
+using qdr.app.studiou.orders2printpack.Properties;
+
+namespace qdr.app.studiou.orders2printpack
+{
+    public partial class dlgImport : Form
+    {
+        public string UrlPrefix => tbUrlPrefix.Text;
+        public string ShortDescription => tbShortDescription.Text;
+        public string Description => tbDescription.Text;
+        public DateTime UploadDate => dtUploadDate.Value;
+        public string ImportDir => tbImportDir.Text;
+
+        public dlgImport()
+        {
+            InitializeComponent();
+            tbUrlPrefix.Text = AppSettings.Default.ImportTplUrl;
+            tbShortDescription.Text = AppSettings.Default.ImportTplShortDesc;
+            tbDescription.Text = AppSettings.Default.ImportTplDesc;
+            dtUploadDate.Value = DateTime.Now;
+        }
+
+        private void butImport_Click(object sender, EventArgs e)
+        {
+            DialogResult = DialogResult.OK;
+            AppSettings.Default.ImportTplUrl =tbUrlPrefix.Text;
+            AppSettings.Default.ImportTplShortDesc = tbShortDescription.Text;
+            AppSettings.Default.ImportTplDesc = tbDescription.Text;
+            AppSettings.Default.Save();
+            Close();
+        }
+
+        private void button2_Click(object sender, EventArgs e)
+        {
+            DialogResult = DialogResult.Cancel;
+            Close();
+        }
+
+        private void butOpenDir_Click(object sender, EventArgs e)
+        {
+            if (dlgOpenDir.ShowDialog(this)==DialogResult.OK)
+            {
+                tbImportDir.Text = dlgOpenDir.SelectedPath;
+            }
+        }
+    }
+}

+ 133 - 0
dlgImport.resx

@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="textBox3.Text" xml:space="preserve">
+    <value>{DD} - den (01)
+{MM} - měsíc (10)
+{YY} - rok krátký (24)
+{YYYY} - rok dlouhý (2024)
+{file} - jméno souboru bez přípony (IMG_5502)
+{ext} - přípona (jpg)
+{category} - kategorie (ZŠ Letců R.A.F)
+{variant} - varianta (Magnetka 10x13cm)</value>
+  </data>
+  <metadata name="dlgOpenDir.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>399, 17</value>
+  </metadata>
+</root>

+ 1 - 0
qdr.app.studiou.orders2printpack.csproj

@@ -19,6 +19,7 @@
     <AssemblyVersion>1.0.2.0</AssemblyVersion>
     <FileVersion>1.0.2.0</FileVersion>
     <Version>1.0.2</Version>
+	<ForceDesignerDpiUnaware>true</ForceDesignerDpiUnaware>
   </PropertyGroup>
 
   <ItemGroup>