diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencespathpage.cpp b/src/app/puzzle/dialogs/configpages/puzzlepreferencespathpage.cpp index d8e042f6c..eb039f6d6 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencespathpage.cpp +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencespathpage.cpp @@ -64,6 +64,7 @@ void PuzzlePreferencesPathPage::Apply() VPSettings *settings = VPApplication::VApp()->PuzzleSettings(); settings->SetPathSVGFonts(ui->pathTable->item(0, 1)->text()); settings->SetPathFontCorrections(ui->pathTable->item(1, 1)->text()); + settings->SetPathKnownMeasurements(ui->pathTable->item(1, 1)->text()); } //--------------------------------------------------------------------------------------------------------------------- @@ -95,6 +96,9 @@ void PuzzlePreferencesPathPage::DefaultPath() case 1: // font corrections path = VCommonSettings::GetDefPathFontCorrections(); break; + case 2: // known measurements + path = VCommonSettings::GetDefPathKnownMeasurements(); + break; default: break; } @@ -119,6 +123,9 @@ void PuzzlePreferencesPathPage::EditPath() case 1: // font corrections path = VPApplication::VApp()->PuzzleSettings()->GetPathFontCorrections(); break; + case 2: // known measurements + path = VPApplication::VApp()->PuzzleSettings()->GetDefPathKnownMeasurements(); + break; default: break; } @@ -152,7 +159,7 @@ void PuzzlePreferencesPathPage::EditPath() void PuzzlePreferencesPathPage::InitTable() { ui->pathTable->clearContents(); - ui->pathTable->setRowCount(2); + ui->pathTable->setRowCount(3); ui->pathTable->setColumnCount(2); const VPSettings *settings = VPApplication::VApp()->PuzzleSettings(); @@ -171,6 +178,13 @@ void PuzzlePreferencesPathPage::InitTable() ui->pathTable->setItem(1, 1, item); } + { + ui->pathTable->setItem(2, 0, new QTableWidgetItem(tr("My known measurements"))); + auto *item = new QTableWidgetItem(settings->GetPathKnownMeasurements()); + item->setToolTip(settings->GetPathKnownMeasurements()); + ui->pathTable->setItem(2, 1, item); + } + ui->pathTable->verticalHeader()->setDefaultSectionSize(20); ui->pathTable->resizeColumnsToContents(); ui->pathTable->resizeRowsToContents(); diff --git a/src/app/puzzle/vpmainwindow.cpp b/src/app/puzzle/vpmainwindow.cpp index cfeadd190..47ddd46ee 100644 --- a/src/app/puzzle/vpmainwindow.cpp +++ b/src/app/puzzle/vpmainwindow.cpp @@ -1636,7 +1636,7 @@ void VPMainWindow::UpdateWindowTitle() QString showName; if (not curFile.isEmpty()) { - showName = StrippedName(curFile); + showName = QFileInfo(curFile).fileName(); } else { diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp index 2bd002a7d..98689a411 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp @@ -162,22 +162,22 @@ auto TapePreferencesConfigurationPage::Apply() -> QStringList settings->SetDontUseNativeDialog(ui->checkBoxDontUseNativeDialog->isChecked()); } - if (m_langChanged || m_systemChanged) + if (m_systemChanged) + { + const auto id = ui->comboBoxKnownMeasurements->currentData().toUuid(); + settings->SetKnownMeasurementsId(id); + m_systemChanged = false; + } + + if (m_langChanged) { const auto locale = qvariant_cast(ui->langCombo->currentData()); settings->SetLocale(locale); VGAnalytics::Instance()->SetGUILanguage(settings->GetLocale()); m_langChanged = false; - const auto id = ui->comboBoxKnownMeasurements->currentData().toUuid(); - settings->SetKnownMeasurementsId(id); - m_systemChanged = false; - VAbstractApplication::VApp()->LoadTranslation(locale); QCoreApplication::processEvents(); // force to call changeEvent - - // Part about measurments will not be updated automatically - MApplication::VApp()->RetranslateTables(); } if (settings->IsAutomaticallyCheckUpdates() != ui->checkBoxAutomaticallyCheckUpdates->isChecked()) diff --git a/src/app/tape/dialogs/dialogtapepreferences.ui b/src/app/tape/dialogs/dialogtapepreferences.ui index 3a49faf4c..ac407d00e 100644 --- a/src/app/tape/dialogs/dialogtapepreferences.ui +++ b/src/app/tape/dialogs/dialogtapepreferences.ui @@ -37,7 +37,7 @@ 96 - 84 + 96 diff --git a/src/app/tape/mapplication.cpp b/src/app/tape/mapplication.cpp index 08898db30..8635e7ed7 100644 --- a/src/app/tape/mapplication.cpp +++ b/src/app/tape/mapplication.cpp @@ -41,7 +41,9 @@ #include "../vmisc/qt_dispatch/qt_dispatch.h" #include "../vmisc/theme/vtheme.h" #include "../vmisc/vsysexits.h" -#include "qtpreprocessorsupport.h" +#include "dialogs/dialogtapepreferences.h" +#include "qfuturewatcher.h" +#include "tkmmainwindow.h" #include "tmainwindow.h" #include "version.h" #include "vtapeshortcutmanager.h" @@ -106,6 +108,9 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, SINGLE_OPTION_DIMENSION_C, ('c'_L1)) Q_GLOBAL_STATIC_WITH_ARGS(const QString, LONG_OPTION_UNITS, ("units"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, SINGLE_OPTION_UNITS, ('u'_L1)) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, LONG_OPTION_KNOWN, ("known"_L1)) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, SINGLE_OPTION_KNOWN, ('k'_L1)) // NOLINT + Q_GLOBAL_STATIC_WITH_ARGS(const QString, LONG_OPTION_TEST, ("test"_L1)) // NOLINT QT_WARNING_POP @@ -308,7 +313,8 @@ inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &con //--------------------------------------------------------------------------------------------------------------------- MApplication::MApplication(int &argc, char **argv) - : VAbstractApplication(argc, argv) + : VAbstractApplication(argc, argv), + m_knownMeasurementsRepopulateWatcher(new QFutureWatcher(this)) { setApplicationDisplayName(QStringLiteral(VER_PRODUCTNAME_STR)); setApplicationName(QStringLiteral(VER_INTERNALNAME_STR)); @@ -338,6 +344,7 @@ MApplication::~MApplication() } qDeleteAll(m_mainWindows); + qDeleteAll(m_kmMainWindows); delete m_trVars; if (not m_dataBase.isNull()) @@ -440,20 +447,20 @@ auto MApplication::IsAppInGUIMode() const -> bool } //--------------------------------------------------------------------------------------------------------------------- -auto MApplication::MainWindow() -> TMainWindow * +auto MApplication::MainTapeWindow() -> TMainWindow * { - Clean(); + CleanTapeWindows(); if (m_mainWindows.isEmpty()) { - NewMainWindow(); + NewMainTapeWindow(); } - return m_mainWindows[0]; + return m_mainWindows.first(); } //--------------------------------------------------------------------------------------------------------------------- -auto MApplication::MainWindows() -> QList +auto MApplication::MainTapeWindows() -> QList { - Clean(); + CleanTapeWindows(); QList list; list.reserve(m_mainWindows.size()); for (auto &w : m_mainWindows) @@ -539,7 +546,7 @@ auto MApplication::event(QEvent *e) -> bool const QString macFileOpen = fileOpenEvent->file(); if (not macFileOpen.isEmpty()) { - TMainWindow *mw = MainWindow(); + TMainWindow *mw = MainTapeWindow(); if (mw) { mw->LoadFile(macFileOpen); // open file in existing window @@ -591,16 +598,6 @@ auto MApplication::TapeSettings() -> VTapeSettings * return qobject_cast(settings); } -//--------------------------------------------------------------------------------------------------------------------- -void MApplication::RetranslateTables() -{ - const QList list = MainWindows(); - for (auto *w : list) - { - w->RetranslateTable(); - } -} - //--------------------------------------------------------------------------------------------------------------------- void MApplication::ParseCommandLine(const SocketConnection &connection, const QStringList &arguments) { @@ -614,6 +611,7 @@ void MApplication::ParseCommandLine(const SocketConnection &connection, const QS parser.process(arguments); m_testMode = parser.isSet(*LONG_OPTION_TEST); + m_knownMeasurementsMode = parser.isSet(*LONG_OPTION_KNOWN); if (not m_testMode && connection == SocketConnection::Client) { @@ -674,6 +672,52 @@ auto MApplication::KnownMeasurementsDatabase() -> VKnownMeasurementsDatabase * return m_knownMeasurementsDatabase; } +//--------------------------------------------------------------------------------------------------------------------- +void MApplication::Preferences(QWidget *parent) +{ + // Calling constructor of the dialog take some time. Because of this user have time to call the dialog twice. + static QPointer guard; // Prevent any second run + if (guard.isNull()) + { + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + auto *preferences = new DialogTapePreferences(parent); + // QScopedPointer needs to be sure any exception will never block guard + QScopedPointer dlg(preferences); + guard = preferences; + // Must be first + + for (const auto &w : m_mainWindows) + { + if (!w.isNull()) + { + connect(dlg.data(), &DialogTapePreferences::UpdateProperties, w, &TMainWindow::WindowsLocale, + Qt::QueuedConnection); + connect(dlg.data(), &DialogTapePreferences::UpdateProperties, w, &TMainWindow::ToolBarStyles, + Qt::QueuedConnection); + } + } + + for (const auto &w : m_kmMainWindows) + { + if (!w.isNull()) + { + connect(dlg.data(), &DialogTapePreferences::UpdateProperties, w, &TKMMainWindow::WindowsLocale, + Qt::QueuedConnection); + connect(dlg.data(), &DialogTapePreferences::UpdateProperties, w, &TKMMainWindow::ToolBarStyles, + Qt::QueuedConnection); + } + } + + QGuiApplication::restoreOverrideCursor(); + dlg->exec(); + } + else + { + guard->raise(); + guard->activateWindow(); + } +} + //--------------------------------------------------------------------------------------------------------------------- void MApplication::RestartKnownMeasurementsDatabaseWatcher() { @@ -687,7 +731,7 @@ void MApplication::RestartKnownMeasurementsDatabaseWatcher() } //--------------------------------------------------------------------------------------------------------------------- -auto MApplication::NewMainWindow() -> TMainWindow * +auto MApplication::NewMainTapeWindow() -> TMainWindow * { auto *tape = new TMainWindow(); m_mainWindows.prepend(tape); @@ -699,6 +743,43 @@ auto MApplication::NewMainWindow() -> TMainWindow * return tape; } +//--------------------------------------------------------------------------------------------------------------------- +auto MApplication::MainKMWindow() -> TKMMainWindow * +{ + CleanKMWindows(); + if (m_kmMainWindows.isEmpty()) + { + NewMainKMWindow(); + } + return m_kmMainWindows.first(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MApplication::MainKMWindows() -> QList +{ + CleanKMWindows(); + QList list; + list.reserve(m_kmMainWindows.size()); + for (auto &w : m_kmMainWindows) + { + list.append(w); + } + return list; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MApplication::NewMainKMWindow() -> TKMMainWindow * +{ + auto *known = new TKMMainWindow(); + m_kmMainWindows.prepend(known); + if (not MApplication::VApp()->IsTestMode()) + { + known->show(); + known->UpdateWindowTitle(); + } + return known; +} + //--------------------------------------------------------------------------------------------------------------------- void MApplication::ProcessCMD() { @@ -730,8 +811,8 @@ void MApplication::NewLocalSocketConnection() ParseCommandLine(SocketConnection::Server, arg.split(QStringLiteral(";;"))); } delete socket; - MainWindow()->raise(); - MainWindow()->activateWindow(); + MainTapeWindow()->raise(); + MainTapeWindow()->activateWindow(); } //--------------------------------------------------------------------------------------------------------------------- @@ -740,8 +821,10 @@ void MApplication::RepopulateMeasurementsDatabase(const QString &path) Q_UNUSED(path) if (m_knownMeasurementsDatabase != nullptr) { - QFuture future = - QtConcurrent::run([this]() { m_knownMeasurementsDatabase->PopulateMeasurementsDatabase(); }); + m_knownMeasurementsRepopulateWatcher->setFuture( + QtConcurrent::run([this]() { m_knownMeasurementsDatabase->PopulateMeasurementsDatabase(); })); + QObject::connect(m_knownMeasurementsRepopulateWatcher, &QFutureWatcher::finished, this, + &MApplication::SyncKnownMeasurements); } } @@ -759,7 +842,19 @@ void MApplication::KnownMeasurementsPathChanged(const QString &oldPath, const QS } //--------------------------------------------------------------------------------------------------------------------- -void MApplication::Clean() +void MApplication::SyncKnownMeasurements() +{ + for (const auto &w : m_mainWindows) + { + if (!w.isNull()) + { + w->SyncKnownMeasurements(); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void MApplication::CleanTapeWindows() { // cleanup any deleted main windows first for (vsizetype i = m_mainWindows.count() - 1; i >= 0; --i) @@ -771,6 +866,19 @@ void MApplication::Clean() } } +//--------------------------------------------------------------------------------------------------------------------- +void MApplication::CleanKMWindows() +{ + // cleanup any deleted main windows first + for (vsizetype i = m_kmMainWindows.count() - 1; i >= 0; --i) + { + if (m_kmMainWindows.at(i).isNull()) + { + m_kmMainWindows.removeAt(i); + } + } +} + //--------------------------------------------------------------------------------------------------------------------- void MApplication::InitParserOptions(QCommandLineParser &parser) { @@ -793,6 +901,8 @@ void MApplication::InitParserOptions(QCommandLineParser &parser) tr("Set pattern file units: cm, mm, inch."), tr("The pattern units")}, + {{*SINGLE_OPTION_KNOWN, *LONG_OPTION_KNOWN}, tr("Activate known measurements mode.")}, + {*LONG_OPTION_TEST, tr("Use for unit testing. Run the program and open a file without showing the main window.")}, @@ -840,6 +950,19 @@ auto MApplication::StartWithFiles(QCommandLineParser &parser) -> bool parser.showHelp(V_EX_USAGE); } + if (!m_knownMeasurementsMode) + { + return StartWithMeasurementFiles(parser); + } + + return StartWithKnownMeasurementFiles(parser); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MApplication::StartWithMeasurementFiles(QCommandLineParser &parser) -> bool +{ + const QStringList args = parser.positionalArguments(); + bool flagDimensionA = false; bool flagDimensionB = false; bool flagDimensionC = false; @@ -855,41 +978,57 @@ auto MApplication::StartWithFiles(QCommandLineParser &parser) -> bool ParseDimensionCOption(parser, dimensionCValue, flagDimensionC); ParseUnitsOption(parser, unit, flagUnits); - for (const auto &arg : args) - { - NewMainWindow(); - if (not MainWindow()->LoadFile(arg)) - { - if (m_testMode) - { - return false; // process only one input file - } - delete MainWindow(); - continue; - } + return std::all_of(args.begin(), args.end(), + [&](const auto &arg) + { + NewMainTapeWindow(); + if (not MainTapeWindow()->LoadFile(arg)) + { + delete MainTapeWindow(); + return !m_testMode; + } - if (flagDimensionA) - { - MainWindow()->SetDimensionABase(dimensionAValue); - } + if (flagDimensionA) + { + MainTapeWindow()->SetDimensionABase(dimensionAValue); + } - if (flagDimensionB) - { - MainWindow()->SetDimensionBBase(dimensionBValue); - } + if (flagDimensionB) + { + MainTapeWindow()->SetDimensionBBase(dimensionBValue); + } - if (flagDimensionC) - { - MainWindow()->SetDimensionCBase(dimensionCValue); - } + if (flagDimensionC) + { + MainTapeWindow()->SetDimensionCBase(dimensionCValue); + } - if (flagUnits) - { - MainWindow()->SetPUnit(unit); - } - } + if (flagUnits) + { + MainTapeWindow()->SetPUnit(unit); + } - return true; + return true; + }); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MApplication::StartWithKnownMeasurementFiles(QCommandLineParser &parser) -> bool +{ + const QStringList args = parser.positionalArguments(); + + return std::all_of(args.begin(), args.end(), + [&](const auto &arg) + { + NewMainKMWindow(); + if (not MainKMWindow()->LoadFile(arg)) + { + delete MainKMWindow(); + return !m_testMode; + } + + return true; + }); } //--------------------------------------------------------------------------------------------------------------------- @@ -897,7 +1036,14 @@ auto MApplication::SingleStart(QCommandLineParser &parser) -> bool { if (not m_testMode) { - NewMainWindow(); + if (!m_knownMeasurementsMode) + { + NewMainTapeWindow(); + } + else + { + NewMainKMWindow(); + } } else { diff --git a/src/app/tape/mapplication.h b/src/app/tape/mapplication.h index 5cae4a7f4..276fe4e2c 100644 --- a/src/app/tape/mapplication.h +++ b/src/app/tape/mapplication.h @@ -34,7 +34,10 @@ #include "dialogs/dialogmdatabase.h" #include "vtapesettings.h" +#include + class TMainWindow; +class TKMMainWindow; class QLocalServer; class QCommandLineParser; class VKnownMeasurementsDatabase; @@ -57,9 +60,14 @@ public: auto IsTestMode() const -> bool; auto IsAppInGUIMode() const -> bool override; - auto MainWindow() -> TMainWindow *; - auto MainWindows() -> QList; - auto NewMainWindow() -> TMainWindow *; + + auto MainTapeWindow() -> TMainWindow *; + auto MainTapeWindows() -> QList; + auto NewMainTapeWindow() -> TMainWindow *; + + auto MainKMWindow() -> TKMMainWindow *; + auto MainKMWindows() -> QList; + auto NewMainKMWindow() -> TKMMainWindow *; void InitOptions(); @@ -68,14 +76,14 @@ public: void OpenSettings() override; auto TapeSettings() -> VTapeSettings *; - void RetranslateTables(); - void ParseCommandLine(const SocketConnection &connection, const QStringList &arguments); static auto VApp() -> MApplication *; auto KnownMeasurementsDatabase() -> VKnownMeasurementsDatabase * override; + void Preferences(QWidget *parent = nullptr); + public slots: void ProcessCMD(); @@ -90,24 +98,31 @@ private slots: void NewLocalSocketConnection(); void RepopulateMeasurementsDatabase(const QString &path); void KnownMeasurementsPathChanged(const QString &oldPath, const QString &newPath); + void SyncKnownMeasurements(); private: // cppcheck-suppress unknownMacro Q_DISABLE_COPY_MOVE(MApplication) // NOLINT QList> m_mainWindows{}; + QList> m_kmMainWindows{}; QLocalServer *m_localServer{nullptr}; VTranslateVars *m_trVars{nullptr}; QPointer m_dataBase{}; bool m_testMode{false}; + bool m_knownMeasurementsMode{false}; VKnownMeasurementsDatabase *m_knownMeasurementsDatabase{nullptr}; QFileSystemWatcher *m_knownMeasurementsDatabaseWatcher{nullptr}; + QFutureWatcher *m_knownMeasurementsRepopulateWatcher; - void Clean(); + void CleanTapeWindows(); + void CleanKMWindows(); static void InitParserOptions(QCommandLineParser &parser); void StartLocalServer(const QString &serverName); auto StartWithFiles(QCommandLineParser &parser) -> bool; + auto StartWithMeasurementFiles(QCommandLineParser &parser) -> bool; + auto StartWithKnownMeasurementFiles(QCommandLineParser &parser) -> bool; auto SingleStart(QCommandLineParser &parser) -> bool; static void ParseDimensionAOption(QCommandLineParser &parser, qreal &dimensionAValue, bool &flagDimensionA); diff --git a/src/app/tape/tape.pri b/src/app/tape/tape.pri index 30abca735..5f86da5f7 100644 --- a/src/app/tape/tape.pri +++ b/src/app/tape/tape.pri @@ -18,7 +18,8 @@ SOURCES += \ $$PWD/dialogs/configpages/tapepreferencespathpage.cpp \ $$PWD/vtapesettings.cpp \ $$PWD/dialogs/dialogsetupmultisize.cpp \ - $$PWD/vtapeshortcutmanager.cpp + $$PWD/vtapeshortcutmanager.cpp \ + $$PWD/tkmmainwindow.cpp *msvc*:SOURCES += $$PWD/stable.cpp @@ -40,7 +41,8 @@ HEADERS += \ $$PWD/dialogs/configpages/tapepreferencespathpage.h \ $$PWD/vtapesettings.h \ $$PWD/dialogs/dialogsetupmultisize.h \ - $$PWD/vtapeshortcutmanager.h + $$PWD/vtapeshortcutmanager.h \ + $$PWD/tkmmainwindow.h FORMS += \ $$PWD/dialogs/dialogdimensioncustomnames.ui \ @@ -54,4 +56,6 @@ FORMS += \ $$PWD/dialogs/dialogtapepreferences.ui \ $$PWD/dialogs/configpages/tapepreferencesconfigurationpage.ui \ $$PWD/dialogs/configpages/tapepreferencespathpage.ui \ - $$PWD/dialogs/dialogsetupmultisize.ui + $$PWD/dialogs/dialogsetupmultisize.ui \ + $$PWD/tkmmainwindow.ui + diff --git a/src/app/tape/tape.qbs b/src/app/tape/tape.qbs index 532aab5ff..3f41808d7 100644 --- a/src/app/tape/tape.qbs +++ b/src/app/tape/tape.qbs @@ -52,6 +52,9 @@ VToolApp { files: [ "main.cpp", + "tkmmainwindow.cpp", + "tkmmainwindow.h", + "tkmmainwindow.ui", "tmainwindow.cpp", "mapplication.cpp", "vlitepattern.cpp", diff --git a/src/app/tape/tkmmainwindow.cpp b/src/app/tape/tkmmainwindow.cpp new file mode 100644 index 000000000..1a2aba406 --- /dev/null +++ b/src/app/tape/tkmmainwindow.cpp @@ -0,0 +1,2431 @@ +/************************************************************************ + ** + ** @file tkmmainwindow.cpp + ** @author Roman Telezhynskyi + ** @date 31 10, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "tkmmainwindow.h" +#include "../ifc/exception/vexception.h" +#include "../ifc/xml/utils.h" +#include "../ifc/xml/vknownmeasurementsconverter.h" +#include "../vformat/knownmeasurements/vknownmeasurement.h" +#include "../vformat/knownmeasurements/vknownmeasurementsdocument.h" +#include "../vganalytics/vganalytics.h" +#include "../vmisc/compatibility.h" +#include "../vmisc/defglobal.h" +#include "../vmisc/dialogs/dialogaskcollectstatistic.h" +#include "../vmisc/dialogs/dialogexporttocsv.h" +#include "../vmisc/dialogs/dialogselectlanguage.h" +#include "../vmisc/qxtcsvmodel.h" +#include "../vmisc/theme/vtheme.h" +#include "../vmisc/vsysexits.h" +#include "dialogs/dialogabouttape.h" +#include "knownmeasurements/vknownmeasurements.h" +#include "mapplication.h" // Should be last because of definning qApp +#include "quuid.h" +#include "ui_tkmmainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include "../vmisc/vtextcodec.h" +#else +#include +#endif + +#if (defined(Q_CC_GNU) && Q_CC_GNU < 409) && !defined(Q_CC_CLANG) +// DO NOT WORK WITH GCC 4.8 +#else +#if __cplusplus >= 201402L +using namespace std::chrono_literals; +#else +#include "../vmisc/bpstd/chrono.hpp" +using namespace bpstd::literals::chrono_literals; +#endif // __cplusplus >= 201402L +#endif //(defined(Q_CC_GNU) && Q_CC_GNU < 409) && !defined(Q_CC_CLANG) + +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wmissing-prototypes") +QT_WARNING_DISABLE_INTEL(1418) + +Q_LOGGING_CATEGORY(kmMainWindow, "km.mainwindow") // NOLINT + +QT_WARNING_POP + +using namespace Qt::Literals::StringLiterals; + +namespace +{ +constexpr int DIALOG_MAX_FORMULA_HEIGHT = 64; + +// We need this enum in case we will add or delete a column. And also make code more readable. +enum +{ + ColumnName = 0, + ColumnFullName = 1, + ColumnGroup = 2 +}; + +enum class MUnits : qint8 +{ + Table, + Degrees +}; +} // namespace + +//--------------------------------------------------------------------------------------------------------------------- +TKMMainWindow::TKMMainWindow(QWidget *parent) + : VAbstractMainWindow(parent), + ui(new Ui::TKMMainWindow), + m_searchHistory(new QMenu(this)) +{ + ui->setupUi(this); + + InitIcons(); + WindowsLocale(); + + ui->labelDiagram->setText(UnknownMeasurementImage()); + + ui->lineEditFind->installEventFilter(this); + ui->plainTextEditFormula->installEventFilter(this); + + m_search = QSharedPointer(new VTableSearch(ui->tableWidget)); + ui->tabWidget->setVisible(false); + + ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu); + + m_recentFileActs.fill(nullptr); + + SetupMenu(); + ReadSettings(); + +#if defined(Q_OS_MAC) + ui->pushButtonShowInExplorer->setText(tr("Show in Finder")); +#endif // defined(Q_OS_MAC) + + if (MApplication::VApp()->IsAppInGUIMode()) + { + QTimer::singleShot(V_SECONDS(1), this, &TKMMainWindow::AskDefaultSettings); + } + + m_buttonShortcuts.insert(VShortcutAction::CaseSensitiveMatch, ui->toolButtonCaseSensitive); + m_buttonShortcuts.insert(VShortcutAction::WholeWordMatch, ui->toolButtonWholeWord); + m_buttonShortcuts.insert(VShortcutAction::RegexMatch, ui->toolButtonRegexp); + m_buttonShortcuts.insert(VShortcutAction::SearchHistory, ui->pushButtonSearch); + m_buttonShortcuts.insert(VShortcutAction::RegexMatchUnicodeProperties, ui->toolButtonUseUnicodeProperties); + m_buttonShortcuts.insert(VShortcutAction::FindNext, ui->toolButtonFindNext); + m_buttonShortcuts.insert(VShortcutAction::FindPrevious, ui->toolButtonFindNext); + + if (VAbstractShortcutManager *manager = VAbstractApplication::VApp()->GetShortcutManager()) + { + connect(manager, &VAbstractShortcutManager::ShortcutsUpdated, this, &TKMMainWindow::UpdateShortcuts); + UpdateShortcuts(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +TKMMainWindow::~TKMMainWindow() +{ + ui->lineEditFind->blockSignals(true); // prevents crash + delete m_m; + delete ui; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::CurrentFile() const -> QString +{ + return m_curFile; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::LoadFile(const QString &path) -> bool +{ + if (m_m != nullptr) + { + return MApplication::VApp()->NewMainKMWindow()->LoadFile(path); + } + + if (not QFileInfo::exists(path)) + { + qCCritical(kmMainWindow, "%s", qUtf8Printable(tr("File '%1' doesn't exist!").arg(path))); + if (MApplication::VApp()->IsTestMode()) + { + QCoreApplication::exit(V_EX_NOINPUT); + } + return false; + } + + // Check if file already opened + const QList list = MApplication::VApp()->MainKMWindows(); + auto w = + std::find_if(list.begin(), list.end(), [path](TKMMainWindow *window) { return window->CurrentFile() == path; }); + if (w != list.end()) + { + (*w)->activateWindow(); + close(); + return false; + } + + VlpCreateLock(m_lock, path); + + if (not m_lock->IsLocked()) + { + if (not IgnoreLocking(m_lock->GetLockError(), path, MApplication::VApp()->IsAppInGUIMode())) + { + return false; + } + } + + try + { + VKnownMeasurementsConverter converter(path); + m_curFileFormatVersion = converter.GetCurrentFormatVersion(); + m_curFileFormatVersionStr = converter.GetFormatVersionStr(); + m_m = new VKnownMeasurementsDocument(); + m_m->setXMLContent(converter.Convert()); + + VCommonSettings *settings = VAbstractApplication::VApp()->Settings(); + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + + QString clientID = settings->GetClientID(); + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + } + + statistic->Enable(true); + + const qint64 uptime = VAbstractApplication::VApp()->AppUptime(); + statistic->SendMultisizeMeasurementsFormatVersion(uptime, m_curFileFormatVersionStr); + } + + ui->labelToolTip->setVisible(false); + ui->tabWidget->setVisible(true); + + m_mIsReadOnly = m_m->IsReadOnly(); + UpdatePadlock(m_mIsReadOnly); + + SetCurrentFile(path); + + InitWindow(); + + RefreshTable(); + + if (ui->tableWidget->rowCount() > 0) + { + ui->tableWidget->selectRow(0); + } + + RefreshImages(); + + if (ui->listWidget->count() > 0) + { + ui->listWidget->setCurrentRow(0); + } + + Controls(); // Buttons remove, up, down + + ui->actionImportFromCSV->setEnabled(true); + } + catch (VException &e) + { + qCCritical(kmMainWindow, "%s\n\n%s\n\n%s", qUtf8Printable(tr("File error.")), qUtf8Printable(e.ErrorMessage()), + qUtf8Printable(e.DetailedInformation())); + ui->labelToolTip->setVisible(true); + ui->tabWidget->setVisible(false); + delete m_m; + m_m = nullptr; + m_lock.reset(); + + if (MApplication::VApp()->IsTestMode()) + { + QCoreApplication::exit(V_EX_NOINPUT); + } + return false; + } + + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::UpdateWindowTitle() +{ + QString showName; + bool isFileWritable = true; + if (not m_curFile.isEmpty()) + { + // #ifdef Q_OS_WIN32 + // qt_ntfs_permission_lookup++; // turn checking on + // #endif /*Q_OS_WIN32*/ + isFileWritable = QFileInfo(m_curFile).isWritable(); + // #ifdef Q_OS_WIN32 + // qt_ntfs_permission_lookup--; // turn it off again + // #endif /*Q_OS_WIN32*/ + showName = QFileInfo(m_curFile).fileName(); + } + else + { + auto index = MApplication::VApp()->MainKMWindows().indexOf(this); + if (index != -1) + { + showName = tr("untitled %1").arg(index + 1); + } + else + { + showName = tr("untitled"); + } + showName += ".vkm"_L1; + } + + showName += "[*]"_L1; + + if (m_mIsReadOnly || not isFileWritable) + { + showName += " ("_L1 + tr("read only") + ')'_L1; + } + + setWindowTitle(showName); + setWindowFilePath(m_curFile); + +#if defined(Q_OS_MAC) + static QIcon fileIcon = QIcon(QCoreApplication::applicationDirPath() + "/../Resources/measurements.icns"_L1); + QIcon icon; + if (not m_curFile.isEmpty()) + { + if (not isWindowModified()) + { + icon = fileIcon; + } + else + { + static QIcon darkIcon; + + if (darkIcon.isNull()) + { + darkIcon = QIcon(darkenPixmap(fileIcon.pixmap(16, 16))); + } + icon = darkIcon; + } + } + setWindowIcon(icon); +#endif // defined(Q_OS_MAC) +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::closeEvent(QCloseEvent *event) +{ +#if defined(Q_OS_MAC) && QT_VERSION < QT_VERSION_CHECK(5, 11, 1) + // Workaround for Qt bug https://bugreports.qt.io/browse/QTBUG-43344 + static int numCalled = 0; + if (numCalled++ >= 1) + { + return; + } +#endif + + if (MaybeSave()) + { + WriteSettings(); + event->accept(); + deleteLater(); + } + else + { + event->ignore(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) + { + WindowsLocale(); + + // retranslate designer form (single inheritance approach) + ui->retranslateUi(this); + + ui->lineEditFind->setPlaceholderText(m_search->SearchPlaceholder()); + UpdateSearchControlsTooltips(); + + UpdateWindowTitle(); + + InitMeasurementUnits(); + + ui->comboBoxDiagram->blockSignals(true); + + QUuid current; + if (ui->comboBoxDiagram->currentIndex() != -1) + { + current = ui->comboBoxDiagram->currentData().toUuid(); + } + + InitMeasurementDiagramList(); + + int i = ui->comboBoxDiagram->findData(current); + if (i != -1) + { + ui->comboBoxDiagram->setCurrentIndex(i); + } + + ui->comboBoxDiagram->blockSignals(false); + } + + if (event->type() == QEvent::PaletteChange) + { + InitIcons(); + } + + // remember to call base class implementation + QMainWindow::changeEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::eventFilter(QObject *object, QEvent *event) -> bool +{ + if (auto *plainTextEdit = qobject_cast(object)) + { + if (event->type() == QEvent::KeyPress) + { + auto *keyEvent = static_cast(event); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + if ((keyEvent->key() == Qt::Key_Period) && ((keyEvent->modifiers() & Qt::KeypadModifier) != 0U)) + { + if (VAbstractApplication::VApp()->Settings()->GetOsSeparator()) + { + plainTextEdit->insertPlainText(LocaleDecimalPoint(QLocale())); + } + else + { + plainTextEdit->insertPlainText(LocaleDecimalPoint(QLocale::c())); + } + return true; + } + } + } + else if (auto *textEdit = qobject_cast(object)) + { + if (event->type() == QEvent::KeyPress) + { + auto *keyEvent = static_cast(event); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + if ((keyEvent->key() == Qt::Key_Period) && ((keyEvent->modifiers() & Qt::KeypadModifier) != 0U)) + { + if (VAbstractApplication::VApp()->Settings()->GetOsSeparator()) + { + textEdit->insert(LocaleDecimalPoint(QLocale())); + } + else + { + textEdit->insert(LocaleDecimalPoint(QLocale::c())); + } + return true; + } + } + } + + // pass the event on to the parent class + return QMainWindow::eventFilter(object, event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ExportToCSVData(const QString &fileName, bool withHeader, int mib, const QChar &separator) +{ + QxtCsvModel csv; + const int columns = ui->tableWidget->columnCount(); + { + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + csv.insertColumn(colCount++); + } + } + } + + if (withHeader) + { + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + QString text; + if (QTableWidgetItem *header = ui->tableWidget->horizontalHeaderItem(column)) + { + text = header->text(); + } + csv.setHeaderText(colCount, text); + ++colCount; + } + } + } + + const int rows = ui->tableWidget->rowCount(); + for (int row = 0; row < rows; ++row) + { + csv.insertRow(row); + int colCount = 0; + for (int column = 0; column < columns; ++column) + { + if (not ui->tableWidget->isColumnHidden(column)) + { + QString text; + if (QTableWidgetItem *item = ui->tableWidget->item(row, column)) + { + text = item->text(); + } + csv.setText(row, colCount, text); + ++colCount; + } + } + } + + QString error; + csv.toCSV(fileName, error, withHeader, separator, VTextCodec::codecForMib(mib)); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::RecentFileList() const -> QStringList +{ + return MApplication::VApp()->TapeSettings()->GetRecentKMFileList(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::FileNew() +{ + if (m_m != nullptr) + { + MApplication::VApp()->NewMainKMWindow()->FileNew(); + return; + } + + m_m = new VKnownMeasurementsDocument(this); + m_m->CreateEmptyFile(); + + m_curFileFormatVersion = VKnownMeasurementsConverter::KnownMeasurementsMaxVer; + m_curFileFormatVersionStr = VKnownMeasurementsConverter::KnownMeasurementsMaxVerStr; + + m_mIsReadOnly = m_m->IsReadOnly(); + UpdatePadlock(m_mIsReadOnly); + + SetCurrentFile(QString()); + MeasurementsWereSaved(false); + + InitWindow(); + + ui->actionImportFromCSV->setEnabled(true); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::OpenKnownMeasurements() +{ + const QString filter = tr("Known measurements") + " (*.vit);;"_L1 + tr("All files") + " (*.*)"_L1; + // Use standard path to known measurements + QString pathTo = MApplication::VApp()->TapeSettings()->GetPathKnownMeasurements(); + + Open(pathTo, filter); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ToolBarStyles() +{ + ToolBarStyle(ui->toolBar); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::FileSave() -> bool +{ + if (m_curFile.isEmpty() || m_mIsReadOnly) + { + return FileSaveAs(); + } + + if (m_curFileFormatVersion < VKnownMeasurementsConverter::KnownMeasurementsMaxVer && + not ContinueFormatRewrite(m_curFileFormatVersionStr, VKnownMeasurementsConverter::KnownMeasurementsMaxVerStr)) + { + return false; + } + + if (not CheckFilePermissions(m_curFile, this)) + { + return false; + } + + QString error; + if (not SaveKnownMeasurements(m_curFile, error)) + { + QMessageBox messageBox; + messageBox.setIcon(QMessageBox::Warning); + messageBox.setText(tr("Could not save the file")); + messageBox.setDefaultButton(QMessageBox::Ok); + messageBox.setDetailedText(error); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + return false; + } + + m_curFileFormatVersion = VKnownMeasurementsConverter::KnownMeasurementsMaxVer; + m_curFileFormatVersionStr = VKnownMeasurementsConverter::KnownMeasurementsMaxVerStr; + + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::FileSaveAs() -> bool +{ + QString filters = tr("Known measurements") + QStringLiteral(" (*.vkm)"); + + QString fName = tr("known measurements"); + QString suffix = QStringLiteral("vkm"); + + fName += '.'_L1 + suffix; + + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + const QString dir = settings->GetPathKnownMeasurements(); + + QDir directory(dir); + if (not directory.exists()) + { + directory.mkpath(QChar('.')); + } + + if (not m_curFile.isEmpty()) + { + fName = QFileInfo(m_curFile).fileName(); + } + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save as"), dir + '/'_L1 + fName, filters, nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); + + if (fileName.isEmpty()) + { + return false; + } + + QFileInfo f(fileName); + if (f.suffix().isEmpty() && f.suffix() != suffix) + { + fileName += '.'_L1 + suffix; + } + + if (QFileInfo::exists(fileName) && m_curFile != fileName) + { + // Temporary try to lock the file before saving + VLockGuard tmp(fileName); + if (not tmp.IsLocked()) + { + qCCritical(kmMainWindow, "%s", + qUtf8Printable(tr("Failed to lock. This file already opened in another window."))); + return false; + } + } + + // Need for restoring previous state in case of failure + const bool readOnly = m_m->IsReadOnly(); + + m_m->SetReadOnly(false); + m_mIsReadOnly = false; + + QString error; + bool result = SaveKnownMeasurements(fileName, error); + if (not result) + { + QMessageBox messageBox; + messageBox.setIcon(QMessageBox::Warning); + messageBox.setInformativeText(tr("Could not save file")); + messageBox.setDefaultButton(QMessageBox::Ok); + messageBox.setDetailedText(error); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + + // Restore previous state + m_m->SetReadOnly(readOnly); + m_mIsReadOnly = readOnly; + return false; + } + + m_curFileFormatVersion = VKnownMeasurementsConverter::KnownMeasurementsMaxVer; + m_curFileFormatVersionStr = VKnownMeasurementsConverter::KnownMeasurementsMaxVerStr; + + UpdatePadlock(false); + UpdateWindowTitle(); + + if (m_curFile == fileName && not m_lock.isNull()) + { + m_lock->Unlock(); + } + VlpCreateLock(m_lock, fileName); + if (not m_lock->IsLocked()) + { + qCCritical(kmMainWindow, "%s", + qUtf8Printable(tr("Failed to lock. This file already opened in another window. " + "Expect collissions when run 2 copies of the program."))); + return false; + } + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::AboutToShowWindowMenu() +{ + ui->menuWindow->clear(); + CreateWindowMenu(ui->menuWindow); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ShowWindow() const +{ + if (auto *action = qobject_cast(sender())) + { + const QVariant v = action->data(); + if (v.canConvert()) + { + const int offset = qvariant_cast(v); + const QList windows = MApplication::VApp()->MainKMWindows(); + windows.at(offset)->raise(); + windows.at(offset)->activateWindow(); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ImportDataFromCSV() +{ +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveKnownMeasurementsName() +{ + if (m_m->Name() != ui->lineEditKMName->text()) + { + m_m->SetName(ui->lineEditKMName->text()); + MeasurementsWereSaved(false); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveKnownMeasurementsDescription() +{ + if (m_m->Description() != ui->plainTextEditKMDescription->toPlainText()) + { + m_m->SetDescription(ui->plainTextEditKMDescription->toPlainText()); + MeasurementsWereSaved(false); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::RemoveMeasurement() +{ + ShowMDiagram(VPatternImage()); + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), 0); + m_m->RemoveMeasurement(nameField->data(Qt::UserRole).toString()); + + MeasurementsWereSaved(false); + + m_search->RemoveRow(row); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + if (ui->tableWidget->rowCount() > 0) + { + ui->tableWidget->selectRow(row >= ui->tableWidget->rowCount() ? ui->tableWidget->rowCount() - 1 : row); + } + else + { + MFields(false); + + ui->actionExportToCSV->setEnabled(false); + + ui->lineEditName->blockSignals(true); + ui->lineEditName->setText(QString()); + ui->lineEditName->blockSignals(false); + + ui->plainTextEditDescription->blockSignals(true); + ui->plainTextEditDescription->setPlainText(QString()); + ui->plainTextEditDescription->blockSignals(false); + + ui->lineEditFullName->blockSignals(true); + ui->lineEditFullName->setText(QString()); + ui->lineEditFullName->blockSignals(false); + + ui->comboBoxMUnits->blockSignals(true); + ui->comboBoxMUnits->setCurrentIndex(-1); + ui->comboBoxMUnits->blockSignals(false); + } + + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MoveTop() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(row, ColumnName); + m_m->MoveTop(nameField->data(Qt::UserRole).toString()); + MeasurementsWereSaved(false); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + ui->tableWidget->selectRow(0); + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MoveUp() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(row, ColumnName); + m_m->MoveUp(nameField->data(Qt::UserRole).toString()); + MeasurementsWereSaved(false); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + ui->tableWidget->selectRow(row - 1); + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MoveDown() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(row, ColumnName); + m_m->MoveDown(nameField->data(Qt::UserRole).toString()); + MeasurementsWereSaved(false); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + ui->tableWidget->selectRow(row + 1); + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MoveBottom() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(row, ColumnName); + m_m->MoveBottom(nameField->data(Qt::UserRole).toString()); + MeasurementsWereSaved(false); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + ui->tableWidget->selectRow(ui->tableWidget->rowCount() - 1); + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::AddImage() +{ + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + + const QString filePath = + QFileDialog::getOpenFileName(this, tr("Measurement image"), settings->GetPathCustomImage(), + PrepareImageFilters(), nullptr, VAbstractApplication::VApp()->NativeFileDialog()); + + if (!filePath.isEmpty()) + { + if (QFileInfo::exists(filePath)) + { + settings->SetPathCustomImage(QFileInfo(filePath).absolutePath()); + } + + VPatternImage image = VPatternImage::FromFile(filePath); + + if (not image.IsValid()) + { + qCritical() << tr("Invalid image. Error: %1").arg(image.ErrorString()); + return; + } + + m_m->AddImage(image); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshImages(); + + ShowImageData(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::RemoveImage() +{ + auto *item = ui->listWidget->currentItem(); + + if (item == nullptr) + { + ui->toolButtonRemoveImage->setDisabled(true); + return; + } + + m_m->RemoveImage(item->data(Qt::UserRole).toUuid()); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshImages(); + + if (m_known.Images().isEmpty()) + { + ui->toolButtonRemoveImage->setDisabled(true); + ui->toolButtonSaveImage->setDisabled(true); + } + else + { + ui->listWidget->setCurrentRow(0); + } + + const int row = ui->tableWidget->currentRow(); + RefreshTable(); + ui->tableWidget->selectRow(row); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveImage() +{ + auto *item = ui->listWidget->currentItem(); + + if (item == nullptr) + { + ui->toolButtonSaveImage->setDisabled(true); + return; + } + + QMap images = m_known.Images(); + + QUuid id = item->data(Qt::UserRole).toUuid(); + if (!images.contains(id)) + { + ui->toolButtonSaveImage->setDisabled(true); + return; + } + + const VPatternImage image = images.value(id); + + if (not image.IsValid()) + { + qCritical() << tr("Unable to save image. Error: %1").arg(image.ErrorString()); + return; + } + + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + + QMimeType mime = image.MimeTypeFromData(); + + QString title = image.Title(); + if (title.isEmpty()) + { + title = tr("untitled"); + } + QString path = settings->GetPathCustomImage() + QDir::separator() + title; + + QStringList suffixes = mime.suffixes(); + if (not suffixes.isEmpty()) + { + path += '.'_L1 + suffixes.at(0); + } + + QString filter = mime.filterString(); + QString filename = QFileDialog::getSaveFileName(this, tr("Save Image"), path, filter, nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); + if (not filename.isEmpty()) + { + if (QFileInfo::exists(filename)) + { + settings->SetPathCustomImage(QFileInfo(filename).absolutePath()); + } + + QFile file(filename); + if (file.open(QIODevice::WriteOnly)) + { + file.write(QByteArray::fromBase64(image.ContentData())); + } + else + { + qCritical() << tr("Unable to save image. Error: %1").arg(file.errorString()); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ShowImage() +{ + auto *item = ui->listWidget->currentItem(); + + if (item == nullptr) + { + return; + } + + QMap images = m_known.Images(); + + QUuid id = item->data(Qt::UserRole).toUuid(); + if (!images.contains(id)) + { + return; + } + + const VPatternImage image = images.value(id); + + if (not image.IsValid()) + { + qCritical() << tr("Unable to show image. Error: %1").arg(image.ErrorString()); + return; + } + + QMimeType mime = image.MimeTypeFromData(); + QString name = QDir::tempPath() + QDir::separator() + "image.XXXXXX"_L1; + + QStringList suffixes = mime.suffixes(); + if (not suffixes.isEmpty()) + { + name += '.'_L1 + suffixes.at(0); + } + + delete m_tmpImage; + m_tmpImage = new QTemporaryFile(name, this); + if (m_tmpImage->open()) + { + m_tmpImage->write(QByteArray::fromBase64(image.ContentData())); + m_tmpImage->flush(); + QDesktopServices::openUrl(QUrl::fromLocalFile(m_tmpImage->fileName())); + } + else + { + qCritical() << "Unable to open temp file"; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::AddKnown() +{ + const QString name = GenerateMeasurementName(); + qint32 currentRow = -1; + + if (ui->tableWidget->currentRow() == -1) + { + currentRow = ui->tableWidget->rowCount(); + m_m->AddEmptyMeasurement(name); + } + else + { + currentRow = ui->tableWidget->currentRow() + 1; + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + m_m->AddEmptyMeasurementAfter(nameField->data(Qt::UserRole).toString(), name); + } + + m_search->AddRow(currentRow); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->selectRow(currentRow); + + ui->actionExportToCSV->setEnabled(true); + + MeasurementsWereSaved(false); + ui->tableWidget->repaint(); // Force repain to fix paint artifacts on Mac OS X +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ShowMData() +{ + if (ui->tableWidget->rowCount() <= 0) + { + MFields(false); + return; + } + + MFields(true); + + if (ui->tableWidget->currentRow() == -1) + { + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(0); + ui->tableWidget->blockSignals(false); + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); // name + SCASSERT(nameField != nullptr) + + VKnownMeasurement m = m_known.Measurement(nameField->data(Qt::UserRole).toString()); + + ShowMDiagram(m_known.Image(m.diagram)); + + ui->plainTextEditDescription->blockSignals(true); + ui->plainTextEditDescription->setPlainText(m.description); + ui->plainTextEditDescription->blockSignals(false); + + // Don't block all signal for QLineEdit. Need for correct handle with clear button. + disconnect(ui->lineEditName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMName); + ui->lineEditName->setText(m.name); + connect(ui->lineEditName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMName); + + disconnect(ui->lineEditFullName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMFullName); + ui->lineEditFullName->setText(m.fullName); + connect(ui->lineEditFullName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMFullName); + + disconnect(ui->lineEditGroup, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMGroup); + ui->lineEditGroup->setText(m.group); + connect(ui->lineEditGroup, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMGroup); + + ui->comboBoxMUnits->blockSignals(true); + ui->comboBoxMUnits->setCurrentIndex( + ui->comboBoxMUnits->findData(static_cast(m.specialUnits ? MUnits::Degrees : MUnits::Table))); + ui->comboBoxMUnits->blockSignals(false); + + ui->comboBoxDiagram->blockSignals(true); + InitMeasurementDiagramList(); + ui->comboBoxDiagram->setCurrentIndex(ui->comboBoxDiagram->findData(m.diagram)); + ui->comboBoxDiagram->blockSignals(false); + + ui->plainTextEditFormula->blockSignals(true); + ui->plainTextEditFormula->setPlainText(m.formula); + ui->plainTextEditFormula->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ShowImageData() +{ + if (ui->listWidget->count() <= 0) + { + ImageFields(false); + return; + } + + if (ui->listWidget->currentRow() == -1) + { + ui->listWidget->blockSignals(true); + ui->listWidget->setCurrentRow(0); + ui->listWidget->blockSignals(false); + } + + ImageFields(true); + + const QListWidgetItem *activeImage = ui->listWidget->item(ui->listWidget->currentRow()); + QUuid imageId = activeImage->data(Qt::UserRole).toUuid(); + VPatternImage image = m_known.Image(imageId); + + // Don't block all signal for QLineEdit. Need for correct handle with clear button. + disconnect(ui->lineEditImageTitle, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveImageTitle); + ui->lineEditImageTitle->setText(image.Title()); + connect(ui->lineEditImageTitle, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveImageTitle); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::DeployFormula() +{ + SCASSERT(ui->plainTextEditFormula != nullptr) + SCASSERT(ui->pushButtonGrow != nullptr) + + const QTextCursor cursor = ui->plainTextEditFormula->textCursor(); + + if (ui->plainTextEditFormula->height() < DIALOG_MAX_FORMULA_HEIGHT) + { + ui->plainTextEditFormula->setFixedHeight(DIALOG_MAX_FORMULA_HEIGHT); + // Set icon from theme (internal for Windows system) + ui->pushButtonGrow->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); + } + else + { + ui->plainTextEditFormula->setFixedHeight(m_formulaBaseHeight); + // Set icon from theme (internal for Windows system) + ui->pushButtonGrow->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); + } + + // I found that after change size of formula field, it was filed for angle formula, field for formula became black. + // This code prevent this. + setUpdatesEnabled(false); + repaint(); + setUpdatesEnabled(true); + + ui->plainTextEditFormula->setFocus(); + ui->plainTextEditFormula->setTextCursor(cursor); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMName() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + + QString newName = ui->lineEditName->text().isEmpty() ? GenerateMeasurementName() : ui->lineEditName->text(); + + QHash m = m_known.Measurments(); + if (m.contains(newName)) + { + qint32 num = 2; + QString name = newName; + do + { + name = name + '_'_L1 + QString::number(num); + num++; + } while (!m.contains(newName)); + newName = name; + } + + m_m->SetMName(nameField->text(), newName); + MeasurementsWereSaved(false); + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMFormula() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(row, ColumnName); + + QString formula = ui->plainTextEditFormula->toPlainText(); + m_m->SetMFormula(nameField->data(Qt::UserRole).toString(), formula); + + MeasurementsWereSaved(false); + + const QTextCursor cursor = ui->plainTextEditFormula->textCursor(); + + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); + + ui->plainTextEditFormula->setTextCursor(cursor); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMDescription() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + m_m->SetMDescription(nameField->data(Qt::UserRole).toString(), ui->plainTextEditDescription->toPlainText()); + + MeasurementsWereSaved(false); + + const QTextCursor cursor = ui->plainTextEditDescription->textCursor(); + + m_known = VKnownMeasurements(); + RefreshTable(); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); + + ui->plainTextEditDescription->setTextCursor(cursor); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMFullName() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + + m_m->SetMFullName(nameField->data(Qt::UserRole).toString(), ui->lineEditFullName->text()); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshTable(); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMUnits() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + const MUnits units = static_cast(ui->comboBoxMUnits->currentData().toInt()); + m_m->SetMSpecialUnits(nameField->data(Qt::UserRole).toString(), units == MUnits::Degrees); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMGroup() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + + m_m->SetMGroup(nameField->data(Qt::UserRole).toString(), ui->lineEditGroup->text()); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshTable(); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveMDiagram() +{ + const int row = ui->tableWidget->currentRow(); + + if (row == -1) + { + return; + } + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); + const QUuid id = ui->comboBoxDiagram->currentData().toUuid(); + m_m->SetMImage(nameField->data(Qt::UserRole).toString(), id); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshTable(); + m_search->RefreshList(ui->lineEditFind->text()); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); + + ShowMDiagram(m_known.Image(id)); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveImageTitle() +{ + auto *item = ui->listWidget->currentItem(); + int row = ui->listWidget->currentRow(); + + if (item == nullptr) + { + return; + } + + m_m->SetImageTitle(item->data(Qt::UserRole).toUuid(), ui->lineEditImageTitle->text()); + + MeasurementsWereSaved(false); + + m_known = VKnownMeasurements(); + RefreshImages(); + + ui->listWidget->blockSignals(true); + ui->listWidget->setCurrentRow(row); + ui->listWidget->blockSignals(false); + + ShowMData(); +} + +//--------------------------------------------------------------------------------------------------------------------- +#if defined(Q_OS_MAC) +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::OpenAt(QAction *where) +{ + const QString path = m_curFile.left(m_curFile.indexOf(where->text())) + where->text(); + if (path == m_curFile) + { + return; + } + QProcess process; + process.start(QStringLiteral("/usr/bin/open"), QStringList() << path, QIODevice::ReadOnly); + process.waitForFinished(); +} +#endif // defined(Q_OS_MAC) + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::AskDefaultSettings() +{ + if (!MApplication::VApp()->IsAppInGUIMode()) + { + return; + } + + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + if (not settings->IsLocaleSelected()) + { + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + DialogSelectLanguage dialog(this); + QGuiApplication::restoreOverrideCursor(); + dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Accepted) + { + QString locale = dialog.Locale(); + settings->SetLocale(locale); + VAbstractApplication::VApp()->LoadTranslation(locale); + } + } + + if (settings->IsAskCollectStatistic()) + { + DialogAskCollectStatistic dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + settings->SetCollectStatistic(dialog.CollectStatistic()); + } + + settings->SetAskCollectStatistic(false); + } + + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + statistic->SetGUILanguage(settings->GetLocale()); + + QString clientID = settings->GetClientID(); + bool freshID = false; + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + freshID = true; + } + + statistic->Enable(true); + + const qint64 uptime = MApplication::VApp()->AppUptime(); + freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::UpdateShortcuts() +{ + if (VAbstractShortcutManager *manager = VAbstractApplication::VApp()->GetShortcutManager()) + { + manager->UpdateButtonShortcut(m_buttonShortcuts); + manager->UpdateActionShortcuts(m_actionShortcuts); + UpdateSearchControlsTooltips(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SetupMenu() +{ + // File + connect(ui->actionNew, &QAction::triggered, this, &TKMMainWindow::FileNew); + m_actionShortcuts.insert(VShortcutAction::New, ui->actionNew); + + connect(ui->actionOpen, &QAction::triggered, this, &TKMMainWindow::OpenKnownMeasurements); + m_actionShortcuts.insert(VShortcutAction::Open, ui->actionOpen); + + connect(ui->actionSave, &QAction::triggered, this, &TKMMainWindow::FileSave); + m_actionShortcuts.insert(VShortcutAction::Save, ui->actionSave); + + connect(ui->actionSaveAs, &QAction::triggered, this, &TKMMainWindow::FileSaveAs); + m_actionShortcuts.insert(VShortcutAction::SaveAs, ui->actionSaveAs); + + connect(ui->actionExportToCSV, &QAction::triggered, this, &TKMMainWindow::ExportDataToCSV); + connect(ui->actionImportFromCSV, &QAction::triggered, this, &TKMMainWindow::ImportDataFromCSV); + connect(ui->actionReadOnly, &QAction::triggered, this, + [this](bool ro) + { + if (not m_mIsReadOnly) + { + m_m->SetReadOnly(ro); + MeasurementsWereSaved(false); + UpdatePadlock(ro); + UpdateWindowTitle(); + } + else + { + if (auto *action = qobject_cast(this->sender())) + { + action->setChecked(true); + } + } + }); + connect(ui->actionPreferences, &QAction::triggered, this, [this]() { MApplication::VApp()->Preferences(this); }); + + for (auto &recentFileAct : m_recentFileActs) + { + auto *action = new QAction(this); + recentFileAct = action; + connect(action, &QAction::triggered, this, + [this]() + { + if (auto *senderAction = qobject_cast(sender())) + { + const QString filePath = senderAction->data().toString(); + if (not filePath.isEmpty()) + { + LoadFile(filePath); + } + } + }); + ui->menuFile->insertAction(ui->actionPreferences, recentFileAct); + recentFileAct->setVisible(false); + } + + m_separatorAct = new QAction(this); + m_separatorAct->setSeparator(true); + m_separatorAct->setVisible(false); + ui->menuFile->insertAction(ui->actionPreferences, m_separatorAct); + + connect(ui->actionQuit, &QAction::triggered, this, &TKMMainWindow::close); + m_actionShortcuts.insert(VShortcutAction::Quit, ui->actionQuit); + + // Measurements + connect(ui->actionAddKnown, &QAction::triggered, this, &TKMMainWindow::AddKnown); + + // Window + connect(ui->menuWindow, &QMenu::aboutToShow, this, &TKMMainWindow::AboutToShowWindowMenu); + AboutToShowWindowMenu(); + + // Help + connect(ui->actionAboutQt, &QAction::triggered, this, [this]() { QMessageBox::aboutQt(this, tr("About Qt")); }); + connect(ui->actionAboutTape, &QAction::triggered, this, + [this]() + { + auto *aboutDialog = new DialogAboutTape(this); + aboutDialog->setAttribute(Qt::WA_DeleteOnClose, true); + aboutDialog->show(); + }); + + // Actions for recent files loaded by a tape window application. + UpdateRecentFileActions(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitWindow() +{ + SCASSERT(m_m != nullptr) + ui->labelToolTip->setVisible(false); + ui->tabWidget->setVisible(true); + ui->tabWidget->setCurrentIndex(0); // measurements + ui->tableWidget->resizeColumnsToContents(); + ui->tableWidget->resizeRowsToContents(); + ui->tableWidget->horizontalHeader()->setStretchLastSection(true); + + connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, &TKMMainWindow::ShowMData); + + // Tab measurements + m_formulaBaseHeight = ui->plainTextEditFormula->height(); + connect(ui->plainTextEditFormula, &QPlainTextEdit::textChanged, this, &TKMMainWindow::SaveMFormula); + + InitSearch(); + + ui->actionAddKnown->setEnabled(true); + ui->actionSaveAs->setEnabled(true); + + ui->lineEditName->setValidator(new QRegularExpressionValidator( + QRegularExpression("^$|"_L1 + NameRegExp(VariableRegex::KnownMeasurement)), this)); + + connect(ui->toolButtonRemoveMeasurement, &QToolButton::clicked, this, &TKMMainWindow::RemoveMeasurement); + connect(ui->toolButtonTop, &QToolButton::clicked, this, &TKMMainWindow::MoveTop); + connect(ui->toolButtonUp, &QToolButton::clicked, this, &TKMMainWindow::MoveUp); + connect(ui->toolButtonDown, &QToolButton::clicked, this, &TKMMainWindow::MoveDown); + connect(ui->toolButtonBottom, &QToolButton::clicked, this, &TKMMainWindow::MoveBottom); + + connect(ui->pushButtonGrow, &QPushButton::clicked, this, &TKMMainWindow::DeployFormula); + connect(ui->lineEditName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMName); + connect(ui->plainTextEditDescription, &QPlainTextEdit::textChanged, this, &TKMMainWindow::SaveMDescription); + connect(ui->lineEditFullName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMFullName); + connect(ui->lineEditGroup, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveMGroup); + + InitMeasurementUnits(); + + ui->comboBoxMUnits->blockSignals(true); + ui->comboBoxMUnits->setCurrentIndex(-1); + ui->comboBoxMUnits->blockSignals(false); + + connect(ui->comboBoxMUnits, QOverload::of(&QComboBox::currentIndexChanged), this, &TKMMainWindow::SaveMUnits); + + ui->comboBoxDiagram->blockSignals(true); + ui->comboBoxDiagram->setCurrentIndex(-1); + ui->comboBoxDiagram->blockSignals(false); + + connect(ui->comboBoxDiagram, QOverload::of(&QComboBox::currentIndexChanged), this, + &TKMMainWindow::SaveMDiagram); + + m_groupCompleter = new QCompleter(m_known.Groups(), this); + m_groupCompleter->setCompletionMode(QCompleter::PopupCompletion); + m_groupCompleter->setModelSorting(QCompleter::UnsortedModel); + m_groupCompleter->setFilterMode(Qt::MatchContains); + m_groupCompleter->setCaseSensitivity(Qt::CaseInsensitive); + + ui->lineEditGroup->setCompleter(m_groupCompleter); + + // Tab Images + ui->toolButtonRemoveImage->setDisabled(true); + ui->toolButtonSaveImage->setDisabled(true); + + connect(ui->toolButtonAddImage, &QToolButton::clicked, this, &TKMMainWindow::AddImage); + connect(ui->toolButtonRemoveImage, &QToolButton::clicked, this, &TKMMainWindow::RemoveImage); + connect(ui->toolButtonSaveImage, &QToolButton::clicked, this, &TKMMainWindow::SaveImage); + + connect(ui->listWidget, &QListWidget::itemSelectionChanged, this, &TKMMainWindow::ShowImageData); + + connect(ui->lineEditImageTitle, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveImageTitle); + + // Tab info + ui->plainTextEditKMDescription->setEnabled(true); + ui->plainTextEditKMDescription->setPlainText(m_m->Description()); + connect(ui->plainTextEditKMDescription, &QPlainTextEdit::textChanged, this, + &TKMMainWindow::SaveKnownMeasurementsDescription); + + ui->lineEditKMName->setEnabled(true); + ui->lineEditKMName->setText(m_m->Name()); + connect(ui->lineEditKMName, &QLineEdit::editingFinished, this, &TKMMainWindow::SaveKnownMeasurementsName); + + connect(ui->pushButtonShowInExplorer, &QPushButton::clicked, this, [this]() { ShowInGraphicalShell(m_curFile); }); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitSearch() +{ + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + m_search->SetUseUnicodePreperties(settings->GetKMSearchOptionUseUnicodeProperties()); + m_search->SetMatchWord(settings->GetKMSearchOptionWholeWord()); + m_search->SetMatchRegexp(settings->GetKMSearchOptionRegexp()); + m_search->SetMatchCase(settings->GetKMSearchOptionMatchCase()); + + ui->lineEditFind->setPlaceholderText(m_search->SearchPlaceholder()); + + UpdateSearchControlsTooltips(); + + connect(ui->lineEditFind, &QLineEdit::textChanged, this, [this](const QString &term) { m_search->Find(term); }); + connect(ui->lineEditFind, &QLineEdit::editingFinished, this, + [this]() + { + SaveSearchRequest(); + InitSearchHistory(); + m_search->Find(ui->lineEditFind->text()); + }); + connect(ui->toolButtonFindPrevious, &QToolButton::clicked, this, + [this]() + { + SaveSearchRequest(); + InitSearchHistory(); + m_search->FindPrevious(); + ui->labelResults->setText( + QStringLiteral("%1/%2").arg(m_search->MatchIndex() + 1).arg(m_search->MatchCount())); + }); + connect(ui->toolButtonFindNext, &QToolButton::clicked, this, + [this]() + { + SaveSearchRequest(); + InitSearchHistory(); + m_search->FindNext(); + ui->labelResults->setText( + QStringLiteral("%1/%2").arg(m_search->MatchIndex() + 1).arg(m_search->MatchCount())); + }); + + connect(m_search.data(), &VTableSearch::HasResult, this, + [this](bool state) + { + ui->toolButtonFindPrevious->setEnabled(state); + ui->toolButtonFindNext->setEnabled(state); + + if (state) + { + ui->labelResults->setText( + QStringLiteral("%1/%2").arg(m_search->MatchIndex() + 1).arg(m_search->MatchCount())); + } + else + { + ui->labelResults->setText(tr("0 results")); + } + + QPalette palette; + + if (not state && not ui->lineEditFind->text().isEmpty()) + { + palette.setColor(QPalette::Text, Qt::red); + ui->lineEditFind->setPalette(palette); + + palette.setColor(QPalette::Active, ui->labelResults->foregroundRole(), Qt::red); + palette.setColor(QPalette::Inactive, ui->labelResults->foregroundRole(), Qt::red); + ui->labelResults->setPalette(palette); + } + else + { + ui->lineEditFind->setPalette(palette); + ui->labelResults->setPalette(palette); + } + }); + + connect(ui->toolButtonCaseSensitive, &QToolButton::toggled, this, + [this](bool checked) + { + m_search->SetMatchCase(checked); + m_search->Find(ui->lineEditFind->text()); + ui->lineEditFind->setPlaceholderText(m_search->SearchPlaceholder()); + }); + + connect(ui->toolButtonWholeWord, &QToolButton::toggled, this, + [this](bool checked) + { + m_search->SetMatchWord(checked); + m_search->Find(ui->lineEditFind->text()); + ui->lineEditFind->setPlaceholderText(m_search->SearchPlaceholder()); + }); + + connect(ui->toolButtonRegexp, &QToolButton::toggled, this, + [this](bool checked) + { + m_search->SetMatchRegexp(checked); + + if (checked) + { + ui->toolButtonWholeWord->blockSignals(true); + ui->toolButtonWholeWord->setChecked(false); + ui->toolButtonWholeWord->blockSignals(false); + ui->toolButtonWholeWord->setEnabled(false); + + ui->toolButtonUseUnicodeProperties->setEnabled(true); + } + else + { + ui->toolButtonWholeWord->setEnabled(true); + ui->toolButtonUseUnicodeProperties->blockSignals(true); + ui->toolButtonUseUnicodeProperties->setChecked(false); + ui->toolButtonUseUnicodeProperties->blockSignals(false); + ui->toolButtonUseUnicodeProperties->setEnabled(false); + } + m_search->Find(ui->lineEditFind->text()); + ui->lineEditFind->setPlaceholderText(m_search->SearchPlaceholder()); + }); + + connect(ui->toolButtonUseUnicodeProperties, &QToolButton::toggled, this, + [this](bool checked) + { + m_search->SetUseUnicodePreperties(checked); + m_search->Find(ui->lineEditFind->text()); + }); + + m_searchHistory->setStyleSheet(QStringLiteral("QMenu { menu-scrollable: 1; }")); + InitSearchHistory(); + ui->pushButtonSearch->setMenu(m_searchHistory); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MeasurementsWereSaved(bool saved) +{ + setWindowModified(!saved); + not m_mIsReadOnly ? ui->actionSave->setEnabled(!saved) : ui->actionSave->setEnabled(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SetCurrentFile(const QString &fileName) +{ + m_curFile = fileName; + if (m_curFile.isEmpty()) + { + ui->lineEditPathToFile->setText('<'_L1 + tr("Empty") + '>'_L1); + ui->lineEditPathToFile->setToolTip(tr("File was not saved yet.")); + ui->lineEditPathToFile->setCursorPosition(0); + ui->pushButtonShowInExplorer->setEnabled(false); + } + else + { + ui->lineEditPathToFile->setText(QDir::toNativeSeparators(m_curFile)); + ui->lineEditPathToFile->setToolTip(QDir::toNativeSeparators(m_curFile)); + ui->lineEditPathToFile->setCursorPosition(0); + ui->pushButtonShowInExplorer->setEnabled(true); + auto *settings = MApplication::VApp()->TapeSettings(); + QStringList files = settings->GetRecentKMFileList(); + files.removeAll(fileName); + files.prepend(fileName); + while (files.size() > MaxRecentFiles) + { + files.removeLast(); + } + settings->SetRecentKMFileList(files); + UpdateRecentFileActions(); + } + + UpdateWindowTitle(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::SaveKnownMeasurements(const QString &fileName, QString &error) -> bool +{ + const bool result = m_m->SaveDocument(fileName, error); + if (result) + { + SetCurrentFile(fileName); + MeasurementsWereSaved(result); + } + return result; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::MaybeSave() -> bool +{ + if (!isWindowModified()) + { + return true; + } + + if (m_curFile.isEmpty() && ui->tableWidget->rowCount() == 0) + { + return true; // Don't ask if file was created without modifications. + } + + QScopedPointer messageBox( + new QMessageBox(QMessageBox::Warning, tr("Unsaved changes"), + tr("Measurements have been modified. Do you want to save your changes?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this, Qt::Sheet)); + + messageBox->setDefaultButton(QMessageBox::Yes); + messageBox->setEscapeButton(QMessageBox::Cancel); + + if (QAbstractButton *button = messageBox->button(QMessageBox::Yes)) + { + button->setText(m_curFile.isEmpty() || m_mIsReadOnly ? tr("Save…") : tr("Save")); + } + + if (QAbstractButton *button = messageBox->button(QMessageBox::No)) + { + button->setText(tr("Don't Save")); + } + + messageBox->setWindowModality(Qt::ApplicationModal); + const auto ret = static_cast(messageBox->exec()); + + switch (ret) + { + case QMessageBox::Yes: + if (m_mIsReadOnly) + { + return FileSaveAs(); + } + else + { + return FileSave(); + } + case QMessageBox::No: + return true; + case QMessageBox::Cancel: + return false; + default: + break; + } + + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::UpdatePadlock(bool ro) +{ + ui->actionReadOnly->setChecked(ro); + ui->actionReadOnly->setIcon(ro ? QIcon("://tapeicon/24x24/padlock_locked.png") + : QIcon("://tapeicon/24x24/padlock_opened.png")); + ui->actionReadOnly->setDisabled(m_mIsReadOnly); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::AddCell(const QString &text, int row, int column, int aligment) -> QTableWidgetItem * +{ + auto *item = new QTableWidgetItem(text); + SetTextAlignment(item, static_cast(aligment)); + item->setToolTip(text); + + // set the item non-editable (view only), and non-selectable + Qt::ItemFlags flags = item->flags(); + flags &= ~(Qt::ItemIsEditable); // reset/clear the flag + item->setFlags(flags); + + ui->tableWidget->setItem(row, column, item); + + return item; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ReadSettings() +{ + const VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + + if (settings->status() == QSettings::NoError) + { + restoreGeometry(settings->GetKMGeometry()); + restoreState(settings->GetKMToolbarsState(), static_cast(AppVersion())); + + // Text under tool buton icon + ToolBarStyles(); + + // Stack limit + // VAbstractApplication::VApp()->getUndoStack()->setUndoLimit(settings->GetUndoCount()); + } + else + { + qWarning() << tr("Cannot read settings from a malformed .INI file."); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::WriteSettings() +{ + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + settings->SetKMGeometry(saveGeometry()); + settings->SetKMToolbarsState(saveState(static_cast(AppVersion()))); + + settings->SetKMSearchOptionMatchCase(m_search->IsMatchCase()); + settings->SetKMSearchOptionWholeWord(m_search->IsMatchWord()); + settings->SetKMSearchOptionRegexp(m_search->IsMatchRegexp()); + settings->SetKMSearchOptionUseUnicodeProperties(m_search->IsUseUnicodePreperties()); + + settings->sync(); + if (settings->status() == QSettings::AccessError) + { + qWarning() << tr("Cannot save settings. Access denied."); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitIcons() +{ + QString iconResource = QStringLiteral("icon"); + ui->toolButtonAddImage->setIcon(VTheme::GetIconResource(iconResource, QStringLiteral("16x16/insert-image.png"))); + ui->toolButtonRemoveImage->setIcon(VTheme::GetIconResource(iconResource, QStringLiteral("16x16/remove-image.png"))); + + int index = ui->tabWidget->indexOf(ui->tabImages); + if (index != -1) + { + ui->tabWidget->setTabIcon(index, VTheme::GetIconResource(iconResource, QStringLiteral("16x16/viewimage.png"))); + } + + QString tapeIconResource = QStringLiteral("tapeicon"); + ui->actionMeasurementDiagram->setIcon( + VTheme::GetIconResource(tapeIconResource, QStringLiteral("24x24/mannequin.png"))); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitSearchHistory() +{ + QStringList searchHistory = MApplication::VApp()->TapeSettings()->GetTapeSearchHistory(); + m_searchHistory->clear(); + + if (searchHistory.isEmpty()) + { + QAction *action = m_searchHistory->addAction('<'_L1 + tr("Empty", "list") + '>'_L1); + action->setDisabled(true); + return; + } + + for (const auto &term : searchHistory) + { + QAction *action = m_searchHistory->addAction(term); + action->setData(term); + connect(action, &QAction::triggered, this, + [this]() + { + auto *action = qobject_cast(sender()); + if (action != nullptr) + { + QString term = action->data().toString(); + ui->lineEditFind->setText(term); + m_search->Find(term); + ui->lineEditFind->setFocus(); + } + }); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::SaveSearchRequest() +{ + QStringList searchHistory = MApplication::VApp()->TapeSettings()->GetKMSearchHistory(); + QString term = ui->lineEditFind->text(); + if (term.isEmpty()) + { + return; + } + + searchHistory.removeAll(term); + searchHistory.prepend(term); + while (searchHistory.size() > VTableSearch::MaxHistoryRecords) + { + searchHistory.removeLast(); + } + MApplication::VApp()->TapeSettings()->SetKMSearchHistory(searchHistory); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::UpdateSearchControlsTooltips() +{ + auto UpdateToolTip = [this](QAbstractButton *button) + { + if (button->toolTip().contains("%1"_L1)) + { + m_serachButtonTooltips.insert(button, button->toolTip()); + button->setToolTip(button->toolTip().arg(button->shortcut().toString(QKeySequence::NativeText))); + } + else if (m_serachButtonTooltips.contains(button)) + { + QString tooltip = m_serachButtonTooltips.value(button); + button->setToolTip(tooltip.arg(button->shortcut().toString(QKeySequence::NativeText))); + } + }; + + UpdateToolTip(ui->toolButtonCaseSensitive); + UpdateToolTip(ui->toolButtonWholeWord); + UpdateToolTip(ui->toolButtonRegexp); + UpdateToolTip(ui->toolButtonUseUnicodeProperties); + UpdateToolTip(ui->pushButtonSearch); + UpdateToolTip(ui->toolButtonFindPrevious); + UpdateToolTip(ui->toolButtonFindNext); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::UnknownMeasurementImage() -> QString +{ + return u"

