From a0e1dae5cf2c074f394607d4b2cee56e44fefc93 Mon Sep 17 00:00:00 2001
From: Jim Evins
Date: Sat, 7 Apr 2018 22:24:09 -0400
Subject: [PATCH] Implemented TemplateDesigner.
---
CMakeLists.txt | 1 +
README.md | 6 +-
docs/PRODUCT-TEMPLATES.md | 10 +-
glabels/CMakeLists.txt | 14 +
glabels/File.cpp | 11 +
glabels/File.h | 1 +
glabels/MainWindow.cpp | 4 +-
glabels/TemplateDesigner.cpp | 1436 +++++++++++++++++
glabels/TemplateDesigner.h | 289 ++++
glabels/images.qrc | 11 +
.../images/TemplateDesigner/ex-1layout.png | Bin 0 -> 1567 bytes
.../images/TemplateDesigner/ex-2layouts.png | Bin 0 -> 1610 bytes
.../images/TemplateDesigner/ex-cd-size.png | Bin 0 -> 32300 bytes
.../TemplateDesigner/ex-ellipse-size.png | Bin 0 -> 16779 bytes
.../images/TemplateDesigner/ex-rect-size.png | Bin 0 -> 4972 bytes
.../images/TemplateDesigner/ex-round-size.png | Bin 0 -> 16682 bytes
.../images/TemplateDesigner/wizard-banner.png | Bin 0 -> 45285 bytes
glabels/ui/TemplateDesignerApplyPage.ui | 69 +
glabels/ui/TemplateDesignerCdPage.ui | 190 +++
glabels/ui/TemplateDesignerEllipsePage.ui | 156 ++
glabels/ui/TemplateDesignerIntroPage.ui | 76 +
glabels/ui/TemplateDesignerNLayoutsPage.ui | 250 +++
glabels/ui/TemplateDesignerNamePage.ui | 134 ++
glabels/ui/TemplateDesignerOneLayoutPage.ui | 218 +++
glabels/ui/TemplateDesignerPageSizePage.ui | 107 ++
glabels/ui/TemplateDesignerRectPage.ui | 177 ++
glabels/ui/TemplateDesignerRoundPage.ui | 139 ++
glabels/ui/TemplateDesignerShapePage.ui | 81 +
glabels/ui/TemplateDesignerTwoLayoutPage.ui | 256 +++
model/Db.cpp | 69 +-
model/Db.h | 5 +-
model/FileUtil.cpp | 22 +
model/FileUtil.h | 1 +
model/Settings.cpp | 34 +
model/Settings.h | 5 +-
model/Template.cpp | 10 +-
model/Template.h | 7 +-
model/XmlTemplateParser.cpp | 14 +-
model/XmlTemplateParser.h | 6 +-
translations/glabels_C.ts | 661 +++++++-
40 files changed, 4397 insertions(+), 73 deletions(-)
create mode 100644 glabels/TemplateDesigner.cpp
create mode 100644 glabels/TemplateDesigner.h
create mode 100644 glabels/images/TemplateDesigner/ex-1layout.png
create mode 100644 glabels/images/TemplateDesigner/ex-2layouts.png
create mode 100644 glabels/images/TemplateDesigner/ex-cd-size.png
create mode 100644 glabels/images/TemplateDesigner/ex-ellipse-size.png
create mode 100644 glabels/images/TemplateDesigner/ex-rect-size.png
create mode 100644 glabels/images/TemplateDesigner/ex-round-size.png
create mode 100644 glabels/images/TemplateDesigner/wizard-banner.png
create mode 100644 glabels/ui/TemplateDesignerApplyPage.ui
create mode 100644 glabels/ui/TemplateDesignerCdPage.ui
create mode 100644 glabels/ui/TemplateDesignerEllipsePage.ui
create mode 100644 glabels/ui/TemplateDesignerIntroPage.ui
create mode 100644 glabels/ui/TemplateDesignerNLayoutsPage.ui
create mode 100644 glabels/ui/TemplateDesignerNamePage.ui
create mode 100644 glabels/ui/TemplateDesignerOneLayoutPage.ui
create mode 100644 glabels/ui/TemplateDesignerPageSizePage.ui
create mode 100644 glabels/ui/TemplateDesignerRectPage.ui
create mode 100644 glabels/ui/TemplateDesignerRoundPage.ui
create mode 100644 glabels/ui/TemplateDesignerShapePage.ui
create mode 100644 glabels/ui/TemplateDesignerTwoLayoutPage.ui
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ce923e1..14b5bee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,6 +83,7 @@ find_package (Qt5Test 5.4 QUIET)
# (not recommended -- only for testing -- also not portable)
#
#add_compile_options("-Wall" "-Werror" "-Wpedantic")
+add_compile_options("-g")
#=======================================
diff --git a/README.md b/README.md
index 29d796c..e0c25cf 100644
--- a/README.md
+++ b/README.md
@@ -25,10 +25,8 @@ gLabels-qt is the development version of the next major version of gLabels (4.0)
gLabels-qt has been under off-and-on development for several years..
It is still missing several features to bring it in parity with glabels-3.4. These include
-- Batch mode
-- Compatability with older glabels files
-- Custom product templates designer
-- Online manual
+- Compatability with older glabels project files
+- An online manual
## Download
diff --git a/docs/PRODUCT-TEMPLATES.md b/docs/PRODUCT-TEMPLATES.md
index fadf9d0..1be42f4 100644
--- a/docs/PRODUCT-TEMPLATES.md
+++ b/docs/PRODUCT-TEMPLATES.md
@@ -6,11 +6,11 @@ This document is a reference for manually creating *gLabels* product templates.
*gLabels* searches for templates in several locations as described here:
-Location | Description
------------------------------------------|-----------------------------------------
-${prefix}/share/libglabels-qt/templates/ | Predefined templates distributed with glabels.
-${XDG_CONFIG_HOME}/libglabels/templates | User defined templates created with the gLabels Template Designer.
-${HOME}/.glabels | Manually created templates should be placed here.
+Location | Description
+------------------------------------------|-----------------------------------------
+${prefix}/share/glabels-qt/templates/ | Predefined templates distributed with glabels.
+${XDG_CONFIG_HOME}/glabels.org/glabels-qt | User defined templates created with the gLabels Product Template Designer. **Do not place manually created templates here!**
+${HOME}/.glabels | Manually created templates should be placed here.
diff --git a/glabels/CMakeLists.txt b/glabels/CMakeLists.txt
index 30aa1cd..262395b 100644
--- a/glabels/CMakeLists.txt
+++ b/glabels/CMakeLists.txt
@@ -33,6 +33,7 @@ set (glabels_sources
SelectProductDialog.cpp
SimplePreview.cpp
StartupView.cpp
+ TemplateDesigner.cpp
TemplatePicker.cpp
TemplatePickerItem.cpp
UndoRedoModel.cpp
@@ -61,6 +62,7 @@ set (glabels_qobject_headers
SelectProductDialog.h
SimplePreview.h
StartupView.h
+ TemplateDesigner.h
TemplatePicker.h
UndoRedoModel.h
)
@@ -74,6 +76,18 @@ set (glabels_forms
ui/PropertiesView.ui
ui/SelectProductDialog.ui
ui/StartupView.ui
+ ui/TemplateDesignerIntroPage.ui
+ ui/TemplateDesignerNamePage.ui
+ ui/TemplateDesignerPageSizePage.ui
+ ui/TemplateDesignerShapePage.ui
+ ui/TemplateDesignerRectPage.ui
+ ui/TemplateDesignerRoundPage.ui
+ ui/TemplateDesignerEllipsePage.ui
+ ui/TemplateDesignerCdPage.ui
+ ui/TemplateDesignerNLayoutsPage.ui
+ ui/TemplateDesignerOneLayoutPage.ui
+ ui/TemplateDesignerTwoLayoutPage.ui
+ ui/TemplateDesignerApplyPage.ui
)
set (glabels_resource_files
diff --git a/glabels/File.cpp b/glabels/File.cpp
index d07d0e4..f87a4c8 100644
--- a/glabels/File.cpp
+++ b/glabels/File.cpp
@@ -22,6 +22,7 @@
#include "MainWindow.h"
#include "SelectProductDialog.h"
+#include "TemplateDesigner.h"
#include "model/FileUtil.h"
#include "model/Model.h"
@@ -221,6 +222,16 @@ namespace glabels
}
+ ///
+ /// Template Designer
+ ///
+ void File::templateDesigner( MainWindow *window )
+ {
+ TemplateDesigner dialog( window );
+ dialog.exec();
+ }
+
+
///
/// Close file
///
diff --git a/glabels/File.h b/glabels/File.h
index 0329a1f..f151b04 100644
--- a/glabels/File.h
+++ b/glabels/File.h
@@ -46,6 +46,7 @@ namespace glabels
static void open( MainWindow *window );
static bool save( MainWindow *window );
static bool saveAs( MainWindow *window );
+ static void templateDesigner( MainWindow *window );
static void close( MainWindow *window );
static void exit();
diff --git a/glabels/MainWindow.cpp b/glabels/MainWindow.cpp
index 603e453..112ae2d 100644
--- a/glabels/MainWindow.cpp
+++ b/glabels/MainWindow.cpp
@@ -246,7 +246,7 @@ namespace glabels
fileSaveAsAction->setStatusTip( tr("Save current gLabels project to a different name") );
connect( fileSaveAsAction, SIGNAL(triggered()), this, SLOT(fileSaveAs()) );
- fileTemplateDesignerAction = new QAction( tr("Template &Designer..."), this );
+ fileTemplateDesignerAction = new QAction( tr("Product Template &Designer..."), this );
fileTemplateDesignerAction->setStatusTip( tr("Create custom templates") );
connect( fileTemplateDesignerAction, SIGNAL(triggered()), this, SLOT(fileTemplateDesigner()) );
@@ -1041,7 +1041,7 @@ namespace glabels
///
void MainWindow::fileTemplateDesigner()
{
- qDebug() << "ACTION: file->Template Designer";
+ File::templateDesigner( this );
}
diff --git a/glabels/TemplateDesigner.cpp b/glabels/TemplateDesigner.cpp
new file mode 100644
index 0000000..3ed5bce
--- /dev/null
+++ b/glabels/TemplateDesigner.cpp
@@ -0,0 +1,1436 @@
+/* TemplateDesigner.cpp
+ *
+ * Copyright (C) 2018 Jim Evins
+ *
+ * This file is part of gLabels-qt.
+ *
+ * gLabels-qt is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * gLabels-qt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gLabels-qt. If not, see .
+ */
+
+#include "TemplateDesigner.h"
+
+#include "SelectProductDialog.h"
+#include "model/Db.h"
+#include "model/Distance.h"
+#include "model/FrameCd.h"
+#include "model/FrameEllipse.h"
+#include "model/FrameRect.h"
+#include "model/FrameRound.h"
+#include "model/Markup.h"
+#include "model/Model.h"
+#include "model/PageRenderer.h"
+#include "model/Settings.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace glabels
+{
+
+ //
+ // Private types and constants
+ //
+ namespace
+ {
+
+ enum PageId
+ {
+ IntroPageId,
+ NamePageId,
+ PageSizePageId,
+ ShapePageId,
+ RectPageId,
+ RoundPageId,
+ EllipsePageId,
+ CdPageId,
+ NLayoutsPageId,
+ OneLayoutPageId,
+ TwoLayoutPageId,
+ ApplyPageId
+ };
+
+
+ const QString defaultPageSize[] =
+ {
+ /* ISO */ "A4",
+ /* US */ "US Letter"
+ };
+
+
+ const double maxPageSize[] =
+ {
+ /* PT */ 5000,
+ /* IN */ 70,
+ /* MM */ 1800,
+ /* CM */ 180,
+ /* PC */ 420
+ };
+
+ const model::Distance defaultMargin = model::Distance::in(0.125);
+ const model::Distance defaultWaste = model::Distance::in(0);
+
+ const model::Distance defaultRectW = model::Distance::in(3.5);
+ const model::Distance defaultRectH = model::Distance::in(2.0);
+ const model::Distance defaultRectR = model::Distance::in(0);
+
+ const model::Distance defaultRoundR = model::Distance::in(0.75);
+
+ const model::Distance defaultEllipseW = model::Distance::in(3.5);
+ const model::Distance defaultEllipseH = model::Distance::in(2.0);
+
+ const model::Distance defaultCdR1 = model::Distance::in(2.3125);
+ const model::Distance defaultCdR2 = model::Distance::in(0.8125);
+ const model::Distance defaultCdClip = model::Distance::in(0);
+
+ }
+
+
+ ///
+ /// Constructor
+ ///
+ TemplateDesigner::TemplateDesigner( QWidget* parent )
+ : mIsBasedOnCopy(false), QWizard(parent)
+ {
+ setWindowTitle( tr("Product Template Designer") );
+ setPixmap( QWizard::LogoPixmap, QPixmap( ":icons/scalable/apps/glabels.svg" ) );
+ setWizardStyle( QWizard::ModernStyle );
+ setOption( QWizard::IndependentPages, false );
+ setOption( QWizard::NoBackButtonOnStartPage, true );
+
+ setPage( IntroPageId, new TemplateDesignerIntroPage() );
+ setPage( NamePageId, new TemplateDesignerNamePage() );
+ setPage( PageSizePageId, new TemplateDesignerPageSizePage() );
+ setPage( ShapePageId, new TemplateDesignerShapePage() );
+ setPage( RectPageId, new TemplateDesignerRectPage() );
+ setPage( RoundPageId, new TemplateDesignerRoundPage() );
+ setPage( EllipsePageId, new TemplateDesignerEllipsePage() );
+ setPage( CdPageId, new TemplateDesignerCdPage() );
+ setPage( NLayoutsPageId, new TemplateDesignerNLayoutsPage() );
+ setPage( OneLayoutPageId, new TemplateDesignerOneLayoutPage() );
+ setPage( TwoLayoutPageId, new TemplateDesignerTwoLayoutPage() );
+ setPage( ApplyPageId, new TemplateDesignerApplyPage() );
+ }
+
+
+ ///
+ /// Control wizard's non-linear page order
+ ///
+ int TemplateDesigner::nextId() const
+ {
+ switch (currentId())
+ {
+
+ case IntroPageId:
+ return NamePageId;
+
+ case NamePageId:
+ return PageSizePageId;
+
+ case PageSizePageId:
+ return ShapePageId;
+
+ case ShapePageId:
+ if ( field( "shape.rect" ).toBool() )
+ {
+ return RectPageId;
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ return RoundPageId;
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ return EllipsePageId;
+ }
+ else
+ {
+ return CdPageId;
+ }
+
+ case RectPageId:
+ return NLayoutsPageId;
+
+ case RoundPageId:
+ return NLayoutsPageId;
+
+ case EllipsePageId:
+ return NLayoutsPageId;
+
+ case CdPageId:
+ return NLayoutsPageId;
+
+ case NLayoutsPageId:
+ if ( field( "nLayouts.one" ).toBool() )
+ {
+ return OneLayoutPageId;
+ }
+ else
+ {
+ return TwoLayoutPageId;
+ }
+
+ case OneLayoutPageId:
+ return ApplyPageId;
+
+ case TwoLayoutPageId:
+ return ApplyPageId;
+
+ case ApplyPageId:
+ default:
+ return -1;
+ }
+ }
+
+
+ ///
+ /// Determine width of individual item
+ ///
+ double TemplateDesigner::itemWidth()
+ {
+ // Note: all distance units are the same in wizard, so no conversions needed
+ if ( field( "shape.rect" ).toBool() )
+ {
+ return field( "rect.w" ).toDouble();
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ return 2 * field( "round.r" ).toDouble();
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ return field( "ellipse.w" ).toDouble();
+ }
+ else
+ {
+ if ( field( "cd.xClip" ).toDouble() == 0 )
+ {
+ return 2 * field( "cd.r1" ).toDouble();
+ }
+ else
+ {
+ return field( "cd.xClip" ).toDouble();
+ }
+ }
+ }
+
+
+ ///
+ /// Determine height of individual item
+ ///
+ double TemplateDesigner::itemHeight()
+ {
+ // Note: all distance units are the same in wizard, so no conversions needed
+ if ( field( "shape.rect" ).toBool() )
+ {
+ return field( "rect.h" ).toDouble();
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ return 2 * field( "round.r" ).toDouble();
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ return field( "ellipse.h" ).toDouble();
+ }
+ else
+ {
+ if ( field( "cd.xClip" ).toDouble() == 0 )
+ {
+ return 2 * field( "cd.r1" ).toDouble();
+ }
+ else
+ {
+ return field( "cd.yClip" ).toDouble();
+ }
+ }
+ }
+
+
+ ///
+ /// Determine X Waste of individual item
+ ///
+ double TemplateDesigner::itemXWaste()
+ {
+ // Note: all distance units are the same in wizard, so no conversions needed
+ if ( field( "shape.rect" ).toBool() )
+ {
+ return field( "rect.xWaste" ).toDouble();
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ return 2 * field( "round.waste" ).toDouble();
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ return field( "ellipse.waste" ).toDouble();
+ }
+ else
+ {
+ return field( "cd.waste" ).toDouble();
+ }
+ }
+
+
+ ///
+ /// Determine Y Waste of individual item
+ ///
+ double TemplateDesigner::itemYWaste()
+ {
+ // Note: all distance units are the same in wizard, so no conversions needed
+ if ( field( "shape.rect" ).toBool() )
+ {
+ return field( "rect.yWaste" ).toDouble();
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ return 2 * field( "round.waste" ).toDouble();
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ return field( "ellipse.waste" ).toDouble();
+ }
+ else
+ {
+ return field( "cd.waste" ).toDouble();
+ }
+ }
+
+
+ ///
+ /// Build template from wizard pages
+ ///
+ model::Template* TemplateDesigner::buildTemplate()
+ {
+ model::Units units = model::Settings::units();
+
+ QString brand = field( "name.brand" ).toString();
+ QString part = field( "name.part" ).toString();
+ QString description = field( "name.description" ).toString();
+ QString paperId = model::Db::lookupPaperIdFromName( field( "pageSize.pageSize" ).toString() );
+ model::Distance pageW( field( "pageSize.w" ).toDouble(), units );
+ model::Distance pageH( field( "pageSize.h" ).toDouble(), units );
+
+ auto t = new model::Template( brand, part, description, paperId, pageW, pageH, true );
+
+ model::Frame* frame;
+ if ( field( "shape.rect" ).toBool() )
+ {
+ model::Distance w( field( "rect.w" ).toDouble(), units );
+ model::Distance h( field( "rect.h" ).toDouble(), units );
+ model::Distance r( field( "rect.r" ).toDouble(), units );
+ model::Distance xWaste( field( "rect.xWaste" ).toDouble(), units );
+ model::Distance yWaste( field( "rect.yWaste" ).toDouble(), units );
+ model::Distance margin( field( "rect.margin" ).toDouble(), units );
+
+ frame = new model::FrameRect( w, h, r, xWaste, yWaste );
+ frame->addMarkup( new model::MarkupMargin( frame, margin ) );
+ }
+ else if ( field( "shape.round" ).toBool() )
+ {
+ model::Distance r( field( "round.r" ).toDouble(), units );
+ model::Distance waste( field( "round.waste" ).toDouble(), units );
+ model::Distance margin( field( "round.margin" ).toDouble(), units );
+
+ frame = new model::FrameRound( r, waste );
+ frame->addMarkup( new model::MarkupMargin( frame, margin ) );
+ }
+ else if ( field( "shape.ellipse" ).toBool() )
+ {
+ model::Distance w( field( "ellipse.w" ).toDouble(), units );
+ model::Distance h( field( "ellipse.h" ).toDouble(), units );
+ model::Distance waste( field( "ellipse.waste" ).toDouble(), units );
+ model::Distance margin( field( "ellipse.margin" ).toDouble(), units );
+
+ frame = new model::FrameEllipse( w, h, waste );
+ frame->addMarkup( new model::MarkupMargin( frame, margin ) );
+ }
+ else
+ {
+ model::Distance r1( field( "cd.r1" ).toDouble(), units );
+ model::Distance r2( field( "cd.r2" ).toDouble(), units );
+ model::Distance xClip( field( "cd.xClip" ).toDouble(), units );
+ model::Distance yClip( field( "cd.yClip" ).toDouble(), units );
+ model::Distance waste( field( "cd.waste" ).toDouble(), units );
+ model::Distance margin( field( "cd.margin" ).toDouble(), units );
+
+ frame = new model::FrameCd( r1, r2, xClip, yClip, waste );
+ frame->addMarkup( new model::MarkupMargin( frame, margin ) );
+ }
+ t->addFrame( frame );
+
+ if ( field( "nLayouts.one" ).toBool() )
+ {
+ int nx = field( "oneLayout.nx" ).toInt();
+ int ny = field( "oneLayout.ny" ).toInt();
+ model::Distance x0( field( "oneLayout.x0" ).toDouble(), units );
+ model::Distance y0( field( "oneLayout.y0" ).toDouble(), units );
+ model::Distance dx( field( "oneLayout.dx" ).toDouble(), units );
+ model::Distance dy( field( "oneLayout.dy" ).toDouble(), units );
+
+ frame->addLayout( new model::Layout( nx, ny, x0, y0, dx, dy ) );
+ }
+ else
+ {
+ int nx1 = field( "twoLayout.nx1" ).toInt();
+ int ny1 = field( "twoLayout.ny1" ).toInt();
+ model::Distance x01( field( "twoLayout.x01" ).toDouble(), units );
+ model::Distance y01( field( "twoLayout.y01" ).toDouble(), units );
+ model::Distance dx1( field( "twoLayout.dx1" ).toDouble(), units );
+ model::Distance dy1( field( "twoLayout.dy1" ).toDouble(), units );
+
+ int nx2 = field( "twoLayout.nx2" ).toInt();
+ int ny2 = field( "twoLayout.ny2" ).toInt();
+ model::Distance x02( field( "twoLayout.x02" ).toDouble(), units );
+ model::Distance y02( field( "twoLayout.y02" ).toDouble(), units );
+ model::Distance dx2( field( "twoLayout.dx2" ).toDouble(), units );
+ model::Distance dy2( field( "twoLayout.dy2" ).toDouble(), units );
+
+ frame->addLayout( new model::Layout( nx1, ny1, x01, y01, dx1, dy1 ) );
+ frame->addLayout( new model::Layout( nx2, ny2, x02, y02, dx2, dy2 ) );
+ }
+
+ return t;
+ }
+
+
+ ///
+ /// Print test sheet
+ ///
+ void TemplateDesigner::printTestSheet()
+ {
+ auto sheet = new model::Model();
+ sheet->setTmplate( buildTemplate() );
+
+ model::PageRenderer renderer( sheet );
+ renderer.setNCopies( sheet->frame()->nLabels() );
+ renderer.setStartLabel( 0 );
+ renderer.setPrintOutlines( true );
+
+ QPrinter printer( QPrinter::HighResolution );
+
+ QPrintDialog printDialog( &printer, this );
+ printDialog.setOption( QAbstractPrintDialog::PrintToFile, true );
+ printDialog.setOption( QAbstractPrintDialog::PrintSelection, false );
+ printDialog.setOption( QAbstractPrintDialog::PrintPageRange, false );
+ printDialog.setOption( QAbstractPrintDialog::PrintShowPageSize, true );
+ printDialog.setOption( QAbstractPrintDialog::PrintCollateCopies, false );
+ printDialog.setOption( QAbstractPrintDialog::PrintCurrentPage, false );
+
+ if ( printDialog.exec() == QDialog::Accepted )
+ {
+ renderer.print( &printer );
+ }
+
+ delete sheet;
+ }
+
+
+ ///
+ /// Load wizard from template
+ ///
+ void TemplateDesigner::loadFromTemplate( const model::Template* tmplate )
+ {
+ mIsBasedOnCopy = true;
+
+ model::Units units = model::Settings::units();
+
+ setField( "name.brand", tmplate->brand() );
+ setField( "name.part", tmplate->part() + QString(" (%1)").arg( tr("Copy") ) );
+ setField( "name.description", tmplate->description() );
+
+ setField( "pageSize.pageSize", model::Db::lookupPaperNameFromId( tmplate->paperId() ) );
+ setField( "pageSize.w", tmplate->pageWidth().inUnits( units ) );
+ setField( "pageSize.h", tmplate->pageHeight().inUnits( units ) );
+
+ const model::Frame* frame = tmplate->frames().first();
+ if ( auto frameRect = dynamic_cast( frame ) )
+ {
+ setField( "shape.rect", true );
+
+ setField( "rect.w", frameRect->w().inUnits( units ) );
+ setField( "rect.h", frameRect->h().inUnits( units ) );
+ setField( "rect.r", frameRect->r().inUnits( units ) );
+ setField( "rect.xWaste", frameRect->xWaste().inUnits( units ) );
+ setField( "rect.yWaste", frameRect->yWaste().inUnits( units ) );
+ }
+ else if ( auto frameRound = dynamic_cast( frame ) )
+ {
+ setField( "shape.round", true );
+
+ setField( "round.r", frameRound->r().inUnits( units ) );
+ setField( "round.waste", frameRound->waste().inUnits( units ) );
+ }
+ else if ( auto frameEllipse = dynamic_cast( frame ) )
+ {
+ setField( "shape.ellipse", true );
+
+ setField( "ellipse.w", frameEllipse->w().inUnits( units ) );
+ setField( "ellipse.h", frameEllipse->h().inUnits( units ) );
+ setField( "ellipse.waste", frameEllipse->waste().inUnits( units ) );
+ }
+ else if ( auto frameCd = dynamic_cast( frame ) )
+ {
+ setField( "shape.cd", true );
+
+ setField( "cd.r1", frameCd->r1().inUnits( units ) );
+ setField( "cd.r2", frameCd->r2().inUnits( units ) );
+ setField( "cd.xClip", frameCd->w().inUnits( units ) );
+ setField( "cd.yClip", frameCd->h().inUnits( units ) );
+ setField( "cd.waste", frameCd->waste().inUnits( units ) );
+ }
+
+ foreach( auto markup, frame->markups() )
+ {
+ if ( auto markupMargin = dynamic_cast( markup ) )
+ {
+ setField( "rect.margin", markupMargin->size().inUnits( units ) );
+ setField( "round.margin", markupMargin->size().inUnits( units ) );
+ setField( "ellipse.margin", markupMargin->size().inUnits( units ) );
+ setField( "cd.margin", markupMargin->size().inUnits( units ) );
+ }
+ }
+
+ QList layouts = frame->layouts();
+ if ( layouts.size() == 1 )
+ {
+ setField( "oneLayout.nx", layouts[0]->nx() );
+ setField( "oneLayout.ny", layouts[0]->ny() );
+ setField( "oneLayout.x0", layouts[0]->x0().inUnits( units ) );
+ setField( "oneLayout.y0", layouts[0]->y0().inUnits( units ) );
+ setField( "oneLayout.dx", layouts[0]->dx().inUnits( units ) );
+ setField( "oneLayout.dy", layouts[0]->dy().inUnits( units ) );
+ }
+ else if ( layouts.size() > 1 )
+ {
+ setField( "twoLayout.nx1", layouts[0]->nx() );
+ setField( "twoLayout.ny1", layouts[0]->ny() );
+ setField( "twoLayout.x01", layouts[0]->x0().inUnits( units ) );
+ setField( "twoLayout.y01", layouts[0]->y0().inUnits( units ) );
+ setField( "twoLayout.dx1", layouts[0]->dx().inUnits( units ) );
+ setField( "twoLayout.dy1", layouts[0]->dy().inUnits( units ) );
+
+ setField( "twoLayout.nx2", layouts[1]->nx() );
+ setField( "twoLayout.ny2", layouts[1]->ny() );
+ setField( "twoLayout.x02", layouts[1]->x0().inUnits( units ) );
+ setField( "twoLayout.y02", layouts[1]->y0().inUnits( units ) );
+ setField( "twoLayout.dx2", layouts[1]->dx().inUnits( units ) );
+ setField( "twoLayout.dy2", layouts[1]->dy().inUnits( units ) );
+ }
+ }
+
+
+ ///
+ /// Is the wizard based on a copy?
+ ///
+ bool TemplateDesigner::isBasedOnCopy()
+ {
+ return mIsBasedOnCopy;
+ }
+
+
+ ///
+ /// Intro Page
+ ///
+ TemplateDesignerIntroPage::TemplateDesignerIntroPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Welcome") );
+ setSubTitle( tr("Welcome to the gLabels Product Template Designer.") );
+ setPixmap( QWizard::WatermarkPixmap, QPixmap( ":images/TemplateDesigner/wizard-banner.png" ) );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ connect( copyButton, &QCommandLinkButton::clicked, this, &TemplateDesignerIntroPage::onCopyButtonClicked );
+ connect( newButton, &QCommandLinkButton::clicked, this, &TemplateDesignerIntroPage::onNewButtonClicked );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ bool TemplateDesignerIntroPage::isComplete() const
+ {
+ // Use CommandLinkButtons on intro page to advance
+ return false;
+ }
+
+
+ void TemplateDesignerIntroPage::onCopyButtonClicked()
+ {
+ SelectProductDialog dialog;
+ dialog.exec();
+
+ const model::Template* tmplate = dialog.tmplate();
+ if ( tmplate )
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ td->loadFromTemplate( tmplate );
+ td->next();
+ }
+ }
+ }
+
+
+ void TemplateDesignerIntroPage::onNewButtonClicked()
+ {
+ wizard()->next();
+ }
+
+
+ ///
+ /// Name and Description Page
+ ///
+ TemplateDesignerNamePage::TemplateDesignerNamePage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Name and Description") );
+ setSubTitle( tr("Please enter the following identifying information about the product.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ warningLabel->setText( "" );
+
+ registerField( "name.brand", brandEntry );
+ registerField( "name.part", partEntry );
+ registerField( "name.description", descriptionEntry );
+
+ connect( brandEntry, &QLineEdit::textChanged, this, &TemplateDesignerNamePage::onChanged );
+ connect( partEntry, &QLineEdit::textChanged, this, &TemplateDesignerNamePage::onChanged );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ bool TemplateDesignerNamePage::isComplete() const
+ {
+ return mCanContinue;
+ }
+
+
+ void TemplateDesignerNamePage::onChanged()
+ {
+ bool isDuplicate = model::Db::isSystemTemplateKnown( brandEntry->text(), partEntry->text() );
+ if ( isDuplicate )
+ {
+ QString warningText = tr("Brand and part number match an existing built-in product template!");
+ warningLabel->setText( "" + warningText + "" );
+ }
+ else
+ {
+ warningLabel->setText( "" );
+ }
+
+ mCanContinue = !brandEntry->text().isEmpty() && !partEntry->text().isEmpty() && !isDuplicate;
+ emit completeChanged();
+ }
+
+
+ ///
+ /// Page Size Page
+ ///
+ TemplateDesignerPageSizePage::TemplateDesignerPageSizePage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Page Size") );
+ setSubTitle( tr("Please select the product page size.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ pageSizeCombo->insertItem( 0, tr("Other") );
+ pageSizeCombo->insertItems( 1, model::Db::paperNames() );
+ pageSizeCombo->setCurrentText( defaultPageSize[ model::Settings::preferedPageSizeFamily() ] );
+
+ wSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wSpin->setSingleStep( model::Settings::units().resolution() );
+ wSpin->setMaximum( maxPageSize[ model::Settings::units().toEnum() ] );
+ wSpin->setEnabled( pageSizeCombo->currentText() == tr("Other") );
+
+ hSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ hSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ hSpin->setSingleStep( model::Settings::units().resolution() );
+ hSpin->setMaximum( maxPageSize[ model::Settings::units().toEnum() ] );
+ hSpin->setEnabled( pageSizeCombo->currentText() == tr("Other") );
+
+ if ( pageSizeCombo->currentText() != tr("Other") )
+ {
+ const model::Paper* paper = model::Db::lookupPaperFromName( pageSizeCombo->currentText() );
+ wSpin->setValue( paper->width().inUnits( model::Settings::units() ) );
+ hSpin->setValue( paper->height().inUnits( model::Settings::units() ) );
+ }
+
+ registerField( "pageSize.pageSize", pageSizeCombo, "currentText" );
+ registerField( "pageSize.w", wSpin, "value" );
+ registerField( "pageSize.h", hSpin, "value" );
+
+ connect( pageSizeCombo, &QComboBox::currentTextChanged, this, &TemplateDesignerPageSizePage::onComboChanged );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerPageSizePage::initializePage()
+ {
+ }
+
+
+ void TemplateDesignerPageSizePage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ void TemplateDesignerPageSizePage::onComboChanged()
+ {
+ if ( pageSizeCombo->currentText() != tr("Other") )
+ {
+ const model::Paper* paper = model::Db::lookupPaperFromName( pageSizeCombo->currentText() );
+ wSpin->setValue( paper->width().inUnits( model::Settings::units() ) );
+ hSpin->setValue( paper->height().inUnits( model::Settings::units() ) );
+ }
+
+ wSpin->setEnabled( pageSizeCombo->currentText() == tr("Other") );
+ hSpin->setEnabled( pageSizeCombo->currentText() == tr("Other") );
+ }
+
+
+ ///
+ /// Shape Page
+ ///
+ TemplateDesignerShapePage::TemplateDesignerShapePage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Product Shape") );
+ setSubTitle( tr("Please select the basic product shape.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ registerField( "shape.rect", rectRadio );
+ registerField( "shape.round", roundRadio );
+ registerField( "shape.ellipse", ellipseRadio );
+ registerField( "shape.cd", cdRadio );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerShapePage::initializePage()
+ {
+ }
+
+
+ void TemplateDesignerShapePage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// Rectangular Product Page
+ ///
+ TemplateDesignerRectPage::TemplateDesignerRectPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Product Size") );
+ setSubTitle( tr("Please adjust the size parameters of a single product item.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ wSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wSpin->setSingleStep( model::Settings::units().resolution() );
+
+ hSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ hSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ hSpin->setSingleStep( model::Settings::units().resolution() );
+
+ rSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ rSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ rSpin->setSingleStep( model::Settings::units().resolution() );
+
+ xWasteSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ xWasteSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ xWasteSpin->setSingleStep( model::Settings::units().resolution() );
+
+ yWasteSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ yWasteSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ yWasteSpin->setSingleStep( model::Settings::units().resolution() );
+
+ marginSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ marginSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ marginSpin->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "rect.w", wSpin, "value" );
+ registerField( "rect.h", hSpin, "value" );
+ registerField( "rect.r", rSpin, "value" );
+ registerField( "rect.xWaste", xWasteSpin, "value" );
+ registerField( "rect.yWaste", yWasteSpin, "value" );
+ registerField( "rect.margin", marginSpin, "value" );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerRectPage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen page size
+ double wMax = field("pageSize.w").toDouble();
+ double hMax = field("pageSize.h").toDouble();
+
+ wSpin->setMaximum( wMax );
+ hSpin->setMaximum( hMax );
+ rSpin->setMaximum( std::min(wMax,hMax)/2.0 );
+ xWasteSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ yWasteSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ marginSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults
+ wSpin->setValue( defaultRectW.inUnits( model::Settings::units() ) );
+ hSpin->setValue( defaultRectH.inUnits( model::Settings::units() ) );
+ rSpin->setValue( defaultRectR.inUnits( model::Settings::units() ) );
+ xWasteSpin->setValue( defaultWaste.inUnits( model::Settings::units() ) );
+ yWasteSpin->setValue( defaultWaste.inUnits( model::Settings::units() ) );
+ marginSpin->setValue( defaultMargin.inUnits( model::Settings::units() ) );
+ }
+ }
+ }
+
+
+ void TemplateDesignerRectPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// Round Product Page
+ ///
+ TemplateDesignerRoundPage::TemplateDesignerRoundPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Product Size") );
+ setSubTitle( tr("Please adjust the size parameters of a single product item.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ rSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ rSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ rSpin->setSingleStep( model::Settings::units().resolution() );
+
+ wasteSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wasteSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wasteSpin->setSingleStep( model::Settings::units().resolution() );
+
+ marginSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ marginSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ marginSpin->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "round.r", rSpin, "value" );
+ registerField( "round.waste", wasteSpin, "value" );
+ registerField( "round.margin", marginSpin, "value" );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerRoundPage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen page size
+ double wMax = field("pageSize.w").toDouble();
+ double hMax = field("pageSize.h").toDouble();
+
+ rSpin->setMaximum( std::min(wMax,hMax)/2.0 );
+ wasteSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ marginSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults
+ rSpin->setValue( defaultRoundR.inUnits( model::Settings::units() ) );
+ wasteSpin->setValue( defaultWaste.inUnits( model::Settings::units() ) );
+ marginSpin->setValue( defaultMargin.inUnits( model::Settings::units() ) );
+ }
+ }
+ }
+
+
+ void TemplateDesignerRoundPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// Elliptical Product Page
+ ///
+ TemplateDesignerEllipsePage::TemplateDesignerEllipsePage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Product Size") );
+ setSubTitle( tr("Please adjust the size parameters of a single product item.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ wSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wSpin->setSingleStep( model::Settings::units().resolution() );
+
+ hSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ hSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ hSpin->setSingleStep( model::Settings::units().resolution() );
+
+ wasteSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wasteSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wasteSpin->setSingleStep( model::Settings::units().resolution() );
+
+ marginSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ marginSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ marginSpin->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "ellipse.w", wSpin, "value" );
+ registerField( "ellipse.h", hSpin, "value" );
+ registerField( "ellipse.waste", wasteSpin, "value" );
+ registerField( "ellipse.margin", marginSpin, "value" );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerEllipsePage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen page size
+ double wMax = field("pageSize.w").toDouble();
+ double hMax = field("pageSize.h").toDouble();
+
+ wSpin->setMaximum( wMax );
+ hSpin->setMaximum( hMax );
+ wasteSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ marginSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults
+ wSpin->setValue( defaultEllipseW.inUnits( model::Settings::units() ) );
+ hSpin->setValue( defaultEllipseH.inUnits( model::Settings::units() ) );
+ wasteSpin->setValue( defaultWaste.inUnits( model::Settings::units() ) );
+ marginSpin->setValue( defaultMargin.inUnits( model::Settings::units() ) );
+ }
+ }
+ }
+
+
+ void TemplateDesignerEllipsePage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// CD/DVD Product Page
+ ///
+ TemplateDesignerCdPage::TemplateDesignerCdPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Product Size") );
+ setSubTitle( tr("Please adjust the size parameters of a single product item.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ r1Spin->setSuffix( " " + model::Settings::units().toTrName() );
+ r1Spin->setDecimals( model::Settings::units().resolutionDigits() );
+ r1Spin->setSingleStep( model::Settings::units().resolution() );
+
+ r2Spin->setSuffix( " " + model::Settings::units().toTrName() );
+ r2Spin->setDecimals( model::Settings::units().resolutionDigits() );
+ r2Spin->setSingleStep( model::Settings::units().resolution() );
+
+ xClipSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ xClipSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ xClipSpin->setSingleStep( model::Settings::units().resolution() );
+
+ yClipSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ yClipSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ yClipSpin->setSingleStep( model::Settings::units().resolution() );
+
+ wasteSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ wasteSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ wasteSpin->setSingleStep( model::Settings::units().resolution() );
+
+ marginSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ marginSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ marginSpin->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "cd.r1", r1Spin, "value" );
+ registerField( "cd.r2", r2Spin, "value" );
+ registerField( "cd.xClip", xClipSpin, "value" );
+ registerField( "cd.yClip", yClipSpin, "value" );
+ registerField( "cd.waste", wasteSpin, "value" );
+ registerField( "cd.margin", marginSpin, "value" );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerCdPage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen page size
+ double wMax = field("pageSize.w").toDouble();
+ double hMax = field("pageSize.h").toDouble();
+
+ r1Spin->setMaximum( std::min(wMax,hMax)/2.0 );
+ r2Spin->setMaximum( std::min(wMax,hMax)/4.0 );
+ xClipSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ yClipSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ wasteSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+ marginSpin->setMaximum( std::min(wMax,hMax)/4.0 );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults
+ r1Spin->setValue( defaultCdR1.inUnits( model::Settings::units() ) );
+ r2Spin->setValue( defaultCdR2.inUnits( model::Settings::units() ) );
+ xClipSpin->setValue( defaultCdClip.inUnits( model::Settings::units() ) );
+ yClipSpin->setValue( defaultCdClip.inUnits( model::Settings::units() ) );
+ wasteSpin->setValue( defaultWaste.inUnits( model::Settings::units() ) );
+ marginSpin->setValue( defaultMargin.inUnits( model::Settings::units() ) );
+ }
+ }
+ }
+
+
+ void TemplateDesignerCdPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// Number of Layouts Page
+ ///
+ TemplateDesignerNLayoutsPage::TemplateDesignerNLayoutsPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Number of Layouts") );
+ setSubTitle( tr("Please select the number of layouts required.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ registerField( "nLayouts.one", oneLayoutRadio );
+ registerField( "nLayouts.two", twoLayoutsRadio );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerNLayoutsPage::initializePage()
+ {
+ }
+
+
+ void TemplateDesignerNLayoutsPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ ///
+ /// One Layout Page
+ ///
+ TemplateDesignerOneLayoutPage::TemplateDesignerOneLayoutPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Layout") );
+ setSubTitle( tr("Please enter parameters for your single layout.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ x0Spin->setSuffix( " " + model::Settings::units().toTrName() );
+ x0Spin->setDecimals( model::Settings::units().resolutionDigits() );
+ x0Spin->setSingleStep( model::Settings::units().resolution() );
+
+ y0Spin->setSuffix( " " + model::Settings::units().toTrName() );
+ y0Spin->setDecimals( model::Settings::units().resolutionDigits() );
+ y0Spin->setSingleStep( model::Settings::units().resolution() );
+
+ dxSpin->setSuffix( " " + model::Settings::units().toTrName() );
+ dxSpin->setDecimals( model::Settings::units().resolutionDigits() );
+ dxSpin->setSingleStep( model::Settings::units().resolution() );
+
+ dySpin->setSuffix( " " + model::Settings::units().toTrName() );
+ dySpin->setDecimals( model::Settings::units().resolutionDigits() );
+ dySpin->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "oneLayout.nx", nxSpin );
+ registerField( "oneLayout.ny", nySpin );
+ registerField( "oneLayout.x0", x0Spin, "value" );
+ registerField( "oneLayout.y0", y0Spin, "value" );
+ registerField( "oneLayout.dx", dxSpin, "value" );
+ registerField( "oneLayout.dy", dySpin, "value" );
+
+ connect( nxSpin, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( nySpin, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( x0Spin, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( y0Spin, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dxSpin, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dySpin, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+
+ connect( printButton, SIGNAL(clicked()), this, SLOT(onPrintButtonClicked()) );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerOneLayoutPage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen values
+ double pageW = field("pageSize.w").toDouble();
+ double pageH = field("pageSize.h").toDouble();
+ double w = td->itemWidth();
+ double h = td->itemHeight();
+ double xWaste = td->itemXWaste();
+ double yWaste = td->itemYWaste();
+
+ int nxMax = std::max( pageW/(w + 2*xWaste), 1.0 );
+ int nyMax = std::max( pageH/(h + 2*yWaste), 1.0 );
+ double x0Min = xWaste;
+ double x0Max = pageW - w - 2*xWaste;
+ double y0Min = yWaste;
+ double y0Max = pageH - h - 2*yWaste;
+ double dxMin = w + 2*xWaste;
+ double dxMax = pageW - w - 2*xWaste;
+ double dyMin = h + 2*yWaste;
+ double dyMax = pageH - h - 2*yWaste;
+
+ nxSpin->setRange( 1, nxMax );
+ nySpin->setRange( 1, nyMax );
+ x0Spin->setRange( x0Min, x0Max );
+ y0Spin->setRange( y0Min, y0Max );
+ dxSpin->setRange( dxMin, dxMax );
+ dySpin->setRange( dyMin, dxMax );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults based on symetric sheet using previosly chosen values
+ nxSpin->setValue( nxMax );
+ nySpin->setValue( nyMax );
+ x0Spin->setValue( (pageW - (nxMax-1)*dxMin - w) / 2 );
+ y0Spin->setValue( (pageH - (nyMax-1)*dyMin - h) / 2 );
+ dxSpin->setValue( dxMin );
+ dySpin->setValue( dyMin );
+ }
+
+ preview->setTemplate( td->buildTemplate() );
+ }
+ }
+
+
+ void TemplateDesignerOneLayoutPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ void TemplateDesignerOneLayoutPage::onChanged()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ preview->setTemplate( td->buildTemplate() );
+ }
+ }
+
+
+ void TemplateDesignerOneLayoutPage::onPrintButtonClicked()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ td->printTestSheet();
+ }
+ }
+
+
+ ///
+ /// Two Layout Page
+ ///
+ TemplateDesignerTwoLayoutPage::TemplateDesignerTwoLayoutPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Layouts") );
+ setSubTitle( tr("Please enter parameters for your two layouts.") );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ x0Spin1->setSuffix( " " + model::Settings::units().toTrName() );
+ x0Spin1->setDecimals( model::Settings::units().resolutionDigits() );
+ x0Spin1->setSingleStep( model::Settings::units().resolution() );
+
+ y0Spin1->setSuffix( " " + model::Settings::units().toTrName() );
+ y0Spin1->setDecimals( model::Settings::units().resolutionDigits() );
+ y0Spin1->setSingleStep( model::Settings::units().resolution() );
+
+ dxSpin1->setSuffix( " " + model::Settings::units().toTrName() );
+ dxSpin1->setDecimals( model::Settings::units().resolutionDigits() );
+ dxSpin1->setSingleStep( model::Settings::units().resolution() );
+
+ dySpin1->setSuffix( " " + model::Settings::units().toTrName() );
+ dySpin1->setDecimals( model::Settings::units().resolutionDigits() );
+ dySpin1->setSingleStep( model::Settings::units().resolution() );
+
+ x0Spin2->setSuffix( " " + model::Settings::units().toTrName() );
+ x0Spin2->setDecimals( model::Settings::units().resolutionDigits() );
+ x0Spin2->setSingleStep( model::Settings::units().resolution() );
+
+ y0Spin2->setSuffix( " " + model::Settings::units().toTrName() );
+ y0Spin2->setDecimals( model::Settings::units().resolutionDigits() );
+ y0Spin2->setSingleStep( model::Settings::units().resolution() );
+
+ dxSpin2->setSuffix( " " + model::Settings::units().toTrName() );
+ dxSpin2->setDecimals( model::Settings::units().resolutionDigits() );
+ dxSpin2->setSingleStep( model::Settings::units().resolution() );
+
+ dySpin2->setSuffix( " " + model::Settings::units().toTrName() );
+ dySpin2->setDecimals( model::Settings::units().resolutionDigits() );
+ dySpin2->setSingleStep( model::Settings::units().resolution() );
+
+ registerField( "twoLayout.nx1", nxSpin1 );
+ registerField( "twoLayout.ny1", nySpin1 );
+ registerField( "twoLayout.x01", x0Spin1, "value" );
+ registerField( "twoLayout.y01", y0Spin1, "value" );
+ registerField( "twoLayout.dx1", dxSpin1, "value" );
+ registerField( "twoLayout.dy1", dySpin1, "value" );
+
+ registerField( "twoLayout.nx2", nxSpin2 );
+ registerField( "twoLayout.ny2", nySpin2 );
+ registerField( "twoLayout.x02", x0Spin2, "value" );
+ registerField( "twoLayout.y02", y0Spin2, "value" );
+ registerField( "twoLayout.dx2", dxSpin2, "value" );
+ registerField( "twoLayout.dy2", dySpin2, "value" );
+
+ connect( nxSpin1, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( nySpin1, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( x0Spin1, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( y0Spin1, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dxSpin1, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dySpin1, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+
+ connect( nxSpin2, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( nySpin2, SIGNAL(valueChanged(int)), this, SLOT(onChanged()) );
+ connect( x0Spin2, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( y0Spin2, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dxSpin2, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+ connect( dySpin2, SIGNAL(valueChanged(double)), this, SLOT(onChanged()) );
+
+ connect( printButton, SIGNAL(clicked()), this, SLOT(onPrintButtonClicked()) );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ void TemplateDesignerTwoLayoutPage::initializePage()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ // set realistic limits based on previously chosen values
+ double pageW = field("pageSize.w").toDouble();
+ double pageH = field("pageSize.h").toDouble();
+ double w = td->itemWidth();
+ double h = td->itemHeight();
+ double xWaste = td->itemXWaste();
+ double yWaste = td->itemYWaste();
+
+ int nxMax = std::max( pageW/(w + 2*xWaste), 1.0 );
+ int nyMax = std::max( pageH/(h + 2*yWaste), 1.0 );
+ double x0Min = xWaste;
+ double x0Max = pageW - w - 2*xWaste;
+ double y0Min = yWaste;
+ double y0Max = pageH - h - 2*yWaste;
+ double dxMin = w + 2*xWaste;
+ double dxMax = pageW - w - 2*xWaste;
+ double dyMin = h + 2*yWaste;
+ double dyMax = pageH - h - 2*yWaste;
+
+ nxSpin1->setRange( 1, nxMax );
+ nySpin1->setRange( 1, nyMax );
+ x0Spin1->setRange( x0Min, x0Max );
+ y0Spin1->setRange( y0Min, y0Max );
+ dxSpin1->setRange( dxMin, dxMax );
+ dySpin1->setRange( dyMin, dyMax );
+
+ nxSpin2->setRange( 1, nxMax );
+ nySpin2->setRange( 1, nyMax );
+ x0Spin2->setRange( x0Min, x0Max );
+ y0Spin2->setRange( y0Min, y0Max );
+ dxSpin2->setRange( dxMin, dxMax );
+ dySpin2->setRange( dyMin, dyMax );
+
+ static bool alreadyInitialized = false;
+ if ( !td->isBasedOnCopy() && !alreadyInitialized )
+ {
+ alreadyInitialized = true;
+
+ // Set some realistic defaults based on symetric sheet using previosly chosen values
+ nxSpin1->setValue( nxMax );
+ nySpin1->setValue( nyMax - nyMax/2 );
+ x0Spin1->setValue( (pageW - (nxMax-1)*dxMin - w) / 2 );
+ y0Spin1->setValue( (pageH - (nyMax-1)*dyMin - h) / 2 );
+ dxSpin1->setValue( dxMin );
+ dySpin1->setValue( 2*dyMin );
+
+ nxSpin2->setValue( nxMax );
+ nySpin2->setValue( nyMax/2 );
+ x0Spin2->setValue( (pageW - (nxMax-1)*dxMin - w) / 2 );
+ y0Spin2->setValue( (pageH - (nyMax-1)*dyMin - h) / 2 + dyMin );
+ dxSpin2->setValue( dxMin );
+ dySpin2->setValue( 2*dyMin );
+ }
+
+ preview->setTemplate( td->buildTemplate() );
+ }
+ }
+
+
+ void TemplateDesignerTwoLayoutPage::cleanupPage()
+ {
+ // Leave current settings alone
+ }
+
+
+ void TemplateDesignerTwoLayoutPage::onChanged()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ preview->setTemplate( td->buildTemplate() );
+ }
+ }
+
+
+ void TemplateDesignerTwoLayoutPage::onPrintButtonClicked()
+ {
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ td->printTestSheet();
+ }
+ }
+
+
+ ///
+ /// Apply Page
+ ///
+ TemplateDesignerApplyPage::TemplateDesignerApplyPage( QWidget* parent ) : QWizardPage(parent)
+ {
+ setTitle( tr("Save Product Template") );
+ setSubTitle( tr("Click \"Save\" to save your custom product template!") );
+
+ setFinalPage( true );
+ setButtonText( QWizard::FinishButton, "Save" );
+
+ QWidget* widget = new QWidget;
+ setupUi( widget );
+
+ QVBoxLayout* layout = new QVBoxLayout;
+ layout->addWidget( widget );
+ setLayout( layout );
+ }
+
+
+ bool TemplateDesignerApplyPage::validatePage()
+ {
+ //
+ // Save button pressed
+ //
+ QString brand = field( "name.brand" ).toString();
+ QString part = field( "name.part" ).toString();
+ QString filename = model::Db::userTemplateFilename( brand, part );
+
+ if ( QFileInfo::exists(filename) )
+ {
+ QMessageBox msgBox( wizard() );
+ msgBox.setWindowTitle( tr("Save Product Template") );
+ msgBox.setIcon( QMessageBox::Warning );
+ msgBox.setText( tr("User product template (%1 %2) already exists.").arg(brand).arg(part) );
+ msgBox.setInformativeText( tr("Do you want to replace it?") );
+ msgBox.setStandardButtons( QMessageBox::Yes | QMessageBox::No );
+ msgBox.setDefaultButton( QMessageBox::No );
+
+ if ( msgBox.exec() == QMessageBox::No )
+ {
+ return false;
+ }
+
+ model::Db::deleteUserTemplateByBrandPart( brand, part );
+ }
+
+ if ( auto td = dynamic_cast( wizard() ) )
+ {
+ model::Db::registerUserTemplate( td->buildTemplate() );
+ }
+ return true;
+ }
+
+
+} // namespace glabels
diff --git a/glabels/TemplateDesigner.h b/glabels/TemplateDesigner.h
new file mode 100644
index 0000000..15534f3
--- /dev/null
+++ b/glabels/TemplateDesigner.h
@@ -0,0 +1,289 @@
+/* TemplateDesigner.h
+ *
+ * Copyright (C) 2018 Jim Evins
+ *
+ * This file is part of gLabels-qt.
+ *
+ * gLabels-qt is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * gLabels-qt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gLabels-qt. If not, see .
+ */
+
+#ifndef TemplateDesigner_h
+#define TemplateDesigner_h
+
+
+#include "ui_TemplateDesignerIntroPage.h"
+#include "ui_TemplateDesignerNamePage.h"
+#include "ui_TemplateDesignerPageSizePage.h"
+#include "ui_TemplateDesignerShapePage.h"
+#include "ui_TemplateDesignerRectPage.h"
+#include "ui_TemplateDesignerRoundPage.h"
+#include "ui_TemplateDesignerEllipsePage.h"
+#include "ui_TemplateDesignerCdPage.h"
+#include "ui_TemplateDesignerNLayoutsPage.h"
+#include "ui_TemplateDesignerOneLayoutPage.h"
+#include "ui_TemplateDesignerTwoLayoutPage.h"
+#include "ui_TemplateDesignerApplyPage.h"
+
+#include "model/Template.h"
+
+#include
+#include
+
+
+namespace glabels
+{
+ ///
+ /// About Dialog Widget
+ ///
+ class TemplateDesigner : public QWizard
+ {
+ Q_OBJECT
+
+ // My subpages are my friends :-)
+ friend class TemplateDesignerIntroPage;
+ friend class TemplateDesignerNamePage;
+ friend class TemplateDesignerPageSizePage;
+ friend class TemplateDesignerShapePage;
+ friend class TemplateDesignerRectPage;
+ friend class TemplateDesignerRoundPage;
+ friend class TemplateDesignerEllipsePage;
+ friend class TemplateDesignerCdPage;
+ friend class TemplateDesignerNLayoutsPage;
+ friend class TemplateDesignerOneLayoutPage;
+ friend class TemplateDesignerTwoLayoutPage;
+ friend class TemplateDesignerApplyPage;
+
+
+ /////////////////////////////////
+ // Life Cycle
+ /////////////////////////////////
+ public:
+ TemplateDesigner( QWidget *parent = nullptr );
+
+
+ /////////////////////////////////
+ // Private methods
+ /////////////////////////////////
+ private:
+ int nextId() const override;
+
+ double itemWidth();
+ double itemHeight();
+ double itemXWaste();
+ double itemYWaste();
+ model::Template* buildTemplate();
+ void printTestSheet();
+ void loadFromTemplate( const model::Template* tmplate );
+ bool isBasedOnCopy();
+
+
+ /////////////////////////////////
+ // Private methods
+ /////////////////////////////////
+ private:
+ bool mIsBasedOnCopy;
+ };
+
+
+ //
+ // Intro Page
+ //
+ class TemplateDesignerIntroPage : public QWizardPage, public Ui::TemplateDesignerIntroPage
+ {
+ Q_OBJECT
+
+ public:
+ TemplateDesignerIntroPage( QWidget* parent = nullptr );
+
+ bool isComplete() const override;
+
+ private slots:
+ void onCopyButtonClicked();
+ void onNewButtonClicked();
+ };
+
+
+ //
+ // Name Page
+ //
+ class TemplateDesignerNamePage : public QWizardPage, public Ui::TemplateDesignerNamePage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerNamePage( QWidget* parent = nullptr );
+
+ bool isComplete() const override;
+
+ private slots:
+ void onChanged();
+
+ private:
+ bool mCanContinue = false;
+ };
+
+
+ //
+ // Page Size Page
+ //
+ class TemplateDesignerPageSizePage : public QWizardPage, public Ui::TemplateDesignerPageSizePage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerPageSizePage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+
+ private slots:
+ void onComboChanged();
+ };
+
+
+ //
+ // Shape Page
+ //
+ class TemplateDesignerShapePage : public QWizardPage, public Ui::TemplateDesignerShapePage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerShapePage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // Rect Page
+ //
+ class TemplateDesignerRectPage : public QWizardPage, public Ui::TemplateDesignerRectPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerRectPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // Round Page
+ //
+ class TemplateDesignerRoundPage : public QWizardPage, public Ui::TemplateDesignerRoundPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerRoundPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // Ellipse Page
+ //
+ class TemplateDesignerEllipsePage : public QWizardPage, public Ui::TemplateDesignerEllipsePage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerEllipsePage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // Cd Page
+ //
+ class TemplateDesignerCdPage : public QWizardPage, public Ui::TemplateDesignerCdPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerCdPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // NLayouts Page
+ //
+ class TemplateDesignerNLayoutsPage : public QWizardPage, public Ui::TemplateDesignerNLayoutsPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerNLayoutsPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+ };
+
+
+ //
+ // OneLayout Page
+ //
+ class TemplateDesignerOneLayoutPage : public QWizardPage, public Ui::TemplateDesignerOneLayoutPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerOneLayoutPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+
+ private slots:
+ void onChanged();
+ void onPrintButtonClicked();
+ };
+
+
+ //
+ // TwoLayout Page
+ //
+ class TemplateDesignerTwoLayoutPage : public QWizardPage, public Ui::TemplateDesignerTwoLayoutPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerTwoLayoutPage( QWidget* parent = nullptr );
+
+ void initializePage() override;
+ void cleanupPage() override;
+
+ private slots:
+ void onChanged();
+ void onPrintButtonClicked();
+ };
+
+
+ //
+ // Apply Page
+ //
+ class TemplateDesignerApplyPage : public QWizardPage, public Ui::TemplateDesignerApplyPage
+ {
+ Q_OBJECT
+ public:
+ TemplateDesignerApplyPage( QWidget* parent = nullptr );
+
+ bool validatePage();
+ };
+
+
+}
+
+
+#endif // TemplateDesigner_h
diff --git a/glabels/images.qrc b/glabels/images.qrc
index 315a144..9b6b53b 100644
--- a/glabels/images.qrc
+++ b/glabels/images.qrc
@@ -2,8 +2,19 @@
+
images/glabels-label-designer.png
images/glabels-logo.png
+
images/checkerboard.png
+
+ images/TemplateDesigner/wizard-banner.png
+ images/TemplateDesigner/ex-1layout.png
+ images/TemplateDesigner/ex-2layouts.png
+ images/TemplateDesigner/ex-cd-size.png
+ images/TemplateDesigner/ex-ellipse-size.png
+ images/TemplateDesigner/ex-rect-size.png
+ images/TemplateDesigner/ex-round-size.png
+
diff --git a/glabels/images/TemplateDesigner/ex-1layout.png b/glabels/images/TemplateDesigner/ex-1layout.png
new file mode 100644
index 0000000000000000000000000000000000000000..3045c5f231a11ae86e2d076d8e762a7ca2914618
GIT binary patch
literal 1567
zcmV+)2H^RLP)1c~
z2F5lvRuiIX3W+oZ7rV_C9^+wjwn&tCvx+TQI{IUu9~cdHtnXZXKDk$au8xAuP=N2`
zjZZIILj1=kegXJ7$d`zA$16oq0N@LNC4g@Lz9Z&j5Wj$*c)OKZQ~&&7
zU-S`pn{y+5lG0t#{sTZ2z;Adve_;13EQ7&-&d<+DRaG(!gPxwAXt7w3>$*fl6zU=Uj$IQ?21G>(oZrR%kG8MVo;FkD_<(&_0bJw85$+VAo4
zkxoxf>GJY2mVRvO*S?-(WfsSAP^nZZbA|(0}O{l
zj7B3gn@tw|X0wUWXoTT#$fECuBvuBvy}iZV-JN%rJ9ZRB!QI^*Zf|c{^!<>;N*Tv-
z@bK^u`syz%nx^65;UTfBL`kfa>2|xgxw%Os*3Hcgy4`Lfu_7myq9~C*N$X~fe!q`K
zqmf9gMx%j#zn@5~NY4t1=_x*!ZWggD3kL@WiNrcMIDlnYiNunrFlDCGDGG%`BC!gE
z0;bbxBC#TkSaD4`??)&u-7GSn&l8)v6-B{(K2Ic8#bOaelt_Px
z2*qNNJq{pKVd|7hB`lZAL}D$MOO#5bL}E!|rHrO&n9XL1#G1`!?Dxw&B(YMaUazCq
z>m?Ga*XyBPuO|{K2y35wN5xNrOV?{hhE_M&woR(4Mn2vksHz%z3C*^C$%vIQs;c7T
zuVN$KP0g-K&@6owOYmf{XL8R
z{rx?v)hcSW8jJq6tet$M|8=UA)k(hAZ@MnPWx3gy=(WX*ey;WR1^ziitk
zUDwGljMw)E!{WLw8HPc+u188LBDpHu8B|rpcszc6g)8jUqGegHua1q!a=atMpAT>yRASuTk
z8L2@ku&}E&NTnQi%6K(M4-XI7W96y_DG^2bB(0k@qz35+Prjq#r@^J`wIf5T8>9xQ
zz=G5u71)p(qyh_4gVbmKjf$TJm#){244q@GA}dG*7CM~{E-o%$nr19|rfK5h;sTvc
zClW2wl!t3jGa
z@|aj@A~i^bOc7{x!@exKiwp59SYmww@CASh;4}PB-V{8a`xRu&Rsj40;8y^Jl{VnnG&fuy1Hq;1L3sZ0zajqQTIW|@
z3GG+V16JOB*LS~0qoEuhAHV+J*RNj*0D8S%q#uq!msYkMBUT{4aztpq{sp2{ZDY__
R>8Ahy002ovPDHLkV1ne**HHif
literal 0
HcmV?d00001
diff --git a/glabels/images/TemplateDesigner/ex-2layouts.png b/glabels/images/TemplateDesigner/ex-2layouts.png
new file mode 100644
index 0000000000000000000000000000000000000000..1bb5931ac2ec6d60075bb44d514d58fef7e60fcf
GIT binary patch
literal 1610
zcmV-Q2DSN#P)8#64SWS@1RuaR
z5EVr)VxL>Q2wwKGh!Y&f+Jl;%&7VnESykQIzYmCEu&aK4^X=+XSM_%cd<^sOnN#Cp
z|IQ%)`;lG%e)aMp#?D78!!Q8gGk^_%uK>P5vKdG(;1{)J1r`&)p8%o&44-Q`1-SJJ
ztFdALeh2UgWGpXm>*eWE$jfXtL%ZEZx7&r|I9MzeSg+TJMx#h160j@_`FtKXH#aa%
zQ$-0Mae>`Qb(W^uU%rHR=Q7T%m(&rf-|wSbE~8j1A{L9GUa!M(9IRF=FvhT2t>8Eg
z>h(Heu^5WQBFg15`u)DlG6?Jp!u#@}#*cY?d}P2%7judmtT<0IE`
zqG0!j=U)J_0RFGajvass4_#juX}E=n-o+n<10QAeBmCJRZZcEUx1%%ffg(MkNzHaoT?;zW4Y;jZhlWHQM{qmj@V@{-YL#FEJ*b6r
zraCYhjaWLJmfGfzs1)iWy>ua3!&}FR>Lq+BtW+v^cz8f6l@e{@O;V{89v&W0sZ^x4
zOXx+&Sp9wvqV7*!{C0&gxgr{b{n-?O==PEQLEL^Znw4cbxtsW
zo1;wfO;T=Sb-P^@3I%Be{ER}OfNr;|r7uD*+-rr7;~*Z7tElA_@pv4L<7nyYoKUb<
z$PKr#7K;TUk%*MeKOz!|V6j+e>FeAOF^J5+v$!vX>-AdMZ15$9VPL&pYw3%S3%9YN
z(WthSaK;#-(WsWb2)S?@E0IWGv)QPq5IvwQmc`bbr`uX2#x9hshG)TIJmmH@@r2#aU?>E(A8=c_xJb8W`~S1+~41$TCFN+KMb7QS|I@B
zayewPSu~qXsl^;hnSOC@vJ=Xa)w1bpSuTw-+ucIEOXti3ny}iYF
zJU*tGXU5|(Zf|eVYPF=aM+BRnI@)zzmQJUcZQE?S-3px*+wGRww$0M%w9*GD7^4x3
z7g4>Ae*DWcO$>*_=SR4BXN!*GJU==%91dv~7$Q8oyVvWnQmMpD(`2@7v-y0^o}Qkb
zN3!j9%buQ|*nB=`wrw-hG+C)sV!d8Z)d}|s(1^u_s9s*?nEUp<6XfiM{j~ZEfWHB_
z`}gk>yWrL<$cXBt#8^RwjKznjUSW+fA{0Z$B9uVJB9uVJB9x#c(eGG-JgX$U5vUQ1
z5&^@7mK$-p5&=OsNVyQz%l8qB5&^@Dva`XDL_pAp#f3_}lojpAelC?lsn*L6MD_CR
zQb>t_=Z!DM3R@ze$o_K``_Ogbi?PC(2*?7y|6DfOjYWxo;Xu2wC=oCmXg3xm0)_+a
z#-c>PaG>2-ln59Ov>S^O0mFfIV^JbtIMBWy>yQX|d3mY4uOKA?p7PC35wwq3VN3)R
zxjB03H%YY{ixL5e15#JZ{Z0gg54#)oeG0w&MpQ5DBUV@w0a=JHgMd$q(3A*x{`g`n
zN(3YT+Ch4>UZNVR)SjmX5xsnUn1T`k!;R82LU$q{XvE^KK}0X#AOE66!0@8&?9h=2
z2pX}tYY@>Zkilyzvm5*fUJJticq$4d`gw#B{rI3nKO#_&u?Qu2+K&~sy+p@CdU-R}
z1%S^0OaL*wJu5Jmjqhh8r)W3~a{
z6M)|UM1J_UQ-)#p%l``b<|4h|{R@&bb@Bjb+07*qo
IM6N<$f>vJvIRF3v
literal 0
HcmV?d00001
diff --git a/glabels/images/TemplateDesigner/ex-cd-size.png b/glabels/images/TemplateDesigner/ex-cd-size.png
new file mode 100644
index 0000000000000000000000000000000000000000..68261f0367506c6374d30fc53b9c3bd6ab50fc74
GIT binary patch
literal 32300
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ⅈ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
literal 0
HcmV?d00001
diff --git a/glabels/images/TemplateDesigner/ex-ellipse-size.png b/glabels/images/TemplateDesigner/ex-ellipse-size.png
new file mode 100644
index 0000000000000000000000000000000000000000..456ec2eb19e1a03dfcc2eec4fc2dc33202c6ffaa
GIT binary patch
literal 16779
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