diff --git a/backends/merge/Merge.cpp b/backends/merge/Merge.cpp index 993c0d1..8277ac5 100644 --- a/backends/merge/Merge.cpp +++ b/backends/merge/Merge.cpp @@ -31,7 +31,7 @@ namespace glabels /// /// Constructor /// - Merge::Merge( const Merge* merge ) : mSource(merge->mSource) + Merge::Merge( const Merge* merge ) : mId(merge->mId), mSource(merge->mSource) { foreach ( Record* record, merge->mRecordList ) { diff --git a/backends/merge/Text.cpp b/backends/merge/Text.cpp index 9d40cc3..93d5350 100644 --- a/backends/merge/Text.cpp +++ b/backends/merge/Text.cpp @@ -45,7 +45,7 @@ namespace glabels Text::Text( const Text* merge ) : Merge( merge ), mDelimeter(merge->mDelimeter), mLine1HasKeys(merge->mLine1HasKeys), - mNFieldsMax(merge->mNFieldsMax) + mKeys(merge->mKeys), mNFieldsMax(merge->mNFieldsMax) { } diff --git a/glabels/LabelEditor.cpp b/glabels/LabelEditor.cpp index c3a4c15..560eb41 100644 --- a/glabels/LabelEditor.cpp +++ b/glabels/LabelEditor.cpp @@ -661,6 +661,7 @@ namespace glabels break; case ArrowResize: + mUndoRedoModel->checkpoint( tr("Resize") ); handleResizeMotion( xWorld, yWorld ); break; diff --git a/glabels/MainWindow.cpp b/glabels/MainWindow.cpp index 15bfda9..fb09531 100644 --- a/glabels/MainWindow.cpp +++ b/glabels/MainWindow.cpp @@ -62,7 +62,7 @@ namespace glabels /// /// Constructor /// - MainWindow::MainWindow() : mModel(nullptr) + MainWindow::MainWindow() : mModel(nullptr), mUndoRedoModel(nullptr) { setWindowIcon( Icons::Glabels() ); @@ -194,7 +194,15 @@ namespace glabels /// MainWindow::~MainWindow() { - // empty + if ( mUndoRedoModel ) + { + delete mUndoRedoModel; + } + if ( mModel ) + { + delete mModel->merge(); // Ownership of final Merge instance is ours + delete mModel; + } } @@ -212,7 +220,7 @@ namespace glabels /// void MainWindow::setModel( model::Model* model ) { - mModel = model; + mModel = model; // Ownership passes to us mUndoRedoModel = new UndoRedoModel( mModel ); mPropertiesView->setModel( mModel, mUndoRedoModel ); @@ -882,8 +890,38 @@ namespace glabels fileExitAction->setEnabled( true ); // Edit actions - editUndoAction->setEnabled( hasModel && mUndoRedoModel->canUndo() ); - editRedoAction->setEnabled( hasModel && mUndoRedoModel->canRedo() ); + if ( hasModel ) + { + if ( mUndoRedoModel->canUndo() ) + { + editUndoAction->setEnabled( true ); + /* Translators: %1 is the action description to undo. */ + editUndoAction->setText( QString( tr("Undo %1") ).arg( mUndoRedoModel->undoDescription() ) ); + } + else + { + editUndoAction->setEnabled( false ); + editUndoAction->setText( tr("Undo") ); + } + if ( mUndoRedoModel->canRedo() ) + { + editRedoAction->setEnabled( true ); + /* Translators: %1 is the action description to redo. */ + editRedoAction->setText( QString( tr("Redo %1") ).arg( mUndoRedoModel->redoDescription() ) ); + } + else + { + editRedoAction->setEnabled( false ); + editRedoAction->setText( tr("Redo") ); + } + } + else + { + editUndoAction->setEnabled( false ); + editUndoAction->setText( tr("Undo") ); + editRedoAction->setEnabled( false ); + editRedoAction->setText( tr("Redo") ); + } editCutAction->setEnabled( isEditorPage && hasSelection ); editCopyAction->setEnabled( isEditorPage && hasSelection ); editPasteAction->setEnabled( isEditorPage && canPaste ); diff --git a/glabels/ObjectEditor.cpp b/glabels/ObjectEditor.cpp index 0053bc2..b635ae3 100644 --- a/glabels/ObjectEditor.cpp +++ b/glabels/ObjectEditor.cpp @@ -631,7 +631,7 @@ namespace glabels { mBlocked = true; - mUndoRedoModel->checkpoint( tr("Move") ); + mUndoRedoModel->checkpoint( tr("Position") ); model::Distance x = model::Distance(posXSpin->value(), mUnits); model::Distance y = model::Distance(posYSpin->value(), mUnits); @@ -744,6 +744,8 @@ namespace glabels { mBlocked = true; + mUndoRedoModel->checkpoint( tr("Barcode") ); + barcode::Style bcStyle = barcodeStyleButton->bcStyle(); barcodeShowTextCheck->setEnabled( bcStyle.textOptional() ); @@ -778,6 +780,8 @@ namespace glabels void ObjectEditor::onResetImageSize() { + mUndoRedoModel->checkpoint( tr("Reset") ); + mObject->setSize( mObject->naturalSize() ); } diff --git a/model/Model.cpp b/model/Model.cpp index b0eb83d..32630d8 100644 --- a/model/Model.cpp +++ b/model/Model.cpp @@ -61,12 +61,19 @@ namespace glabels } + Model::Model( merge::Merge* merge ) + : mUntitledInstance(0), mModified(true), mRotate(false) + { + mMerge = merge; // Shared + } + + /// /// Destructor. /// Model::~Model() { - delete mMerge; + // Final instance of mMerge to be deleted by Model owner } @@ -75,7 +82,13 @@ namespace glabels /// Model* Model::save() const { - auto* savedModel = new Model; + auto* savedModel = new Model( mMerge ); // mMerge shared between models + + if ( mFileName.isEmpty() && mUntitledInstance == 0 ) + { + qDebug() << "Model::save: Warning: called before mUntitledInstance has been initialized: untitled names will differ"; + } + savedModel->restore( this ); return savedModel; @@ -112,18 +125,12 @@ namespace glabels connect( object, SIGNAL(moved()), this, SLOT(onObjectMoved()) ); } - delete mMerge; - mMerge = savedModel->mMerge->clone(); - // Emit signals based on potential changes emit changed(); emit selectionChanged(); emit modifiedChanged(); emit nameChanged(); emit sizeChanged(); - emit mergeChanged(); - emit mergeSourceChanged(); - emit mergeSelectionChanged(); } diff --git a/model/Model.h b/model/Model.h index 992606e..89537db 100644 --- a/model/Model.h +++ b/model/Model.h @@ -57,6 +57,7 @@ namespace glabels ///////////////////////////////// public: Model(); + Model( merge::Merge* merge ); ~Model(); diff --git a/model/unit_tests/CMakeLists.txt b/model/unit_tests/CMakeLists.txt index d05896c..c896762 100644 --- a/model/unit_tests/CMakeLists.txt +++ b/model/unit_tests/CMakeLists.txt @@ -24,4 +24,44 @@ if (Qt5Test_FOUND) target_link_libraries (TestXmlLabel Model Qt5::Test) add_test (NAME XmlLabel COMMAND TestXmlLabel) + #======================================= + # Test ColorNode class + #======================================= + qt5_wrap_cpp (TestColorNode_moc_sources TestColorNode.h) + add_executable (TestColorNode TestColorNode.cpp ${TestColorNode_moc_sources}) + target_link_libraries (TestColorNode Model Qt5::Test) + add_test (NAME ColorNode COMMAND TestColorNode) + + #======================================= + # Test Merge classes + #======================================= + qt5_wrap_cpp (TestMerge_moc_sources TestMerge.h) + add_executable (TestMerge TestMerge.cpp ${TestMerge_moc_sources}) + target_link_libraries (TestMerge Model Qt5::Test) + add_test (NAME Merge COMMAND TestMerge) + + #======================================= + # Test Model class + #======================================= + qt5_wrap_cpp (TestModel_moc_sources TestModel.h) + add_executable (TestModel TestModel.cpp ${TestModel_moc_sources}) + target_link_libraries (TestModel Model Qt5::Test) + add_test (NAME Model COMMAND TestModel) + + #======================================= + # Test RawText class + #======================================= + qt5_wrap_cpp (TestRawText_moc_sources TestRawText.h) + add_executable (TestRawText TestRawText.cpp ${TestRawText_moc_sources}) + target_link_libraries (TestRawText Model Qt5::Test) + add_test (NAME RawText COMMAND TestRawText) + + #======================================= + # Test TextNode class + #======================================= + qt5_wrap_cpp (TestTextNode_moc_sources TestTextNode.h) + add_executable (TestTextNode TestTextNode.cpp ${TestTextNode_moc_sources}) + target_link_libraries (TestTextNode Model Qt5::Test) + add_test (NAME TextNode COMMAND TestTextNode) + endif (Qt5Test_FOUND) diff --git a/model/unit_tests/TestColorNode.cpp b/model/unit_tests/TestColorNode.cpp new file mode 100644 index 0000000..be30ebf --- /dev/null +++ b/model/unit_tests/TestColorNode.cpp @@ -0,0 +1,129 @@ +/* TestColorNode.cpp + * + * Copyright (C) 2019 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 "TestColorNode.h" + +#include "model/ColorNode.h" + +#include "merge/Record.h" + +#include + + +QTEST_MAIN(TestColorNode) + +using namespace glabels::model; +using namespace glabels::merge; + + +void TestColorNode::colorNode() +{ + uint32_t rgbaBlackTransparent = 0; + uint32_t rgbaWhite = 0xFFFFFFFF; + uint32_t rgbaRed = 0xFF0000FF; // ColorNode uses RGBA. QColor set alpha to opaque 0xFF by default + uint32_t qRgbaRed = 0xFFFF0000; // QColor uses ARGB, ie alpha at top + uint32_t qRgbaGreen80 = 0x8000FF00; + + QColor blackTransparent = QColor::fromRgba( rgbaBlackTransparent ); + QColor white = QColor::fromRgba( rgbaWhite ); + QColor red = QColor::fromRgba( qRgbaRed ); + QColor green80 = QColor::fromRgba( qRgbaGreen80 ); + + Record record; + + ColorNode colorNode; + QVERIFY( !colorNode.isField() ); + QCOMPARE( colorNode.color(), blackTransparent ); + QCOMPARE( colorNode.key(), QString( "" ) ); + QCOMPARE( colorNode.rgba(), rgbaBlackTransparent ); + QCOMPARE( colorNode.color( nullptr ), blackTransparent ); + QCOMPARE( colorNode.color( &record ), blackTransparent ); + + colorNode.setField( true ); + QVERIFY( colorNode.isField() ); + colorNode.setField( false ); + QVERIFY( !colorNode.isField() ); + + colorNode.setColor( white ); + QCOMPARE( colorNode.color(), white ); + QCOMPARE( colorNode.rgba(), rgbaWhite ); + QCOMPARE( colorNode.color( nullptr ), white ); + QCOMPARE( colorNode.color( &record ), white ); + + colorNode.setKey( "key1" ); + QCOMPARE( colorNode.key(), QString( "key1" ) ); + + /// + /// Constructors + /// + ColorNode colorNode2( true, white, QString( "key2" ) ); + QVERIFY( colorNode2.isField() ); + QCOMPARE( colorNode2.key(), QString( "key2" ) ); + QCOMPARE( colorNode2.color(), white ); + + QVERIFY( colorNode2 != colorNode ); + colorNode.setField( true ); + QVERIFY( colorNode2 != colorNode ); + colorNode.setKey( "key2" ); + QVERIFY( colorNode2 == colorNode ); + + ColorNode colorNode3( red ); + QVERIFY( !colorNode3.isField() ); + QCOMPARE( colorNode3.key(), QString( "" ) ); + QCOMPARE( colorNode3.color(), red ); + QCOMPARE( colorNode3.rgba(), rgbaRed ); + + QVERIFY( colorNode3 != colorNode ); + colorNode.setField( false ); + QVERIFY( colorNode3 != colorNode ); + colorNode.setKey( "" ); + QVERIFY( colorNode3 != colorNode ); + colorNode.setColor( red ); + QVERIFY( colorNode3 == colorNode ); + + colorNode = ColorNode( QString( "key1" ) ); + QVERIFY( colorNode.isField() ); // Defaults to true if given key only + QCOMPARE( colorNode.key(), QString( "key1" ) ); + QCOMPARE( colorNode.color(), blackTransparent ); + QCOMPARE( colorNode.color( &record ), blackTransparent ); + + /// + /// Record + /// + record["key1"] = "white"; + QCOMPARE( colorNode.color( &record ), white ); + + record["key1"] = "red"; + QCOMPARE( colorNode.color( &record ), red ); + + record["key1"] = "#FF0000"; + QCOMPARE( colorNode.color( &record ), red ); + + record["key1"] = "#FFFF0000"; // ARGB + QCOMPARE( colorNode.color( &record ), red ); + + record["key1"] = "#8000FF00"; + QCOMPARE( colorNode.color( &record ), green80 ); + + colorNode.setKey( "key2" ); + QCOMPARE( colorNode.color( &record ), blackTransparent ); + record["key2"] = "#8000FF00"; + QCOMPARE( colorNode.color( &record ), green80 ); +} diff --git a/model/unit_tests/TestColorNode.h b/model/unit_tests/TestColorNode.h new file mode 100644 index 0000000..4837c5a --- /dev/null +++ b/model/unit_tests/TestColorNode.h @@ -0,0 +1,30 @@ +/* TestColorNode.h + * + * Copyright (C) 2019 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 + + +class TestColorNode : public QObject +{ + Q_OBJECT + +private slots: + void colorNode(); +}; diff --git a/model/unit_tests/TestMerge.cpp b/model/unit_tests/TestMerge.cpp new file mode 100644 index 0000000..72c9dd3 --- /dev/null +++ b/model/unit_tests/TestMerge.cpp @@ -0,0 +1,347 @@ +/* TestMerge.cpp + * + * Copyright (C) 2019 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 "TestMerge.h" + +#include "merge/Factory.h" +#include "merge/None.h" +#include "merge/TextCsv.h" +#include "merge/TextCsvKeys.h" +#include "merge/TextTsv.h" +#include "merge/TextTsvKeys.h" +#include "merge/TextColon.h" +#include "merge/TextColonKeys.h" +#include "merge/TextSemicolon.h" +#include "merge/TextSemicolonKeys.h" + +#include "merge/Record.h" + +#include + + +QTEST_MAIN(TestMerge) + +Q_DECLARE_METATYPE(glabels::merge::Factory::SourceType) + +using namespace glabels::merge; + + +void TestMerge::initTestCase() +{ + Factory::init(); +} + + +void TestMerge::factory_data() +{ + QTest::addColumn( "id" ); + QTest::addColumn( "name" ); + QTest::addColumn( "type" ); + QTest::addColumn( "index" ); + + int index = 0; + QTest::newRow( "None" ) << None::id() << "None" << Factory::NONE << index++; + QTest::newRow( "TextCsv" ) << TextCsv::id() << "Text: Comma Separated Values (CSV)" << Factory::FILE << index++; + QTest::newRow( "TextCsvKeys" ) << TextCsvKeys::id() << "Text: Comma Separated Values (CSV), keys on line 1" << Factory::FILE << index++; + QTest::newRow( "TextTsv" ) << TextTsv::id() << "Text: Tab Separated Values (TSV)" << Factory::FILE << index++; + QTest::newRow( "TextTsvKeys" ) << TextTsvKeys::id() << "Text: Tab Separated Values (TSV), keys on line 1" << Factory::FILE << index++; + QTest::newRow( "TextColon" ) << TextColon::id() << "Text: Colon Separated Values" << Factory::FILE << index++; + QTest::newRow( "TextColonKeys" ) << TextColonKeys::id() << "Text: Colon Separated Values, keys on line 1" << Factory::FILE << index++; + QTest::newRow( "TextSemicolon" ) << TextSemicolon::id() << "Text: Semicolon Separated Values" << Factory::FILE << index++; + QTest::newRow( "TextSemicolonKeys" ) << TextSemicolonKeys::id() << "Text: Semicolon Separated Values, keys on line 1" << Factory::FILE << index++; +} + + +void TestMerge::factory() +{ + QFETCH( QString, id ); + QFETCH( QString, name ); + QFETCH( Factory::SourceType, type ); + QFETCH( int, index ); + + QVERIFY( Factory::nameList().contains( name ) ); + + QString outName = Factory::idToName( id ); + QCOMPARE( outName, name ); + + QString outId = Factory::nameToId( name ); + QCOMPARE( outId, id ); + + Factory::SourceType outType = Factory::idToType( id ); + QCOMPARE( outType, type ); + + outId = Factory::indexToId( index ); + QCOMPARE( outId, id ); + + Merge* merge = Factory::createMerge( id ); + QVERIFY( merge ); + QCOMPARE( merge->id(), id ); + + Merge* cloneMerge = merge->clone(); + QCOMPARE( cloneMerge->id(), merge->id() ); + delete cloneMerge; + delete merge; +} + + +void TestMerge::factoryNotRegistered() +{ + QString id( "unregistered" ); + Merge* merge = Factory::createMerge( id ); + QVERIFY( merge ); + QVERIFY( merge->id() != id ); + QCOMPARE( merge->id(), None::id() ); + delete merge; +} + + +void TestMerge::text_data() +{ + QTest::addColumn( "id" ); + QTest::addColumn( "keyed" ); + QTest::addColumn( "delim" ); + + QTest::newRow( "TextCsv" ) << TextCsv::id() << false << ','; + QTest::newRow( "TextCsvKeys" ) << TextCsvKeys::id() << true << ','; + QTest::newRow( "TextTsv" ) << TextTsv::id() << false << '\t'; + QTest::newRow( "TextTsvKeys" ) << TextTsvKeys::id() << true << '\t'; + QTest::newRow( "TextColon" ) << TextColon::id() << false << ':'; + QTest::newRow( "TextColonKeys" ) << TextColonKeys::id() << true << ':'; + QTest::newRow( "TextSemicolon" ) << TextSemicolon::id() << false << ';'; + QTest::newRow( "TextSemicolonKeys" ) << TextSemicolonKeys::id() << true << ';'; +} + + +void TestMerge::text() +{ + QFETCH( QString, id ); + QFETCH( bool, keyed ); + QFETCH( char, delim ); + + QTemporaryFile file; + file.open(); + if ( keyed ) + { + file.write( "header1" ); + file.putChar( delim ); + file.write( "\"header 2\"" ); + file.putChar( delim ); + file.write( "header3\r\n" ); + } + file.write( " val11" ); // Leading spaces in SIMPLE entry + file.putChar( delim ); + file.write( "\"\"\"val 12\"\"\"" ); // 2DQUOTE at beginning and end of DQUOTE entry + file.putChar( delim ); + file.write( " \"val 13\"\n" ); // Leading spaces before DQUOTE entry, end line with LF only + + file.write( "\" val21\"\"\"" ); // Leading spaces within DQUOTE entry, 2DQUOTE at end + file.putChar( delim ); + file.write( "\"\"\"val 22\"" ); // 2DQUOTE at beginning of DQUOTE entry + file.putChar( delim ); + file.write( "\r\n" ); // Last field blank + + file.write( "\"\"\"\"\"\"" ); // 2 2DQUOTES alone in DQUOTE entry + file.putChar( delim ); + file.write( "val \"32" ); // DQUOTE in SIMPLE entry + file.putChar( delim ); + file.write( "val \"\\\"33\r\n" ); // DQUOTE backslashed-DQUOTE in SIMPLE entry + + file.putChar( delim ); file.putChar( delim ); // All fields blank + file.write( "\r\n" ); + + file.write( "val\\n \\t \\r \\\\ \\x51" ); // Backslashed-n/-t/-r/-backslash/-x in SIMPLE entry + file.putChar( delim ); + file.write( "\"val\\n \\t \\r \\\\ \\x52\"" ); // Backslashed-n/-t/-r/-backslash/-x in QUOTE entry + file.write( "\r\n" ); // No last delim + + file.write( "\"val \"\"61\"" ); // 2DQUOTE in middle of DQUOTE entry + file.putChar( delim ); + file.write( "\"val\"\"" ); file.putChar( delim ); file.write( "\r\n\\\"\u2019\\\\52\"" ); // 2DQUOTE delim CRLF backslashed-DQUOTE U+2019 backslashed-backslash + file.putChar( delim ); + file.write( "\"val63\"" ); // End without CRLF + file.close(); + + Merge* merge = Factory::createMerge( id ); + QCOMPARE( merge->id(), id ); + + merge->setSource( file.fileName() ); + QCOMPARE( merge->source(), file.fileName() ); + + const QList& recordList = merge->recordList(); + QCOMPARE( recordList.size(), 6 ); + + // + // Records + // + const char* h1 = keyed ? "header1" : "1"; + const char* h2 = keyed ? "header 2" : "2"; + const char* h3 = keyed ? "header3" : "3"; + const Record* record; + + record = recordList[0]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( " val11" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "\"val 12\"" ) ); + QVERIFY( record->contains( h3 ) ); + QCOMPARE( record->value( h3 ), QString( " \"val 13\"" ) ); // NOTE: Treats as unquoted due to leading spaces + + record = recordList[1]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( " val21\"" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "\"val 22" ) ); + QVERIFY( record->contains( h3 ) ); + QCOMPARE( record->value( h3 ), QString( "" ) ); + + record = recordList[2]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( "\"\"" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "val \"32" ) ); + QVERIFY( record->contains( h3 ) ); + QCOMPARE( record->value( h3 ), QString( "val \"\"33" ) ); + + record = recordList[3]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( "" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "" ) ); + QVERIFY( record->contains( h3 ) ); + QCOMPARE( record->value( h3 ), QString( "" ) ); + + record = recordList[4]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( "val\n \t r \\ x51" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "val\n \t r \\ x52" ) ); + QVERIFY( !record->contains( h3 ) ); + + record = recordList[5]; + QVERIFY( record->contains( h1 ) ); + QCOMPARE( record->value( h1 ), QString( "val \"61" ) ); + QVERIFY( record->contains( h2 ) ); + QCOMPARE( record->value( h2 ), QString( "val\"" ).append( delim ).append( "\n\"\u2019\\52" ) ); // NOTE: CR missing (QIODevice::Text strips all CRs from stream) + QVERIFY( record->contains( h3 ) ); + QCOMPARE( record->value( h3 ), QString( "val63" ) ); + + // + // Selection + // + QCOMPARE( merge->nSelectedRecords(), 6 ); // Initially all selected + merge->unselectAll(); + QCOMPARE( merge->nSelectedRecords(), 0 ); + + record = recordList[1]; + merge->select( (Record*)record ); + QCOMPARE( merge->nSelectedRecords(), 1 ); + QCOMPARE( merge->selectedRecords().size(), 1 ); + QCOMPARE( merge->selectedRecords().first(), record ); // Pointers same + + merge->unselect( (Record*)record ); + QCOMPARE( merge->nSelectedRecords(), 0 ); + QCOMPARE( merge->selectedRecords().size(), 0 ); + + merge->setSelected( 0 ); + merge->setSelected( 3 ); + QCOMPARE( merge->nSelectedRecords(), 2 ); + QCOMPARE( merge->selectedRecords().size(), 2 ); + QCOMPARE( merge->selectedRecords().first(), recordList[0] ); + QCOMPARE( merge->selectedRecords().last(), recordList[3] ); + + merge->setSelected( 0, false ); + QCOMPARE( merge->nSelectedRecords(), 1 ); + QCOMPARE( merge->selectedRecords().size(), 1 ); + + // + // Keys + // + QStringList keys = merge->keys(); + QCOMPARE( keys.size(), 3 ); + QCOMPARE( keys[0], QString( h1 ) ); + QCOMPARE( keys[1], QString( h2 ) ); + QCOMPARE( keys[2], QString( h3 ) ); + QCOMPARE( merge->primaryKey(), QString( h1 ) ); + + // + // Clone + // + merge->unselectAll(); + merge->setSelected( 0 ); + QCOMPARE( merge->nSelectedRecords(), 1 ); + + Merge* cloneMerge = merge->clone(); + QCOMPARE( cloneMerge->id(), merge->id() ); + QCOMPARE( cloneMerge->source(), merge->source() ); + QCOMPARE( cloneMerge->recordList().size(), merge->recordList().size() ); + QCOMPARE( *(cloneMerge->recordList()[0]), *(merge->recordList()[0]) ); // Pointers different + QCOMPARE( *(cloneMerge->recordList()[1]), *(merge->recordList()[1]) ); + QCOMPARE( *(cloneMerge->recordList()[2]), *(merge->recordList()[2]) ); + QCOMPARE( *(cloneMerge->recordList()[3]), *(merge->recordList()[3]) ); + QCOMPARE( *(cloneMerge->recordList()[4]), *(merge->recordList()[4]) ); + QCOMPARE( *(cloneMerge->recordList()[5]), *(merge->recordList()[5]) ); + QCOMPARE( cloneMerge->nSelectedRecords(), merge->nSelectedRecords() ); + QCOMPARE( cloneMerge->selectedRecords().size(), merge->selectedRecords().size() ); + QCOMPARE( *(cloneMerge->selectedRecords()[0]), *(merge->selectedRecords()[0]) ); + QCOMPARE( cloneMerge->keys(), merge->keys() ); + QCOMPARE( cloneMerge->primaryKey(), merge->primaryKey() ); + delete cloneMerge; + delete merge; +} + + +void TestMerge::none() +{ + None none; + QCOMPARE( none.id(), QString( "None" ) ); + + None* cloneNone = none.clone(); + QCOMPARE( cloneNone->id(), none.id() ); + QCOMPARE( cloneNone->keys(), none.keys() ); + QCOMPARE( cloneNone->primaryKey(), none.primaryKey() ); + delete cloneNone; +} + + +void TestMerge::record() +{ + Record record; + QCOMPARE( record.isSelected(), true ); + record.setSelected( false ); + QCOMPARE( record.isSelected(), false ); + record.setSelected( true ); + QCOMPARE( record.isSelected(), true ); + + record["key"] = "val"; + QVERIFY( record.contains( "key" ) ); + QCOMPARE( record["key"], QString( "val" ) ); + + Record* cloneRecord = record.clone(); + QCOMPARE( cloneRecord->isSelected(), true ); + QVERIFY( cloneRecord->contains( "key" ) ); + QCOMPARE( cloneRecord->value( "key" ), QString( "val" ) ); + delete cloneRecord; + + record.setSelected( false ); + Record record2( &record ); + QCOMPARE( record2.isSelected(), false ); + QVERIFY( record2.contains( "key" ) ); + QCOMPARE( record2["key"], QString( "val" ) ); +} diff --git a/model/unit_tests/TestMerge.h b/model/unit_tests/TestMerge.h new file mode 100644 index 0000000..5fd43bd --- /dev/null +++ b/model/unit_tests/TestMerge.h @@ -0,0 +1,37 @@ +/* TestMerge.h + * + * Copyright (C) 2019 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 + + +class TestMerge : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void factory_data(); + void factory(); + void factoryNotRegistered(); + void text_data(); + void text(); + void none(); + void record(); +}; diff --git a/model/unit_tests/TestModel.cpp b/model/unit_tests/TestModel.cpp new file mode 100644 index 0000000..31fc103 --- /dev/null +++ b/model/unit_tests/TestModel.cpp @@ -0,0 +1,475 @@ +/* TestModel.cpp + * + * Copyright (C) 2019 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 "TestModel.h" + +#include "model/Model.h" +#include "model/ModelBoxObject.h" +#include "model/ModelEllipseObject.h" +#include "model/ModelLineObject.h" +#include "model/ModelTextObject.h" +#include "model/FrameRect.h" +#include "model/FrameContinuous.h" +#include "model/Region.h" +#include "model/Settings.h" + +#include "merge/Factory.h" +#include "merge/Merge.h" +#include "merge/None.h" +#include "merge/TextCsv.h" +#include "merge/TextCsvKeys.h" + +#include + + +QTEST_MAIN(TestModel) + +using namespace glabels::model; +using namespace glabels::merge; + + +void TestModel::initTestCase() +{ + Factory::init(); + Settings::init(); +} + + +void TestModel::model() +{ + Model model; + QVERIFY( model.isModified() ); + model.clearModified(); + QVERIFY( !model.isModified() ); + + QVERIFY( model.shortName().contains( QRegExp( "^Untitled[1-9][0-9]*$" ) ) ); + model.setFileName( "dir/file1.ext" ); + QCOMPARE( model.fileName(), QString( "dir/file1.ext" ) ); + QCOMPARE( model.shortName(), QString( "file1" ) ); + QVERIFY( !model.isModified() ); + + QCOMPARE( model.w(), Distance( 0 ) ); + QCOMPARE( model.h(), Distance( 0 ) ); + + Template tmplate( "Test Brand", "part", "desc", "testPaperId", 100, 400 ); + FrameRect* frame = new FrameRect( 100, 200, 5, 0, 0, "rect1" ); + QVERIFY( frame->w() != frame->h() ); + tmplate.addFrame( frame ); + model.setTmplate( &tmplate ); // Copies + QCOMPARE( model.tmplate()->brand(), QString( "Test Brand" ) ); + QCOMPARE( model.tmplate()->part(), QString( "part" ) ); + QCOMPARE( model.tmplate()->description(), QString( "desc" ) ); + QCOMPARE( model.tmplate()->paperId(), QString( "testPaperId" ) ); + QCOMPARE( model.tmplate()->pageWidth(), Distance( 100 ) ); + QCOMPARE( model.tmplate()->pageHeight(), Distance( 400 ) ); + QVERIFY( model.isModified() ); + + QVERIFY( model.frame()->id() == frame->id() ); + QCOMPARE( model.w(), Distance( 100 ) ); + QCOMPARE( model.h(), Distance( 200 ) ); + QCOMPARE( model.w(), frame->w() ); + QCOMPARE( model.h(), frame->h() ); + + model.clearModified(); + QVERIFY( !model.isModified() ); + + QVERIFY( !model.rotate() ); + model.setRotate( false ); + QVERIFY( !model.rotate() ); + QVERIFY( !model.isModified() ); + model.setRotate( true ); + QVERIFY( model.rotate() ); + QVERIFY( model.isModified() ); + + QCOMPARE( model.w(), frame->h() ); + QCOMPARE( model.h(), frame->w() ); + + model.setRotate( false ); + QVERIFY( !model.rotate() ); + + model.clearModified(); + QVERIFY( !model.isModified() ); + + model.setH( 300 ); // Default does nothing + QCOMPARE( model.h(), Distance( 200 ) ); + QVERIFY( model.isModified() ); // Set anyway + + // Continuous frame implements setH() + Template tmplate2( "Test Brand2", "part2", "desc2", "testPaperId2", 100, 400 ); + FrameContinuous* frame2 = new FrameContinuous( 100, 0, 500, 200, "continuous1" ); + QCOMPARE( frame2->h(), Distance( 200 ) ); + tmplate2.addFrame( frame2 ); + model.setTmplate( &tmplate2 ); + QVERIFY( model.frame()->id() == frame2->id() ); + QCOMPARE( model.w(), Distance( 100 ) ); + QCOMPARE( model.h(), Distance( 200 ) ); + QCOMPARE( model.w(), frame2->w() ); + QCOMPARE( model.h(), frame2->h() ); + + model.clearModified(); + QVERIFY( !model.isModified() ); + + model.setH( 300 ); + QCOMPARE( model.h(), Distance( 300 ) ); + QVERIFY( model.isModified() ); + + // + // Objects + // + ColorNode black( Qt::black ); + ModelObject* ellipse = new ModelEllipseObject( 1, 0, 100, 100, false, 1, black, black ); + ModelObject* box = new ModelBoxObject( 1, 100, 100, 100, false, 1, black, black ); + ModelObject* line = new ModelLineObject( 1, 200, 99 /*dx*/, 1 /*dy*/, 1.0, black ); + ModelObject* text = new ModelTextObject( 1, 201, 100, 30, false, "", "Sans", 10, QFont::Normal, false, false, black, Qt::AlignLeft, Qt::AlignTop, QTextOption::WordWrap, 1, false ); + + model.clearModified(); + QVERIFY( !model.isModified() ); + + model.addObject( ellipse ); + QVERIFY( model.isModified() ); + model.addObject( box ); + model.addObject( line ); + model.addObject( text ); + + QCOMPARE( model.objectList().size(), 4 ); + + ModelObject* line2 = new ModelLineObject( 1, 231, 100 /*dx*/, 1 /*dy*/, 1.0, black ); + model.addObject( line2 ); + QCOMPARE( model.objectList().size(), 5 ); + + model.clearModified(); + QVERIFY( !model.isModified() ); + + model.deleteObject( line2 ); + QCOMPARE( model.objectList().size(), 4 ); + QVERIFY( model.isModified() ); + + ModelObject* object; + + object = model.objectAt( 1 /*scale*/, 1, 200 ); + QVERIFY( object ); + QVERIFY( dynamic_cast(object) ); + QCOMPARE( object->id(), line->id() ); + + object = model.objectAt( 1 /*scale*/, 100, 150 ); + QVERIFY( object ); + QVERIFY( dynamic_cast(object) ); + QCOMPARE( object->id(), box->id() ); + + object = model.objectAt( 1 /*scale*/, 50, 0 ); + QVERIFY( object ); + QVERIFY( dynamic_cast(object) ); + QCOMPARE( object->id(), ellipse->id() ); + + object = model.objectAt( 1 /*scale*/, 1 + 3, 201 + 3 ); // Allow for text offset + QVERIFY( object ); + QVERIFY( dynamic_cast(object) ); + QCOMPARE( object->id(), text->id() ); + + // + // Selection + // + QVERIFY( model.isSelectionEmpty() ); + QVERIFY( !model.isSelectionAtomic() ); + QVERIFY( model.getSelection().isEmpty() ); + QVERIFY( !model.getFirstSelectedObject() ); + + QVERIFY( !model.canSelectionText() ); + QVERIFY( !model.canSelectionFill() ); + QVERIFY( !model.canSelectionLineColor() ); + QVERIFY( !model.canSelectionLineWidth() ); + + model.selectAll(); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( !model.isSelectionAtomic() ); + QVERIFY( !model.getSelection().isEmpty() ); + QCOMPARE( model.getSelection().size(), 4 ); + QCOMPARE( model.getSelection().first()->id(), ellipse->id() ); + QCOMPARE( model.getSelection().at(1)->id(), box->id() ); + QCOMPARE( model.getSelection().at(2)->id(), line->id() ); + QCOMPARE( model.getSelection().at(3)->id(), text->id() ); + QVERIFY( model.getFirstSelectedObject() ); + QCOMPARE( model.getFirstSelectedObject()->id(), ellipse->id() ); + + QVERIFY( model.canSelectionText() ); + QVERIFY( model.canSelectionFill() ); + QVERIFY( model.canSelectionLineColor() ); + QVERIFY( model.canSelectionLineWidth() ); + + model.unselectAll(); + QVERIFY( model.isSelectionEmpty() ); + QVERIFY( !model.isSelectionAtomic() ); + QVERIFY( model.getSelection().isEmpty() ); + QVERIFY( !model.getFirstSelectedObject() ); + + model.selectObject( text ); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( model.isSelectionAtomic() ); + QCOMPARE( model.getSelection().size(), 1 ); + QCOMPARE( model.getFirstSelectedObject()->id(), text->id() ); + + QVERIFY( model.canSelectionText() ); + QVERIFY( !model.canSelectionFill() ); + QVERIFY( !model.canSelectionLineColor() ); + QVERIFY( !model.canSelectionLineWidth() ); + + model.unselectObject( text ); + QVERIFY( model.isSelectionEmpty() ); + + model.selectObject( line ); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( model.isSelectionAtomic() ); + QCOMPARE( model.getSelection().size(), 1 ); + QCOMPARE( model.getFirstSelectedObject()->id(), line->id() ); + + QVERIFY( !model.canSelectionText() ); + QVERIFY( !model.canSelectionFill() ); + QVERIFY( model.canSelectionLineColor() ); + QVERIFY( model.canSelectionLineWidth() ); + + model.unselectAll(); + QVERIFY( model.isSelectionEmpty() ); + + double margin = 0.5; // Allow 0.5pt margin + Region region( 1 - margin, 302 - margin, 101 + margin /*x2*/, 302 + margin /*y2*/ ); // Outside all objects + model.selectRegion( region ); + QVERIFY( model.getSelection().isEmpty() ); + QVERIFY( model.isSelectionEmpty() ); + + region.setY1( 0 - margin ); // Ellipse + region.setY2( 100 + margin ); + model.selectRegion( region ); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( model.isSelectionAtomic() ); + QCOMPARE( model.getSelection().size(), 1 ); + QCOMPARE( model.getFirstSelectedObject()->id(), ellipse->id() ); + + QVERIFY( !model.canSelectionText() ); + QVERIFY( model.canSelectionFill() ); + QVERIFY( model.canSelectionLineColor() ); + QVERIFY( model.canSelectionLineWidth() ); + + region.setY1( 200 - margin ); // Line + region.setY2( 201 + margin ); + model.selectRegion( region ); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( !model.isSelectionAtomic() ); // Accumulative + QCOMPARE( model.getSelection().size(), 2 ); + QCOMPARE( model.getSelection().at(0)->id(), ellipse->id() ); + QCOMPARE( model.getSelection().at(1)->id(), line->id() ); + + model.unselectObject( ellipse ); + QVERIFY( !model.isSelectionEmpty() ); + QVERIFY( model.isSelectionAtomic() ); + QCOMPARE( model.getSelection().size(), 1 ); + QCOMPARE( model.getFirstSelectedObject()->id(), line->id() ); + + model.unselectAll(); + QVERIFY( model.isSelectionEmpty() ); + + // TODO: Operations on selections etc +} + + +void TestModel::saveRestore() +{ + Model* model = new Model; + QVERIFY( model->isModified() ); + model->clearModified(); + QVERIFY( !model->isModified() ); + + // + // Set template/frame + // + Template tmplate( "Test Brand", "part", "desc", "testPaperId", 110, 410 ); + FrameRect* frame = new FrameRect( 120, 220, 5, 0, 0, "rect1" ); + tmplate.addFrame( frame ); + model->setTmplate( &tmplate ); // Copies + QCOMPARE( model->tmplate()->brand(), QString( "Test Brand" ) ); + QVERIFY( model->isModified() ); + + model->clearModified(); + QVERIFY( !model->isModified() ); + + // + // Set merge + // + Merge* merge = Factory::createMerge( TextCsvKeys::id() ); + QCOMPARE( merge->id(), TextCsvKeys::id() ); + + model->setMerge( merge ); + QCOMPARE( model->merge(), merge ); + QVERIFY( model->isModified() ); + + model->clearModified(); + QVERIFY( !model->isModified() ); + + QTemporaryFile csv; + csv.open(); + csv.write( "id,text\n1,text1\n2,text2\n3,text3\n" ); + csv.close(); + + merge->setSource( csv.fileName() ); + QCOMPARE( merge->source(), csv.fileName() ); + QCOMPARE( merge->recordList().size(), 3 ); + QVERIFY( model->isModified() ); + + model->clearModified(); + QVERIFY( !model->isModified() ); + + // + // Add some objects + // + ColorNode black( Qt::black ); + ModelObject* object1 = new ModelLineObject( 1, 1, 90, 80, 1.0, black ); + model->addObject( object1 ); + QVERIFY( model->isModified() ); + QCOMPARE( model->objectList().size(), 1 ); + QCOMPARE( model->objectList().first(), object1 ); + + model->clearModified(); + QVERIFY( !model->isModified() ); + + ModelObject* object2 = new ModelTextObject( 2, 2, 70, 30, false, "", "Sans", 10, QFont::Normal, false, false, black, Qt::AlignLeft, Qt::AlignTop, QTextOption::WordWrap, 1, false ); + model->addObject( object2 ); + QVERIFY( model->isModified() ); + QCOMPARE( model->objectList().size(), 2 ); + QCOMPARE( model->objectList().last(), object2 ); + + QString modelShortName = model->shortName(); // If no fileName set then model expects to have have this called before being saved/restored (otherwise get differing untitled names) + + // + // Test + // + Model* saved = model->save(); + QVERIFY( saved->isModified() ); + QCOMPARE( saved->merge(), model->merge() ); // Shared + QCOMPARE( saved->isModified(), model->isModified() ); + QCOMPARE( saved->shortName(), modelShortName ); + QCOMPARE( saved->shortName(), model->shortName() ); + QCOMPARE( saved->fileName(), model->fileName() ); + QCOMPARE( saved->rotate(), model->rotate() ); + QCOMPARE( saved->objectList().size(), model->objectList().size() ); + QVERIFY( saved->objectList().at(0) != object1 ); // Objects copied + QVERIFY( saved->objectList().at(1) != object2 ); // Objects copied + QCOMPARE( saved->objectList().at(0)->x0(), model->objectList().at(0)->x0() ); + QCOMPARE( saved->objectList().at(0)->y0(), model->objectList().at(0)->y0() ); + QCOMPARE( saved->objectList().at(1)->x0(), model->objectList().at(1)->x0() ); + QCOMPARE( saved->objectList().at(1)->y0(), model->objectList().at(1)->y0() ); + + // Modify original + Template tmplate2( "Test Brand2", "part2", "desc2", "testPaperId2", 230, 630 ); + FrameRect* frame2 = new FrameRect( 240, 340, 5, 0, 0, "rect2" ); + tmplate2.addFrame( frame2 ); + model->setTmplate( &tmplate2 ); + QCOMPARE( model->tmplate()->brand(), QString( "Test Brand2" ) ); + QCOMPARE( model->w(), Distance( 240 ) ); + QCOMPARE( model->h(), Distance( 340 ) ); + + model->setFileName( "dir/file1.ext" ); + QCOMPARE( model->shortName(), QString( "file1" ) ); + + model->setRotate( true ); + QVERIFY( model->rotate() ); + QCOMPARE( model->w(), Distance( 340 ) ); + QCOMPARE( model->h(), Distance( 240 ) ); + + model->deleteObject( model->objectList().first() ); + QCOMPARE( model->objectList().size(), 1 ); + QCOMPARE( model->objectList().first(), object2 ); + + model->objectList().first()->setY0( model->objectList().first()->y0() + 1 ); + + Merge* merge2 = Factory::createMerge( TextCsv::id() ); + QCOMPARE( merge2->id(), TextCsv::id() ); + QTemporaryFile csv2; csv2.open(); csv2.write( "21,text21\n22,text22\n23,text23\n24,text24\n" ); csv2.close(); + merge2->setSource( csv2.fileName() ); + QCOMPARE( merge2->source(), csv2.fileName() ); + QCOMPARE( merge2->recordList().size(), 4 ); + + model->setMerge( merge2 ); // Deletes original so saved->merge() now invalid + QCOMPARE( model->merge(), merge2 ); + + Model* modified = model->save(); + QCOMPARE( modified->merge(), merge2 ); // Shared + + // Verify differences + QVERIFY( model->shortName() != modelShortName ); + QVERIFY( model->shortName() != saved->shortName() ); + QVERIFY( model->fileName() != saved->fileName() ); + QVERIFY( model->tmplate()->brand() != saved->tmplate()->brand() ); + QVERIFY( model->rotate() != saved->rotate() ); + QVERIFY( model->w() != saved->w() ); + QVERIFY( model->h() != saved->h() ); + QVERIFY( model->objectList().size() != saved->objectList().size() ); + QVERIFY( model->objectList().at(0)->x0() != saved->objectList().at(0)->x0() ); + QVERIFY( model->objectList().at(0)->y0() != saved->objectList().at(0)->y0() ); + QCOMPARE( model->objectList().at(0)->x0(), saved->objectList().at(1)->x0() ); // Unchanged + QVERIFY( model->objectList().at(0)->y0() != saved->objectList().at(1)->y0() ); + + // Restore + model->restore( saved ); + QCOMPARE( model->shortName(), modelShortName ); + QCOMPARE( model->shortName(), saved->shortName() ); + QCOMPARE( model->fileName(), saved->fileName() ); + QCOMPARE( model->tmplate()->brand(), saved->tmplate()->brand() ); + QCOMPARE( model->rotate(), saved->rotate() ); + QCOMPARE( model->w(), saved->w() ); + QCOMPARE( model->h(), saved->h() ); + QCOMPARE( model->objectList().size(), saved->objectList().size() ); + QCOMPARE( model->objectList().size(), 2 ); + QCOMPARE( model->objectList().at(0)->x0(), saved->objectList().at(0)->x0() ); + QCOMPARE( model->objectList().at(0)->y0(), saved->objectList().at(0)->y0() ); + QCOMPARE( model->objectList().at(1)->x0(), saved->objectList().at(1)->x0() ); + QCOMPARE( model->objectList().at(1)->y0(), saved->objectList().at(1)->y0() ); + + QCOMPARE( model->merge(), merge2 ); // Unchanged + QVERIFY( model->merge() != saved->merge() ); // NOTE saved->merge() now points to deleted object + + // Unrestore + model->restore( modified ); + QVERIFY( model->shortName() != modelShortName ); + QVERIFY( model->shortName() != saved->shortName() ); + QVERIFY( model->fileName() != saved->fileName() ); + QVERIFY( model->tmplate()->brand() != saved->tmplate()->brand() ); + QVERIFY( model->rotate() != saved->rotate() ); + QVERIFY( model->w() != saved->w() ); + QVERIFY( model->h() != saved->h() ); + QCOMPARE( model->objectList().size(), 1 ); + QVERIFY( model->objectList().size() != saved->objectList().size() ); + QVERIFY( model->objectList().at(0)->x0() != saved->objectList().at(0)->x0() ); + QVERIFY( model->objectList().at(0)->y0() != saved->objectList().at(0)->y0() ); + QCOMPARE( model->merge(), merge2 ); // Same + + QCOMPARE( model->shortName(), modified->shortName() ); + QCOMPARE( model->fileName(), modified->fileName() ); + QCOMPARE( model->tmplate()->brand(), modified->tmplate()->brand() ); + QCOMPARE( model->rotate(), modified->rotate() ); + QCOMPARE( model->w(), modified->w() ); + QCOMPARE( model->h(), modified->h() ); + QCOMPARE( model->objectList().size(), modified->objectList().size() ); + QCOMPARE( model->objectList().at(0)->x0(), modified->objectList().at(0)->x0() ); + QCOMPARE( model->objectList().at(0)->y0(), modified->objectList().at(0)->y0() ); + + delete model->merge(); // Final instance owned by us + delete model; + delete saved; + delete modified; +} diff --git a/model/unit_tests/TestModel.h b/model/unit_tests/TestModel.h new file mode 100644 index 0000000..5a7f580 --- /dev/null +++ b/model/unit_tests/TestModel.h @@ -0,0 +1,32 @@ +/* TestModel.h + * + * Copyright (C) 2019 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 + + +class TestModel : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void model(); + void saveRestore(); +}; diff --git a/model/unit_tests/TestRawText.cpp b/model/unit_tests/TestRawText.cpp new file mode 100644 index 0000000..de48118 --- /dev/null +++ b/model/unit_tests/TestRawText.cpp @@ -0,0 +1,94 @@ +/* TestRawText.cpp + * + * Copyright (C) 2019 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 "TestRawText.h" + +#include "model/RawText.h" + +#include "merge/Record.h" + +#include + + +QTEST_MAIN(TestRawText) + +using namespace glabels::model; +using namespace glabels::merge; + + +void TestRawText::rawText() +{ + RawText rawText; + Record record; + + QVERIFY( rawText.isEmpty() ); + QVERIFY( !rawText.hasPlaceHolders() ); + QCOMPARE( rawText.toString(), QString( "" ) ); + QCOMPARE( rawText.toStdString(), std::string( "" ) ); + QCOMPARE( rawText.expand( &record ), QString( "" ) ); + + rawText = "text"; + QVERIFY( !rawText.isEmpty() ); + QVERIFY( !rawText.hasPlaceHolders() ); + QCOMPARE( rawText.toString(), QString( "text" ) ); + QCOMPARE( rawText.toStdString(), std::string( "text" ) ); + QCOMPARE( rawText.expand( &record ), QString( "text" ) ); + + RawText rawText2( "text" ); + QVERIFY( !rawText2.isEmpty() ); + QVERIFY( !rawText2.hasPlaceHolders() ); + QCOMPARE( rawText2.toString(), QString( "text" ) ); + + rawText = "${key1}"; + QVERIFY( !rawText.isEmpty() ); + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.toString(), QString( "${key1}" ) ); + QCOMPARE( rawText.toStdString(), std::string( "${key1}" ) ); + QCOMPARE( rawText.expand( &record ), QString( "" ) ); + + /// + /// Record + /// + record["key1"] = "val1"; + QCOMPARE( rawText.expand( &record ), QString( "val1" ) ); + + rawText = "${key1}${key2}"; + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.expand( &record ), QString( "val1" ) ); + + record["key2"] = "val2"; + QCOMPARE( rawText.expand( &record ), QString( "val1val2" ) ); + + rawText = "${key1}text${key2}"; + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.expand( &record ), QString( "val1textval2" ) ); + + rawText = "text1${key1}text2${key2}text3"; + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.expand( &record ), QString( "text1val1text2val2text3" ) ); + + rawText = "${key1}text${key2}${key3}"; + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.expand( &record ), QString( "val1textval2" ) ); + + rawText = "${key2}${key3}${key1}"; + QVERIFY( rawText.hasPlaceHolders() ); + QCOMPARE( rawText.expand( &record ), QString( "val2val1" ) ); +} diff --git a/model/unit_tests/TestRawText.h b/model/unit_tests/TestRawText.h new file mode 100644 index 0000000..781ecf8 --- /dev/null +++ b/model/unit_tests/TestRawText.h @@ -0,0 +1,30 @@ +/* TestRawText.h + * + * Copyright (C) 2019 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 + + +class TestRawText : public QObject +{ + Q_OBJECT + +private slots: + void rawText(); +}; diff --git a/model/unit_tests/TestTextNode.cpp b/model/unit_tests/TestTextNode.cpp new file mode 100644 index 0000000..3800803 --- /dev/null +++ b/model/unit_tests/TestTextNode.cpp @@ -0,0 +1,105 @@ +/* TestTextNode.cpp + * + * Copyright (C) 2019 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 "TestTextNode.h" + +#include "model/TextNode.h" + +#include "merge/Record.h" + +#include + + +QTEST_MAIN(TestTextNode) + +using namespace glabels::model; +using namespace glabels::merge; + + +void TestTextNode::textNode() +{ + Record record; + + TextNode textNode; + QVERIFY( !textNode.isField() ); + QCOMPARE( textNode.data(), QString( "" ) ); + QVERIFY( textNode == TextNode() ); + QVERIFY( !(textNode != TextNode()) ); + QCOMPARE( textNode.text( nullptr ), QString( "" ) ); + QCOMPARE( textNode.text( &record ), QString( "" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); + + textNode.setField( true ); + QVERIFY( textNode.isField() ); + QCOMPARE( textNode.text( &record ), QString( "" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); + + textNode.setField( false ); + QVERIFY( !textNode.isField() ); + + textNode.setData( QString( "data1" ) ); + QCOMPARE( textNode.data(), QString( "data1" ) ); + QCOMPARE( textNode.text( nullptr ), QString( "data1" ) ); + QCOMPARE( textNode.text( &record ), QString( "data1" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); + + textNode.setField( true ); + QCOMPARE( textNode.text( nullptr ), QString( "${data1}" ) ); + QCOMPARE( textNode.text( &record ), QString( "" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); + + /// + /// Constructors + /// + TextNode textNode2( true, "data2" ); + QVERIFY( textNode2.isField() ); + QCOMPARE( textNode2.data(), QString( "data2" ) ); + textNode.setField( false ); + QVERIFY( !(textNode2 == textNode) ); + QVERIFY( textNode2 != textNode ); + textNode.setField( true ); + QVERIFY( !(textNode2 == textNode) ); + QVERIFY( textNode2 != textNode ); + textNode.setData( QString( "data2" ) ); + QVERIFY( textNode2 == textNode ); + QVERIFY( !(textNode2 != textNode) ); + + /// + /// Record + /// + record["key1"] = ""; + QCOMPARE( textNode.text( &record ), QString( "" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); + + textNode.setData( QString( "key1" ) ); + QCOMPARE( textNode.text( &record ), QString( "" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( textNode.isEmptyField( &record ) ); + + record["key1"] = "val1"; + QCOMPARE( textNode.text( &record ), QString( "val1" ) ); + QVERIFY( !textNode.isEmptyField( nullptr ) ); + QVERIFY( !textNode.isEmptyField( &record ) ); +} diff --git a/model/unit_tests/TestTextNode.h b/model/unit_tests/TestTextNode.h new file mode 100644 index 0000000..76553d3 --- /dev/null +++ b/model/unit_tests/TestTextNode.h @@ -0,0 +1,30 @@ +/* TestTextNode.h + * + * Copyright (C) 2019 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 + + +class TestTextNode : public QObject +{ + Q_OBJECT + +private slots: + void textNode(); +}; diff --git a/translations/glabels_C.ts b/translations/glabels_C.ts index 4c4c4b6..e60e71f 100644 --- a/translations/glabels_C.ts +++ b/translations/glabels_C.ts @@ -1209,6 +1209,10 @@ Delete + + Resize + + glabels::MainWindow @@ -1740,6 +1744,14 @@ Quick Access Toolbar + + Undo %1 + + + + Redo %1 + + glabels::MergeView @@ -1898,10 +1910,6 @@ Set image - - Move - - Size @@ -1914,6 +1922,18 @@ Shadow + + Position + + + + Barcode + + + + Reset + + glabels::PrintView