From 8bcec37d7c94739d1a672268ad2691bc9c15c9ed Mon Sep 17 00:00:00 2001 From: Jim Evins Date: Sat, 18 Nov 2017 22:36:43 -0500 Subject: [PATCH] Created SubstitutionField class. --- CMakeLists.txt | 29 +- glabels/Merge/CMakeLists.txt | 2 + glabels/Merge/SubstitutionField.cpp | 291 ++++++++++++++++++ glabels/Merge/SubstitutionField.h | 77 +++++ glabels/Merge/unit_tests/CMakeLists.txt | 13 + .../unit_tests/TestSubstitutionField.cpp | 222 +++++++++++++ .../Merge/unit_tests/TestSubstitutionField.h | 37 +++ translations/glabels_C.ts | 2 +- 8 files changed, 666 insertions(+), 7 deletions(-) create mode 100644 glabels/Merge/SubstitutionField.cpp create mode 100644 glabels/Merge/SubstitutionField.h create mode 100644 glabels/Merge/unit_tests/CMakeLists.txt create mode 100644 glabels/Merge/unit_tests/TestSubstitutionField.cpp create mode 100644 glabels/Merge/unit_tests/TestSubstitutionField.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 98a5837..ba8471c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,11 +35,11 @@ if (MINGW) set (CMAKE_PREFIX_PATH ${MINGW_BASE_DIR} ) endif () -find_package(Qt5Widgets 5.4 REQUIRED) -find_package(Qt5PrintSupport 5.4 REQUIRED) -find_package(Qt5Xml 5.4 REQUIRED) -find_package(Qt5Svg 5.4 REQUIRED) -find_package(Qt5LinguistTools) +find_package (Qt5Widgets 5.4 REQUIRED) +find_package (Qt5PrintSupport 5.4 REQUIRED) +find_package (Qt5Xml 5.4 REQUIRED) +find_package (Qt5Svg 5.4 REQUIRED) +find_package (Qt5LinguistTools) if (MINGW) # Locate Qt directories @@ -47,7 +47,7 @@ if (MINGW) set (QT_BIN_DIR ${QT_BASE_DIR}/bin) endif () -find_package(ZLIB 1.2 REQUIRED) +find_package (ZLIB 1.2 REQUIRED) # # Optional dependencies @@ -55,6 +55,17 @@ find_package(ZLIB 1.2 REQUIRED) find_package (GnuBarcode 0.98 QUIET) find_package (LibQrencode 3.4 QUIET) find_package (LibZint 2.6 QUIET) +# Unit testing support +find_package (Qt5Test 5.4 QUIET) + + +#======================================= +# Unit Testing +#======================================= +if (Qt5Test_FOUND) + enable_testing () +endif () + #======================================= # Subdirectories @@ -96,6 +107,12 @@ else (LIBZINT_FOUND) message (STATUS "libzint (optional)....... No.") endif (LIBZINT_FOUND) +if (Qt5Test_FOUND) + message (STATUS "QtTest (optional)........ " ${Qt5Test_VERSION}) +else (Qt5Test_FOUND) + message (STATUS "QtTest (optional)........ No.") +endif (Qt5Test_FOUND) + if (MINGW) message (STATUS "MinGW location .......... " ${MINGW_BASE_DIR}) message (STATUS "MinGW Qt location ....... " ${QT_BASE_DIR}) diff --git a/glabels/Merge/CMakeLists.txt b/glabels/Merge/CMakeLists.txt index c06693a..93dee75 100644 --- a/glabels/Merge/CMakeLists.txt +++ b/glabels/Merge/CMakeLists.txt @@ -15,6 +15,7 @@ set (merge_sources TextColonKeys.cpp TextSemicolon.cpp TextSemicolonKeys.cpp + SubstitutionField.cpp ) set (merge_qobject_headers @@ -43,6 +44,7 @@ link_directories ( #======================================= # Subdirectories #======================================= +add_subdirectory (unit_tests) #======================================= diff --git a/glabels/Merge/SubstitutionField.cpp b/glabels/Merge/SubstitutionField.cpp new file mode 100644 index 0000000..ebbc4f2 --- /dev/null +++ b/glabels/Merge/SubstitutionField.cpp @@ -0,0 +1,291 @@ +/* SubstitutionField.cpp + * + * Copyright (C) 2017 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 "SubstitutionField.h" + +#include + + +namespace glabels +{ + + merge::SubstitutionField::SubstitutionField( const QString& string ) + { + QStringRef s(&string); + parseSubstitutionField( s ); + } + + + QString merge::SubstitutionField::evaluate( const merge::Record& record ) const + { + QString value = mDefaultValue; + + if ( record.contains(mFieldName) ) + { + value = record[mFieldName]; + } + + if ( mFormatType.isNull() ) + { + return value; + } + else + { + return formatValue( value ); + } + } + + + QString merge::SubstitutionField::fieldName() const + { + return mFieldName; + } + + + QString merge::SubstitutionField::defaultValue() const + { + return mDefaultValue; + } + + + QString merge::SubstitutionField::format() const + { + return mFormat; + } + + + QChar merge::SubstitutionField::formatType() const + { + return mFormatType; + } + + + QString merge::SubstitutionField::formatValue( const QString& value ) const + { + switch (mFormatType.unicode()) + { + + case 'd': + case 'i': + return QString::asprintf( mFormat.toStdString().c_str(), + value.toLongLong(nullptr,0) ); + break; + + + case 'u': + case 'x': + case 'X': + case 'o': + return QString::asprintf( mFormat.toStdString().c_str(), + value.toULongLong(nullptr,0) ); + break; + + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + return QString::asprintf( mFormat.toStdString().c_str(), + value.toDouble() ); + break; + + case 's': + return QString::asprintf( mFormat.toStdString().c_str(), + value.toStdString().c_str() ); + break; + + default: + // Invalid format + return ""; + break; + + } + } + + + void merge::SubstitutionField::parseSubstitutionField( QStringRef& s ) + { + if ( s.startsWith( "${" ) ) + { + s = s.mid(2); + parseFieldName( s ); + + while ( s.size() && s[0] == ':' ) + { + s = s.mid(1); + parseModifier( s ); + } + + if ( s.size() && s[0] == '}' ) + { + s = s.mid(1); + + if ( s.size() ) + { + // Invalid -- extraneous input + } + } + else + { + // Invalid -- expected '}' + } + + } + else + { + // Invalid -- expected '${' + } + } + + + void merge::SubstitutionField::parseFieldName( QStringRef& s ) + { + while ( s.size() && (s[0].isDigit() || s[0].isLetter() || s[0] == '_' || s[0] == '-') ) + { + mFieldName.append( s[0] ); + s = s.mid(1); + } + } + + + void merge::SubstitutionField::parseModifier( QStringRef& s ) + { + if ( s.size() && s[0] == '%' ) + { + s = s.mid(1); + parseFormatModifier( s ); + } + else if ( s.size() && s[0] == '=' ) + { + s = s.mid(1); + parseDefaultValueModifier( s ); + } + else + { + // Invalid -- unrecognized modifier, expecting one of '%' or '=' + } + } + + + void merge::SubstitutionField::parseDefaultValueModifier( QStringRef& s ) + { + while ( s.size() && s[0] != ':' && s[0] != '}' ) + { + if ( s[0] == '\\' ) + { + s = s.mid(1); // Skip escape + if ( s.size() ) + { + mDefaultValue.append( s[0] ); + s = s.mid(1); + } + { + // Invalid -- end of string encountered during escape + } + } + else + { + mDefaultValue.append( s[0] ); + s = s.mid(1); + } + } + } + + + void merge::SubstitutionField::parseFormatModifier( QStringRef& s ) + { + mFormat = "%"; + + mFormat += parseFormatFlags( s ); + mFormat += parseFormatWidth( s ); + + if ( s.size() && s[0] == '.' ) + { + s = s.mid(1); + mFormat += "." + parseFormatPrecision( s ); + } + + mFormatType = parseFormatType( s ); + mFormat += mFormatType; + } + + + QString merge::SubstitutionField::parseFormatFlags( QStringRef& s ) + { + QString flags; + + while ( s.size() && QString( "-+ 0" ).contains( s[0] ) ) + { + flags.append( s[0] ); + s = s.mid(1); + } + + return flags; + } + + + QString merge::SubstitutionField::parseFormatWidth( QStringRef& s ) + { + return parseNaturalInteger( s ); + } + + + QString merge::SubstitutionField::parseFormatPrecision( QStringRef& s ) + { + return parseNaturalInteger( s ); + } + + + QChar merge::SubstitutionField::parseFormatType( QStringRef& s ) + { + QChar type = 0; + + if ( s.size() && QString( "diufFeEgGxXos" ).contains( s[0] ) ) + { + type = s[0]; + s = s.mid(1); + } + + return type; + } + + + QString merge::SubstitutionField::parseNaturalInteger( QStringRef& s ) + { + QString value = ""; + + if ( s.size() && s[0] >= '1' && s[0] <= '9' ) + { + value += s[0]; + s = s.mid(1); + + while ( s.size() && s[0].isDigit() ) + { + value += s[0]; + s = s.mid(1); + } + } + + return value; + } + + +} // namespace glabels diff --git a/glabels/Merge/SubstitutionField.h b/glabels/Merge/SubstitutionField.h new file mode 100644 index 0000000..8836b54 --- /dev/null +++ b/glabels/Merge/SubstitutionField.h @@ -0,0 +1,77 @@ +/* SubstitutionField.h + * + * Copyright (C) 2017 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 glabels_SubstitutionField_h +#define glabels_SubstitutionField_h + + +#include "Record.h" + +#include +#include + + +namespace glabels +{ + + namespace merge + { + + class SubstitutionField + { + public: + SubstitutionField() = default; + SubstitutionField( const QString& string ); + + QString evaluate( const merge::Record& record ) const; + + QString fieldName() const; + QString defaultValue() const; + QString format() const; + QChar formatType() const; + + private: + QString formatValue( const QString& value ) const; + void parseSubstitutionField( QStringRef& s ); + void parseFieldName( QStringRef& s ); + void parseModifier( QStringRef& s ); + void parseDefaultValueModifier( QStringRef& s ); + void parseFormatModifier( QStringRef& s ); + + QString parseFormatFlags( QStringRef& s ); + QString parseFormatWidth( QStringRef& s ); + QString parseFormatPrecision( QStringRef& s ); + QChar parseFormatType( QStringRef& s ); + QString parseNaturalInteger( QStringRef& s ); + + QString mFieldName; + + QString mDefaultValue; + + QString mFormat; + QChar mFormatType; + }; + + } + +} + + +#endif // glabels_SubstitutionField_h diff --git a/glabels/Merge/unit_tests/CMakeLists.txt b/glabels/Merge/unit_tests/CMakeLists.txt new file mode 100644 index 0000000..07066d2 --- /dev/null +++ b/glabels/Merge/unit_tests/CMakeLists.txt @@ -0,0 +1,13 @@ +if (Qt5Test_FOUND) + + include_directories (..) + + #======================================= + # Test SubstitutionField class + #======================================= + qt5_wrap_cpp (TestSubstitutionField_moc_sources TestSubstitutionField.h) + add_executable (TestSubstitutionField TestSubstitutionField.cpp ${TestSubstitutionField_moc_sources}) + target_link_libraries (TestSubstitutionField Merge ${Qt5Core_LIBRARIES} ${Qt5Test_LIBRARIES} ) + add_test (NAME SubstitutionField COMMAND TestSubstitutionField) + +endif (Qt5Test_FOUND) diff --git a/glabels/Merge/unit_tests/TestSubstitutionField.cpp b/glabels/Merge/unit_tests/TestSubstitutionField.cpp new file mode 100644 index 0000000..9c4c6a6 --- /dev/null +++ b/glabels/Merge/unit_tests/TestSubstitutionField.cpp @@ -0,0 +1,222 @@ +/* TestSubstitutionField.cpp + * + * Copyright (C) 2017 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 "TestSubstitutionField.h" + +#include "SubstitutionField.h" + + +QTEST_MAIN(TestSubstitutionField) + + +void TestSubstitutionField::construction() +{ + glabels::merge::SubstitutionField f1( "${1234}" ); + QCOMPARE( f1.fieldName(), QString( "1234" ) ); + + glabels::merge::SubstitutionField f2( "${abc:=ABC}" ); + QCOMPARE( f2.fieldName(), QString( "abc" ) ); + QCOMPARE( f2.defaultValue(), QString( "ABC" ) ); + + glabels::merge::SubstitutionField f3( "${x:%08.2f}" ); + QCOMPARE( f3.fieldName(), QString( "x" ) ); + QCOMPARE( f3.format(), QString( "%08.2f" ) ); + QCOMPARE( f3.formatType(), QChar('f') ); + + glabels::merge::SubstitutionField f4( "${y:%08.2f:=12.34}" ); + QCOMPARE( f4.fieldName(), QString( "y" ) ); + QCOMPARE( f4.defaultValue(), QString( "12.34" ) ); + QCOMPARE( f4.format(), QString( "%08.2f" ) ); + QCOMPARE( f4.formatType(), QChar('f') ); +} + + +void TestSubstitutionField::simpleEvaluation() +{ + glabels::merge::SubstitutionField f1( "${1}" ); + glabels::merge::SubstitutionField f2( "${2}" ); + glabels::merge::SubstitutionField f3( "${3}" ); + glabels::merge::SubstitutionField f4( "${4}" ); + + glabels::merge::Record record1; + record1[ "1" ] = "Abcdefg"; + record1[ "2" ] = "Hijklmn"; + record1[ "3" ] = "Opqrstu"; + record1[ "4" ] = "Vwxyz!@"; + + QCOMPARE( f1.evaluate( record1 ), QString( "Abcdefg" ) ); + QCOMPARE( f2.evaluate( record1 ), QString( "Hijklmn" ) ); + QCOMPARE( f3.evaluate( record1 ), QString( "Opqrstu" ) ); + QCOMPARE( f4.evaluate( record1 ), QString( "Vwxyz!@" ) ); + + glabels::merge::Record record2; + record2[ "1" ] = "1234567"; + record2[ "2" ] = "FooBar"; + record2[ "3" ] = "8901234"; + record2[ "4" ] = "#$%^&*"; + + QCOMPARE( f1.evaluate( record2 ), QString( "1234567" ) ); + QCOMPARE( f2.evaluate( record2 ), QString( "FooBar" ) ); + QCOMPARE( f3.evaluate( record2 ), QString( "8901234" ) ); + QCOMPARE( f4.evaluate( record2 ), QString( "#$%^&*" ) ); +} + + +void TestSubstitutionField::defaultValueEvaluation() +{ + glabels::merge::SubstitutionField f1( "${1:=foo1}" ); + glabels::merge::SubstitutionField f2( "${2:=foo2}" ); + glabels::merge::SubstitutionField f3( "${3:=foo3}" ); + glabels::merge::SubstitutionField f4( "${4:=foo4}" ); + + glabels::merge::Record record1; + record1[ "1" ] = "Abcdefg"; + record1[ "2" ] = "Hijklmn"; + record1[ "3" ] = "Opqrstu"; + record1[ "4" ] = "Vwxyz!@"; + + QCOMPARE( f1.evaluate( record1 ), QString( "Abcdefg" ) ); + QCOMPARE( f2.evaluate( record1 ), QString( "Hijklmn" ) ); + QCOMPARE( f3.evaluate( record1 ), QString( "Opqrstu" ) ); + QCOMPARE( f4.evaluate( record1 ), QString( "Vwxyz!@" ) ); + + glabels::merge::Record record2; // All fields empty + + QCOMPARE( f1.evaluate( record2 ), QString( "foo1" ) ); + QCOMPARE( f2.evaluate( record2 ), QString( "foo2" ) ); + QCOMPARE( f3.evaluate( record2 ), QString( "foo3" ) ); + QCOMPARE( f4.evaluate( record2 ), QString( "foo4" ) ); + + glabels::merge::Record record3; + record3[ "1" ] = "xyzzy"; + // Field "2" empty + // Field "3" empty + record3[ "4" ] = "plugh"; + + QCOMPARE( f1.evaluate( record3 ), QString( "xyzzy" ) ); + QCOMPARE( f2.evaluate( record3 ), QString( "foo2" ) ); + QCOMPARE( f3.evaluate( record3 ), QString( "foo3" ) ); + QCOMPARE( f4.evaluate( record3 ), QString( "plugh" ) ); +} + + +void TestSubstitutionField::formattedStringEvaluation() +{ + glabels::merge::SubstitutionField f1( "${1:%10s}" ); + glabels::merge::SubstitutionField f2( "${2:%10s}" ); + glabels::merge::SubstitutionField f3( "${3:%10s}" ); + glabels::merge::SubstitutionField f4( "${4:%10s}" ); + + glabels::merge::SubstitutionField f5( "${5:%-10s}" ); + glabels::merge::SubstitutionField f6( "${6:%-10s}" ); + glabels::merge::SubstitutionField f7( "${7:%-10s}" ); + glabels::merge::SubstitutionField f8( "${8:%-10s}" ); + + glabels::merge::Record record1; + record1[ "1" ] = "0"; + record1[ "2" ] = "1"; + record1[ "3" ] = "-1"; + record1[ "4" ] = "3.14"; + + record1[ "5" ] = "0"; + record1[ "6" ] = "100"; + record1[ "7" ] = "-100"; + record1[ "8" ] = "3.14"; + + QCOMPARE( f1.evaluate( record1 ), QString( " 0" ) ); + QCOMPARE( f2.evaluate( record1 ), QString( " 1" ) ); + QCOMPARE( f3.evaluate( record1 ), QString( " -1" ) ); + QCOMPARE( f4.evaluate( record1 ), QString( " 3.14" ) ); + + QCOMPARE( f5.evaluate( record1 ), QString( "0 " ) ); + QCOMPARE( f6.evaluate( record1 ), QString( "100 " ) ); + QCOMPARE( f7.evaluate( record1 ), QString( "-100 " ) ); + QCOMPARE( f8.evaluate( record1 ), QString( "3.14 " ) ); +} + + +void TestSubstitutionField::formattedFloatEvaluation() +{ + glabels::merge::SubstitutionField f1( "${1:%+5.2f}" ); + glabels::merge::SubstitutionField f2( "${2:%+5.2f}" ); + glabels::merge::SubstitutionField f3( "${3:%+5.2f}" ); + glabels::merge::SubstitutionField f4( "${4:%+5.2f}" ); + + glabels::merge::SubstitutionField f5( "${5:%+5.2e}" ); + glabels::merge::SubstitutionField f6( "${6:%+5.2e}" ); + glabels::merge::SubstitutionField f7( "${7:%+5.2e}" ); + glabels::merge::SubstitutionField f8( "${8:%+5.2e}" ); + + glabels::merge::Record record1; + record1[ "1" ] = "0"; + record1[ "2" ] = "1"; + record1[ "3" ] = "-1"; + record1[ "4" ] = "3.14"; + + record1[ "5" ] = "0"; + record1[ "6" ] = "100"; + record1[ "7" ] = "-100"; + record1[ "8" ] = "3.14"; + + QCOMPARE( f1.evaluate( record1 ), QString( "+0.00" ) ); + QCOMPARE( f2.evaluate( record1 ), QString( "+1.00" ) ); + QCOMPARE( f3.evaluate( record1 ), QString( "-1.00" ) ); + QCOMPARE( f4.evaluate( record1 ), QString( "+3.14" ) ); + + QCOMPARE( f5.evaluate( record1 ), QString( "+0.00e+00" ) ); + QCOMPARE( f6.evaluate( record1 ), QString( "+1.00e+02" ) ); + QCOMPARE( f7.evaluate( record1 ), QString( "-1.00e+02" ) ); + QCOMPARE( f8.evaluate( record1 ), QString( "+3.14e+00" ) ); +} + + +void TestSubstitutionField::formattedIntEvaluation() +{ + glabels::merge::SubstitutionField f1( "${1:%08d}" ); + glabels::merge::SubstitutionField f2( "${2:%08d}" ); + glabels::merge::SubstitutionField f3( "${3:%08d}" ); + glabels::merge::SubstitutionField f4( "${4:%08d}" ); + + glabels::merge::SubstitutionField f5( "${5:%08x}" ); + glabels::merge::SubstitutionField f6( "${6:%08x}" ); + glabels::merge::SubstitutionField f7( "${7:%08x}" ); + glabels::merge::SubstitutionField f8( "${8:%08x}" ); + + glabels::merge::Record record1; + record1[ "1" ] = "0"; + record1[ "2" ] = "1"; + record1[ "3" ] = "-1"; + record1[ "4" ] = "3.14"; + + record1[ "5" ] = "100"; + record1[ "6" ] = "0x100"; + record1[ "7" ] = "-1"; + record1[ "8" ] = "314"; + + QCOMPARE( f1.evaluate( record1 ), QString( "00000000" ) ); + QCOMPARE( f2.evaluate( record1 ), QString( "00000001" ) ); + QCOMPARE( f3.evaluate( record1 ), QString( "-0000001" ) ); + QCOMPARE( f4.evaluate( record1 ), QString( "00000000" ) ); // Invalid integer value + + QCOMPARE( f5.evaluate( record1 ), QString( "00000064" ) ); // 100(decimal) == 64(hex) + QCOMPARE( f6.evaluate( record1 ), QString( "00000100" ) ); + QCOMPARE( f7.evaluate( record1 ), QString( "00000000" ) ); // Invalid unsigned integer + QCOMPARE( f8.evaluate( record1 ), QString( "0000013a" ) ); // 314(decimal) == 13a(hex) +} diff --git a/glabels/Merge/unit_tests/TestSubstitutionField.h b/glabels/Merge/unit_tests/TestSubstitutionField.h new file mode 100644 index 0000000..735d402 --- /dev/null +++ b/glabels/Merge/unit_tests/TestSubstitutionField.h @@ -0,0 +1,37 @@ +/* TestSubstitutionField.h + * + * Copyright (C) 2017 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 TestSubstitutionField : public QObject +{ + Q_OBJECT + +private slots: + void construction(); + void simpleEvaluation(); + void defaultValueEvaluation(); + void formattedStringEvaluation(); + void formattedFloatEvaluation(); + void formattedIntEvaluation(); +}; + + diff --git a/translations/glabels_C.ts b/translations/glabels_C.ts index 484a0f4..c4e8a3d 100644 --- a/translations/glabels_C.ts +++ b/translations/glabels_C.ts @@ -652,7 +652,7 @@ - + &Cancel