From f7ccf4ca7bf030e714807e4dfaa5bb923d7317db Mon Sep 17 00:00:00 2001 From: Jim Evins Date: Sun, 26 Jun 2016 12:49:47 -0400 Subject: [PATCH] Added basic image object functionality. --- glabels/CMakeLists.txt | 2 + glabels/LabelEditor.cpp | 16 +- glabels/LabelModelImageObject.cpp | 240 ++++++++++++++++++++++++++++++ glabels/LabelModelImageObject.h | 93 ++++++++++++ glabels/MainWindow.cpp | 5 +- glabels/ObjectEditor.cpp | 59 ++++++++ glabels/ObjectEditor.h | 2 + glabels/TextNode.cpp | 127 ++++++++-------- glabels/TextNode.h | 16 +- glabels/images.qrc | 1 + glabels/images/checkerboard.png | Bin 0 -> 177 bytes glabels/ui/ObjectEditor.ui | 204 +++++++++++++------------ 12 files changed, 585 insertions(+), 180 deletions(-) create mode 100644 glabels/LabelModelImageObject.cpp create mode 100644 glabels/LabelModelImageObject.h create mode 100644 glabels/images/checkerboard.png diff --git a/glabels/CMakeLists.txt b/glabels/CMakeLists.txt index 928c2a7..5fdd1c2 100644 --- a/glabels/CMakeLists.txt +++ b/glabels/CMakeLists.txt @@ -37,6 +37,7 @@ set (glabels_sources LabelModelObject.cpp LabelModelBoxObject.cpp LabelModelEllipseObject.cpp + LabelModelImageObject.cpp LabelModelLineObject.cpp LabelModelShapeObject.cpp LabelRegion.cpp @@ -81,6 +82,7 @@ set (glabels_qobject_headers LabelModelObject.h LabelModelBoxObject.h LabelModelEllipseObject.h + LabelModelImageObject.h LabelModelLineObject.h LabelModelShapeObject.h MainWindow.h diff --git a/glabels/LabelEditor.cpp b/glabels/LabelEditor.cpp index a6dcb9d..48b0d97 100644 --- a/glabels/LabelEditor.cpp +++ b/glabels/LabelEditor.cpp @@ -29,6 +29,7 @@ #include "LabelModelObject.h" #include "LabelModelBoxObject.h" #include "LabelModelEllipseObject.h" +#include "LabelModelImageObject.h" #include "LabelModelLineObject.h" #include "UndoRedoModel.h" #include "Settings.h" @@ -340,6 +341,19 @@ LabelEditor::createEllipseMode() } +/// +/// Create image mode +/// +void +LabelEditor::createImageMode() +{ + setCursor( Cursors::Image() ); + + mCreateObjectType = Image; + mState = CreateIdle; +} + + /// /// Create line mode /// @@ -492,7 +506,7 @@ LabelEditor::mousePressEvent( QMouseEvent* event ) mCreateObject = new LabelModelLineObject(); break; case Image: - // mCreateObject = new LabelModelImageObject(); + mCreateObject = new LabelModelImageObject(); break; case Text: // mCreateObject = new LabelModelTextObject(); diff --git a/glabels/LabelModelImageObject.cpp b/glabels/LabelModelImageObject.cpp new file mode 100644 index 0000000..57075f6 --- /dev/null +++ b/glabels/LabelModelImageObject.cpp @@ -0,0 +1,240 @@ +/* LabelModelImageObject.cpp + * + * Copyright (C) 2013-2016 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 "LabelModelImageObject.h" + +#include +#include +#include + + +namespace +{ +} + + +/// +/// Static data +/// +QImage* LabelModelImageObject::smDefaultImage = 0; + + +/// +/// Constructor +/// +LabelModelImageObject::LabelModelImageObject() : mImage(0) +{ + mOutline = new Outline( this ); + + mHandles << new HandleNorthWest( this ); + mHandles << new HandleNorth( this ); + mHandles << new HandleNorthEast( this ); + mHandles << new HandleEast( this ); + mHandles << new HandleSouthEast( this ); + mHandles << new HandleSouth( this ); + mHandles << new HandleSouthWest( this ); + mHandles << new HandleWest( this ); + + if ( smDefaultImage == 0 ) + { + smDefaultImage = new QImage( ":images/checkerboard.png" ); + } +} + + +/// +/// Copy constructor +/// +LabelModelImageObject::LabelModelImageObject( const LabelModelImageObject* object ) : LabelModelObject(object) +{ + mFilenameNode = object->mFilenameNode; +} + + +/// +/// Destructor +/// +LabelModelImageObject::~LabelModelImageObject() +{ + delete mOutline; + + foreach( Handle* handle, mHandles ) + { + delete handle; + } + mHandles.clear(); +} + + +/// +/// Clone +/// +LabelModelImageObject* LabelModelImageObject::clone() const +{ + return new LabelModelImageObject( this ); +} + + +/// +/// Image filenameNode Property Getter +/// +TextNode LabelModelImageObject::filenameNode( void ) const +{ + return mFilenameNode; +} + + +/// +/// Image filenameNode Property Setter +/// +void LabelModelImageObject::setFilenameNode( const TextNode& value ) +{ + if ( mFilenameNode != value ) + { + mFilenameNode = value; + loadImage(); + + emit changed(); + } +} + + +/// +/// Draw shadow of object +/// +void LabelModelImageObject::drawShadow( QPainter* painter, bool inEditor, merge::Record* record ) const +{ + QRectF destRect( 0, 0, mW.pt(), mH.pt() ); + + QColor shadowColor = mShadowColorNode.color( record ); + shadowColor.setAlphaF( mShadowOpacity ); + + if ( mImage && mImage->hasAlphaChannel() && (mImage->depth() == 32) ) + { + QImage* shadowImage = createShadowImage( shadowColor ); + painter->drawImage( destRect, *shadowImage ); + delete shadowImage; + } + else + { + painter->setBrush( shadowColor ); + painter->setPen( QPen( Qt::NoPen ) ); + + painter->drawRect( destRect ); + } +} + + +/// +/// Draw object itself +/// +void LabelModelImageObject::drawObject( QPainter* painter, bool inEditor, merge::Record* record ) const +{ + QRectF destRect( 0, 0, mW.pt(), mH.pt() ); + + if ( inEditor && (mFilenameNode.isField() || !mImage ) ) + { + painter->save(); + painter->setRenderHint( QPainter::SmoothPixmapTransform, false ); + painter->drawImage( destRect, *smDefaultImage ); + painter->restore(); + } + else if ( mImage ) + { + painter->drawImage( destRect, *mImage ); + } + else if ( mFilenameNode.isField() ) + { + } +} + + +/// +/// Path to test for hover condition +/// +QPainterPath LabelModelImageObject::hoverPath( double scale ) const +{ + QPainterPath path; + path.addRect( 0, 0, mW.pt(), mH.pt() ); + + return path; +} + + +/// +/// Load image +/// +void LabelModelImageObject::loadImage() +{ + if ( mImage ) + { + delete mImage; + } + + if ( mFilenameNode.isField() ) + { + mImage = 0; + } + else + { + QString filename = mFilenameNode.data(); + mImage = new QImage( filename ); + if ( mImage->isNull() ) + { + mImage = 0; + } + else + { + double imageW = mImage->width(); + double imageH = mImage->height(); + double aspectRatio = imageH / imageW; + if ( mH > mW*aspectRatio ) + { + mH = mW*aspectRatio; + } + else + { + mW = mH/aspectRatio; + } + } + } +} + + +QImage* LabelModelImageObject::createShadowImage( const QColor& color ) const +{ + int r = color.red(); + int g = color.green(); + int b = color.blue(); + int a = color.alpha(); + + QImage* shadow = new QImage( *mImage ); + for ( int iy = 0; iy < shadow->height(); iy++ ) + { + QRgb* scanLine = (QRgb*)shadow->scanLine( iy ); + + for ( int ix = 0; ix < shadow->width(); ix++ ) + { + scanLine[ix] = qRgba( r, g, b, (a*qAlpha(scanLine[ix]))/255 ); + } + } + + return shadow; +} diff --git a/glabels/LabelModelImageObject.h b/glabels/LabelModelImageObject.h new file mode 100644 index 0000000..a1d8df4 --- /dev/null +++ b/glabels/LabelModelImageObject.h @@ -0,0 +1,93 @@ +/* LabelModelImageObject.h + * + * Copyright (C) 2013-2016 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 LabelModelImageObject_h +#define LabelModelImageObject_h + +#include "LabelModelObject.h" + + +/// +/// Label Model Image Object +/// +class LabelModelImageObject : public LabelModelObject +{ + Q_OBJECT + + /////////////////////////////////////////////////////////////// + // Lifecycle Methods + /////////////////////////////////////////////////////////////// +public: + LabelModelImageObject(); + LabelModelImageObject( const LabelModelImageObject* object ); + virtual ~LabelModelImageObject(); + + + /////////////////////////////////////////////////////////////// + // Object duplication + /////////////////////////////////////////////////////////////// + virtual LabelModelImageObject* clone() const; + + + /////////////////////////////////////////////////////////////// + // Property Implementations + /////////////////////////////////////////////////////////////// +public: + // + // Image Property: filenameNode + // + virtual TextNode filenameNode( void ) const; + virtual void setFilenameNode( const TextNode& value ); + + + /////////////////////////////////////////////////////////////// + // Capability Implementations + /////////////////////////////////////////////////////////////// + + + /////////////////////////////////////////////////////////////// + // Drawing operations + /////////////////////////////////////////////////////////////// +protected: + virtual void drawShadow( QPainter* painter, bool inEditor, merge::Record* record ) const; + virtual void drawObject( QPainter* painter, bool inEditor, merge::Record* record ) const; + virtual QPainterPath hoverPath( double scale ) const; + + + /////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////// + void loadImage(); + QImage* createShadowImage( const QColor& color ) const; + + + /////////////////////////////////////////////////////////////// + // Private Members + /////////////////////////////////////////////////////////////// +protected: + TextNode mFilenameNode; + QImage* mImage; + + static QImage* smDefaultImage; + +}; + + +#endif // LabelModelImageObject_h diff --git a/glabels/MainWindow.cpp b/glabels/MainWindow.cpp index 88cc421..42527de 100644 --- a/glabels/MainWindow.cpp +++ b/glabels/MainWindow.cpp @@ -45,8 +45,6 @@ #include "MergeView.h" #include "PrintView.h" #include "LabelModel.h" -#include "LabelModelBoxObject.h" -#include "LabelModelEllipseObject.h" #include "UndoRedoModel.h" #include "Icons.h" #include "File.h" @@ -1255,7 +1253,8 @@ void MainWindow::objectsCreateEllipse() /// void MainWindow::objectsCreateImage() { - qDebug() << "ACTION: objects->Create->Image"; + mUndoRedoModel->checkpoint( tr("Create Image") ); + mLabelEditor->createImageMode(); } diff --git a/glabels/ObjectEditor.cpp b/glabels/ObjectEditor.cpp index f50ee80..2242889 100644 --- a/glabels/ObjectEditor.cpp +++ b/glabels/ObjectEditor.cpp @@ -25,6 +25,7 @@ #include "LabelModelObject.h" #include "LabelModelBoxObject.h" #include "LabelModelEllipseObject.h" +#include "LabelModelImageObject.h" #include "LabelModelLineObject.h" #include "UndoRedoModel.h" @@ -32,6 +33,7 @@ #include "Settings.h" +#include #include #include @@ -82,6 +84,28 @@ void ObjectEditor::hidePages() } +void ObjectEditor::loadImagePage() +{ + if ( mObject ) + { + mBlocked = true; + + TextNode filenameNode = mObject->filenameNode(); + + if ( filenameNode.isField() ) + { + imageFilenameLineEdit->setText( QString("${%1}").arg( filenameNode.data() ) ); + } + else + { + imageFilenameLineEdit->setText( filenameNode.data() ); + } + + mBlocked = false; + } +} + + void ObjectEditor::loadLineFillPage() { if ( mObject ) @@ -273,6 +297,25 @@ void ObjectEditor::onSelectionChanged() setEnabled( true ); } + else if ( dynamic_cast(mObject) ) + { + titleImageLabel->setPixmap( QPixmap(":icons/24x24/actions/glabels-image.png") ); + titleLabel->setText( tr("Image object properties") ); + + notebook->addTab( imagePage, "image" ); + notebook->addTab( posSizePage, "position/size" ); + notebook->addTab( shadowPage, "shadow" ); + + sizeRectFrame->setVisible( true ); + sizeResetImageButton->setVisible( true ); + sizeLineFrame->setVisible( false ); + + loadImagePage(); + loadPositionPage(); + loadShadowPage(); + + setEnabled( true ); + } else if ( dynamic_cast(mObject) ) { titleImageLabel->setPixmap( QPixmap(":icons/24x24/actions/glabels-line.png") ); @@ -333,6 +376,7 @@ void ObjectEditor::onObjectChanged() loadLineFillPage(); loadRectSizePage(); loadLineSizePage(); + loadImagePage(); loadShadowPage(); } } @@ -385,6 +429,21 @@ void ObjectEditor::onFillControlsChanged() } +void ObjectEditor::onImageFileButtonClicked() +{ + QString filename = + QFileDialog::getOpenFileName( this->window(), + tr("gLabels - Select image file"), + ".", + tr("Image Files (*.png *.jpg *.bmp);;All files (*)") ); + if ( !filename.isEmpty() ) + { + mUndoRedoModel->checkpoint( tr("Set image") ); + mObject->setFilenameNode( TextNode( false, filename ) ); + } +} + + void ObjectEditor::onPositionControlsChanged() { if ( !mBlocked ) diff --git a/glabels/ObjectEditor.h b/glabels/ObjectEditor.h index 6df4f2a..88515a7 100644 --- a/glabels/ObjectEditor.h +++ b/glabels/ObjectEditor.h @@ -56,6 +56,7 @@ public: ///////////////////////////////// private: void hidePages(); + void loadImagePage(); void loadLineFillPage(); void loadPositionPage(); void loadRectSizePage(); @@ -76,6 +77,7 @@ private slots: void onObjectDestroyed(); void onLineControlsChanged(); void onFillControlsChanged(); + void onImageFileButtonClicked(); void onPositionControlsChanged(); void onRectSizeControlsChanged(); void onLineSizeControlsChanged(); diff --git a/glabels/TextNode.cpp b/glabels/TextNode.cpp index d22dec8..05b10b2 100644 --- a/glabels/TextNode.cpp +++ b/glabels/TextNode.cpp @@ -37,7 +37,7 @@ namespace { /// Default Constructor /// TextNode::TextNode() - : mFieldFlag(false), mData("") + : mIsField(false), mData("") { } @@ -45,8 +45,8 @@ TextNode::TextNode() /// /// Constructor from Data /// -TextNode::TextNode( bool field_flag, const QString &data ) - : mFieldFlag(field_flag), mData(data) +TextNode::TextNode( bool isField, const QString &data ) + : mIsField(isField), mData(data) { } @@ -54,14 +54,14 @@ TextNode::TextNode( bool field_flag, const QString &data ) /// /// Constructor from Parsing Next Token in Text /// -TextNode::TextNode( const QString &text, int i_start, int &i_next ) +TextNode::TextNode( const QString &text, int iStart, int &iNext ) { State state = START; - QString literal_text; - QString field_name; - bool field_flag = false; + QString literalText; + QString fieldName; + bool isField = false; - int i = i_start; + int i = iStart; while ( state != DONE ) { @@ -84,7 +84,7 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) break; default: /* Start a literal text node. */ - literal_text.append( c ); + literalText.append( c ); i++; state = LITERAL; break; @@ -105,7 +105,7 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) state = DONE; break; default: - literal_text.append( c ); + literalText.append( c ); i++; state = LITERAL; break; @@ -116,26 +116,26 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) switch (c.unicode()) { case '{': /* "${" indicates the start of a new field node, gather for literal too. */ - literal_text.append( '$' ); + literalText.append( '$' ); i++; state = DONE; break; case '\n': /* Append "$" to literal text, don't gather newline. */ - literal_text.append( '$' ); + literalText.append( '$' ); i++; state = DONE; break; case 0: /* Append "$" to literal text, don't gather null. */ - literal_text.append( '$' ); + literalText.append( '$' ); i++; state = DONE; break; default: /* Append "$" to literal text, gather this character too. */ - literal_text.append( '$' ); - literal_text.append( c ); + literalText.append( '$' ); + literalText.append( c ); i+=2; state = LITERAL; break; @@ -146,7 +146,7 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) switch (c.unicode()) { case '{': /* This is probably the begining of a field node, gather for literal too. */ - literal_text.append( c ); + literalText.append( c ); i++; state = FIELD; break; @@ -158,7 +158,7 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) break; default: /* The "$" was literal. */ - literal_text.append( c ); + literalText.append( c ); i++; state = LITERAL; break; @@ -169,7 +169,7 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) switch (c.unicode()) { case '}': /* We now finally know that this node is really field node. */ - field_flag = true; + isField = true; i++; state = DONE; break; @@ -181,8 +181,8 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) break; default: /* Gather for field name and literal, just in case. */ - field_name.append( c ); - literal_text.append( c ); + fieldName.append( c ); + literalText.append( c ); i++; state = FIELD; break; @@ -193,10 +193,10 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) } - mFieldFlag = field_flag; - mData = field_flag ? field_name : literal_text; + mIsField = isField; + mData = isField ? fieldName : literalText; - i_next = i; + iNext = i; } @@ -205,8 +205,8 @@ TextNode::TextNode( const QString &text, int i_start, int &i_next ) /// bool TextNode::operator==( const TextNode& other ) { - return ( (mFieldFlag == other.mFieldFlag) && - (mData == other.mData) ); + return ( (mIsField == other.mIsField) && + (mData == other.mData) ); } @@ -215,17 +215,17 @@ bool TextNode::operator==( const TextNode& other ) /// bool TextNode::operator!=( const TextNode& other ) { - return ( (mFieldFlag != other.mFieldFlag) || - (mData != other.mData) ); + return ( (mIsField != other.mIsField) || + (mData != other.mData) ); } /// -/// Field Flag Property Getter +/// isField? Property Getter /// -bool TextNode::fieldFlag( void ) const +bool TextNode::isField( void ) const { - return mFieldFlag; + return mIsField; } @@ -238,47 +238,48 @@ const QString& TextNode::data( void ) const } -#if TODO - public string expand( MergeRecord? record ) +/// +/// Get text, expand if necessary +/// +QString TextNode::text( merge::Record* record ) const +{ + if ( mIsField ) + { + if ( !record ) { - if ( field_flag ) + return QString("${%1}").arg( mData ); + } + else + { + if ( record->contains( mData ) ) { - - if ( record == null ) - { - return "${%s}".printf( data ); - } - else - { - string? text = record.eval_key( data ); - if ( text != null ) - { - return text; - } - else - { - return ""; - } - } - + return (*record)[ mData ]; } else { - return data; + return ""; } } + } + else + { + return mData; + } +} - public bool is_empty_field( MergeRecord? record ) +/// +/// Is it an empty field +/// +bool TextNode::isEmptyField( merge::Record* record ) const +{ + if ( record && mIsField ) + { + if ( record->contains( mData ) ) { - if ( (record !=null) && field_flag ) - { - string? text = record.eval_key( data ); - return ( (text == null) || (text == "") ); - } - else - { - return false; - } + return ( (*record)[mData].isEmpty() ); } -#endif + } + + return false; +} diff --git a/glabels/TextNode.h b/glabels/TextNode.h index a8e050f..626fa43 100644 --- a/glabels/TextNode.h +++ b/glabels/TextNode.h @@ -22,6 +22,7 @@ #define TextNode_h #include +#include "Merge/Record.h" /// @@ -36,7 +37,7 @@ struct TextNode public: TextNode(); - TextNode( bool field_flag, const QString &data ); + TextNode( bool isField, const QString &data ); TextNode( const QString &text, int i_start, int &i_next ); @@ -55,9 +56,9 @@ public: ///////////////////////////////// public: // - // Field Flag Property + // is field? Property // - bool fieldFlag( void ) const; + bool isField( void ) const; // // Data Property @@ -65,14 +66,11 @@ public: const QString& data( void ) const; - ///////////////////////////////// // Methods ///////////////////////////////// -#if TODO - string expand( MergeRecord? record ); - bool is_empty_field( MergeRecord? record ); -#endif + QString text( merge::Record* record ) const; + bool isEmptyField( merge::Record* record ) const; ///////////////////////////////// @@ -80,7 +78,7 @@ public: ///////////////////////////////// private: - bool mFieldFlag; + bool mIsField; QString mData; }; diff --git a/glabels/images.qrc b/glabels/images.qrc index ccf1c19..315a144 100644 --- a/glabels/images.qrc +++ b/glabels/images.qrc @@ -4,5 +4,6 @@ images/glabels-label-designer.png images/glabels-logo.png + images/checkerboard.png diff --git a/glabels/images/checkerboard.png b/glabels/images/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..c97f40e2e67b6e4362b24b2936daa6e99d021dd1 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYYs>cdx@v7EBgaBA#p3si<>mcfI@bjE{-7{$KPJu$jhL>!+elGX~`RV z$1){RX@Sm1cXw_sWt3@H#iU?dc~kuG&EV7d^7R5z84DWOfW$#@Hr{1Pw>tR@s_lR# OF?hQAxvX - 4 + 2 @@ -759,7 +759,71 @@ Image - + + + + File + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + 0 + 0 + + + + File... + + + + + + + + 0 + 0 + + + + Merge field... + + + + + + + + 0 + 0 + + + + + 231 + 0 + + + + true + + + None + + + + + + + + + Qt::Vertical @@ -772,60 +836,6 @@ - - - - File - - - - - - - - File: - - - - - - - Placeholder - - - - - - - Key: - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - @@ -1857,54 +1867,6 @@ - - imageFileRadio - toggled(bool) - ObjectEditor - onChanged() - - - 47 - 118 - - - 0 - 118 - - - - - imageKeyRadio - toggled(bool) - ObjectEditor - onChanged() - - - 42 - 151 - - - 0 - 152 - - - - - imageFieldButton - keySelected() - ObjectEditor - onChanged() - - - 180 - 158 - - - 4 - 203 - - - lineWidthSpin valueChanged(double) @@ -2145,6 +2107,38 @@ + + imageFileButton + clicked() + ObjectEditor + onImageFileButtonClicked() + + + 321 + 119 + + + 394 + 81 + + + + + imageMergeFieldButton + keySelected() + ObjectEditor + onImageKeySelected() + + + 351 + 147 + + + 7 + 143 + + + onChanged() @@ -2155,5 +2149,7 @@ onRectSizeControlsChanged() onShadowControlsChanged() onLineSizeControlsChanged() + onImageFileButtonClicked() + onImageKeySelected()