?

" + u"

%1

"_s.arg(tr("Unknown measurement")); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::CreateWindowMenu(QMenu *menu) +{ + SCASSERT(menu != nullptr) + + QAction *action = menu->addAction(tr("&New Window")); + connect(action, &QAction::triggered, this, []() { MApplication::VApp()->NewMainKMWindow()->activateWindow(); }); + action->setMenuRole(QAction::NoRole); + menu->addSeparator(); + + const QList windows = MApplication::VApp()->MainKMWindows(); + for (int i = 0; i < windows.count(); ++i) + { + TKMMainWindow *window = windows.at(i); + + QString title = QStringLiteral("%1. %2").arg(i + 1).arg(window->windowTitle()); + const auto index = title.lastIndexOf("[*]"_L1); + if (index != -1) + { + window->isWindowModified() ? title.replace(index, 3, '*'_L1) : title.replace(index, 3, QString()); + } + + QAction *action = menu->addAction(title, this, &TKMMainWindow::ShowWindow); + action->setData(i); + action->setCheckable(true); + action->setMenuRole(QAction::NoRole); + if (window->isActiveWindow()) + { + action->setChecked(true); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::RefreshTable() +{ + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->clearContents(); + + if (!m_known.IsValid()) + { + m_known = m_m->KnownMeasurements(); + } + + const QMap orderedTable = m_known.OrderedMeasurments(); + qint32 currentRow = -1; + ui->tableWidget->setRowCount(static_cast(orderedTable.size())); + for (auto iMap = orderedTable.constBegin(); iMap != orderedTable.constEnd(); ++iMap) + { + const VKnownMeasurement &m = iMap.value(); + currentRow++; + + QTableWidgetItem *item = AddCell(m.name, currentRow, ColumnName, Qt::AlignVCenter); // name + item->setData(Qt::UserRole, m.name); + + AddCell(m.fullName, currentRow, ColumnFullName, Qt::AlignVCenter); + AddCell(m.group, currentRow, ColumnGroup, Qt::AlignVCenter); + } + + ui->tableWidget->blockSignals(false); + + ui->actionExportToCSV->setEnabled(ui->tableWidget->rowCount() > 0); + + m_groupCompleter->setModel(new QStringListModel(m_known.Groups(), m_groupCompleter)); + + QGuiApplication::restoreOverrideCursor(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::RefreshImages() +{ + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + int row = ui->listWidget->currentRow(); + + ui->listWidget->blockSignals(true); + ui->listWidget->clear(); + + if (!m_known.IsValid()) + { + m_known = m_m->KnownMeasurements(); + } + QMap images = m_known.Images(); + + int index = 1; + for (auto i = images.cbegin(), end = images.cend(); i != end; ++i) + { + if (i.key().isNull()) + { + continue; + } + + auto *item = new QListWidgetItem(ui->listWidget); + item->setTextAlignment(Qt::AlignCenter); + + if (i.value().IsValid()) + { + QSize size = i.value().Size(); + QSize targetSize = ui->listWidget->iconSize(); + + double scalingFactorWidth = static_cast(targetSize.width()) / size.width(); + double scalingFactorHeight = static_cast(targetSize.height()) / size.height(); + + int newWidth, newHeight; + + if (scalingFactorWidth < scalingFactorHeight) + { + newWidth = targetSize.width(); + newHeight = static_cast(size.height() * scalingFactorWidth); + } + else + { + newWidth = static_cast(size.width() * scalingFactorHeight); + newHeight = targetSize.height(); + } + + QPixmap background(targetSize); + background.fill(Qt::transparent); + + QPainter painter(&background); + QPixmap sourcePixmap = i.value().GetPixmap(newWidth, newHeight); + + // Calculate the position to center the source pixmap in the transparent pixmap + int x = (background.width() - sourcePixmap.width()) / 2; + int y = background.height() - sourcePixmap.height(); + + painter.drawPixmap(x, y, sourcePixmap); + painter.end(); + + item->setIcon(background); + } + else + { + QImageReader imageReader(QStringLiteral("://icon/svg/broken_path.svg")); + imageReader.setScaledSize(ui->listWidget->iconSize()); + QImage image = imageReader.read(); + item->setIcon(QPixmap::fromImage(image)); + } + + QString title = i.value().Title(); + if (title.isEmpty()) + { + title = tr("Unnamed image %1").arg(index); + ++index; + } + + item->setText(title); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + item->setData(Qt::UserRole, i.key()); + } + + ui->tableWidget->blockSignals(false); + + ui->listWidget->blockSignals(true); + ui->listWidget->setCurrentRow(row); + ui->listWidget->blockSignals(false); + + QGuiApplication::restoreOverrideCursor(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ShowMDiagram(const VPatternImage &image) +{ + ui->labelDiagram->setPixmap(QPixmap()); + ui->labelDiagram->setCursor(QCursor()); + ui->labelDiagram->disconnect(); + + if (image.IsValid()) + { + ui->labelDiagram->setCursor(Qt::PointingHandCursor); + ui->labelDiagram->setPixmap(image.GetPixmap()); + connect(ui->labelDiagram, &VAspectRatioPixmapLabel::clicked, this, &TKMMainWindow::ShowImage, + Qt::UniqueConnection); + } + else + { + ui->labelDiagram->setText(UnknownMeasurementImage()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::Open(const QString &pathTo, const QString &filter) -> QString +{ + const QString mPath = QFileDialog::getOpenFileName(this, tr("Open file"), pathTo, filter, nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); + + if (not mPath.isEmpty()) + { + if (m_m == nullptr) + { + LoadFile(mPath); + } + else + { + MApplication::VApp()->NewMainKMWindow()->LoadFile(mPath); + } + } + + return mPath; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::Controls() +{ + ui->toolButtonRemoveMeasurement->setEnabled(ui->tableWidget->rowCount() > 0); + + if (ui->tableWidget->rowCount() >= 2) + { + if (ui->tableWidget->currentRow() == 0) + { + ui->toolButtonTop->setEnabled(false); + ui->toolButtonUp->setEnabled(false); + ui->toolButtonDown->setEnabled(true); + ui->toolButtonBottom->setEnabled(true); + } + else if (ui->tableWidget->currentRow() == ui->tableWidget->rowCount() - 1) + { + ui->toolButtonTop->setEnabled(true); + ui->toolButtonUp->setEnabled(true); + ui->toolButtonDown->setEnabled(false); + ui->toolButtonBottom->setEnabled(false); + } + else + { + ui->toolButtonTop->setEnabled(true); + ui->toolButtonUp->setEnabled(true); + ui->toolButtonDown->setEnabled(true); + ui->toolButtonBottom->setEnabled(true); + } + } + else + { + ui->toolButtonTop->setEnabled(false); + ui->toolButtonUp->setEnabled(false); + ui->toolButtonDown->setEnabled(false); + ui->toolButtonBottom->setEnabled(false); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::MFields(bool enabled) +{ + ui->lineEditName->setEnabled(enabled); + ui->plainTextEditDescription->setEnabled(enabled); + ui->lineEditFullName->setEnabled(enabled); + ui->comboBoxMUnits->setEnabled(enabled); + ui->comboBoxDiagram->setEnabled(enabled); + ui->pushButtonGrow->setEnabled(enabled); + ui->plainTextEditFormula->setEnabled(enabled); + ui->lineEditGroup->setEnabled(enabled); + + ui->lineEditFind->setEnabled(enabled); + if (enabled && not ui->lineEditFind->text().isEmpty()) + { + ui->toolButtonFindPrevious->setEnabled(enabled); + ui->toolButtonFindNext->setEnabled(enabled); + } + else + { + ui->toolButtonFindPrevious->setEnabled(false); + ui->toolButtonFindNext->setEnabled(false); + } + + Controls(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::ImageFields(bool enabled) +{ + ui->lineEditImageTitle->setEnabled(enabled); + ui->toolButtonRemoveImage->setEnabled(enabled); + ui->toolButtonSaveImage->setEnabled(enabled); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TKMMainWindow::GenerateMeasurementName() const -> QString +{ + QHash m = m_known.Measurments(); + qint32 num = 1; + QString name; + do + { + name = VAbstractApplication::VApp()->TrVars()->InternalVarToUser(measurement_) + QString::number(num); + num++; + } while (m.contains(name)); + + return name; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitMeasurementUnits() +{ + ui->comboBoxMUnits->blockSignals(true); + + int current = -1; + if (ui->comboBoxMUnits->currentIndex() != -1) + { + current = ui->comboBoxMUnits->currentData().toInt(); + } + + ui->comboBoxMUnits->clear(); + ui->comboBoxMUnits->addItem(tr("Length units"), QVariant(static_cast(MUnits::Table))); + ui->comboBoxMUnits->addItem(tr("Degrees"), QVariant(static_cast(MUnits::Degrees))); + + int i = ui->comboBoxMUnits->findData(current); + if (i != -1) + { + ui->comboBoxMUnits->setCurrentIndex(i); + } + + ui->comboBoxMUnits->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void TKMMainWindow::InitMeasurementDiagramList() +{ + ui->comboBoxDiagram->clear(); + + QMap images = m_known.Images(); + + ui->comboBoxDiagram->addItem(tr("None"), QUuid()); + + int index = 1; + for (auto i = images.cbegin(), end = images.cend(); i != end; ++i) + { + QString title = i.value().Title(); + if (title.isEmpty()) + { + title = tr("Unnamed image %1").arg(index); + ++index; + } + + ui->comboBoxDiagram->addItem(title, i.key()); + } +} diff --git a/src/app/tape/tkmmainwindow.h b/src/app/tape/tkmmainwindow.h new file mode 100644 index 000000000..b028a301f --- /dev/null +++ b/src/app/tape/tkmmainwindow.h @@ -0,0 +1,186 @@ +/************************************************************************ + ** + ** @file tkmmainwindow.h + ** @author Roman Telezhynskyi + ** @date 31 10, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef TKMMAINWINDOW_H +#define TKMMAINWINDOW_H + +#include "../vformat/knownmeasurements/vknownmeasurements.h" +#include "../vmisc/vabstractshortcutmanager.h" +#include "../vmisc/vlockguard.h" +#include "../vmisc/vtablesearch.h" +#include "../vwidgets/vabstractmainwindow.h" + +#include +#include + +namespace Ui +{ +class TKMMainWindow; +} + +class VKnownMeasurementsDocument; +class VPatternImage; +class QCompleter; + +class TKMMainWindow : public VAbstractMainWindow +{ + Q_OBJECT // NOLINT + +public: + explicit TKMMainWindow(QWidget *parent = nullptr); + ~TKMMainWindow() override; + + auto CurrentFile() const -> QString; + + auto LoadFile(const QString &path) -> bool; + + void UpdateWindowTitle(); + +public slots: + void ToolBarStyles(); + +protected: + void closeEvent(QCloseEvent *event) override; + void changeEvent(QEvent *event) override; + auto eventFilter(QObject *object, QEvent *event) -> bool override; + void ExportToCSVData(const QString &fileName, bool withHeader, int mib, const QChar &separator) final; + auto RecentFileList() const -> QStringList override; + +private slots: + void FileNew(); + void OpenKnownMeasurements(); + + bool FileSave(); // NOLINT(modernize-use-trailing-return-type) + bool FileSaveAs(); // NOLINT(modernize-use-trailing-return-type) + void AboutToShowWindowMenu(); + void ShowWindow() const; + void ImportDataFromCSV(); + +#if defined(Q_OS_MAC) + void OpenAt(QAction *where); +#endif // defined(Q_OS_MAC) + + void SaveKnownMeasurementsName(); + void SaveKnownMeasurementsDescription(); + + void RemoveMeasurement(); + void MoveTop(); + void MoveUp(); + void MoveDown(); + void MoveBottom(); + + void AddImage(); + void RemoveImage(); + void SaveImage(); + void ShowImage(); + + void AddKnown(); + + void ShowMData(); + void ShowImageData(); + + void DeployFormula(); + + void SaveMName(); + void SaveMFormula(); + void SaveMDescription(); + void SaveMFullName(); + void SaveMUnits(); + void SaveMGroup(); + void SaveMDiagram(); + + void SaveImageTitle(); + + void AskDefaultSettings(); + + void UpdateShortcuts(); + +private: + // cppcheck-suppress unknownMacro + Q_DISABLE_COPY_MOVE(TKMMainWindow) // NOLINT + Ui::TKMMainWindow *ui; + QString m_curFile{}; + bool m_isInitialized{false}; + bool m_mIsReadOnly{false}; + QMenu *m_searchHistory; + QSharedPointer> m_lock{nullptr}; + QSharedPointer m_search{}; + VKnownMeasurementsDocument *m_m{nullptr}; + int m_formulaBaseHeight{0}; + + QMultiHash m_actionShortcuts{}; + QMultiHash m_buttonShortcuts{}; + QHash m_serachButtonTooltips{}; + + QPointer m_tmpImage{}; + + VKnownMeasurements m_known{}; + + QCompleter *m_groupCompleter{}; + + void SetupMenu(); + void InitWindow(); + void InitSearch(); + + void MeasurementsWereSaved(bool saved); + void SetCurrentFile(const QString &fileName); + auto SaveKnownMeasurements(const QString &fileName, QString &error) -> bool; + + auto MaybeSave() -> bool; + void UpdatePadlock(bool ro); + auto AddCell(const QString &text, int row, int column, int aligment) -> QTableWidgetItem *; + + void ReadSettings(); + void WriteSettings(); + + void InitIcons(); + + void InitSearchHistory(); + void SaveSearchRequest(); + void UpdateSearchControlsTooltips(); + + static auto UnknownMeasurementImage() -> QString; + + void CreateWindowMenu(QMenu *menu); + + void RefreshTable(); + void RefreshImages(); + + void ShowMDiagram(const VPatternImage &image); + + auto Open(const QString &pathTo, const QString &filter) -> QString; + void Controls(); + void MFields(bool enabled); + void ImageFields(bool enabled); + + auto GenerateMeasurementName() const -> QString; + + void InitMeasurementUnits(); + void InitMeasurementDiagramList(); +}; + +#endif // TKMMAINWINDOW_H diff --git a/src/app/tape/tkmmainwindow.ui b/src/app/tape/tkmmainwindow.ui new file mode 100644 index 000000000..857086c4d --- /dev/null +++ b/src/app/tape/tkmmainwindow.ui @@ -0,0 +1,1255 @@ + + + TKMMainWindow + + + + 0 + 0 + 1221 + 921 + + + + + + + + :/tapeicon/64x64/logo.png:/tapeicon/64x64/logo.png + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-size:18pt;">Select New to create a new known measurements file.</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + 1 + + + + + :/tapeicon/16x16/measurement.png:/tapeicon/16x16/measurement.png + + + Measurements + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Search history <span style=" color:#888a85;">%1</span></p></body></html> + + + + + + + ../valentina/dialogs../valentina/dialogs + + + Alt+Down + + + + + + + true + + + Search + + + true + + + + + + + Qt::Vertical + + + + + + + 0 results + + + + + + + Qt::Vertical + + + + + + + + 24 + 24 + + + + + 14 + false + true + + + + <html><head/><body><p>Match Case <span style=" color:#888a85;">%1</span></p></body></html> + + + Cc + + + Alt+C + + + true + + + false + + + + + + + + 24 + 24 + + + + + 14 + true + + + + <html><head/><body><p>Match words <span style=" color:#888a85;">%1</span></p></body></html> + + + W + + + Alt+W + + + true + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + + 14 + true + + + + <html><head/><body><p>Match with regular expressions <span style=" color:#888a85;">%1</span></p></body></html> + + + .* + + + Alt+X + + + true + + + + + + + false + + + + 24 + 24 + + + + + 14 + true + + + + <html><head/><body><p>Use unicode properties <span style=" color:#888a85;">%1</span></p><p><br/><span style=" color:#888a85;">The meaning of the \w, \d, etc., character classes, as well as the meaning of their counterparts (\W, \D, etc.), is changed from matching ASCII characters only to matching any character with the corresponding Unicode property. For instance, \d is changed to match any character with the Unicode Nd (decimal digit) property; \w to match any character with either the Unicode L (letter) or N (digit) property, plus underscore, and so on. This option corresponds to the /u modifier in Perl regular expressions.</span></p></body></html> + + + U + + + Alt+U + + + true + + + + + + + Qt::Vertical + + + + + + + false + + + <html><head/><body><p>Find Previous <span style=" color:#888a85;">%1</span></p></body></html> + + + + + + + ../valentina/dialogs../valentina/dialogs + + + Shift+F3 + + + + + + + false + + + <html><head/><body><p>Find Next %1</p></body></html> + + + + + + + ../valentina/dialogs../valentina/dialogs + + + F3 + + + false + + + + + + + + + Qt::Vertical + + + + + 0 + 3 + + + + true + + + QAbstractItemView::SelectRows + + + true + + + + Name + + + + + Full name + + + + + Group + + + + + + true + + + + 0 + 2 + + + + Details + + + Details + + + + + + 9 + + + + + false + + + Move measurement top + + + + + + + .. + + + + + + + false + + + Move measurement up + + + + + + + .. + + + + + + + false + + + Move measurement down + + + + + + + .. + + + + + + + false + + + Move measurement bottom + + + + + + + .. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Delete measurement + + + + + + + .. + + + + + + + + + Units: + + + + + + + false + + + + 0 + 0 + + + + + + + + Name: + + + + + + + false + + + Measurement's name in a formula + + + Measurement's name in a formula + + + + + + + Full name: + + + + + + + false + + + + + + Measurement's human-readable name + + + + + + + Group: + + + + + + + false + + + + + + + + + + Formula: + + + + + + + + + false + + + + 0 + 0 + + + + + 16777215 + 28 + + + + true + + + + + + + false + + + + 18 + 18 + + + + + 0 + 0 + + + + <html><head/><body><p>Show full calculation in message box</p></body></html> + + + + + + + ../../libs/vtools/dialogs/support../../libs/vtools/dialogs/support + + + + 16 + 16 + + + + true + + + + + + + + + Description: + + + + + + + false + + + + 0 + 1 + + + + + 0 + 28 + + + + + + + + Diagram + + + + + + + + + + + + + + + + :/icon/light/16x16/viewimage.png:/icon/light/16x16/viewimage.png + + + Images + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Qt::Vertical + + + 4 + + + + + 0 + 1 + + + + + 0 + 4 + + + + + 96 + 96 + + + + QListView::Static + + + QListView::LeftToRight + + + QListView::IconMode + + + false + + + + + true + + + + 0 + 0 + + + + + 0 + 1 + + + + Details + + + Details + + + + + + 9 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + ... + + + + :/icon/light/16x16/insert-image.png:/icon/light/16x16/insert-image.png + + + + + + + false + + + Delete measurement + + + + + + + :/icon/light/16x16/remove-image.png:/icon/light/16x16/remove-image.png + + + + + + + false + + + Save image + + + ... + + + + .. + + + + + + + + + Title: + + + + + + + false + + + Measurement's name in a formula + + + + + + + + + + + + + .. + + + Information + + + + QFormLayout::ExpandingFieldsGrow + + + + + Path: + + + + + + + + + + 0 + 0 + + + + false + + + background: transparent; + + + false + + + true + + + Path to the measurement file + + + + + + + false + + + + 0 + 0 + + + + Show in Explorer + + + + + + + + + Name: + + + + + + + false + + + + 0 + 0 + + + + + + + + Description: + + + + + + + false + + + + 0 + 1 + + + + + + + + + + + + + + 0 + 0 + 1221 + 22 + + + + + File + + + + + + + + + + + + + + + + + &Help + + + + + + + &Measurments + + + + + + + &Window + + + + + + + + + + + Measurement diagram + + + 2 + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-size:340pt;">?</span></p><p align=\"center\">Unknown measurement</p></body></html> + + + false + + + + + + + + + toolBar + + + Qt::ToolButtonTextUnderIcon + + + TopToolBarArea + + + false + + + + + + + + + + + + false + + + + .. + + + &Save + + + QAction::NoRole + + + + + false + + + + .. + + + Save &As … + + + QAction::NoRole + + + + + + .. + + + &Quit + + + QAction::QuitRole + + + + + About &Qt + + + QAction::AboutQtRole + + + + + + .. + + + &About Tape + + + QAction::AboutRole + + + + + + .. + + + &New + + + QAction::NoRole + + + + + true + + + false + + + + :/tapeicon/24x24/padlock_opened.png:/tapeicon/24x24/padlock_opened.png + + + Read only + + + QAction::NoRole + + + + + Preferences + + + QAction::PreferencesRole + + + + + + .. + + + &Open + + + Ctrl+O + + + QAction::NoRole + + + + + true + + + true + + + + :/tapeicon/light/24x24/mannequin.png:/tapeicon/light/24x24/mannequin.png + + + Measurement diagram + + + QAction::NoRole + + + + + false + + + + :/tapeicon/24x24/red_plus.png:/tapeicon/24x24/red_plus.png + + + Add known + + + QAction::NoRole + + + + + false + + + Import from CSV + + + Import from CSV + + + QAction::NoRole + + + + + false + + + Export to CSV + + + QAction::NoRole + + + + + + VPlainTextEdit + QPlainTextEdit +
../vwidgets/vplaintextedit.h
+
+ + VAspectRatioPixmapLabel + QLabel +
../vwidgets/vaspectratiopixmaplabel.h
+
+
+ + + + + + + dockWidgetDiagram + visibilityChanged(bool) + actionMeasurementDiagram + setChecked(bool) + + + 1090 + 487 + + + -1 + -1 + + + + + actionMeasurementDiagram + triggered(bool) + dockWidgetDiagram + setVisible(bool) + + + -1 + -1 + + + 1090 + 487 + + + + +
diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp index 5bca42aca..200d16238 100644 --- a/src/app/tape/tmainwindow.cpp +++ b/src/app/tape/tmainwindow.cpp @@ -58,10 +58,8 @@ #include "dialogs/dialognewmeasurements.h" #include "dialogs/dialogrestrictdimension.h" #include "dialogs/dialogsetupmultisize.h" -#include "dialogs/dialogtapepreferences.h" #include "mapplication.h" // Should be last because of definning qApp -#include "qcursor.h" -#include "quuid.h" +#include "tkmmainwindow.h" #include "ui_tmainwindow.h" #include "vlitepattern.h" #include "vtapesettings.h" @@ -83,6 +81,7 @@ #endif #include +#include #include #include #include @@ -91,6 +90,7 @@ #include #include #include +#include #include #include @@ -247,7 +247,6 @@ void SetIndividualMeasurementDescription(int i, const QString &name, const QxtCs } } } -} // namespace // We need this enum in case we will add or delete a column. And also make code more readable. enum @@ -262,12 +261,12 @@ enum ColumnShiftC = 7, ColumnCorrection = 8 }; +} // namespace //--------------------------------------------------------------------------------------------------------------------- TMainWindow::TMainWindow(QWidget *parent) : VAbstractMainWindow(parent), ui(new Ui::TMainWindow), - m_formulaBaseHeight(0), m_gradation(new QTimer(this)), m_searchHistory(new QMenu(this)) { @@ -279,11 +278,6 @@ TMainWindow::TMainWindow(QWidget *parent) ui->labelDiagram->setText(UnknownMeasurementImage()); - ui->lineEditName->setClearButtonEnabled(true); - ui->lineEditFullName->setClearButtonEnabled(true); - ui->lineEditCustomerName->setClearButtonEnabled(true); - ui->lineEditEmail->setClearButtonEnabled(true); - ui->lineEditFind->installEventFilter(this); ui->plainTextEditFormula->installEventFilter(this); @@ -350,18 +344,6 @@ auto TMainWindow::CurrentFile() const -> QString return m_curFile; } -//--------------------------------------------------------------------------------------------------------------------- -void TMainWindow::RetranslateTable() -{ - if (m_m != nullptr) - { - const int row = ui->tableWidget->currentRow(); - RefreshTable(); - ui->tableWidget->selectRow(row); - m_search->RefreshList(ui->lineEditFind->text()); - } -} - //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::SetDimensionABase(qreal base) { @@ -447,7 +429,7 @@ auto TMainWindow::LoadFile(const QString &path) -> bool { if (m_m != nullptr) { - return MApplication::VApp()->NewMainWindow()->LoadFile(path); + return MApplication::VApp()->NewMainTapeWindow()->LoadFile(path); } if (not QFileInfo::exists(path)) @@ -461,7 +443,7 @@ auto TMainWindow::LoadFile(const QString &path) -> bool } // Check if file already opened - const QList list = MApplication::VApp()->MainWindows(); + const QList list = MApplication::VApp()->MainTapeWindows(); auto w = std::find_if(list.begin(), list.end(), [path](TMainWindow *window) { return window->CurrentFile() == path; }); if (w != list.end()) @@ -602,70 +584,71 @@ auto TMainWindow::LoadFile(const QString &path) -> bool //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::FileNew() { - if (m_m == nullptr) + if (m_m != nullptr) { - DialogNewMeasurements measurements(this); - if (measurements.exec() == QDialog::Rejected) + MApplication::VApp()->NewMainTapeWindow()->FileNew(); + return; + } + + DialogNewMeasurements measurements(this); + if (measurements.exec() == QDialog::Rejected) + { + return; + } + + m_mUnit = measurements.MUnit(); + m_pUnit = m_mUnit; + m_mType = measurements.Type(); + + if (m_mType == MeasurementsType::Multisize) + { + DialogSetupMultisize setup(m_mUnit, this); + if (setup.exec() == QDialog::Rejected) { + m_mUnit = Unit::Cm; + m_pUnit = m_mUnit; + m_mType = MeasurementsType::Individual; return; } - m_mUnit = measurements.MUnit(); - m_pUnit = m_mUnit; - m_mType = measurements.Type(); + m_data = new VContainer(VAbstractApplication::VApp()->TrVars(), &m_mUnit, VContainer::UniqueNamespace()); - if (m_mType == MeasurementsType::Multisize) - { - DialogSetupMultisize setup(m_mUnit, this); - if (setup.exec() == QDialog::Rejected) - { - m_mUnit = Unit::Cm; - m_pUnit = m_mUnit; - m_mType = MeasurementsType::Individual; - return; - } + m_m = new VMeasurements(m_mUnit, setup.Dimensions(), m_data); + m_m->SetFullCircumference(setup.FullCircumference()); + m_curFileFormatVersion = VVSTConverter::MeasurementMaxVer; + m_curFileFormatVersionStr = VVSTConverter::MeasurementMaxVerStr; - m_data = new VContainer(VAbstractApplication::VApp()->TrVars(), &m_mUnit, VContainer::UniqueNamespace()); - - m_m = new VMeasurements(m_mUnit, setup.Dimensions(), m_data); - m_m->SetFullCircumference(setup.FullCircumference()); - m_curFileFormatVersion = VVSTConverter::MeasurementMaxVer; - m_curFileFormatVersionStr = VVSTConverter::MeasurementMaxVerStr; - - SetCurrentDimensionValues(); - } - else - { - m_data = new VContainer(VAbstractApplication::VApp()->TrVars(), &m_mUnit, VContainer::UniqueNamespace()); - - m_m = new VMeasurements(m_mUnit, m_data); - m_curFileFormatVersion = VVITConverter::MeasurementMaxVer; - m_curFileFormatVersionStr = VVITConverter::MeasurementMaxVerStr; - } - - m_mIsReadOnly = m_m->IsReadOnly(); - UpdatePadlock(m_mIsReadOnly); - - SetCurrentFile(QString()); - MeasurementsWereSaved(false); - - InitWindow(); - - MeasurementGUI(); - - ui->actionImportFromCSV->setEnabled(true); + SetCurrentDimensionValues(); } else { - MApplication::VApp()->NewMainWindow()->FileNew(); + m_data = new VContainer(VAbstractApplication::VApp()->TrVars(), &m_mUnit, VContainer::UniqueNamespace()); + + m_m = new VMeasurements(m_mUnit, m_data); + m_curFileFormatVersion = VVITConverter::MeasurementMaxVer; + m_curFileFormatVersionStr = VVITConverter::MeasurementMaxVerStr; } + + m_m->SetKnownMeasurements(MApplication::VApp()->TapeSettings()->GetKnownMeasurementsId()); + + m_mIsReadOnly = m_m->IsReadOnly(); + UpdatePadlock(m_mIsReadOnly); + + SetCurrentFile(QString()); + MeasurementsWereSaved(false); + + InitWindow(); + + MeasurementGUI(); + + ui->actionImportFromCSV->setEnabled(true); } //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::OpenIndividual() { - const QString filter = tr("Individual measurements") + QStringLiteral(" (*.vit);;") + tr("Multisize measurements") + - QStringLiteral(" (*.vst);;") + tr("All files") + QStringLiteral(" (*.*)"); + const QString filter = tr("Individual measurements") + " (*.vit);;"_L1 + tr("Multisize measurements") + + " (*.vst);;"_L1 + tr("All files") + " (*.*)"_L1; // Use standard path to individual measurements QString pathTo = MApplication::VApp()->TapeSettings()->GetPathIndividualMeasurements(); @@ -731,31 +714,11 @@ void TMainWindow::CreateFromExisting() } else { - MApplication::VApp()->NewMainWindow()->CreateFromExisting(); + MApplication::VApp()->NewMainTapeWindow()->CreateFromExisting(); } } } -//--------------------------------------------------------------------------------------------------------------------- -void TMainWindow::Preferences() -{ - // Calling constructor of the dialog take some time. Because of this user have time to call the dialog twice. - static QPointer guard; // Prevent any second run - if (guard.isNull()) - { - QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - auto *preferences = new DialogTapePreferences(this); - // QScopedPointer needs to be sure any exception will never block guard - QScopedPointer dlg(preferences); - guard = preferences; - // Must be first - connect(dlg.data(), &DialogTapePreferences::UpdateProperties, this, &TMainWindow::WindowsLocale); - connect(dlg.data(), &DialogTapePreferences::UpdateProperties, this, &TMainWindow::ToolBarStyles); - QGuiApplication::restoreOverrideCursor(); - dlg->exec(); - } -} - //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::ToolBarStyles() { @@ -1050,7 +1013,7 @@ auto TMainWindow::FileSaveAs() -> bool if (not m_curFile.isEmpty()) { - fName = StrippedName(m_curFile); + fName = QFileInfo(m_curFile).fileName(); } QString fileName = QFileDialog::getSaveFileName(this, tr("Save as"), dir + '/'_L1 + fName, filters, nullptr, @@ -1156,7 +1119,7 @@ void TMainWindow::ShowWindow() const if (v.canConvert()) { const int offset = qvariant_cast(v); - const QList windows = MApplication::VApp()->MainWindows(); + const QList windows = MApplication::VApp()->MainTapeWindows(); windows.at(offset)->raise(); windows.at(offset)->activateWindow(); } @@ -1251,7 +1214,7 @@ void TMainWindow::AboutToShowDockMenu() QAction *actionPreferences = menu->addAction(tr("Preferences")); actionPreferences->setMenuRole(QAction::NoRole); - connect(actionPreferences, &QAction::triggered, this, &TMainWindow::Preferences); + connect(actionPreferences, &QAction::triggered, this, [this]() { MApplication::VApp()->Preferences(this); }); } } @@ -1325,11 +1288,13 @@ void TMainWindow::SaveKnownMeasurements(int index) { QUuid known = ui->comboBoxKnownMeasurements->itemData(index).toUuid(); + ui->actionEditCurrentKnownMeasurements->setEnabled(KnownMeasurementsRegistred(known)); + if (m_m->KnownMeasurements() != known) { m_m->SetKnownMeasurements(known); MeasurementsWereSaved(false); - InitKnownMeasurementsDescription(); + SyncKnownMeasurements(); } } @@ -1694,7 +1659,6 @@ void TMainWindow::ShowImage() if (row == -1) { - ui->toolButtonSaveImage->setDisabled(true); return; } @@ -1723,7 +1687,7 @@ void TMainWindow::ShowImage() } QMimeType mime = image.MimeTypeFromData(); - QString name = QDir::tempPath() + QDir::separator() + QStringLiteral("image.XXXXXX"); + QString name = QDir::tempPath() + QDir::separator() + "image.XXXXXX"_L1; QStringList suffixes = mime.suffixes(); if (not suffixes.isEmpty()) @@ -2825,54 +2789,56 @@ void TMainWindow::DimensionCustomNames() //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::AskDefaultSettings() { - if (MApplication::VApp()->IsAppInGUIMode()) + if (!MApplication::VApp()->IsAppInGUIMode()) { - VTapeSettings *settings = MApplication::VApp()->TapeSettings(); - if (not settings->IsLocaleSelected()) + return; + } + + VTapeSettings *settings = MApplication::VApp()->TapeSettings(); + if (not settings->IsLocaleSelected()) + { + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + DialogSelectLanguage dialog(this); + QGuiApplication::restoreOverrideCursor(); + dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Accepted) { - QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - DialogSelectLanguage dialog(this); - QGuiApplication::restoreOverrideCursor(); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Accepted) - { - QString locale = dialog.Locale(); - settings->SetLocale(locale); - VAbstractApplication::VApp()->LoadTranslation(locale); - } + QString locale = dialog.Locale(); + settings->SetLocale(locale); + VAbstractApplication::VApp()->LoadTranslation(locale); + } + } + + if (settings->IsAskCollectStatistic()) + { + DialogAskCollectStatistic dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + settings->SetCollectStatistic(dialog.CollectStatistic()); } - if (settings->IsAskCollectStatistic()) - { - DialogAskCollectStatistic dialog(this); - if (dialog.exec() == QDialog::Accepted) - { - settings->SetCollectStatistic(dialog.CollectStatistic()); - } + settings->SetAskCollectStatistic(false); + } - settings->SetAskCollectStatistic(false); + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + statistic->SetGUILanguage(settings->GetLocale()); + + QString clientID = settings->GetClientID(); + bool freshID = false; + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + freshID = true; } - if (settings->IsCollectStatistic()) - { - auto *statistic = VGAnalytics::Instance(); - statistic->SetGUILanguage(settings->GetLocale()); + statistic->Enable(true); - QString clientID = settings->GetClientID(); - bool freshID = false; - if (clientID.isEmpty()) - { - clientID = QUuid::createUuid().toString(); - settings->SetClientID(clientID); - statistic->SetClientID(clientID); - freshID = true; - } - - statistic->Enable(true); - - const qint64 uptime = MApplication::VApp()->AppUptime(); - freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); - } + const qint64 uptime = MApplication::VApp()->AppUptime(); + freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); } } @@ -2925,7 +2891,7 @@ void TMainWindow::SetupMenu() } } }); - connect(ui->actionPreferences, &QAction::triggered, this, &TMainWindow::Preferences); + connect(ui->actionPreferences, &QAction::triggered, this, [this]() { MApplication::VApp()->Preferences(this); }); for (auto &recentFileAct : m_recentFileActs) { @@ -2962,6 +2928,31 @@ void TMainWindow::SetupMenu() connect(ui->actionImportFromPattern, &QAction::triggered, this, &TMainWindow::ImportFromPattern); + connect(ui->actionCreateKnownMeasurements, &QAction::triggered, this, + []() { MApplication::VApp()->NewMainKMWindow(); }); + + connect(ui->actionEditCurrentKnownMeasurements, &QAction::triggered, this, + [this]() + { + QUuid id = m_m->KnownMeasurements(); + if (id.isNull()) + { + ui->actionEditCurrentKnownMeasurements->setDisabled(true); + return; + } + + VKnownMeasurementsDatabase *db = MApplication::VApp()->KnownMeasurementsDatabase(); + QHash known = db->AllKnownMeasurements(); + if (!known.contains(id)) + { + qCritical() << tr("Unknown known measurements: %1").arg(id.toString()); + ui->actionEditCurrentKnownMeasurements->setDisabled(true); + return; + } + + MApplication::VApp()->MainKMWindow()->LoadFile(known.value(id).path); + }); + // Window connect(ui->menuWindow, &QMenu::aboutToShow, this, &TMainWindow::AboutToShowWindowMenu); AboutToShowWindowMenu(); @@ -3097,6 +3088,7 @@ void TMainWindow::InitWindow() &TMainWindow::SaveMDimension); } + ui->actionEditCurrentKnownMeasurements->setEnabled(!m_m->KnownMeasurements().isNull()); ui->comboBoxKnownMeasurements->setEnabled(true); ui->comboBoxKnownMeasurements->clear(); InitKnownMeasurements(ui->comboBoxKnownMeasurements); @@ -3117,8 +3109,7 @@ void TMainWindow::InitWindow() ui->actionImportFromPattern->setEnabled(true); ui->actionSaveAs->setEnabled(true); - ui->lineEditName->setValidator( - new QRegularExpressionValidator(QRegularExpression(QStringLiteral("^$|") + NameRegExp()), this)); + ui->lineEditName->setValidator(new QRegularExpressionValidator(QRegularExpression("^$|"_L1 + NameRegExp()), this)); connect(ui->toolButtonRemove, &QToolButton::clicked, this, &TMainWindow::Remove); connect(ui->toolButtonTop, &QToolButton::clicked, this, &TMainWindow::MoveTop); @@ -3486,53 +3477,56 @@ auto TMainWindow::SaveMeasurements(const QString &fileName, QString &error) -> b //--------------------------------------------------------------------------------------------------------------------- auto TMainWindow::MaybeSave() -> bool { - if (this->isWindowModified()) + if (!isWindowModified()) { - if (m_curFile.isEmpty() && ui->tableWidget->rowCount() == 0) - { - return true; // Don't ask if file was created without modifications. - } - - QScopedPointer messageBox( - new QMessageBox(QMessageBox::Warning, tr("Unsaved changes"), - tr("Measurements have been modified. Do you want to save your changes?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this, Qt::Sheet)); - - messageBox->setDefaultButton(QMessageBox::Yes); - messageBox->setEscapeButton(QMessageBox::Cancel); - - if (QAbstractButton *button = messageBox->button(QMessageBox::Yes)) - { - button->setText(m_curFile.isEmpty() || m_mIsReadOnly ? tr("Save…") : tr("Save")); - } - - if (QAbstractButton *button = messageBox->button(QMessageBox::No)) - { - button->setText(tr("Don't Save")); - } - - messageBox->setWindowModality(Qt::ApplicationModal); - const auto ret = static_cast(messageBox->exec()); - - switch (ret) - { - case QMessageBox::Yes: - if (m_mIsReadOnly) - { - return FileSaveAs(); - } - else - { - return FileSave(); - } - case QMessageBox::No: - return true; - case QMessageBox::Cancel: - return false; - default: - break; - } + return true; } + + if (m_curFile.isEmpty() && ui->tableWidget->rowCount() == 0) + { + return true; // Don't ask if file was created without modifications. + } + + QScopedPointer messageBox( + new QMessageBox(QMessageBox::Warning, tr("Unsaved changes"), + tr("Measurements have been modified. Do you want to save your changes?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this, Qt::Sheet)); + + messageBox->setDefaultButton(QMessageBox::Yes); + messageBox->setEscapeButton(QMessageBox::Cancel); + + if (QAbstractButton *button = messageBox->button(QMessageBox::Yes)) + { + button->setText(m_curFile.isEmpty() || m_mIsReadOnly ? tr("Save…") : tr("Save")); + } + + if (QAbstractButton *button = messageBox->button(QMessageBox::No)) + { + button->setText(tr("Don't Save")); + } + + messageBox->setWindowModality(Qt::ApplicationModal); + const auto ret = static_cast(messageBox->exec()); + + switch (ret) + { + case QMessageBox::Yes: + if (m_mIsReadOnly) + { + return FileSaveAs(); + } + else + { + return FileSave(); + } + case QMessageBox::No: + return true; + case QMessageBox::Cancel: + return false; + default: + break; + } + return true; } @@ -3654,7 +3648,7 @@ void TMainWindow::RefreshMeasurementData(const QSharedPointer &mea } else { - AddCell(known.description, currentRow, ColumnFullName, Qt::AlignVCenter); + AddCell(known.fullName, currentRow, ColumnFullName, Qt::AlignVCenter); } QString calculatedValue; @@ -3686,7 +3680,7 @@ void TMainWindow::RefreshMeasurementData(const QSharedPointer &mea } else { - AddCell(known.description, currentRow, ColumnFullName, Qt::AlignVCenter); + AddCell(known.fullName, currentRow, ColumnFullName, Qt::AlignVCenter); } QString calculatedValue; @@ -3832,11 +3826,11 @@ void TMainWindow::UpdateWindowTitle() // #ifdef Q_OS_WIN32 // qt_ntfs_permission_lookup--; // turn it off again // #endif /*Q_OS_WIN32*/ - showName = StrippedName(m_curFile); + showName = QFileInfo(m_curFile).fileName(); } else { - auto index = MApplication::VApp()->MainWindows().indexOf(this); + auto index = MApplication::VApp()->MainTapeWindows().indexOf(this); if (index != -1) { showName = tr("untitled %1").arg(index + 1); @@ -3852,7 +3846,7 @@ void TMainWindow::UpdateWindowTitle() if (m_mIsReadOnly || not isFileWritable) { - showName += QStringLiteral(" (") + tr("read only") + ')'_L1; + showName += " ("_L1 + tr("read only") + ')'_L1; } setWindowTitle(showName); @@ -3882,6 +3876,64 @@ void TMainWindow::UpdateWindowTitle() #endif // defined(Q_OS_MAC) } +//--------------------------------------------------------------------------------------------------------------------- +void TMainWindow::SyncKnownMeasurements() +{ + ui->comboBoxKnownMeasurements->blockSignals(true); + ui->comboBoxKnownMeasurements->clear(); + InitKnownMeasurements(ui->comboBoxKnownMeasurements); + const qint32 index = ui->comboBoxKnownMeasurements->findData(m_m->KnownMeasurements()); + ui->comboBoxKnownMeasurements->setCurrentIndex(index); + ui->comboBoxKnownMeasurements->blockSignals(false); + + InitKnownMeasurementsDescription(); + + const int row = ui->tableWidget->currentRow(); + + if (row != -1) + { + RefreshTable(false); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->selectRow(row); + ui->tableWidget->blockSignals(false); + + const QTableWidgetItem *nameField = ui->tableWidget->item(ui->tableWidget->currentRow(), ColumnName); // name + SCASSERT(nameField != nullptr) + QSharedPointer meash; + + try + { + // Translate to internal look. + meash = m_data->GetVariable(nameField->data(Qt::UserRole).toString()); + } + catch (const VExceptionBadId &e) + { + Q_UNUSED(e) + return; + } + + if (meash->IsCustom()) + { + return; + } + + ShowMDiagram(meash); + + VKnownMeasurementsDatabase *db = MApplication::VApp()->KnownMeasurementsDatabase(); + VKnownMeasurements knownDB = db->KnownMeasurements(m_m->KnownMeasurements()); + VKnownMeasurement known = knownDB.Measurement(meash->GetName()); + + ui->plainTextEditDescription->blockSignals(true); + ui->plainTextEditDescription->setPlainText(known.description); + ui->plainTextEditDescription->blockSignals(false); + + ui->lineEditFullName->blockSignals(true); + ui->lineEditFullName->setText(known.fullName); + ui->lineEditFullName->blockSignals(false); + } +} + //--------------------------------------------------------------------------------------------------------------------- auto TMainWindow::ClearCustomName(const QString &name) -> QString { @@ -3961,7 +4013,7 @@ auto TMainWindow::Open(const QString &pathTo, const QString &filter) -> QString } else { - MApplication::VApp()->NewMainWindow()->LoadFile(mPath); + MApplication::VApp()->NewMainTapeWindow()->LoadFile(mPath); } } @@ -4065,7 +4117,7 @@ auto TMainWindow::LoadFromExistingFile(const QString &path) -> bool { if (m_m != nullptr) { - return MApplication::VApp()->NewMainWindow()->LoadFile(path); + return MApplication::VApp()->NewMainTapeWindow()->LoadFile(path); } if (not QFileInfo::exists(path)) @@ -4079,7 +4131,7 @@ auto TMainWindow::LoadFromExistingFile(const QString &path) -> bool } // Check if file already opened - const QList list = MApplication::VApp()->MainWindows(); + const QList list = MApplication::VApp()->MainTapeWindows(); auto w = std::find_if(list.begin(), list.end(), [path](TMainWindow *window) { return window->CurrentFile() == path; }); if (w != list.end()) @@ -4121,6 +4173,8 @@ auto TMainWindow::LoadFromExistingFile(const QString &path) -> bool m_curFileFormatVersionStr = converter->GetFormatVersionStr(); m_m->setXMLContent(converter->Convert()); // Read again after conversion + m_m->SetKnownMeasurements(MApplication::VApp()->TapeSettings()->GetKnownMeasurementsId()); + m_mUnit = m_m->Units(); m_pUnit = m_mUnit; @@ -4176,11 +4230,11 @@ void TMainWindow::CreateWindowMenu(QMenu *menu) SCASSERT(menu != nullptr) QAction *action = menu->addAction(tr("&New Window")); - connect(action, &QAction::triggered, this, []() { MApplication::VApp()->NewMainWindow()->activateWindow(); }); + connect(action, &QAction::triggered, this, []() { MApplication::VApp()->NewMainTapeWindow()->activateWindow(); }); action->setMenuRole(QAction::NoRole); menu->addSeparator(); - const QList windows = MApplication::VApp()->MainWindows(); + const QList windows = MApplication::VApp()->MainTapeWindows(); for (int i = 0; i < windows.count(); ++i) { TMainWindow *window = windows.at(i); @@ -4728,6 +4782,11 @@ void TMainWindow::InitKnownMeasurements(QComboBox *combo) SCASSERT(combo != nullptr) combo->addItem(tr("None"), QUuid()); + if (!known.contains(m_m->KnownMeasurements())) + { + combo->addItem(tr("Invalid link"), m_m->KnownMeasurements()); + } + int index = 1; auto i = known.constBegin(); while (i != known.constEnd()) @@ -4751,14 +4810,27 @@ void TMainWindow::InitKnownMeasurementsDescription() VKnownMeasurementsDatabase *db = MApplication::VApp()->KnownMeasurementsDatabase(); QHash known = db->AllKnownMeasurements(); - ui->labelKnownMeasurementsDescription->clear(); + ui->plainTextEditKnownMeasurementsDescription->clear(); QUuid id = m_m->KnownMeasurements(); if (!id.isNull() && known.contains(id)) { - ui->labelKnownMeasurementsDescription->setText(known.value(id).description); + ui->plainTextEditKnownMeasurementsDescription->setPlainText(known.value(id).description); } } +//--------------------------------------------------------------------------------------------------------------------- +auto TMainWindow::KnownMeasurementsRegistred(const QUuid &id) -> bool +{ + if (id.isNull()) + { + return false; + } + + VKnownMeasurementsDatabase *db = MApplication::VApp()->KnownMeasurementsDatabase(); + QHash known = db->AllKnownMeasurements(); + return known.contains(id); +} + //--------------------------------------------------------------------------------------------------------------------- void TMainWindow::SetDecimals() { diff --git a/src/app/tape/tmainwindow.h b/src/app/tape/tmainwindow.h index 010e7eddf..5d6b7cb8c 100644 --- a/src/app/tape/tmainwindow.h +++ b/src/app/tape/tmainwindow.h @@ -49,6 +49,7 @@ class QLabel; class QxtCsvModel; class VMeasurement; class QAbstractButton; +class QUuid; class TMainWindow : public VAbstractMainWindow { @@ -60,8 +61,6 @@ public: auto CurrentFile() const -> QString; - void RetranslateTable(); - void SetDimensionABase(qreal base); void SetDimensionBBase(qreal base); void SetDimensionCBase(qreal base); @@ -71,6 +70,11 @@ public: void UpdateWindowTitle(); + void SyncKnownMeasurements(); + +public slots: + void ToolBarStyles(); + protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; @@ -84,8 +88,6 @@ private slots: void OpenMultisize(); void OpenTemplate(); void CreateFromExisting(); - void Preferences(); - void ToolBarStyles(); bool FileSave(); // NOLINT(modernize-use-trailing-return-type) bool FileSaveAs(); // NOLINT(modernize-use-trailing-return-type) @@ -176,7 +178,7 @@ private: QComboBox *m_gradationDimensionB{nullptr}; QComboBox *m_gradationDimensionC{nullptr}; QComboBox *m_comboBoxUnits{nullptr}; - int m_formulaBaseHeight; + int m_formulaBaseHeight{0}; QSharedPointer> m_lock{nullptr}; QSharedPointer m_search{}; QLabel *m_labelGradationDimensionA{nullptr}; @@ -302,6 +304,8 @@ private: void InitKnownMeasurements(QComboBox *combo); void InitKnownMeasurementsDescription(); + + static auto KnownMeasurementsRegistred(const QUuid &id) -> bool; }; #endif // TMAINWINDOW_H diff --git a/src/app/tape/tmainwindow.ui b/src/app/tape/tmainwindow.ui index 3b7ee438d..0f66f35c7 100644 --- a/src/app/tape/tmainwindow.ui +++ b/src/app/tape/tmainwindow.ui @@ -532,6 +532,9 @@ Measurement's name in a formula. + + true + @@ -763,6 +766,9 @@ Measurement's human-readable name. + + true + @@ -926,32 +932,7 @@
- - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - - - - - + @@ -967,7 +948,7 @@ - + QFrame::Box @@ -1024,14 +1005,14 @@ - + Customer name: - + false @@ -1045,16 +1026,19 @@ Customer's name + + true + - + Birth date: - + false @@ -1089,14 +1073,14 @@ - + Gender: - + false @@ -1109,14 +1093,14 @@ - + Email: - + false @@ -1130,16 +1114,19 @@ Customer's email address + + true + - + Notes: - + false @@ -1152,6 +1139,9 @@ + + + @@ -1217,6 +1207,9 @@ + + + @@ -1720,6 +1713,19 @@ false + + + Create Known Measurements + + + + + false + + + Edit current Known Measurements + + diff --git a/src/app/tape/vtapesettings.cpp b/src/app/tape/vtapesettings.cpp index 2d1dc380a..ab5404b04 100644 --- a/src/app/tape/vtapesettings.cpp +++ b/src/app/tape/vtapesettings.cpp @@ -43,20 +43,33 @@ namespace QT_WARNING_PUSH QT_WARNING_DISABLE_CLANG("-Wunused-member-function") +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingKnownMeasurementsRecentFileList, ("kmRecentFileList"_L1)) // NOLINT + Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPathsTemplates, ("paths/templates"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingDataBaseGeometry, ("database/geometry"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchHistoryTape, ("searchHistory/tape"_L1)) // NOLINT +// NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchHistoryKnownMeasurments, ("searchHistory/knownMeasurements"_L1)) // NOLINTNEXTLINE Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsTapeUseUnicodeProperties, ("searchOptions/tapeUseUnicodeProperties"_L1)) // NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsKMUseUnicodeProperties, + ("searchOptions/kmUseUnicodeProperties"_L1)) +// NOLINTNEXTLINE Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsTapeWholeWord, ("searchOptions/tapeWholeWord"_L1)) // NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsKMWholeWord, ("searchOptions/kmWholeWord"_L1)) +// NOLINTNEXTLINE Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsTapeRegexp, ("searchOptions/tapeRegexp"_L1)) // NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsKMRegexp, ("searchOptions/kmRegexp"_L1)) +// NOLINTNEXTLINE Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsTapeMatchCase, ("searchOptions/tapeMatchCase"_L1)) +// NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchOptionsKMMatchCase, ("searchOptions/kmMatchCase"_L1)) QT_WARNING_POP } // namespace @@ -103,6 +116,18 @@ void VTapeSettings::SetTapeSearchHistory(const QStringList &history) setValue(*settingSearchHistoryTape, history); } +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetKMSearchHistory() const -> QStringList +{ + return value(*settingSearchHistoryKnownMeasurments).toStringList(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetKMSearchHistory(const QStringList &history) +{ + setValue(*settingSearchHistoryKnownMeasurments, history); +} + //--------------------------------------------------------------------------------------------------------------------- auto VTapeSettings::GetTapeSearchOptionUseUnicodeProperties() const -> bool { @@ -115,6 +140,18 @@ void VTapeSettings::SetTapeSearchOptionUseUnicodeProperties(bool value) setValue(*settingSearchOptionsTapeUseUnicodeProperties, value); } +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetKMSearchOptionUseUnicodeProperties() const -> bool +{ + return value(*settingSearchOptionsKMUseUnicodeProperties, false).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetKMSearchOptionUseUnicodeProperties(bool value) +{ + setValue(*settingSearchOptionsKMUseUnicodeProperties, value); +} + //--------------------------------------------------------------------------------------------------------------------- auto VTapeSettings::GetTapeSearchOptionWholeWord() const -> bool { @@ -127,6 +164,18 @@ void VTapeSettings::SetTapeSearchOptionWholeWord(bool value) setValue(*settingSearchOptionsTapeWholeWord, value); } +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetKMSearchOptionWholeWord() const -> bool +{ + return value(*settingSearchOptionsKMWholeWord, false).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetKMSearchOptionWholeWord(bool value) +{ + setValue(*settingSearchOptionsKMWholeWord, value); +} + //--------------------------------------------------------------------------------------------------------------------- auto VTapeSettings::GetTapeSearchOptionRegexp() const -> bool { @@ -139,6 +188,18 @@ void VTapeSettings::SetTapeSearchOptionRegexp(bool value) setValue(*settingSearchOptionsTapeRegexp, value); } +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetKMSearchOptionRegexp() const -> bool +{ + return value(*settingSearchOptionsKMRegexp, false).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetKMSearchOptionRegexp(bool value) +{ + setValue(*settingSearchOptionsKMRegexp, value); +} + //--------------------------------------------------------------------------------------------------------------------- auto VTapeSettings::GetTapeSearchOptionMatchCase() const -> bool { @@ -150,3 +211,38 @@ void VTapeSettings::SetTapeSearchOptionMatchCase(bool value) { setValue(*settingSearchOptionsTapeMatchCase, value); } + +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetKMSearchOptionMatchCase() const -> bool +{ + return value(*settingSearchOptionsKMMatchCase, false).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetKMSearchOptionMatchCase(bool value) +{ + setValue(*settingSearchOptionsKMMatchCase, value); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VTapeSettings::GetRecentKMFileList() const -> QStringList +{ + const QStringList files = value(*settingKnownMeasurementsRecentFileList).toStringList(); + QStringList cleared; + + for (const auto &f : files) + { + if (QFileInfo::exists(f)) + { + cleared.append(f); + } + } + + return cleared; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VTapeSettings::SetRecentKMFileList(const QStringList &value) +{ + setValue(*settingKnownMeasurementsRecentFileList, value); +} diff --git a/src/app/tape/vtapesettings.h b/src/app/tape/vtapesettings.h index a1aac3863..cd1366368 100644 --- a/src/app/tape/vtapesettings.h +++ b/src/app/tape/vtapesettings.h @@ -57,18 +57,36 @@ public: auto GetTapeSearchHistory() const -> QStringList; void SetTapeSearchHistory(const QStringList &history); + auto GetKMSearchHistory() const -> QStringList; + void SetKMSearchHistory(const QStringList &history); + auto GetTapeSearchOptionUseUnicodeProperties() const -> bool; void SetTapeSearchOptionUseUnicodeProperties(bool value); + auto GetKMSearchOptionUseUnicodeProperties() const -> bool; + void SetKMSearchOptionUseUnicodeProperties(bool value); + auto GetTapeSearchOptionWholeWord() const -> bool; void SetTapeSearchOptionWholeWord(bool value); + auto GetKMSearchOptionWholeWord() const -> bool; + void SetKMSearchOptionWholeWord(bool value); + auto GetTapeSearchOptionRegexp() const -> bool; void SetTapeSearchOptionRegexp(bool value); + auto GetKMSearchOptionRegexp() const -> bool; + void SetKMSearchOptionRegexp(bool value); + auto GetTapeSearchOptionMatchCase() const -> bool; void SetTapeSearchOptionMatchCase(bool value); + auto GetKMSearchOptionMatchCase() const -> bool; + void SetKMSearchOptionMatchCase(bool value); + + auto GetRecentKMFileList() const -> QStringList; + void SetRecentKMFileList(const QStringList &value); + private: Q_DISABLE_COPY_MOVE(VTapeSettings) // NOLINT }; diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp index 2617b45fd..3a961eceb 100644 --- a/src/app/valentina/core/vapplication.cpp +++ b/src/app/valentina/core/vapplication.cpp @@ -736,7 +736,7 @@ void VApplication::StartDetachedProcess(const QString &program, const QStringLis const QString workingDirectory = QFileInfo(program).absoluteDir().absolutePath(); QProcess::startDetached(program, arguments, workingDirectory); #else - if (not program.endsWith(".app")) + if (not program.endsWith(".app"_L1)) { const QString workingDirectory = QFileInfo(program).absoluteDir().absolutePath(); QProcess::startDetached(program, arguments, workingDirectory); @@ -746,11 +746,11 @@ void VApplication::StartDetachedProcess(const QString &program, const QStringLis QStringList openArguments{"-n", QStringLiteral("/Applications/%1").arg(program)}; if (not arguments.isEmpty()) { - openArguments.append("--args"); + openArguments.append("--args"_L1); openArguments += arguments; } - QProcess::startDetached("open", openArguments); + QProcess::startDetached("open"_L1, openArguments); } #endif } diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp index fad118d15..708d43e88 100644 --- a/src/app/valentina/mainwindow.cpp +++ b/src/app/valentina/mainwindow.cpp @@ -2165,33 +2165,33 @@ void MainWindow::ShowMeasurements() QStringList arguments; arguments.append(absoluteMPath); - arguments.append(QStringLiteral("-u")); + arguments.append("-u"_L1); arguments.append(UnitsToStr(VAbstractValApplication::VApp()->patternUnits())); if (VAbstractValApplication::VApp()->GetMeasurementsType() == MeasurementsType::Multisize) { if (m_currentDimensionA > 0) { - arguments.append(QStringLiteral("-a")); + arguments.append("-a"_L1); arguments.append(QString::number(m_currentDimensionA)); } if (m_currentDimensionB > 0) { - arguments.append(QStringLiteral("-b")); + arguments.append("-b"_L1); arguments.append(QString::number(m_currentDimensionB)); } if (m_currentDimensionC > 0) { - arguments.append(QStringLiteral("-c")); + arguments.append("-c"_L1); arguments.append(QString::number(m_currentDimensionC)); } } if (isNoScaling) { - arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); + arguments.append("--"_L1 + LONG_OPTION_NO_HDPI_SCALING); } VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); @@ -4079,7 +4079,7 @@ auto MainWindow::on_actionSaveAs_triggered() -> bool QString newFileName = tr("pattern") + QStringLiteral(".val"); if (not patternPath.isEmpty()) { - newFileName = StrippedName(patternPath); + newFileName = QFileInfo(patternPath).fileName(); } QString filters(tr("Pattern files") + QStringLiteral("(*.val)")); @@ -5186,7 +5186,7 @@ void MainWindow::ActionOpenTape_triggered() QStringList arguments; if (isNoScaling) { - arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); + arguments.append("--"_L1 + LONG_OPTION_NO_HDPI_SCALING); } VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); @@ -6282,7 +6282,7 @@ auto MainWindow::LoadPattern(QString fileName, const QString &customMeasureFile) return false; } - if (fileName.endsWith(QStringLiteral(".vit")) || fileName.endsWith(QStringLiteral(".vst"))) + if (fileName.endsWith(".vit"_L1) || fileName.endsWith(".vst"_L1)) { try { @@ -6296,7 +6296,7 @@ auto MainWindow::LoadPattern(QString fileName, const QString &customMeasureFile) QStringList arguments{fileName}; if (isNoScaling) { - arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); + arguments.append("--"_L1 + LONG_OPTION_NO_HDPI_SCALING); } VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); @@ -6317,14 +6317,30 @@ auto MainWindow::LoadPattern(QString fileName, const QString &customMeasureFile) } } - if (fileName.endsWith(QStringLiteral(".vlt"))) + if (fileName.endsWith(".vkm"_L1)) { // Here comes undocumented Valentina's feature. // Because app bundle in Mac OS X doesn't allow setup assosiation for Puzzle we must do this through Valentina QStringList arguments{fileName}; if (isNoScaling) { - arguments.append(QStringLiteral("--") + LONG_OPTION_NO_HDPI_SCALING); + arguments.append("--"_L1 + LONG_OPTION_NO_HDPI_SCALING); + arguments.append("--known"_L1); + } + + VApplication::StartDetachedProcess(VApplication::TapeFilePath(), arguments); + QCoreApplication::exit(V_EX_OK); + return false; // stop continue processing + } + + if (fileName.endsWith(".vlt"_L1)) + { + // Here comes undocumented Valentina's feature. + // Because app bundle in Mac OS X doesn't allow setup assosiation for Puzzle we must do this through Valentina + QStringList arguments{fileName}; + if (isNoScaling) + { + arguments.append("--"_L1 + LONG_OPTION_NO_HDPI_SCALING); } VApplication::StartDetachedProcess(VApplication::PuzzleFilePath(), arguments); @@ -7451,7 +7467,7 @@ auto MainWindow::GetPatternFileName() -> QString QString shownName = tr("untitled.val"); if (not VAbstractValApplication::VApp()->GetPatternPath().isEmpty()) { - shownName = StrippedName(VAbstractValApplication::VApp()->GetPatternPath()); + shownName = QFileInfo(VAbstractValApplication::VApp()->GetPatternPath()).fileName(); } shownName += "[*]"_L1; return shownName; @@ -7466,7 +7482,7 @@ auto MainWindow::GetMeasurementFileName() -> QString } QString shownName = QStringLiteral(" ["); - shownName += StrippedName(AbsoluteMPath(VAbstractValApplication::VApp()->GetPatternPath(), doc->MPath())); + shownName += QFileInfo(AbsoluteMPath(VAbstractValApplication::VApp()->GetPatternPath(), doc->MPath())).fileName(); if (m_mChanges) { diff --git a/src/libs/ifc/schema/individual_measurements/v0.6.1.xsd b/src/libs/ifc/schema/individual_measurements/v0.6.1.xsd index 8c52b62c8..177d743f5 100644 --- a/src/libs/ifc/schema/individual_measurements/v0.6.1.xsd +++ b/src/libs/ifc/schema/individual_measurements/v0.6.1.xsd @@ -98,4 +98,9 @@ + + + + + diff --git a/src/libs/ifc/schema/known_measurements/v1.0.0.xsd b/src/libs/ifc/schema/known_measurements/v1.0.0.xsd index d2cb48cee..e805d871c 100644 --- a/src/libs/ifc/schema/known_measurements/v1.0.0.xsd +++ b/src/libs/ifc/schema/known_measurements/v1.0.0.xsd @@ -30,7 +30,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -73,4 +73,9 @@ + + + + + diff --git a/src/libs/ifc/schema/multisize_measurements/v0.6.1.xsd b/src/libs/ifc/schema/multisize_measurements/v0.6.1.xsd index dab41dc82..e66bf8a1c 100644 --- a/src/libs/ifc/schema/multisize_measurements/v0.6.1.xsd +++ b/src/libs/ifc/schema/multisize_measurements/v0.6.1.xsd @@ -151,4 +151,9 @@ + + + + + diff --git a/src/libs/ifc/xml/vpatternimage.cpp b/src/libs/ifc/xml/vpatternimage.cpp index 86c50fef8..7c50256e8 100644 --- a/src/libs/ifc/xml/vpatternimage.cpp +++ b/src/libs/ifc/xml/vpatternimage.cpp @@ -160,6 +160,12 @@ auto VPatternImage::GetPixmap() const -> QPixmap //--------------------------------------------------------------------------------------------------------------------- auto VPatternImage::GetPixmap(int width, int height) const -> QPixmap +{ + return GetPixmap(QSize(width, height)); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPatternImage::GetPixmap(const QSize &size) const -> QPixmap { if (not IsValid()) { @@ -171,7 +177,7 @@ auto VPatternImage::GetPixmap(int width, int height) const -> QPixmap buffer.open(QIODevice::ReadOnly); QImageReader imageReader(&buffer); - imageReader.setScaledSize(QSize(width, height)); + imageReader.setScaledSize(size); QImage image = imageReader.read(); if (image.isNull()) diff --git a/src/libs/ifc/xml/vpatternimage.h b/src/libs/ifc/xml/vpatternimage.h index c8d3ca79e..d46f234f8 100644 --- a/src/libs/ifc/xml/vpatternimage.h +++ b/src/libs/ifc/xml/vpatternimage.h @@ -53,6 +53,7 @@ public: auto GetPixmap() const -> QPixmap; auto GetPixmap(int width, int height) const -> QPixmap; + auto GetPixmap(const QSize &size) const -> QPixmap; auto ErrorString() const -> const QString &; diff --git a/src/libs/qmuparser/qmudef.cpp b/src/libs/qmuparser/qmudef.cpp index dd7055968..37dc2bb37 100644 --- a/src/libs/qmuparser/qmudef.cpp +++ b/src/libs/qmuparser/qmudef.cpp @@ -26,28 +26,28 @@ enum State { - Init = 0, - Sign = 1, + Init = 0, + Sign = 1, Thousand = 2, Mantissa = 3, - Dot = 4, + Dot = 4, Abscissa = 5, - ExpMark = 6, - ExpSign = 7, + ExpMark = 6, + ExpSign = 7, Exponent = 8, - Done = 9 + Done = 9 }; enum InputToken { - InputSign = 1, - InputThousand = 2, - InputDigit = 3, - InputDot = 4, - InputExp = 5 + InputSign = 1, + InputThousand = 2, + InputDigit = 3, + InputDot = 4, + InputExp = 5 }; -static const QChar QmuEOF = QChar(static_cast(0xffff)); //guaranteed not to be a character. +static const QChar QmuEOF = QChar(static_cast(0xffff)); // guaranteed not to be a character. //--------------------------------------------------------------------------------------------------------------------- static auto GetChar(const QString &formula, int &index) -> QChar @@ -67,8 +67,7 @@ static auto EatWhiteSpace(const QString &formula, int &index) -> QChar do { c = GetChar(formula, index); - } - while ( c != QmuEOF && c.isSpace() ); + } while (c != QmuEOF && c.isSpace()); return c; } @@ -181,23 +180,8 @@ auto ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QC Q_UNUSED(decimalPoint) Q_UNUSED(groupSeparator) - QSet reserved - { - positiveSign, - negativeSign, - sign0, - sign1, - sign2, - sign3, - sign4, - sign5, - sign6, - sign7, - sign8, - sign9, - expUpper, - expLower - }; + QSet reserved{positiveSign, negativeSign, sign0, sign1, sign2, sign3, sign4, + sign5, sign6, sign7, sign8, sign9, expUpper, expLower}; if (reserved.contains(decimal) || reserved.contains(thousand)) { @@ -206,29 +190,84 @@ auto ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QC } // row - current state, column - new state - static uchar table[9][6] = - { + static uchar table[9][6] = { /* None InputSign InputThousand InputDigit InputDot InputExp */ - { 0, State::Sign, 0, State::Mantissa, State::Dot, 0, }, // Init - { 0, 0, 0, State::Mantissa, State::Dot, 0, }, // Sign - { 0, 0, 0, State::Mantissa, 0, 0, }, // Thousand - { State::Done, State::Done, State::Thousand, State::Mantissa, State::Dot, State::ExpMark,}, // Mantissa - { 0, 0, 0, State::Abscissa, 0, 0, }, // Dot - { State::Done, State::Done, 0, State::Abscissa, 0, State::ExpMark,}, // Abscissa - { 0, State::ExpSign, 0, State::Exponent, 0, 0, }, // ExpMark - { 0, 0, 0, State::Exponent, 0, 0, }, // ExpSign - { State::Done, 0, 0, State::Exponent, 0, State::Done } // Exponent + { + 0, + State::Sign, + 0, + State::Mantissa, + State::Dot, + 0, + }, // Init + { + 0, + 0, + 0, + State::Mantissa, + State::Dot, + 0, + }, // Sign + { + 0, + 0, + 0, + State::Mantissa, + 0, + 0, + }, // Thousand + { + State::Done, + State::Done, + State::Thousand, + State::Mantissa, + State::Dot, + State::ExpMark, + }, // Mantissa + { + 0, + 0, + 0, + State::Abscissa, + 0, + 0, + }, // Dot + { + State::Done, + State::Done, + 0, + State::Abscissa, + 0, + State::ExpMark, + }, // Abscissa + { + 0, + State::ExpSign, + 0, + State::Exponent, + 0, + 0, + }, // ExpMark + { + 0, + 0, + 0, + State::Exponent, + 0, + 0, + }, // ExpSign + {State::Done, 0, 0, State::Exponent, 0, State::Done} // Exponent }; - int state = State::Init; // parse state + int state = State::Init; // parse state QString buf; int index = 0; // start position QChar c = EatWhiteSpace(formula, index); - while ( true ) + while (true) { - const int input = CheckChar(c, locale, decimal, thousand);// input token + const int input = CheckChar(c, locale, decimal, thousand); // input token state = table[state][input]; @@ -246,7 +285,7 @@ auto ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QC if (locale != cLocale && (cDecimal != decimal || cThousand != thousand)) { if (decimal == cThousand) - {// Handle reverse to C locale case: thousand '.', decimal ',' + { // Handle reverse to C locale case: thousand '.', decimal ',' const QChar tmpThousand = QLatin1Char('@'); buf.replace(thousand, tmpThousand); buf.replace(decimal, cDecimal); @@ -279,21 +318,21 @@ auto ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QC } //--------------------------------------------------------------------------------------------------------------------- -auto NameRegExp() -> QString +auto NameRegExp(VariableRegex type) -> QString { static QString regex; if (regex.isEmpty()) { const QList allLocales = - QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); + QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); QString positiveSigns; QString negativeSigns; QString decimalPoints; QString groupSeparators; - for(const auto &locale : allLocales) + for (const auto &locale : allLocales) { if (not positiveSigns.contains(LocalePositiveSign(locale))) { @@ -319,15 +358,28 @@ auto NameRegExp() -> QString negativeSigns.replace('-', QLatin1String("\\-")); groupSeparators.remove('\''); - //Same regexp in pattern.xsd shema file. Don't forget to synchronize. - // \p{Nd} - \p{Decimal_Digit_Number} - // \p{Zs} - \p{Space_Separator} - // Here we use permanent start of string and end of string anchors \A and \z to match whole pattern as one - // string. In some cases, a user may pass multiline or line that ends with a new line. To cover case with a new - // line at the end of string use /z anchor. - regex = QString("\\A([^\\p{Nd}\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;'\"]){1,1}" - "([^\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;\"]){0,}\\z") - .arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators); + // Same regexp in pattern.xsd shema file. Don't forget to synchronize. + // \p{Nd} - \p{Decimal_Digit_Number} + // \p{Zs} - \p{Space_Separator} + // Here we use permanent start of string and end of string anchors \A and \z to match whole pattern as one + // string. In some cases, a user may pass multiline or line that ends with a new line. To cover case with a new + // line at the end of string use /z anchor. + + switch (type) + { + case VariableRegex::Variable: + regex = QString("\\A([^\\p{Nd}\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;'\"]){1,1}" + "([^\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;\"]){0,}\\z") + .arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators); + break; + case VariableRegex::KnownMeasurement: + regex = QString("\\A([^@\\p{Nd}\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;'\"]){1,1}" + "([^\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;\"]){0,}\\z") + .arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators); + break; + default: + break; + } } return regex; @@ -356,21 +408,11 @@ auto FindFirstNotOf(const QString &string, const QString &chars, qmusizetype pos auto SupportedLocale(const QLocale &locale) -> bool { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - return locale.positiveSign().size() == 1 && - locale.negativeSign().size() == 1 && - locale.toString(0).size() == 1 && - locale.toString(1).size() == 1 && - locale.toString(2).size() == 1 && - locale.toString(3).size() == 1 && - locale.toString(4).size() == 1 && - locale.toString(5).size() == 1 && - locale.toString(6).size() == 1 && - locale.toString(7).size() == 1 && - locale.toString(8).size() == 1 && - locale.toString(9).size() == 1 && - locale.exponential().size() == 1 && - locale.decimalPoint().size() == 1 && - locale.groupSeparator().size() == 1; + return locale.positiveSign().size() == 1 && locale.negativeSign().size() == 1 && locale.toString(0).size() == 1 && + locale.toString(1).size() == 1 && locale.toString(2).size() == 1 && locale.toString(3).size() == 1 && + locale.toString(4).size() == 1 && locale.toString(5).size() == 1 && locale.toString(6).size() == 1 && + locale.toString(7).size() == 1 && locale.toString(8).size() == 1 && locale.toString(9).size() == 1 && + locale.exponential().size() == 1 && locale.decimalPoint().size() == 1 && locale.groupSeparator().size() == 1; #else Q_UNUSED(locale) return true; diff --git a/src/libs/qmuparser/qmudef.h b/src/libs/qmuparser/qmudef.h index 85cd7576d..c8a773d02 100644 --- a/src/libs/qmuparser/qmudef.h +++ b/src/libs/qmuparser/qmudef.h @@ -92,7 +92,13 @@ QMUPARSERSHARED_EXPORT auto LocaleGroupSeparator(const QLocale &locale) -> QChar const QChar decimalPoint = LocaleDecimalPoint((locale)); \ const QChar groupSeparator = LocaleGroupSeparator((locale)); -QMUPARSERSHARED_EXPORT auto NameRegExp() -> QString; +enum class VariableRegex +{ + Variable, + KnownMeasurement +}; + +QMUPARSERSHARED_EXPORT auto NameRegExp(VariableRegex type = VariableRegex::Variable) -> QString; QT_WARNING_POP diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurements.cpp b/src/libs/vformat/knownmeasurements/vknownmeasurements.cpp index 02e136e2c..947dfb0df 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurements.cpp +++ b/src/libs/vformat/knownmeasurements/vknownmeasurements.cpp @@ -161,7 +161,7 @@ auto VKnownMeasurements::OrderedGroupMeasurments(const QString &group) const -> } //--------------------------------------------------------------------------------------------------------------------- -auto VKnownMeasurements::Images() const -> QHash +auto VKnownMeasurements::Images() const -> QMap { return d->m_images; } diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurements.h b/src/libs/vformat/knownmeasurements/vknownmeasurements.h index e0213e323..256f9b1ce 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurements.h +++ b/src/libs/vformat/knownmeasurements/vknownmeasurements.h @@ -67,7 +67,7 @@ public: auto Measurments() const -> QHash; auto OrderedMeasurments() const -> QMap; auto OrderedGroupMeasurments(const QString &group) const -> QMap; - auto Images() const -> QHash; + auto Images() const -> QMap; auto Groups() const -> QStringList; auto Measurement(const QString &name) const -> VKnownMeasurement; diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurements_p.h b/src/libs/vformat/knownmeasurements/vknownmeasurements_p.h index 35d384fb8..7feed3e86 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurements_p.h +++ b/src/libs/vformat/knownmeasurements/vknownmeasurements_p.h @@ -50,7 +50,7 @@ public: QString m_description{}; // NOLINT (misc-non-private-member-variables-in-classes) bool m_readOnly{false}; // NOLINT (misc-non-private-member-variables-in-classes) - QHash m_images{}; // NOLINT (misc-non-private-member-variables-in-classes) + QMap m_images{}; // NOLINT (misc-non-private-member-variables-in-classes) QHash m_measurements{}; // NOLINT (misc-non-private-member-variables-in-classes) private: diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurementsdatabase.cpp b/src/libs/vformat/knownmeasurements/vknownmeasurementsdatabase.cpp index d76f70604..7821182da 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurementsdatabase.cpp +++ b/src/libs/vformat/knownmeasurements/vknownmeasurementsdatabase.cpp @@ -82,6 +82,11 @@ auto VKnownMeasurementsDatabase::AllKnownMeasurements() const -> QHash VKnownMeasurements { + if (id.isNull()) + { + return {}; + } + if (m_measurementsCache.contains(id)) { return {*m_measurementsCache.object(id)}; diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.cpp b/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.cpp index aef213cec..080d5c0d8 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.cpp +++ b/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.cpp @@ -54,7 +54,7 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagKnownMeasurements, ("known-measureme Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagMeasurements, ("measurements"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagMeasurement, ("m"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagDiagrams, ("diagrams"_L1)) // NOLINT -Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagImage, ("tagImage"_L1)) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagImage, ("image"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagName, ("name"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagDescription, ("description"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, tagInfo, ("info"_L1)) // NOLINT @@ -169,8 +169,10 @@ void VKnownMeasurementsDocument::RemoveMeasurement(const QString &name) //--------------------------------------------------------------------------------------------------------------------- void VKnownMeasurementsDocument::RemoveImage(const QUuid &id) { - const QDomNodeList list = elementsByTagName(*tagMeasurements); + const QDomNodeList list = elementsByTagName(*tagDiagrams); list.at(0).removeChild(FindImage(id)); + + UpdateDiagramId(id, QUuid()); } //--------------------------------------------------------------------------------------------------------------------- @@ -250,7 +252,7 @@ void VKnownMeasurementsDocument::MoveBottom(const QString &name) } //--------------------------------------------------------------------------------------------------------------------- -QUuid VKnownMeasurementsDocument::GetUId() const +auto VKnownMeasurementsDocument::GetUId() const -> QUuid { QDomNode root = documentElement(); if (not root.isNull() && root.isElement()) @@ -258,7 +260,7 @@ QUuid VKnownMeasurementsDocument::GetUId() const const QDomElement rootElement = root.toElement(); if (not rootElement.isNull()) { - return QUuid(GetParametrEmptyString(rootElement, AttrKMVersion)); + return QUuid(GetParametrEmptyString(rootElement, *attrUId)); } } return {}; @@ -283,7 +285,7 @@ void VKnownMeasurementsDocument::SetUId(const QUuid &id) } //--------------------------------------------------------------------------------------------------------------------- -QString VKnownMeasurementsDocument::Name() const +auto VKnownMeasurementsDocument::Name() const -> QString { return UniqueTagText(*tagName, QString()); } @@ -295,7 +297,7 @@ void VKnownMeasurementsDocument::SetName(const QString &name) } //--------------------------------------------------------------------------------------------------------------------- -QString VKnownMeasurementsDocument::Description() const +auto VKnownMeasurementsDocument::Description() const -> QString { return UniqueTagText(*tagDescription, QString()); } @@ -307,7 +309,7 @@ void VKnownMeasurementsDocument::SetDescription(const QString &desc) } //--------------------------------------------------------------------------------------------------------------------- -bool VKnownMeasurementsDocument::IsReadOnly() const +auto VKnownMeasurementsDocument::IsReadOnly() const -> bool { QDomNode root = documentElement(); if (not root.isNull() && root.isElement()) @@ -487,7 +489,23 @@ void VKnownMeasurementsDocument::SetImageTitle(const QUuid &id, const QString &t } //--------------------------------------------------------------------------------------------------------------------- -QDomElement VKnownMeasurementsDocument::MakeEmptyMeasurement(const QString &name) +void VKnownMeasurementsDocument::SetImageId(const QUuid &id, const QUuid &newId) +{ + QDomElement node = FindImage(id); + if (not node.isNull()) + { + SetAttribute(node, *attrUId, newId); + + UpdateDiagramId(id, newId); + } + else + { + qWarning() << tr("Can't find image by id '%1'").arg(id.toString()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VKnownMeasurementsDocument::MakeEmptyMeasurement(const QString &name) -> QDomElement { QDomElement element = createElement(*tagMeasurement); SetAttribute(element, *attrName, name); @@ -524,7 +542,7 @@ auto VKnownMeasurementsDocument::FindM(const QString &name) const -> QDomElement } //--------------------------------------------------------------------------------------------------------------------- -QDomElement VKnownMeasurementsDocument::MakeEmptyImage(const VPatternImage &image) +auto VKnownMeasurementsDocument::MakeEmptyImage(const VPatternImage &image) -> QDomElement { QDomElement element = createElement(*tagImage); @@ -538,7 +556,7 @@ QDomElement VKnownMeasurementsDocument::MakeEmptyImage(const VPatternImage &imag } //--------------------------------------------------------------------------------------------------------------------- -QDomElement VKnownMeasurementsDocument::FindImage(const QUuid &id) const +auto VKnownMeasurementsDocument::FindImage(const QUuid &id) const -> QDomElement { if (id.isNull()) { @@ -615,3 +633,23 @@ void VKnownMeasurementsDocument::ReadMeasurements(VKnownMeasurements &known) con known.AddMeasurement(m); } } + +//--------------------------------------------------------------------------------------------------------------------- +void VKnownMeasurementsDocument::UpdateDiagramId(const QUuid &oldId, const QUuid &newId) +{ + QDomNodeList list = elementsByTagName(*tagMeasurement); + + for (int i = 0; i < list.size(); ++i) + { + QDomElement domElement = list.at(i).toElement(); + if (domElement.isNull()) + { + continue; + } + + if (QUuid(GetParametrEmptyString(domElement, *attrDiagram)) == oldId) + { + SetAttribute(domElement, *attrDiagram, newId); + } + } +} diff --git a/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.h b/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.h index 295530f37..48391dfda 100644 --- a/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.h +++ b/src/libs/vformat/knownmeasurements/vknownmeasurementsdocument.h @@ -36,6 +36,7 @@ class VKnownMeasurements; class VPatternImage; +class QUuid; class VKnownMeasurementsDocument : public VDomDocument { @@ -83,6 +84,7 @@ public: void SetImageContent(const QUuid &id, const VPatternImage &image); void SetImageTitle(const QUuid &id, const QString &text); + void SetImageId(const QUuid &id, const QUuid &newId); private: Q_DISABLE_COPY_MOVE(VKnownMeasurementsDocument) // NOLINT @@ -94,6 +96,8 @@ private: void ReadImages(VKnownMeasurements &known) const; void ReadMeasurements(VKnownMeasurements &known) const; + + void UpdateDiagramId(const QUuid &oldId, const QUuid &newId); }; #endif // VKNOWNMEASUREMENTSDOCUMENT_H diff --git a/src/libs/vlayout/dialogs/watermarkwindow.cpp b/src/libs/vlayout/dialogs/watermarkwindow.cpp index deca7b4ab..9ccfff7a3 100644 --- a/src/libs/vlayout/dialogs/watermarkwindow.cpp +++ b/src/libs/vlayout/dialogs/watermarkwindow.cpp @@ -587,7 +587,7 @@ auto WatermarkWindow::GetWatermarkFileName() -> QString QString shownName = tr("untitled.vwm"); if (not m_curFile.isEmpty()) { - shownName = StrippedName(m_curFile); + shownName = QFileInfo(m_curFile).fileName(); } shownName += "[*]"_L1; return shownName; diff --git a/src/libs/vmisc/def.cpp b/src/libs/vmisc/def.cpp index ec5585a4e..e298fae0e 100644 --- a/src/libs/vmisc/def.cpp +++ b/src/libs/vmisc/def.cpp @@ -172,17 +172,6 @@ auto SupportedLocales() -> QStringList "fi_FI", "en_US", "en_CA", "en_IN", "ro_RO", "zh_CN", "pt_BR", "el_GR", "pl_PL"}; } -//--------------------------------------------------------------------------------------------------------------------- -/** - * @brief strippedName the function call around curFile to exclude the path to the file. - * @param fullFileName full path to the file. - * @return file name. - */ -auto StrippedName(const QString &fullFileName) -> QString -{ - return QFileInfo(fullFileName).fileName(); -} - //--------------------------------------------------------------------------------------------------------------------- auto RelativeMPath(const QString &patternPath, const QString &absoluteMPath) -> QString { diff --git a/src/libs/vmisc/def.h b/src/libs/vmisc/def.h index c3d7b94b9..0157b16be 100644 --- a/src/libs/vmisc/def.h +++ b/src/libs/vmisc/def.h @@ -739,7 +739,6 @@ void InitLanguages(QComboBox *combobox); void InitPieceLabelLanguages(QComboBox *combobox); Q_REQUIRED_RESULT auto SupportedLocales() -> QStringList; -Q_REQUIRED_RESULT auto StrippedName(const QString &fullFileName) -> QString; Q_REQUIRED_RESULT auto RelativeMPath(const QString &patternPath, const QString &absoluteMPath) -> QString; Q_REQUIRED_RESULT auto AbsoluteMPath(const QString &patternPath, const QString &relativeMPath) -> QString; diff --git a/src/libs/vmisc/share/resources/icon.qrc b/src/libs/vmisc/share/resources/icon.qrc index 160d57d95..e3abb15e9 100644 --- a/src/libs/vmisc/share/resources/icon.qrc +++ b/src/libs/vmisc/share/resources/icon.qrc @@ -209,5 +209,9 @@ icon/dark/16x16/remove-image.png icon/dark/16x16/insert-image@2x.png icon/dark/16x16/insert-image.png + icon/light/16x16/viewimage@2x.png + icon/light/16x16/viewimage.png + icon/dark/16x16/viewimage.png + icon/dark/16x16/viewimage@2x.png diff --git a/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage.png b/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage.png new file mode 100644 index 000000000..03af62b84 Binary files /dev/null and b/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage.png differ diff --git a/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage@2x.png b/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage@2x.png new file mode 100644 index 000000000..d5bccfc0a Binary files /dev/null and b/src/libs/vmisc/share/resources/icon/dark/16x16/viewimage@2x.png differ diff --git a/src/libs/vmisc/share/resources/icon/light/16x16/viewimage.png b/src/libs/vmisc/share/resources/icon/light/16x16/viewimage.png new file mode 100644 index 000000000..33ab22e5c Binary files /dev/null and b/src/libs/vmisc/share/resources/icon/light/16x16/viewimage.png differ diff --git a/src/libs/vmisc/share/resources/icon/light/16x16/viewimage@2x.png b/src/libs/vmisc/share/resources/icon/light/16x16/viewimage@2x.png new file mode 100644 index 000000000..953a5d843 Binary files /dev/null and b/src/libs/vmisc/share/resources/icon/light/16x16/viewimage@2x.png differ diff --git a/src/libs/vmisc/share/resources/icon/svg/dark/viewimage.svg b/src/libs/vmisc/share/resources/icon/svg/dark/viewimage.svg new file mode 100644 index 000000000..fd8f1021b --- /dev/null +++ b/src/libs/vmisc/share/resources/icon/svg/dark/viewimage.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/src/libs/vmisc/share/resources/icon/svg/light/viewimage.svg b/src/libs/vmisc/share/resources/icon/svg/light/viewimage.svg new file mode 100644 index 000000000..d119bafa5 --- /dev/null +++ b/src/libs/vmisc/share/resources/icon/svg/light/viewimage.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/src/libs/vmisc/theme/vtheme.cpp b/src/libs/vmisc/theme/vtheme.cpp index f5a9ab1c8..b015bc54b 100644 --- a/src/libs/vmisc/theme/vtheme.cpp +++ b/src/libs/vmisc/theme/vtheme.cpp @@ -72,6 +72,12 @@ using namespace bpstd::literals::chrono_literals; #include "vapplicationstyle.h" #include "vscenestylesheet.h" +#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0) +#include "../vmisc/compatibility.h" +#endif + +using namespace Qt::Literals::StringLiterals; + namespace { #if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) @@ -282,7 +288,7 @@ void ActivateDefaultTheme() //--------------------------------------------------------------------------------------------------------------------- auto GetResourceName(const QString &root, const QString &iconName, bool dark) -> QString { - return QStringLiteral(":/%1/%2/%3").arg(root, dark ? "dark" : "light", iconName); + return QStringLiteral(":/%1/%2/%3").arg(root, dark ? "dark"_L1 : "light"_L1, iconName); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/libs/vmisc/vcommonsettings.cpp b/src/libs/vmisc/vcommonsettings.cpp index e6d1b4f07..83b691865 100644 --- a/src/libs/vmisc/vcommonsettings.cpp +++ b/src/libs/vmisc/vcommonsettings.cpp @@ -160,7 +160,9 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsPatternTranslateFormula, ("patt Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralRecentFileList, ("recentFileList"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralRestoreFileList, ("restoreFileList"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralGeometry, ("geometry"_L1)) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralKMGeometry, ("kmGeometry"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralToolbarsState, ("toolbarsState"_L1)) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingGeneralKMToolbarsState, ("kmToolbarsState"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingConfigurationThemeMode, ("configuration/themeMode"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPreferenceDialogSize, ("preferenceDialogSize"_L1)) // NOLINT // NOLINTNEXTLINE @@ -704,6 +706,18 @@ void VCommonSettings::SetGeometry(const QByteArray &value) setValue(*settingGeneralGeometry, value); } +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::GetKMGeometry() const -> QByteArray +{ + return value(*settingGeneralKMGeometry).toByteArray(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetKMGeometry(const QByteArray &value) +{ + setValue(*settingGeneralKMGeometry, value); +} + //--------------------------------------------------------------------------------------------------------------------- auto VCommonSettings::GetToolbarsState() const -> QByteArray { @@ -716,6 +730,18 @@ void VCommonSettings::SetToolbarsState(const QByteArray &value) setValue(*settingGeneralToolbarsState, value); } +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::GetKMToolbarsState() const -> QByteArray +{ + return value(*settingGeneralKMToolbarsState).toByteArray(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetKMToolbarsState(const QByteArray &value) +{ + setValue(*settingGeneralKMToolbarsState, value); +} + //--------------------------------------------------------------------------------------------------------------------- auto VCommonSettings::GetPreferenceDialogSize() const -> QSize { diff --git a/src/libs/vmisc/vcommonsettings.h b/src/libs/vmisc/vcommonsettings.h index cc9dc2188..41bb7e0dd 100644 --- a/src/libs/vmisc/vcommonsettings.h +++ b/src/libs/vmisc/vcommonsettings.h @@ -152,9 +152,15 @@ public: auto GetGeometry() const -> QByteArray; void SetGeometry(const QByteArray &value); + auto GetKMGeometry() const -> QByteArray; + void SetKMGeometry(const QByteArray &value); + auto GetToolbarsState() const -> QByteArray; void SetToolbarsState(const QByteArray &value); + auto GetKMToolbarsState() const -> QByteArray; + void SetKMToolbarsState(const QByteArray &value); + auto GetPreferenceDialogSize() const -> QSize; void SetPreferenceDialogSize(const QSize &sz); diff --git a/src/libs/vtools/dialogs/support/dialogeditwrongformula.cpp b/src/libs/vtools/dialogs/support/dialogeditwrongformula.cpp index d6dda845a..f12456029 100644 --- a/src/libs/vtools/dialogs/support/dialogeditwrongformula.cpp +++ b/src/libs/vtools/dialogs/support/dialogeditwrongformula.cpp @@ -203,7 +203,26 @@ void DialogEditWrongFormula::ValChanged(int row) if (ui->radioButtonStandardTable->isChecked()) { const QSharedPointer stable = m_data->GetVariable(name); - SetDescription(item->text(), *stable->GetValue(), stable->IsSpecialUnits(), stable->GetGuiText()); + + QString description; + + if (!stable->IsCustom()) + { + if (VKnownMeasurementsDatabase *db = VAbstractApplication::VApp()->KnownMeasurementsDatabase()) + { + VKnownMeasurements known = db->KnownMeasurements(stable->GetKnownMeasurementsId()); + if (known.IsValid()) + { + description = known.Measurement(stable->GetName()).description; + } + } + } + else + { + description = stable->GetDescription(); + } + + SetDescription(item->text(), *stable->GetValue(), stable->IsSpecialUnits(), description); } else if (ui->radioButtonIncrements->isChecked() || ui->radioButtonPC->isChecked()) { @@ -716,7 +735,7 @@ void DialogEditWrongFormula::ShowMeasurements(const QListKnownMeasurements(var->GetKnownMeasurementsId()); if (known.IsValid()) { - itemFullName->setText(known.Measurement(var->GetName()).description); + itemFullName->setText(known.Measurement(var->GetName()).fullName); } } } diff --git a/src/libs/vwidgets/vabstractmainwindow.cpp b/src/libs/vwidgets/vabstractmainwindow.cpp index 9a656b83a..517c432a3 100644 --- a/src/libs/vwidgets/vabstractmainwindow.cpp +++ b/src/libs/vwidgets/vabstractmainwindow.cpp @@ -242,7 +242,7 @@ void VAbstractMainWindow::UpdateRecentFileActions() for (int i = 0; i < numRecentFiles; ++i) { - QString recent = recentFiles.at(i); + const QString& recent = recentFiles.at(i); if (not recent.isEmpty()) { const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(recentFiles.at(i)); diff --git a/src/libs/vwidgets/vabstractmainwindow.h b/src/libs/vwidgets/vabstractmainwindow.h index 7e4ae2fd7..18c217467 100644 --- a/src/libs/vwidgets/vabstractmainwindow.h +++ b/src/libs/vwidgets/vabstractmainwindow.h @@ -57,9 +57,9 @@ public slots: virtual void UpdateVisibilityGroups(); virtual void UpdateDetailsList(); virtual void ZoomFitBestCurrent(); + void WindowsLocale(); protected slots: - void WindowsLocale(); void ExportDataToCSV(); protected: diff --git a/src/test/ValentinaTest/ValentinaTest.qbs b/src/test/ValentinaTest/ValentinaTest.qbs index 728cc818b..bf870f877 100644 --- a/src/test/ValentinaTest/ValentinaTest.qbs +++ b/src/test/ValentinaTest/ValentinaTest.qbs @@ -11,6 +11,7 @@ VTestApp { Depends { name: "VFormatLib" } Depends { name: "ebr" } Depends { name: "autotest" } + Depends { name: "QMUParserLib" } Depends { name: "Qt.xmlpatterns"