From a047a83c57400c8ac7cb4f49874b38292551dc33 Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 12:00:15 +0200 Subject: [PATCH 1/7] Migration to Qt6: WIP --- CMakeLists.txt | 18 +- editor.qrc | 2 - src/filereader.cpp | 4 +- src/igc.cpp | 30 +- src/igc.h | 7 +- src/kmljsonconvertor.cpp | 32 +- src/main.cpp | 41 +- src/qml/AboutDialog.qml | 6 +- src/qml/CircleParamDialog.qml | 4 +- src/qml/CloneDialog.qml | 4 +- src/qml/CupTextData.qml | 52 +-- src/qml/GFWDialog.qml | 301 ++++++++------- src/qml/LineParamDialog.qml | 6 +- src/qml/MainWindow.qml | 229 +++++------ src/qml/PinchMap.qml | 8 +- src/qml/PointsList.qml | 244 +++++++----- src/qml/PointsListEditableDelegate.qml | 43 ++- src/qml/PolygonList.qml | 209 +++++++--- src/qml/PolygonListDelegate.qml | 51 +-- src/qml/PropertiesDetail.qml | 76 ++-- src/qml/TrackStatistics.qml | 56 +-- src/qml/TracksList.qml | 504 +++++++++++++------------ src/qml/TracksListPolygonsDelegate.qml | 46 ++- src/qml/TracksListTableDelegate.qml | 205 +++++----- src/qml/components/NativeText.qml | 5 - src/qml/components/NativeTextInput.qml | 6 - src/qml/components/Ruler.qml | 2 +- src/qml/components/ShadowElement.qml | 18 +- src/qml/components/TextDialog.qml | 2 +- 29 files changed, 1198 insertions(+), 1013 deletions(-) delete mode 100644 src/qml/components/NativeText.qml delete mode 100644 src/qml/components/NativeTextInput.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 062af21..9667285 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.16) project(editor LANGUAGES CXX) @@ -8,7 +8,7 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -19,8 +19,7 @@ include(FeatureSummary) enable_testing() find_package(Git QUIET REQUIRED) -find_package(Qt5QuickTest REQUIRED) - +find_package(Qt6 COMPONENTS QuickTest REQUIRED) execute_process(COMMAND "${GIT_EXECUTABLE}" describe --dirty --broken --always --tags @@ -31,8 +30,7 @@ execute_process(COMMAND add_definitions( -DGIT_VERSION="${GIT_VERSION}" ) -find_package(Qt5 COMPONENTS Core Quick Xml LinguistTools REQUIRED) -find_package(Qt5QuickCompiler) +find_package(Qt6 COMPONENTS Core Quick Xml LinguistTools REQUIRED) SET(RESOURCES editor.qrc) @@ -72,12 +70,12 @@ set_target_properties(atest-editor PROPERTIES COMPILE_DEFINITIONS RUN_TESTS=1) -QT5_ADD_TRANSLATION(QM_FILES ${TS_FILES} +qt6_add_translation(QM_FILES ${TS_FILES} OPTIONS "-idbased") add_custom_target(translations DEPENDS ${QM_FILES}) add_dependencies(editor translations) -QT5_CREATE_TRANSLATION(QT_FILES "${CMAKE_SOURCE_DIR}" +qt6_create_translation(QT_FILES "${CMAKE_SOURCE_DIR}" OPTIONS "-no-obsolete") install(FILES ${QM_FILES} @@ -95,12 +93,12 @@ install(TARGETS editor RUNTIME target_compile_definitions(editor PRIVATE $<$,$>:QT_QML_DEBUG>) target_link_libraries(editor - PRIVATE Qt5::Core Qt5::Quick Qt5::Xml) + PRIVATE Qt6::Core Qt6::Quick Qt6::Xml) target_compile_definitions(atest-editor PRIVATE $<$,$>:QT_QML_DEBUG>) target_link_libraries(atest-editor - PRIVATE Qt5::Core Qt5::Quick Qt5::Xml Qt5::QuickTest) + PRIVATE Qt6::Core Qt6::Quick Qt6::Xml Qt6::QuickTest) diff --git a/editor.qrc b/editor.qrc index 0974176..5a6e780 100644 --- a/editor.qrc +++ b/editor.qrc @@ -18,8 +18,6 @@ src/qml/components/ShadowElement.qml src/qml/components/WorldFileImage.qml src/qml/components/Waypoint.qml - src/qml/components/NativeText.qml - src/qml/components/NativeTextInput.qml src/qml/components/TextFieldNumber.qml src/qml/components/Ruler.qml src/qml/GFWDialog.qml diff --git a/src/filereader.cpp b/src/filereader.cpp index ab21d8c..bfadbfe 100644 --- a/src/filereader.cpp +++ b/src/filereader.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include FileReader::FileReader(QObject* parent) @@ -80,7 +80,7 @@ void FileReader::writeUTF8(const QUrl& filename, QByteArray data) } QTextStream streamFileOut(&file); - streamFileOut.setCodec(QTextCodec::codecForName("UTF-8")); + streamFileOut.setEncoding(QStringConverter::Utf8); streamFileOut << QString::fromUtf8(data); streamFileOut.flush(); diff --git a/src/igc.cpp b/src/igc.cpp index caeee38..91f26a4 100644 --- a/src/igc.cpp +++ b/src/igc.cpp @@ -83,7 +83,7 @@ QVariant IgcFile::data(const QModelIndex& index, int role) const /// Open a file with given path and /// load it. -bool IgcFile::load(const QString& path, QTextCodec* codec) +bool IgcFile::load(const QString& path) { QUrl url(path); @@ -94,11 +94,11 @@ bool IgcFile::load(const QString& path, QTextCodec* codec) return false; } - return load(&f, codec); + return load(&f); } /// Load a file from opened QIODevice. -bool IgcFile::load(QIODevice* dev, QTextCodec* codec) +bool IgcFile::load(QIODevice* dev, QStringConverter::Encoding codec) { clear(); @@ -106,11 +106,7 @@ bool IgcFile::load(QIODevice* dev, QTextCodec* codec) file = dev; previousRecord = '\0'; - if (codec) { - activeCodec = codec; - } else { - activeCodec = QTextCodec::codecForName("Latin1"); - } + activeCodec = codec; if (!loadOneRecord()) { clear(); @@ -362,9 +358,9 @@ bool IgcFile::processRecordH() return false; } } else if (subtype == "CCL") { - competitionClass_ = activeCodec->toUnicode(value); + competitionClass_ = QStringDecoder(activeCodec).decode(value); } else if (subtype == "CID") { - competitionId_ = activeCodec->toUnicode(value); + competitionId_ = QStringDecoder(activeCodec).decode(value); } else if (subtype == "DTE") { bool ok; date_ = parseDate(data, &ok); @@ -379,19 +375,19 @@ bool IgcFile::processRecordH() } else if (subtype == "FTY") { QList list = value.split(','); if (list.size() == 1) { - frType_ = activeCodec->toUnicode(list[0]); + frType_ = QStringDecoder(activeCodec).decode(list[0]); } else { - manufacturer_ = activeCodec->toUnicode(list[0]); - frType_ = activeCodec->toUnicode(list[1]); + manufacturer_ = QStringDecoder(activeCodec).decode(list[0]); + frType_ = QStringDecoder(activeCodec).decode(list[1]); } } else if (subtype == "GID") { - gliderId_ = activeCodec->toUnicode(value); + gliderId_ = QStringDecoder(activeCodec).decode(value); } else if (subtype == "GPS") { - gps_ = activeCodec->toUnicode(value.split(',')[0]); + gps_ = QStringDecoder(activeCodec).decode(value.split(',')[0]); } else if (subtype == "GTY") { - gliderType_ = activeCodec->toUnicode(value); + gliderType_ = QStringDecoder(activeCodec).decode(value); } else if (subtype == "PLT") { - pilot_ = activeCodec->toUnicode(value); + pilot_ = QStringDecoder(activeCodec).decode(value); } return true; diff --git a/src/igc.h b/src/igc.h index 251b469..e1abe21 100644 --- a/src/igc.h +++ b/src/igc.h @@ -2,6 +2,7 @@ #define IGC__H #include +#include /// A single event from the igc file. /// the field type determines which subclass of Event @@ -125,8 +126,8 @@ class IgcFile : public QAbstractListModel { IgcFile(QObject* object = 0); ~IgcFile() { clear(); } - Q_INVOKABLE bool load(const QString& path, QTextCodec* codec = 0); - bool load(QIODevice* file, QTextCodec* codec = 0); + Q_INVOKABLE bool load(const QString& path); + bool load(QIODevice* file, QStringConverter::Encoding codec = QStringConverter::Latin1); Q_INVOKABLE void clear(); int getCount() { return rowCount(); } @@ -234,7 +235,7 @@ class IgcFile : public QAbstractListModel { char previousRecord; QIODevice* file; - QTextCodec* activeCodec; + QStringConverter::Encoding activeCodec; /// Data extracted from IGC headers. /// \{ diff --git a/src/kmljsonconvertor.cpp b/src/kmljsonconvertor.cpp index 7c68205..767f86c 100644 --- a/src/kmljsonconvertor.cpp +++ b/src/kmljsonconvertor.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "kmljsonconvertor.h" @@ -58,26 +59,28 @@ QString KmlJsonConvertor::kmlToJSONString_local(QString filename) // qDebug() << nodeName; int pos = 0; - QRegExp rx("([\\d+|\\.]*)\\,([\\d+|\\.]*)\\,?([\\d+|\\.]*)"); + QRegularExpression rx("([\\d+|\\.]*)\\,([\\d+|\\.]*)\\,?([\\d+|\\.]*)"); // longitude, latitude, altitude if (point.length() > 0) { - if ((pos = rx.indexIn(coordsString, pos)) != -1) { - // qDebug() << rx.cap(1) << rx.cap(2) << rx.cap(3); + QRegularExpressionMatch match = rx.match(coordsString, pos); + if (match.hasMatch()) { + // qDebug() << match.captured(1) << match.captured(2) << match.captured(3); QString str = QString("{\"lat\": %1,\"lon\":%2, \"alt\": %3, \"name\":\"%4\"},") - .arg(rx.cap(2).toFloat()) - .arg(rx.cap(1).toFloat()) - .arg(rx.cap(3).toFloat()) + .arg(match.captured(2).toFloat()) + .arg(match.captured(1).toFloat()) + .arg(match.captured(3).toFloat()) .arg(nodeName); points = points + str; } } else if (linearRing.length()) { QString str = ""; - while ((pos = rx.indexIn(coordsString, pos)) != -1) { - // qDebug() << rx.cap(1) << rx.cap(2) << rx.cap(3); - str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(rx.cap(2).toFloat()).arg(rx.cap(1).toFloat()); - pos += rx.matchedLength(); + QRegularExpressionMatch match; + while ((match = rx.match(coordsString, pos)).hasMatch()) { + // qDebug() << match.captured(1) << match.captured(2) << match.captured(3); + str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(match.captured(2).toFloat()).arg(match.captured(1).toFloat()); + pos = match.capturedEnd(); } str.remove(str.count() - 1, 1); poly = poly + QString("{\"name\": \"%1\", \"color\":\"%2\", " @@ -87,10 +90,11 @@ QString KmlJsonConvertor::kmlToJSONString_local(QString filename) .arg(str); } else if (lineString.length()) { QString str = ""; - while ((pos = rx.indexIn(coordsString, pos)) != -1) { - // qDebug() << rx.cap(1) << rx.cap(2) << rx.cap(3); - str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(rx.cap(2).toFloat()).arg(rx.cap(1).toFloat()); - pos += rx.matchedLength(); + QRegularExpressionMatch match; + while ((match = rx.match(coordsString, pos)).hasMatch()) { + // qDebug() << match.captured(1) << match.captured(2) << match.captured(3); + str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(match.captured(2).toFloat()).arg(match.captured(1).toFloat()); + pos = match.capturedEnd(); } str.remove(str.count() - 1, 1); poly = poly + QString("{\"name\": \"%1\", \"color\":\"%2\", \"points\":[%3]},").arg(nodeName).arg(color).arg(str); diff --git a/src/main.cpp b/src/main.cpp index 7e218a8..ffd4a8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,7 +59,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, .arg(context.line) .arg(context.function) .arg(msg); - std_out << txt << endl; + std_out << txt << Qt::endl; break; case QtWarningMsg: txt = QString("%1 [W]: %2:%3 @ %4(): %5") @@ -68,7 +68,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, .arg(context.line) .arg(context.function) .arg(msg); - std_out << txt << endl; + std_out << txt << Qt::endl; break; case QtCriticalMsg: txt = QString("%1 [C]: %2:%3 @ %4(): %5") @@ -77,7 +77,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, .arg(context.line) .arg(context.function) .arg(msg); - std_err << txt << endl; + std_err << txt << Qt::endl; break; case QtFatalMsg: txt = QString("%1 [F]: %2:%3 @ %4(): %5") @@ -86,7 +86,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, .arg(context.line) .arg(context.function) .arg(msg); - std_err << txt << endl; + std_err << txt << Qt::endl; abort(); default: txt = QString("%1 [O]: %2:%3 @ %4(): %5") @@ -95,10 +95,10 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, .arg(context.line) .arg(context.function) .arg(msg); - std_err << txt << endl; + std_err << txt << Qt::endl; break; } - ts << txt << endl; + ts << txt << Qt::endl; outFile.close(); } @@ -111,9 +111,9 @@ int main(int argc, char* argv[]) return quick_test_main(argc, argv, "atest-editor", QUICK_TEST_SOURCE_DIR); #else - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; + QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QGuiApplication::quit); app.setOrganizationName("Brno University of Technology"); app.setOrganizationDomain("fit.vutbr.cz"); @@ -142,16 +142,14 @@ int main(int argc, char* argv[]) QTranslator translator; QTranslator qtbasetranslator; - if (translator.load(QLocale(), QLatin1String("editor"), QLatin1String("_"), - QLatin1String("."))) { + + + if (translator.load(QLocale(), QLatin1String("editor"), QLatin1String("_"), QLatin1String("."))) { app.installTranslator(&translator); - engine.rootContext()->setContextProperty("localeBcp", - QLocale::system().bcp47Name()); - } else if (translator.load(QLocale(), QLatin1String("editor"), - QLatin1String("_"), - QString(QLibraryInfo::TranslationsPath))) { - qDebug() << QLibraryInfo::location(QLibraryInfo::TranslationsPath) - << QLocale::system().bcp47Name(); + engine.rootContext()->setContextProperty("localeBcp", QLocale::system().bcp47Name()); + } else if (translator.load(QLocale(), QLatin1String("editor"), QLatin1String("_"), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { + qDebug() << QLibraryInfo::path(QLibraryInfo::TranslationsPath) << QLocale::system().bcp47Name(); + app.installTranslator(&translator); engine.rootContext()->setContextProperty("localeBcp", QLocale::system().bcp47Name()); @@ -159,9 +157,7 @@ int main(int argc, char* argv[]) qDebug() << "translation.load() failed - falling back to English"; if (translator.load(QLatin1String("editor_en_US"), "./")) { app.installTranslator(&translator); - } else if (translator.load( - QLatin1String("editor_en_US"), - QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + } else if (translator.load(QLatin1String("editor_en_US"), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { app.installTranslator(&translator); } @@ -202,9 +198,10 @@ int main(int argc, char* argv[]) QObject* topLevel = engine.rootObjects().value(0); QQuickWindow* window = qobject_cast(topLevel); - - window->setIcon(QIcon(":/images/editor64.png")); - window->show(); + if (window) { + window->setIcon(QIcon(":/images/editor64.png")); + window->show(); + } return app.exec(); #endif } diff --git a/src/qml/AboutDialog.qml b/src/qml/AboutDialog.qml index 4f17886..171ed11 100644 --- a/src/qml/AboutDialog.qml +++ b/src/qml/AboutDialog.qml @@ -10,7 +10,7 @@ ApplicationWindow { modality: Qt.ApplicationModal - NativeText { + Text { id: titleLabel; font.pixelSize: 36; //% "LAA Trajectory Editor" @@ -56,7 +56,7 @@ ApplicationWindow { spacing: 20; anchors.margins: 10; - NativeText { + Text { //% "Build %1 %2 %3" text: qsTrId("about-build-date").arg(builddate).arg(buildtime).arg(version); anchors.left: parent.left @@ -65,7 +65,7 @@ ApplicationWindow { } - NativeText { + Text { id: aboutTextLabel anchors.left: parent.left diff --git a/src/qml/CircleParamDialog.qml b/src/qml/CircleParamDialog.qml index daf68ea..34ca57b 100644 --- a/src/qml/CircleParamDialog.qml +++ b/src/qml/CircleParamDialog.qml @@ -29,7 +29,7 @@ ApplicationWindow { spacing: 5 columns: 2; - NativeText { + Text { //% "Radius (m)" text: qsTrId("circle-param-dialog-radius") } @@ -38,7 +38,7 @@ ApplicationWindow { text: "5556" // 3 nautical miles == 5556 (standard ATZ size) } - NativeText { + Text { //% "Number of points" text: qsTrId("circle-param-dialog-points") } diff --git a/src/qml/CloneDialog.qml b/src/qml/CloneDialog.qml index 75af7f8..37bf8c8 100644 --- a/src/qml/CloneDialog.qml +++ b/src/qml/CloneDialog.qml @@ -62,7 +62,7 @@ ApplicationWindow { && sourceCategoriesSelection.isSelected(categories.index(index, 0)) color: selected ? "#0077cc" : ((index%2 === 0)? "#eee" : "#fff") - NativeText { + Text { text: model.name color: selected ? "#ffffff" : "#000000" anchors.fill: parent; @@ -102,7 +102,7 @@ ApplicationWindow { && destinationCategoriesSelection.isSelected(categories.index(index, 0)) color: selected ? "#0077cc" : ((index%2 === 0)? "#eee" : "#fff") - NativeText { + Text { text: model.name color: selected ? "#ffffff" : "#000000" anchors.fill: parent; diff --git a/src/qml/CupTextData.qml b/src/qml/CupTextData.qml index c2cc519..2d4d7b0 100644 --- a/src/qml/CupTextData.qml +++ b/src/qml/CupTextData.qml @@ -1,7 +1,8 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts -TabView { +ColumnLayout { id: tabView property variant tracksData; @@ -15,6 +16,7 @@ TabView { signal polyListItemSelected(int cid); property int selectedCategoryIndex: 0 + property alias currentIndex: tabBar.currentIndex property int pointPidSelectedFromMap; property variant newPointPosition; @@ -24,16 +26,31 @@ TabView { property real mapCenterLon property bool showTrackAlways; - Tab { - - id: pointsTab + TabBar { + id: tabBar + Layout.fillWidth: true + TabButton { + //% "Points" + text: qsTrId("cup-text-points-title") + } + TabButton { + //% "Polygons" + text: qsTrId("cup-text-polygons-title") + } + TabButton { + //% "Tracks" + text: qsTrId("cup-text-tracks-title") + } + } - //% "Points" - title: qsTrId("cup-text-points-title") + StackLayout { + id: stackLayout + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: tabBar.currentIndex PointsList { id: pointsList; - anchors.fill: parent; points: (tracksData !== undefined) ? tracksData.points : undefined; pointPidSelectedFromMap: tabView.pointPidSelectedFromMap newPointPosition: tabView.newPointPosition; @@ -67,16 +84,9 @@ TabView { } } - } - - Tab { - //% "Polygons" - title: qsTrId("cup-text-polygons-title") - PolygonList { id: polygonList; - anchors.fill: parent; polygons: (tracksData !== undefined) ? tracksData.poly : undefined; onNewPolygons: { @@ -124,13 +134,6 @@ TabView { } - } - - - Tab { - //% "Tracks" - title: qsTrId("cup-text-tracks-title") - TracksList { cupData: tracksData; onNewTracks: { @@ -147,8 +150,5 @@ TabView { computedData: tabView.computedData } - } - - } diff --git a/src/qml/GFWDialog.qml b/src/qml/GFWDialog.qml index 660f01e..609bbff 100644 --- a/src/qml/GFWDialog.qml +++ b/src/qml/GFWDialog.qml @@ -1,14 +1,13 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 -import "functions.js" as F -import "./components" - +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.platform as Platform ApplicationWindow { id: dialog; width: 640 - height: 300 + height: 450 + visible: false property alias wffiles: files signal accepted(variant list); @@ -18,34 +17,45 @@ ApplicationWindow { id: files; } + QtObject { + id: selectionData + property var items: [] + property int count: items.length + function clear() { items = []; itemsChanged(); } + function select(idx) { + var newItems = items.slice(); + if (newItems.indexOf(idx) === -1) { + newItems.push(idx); + items = newItems; + } + } + function deselect(start, end) { + var newItems = []; + for (var i = 0; i < items.length; i++) { + if (items[i] < start || items[i] > end) { + newItems.push(items[i]); + } + } + items = newItems; + } + function forEach(cb) { items.forEach(cb); } + function contains(idx) { return items.indexOf(idx) !== -1; } + signal selectionChanged() + onItemsChanged: selectionChanged() + } - // FileDialog { - // id: gfwFileDialog; - // nameFilters: [ - // //% "ESRI World file" - // qsTrId("gfw-dialog-browse-gfw-gfw")+" (*.gfw *.jgw *.tfw)", - // //% "All files" - // qsTrId("gfw-dialog-browse-gfw-all-files")+" (*)" - // ] - // onAccepted: { - // gfwTextField.text = fileUrl; - // } - // } - - FileDialog { + Platform.FileDialog { id: imageFileDialog; + fileMode: Platform.FileDialog.OpenFiles nameFilters: [ //% "Images" qsTrId("gfw-dialog-browse-image-images")+"(*.jpg *.png *.gif)", //% "All files" qsTrId("gfw-dialog-browse-image-all-files")+" (*)" ] - selectMultiple: true; - selectExisting: true; - selectFolder: false; onAccepted: { - for (var i = 0; i < fileUrls.length; ++i) { - var fn = fileUrls[i]; + for (var i = 0; i < files.length; ++i) { + var fn = files[i]; var ext_regexp = /\.[^/.]+$/; var match = String(fn).match(ext_regexp); var fw = fn.replace(ext_regexp, "") @@ -63,153 +73,154 @@ ApplicationWindow { default: console.log("unknown type \""+match+"\" filename \"" +fn + "\""); continue; - // break; } var file = {"image": fn, "gfw": fw} files.append(file) - - // console.log("pair " + fn + " " + fw) - - } - } - } - - - - TableView { - id: selectedFilesTable; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.margins: 10; - selectionMode: SelectionMode.ExtendedSelection; - - model: files; - TableViewColumn { - title: "image" - role: "image" - width: selectedFilesTable.width/2 - } - TableViewColumn { - title: "gfw" - role: "gfw" - width: selectedFilesTable.width/2 - } - } - - Row { - id: tableButtonsRow; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: selectedFilesTable.bottom; - anchors.margins: 10; - spacing: 10; - - Button { - //% "Add" - text: qsTrId("gfw-dialog-add") - onClicked: { - imageFileDialog.open() - } - } - - Button { - //% "Remove selected" - text: qsTrId("gfw-dialog-remove-selected") - onClicked: { - var removedCount = 0; - selectedFilesTable.selection.forEach( function(rowIndex) { - files.remove(rowIndex-removedCount, 1); - removedCount++; - }) - selectedFilesTable.selection.deselect(0, files.count-1) + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Rectangle { + Layout.fillWidth: true + height: 30 + color: "#eee" + RowLayout { + anchors.fill: parent + Text { text: "image"; Layout.preferredWidth: dialog.width/2 - 10 } + Text { text: "gfw"; Layout.fillWidth: true } } } + ListView { + id: selectedFilesTable + Layout.fillWidth: true + Layout.fillHeight: true + model: files + clip: true + + delegate: Rectangle { + width: selectedFilesTable.width + height: 30 + color: selectionData.contains(index) ? "#0077cc" : (index % 2 == 0 ? "#fff" : "#eee") + + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (mouse.modifiers & Qt.ControlModifier) { + if (selectionData.contains(index)) { + var newItems = selectionData.items.filter(i => i !== index); + selectionData.items = newItems; + } else { + selectionData.select(index); + } + } else { + selectionData.clear(); + selectionData.select(index); + } + } + } - Button { - //% "Remove all" - text: qsTrId("gfw-dialog-remove-all") - onClicked: { - files.clear(); + RowLayout { + anchors.fill: parent + Text { text: model.image; color: selectionData.contains(index) ? "white" : "black"; Layout.preferredWidth: dialog.width/2 - 10; elide: Text.ElideRight } + Text { text: model.gfw; color: selectionData.contains(index) ? "white" : "black"; Layout.fillWidth: true; elide: Text.ElideRight } + } } } - } - - - Grid { - id: gfwRow - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: tableButtonsRow.bottom; - anchors.margins: 10; - spacing: 10; - columns: 2 - + RowLayout { + id: tableButtonsRow + Layout.fillWidth: true + spacing: 10 + Button { + //% "Add" + text: qsTrId("gfw-dialog-add") + onClicked: { + imageFileDialog.open() + } + } - /// + Button { + //% "Remove selected" + text: qsTrId("gfw-dialog-remove-selected") + onClicked: { + var removedCount = 0; + selectionData.forEach( function(rowIndex) { + files.remove(rowIndex-removedCount, 1); + removedCount++; + }) + selectionData.clear() + } + } - NativeText { - //% "UTM Zone" - text: qsTrId("gfw-dialog-utm-zone"); + Button { + //% "Remove all" + text: qsTrId("gfw-dialog-remove-all") + onClicked: { + files.clear(); + } + } } - TextField { - id: utmZoneTextField - text: "33" - } + GridLayout { + id: gfwRow + Layout.fillWidth: true + columns: 2 + rowSpacing: 10 + columnSpacing: 10 + Text { + //% "UTM Zone" + text: qsTrId("gfw-dialog-utm-zone"); + } - NativeText { - //% "North hemisphere" - text: qsTrId("gfw-dialog-north-hemisphere") - } + TextField { + id: utmZoneTextField + text: "33" + } - CheckBox { - id: hemisphereCheckbox - checked: true; + Text { + //% "North hemisphere" + text: qsTrId("gfw-dialog-north-hemisphere") + } + CheckBox { + id: hemisphereCheckbox + checked: true; + } } - - } - - - Row { - id: dialogButtons - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.margins: 10; - spacing: 10; - Button { - //% "&Accept" - text: qsTrId("gfw-dialog-accept") - onClicked: { - var filesCopy = []; - for (var i = 0; i < files.count; i++) { - var item = files.get(i); - filesCopy.push({"image": item.image, "gfw": item.gfw, "utmZone": parseFloat(utmZoneTextField.text), "northHemisphere": hemisphereCheckbox.checked}); + RowLayout { + id: dialogButtons + Layout.alignment: Qt.AlignRight + spacing: 10 + Button { + //% "&Accept" + text: qsTrId("gfw-dialog-accept") + onClicked: { + var filesCopy = []; + for (var i = 0; i < files.count; i++) { + var item = files.get(i); + filesCopy.push({"image": item.image, "gfw": item.gfw, "utmZone": parseFloat(utmZoneTextField.text), "northHemisphere": hemisphereCheckbox.checked}); + } + accepted(filesCopy); + dialog.close(); } - accepted(filesCopy); - dialog.close(); } - } - Button { - //% "&Cancel" - text: qsTrId("gfw-dialog-cancel") - onClicked: { - canceled(); - dialog.close(); + Button { + //% "&Cancel" + text: qsTrId("gfw-dialog-cancel") + onClicked: { + canceled(); + dialog.close(); + } } - } } - - } diff --git a/src/qml/LineParamDialog.qml b/src/qml/LineParamDialog.qml index a72c1ea..37b6fc6 100644 --- a/src/qml/LineParamDialog.qml +++ b/src/qml/LineParamDialog.qml @@ -30,7 +30,7 @@ ApplicationWindow { spacing: 5 columns: 2; - NativeText { + Text { //% "Distance of next point (m)" text: qsTrId("line-param-dialog-distance") } @@ -39,7 +39,7 @@ ApplicationWindow { text: "1000" } - NativeText { + Text { //% "Angle (deg)" text: qsTrId("line-param-dialog-angle") } @@ -50,7 +50,7 @@ ApplicationWindow { - NativeText { + Text { //% "Number of points" text: qsTrId("line-param-dialog-points") } diff --git a/src/qml/MainWindow.qml b/src/qml/MainWindow.qml index 8b8f228..1e8f5ab 100644 --- a/src/qml/MainWindow.qml +++ b/src/qml/MainWindow.qml @@ -1,6 +1,7 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 +import QtQuick +import QtQuick.Controls +import Qt.labs.platform as Platform +import QtQuick.Dialogs import cz.mlich 1.0 import "parseCup.js" as Cup import "parser_fn.js" as Parser @@ -22,15 +23,21 @@ ApplicationWindow { property string tucekSettingsDIR: "results" property string tucekSettingsCSV: "tucek-settings.csv" - onClosing: { - close.accepted = false; - app_before_close(); + onClosing: (close) => { + if (document_changed) { + close.accepted = false; + app_before_close(); + } else { + config.set("recentFiles", recentlyOpenedFiles.jsonGet()) + close.accepted = true; + } } function app_before_close() { if (document_changed) { confirmUnsavedDialog.action = function() { + document_changed = false; config.set("recentFiles", recentlyOpenedFiles.jsonGet()) console.log("Quit") Qt.quit(); @@ -40,6 +47,7 @@ ApplicationWindow { return; } + document_changed = false; config.set("recentFiles", recentlyOpenedFiles.jsonGet()) console.log("Quit") Qt.quit(); @@ -50,7 +58,7 @@ ApplicationWindow { Menu { //% "&File" title: qsTrId("main-file-menu") - MenuItem { + Action { //% "&New" text: qsTrId("main-file-menu-new") onTriggered: { @@ -69,7 +77,7 @@ ApplicationWindow { } } - MenuItem { + Action { //% "&Load" text: qsTrId("main-file-menu-load") shortcut: StandardKey.Open; @@ -97,17 +105,17 @@ ApplicationWindow { Instantiator { model: recentlyOpenedFiles - delegate: MenuItem { - text: "&"+index + " " + model.fileUrl + delegate: Action { + text: "&"+index + " " + model.file onTriggered: { - console.log("Loading " + fileUrl) + console.log("Loading " + file) if (document_changed) { confirmUnsavedDialog.action = function() { - console.log("Loading " + fileUrl) + console.log("Loading " + file) document_changed = false; - opened_track_filename = fileUrl; - tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(fileUrl))) + opened_track_filename = file; + tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(file))) map.requestUpdate() } @@ -117,16 +125,16 @@ ApplicationWindow { } document_changed = false; - opened_track_filename = fileUrl; - tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(fileUrl))) + opened_track_filename = file; + tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(file))) map.requestUpdate() } } - onObjectAdded: recentFilesMenu.insertItem(index, object) - onObjectRemoved: recentFilesMenu.removeItem(object) + onObjectAdded: recentFilesMenu.insertAction(index, object) + onObjectRemoved: recentFilesMenu.removeAction(object) } @@ -135,7 +143,7 @@ ApplicationWindow { - MenuItem { + Action { //% "&Save" text: qsTrId("main-file-menu-save") shortcut: StandardKey.Save; @@ -155,7 +163,7 @@ ApplicationWindow { } - MenuItem { + Action { //% "Save &as..." text: qsTrId("main-file-menu-save-as") shortcut: StandardKey.SaveAs; @@ -165,7 +173,7 @@ ApplicationWindow { } } - MenuItem { + Action { //% "Load &GFW" text: qsTrId("main-file-menu-load-gfw"); onTriggered: { @@ -174,7 +182,7 @@ ApplicationWindow { } } - MenuItem { + Action { //% "&Import" text: qsTrId("main-file-menu-import") onTriggered: { @@ -183,7 +191,7 @@ ApplicationWindow { } } - MenuItem { + Action { //% "E&xport" text: qsTrId("main-file-menu-export") onTriggered: { @@ -193,7 +201,7 @@ ApplicationWindow { } } - MenuItem { + Action { //% "E&xit" text: qsTrId("main-file-menu-exit") onTriggered: { @@ -206,7 +214,7 @@ ApplicationWindow { Menu { //% "&Edit" title: qsTrId("main-edit-menu") - MenuItem { + Action { //% "Clone" text: qsTrId("main-menu-edit-clone") onTriggered: { @@ -214,7 +222,7 @@ ApplicationWindow { } shortcut: "Ctrl+C" } - MenuItem { + Action { //% "Zoom to points" text: qsTrId("main-menu-edit-zoom-to-points") onTriggered: { @@ -222,7 +230,7 @@ ApplicationWindow { } shortcut: "Ctrl+0" } - MenuItem { + Action { //% "Zoom in" text: qsTrId("main-menu-edit-zoom-in") shortcut: StandardKey.ZoomIn; @@ -230,7 +238,7 @@ ApplicationWindow { map.zoomIn(); } } - MenuItem { + Action { //% "Zoom out" text: qsTrId("main-menu-edit-zoom-out") shortcut: StandardKey.ZoomOut; @@ -239,7 +247,7 @@ ApplicationWindow { } } - MenuItem { + Action { id: main_menu_edit_show_track_always //% "Show track always" text: qsTrId("main-menu-edit-show-track-always"); @@ -248,7 +256,7 @@ ApplicationWindow { } - MenuItem { + Action { id: main_menu_edit_show_ruler //% "Ruler" text: qsTrId("main-menu-edit-ruler") @@ -259,7 +267,7 @@ ApplicationWindow { } } - MenuItem { + Action { id: main_menu_edit_autocenter_point //% "Automaticaly snap to center" text: qsTrId("main-menu-edit-autocenter") @@ -275,18 +283,18 @@ ApplicationWindow { Menu { //% "&Map" title: qsTrId("main-map-menu") - ExclusiveGroup { + ActionGroup { id: mapTypeExclusive } - ExclusiveGroup { + ActionGroup { id: mapTypeSecondaryExclusive } - MenuItem { + Action { //% "&None" text: qsTrId("main-map-menu-none") checkable: true; - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive onTriggered: { map.url = ""; map.url_subdomains = []; @@ -296,11 +304,11 @@ ApplicationWindow { shortcut: "Ctrl+1" } - MenuItem { + Action { //% "&Local" text: qsTrId("main-map-menu-local") checkable: true; - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive onTriggered: { setLocalPath(); } @@ -329,11 +337,11 @@ ApplicationWindow { } } - MenuItem { + Action { //% "&OSM Mapnik" text: qsTrId("main-map-menu-osm") checkable: true; - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive onTriggered: { map.url = "https://%(s)d.tile.openstreetmap.org/%(zoom)d/%(x)d/%(y)d.png"; map.url_subdomains = ['a','b', 'c']; @@ -346,11 +354,11 @@ ApplicationWindow { shortcut: "Ctrl+3" } - MenuItem { + Action { //% "Google &Roadmap" text: qsTrId("main-map-menu-google-roadmap") checkable: true; - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive onTriggered: { map.url = "https://%(s)d.google.com/vt/lyrs=m@248407269&hl=x-local&x=%(x)d&y=%(y)d&z=%(zoom)d&s=Galileo" map.url_subdomains = ['mt0','mt1','mt2','mt3'] @@ -360,11 +368,11 @@ ApplicationWindow { shortcut: "Ctrl+4" } - MenuItem { + Action { //% "Google &Terrain" text: qsTrId("main-map-menu-google-terrain") checkable: true; - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive onTriggered: { map.url = "https://%(s)d.google.com/vt/lyrs=t,r&x=%(x)d&y=%(y)d&z=%(zoom)d" map.url_subdomains = ['mt0','mt1','mt2','mt3'] @@ -374,10 +382,10 @@ ApplicationWindow { shortcut: "Ctrl+5" } - MenuItem { + Action { //% "Google &Satellite" text: qsTrId("main-map-menu-google-satellite") - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive checkable: true; onTriggered: { map.url = 'https://%(s)d.google.com/vt/lyrs=s&x=%(x)d&y=%(y)d&z=%(zoom)d'; @@ -388,10 +396,10 @@ ApplicationWindow { shortcut: "Ctrl+6" } - MenuItem { + Action { //% "Google &Hybrid" text: qsTrId("main-map-menu-google-hybrid-tile-layer") - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive checkable: true; onTriggered: { map.url = 'https://%(s)d.google.com/vt/lyrs=s,h&x=%(x)d&y=%(y)d&z=%(zoom)d'; @@ -402,10 +410,10 @@ ApplicationWindow { shortcut: "Ctrl+7" } - MenuItem { + Action { //% "Custom tile layer" text: qsTrId("main-map-menu-custom-tile-layer") - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive checkable: true; onTriggered: { mapurl_dialog.open(); @@ -416,14 +424,14 @@ ApplicationWindow { shortcut: "Ctrl+8" } - MenuItem { + Action { //% "Databáze letišť" text: qsTrId("main-map-menu-dl-map") - exclusiveGroup: mapTypeExclusive + ActionGroup.group: mapTypeExclusive checkable: true; property string homePath: QStandardPathsHomeLocation+"/Maps/DL/" property string binPath: QStandardPathsApplicationFilePath +"/../Maps/DL/" - visible: file_reader.is_dir_and_exists_local(binPath) || file_reader.is_dir_and_exists_local(homePath) + enabled: file_reader.is_dir_and_exists_local(binPath) || file_reader.is_dir_and_exists_local(homePath) onTriggered: { map.url_subdomains = []; @@ -447,10 +455,10 @@ ApplicationWindow { } - MenuItem { + Action { //% "Airspace Off" text: qsTrId("main-map-menu-airspace-off") - exclusiveGroup: mapTypeSecondaryExclusive + ActionGroup.group: mapTypeSecondaryExclusive checkable: true; checked: true; onTriggered: { @@ -460,10 +468,10 @@ ApplicationWindow { } } - MenuItem { + Action { //% "Airspace (skylines.aero)" text: qsTrId("main-map-menu-airspace-prosoar") - exclusiveGroup: mapTypeSecondaryExclusive + ActionGroup.group: mapTypeSecondaryExclusive checkable: true; onTriggered: { map.airspaceUrl = "https://skylines.aero/mapproxy/tiles/1.0.0/airspace+airports/EPSG3857/%(zoom)d/%(x)d/%(y)d.png" @@ -472,10 +480,10 @@ ApplicationWindow { } } - MenuItem { + Action { //% "Airspace (local)" text: qsTrId("main-map-menu-airspace-local") - exclusiveGroup: mapTypeSecondaryExclusive + ActionGroup.group: mapTypeSecondaryExclusive checkable: true; onTriggered: { setLocalPath(); @@ -504,7 +512,7 @@ ApplicationWindow { } - MenuItem { + Action { id: loadGfwMenuItem //% "Show &gfw image" text: qsTrId("main-map-menu-gfw") @@ -527,7 +535,7 @@ ApplicationWindow { Menu { //% "&Help" title: qsTrId("main-help-menu") - MenuItem { + Action { //% "&About" text: qsTrId("main-help-menu-about") onTriggered: { @@ -547,8 +555,7 @@ ApplicationWindow { Rectangle { clip:true; - height: parent.height - width: parent.width/2; + SplitView.preferredWidth: parent.width/2 PinchMap { id: map; @@ -577,7 +584,7 @@ ApplicationWindow { CupTextData { id: cupTextDataTabs; tracksData: tracks - width: parent.width/2; + SplitView.preferredWidth: parent.width/2 height: parent.height mapCenterLat: map.latitude mapCenterLon: map.longitude @@ -689,52 +696,53 @@ ApplicationWindow { } - MessageDialog { + Platform.MessageDialog { id: errorDialog; //% "Error" title: qsTrId("error-dialog") - icon: StandardIcon.Critical; + onAccepted: { Qt.quit(); } } - FileDialog { + Platform.FileDialog { id: loadFileDialog; nameFilters: [ "Laa Editor data file (*.json)" ] onAccepted: { document_changed = false; - opened_track_filename = fileUrl; - tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(fileUrl))) + opened_track_filename = file; + tracks = JSON.parse(file_reader.read(Qt.resolvedUrl(file))) map.requestUpdate() - recentlyOpenedFiles.tryAppend(String(fileUrl)) + recentlyOpenedFiles.tryAppend(String(file)) } } - FileDialog { + Platform.FileDialog { id: saveFileDialog; nameFilters: [ "Laa Editor data file (*.json)", "All files (*)" ] + defaultSuffix: "json" - selectExisting: false; + fileMode: Platform.FileDialog.SaveFile property var action; // function called onAccepted: { document_changed = false; - if (selectedNameFilterExtensions === "*.json") { - if (String(fileUrl).match(/\.json$/)) { - opened_track_filename = fileUrl; + if (currentFile.toString().endsWith(".json")) { + if (String(file).match(/\.json$/)) { + opened_track_filename = file; } else { - // FIXME: the overwrite is checked per fileUrl, but not fileUrl + suffix + // FIXME: the overwrite is checked per file, but not file + suffix console.log("warning overwrite is not checked") - opened_track_filename = fileUrl + ".json"; + opened_track_filename = file + ".json"; } } else { - opened_track_filename = fileUrl + opened_track_filename = file } @@ -757,29 +765,28 @@ ApplicationWindow { property var action; // function called on discard and after save (i.e. exit or new) - standardButtons: StandardButton.Save | StandardButton.Discard | StandardButton.Cancel; - onAccepted: { + buttons: MessageDialog.Save | MessageDialog.Discard | MessageDialog.Cancel; + onButtonClicked: (button, role) => { + if (button === MessageDialog.Save) { + if (opened_track_filename === "") { + saveFileDialog.action = action; + saveFileDialog.open() + return; + } + console.log("writting " + opened_track_filename) + file_reader.write(Qt.resolvedUrl(opened_track_filename), JSON.stringify(tracks)); + storeTrackSettings_with_dir_check(Qt.resolvedUrl(tucekSettingsCSV)); - if (opened_track_filename === "") { - saveFileDialog.action = action; - saveFileDialog.open() - return; + action(); + } else if (button === MessageDialog.Discard) { + action(); } - console.log("writting " + opened_track_filename) - file_reader.write(Qt.resolvedUrl(opened_track_filename), JSON.stringify(tracks)); - storeTrackSettings_with_dir_check(Qt.resolvedUrl(tucekSettingsCSV)); - - action(); - } - - onDiscard: { - action(); } } - FileDialog { + Platform.FileDialog { id: importFileDialog nameFilters: [ "Any supported format (*.cup *.gpx *.kml *.igc)", @@ -791,25 +798,25 @@ ApplicationWindow { ] onAccepted: { - var str = String(fileUrl); + var str = String(file); if (str.match(/\.cup$/i)) { - importCup(fileUrl); + importCup(file); } else if (str.match(/\.kml$/i)) { - importKml(fileUrl) + importKml(file) } else if (str.match(/\.gpx$/i)) { - importGpx(fileUrl) + importGpx(file) } else if (str.match(/\.igc$/i)) { - importIgc(fileUrl) + importIgc(file) } else { - console.error("unsupported file format (please rename file): " + fileUrl) + console.error("unsupported file format (please rename file): " + file) } } } - FileDialog { + Platform.FileDialog { id: exportFileDialog; - selectExisting: false; + fileMode: Platform.FileDialog.SaveFile nameFilters: [ "Keyhole Markup Language (*.kml)", @@ -819,22 +826,22 @@ ApplicationWindow { "HTML Table (*.html)", ] onAccepted: { - console.log("Export: " + exportFileDialog.selectedNameFilterIndex + " " + exportFileDialog.selectedNameFilter + " " + fileUrl) + console.log("Export: " + exportFileDialog.selectedNameFilterIndex + " " + exportFileDialog.selectedNameFilter + " " + file) switch(exportFileDialog.selectedNameFilterIndex) { case 0: - exportKml(fileUrl); + exportKml(file); break; case 1: - exportGpx(fileUrl); + exportGpx(file); break; case 2: - exportGpxRoute(fileUrl); + exportGpxRoute(file); break; case 3: - exportCup(fileUrl); + exportCup(file); break; case 4: - exportHtmlTable(fileUrl); + exportHtmlTable(file); break; default: console.error("unsupported file format (please add file extension)" + exportFileDialog.selectedNameFilter) @@ -867,7 +874,7 @@ ApplicationWindow { function tryAppend(url) { for (var i = 0; i < count; i++) { var item = get(i); - if (item.fileUrl === url) { + if (item.file === url) { remove(i); } } @@ -875,7 +882,7 @@ ApplicationWindow { remove(0); } - recentlyOpenedFiles.append({"fileUrl": url}) + recentlyOpenedFiles.append({"file": url}) } function jsonGet() { @@ -883,7 +890,7 @@ ApplicationWindow { for (var i = 0; i < count; i++) { var item = get(i); - arr.push(item.fileUrl) + arr.push(item.file) } return JSON.stringify(arr); @@ -895,7 +902,7 @@ ApplicationWindow { for (var i = 0; i < data.length; i++) { var fn = data[i]; if (file_reader.file_exists(fn)) { - recentlyOpenedFiles.append({"fileUrl": fn}) + recentlyOpenedFiles.append({"file": fn}) } } } diff --git a/src/qml/PinchMap.qml b/src/qml/PinchMap.qml index 5b9d22b..ccfad2e 100644 --- a/src/qml/PinchMap.qml +++ b/src/qml/PinchMap.qml @@ -538,7 +538,7 @@ Rectangle { // console.log("cache miss ("+imageCache.length+"): " + imageUrl ) var newImage = Qt.createQmlObject( - 'import QtQuick 2.15; + 'import QtQuick; Image { property var lastHit: new Date(); property string cacheUrl: "'+imageUrl+'"; @@ -663,7 +663,7 @@ Rectangle { } visible: mapTileVisible && (img.status !== Image.Ready) } - NativeText { + Text { anchors.left: parent.left anchors.leftMargin: 16 y: parent.height/2 - 32 @@ -1605,7 +1605,7 @@ Rectangle { width: scaleBarLength[0] } - NativeText { + Text { text: G.formatDistance(scaleBarLength[1], {'distanceUnit':'m'}) anchors.horizontalCenter: scaleBar.horizontalCenter anchors.top: scaleBar.bottom @@ -1908,7 +1908,7 @@ Rectangle { id: filereader } - NativeText { + Text { id: attributionText anchors.bottom: parent.bottom anchors.left: parent.left diff --git a/src/qml/PointsList.qml b/src/qml/PointsList.qml index 87342b6..515d3d0 100644 --- a/src/qml/PointsList.qml +++ b/src/qml/PointsList.qml @@ -1,8 +1,9 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "geom.js" as G -TableView { +Item { id: tableView property variant points @@ -17,27 +18,43 @@ TableView { property bool enableSnap: false; property variant lateSelect; - model: pModel; - selectionMode: SelectionMode.ExtendedSelection - - onRowCountChanged: { - if (lateSelect !== undefined) { - tableView.selection.clear(); - tableView.selection.select(lateSelect) - lateSelect = undefined; + property int currentRow: listView.currentIndex + + QtObject { + id: selectionData + property var items: [] + property int count: items.length + function clear() { items = []; itemsChanged(); } + function select(idx) { + var newItems = items.slice(); + if (newItems.indexOf(idx) === -1) { + newItems.push(idx); + items = newItems; + } } - + function deselect(start, end) { + var newItems = []; + for (var i = 0; i < items.length; i++) { + if (items[i] < start || items[i] > end) { + newItems.push(items[i]); + } + } + items = newItems; + } + function forEach(cb) { items.forEach(cb); } + function contains(idx) { return items.indexOf(idx) !== -1; } + signal selectionChanged() + onItemsChanged: selectionChanged() } - + property alias selection: selectionData onPointPidSelectedFromMapChanged: { for (var i = 0; i < pModel.count; i++) { - var item = pModel.get(i); if (item.pid === pointPidSelectedFromMap) { - tableView.selection.clear(); - tableView.selection.select(i); - currentRow =i; + selection.clear(); + selection.select(i); + listView.currentIndex = i; } } } @@ -56,7 +73,7 @@ TableView { if (item.pid === pid) { pModel.setProperty(i, "lat", newPointPosition.lat) pModel.setProperty(i, "lon", newPointPosition.lon) - currentRow = i; + listView.currentIndex = i; return; } } @@ -69,10 +86,9 @@ TableView { function pointlistSelectionChanged() { if (selection.count === 1) { selection.forEach( function(rowIndex) { - pointSelected(model.get(rowIndex).pid); + pointSelected(pModel.get(rowIndex).pid); }); } - } onPointsChanged: { @@ -90,26 +106,6 @@ TableView { } } - - - - itemDelegate: PointsListEditableDelegate { - onChangeModel: { - tableView.model.setProperty(row, role, value); - - tableView.selection.deselect(0, pModel.count-1); - tableView.selection.select(row) - tableView.currentRow = row; - - pointSelected(pModel.get(row).pid); - - } - - onReverseGeocoding: { - tableView.startReverseGeocoding(row) - } - } - function startReverseGeocoding(row) { var item = pModel.get(row); @@ -135,9 +131,9 @@ TableView { } else if (response.address.village !== undefined) { pModel.setProperty(row, "name", response.address.village); } - tableView.selection.deselect(0, pModel.count-1); - tableView.selection.select(row) - tableView.currentRow = row; + selection.deselect(0, pModel.count-1); + selection.select(row) + listView.currentIndex = row; pointSelected(pModel.get(row).pid); } catch (e) { @@ -150,7 +146,6 @@ TableView { http.send() } - MouseArea { acceptedButtons: Qt.RightButton anchors.fill: parent @@ -158,7 +153,6 @@ TableView { onClicked: { contextMenu.popup(); } - } function addPointToList(name = 'point', lat, lon) { @@ -176,31 +170,31 @@ TableView { } pModel.append(item) - } - Menu { id: contextMenu; - MenuItem { + Action { //% "Add point" text: qsTrId("points-list-add-point") onTriggered: { - //% "Turn point" var name = qsTrId("points-list-default-name"); addPointToList(name, mapCenterLat, mapCenterLon) var current = pModel.count-1; pModel.pointsChanged() - lateSelect = current; // workarround for https://bugreports.qt.io/browse/QTBUG-53027 - + + selection.clear(); + selection.select(current); + listView.currentIndex = current; } } MenuItem { //% "Add circle" text: qsTrId("points-list-add-circle") visible: (tableView.currentRow !== -1) + height: visible ? implicitHeight : 0 onTriggered: circleParamDialog.show(); } @@ -208,22 +202,23 @@ TableView { //% "Add points (in line)" text: qsTrId("points-list-add-line") visible: (tableView.currentRow !== -1) + height: visible ? implicitHeight : 0 onTriggered: lineParamDialog.show(); } - - MenuItem { + Action { //% "Remove points" text: qsTrId("points-list-remove-points") enabled: (tableView.currentRow !== -1) onTriggered: { var removedCount = 0; - tableView.selection.forEach(function(rowIndex) { + var sortedItems = selection.items.slice().sort(function(a, b){return a - b}); + sortedItems.forEach(function(rowIndex) { var removeIndex = rowIndex - removedCount; pModel.remove(removeIndex, 1); removedCount++ }) - tableView.selection.clear(); + selection.clear(); pModel.pointsChanged(); @@ -233,10 +228,11 @@ TableView { MenuItem { //% "Snap to.." text: qsTrId("points-list-snap-to") - enabled: (tableView.selection.count === 1) + enabled: (selection.count === 1) visible: enableSnap + height: visible ? implicitHeight : 0 onTriggered: { - tableView.selection.forEach(function(rowIndex) { + selection.forEach(function(rowIndex) { var item = pModel.get(rowIndex) snapToSth(item.pid) console.log("Snap to: (" +item.pid + ") " + item.name) @@ -247,10 +243,12 @@ TableView { MenuItem { //% "Transform to polygon" text: qsTrId("points-list-transform-to-polygon") - visible: (tableView.selection.count > 1) + visible: (selection.count > 1) + height: visible ? implicitHeight : 0 + onTriggered: { var newPointsArr = [] - tableView.selection.forEach(function(rowIndex) { + selection.forEach(function(rowIndex) { var item = pModel.get(rowIndex) newPointsArr.push({"lat": item.lat, "lon": item.lon}) }) @@ -261,34 +259,29 @@ TableView { "points": newPointsArr, "closed": false, } - - newPolygon(newPolyData) } - } MenuItem { //% "Retrieve local name" text: qsTrId("points-list-reverse-geocoding") - visible: (tableView.selection.count > 0) && (tableView.selection.count <= 3) + visible: (selection.count > 0) && (selection.count <= 3) + height: visible ? implicitHeight : 0 onTriggered: { - tableView.selection.forEach(function(rowIndex) { + selection.forEach(function(rowIndex) { tableView.startReverseGeocoding(rowIndex); }) } } - } - ListModel { id: pModel; onDataChanged: { pointsChanged(); } - function pointsChanged() { var new_arr = []; for (var i = 0; i < count; i++) { @@ -303,40 +296,96 @@ TableView { } newPoints(new_arr); } - } + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + Layout.fillWidth: true + height: 30 + color: "#eee" + RowLayout { + anchors.fill: parent + Text { text: qsTrId("points-list-id"); Layout.preferredWidth: 50 } + Text { text: qsTrId("points-list-name"); Layout.preferredWidth: 200 } + Text { text: qsTrId("points-list-lat"); Layout.preferredWidth: 150 } + Text { text: qsTrId("points-list-lon"); Layout.fillWidth: true } + } + } - TableViewColumn { - role: "pid" - //% "Id" - title: qsTrId("points-list-id"); - width: 50; - } - - TableViewColumn { - role: "name" - //% "Name" - title: qsTrId("points-list-name"); - width: 200; - } + ListView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: true + model: pModel + clip: true + + onCountChanged: { + if (lateSelect !== undefined) { + selection.clear(); + selection.select(lateSelect) + listView.currentIndex = lateSelect; + lateSelect = undefined; + } + } - TableViewColumn { - role: "lat" - //% "Latitude" - title: qsTrId("points-list-lat"); - width: 150; - } + delegate: Rectangle { + width: listView.width + height: 30 + color: selection.contains(index) ? "#0077cc" : (index % 2 == 0 ? "#fff" : "#f5f5f5") + + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (mouse.modifiers & Qt.ControlModifier) { + if (selection.contains(index)) { + var newItems = selection.items.filter(i => i !== index); + selection.items = newItems; + } else { + selection.select(index); + } + } else { + selection.clear(); + selection.select(index); + listView.currentIndex = index; + } + pointSelected(model.pid); + } + } - TableViewColumn { - role: "lon" - //% "Longitude" - title: qsTrId("points-list-lon"); - width: 150; + RowLayout { + anchors.fill: parent + PointsListEditableDelegate { + Layout.preferredWidth: 50 + role: "pid"; value: model.pid; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { pModel.setProperty(row, role, value); } + } + PointsListEditableDelegate { + Layout.preferredWidth: 200 + role: "name"; value: model.name; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { pModel.setProperty(row, role, value); } + } + PointsListEditableDelegate { + Layout.preferredWidth: 150 + role: "lat"; value: model.lat; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { pModel.setProperty(row, role, value); } + } + PointsListEditableDelegate { + Layout.fillWidth: true + role: "lon"; value: model.lon; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { pModel.setProperty(row, role, value); } + } + } + } + } } - - CircleParamDialog { id: circleParamDialog onAccepted: { @@ -347,13 +396,11 @@ TableView { var list = G.insertMidArcByAngle(selectedPoint.lat, selectedPoint.lon, 0, Math.PI*2, true, G.distToAngle(radius_num), (Math.PI*2)/(points_num+0.01)); for (var i = 0; i < list.length; i++) { - //% "Circle point %n" var name = selectedPoint.name + ": " + qsTrId("points-list-circle-point-name", i+1) var coord = list[i]; addPointToList(name, coord[0], coord[1]) } pModel.pointsChanged() - } } @@ -368,17 +415,12 @@ TableView { console.log("Add points for line: " + distance_num + " " + angle_num + " " + points_num) for (var i = 0; i < points_num; i++) { - //% "Line point %n" var name = selectedPoint.name + ": " + qsTrId("points-list-line-point-name", i+1) distance_sum = distance_sum + distance_num; var coord = G.getCoordByDistanceBearing(selectedPoint.lat, selectedPoint.lon, angle_num, distance_sum) addPointToList(name, coord.lat, coord.lon) } pModel.pointsChanged() - } - } - - } diff --git a/src/qml/PointsListEditableDelegate.qml b/src/qml/PointsListEditableDelegate.qml index adf820a..f614351 100644 --- a/src/qml/PointsListEditableDelegate.qml +++ b/src/qml/PointsListEditableDelegate.qml @@ -1,5 +1,5 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls import "geom.js" as G import "./components" @@ -9,16 +9,23 @@ Item { id: editableDelegate signal changeModel(int row, string role, variant value); signal reverseGeocoding(int row); + + property int row + property string role + property variant value + property bool selected: false + property color textColor: selected ? "white" : "black" + property int elideMode: Text.ElideRight - NativeText { + Text { width: parent.width anchors.margins: 4 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - elide: styleData.elideMode - text: getStyledData(styleData.value, styleData.role) // zobrazi se ruzne podle role - color: styleData.textColor - visible: !styleData.selected + elide: editableDelegate.elideMode + text: getStyledData(editableDelegate.value, editableDelegate.role) // zobrazi se ruzne podle role + color: editableDelegate.textColor + visible: !editableDelegate.selected } Loader { // Initialize text editor lazily to improve performance id: loaderEditor @@ -33,40 +40,40 @@ Item { // function onAccepted() { onAccepted: { - switch (styleData.role) { + switch (editableDelegate.role) { case "name": // default if (loaderEditor.item.text === "") { - reverseGeocoding(styleData.row); + reverseGeocoding(editableDelegate.row); //% "Turn point" - changeModel(styleData.row, styleData.role, qsTrId("points-list-default-name")) + changeModel(editableDelegate.row, editableDelegate.role, qsTrId("points-list-default-name")) } else { - changeModel(styleData.row, styleData.role, loaderEditor.item.text) + changeModel(editableDelegate.row, editableDelegate.role, loaderEditor.item.text) } break; case "pid": console.log("Cannot change point id"); // neni mozne prepsat pid break; case "lat": - changeModel(styleData.row, styleData.role, G.DMStoFloat(loaderEditor.item.text)) + changeModel(editableDelegate.row, editableDelegate.role, G.DMStoFloat(loaderEditor.item.text)) break; case "lon": - changeModel(styleData.row, styleData.role, G.DMStoFloat(loaderEditor.item.text)) + changeModel(editableDelegate.row, editableDelegate.role, G.DMStoFloat(loaderEditor.item.text)) break; default: - changeModel(styleData.row, styleData.role, loaderEditor.item.text) + changeModel(editableDelegate.row, editableDelegate.role, loaderEditor.item.text) break; } } } - sourceComponent: styleData.selected ? editor : null + sourceComponent: editableDelegate.selected ? editor : null Component { id: editor - NativeTextInput { + TextInput { id: textinput - color: styleData.textColor - text: getStyledData(styleData.value, styleData.role) + color: editableDelegate.textColor + text: getStyledData(editableDelegate.value, editableDelegate.role) MouseArea { id: mouseArea diff --git a/src/qml/PolygonList.qml b/src/qml/PolygonList.qml index 98c43f3..cb30a21 100644 --- a/src/qml/PolygonList.qml +++ b/src/qml/PolygonList.qml @@ -1,18 +1,45 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs -TableView { +Item { id: tableView property variant polygons signal polygonSelected(int cid); - model: pModel - signal newPolygons(variant p); signal polygonToPoints(int cid); + QtObject { + id: selectionData + property var items: [] + property int count: items.length + function clear() { items = []; itemsChanged(); } + function select(idx) { + var newItems = items.slice(); + if (newItems.indexOf(idx) === -1) { + newItems.push(idx); + items = newItems; + } + } + function deselect(start, end) { + var newItems = []; + for (var i = 0; i < items.length; i++) { + if (items[i] < start || items[i] > end) { + newItems.push(items[i]); + } + } + items = newItems; + } + function forEach(cb) { items.forEach(cb); } + function contains(idx) { return items.indexOf(idx) !== -1; } + signal selectionChanged() + onItemsChanged: selectionChanged() + } + property alias selection: selectionData + onPolygonsChanged: { if (polygons === undefined) { return; @@ -29,14 +56,12 @@ TableView { "closed": p.closed, }); } - - } function polygonSelectionChanged() { if (selection.count === 1) { selection.forEach( function(rowIndex) { - var item = model.get(rowIndex); + var item = pModel.get(rowIndex); polygonSelected(item.cid) }) } @@ -47,7 +72,10 @@ TableView { property int returnRow; onAccepted: { - var col = String(colorDialog.currentColor).substring(1); + var col = String(colorDialog.color).substring(1); // Qt6 color returns #AARRGGBB or #RRGGBB + if (col.length === 8 && col.startsWith("FF")) { + col = col.substring(2); + } pModel.setProperty(returnRow, "color", col) tableView.selection.deselect(0, pModel.count-1); @@ -88,13 +116,12 @@ TableView { onClicked: { contextMenu.popup(); } - } Menu { id: contextMenu - MenuItem { + Action { //% "Transform to points" text: qsTrId("polygon-list-polygon-to-points") enabled: (tableView.selection.count > 0) @@ -107,7 +134,7 @@ TableView { } } - MenuItem { + Action { //% "Remove polygon" text: qsTrId("polygon-list-remove-polygon") enabled: (tableView.selection.count > 0) @@ -125,59 +152,117 @@ TableView { } - itemDelegate: PolygonListDelegate { - onChangeModel: { - var tmpValue = value; - if (role == "closed") { - tmpValue = (value === "true") + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + Layout.fillWidth: true + height: 30 + color: "#eee" + RowLayout { + anchors.fill: parent + Text { text: qsTrId("polygon-list-id"); Layout.preferredWidth: 50 } + Text { text: qsTrId("polygon-list-name"); Layout.preferredWidth: 250 } + Text { text: qsTrId("polygon-list-color"); Layout.preferredWidth: 70 } + Text { text: qsTrId("polygon-points-count"); Layout.preferredWidth: 50 } + Text { text: qsTrId("polygon-closed"); Layout.fillWidth: true } } - console.log(row + " " + role + " " + tmpValue) - pModel.setProperty(row, role, tmpValue); - tableView.selection.deselect(0, pModel.count-1); - tableView.selection.select(row) - } - onOpenColorDialog: { - colorDialog.returnRow = row; - colorDialog.color = "#" + prevValue - colorDialog.open(); + ListView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: true + model: pModel + clip: true + + delegate: Rectangle { + width: listView.width + height: 30 + color: selection.contains(index) ? "#0077cc" : (index % 2 == 0 ? "#fff" : "#f5f5f5") + + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (mouse.modifiers & Qt.ControlModifier) { + if (selection.contains(index)) { + var newItems = selection.items.filter(i => i !== index); + selection.items = newItems; + } else { + selection.select(index); + } + } else { + selection.clear(); + selection.select(index); + listView.currentIndex = index; + } + } + } + + RowLayout { + anchors.fill: parent + PolygonListDelegate { + Layout.preferredWidth: 50 + role: "cid"; value: model.cid; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { + var tmpValue = value; + if (role == "closed") { + tmpValue = (value === "true") + } + pModel.setProperty(row, role, tmpValue); + tableView.selection.deselect(0, pModel.count-1); + tableView.selection.select(row) + } + } + PolygonListDelegate { + Layout.preferredWidth: 250 + role: "name"; value: model.name; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { + pModel.setProperty(row, role, value); + tableView.selection.deselect(0, pModel.count-1); + tableView.selection.select(row) + } + } + PolygonListDelegate { + Layout.preferredWidth: 70 + role: "color"; value: model.color; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { + pModel.setProperty(row, role, value); + tableView.selection.deselect(0, pModel.count-1); + tableView.selection.select(row) + } + onOpenColorDialog: (row, prevValue) => { + colorDialog.returnRow = row; + colorDialog.selectedColor = "#" + prevValue + colorDialog.open(); + } + } + PolygonListDelegate { + Layout.preferredWidth: 50 + role: "point_count"; value: model.point_count; row: index + selected: selection.contains(index) + } + PolygonListDelegate { + Layout.fillWidth: true + role: "closed"; value: model.closed; row: index + selected: selection.contains(index) + onChangeModel: (row, role, value) => { + var tmpValue = value; + if (role == "closed") { + tmpValue = (value === "true" || value === true) + } + pModel.setProperty(row, role, tmpValue); + tableView.selection.deselect(0, pModel.count-1); + tableView.selection.select(row) + } + } + } + } } - - } - - TableViewColumn { - //% "Id" - title: qsTrId("polygon-list-id") - role: "cid" - width: 50 - } - - TableViewColumn { - //% "Name" - title: qsTrId("polygon-list-name"); - role: "name" - width: 250; - } - - TableViewColumn { - //% "Color" - title: qsTrId("polygon-list-color"); - role: "color"; - width: 70; - } - - TableViewColumn { - //% "Points" - title: qsTrId("polygon-points-count"); - role: "point_count"; - width: 50; - } - TableViewColumn { - //% "Closed" - title: qsTrId("polygon-closed") - role: "closed"; - width: 50 } Component.onCompleted: { diff --git a/src/qml/PolygonListDelegate.qml b/src/qml/PolygonListDelegate.qml index 47fb860..d4f07f0 100644 --- a/src/qml/PolygonListDelegate.qml +++ b/src/qml/PolygonListDelegate.qml @@ -1,5 +1,5 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls import "functions.js" as F import "./components" @@ -9,16 +9,23 @@ Item { signal changeModel(int row, string role, string value); signal openColorDialog(int row, string prevValue); + property int row + property string role + property variant value + property bool selected: false + property color textColor: selected ? "white" : "black" + property int elideMode: Text.ElideRight - NativeText { + + Text { width: parent.width anchors.margins: 4 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - elide: styleData.elideMode - text: styleData.value - color: styleData.textColor - visible: !styleData.selected && (styleData.role !== "closed") && (styleData.role !== "color") + elide: editableDelegate.elideMode + text: editableDelegate.value + color: editableDelegate.textColor + visible: !editableDelegate.selected && (editableDelegate.role !== "closed") && (editableDelegate.role !== "color") } Loader { // Initialize text editor lazily to improve performance id: loaderEditor @@ -32,17 +39,17 @@ Item { onAccepted: { // function onAccepted() { - switch (styleData.role) { + switch (editableDelegate.role) { case "name": // default if (loaderEditor.item.text === "") { //% "Polygon" - changeModel(styleData.row, styleData.role, qsTrId("polygon-list-default-name")); + changeModel(editableDelegate.row, editableDelegate.role, qsTrId("polygon-list-default-name")); } else { - changeModel(styleData.row, styleData.role, loaderEditor.item.text) + changeModel(editableDelegate.row, editableDelegate.role, loaderEditor.item.text) } break; case "color": // default - changeModel(styleData.row, styleData.role, validateColor(loaderEditor.item.text)) + changeModel(editableDelegate.row, editableDelegate.role, validateColor(loaderEditor.item.text)) break; case "cid": console.log("Cannot change point id"); // neni mozne prepsat pid @@ -51,20 +58,20 @@ Item { console.log("Cannot change point count so easy"); // neni mozne prepsat pocet bodu (TODO: editor bodu) break; case "closed": - changeModel(styleData.row, styleData.role, loaderEditor.item.checked) + changeModel(editableDelegate.row, editableDelegate.role, loaderEditor.item.checked) break; default: - changeModel(styleData.row, styleData.role, loaderEditor.item.text) + changeModel(editableDelegate.row, editableDelegate.role, loaderEditor.item.text) break; } } } - sourceComponent: (styleData.role === "closed") ? btn : ( (styleData.role === "color") ? colorRect : (styleData.selected ? editor : null) ) + sourceComponent: (editableDelegate.role === "closed") ? btn : ( (editableDelegate.role === "color") ? colorRect : (editableDelegate.selected ? editor : null) ) Component { id: btn CheckBox { - checked: styleData.value; + checked: editableDelegate.value; signal accepted(); onClicked: { accepted(); @@ -76,12 +83,12 @@ Item { Rectangle { width: 10; height: 10; - color: "#" + styleData.value + color: "#" + editableDelegate.value signal accepted(); MouseArea { anchors.fill: parent; onClicked: { - openColorDialog(styleData.row, styleData.value) + openColorDialog(editableDelegate.row, editableDelegate.value) } } } @@ -89,11 +96,11 @@ Item { Component { id: editor - NativeTextInput { + TextInput { id: textinput - color: styleData.textColor - text: styleData.value + color: editableDelegate.textColor + text: editableDelegate.value MouseArea { id: mouseArea @@ -101,8 +108,8 @@ Item { hoverEnabled: true onClicked: { textinput.forceActiveFocus() - if (styleData.role === "color") { - openColorDialog(styleData.row, styleData.value) + if (editableDelegate.role === "color") { + openColorDialog(editableDelegate.row, editableDelegate.value) } } } diff --git a/src/qml/PropertiesDetail.qml b/src/qml/PropertiesDetail.qml index b8ee32c..76d397e 100644 --- a/src/qml/PropertiesDetail.qml +++ b/src/qml/PropertiesDetail.qml @@ -1,5 +1,5 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick +import QtQuick.Controls import "functions.js" as F import "./components" @@ -95,7 +95,7 @@ ApplicationWindow { anchors.margins: 10; spacing: 5; columns: 2; - NativeText { + Text { //% "Time gate max score [points]" text: qsTrId("props-detail-tg-max-score") } @@ -103,7 +103,7 @@ ApplicationWindow { id: tg_max_score_textfield } - NativeText { + Text { //% "Time gate tolerance [sec]" text: qsTrId("props-detail-tg-tolerance") } @@ -111,7 +111,7 @@ ApplicationWindow { id: tg_tolerance_textfield; } - NativeText { + Text { //% "Time gate penalty [points per sec]" text: qsTrId("props-detail-tg-penalty") } @@ -119,7 +119,7 @@ ApplicationWindow { id: tg_penalty_textfield } - NativeText { + Text { //% "Space gate max score [points]" text: qsTrId("props-detail-sg-max-score") } @@ -128,7 +128,7 @@ ApplicationWindow { id: sg_max_score_textfield } - NativeText { + Text { //% "Turn point max score [points]" text: qsTrId("props-detail-tp-max-score") } @@ -137,7 +137,7 @@ ApplicationWindow { id: tp_max_score_textfield } - NativeText { + Text { //% "Marker max score [points]" text: qsTrId("props-detail-marker-max-score") } @@ -145,7 +145,7 @@ ApplicationWindow { id: marker_max_score_textfield } - NativeText { + Text { //% "Photos max score [points]" text: qsTrId("props-detail-photos-max-score") } @@ -153,7 +153,7 @@ ApplicationWindow { id: photos_max_score_textfield } - NativeText { + Text { //% "Time window size [sec]" text: qsTrId("props-detail-time-window-size") } @@ -161,7 +161,7 @@ ApplicationWindow { id: time_window_size_textfield } - NativeText { + Text { //% "Time window penalty [%]" text: qsTrId("props-detail-time-window-penalty") } @@ -169,7 +169,7 @@ ApplicationWindow { id: time_window_penalty_textfield } - NativeText { + Text { //% "Altitude penalty [points per meter]" text: qsTrId("props-detail-alt-penalty") } @@ -177,7 +177,7 @@ ApplicationWindow { id: alt_penalty_textfield } - NativeText { + Text { //% "Gyre penalty [%]" text: qsTrId("props-detail-gyre-penalty") } @@ -185,7 +185,7 @@ ApplicationWindow { id: gyre_penalty_textfield } - NativeText { + Text { //% "Oposite direction penalty [%]" text: qsTrId("props-detail-oposite-direction-penalty") } @@ -193,7 +193,7 @@ ApplicationWindow { id: oposite_direction_penalty_textfield } - NativeText { + Text { //% "Out of sector pentaly [%]" text: qsTrId("props-detail-out-of-sector-penalty") } @@ -203,7 +203,7 @@ ApplicationWindow { } - NativeText { + Text { //% "Speed max score [points]" text: qsTrId("props-detail-speed-max-score") } @@ -212,7 +212,7 @@ ApplicationWindow { } - NativeText { + Text { //% "Speed penalty [points per km/h]" text: qsTrId("props-detail-speed-penalty") } @@ -220,7 +220,7 @@ ApplicationWindow { id: speed_penalty_textfield } - NativeText { + Text { //% "Speed tolerance [km/h]" text: qsTrId("props-detail-speed-tolerance") } @@ -228,7 +228,7 @@ ApplicationWindow { id: speed_tolerance_textfield } - NativeText { + Text { //% "Preparation time [hh:mm:ss]" text: qsTrId("props-detail-preparation-time"); } @@ -236,7 +236,7 @@ ApplicationWindow { id: preparation_time_textfield property string seconds text: F.addTimeStrFormat(seconds); - validator: RegExpValidator { regExp: /^(\d+):(\d+):(\d+)$/; } + validator: RegularExpressionValidator { regularExpression: /^(\d+):(\d+):(\d+)$/; } @@ -282,7 +282,7 @@ ApplicationWindow { columns: 2; - NativeText { + Text { //% "Radius [m]" text: qsTrId("props-detail-default_radius") } @@ -292,7 +292,7 @@ ApplicationWindow { } - NativeText { + Text { //% "Minimum Altitude [m]" text: qsTrId("props-detail-default_alt_min") } @@ -301,7 +301,7 @@ ApplicationWindow { id: default_alt_min_textfield } - NativeText { + Text { //% "Maximum Altitude [m]" text: qsTrId("props-detail-default_alt_max") } @@ -311,7 +311,7 @@ ApplicationWindow { } - NativeText { + Text { //% "Flags" text: qsTrId("props-detail-default_flags") } @@ -324,7 +324,7 @@ ApplicationWindow { } - NativeText { text: " " } + Text { text: " " } CheckBox { id: tp_cb; //% "Turn Point" @@ -332,7 +332,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(0, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: tg_cb; //% "Time gate" @@ -340,7 +340,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(1, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: sg_cb; //% "Space gate" @@ -349,7 +349,7 @@ ApplicationWindow { } - NativeText { text: " " } + Text { text: " " } CheckBox { id: alt_min_cb; //% "Altitude min" @@ -357,7 +357,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(3, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: alt_max_cb; @@ -367,7 +367,7 @@ ApplicationWindow { } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_speed_start_cb; //% "Section speed start" @@ -375,7 +375,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(7, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_speed_end_cb; //% "Section speed end" @@ -383,7 +383,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(8, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_alt_start_cb; //% "Section alt start" @@ -391,7 +391,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(9, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_alt_end_cb; //% "Section alt end" @@ -399,7 +399,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(10, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_space_start_cb; //% "Section space start" @@ -407,7 +407,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(11, checked); } - NativeText { text: " " } + Text { text: " " } CheckBox { id: section_space_end_cb; //% "Section space end" @@ -418,7 +418,7 @@ ApplicationWindow { - NativeText { text: " "; visible: false;} + Text { text: " "; visible: false;} CheckBox { visible: false; id: secret_turn_point_cb; @@ -427,7 +427,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(13, checked); } - NativeText { text: " "; visible: false; } + Text { text: " "; visible: false; } CheckBox { visible: false; id: secret_time_gate_cb; @@ -436,7 +436,7 @@ ApplicationWindow { onCheckedChanged: updateDefaultFlagsIndex(14, checked); } - NativeText { text: " "; visible: false; } + Text { text: " "; visible: false; } CheckBox { visible: false; id: secret_space_gate_cb; diff --git a/src/qml/TrackStatistics.qml b/src/qml/TrackStatistics.qml index 48c9392..627d963 100644 --- a/src/qml/TrackStatistics.qml +++ b/src/qml/TrackStatistics.qml @@ -35,23 +35,23 @@ ApplicationWindow { spacing: 5 columns: 5; - NativeText { + Text { //% "Issue" text: qsTrId("track-statistics-issue") } - NativeText { + Text { //% "Number" text: qsTrId("track-statistics-number") } - NativeText { + Text { //% "Scoring value" text: qsTrId("track-statistics-scoring-value") } - NativeText { + Text { //% "Value in task" text: qsTrId("track-statistics-value-in-task") } - NativeText { + Text { //% "% of task value" text: qsTrId("track-statistics-percent-of-task-value") } @@ -59,7 +59,7 @@ ApplicationWindow { ///// - NativeText { + Text { //% "Turn points" text: qsTrId("track-statistics-turn-point") } @@ -71,19 +71,19 @@ ApplicationWindow { id: turnpoints_scoring_value text: "0" } - NativeText { + Text { // value in task id: turnpoints_value_in_task text: Math.round(parseFloat(turnpoints_number.text, 10) * parseFloat(turnpoints_scoring_value.text, 10),2) } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(turnpoints_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } ///// - NativeText { + Text { //% "Time gates" text: qsTrId("track-statistics-time-gates") } @@ -95,19 +95,19 @@ ApplicationWindow { id: timegates_scoring_value text: "0" } - NativeText { + Text { // value in task id: timegates_value_in_task text: Math.round(parseFloat(timegates_number.text, 10) * parseFloat(timegates_scoring_value.text, 10),2) } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(timegates_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } ///// - NativeText { + Text { //% "Space gates" text: qsTrId("track-statistics-space-gates") } @@ -120,12 +120,12 @@ ApplicationWindow { text: "0" } - NativeText { + Text { // value in task id: spacegates_value_in_task text: Math.round(parseFloat(spacegates_number.text, 10) * parseFloat(spacegates_scoring_value.text, 10),2) } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(spacegates_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } @@ -133,7 +133,7 @@ ApplicationWindow { ///// - NativeText { + Text { //% "Markers" text: qsTrId("track-statistics-markers") } @@ -145,12 +145,12 @@ ApplicationWindow { id: markers_scoring_value text: "0" } - NativeText { + Text { // value in task id: markers_value_in_task text: Math.round(parseFloat(markers_number.text, 10) * parseFloat(markers_scoring_value.text, 10),2) } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(markers_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } @@ -158,7 +158,7 @@ ApplicationWindow { ///// - NativeText { + Text { //% "Photos" text: qsTrId("track-statistics-photos") } @@ -170,12 +170,12 @@ ApplicationWindow { id: photos_scoring_value text: "0" } - NativeText { + Text { // value in task id: photos_value_in_task text: Math.round(parseFloat(photos_number.text, 10) * parseFloat(photos_scoring_value.text, 10),2) } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(photos_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } @@ -183,15 +183,15 @@ ApplicationWindow { ///// - NativeText { + Text { //% "Other" text: qsTrId("track-statistics-other") } - NativeText { + Text { text: "-" } - NativeText { + Text { text: "-" } @@ -201,7 +201,7 @@ ApplicationWindow { text: "0" } - NativeText { + Text { // % of task text: Math.round(100.0*parseFloat(other_value_in_task.text, 10)/parseFloat(total_score.text,10),2); } @@ -212,18 +212,18 @@ ApplicationWindow { - NativeText { + Text { //% "Total" text: qsTrId("track-statistics-total"); } - NativeText { + Text { text: " " } - NativeText { + Text { text: " " } - NativeText { + Text { id: total_score text: Math.round(parseFloat(turnpoints_value_in_task.text,10) + parseFloat(timegates_value_in_task.text,10) diff --git a/src/qml/TracksList.qml b/src/qml/TracksList.qml index 2a9a85c..a4618bf 100644 --- a/src/qml/TracksList.qml +++ b/src/qml/TracksList.qml @@ -1,5 +1,6 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "functions.js" as F import "geom.js" as G @@ -136,9 +137,10 @@ Rectangle { }) } - trackToTable(categoryChooser.currentIndex) - - + // currentIndex may already be 0, so onCurrentIndexChanged won't fire — force init + categoryChooser.currentIndex = 0; + trackToTable(0); + categoryChanged(0); } @@ -407,6 +409,7 @@ Rectangle { anchors.left: parent.left width: parent.width/3 anchors.margins: 2 + textRole: "text" model: categories; @@ -503,141 +506,157 @@ Rectangle { SplitView { + id: tracksSplitView orientation: Qt.Vertical anchors.top: categoryChooser.bottom; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; - TableView { + Item { id: tracksPointTable - anchors.top: parent.top; - anchors.left: parent.left; - anchors.right: parent.right; - height: parent.height*0.75; - - model: tracksModel; - - selectionMode:SelectionMode.ExtendedSelection; - - itemDelegate: TracksListTableDelegate { - comboModel: pointsModel - typeModel: connectionTypeModel - category_defaults: propsDetail; - - onChangeModel: { - tracksModel.setProperty(row, role, value); + SplitView.fillWidth: true + SplitView.fillHeight: true + SplitView.preferredHeight: 300 + SplitView.minimumHeight: 100 - tracksPointTable.selection.clear(); -// tracksPointTable.selection.select(row); -// tracksPointTable.currentRow = row; + property int currentRow: listView.currentIndex + property alias selection: selectionData + // Background right-click handler: opens context menu even on empty list + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + z: -1 + onClicked: (mouse) => { + tracksContextMenu.popup(); } - - } - - rowDelegate: Rectangle { - height: 30; - color: styleData.selected ? "#0077cc" : (styleData.alternate? "#eee" : "#fff") - - } - - TableViewColumn { - //% "Id" - title: qsTrId("tracks-list-id") - role: "tid" - width: 50; - } - TableViewColumn { - //% "Point" - title: qsTrId("tracks-list-point"); - role: "pid" - width: 250; - } - - TableViewColumn { - //% "Type" - title: qsTrId("tracks-list-type"); - role: "type" - width: 100; - - } - TableViewColumn { - //% "Angle [deg]" - title: qsTrId("tracks-list-angle") - role: "angle" - width: 50; - } - - TableViewColumn { - //% "Distance to previous point [m]" - title: qsTrId("tracks-list-distance") - role: "distance" - width: 50; - } - - TableViewColumn { - //% "Distance to start point [m]" - title: qsTrId("tracks-list-distance-sum") - role: "distance_sum" - width: 50; - } - - TableViewColumn { - //% "Time to next point [s]" - title: qsTrId("tracks-list-addTime") - role: "addTime" - width: 80; } - - TableViewColumn { - //% "Radius [m]" - title: qsTrId("tracks-list-radius") - role: "radius" - width: 50; - } - - - TableViewColumn { - //% "Flags" - title: qsTrId("tracks-list-flags") - role: "flags" - width: 150; - } - - TableViewColumn { - //% "Min Alt [m]" - title: qsTrId("tracks-list-alt_min") - role: "alt_min" - width: 50; - } - TableViewColumn { - //% "Max Alt [m]" - title: qsTrId("tracks-list-alt_max") - role: "alt_max" - width: 50; - //visible: false; - } - - TableViewColumn { - //% "Arc/Poly" - title: qsTrId("tracks-list-ptr") - role: "ptr" - width: 50; + QtObject { + id: selectionData + property var items: [] + property int count: items.length + function clear() { items = []; itemsChanged(); } + function select(idx) { + var newItems = items.slice(); + if (newItems.indexOf(idx) === -1) { + newItems.push(idx); + items = newItems; + } + } + function deselect(start, end) { + var newItems = []; + for (var i = 0; i < items.length; i++) { + if (items[i] < start || items[i] > end) { + newItems.push(items[i]); + } + } + items = newItems; + } + function forEach(cb) { items.forEach(cb); } + function contains(idx) { return items.indexOf(idx) !== -1; } + signal selectionChanged() + onItemsChanged: selectionChanged() } + Flickable { + anchors.fill: parent + contentWidth: 1020 + contentHeight: parent.height + clip: true + boundsBehavior: Flickable.StopAtBounds + + ColumnLayout { + width: 1020 + height: parent.height + spacing: 0 + + // Header Row + Rectangle { + Layout.fillWidth: true + height: 30 + color: "#eee" + border.color: "#ccc" + Row { + id: headerRow + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + Rectangle { width: 50; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-tid"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 180; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-pid"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 100; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-type"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 60; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-angle"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 70; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-distance"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 80; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-distance-sum"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 80; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-add-time"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 60; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-radius"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 150; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-flags"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 70; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-alt_min"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 70; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-alt_max"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 50; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("tracks-list-ptr"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + } + } + ListView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: true + model: tracksModel + clip: true + + delegate: Rectangle { + width: headerRow.width + height: 30 + color: selectionData.contains(index) ? "#0077cc" : (index % 2 == 0 ? "#fff" : "#eee") + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (mouse) => { + if (mouse.button === Qt.RightButton) { + if (!selectionData.contains(index)) { + selectionData.clear(); + selectionData.select(index); + listView.currentIndex = index; + } + tracksContextMenu.popup(); + } else { + if (mouse.modifiers & Qt.ControlModifier) { + if (selectionData.contains(index)) { + var newItems = selectionData.items.filter(i => i !== index); + selectionData.items = newItems; + } else { + selectionData.select(index); + } + } else { + selectionData.clear(); + selectionData.select(index); + listView.currentIndex = index; + } + } + } + } - MouseArea { - acceptedButtons: Qt.RightButton - anchors.fill: parent - propagateComposedEvents: true - onClicked: { - tracksContextMenu.popup(); + Row { + spacing: 0 + anchors.fill: parent + TracksListTableDelegate { width: 50; role: "tid"; value: model.tid; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 180; role: "pid"; value: model.pid; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 100; role: "type"; value: model.type; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 60; role: "angle"; value: model.angle; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 70; role: "distance"; value: model.distance; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 80; role: "distance_sum"; value: model.distance_sum; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 80; role: "addTime"; value: model.addTime; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 60; role: "radius"; value: model.radius; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 150; role: "flags"; value: model.flags; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 70; role: "alt_min"; value: model.alt_min; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 70; role: "alt_max"; value: model.alt_max; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + TracksListTableDelegate { width: 50; role: "ptr"; value: model.ptr; row: index; selected: selectionData.contains(index); comboModel: pointsModel; typeModel: connectionTypeModel; category_defaults: propsDetail; onChangeModel: (row, role, value) => { tracksModel.setProperty(row, role, value); selectionData.clear(); } } + } + } + } } - } Component.onCompleted: { @@ -645,22 +664,20 @@ Rectangle { } function selectionChangedHanlder() { - if (currentRow < 0) { return; } - if (!tracksPointTable.selection.contains(currentRow)) { + if (!selectionData.contains(currentRow)) { return; } var sel = []; - var checkedCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; - tracksPointTable.selection.forEach( function(rowIndex) { + selectionData.forEach( function(rowIndex) { sel.push(rowIndex) - var item = model.get(rowIndex); + var item = tracksModel.get(rowIndex); var flags = parseInt(item.flags, 10); if (flags < 0) { flags = parseInt(propsDetail.default_flags, 10) @@ -673,10 +690,9 @@ Rectangle { checkedCount[i] = checkedCount[i] + 1 } } - }); - var sum = tracksPointTable.selection.count; + var sum = selectionData.count; menu_tp_cb.checked = checkedCount[0] > 0; menu_tg_cb.checked = checkedCount[1] > 0; @@ -689,11 +705,10 @@ Rectangle { menu_section_alt_end_cb.checked = checkedCount[10] > 0; menu_section_space_start_cb.checked = checkedCount[11] > 0; menu_section_space_end_cb.checked = checkedCount[12] > 0; - menu_sectet_turn_point_cb.checked = checkedCount[13] > 0 + menu_sectet_turn_point_cb.checked = checkedCount[13] > 0; menu_sectet_time_gate_cb.checked = checkedCount[14] > 0; menu_sectet_space_gate_cb.checked = checkedCount[15] > 0; - menu_tp_cb.enabled = (checkedCount[0] === 0) || (checkedCount[0] === sum); menu_tg_cb.enabled = (checkedCount[1] === 0) || (checkedCount[1] === sum); menu_sg_cb.enabled = (checkedCount[2] === 0) || (checkedCount[2] === sum); @@ -709,23 +724,18 @@ Rectangle { menu_sectet_time_gate_cb.enabled = (checkedCount[14] === 0) || (checkedCount[14] === sum); menu_sectet_space_gate_cb.enabled = (checkedCount[15] === 0) || (checkedCount[15] === sum); - - // highlight first one in map - if (sel.length >0) { - var item = model.get(sel[0]); + if (sel.length > 0) { + var item = tracksModel.get(sel[0]); pointSelected(item.tid) } - } function switchFlag(flags_index) { - var sel = []; - - tracksPointTable.selection.forEach( function(rowIndex) { + selectionData.forEach( function(rowIndex) { sel.push(rowIndex); - var item = model.get(rowIndex); + var item = tracksModel.get(rowIndex); var flags = parseInt(item.flags, 10); if (flags < 0) { flags = parseInt(propsDetail.default_flags, 10) @@ -734,19 +744,13 @@ Rectangle { var mask = (0x1 << flags_index); flags = flags ^ mask tracksModel.setProperty(rowIndex, "flags", flags); - - }) - - // FIXME check selection console.log("selection") - tracksPointTable.selection.clear(); - + selectionData.clear(); for (var i = 0; i < sel.length; i++) { - tracksPointTable.selection.select(sel[i]); + selectionData.select(sel[i]); } - } Menu { @@ -904,15 +908,11 @@ Rectangle { if ((pos === -1) || (pos >= tracksModel.count)) { tracksModel.append(obj) tracksPointTable.selection.clear(); -// tracksPointTable.currentRow = 0; -// tracksPointTable.selection.select(0) } else { tracksModel.insert(pos, obj) tracksPointTable.selection.clear(); -// tracksPointTable.currentRow = pos; -// tracksPointTable.selection.select(pos) } @@ -946,7 +946,7 @@ Rectangle { text: qsTrId("point-detail-reset-flags"); onTriggered: { tracksPointTable.selection.forEach( function(rowIndex) { - tracksPointTable.model.setProperty(rowIndex, "flags", -1); + tracksModel.setProperty(rowIndex, "flags", -1); }); tracksPointTable.selectionChangedHanlder(); } @@ -1037,96 +1037,137 @@ Rectangle { text: qsTrId("point-detail-section_space_end-checkbox"); onTriggered: tracksPointTable.switchFlag(12); } + + MenuItem { + id: menu_sectet_turn_point_cb + visible: menu_flags_reset.visible + checkable: true; + //% "Secret Turn Point" + text: qsTrId("point-detail-secret_turn_point-checkbox"); + onTriggered: tracksPointTable.switchFlag(13); + } + MenuItem { + id: menu_sectet_time_gate_cb + visible: menu_flags_reset.visible + checkable: true; + text: qsTrId("point-detail-secret_time_gate-checkbox"); + onTriggered: tracksPointTable.switchFlag(14); + } + MenuItem { + id: menu_sectet_space_gate_cb + visible: menu_flags_reset.visible + checkable: true; + text: qsTrId("point-detail-secret_space_gate-checkbox"); + onTriggered: tracksPointTable.switchFlag(15); + } } } Item { - visible: false; - enabled: false; - - MenuItem { - id: menu_sectet_turn_point_cb - visible: menu_flags_reset.visible - checkable: true; - //% "Secret Turn Point" - text: qsTrId("point-detail-secret_turn_point-checkbox"); - onTriggered: tracksPointTable.switchFlag(13); - } - MenuItem { - id: menu_sectet_time_gate_cb - visible: menu_flags_reset.visible - checkable: true; - text: qsTrId("point-detail-secret_time_gate-checkbox"); - onTriggered: tracksPointTable.switchFlag(14); - } - MenuItem { - id: menu_sectet_space_gate_cb - visible: menu_flags_reset.visible - checkable: true; - text: qsTrId("point-detail-secret_space_gate-checkbox"); - onTriggered: tracksPointTable.switchFlag(15); - } - } - - - TableView { id: polygonsTable - anchors.left: parent.left; - anchors.right: parent.right - anchors.top: tracksPointTable.bottom; - anchors.bottom: parent.bottom; - - model: selectedPolygonsModel - - rowDelegate: Rectangle { - height: 30; - color: styleData.selected ? "#0077cc" : (styleData.alternate? "#eee" : "#fff") - - } - itemDelegate: TracksListPolygonsDelegate { - comboModel: allPolygonsModel - onChangeModel: { - if (row >= selectedPolygonsModel.count) { - return; + SplitView.fillWidth: true + SplitView.preferredHeight: 150 + SplitView.minimumHeight: 100 + + property int currentRow: listViewPoly.currentIndex + property alias selection: selectionPolyData + + QtObject { + id: selectionPolyData + property var items: [] + property int count: items.length + function clear() { items = []; itemsChanged(); } + function select(idx) { + var newItems = items.slice(); + if (newItems.indexOf(idx) === -1) { + newItems.push(idx); + items = newItems; + } + } + function deselect(start, end) { + var newItems = []; + for (var i = 0; i < items.length; i++) { + if (items[i] < start || items[i] > end) { + newItems.push(items[i]); + } } - selectedPolygonsModel.setProperty(row, role, value); - - polygonsTable.selection.clear(); -// polygonsTable.selection.select(row); -// polygonsTable.currentRow = row; - + items = newItems; } - + function forEach(cb) { items.forEach(cb); } + function contains(idx) { return items.indexOf(idx) !== -1; } + signal selectionChanged() + onItemsChanged: selectionChanged() } - TableViewColumn { - //% "Id" - title: qsTrId("track-list-polygon-did") - role: "did"; - width: 50; - - } - TableViewColumn { - //% "Polygon" - title: qsTrId("track-list-polygon-cid") - role: "cid"; - width: 150; - } - TableViewColumn { - //% "Score" - title: qsTrId("track-list-polygon-score") - role: "score" - width: 150; - } - - MouseArea { - acceptedButtons: Qt.RightButton + ColumnLayout { anchors.fill: parent - propagateComposedEvents: true - onClicked: { - polyContextMenu.popup(); + spacing: 0 + + // Header Row + Rectangle { + Layout.fillWidth: true + height: 30 + color: "#eee" + border.color: "#ccc" + Row { + id: headerPolyRow + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + Rectangle { width: 50; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("track-list-polygon-did"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 150; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("track-list-polygon-cid"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + Rectangle { width: 150; height: 30; color: "transparent"; border.color: "#ccc"; Text { text: qsTrId("track-list-polygon-score"); anchors.fill: parent; anchors.margins: 4; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; font.pixelSize: 11; font.bold: true } } + } } + ListView { + id: listViewPoly + Layout.fillWidth: true + Layout.fillHeight: true + model: selectedPolygonsModel + clip: true + + delegate: Rectangle { + width: headerPolyRow.width + height: 30 + color: selectionPolyData.contains(index) ? "#0077cc" : (index % 2 == 0 ? "#fff" : "#eee") + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (mouse) => { + if (mouse.button === Qt.RightButton) { + if (!selectionPolyData.contains(index)) { + selectionPolyData.clear(); + selectionPolyData.select(index); + listViewPoly.currentIndex = index; + } + polyContextMenu.popup(); + } else { + if (mouse.modifiers & Qt.ControlModifier) { + if (selectionPolyData.contains(index)) { + var newItems = selectionPolyData.items.filter(i => i !== index); + selectionPolyData.items = newItems; + } else { + selectionPolyData.select(index); + } + } else { + selectionPolyData.clear(); + selectionPolyData.select(index); + listViewPoly.currentIndex = index; + } + } + } + } + + Row { + spacing: 0 + anchors.fill: parent + TracksListPolygonsDelegate { width: 50; role: "did"; value: model.did; row: index; selected: selectionPolyData.contains(index); comboModel: allPolygonsModel; onChangeModel: (row, role, value) => { if (row < selectedPolygonsModel.count) { selectedPolygonsModel.setProperty(row, role, value); selectionPolyData.clear(); } } } + TracksListPolygonsDelegate { width: 150; role: "cid"; value: model.cid; row: index; selected: selectionPolyData.contains(index); comboModel: allPolygonsModel; onChangeModel: (row, role, value) => { if (row < selectedPolygonsModel.count) { selectedPolygonsModel.setProperty(row, role, value); selectionPolyData.clear(); } } } + TracksListPolygonsDelegate { width: 150; role: "score"; value: model.score; row: index; selected: selectionPolyData.contains(index); comboModel: allPolygonsModel; onChangeModel: (row, role, value) => { if (row < selectedPolygonsModel.count) { selectedPolygonsModel.setProperty(row, role, value); selectionPolyData.clear(); } } } + } + } + } } Menu { @@ -1171,3 +1212,4 @@ Rectangle { } } + diff --git a/src/qml/TracksListPolygonsDelegate.qml b/src/qml/TracksListPolygonsDelegate.qml index 4121f1d..2dd3f04 100644 --- a/src/qml/TracksListPolygonsDelegate.qml +++ b/src/qml/TracksListPolygonsDelegate.qml @@ -1,25 +1,33 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls import "functions.js" as F import "./components" Item { id: item; + height: parent ? parent.height : 30 property variant comboModel property variant typeModel signal changeModel(int row, string role, variant value); + property int row + property string role + property variant value + property bool selected: false + property color textColor: selected ? "white" : "black" + property int elideMode: Text.ElideRight - NativeText { + + Text { width: parent.width anchors.margins: 4 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - elide: styleData.elideMode - text: styleData.value; - color: styleData.textColor - visible: (styleData.role === "did") || ((styleData.role === "score") && !styleData.selected) + elide: item.elideMode + text: item.value; + color: item.textColor + visible: (item.role === "did") || ((item.role === "score") && !item.selected) } Loader { // Initialize text editor lazily to improve performance @@ -31,28 +39,26 @@ Item { anchors.margins: 4 Connections { target: cidComboLoader.item - onNewCid: { -// function onNewCid(cid) { - changeModel(styleData.row, styleData.role, cid) + function onNewCid(cid) { + changeModel(item.row, item.role, cid) } } Connections { target: cidComboLoader.item - onNewScore: { -// function onNewScore(value) { - changeModel(styleData.row, styleData.role, value) + function onNewScore(value) { + changeModel(item.row, item.role, value) } } - sourceComponent: (styleData.role === "cid") ? polygonSelection : ((styleData.selected && styleData.role === "score") ? polygonScoreText : null) + sourceComponent: (item.role === "cid") ? polygonSelection : ((item.selected && item.role === "score") ? polygonScoreText : null) Component { id: polygonSelection ComboBox { id: combo width: 130; textRole: "name" - property string tableCid: parseInt(styleData.value); + property string tableCid: parseInt(item.value); signal newCid(int cid); signal newScore(int value); @@ -63,7 +69,7 @@ Item { } var it = comboModel.get(currentIndex); - if (it.cid != styleData.value) { + if (it.cid != item.value) { newCid(it.cid); } } @@ -82,7 +88,7 @@ Item { for (var i = 0; i < model.count; i++) { var it = model.get(i); - if (it.cid == styleData.value) { + if (it.cid == item.value) { toIdx = i; break; } @@ -97,13 +103,13 @@ Item { Component { id: polygonScoreText - NativeTextInput { + TextInput { id: textinput signal newCid(); signal newScore(int value); - color: styleData.textColor - text: styleData.value + color: item.textColor + text: item.value onAccepted: { newScore(parseInt(textinput.text, 10)) } diff --git a/src/qml/TracksListTableDelegate.qml b/src/qml/TracksListTableDelegate.qml index 43c292d..30dcd33 100644 --- a/src/qml/TracksListTableDelegate.qml +++ b/src/qml/TracksListTableDelegate.qml @@ -1,27 +1,35 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 +import QtQuick +import QtQuick.Controls import "functions.js" as F import "./components" Item { id: delegate; + height: parent ? parent.height : 30 property variant comboModel property variant typeModel property variant category_defaults; signal changeModel(int row, string role, variant value); - NativeText { + property int row + property string role + property variant value + property bool selected: false + property color textColor: selected ? "white" : "black" + property int elideMode: Text.ElideRight + + Text { width: parent.width anchors.margins: 4 anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - elide: styleData.elideMode - text: getTextForRole(styleData.row, styleData.role, styleData.value); - color: (styleData.value === -1 - || (styleData.role === "addTime" && styleData.value === 0) - ) ? "#aaa" : styleData.textColor - visible: ((styleData.role === "tid") || (styleData.role === "flags") || styleData.role === "distance_sum" ) || (!styleData.selected && (styleData.role !== "type")) + elide: delegate.elideMode + text: getTextForRole(delegate.row, delegate.role, delegate.value); + color: (delegate.value === -1 + || (delegate.role === "addTime" && delegate.value === 0) + ) ? "#aaa" : delegate.textColor + visible: ((delegate.role === "tid") || (delegate.role === "flags") || delegate.role === "distance_sum" ) || (!delegate.selected && (delegate.role !== "type")) } @@ -35,58 +43,53 @@ Item { anchors.margins: 4 Connections { target: pidComboLoader.item -// function onNewPid() { - onNewPid: { - changeModel(styleData.row, styleData.role, pid) + function onNewPid(pid) { + changeModel(delegate.row, delegate.role, pid) } } - sourceComponent: (styleData.role === "pid") ? pointSelection : null + sourceComponent: (delegate.role === "pid") ? pointSelection : null Component { id: pointSelection ComboBox { id: combo - width: delegate.width-10; + width: delegate.width - 10 textRole: "text" + model: comboModel - property int tablePid: parseInt(styleData.value); - - signal newPid(int pid); - onCurrentIndexChanged: { - if (comboModel === undefined) { - return; - } - - var it = comboModel.get(currentIndex); - if (it.pid !== styleData.value) { // jen kdyz se zmenilo - newPid(it.pid); - } - - } - - onTablePidChanged: { - if (tablePid < 0) { - return; - } - - model = comboModel - var toIdx = 0; + property int tablePid: parseInt(delegate.value) + property bool initializing: true + signal newPid(int pid) + function updateIndex() { + if (!model || model.count === 0) return; + initializing = true; + var target = delegate.value; for (var i = 0; i < model.count; i++) { var it = model.get(i); - if (it.pid === styleData.value) { - toIdx = i; - break; + if (it.pid == target) { // == for type-safe comparison + currentIndex = i; + initializing = false; + return; } } - currentIndex = toIdx; + currentIndex = 0; + initializing = false; } + Component.onCompleted: updateIndex() + onModelChanged: if (model && model.count > 0) updateIndex() + onTablePidChanged: if (model && model.count > 0) updateIndex() - + onCurrentIndexChanged: { + if (initializing || !model || model.count === 0 || currentIndex < 0) return; + var it = model.get(currentIndex); + if (it && it.pid != delegate.value) { + newPid(it.pid); + } + } } } - } @@ -100,56 +103,52 @@ Item { anchors.margins: 4 Connections { target: loaderType.item - - onNewType: { -// function onNewType(t) { - changeModel(styleData.row, styleData.role, t) + function onNewType(t) { + changeModel(delegate.row, delegate.role, t) } - } - sourceComponent: ((styleData.role === "type") && (styleData.row !== 0)) ? typeSelection : null + sourceComponent: ((delegate.role === "type") && (delegate.row !== 0)) ? typeSelection : null Component { id: typeSelection ComboBox { id: typeCombo - width: delegate.width-10 + width: delegate.width - 10 textRole: "text" + model: typeModel - property string tableType: styleData.value; - signal newType(string t); - + property string tableType: (delegate.value !== undefined && delegate.value !== null) ? delegate.value : "" + property bool initializing: true + signal newType(string t) - onCurrentIndexChanged: { - if (typeModel === undefined) { - return; - } - var it = typeModel.get(currentIndex) - if (it.typeId !== styleData.value) { // jen kdyz se zmenilo - newType(it.typeId); + function updateIndex() { + if (!model || model.count === 0 || tableType === "") return; + initializing = true; + var target = delegate.value; + for (var i = 0; i < model.count; i++) { + var it = model.get(i); + if (it.typeId === target) { + currentIndex = i; + initializing = false; + return; + } } + initializing = false; } + Component.onCompleted: updateIndex() + onModelChanged: if (model && model.count > 0) updateIndex() + onTableTypeChanged: if (model && model.count > 0) updateIndex() - onTableTypeChanged: { - if (tableType =="") { - return; - } - - model = typeModel; - var toIdx = 0; - for (var i = 0; i < model.count; i++) { - var it = model.get(i); - if (it.typeId === styleData.value) { - toIdx = i; - break; - } + onCurrentIndexChanged: { + if (initializing || !model || model.count === 0 || currentIndex < 0) return; + var it = model.get(currentIndex); + if (it && it.typeId !== delegate.value) { + newType(it.typeId); } - currentIndex = toIdx } } } - } @@ -165,10 +164,10 @@ Item { // target:loaderSpinBox.item // onNewAngle: { //// function onNewAngle(angle) { - // changeModel(styleData.row, styleData.role, angle) + // changeModel(delegate.row, delegate.role, angle) // } // } - // sourceComponent: ((styleData.role === "angle") && (styleData.selected)) ? spinbox : null; + // sourceComponent: ((delegate.role === "angle") && (delegate.selected)) ? spinbox : null; // Component { // id: spinbox; // SpinBox { @@ -177,8 +176,8 @@ Item { // minimumValue: -1; // maximumValue: 360; // stepSize: 10; - // value: getTextForRole(styleData.row, styleData.role, styleData.value); - // font.weight: (styleData.value === -1) ? Font.Light : Font.Normal + // value: getTextForRole(delegate.row, delegate.role, delegate.value); + // font.weight: (delegate.value === -1) ? Font.Light : Font.Normal // onEditingFinished: { // newAngle(value) @@ -199,17 +198,15 @@ Item { anchors.margins: 4 Connections { target: loaderEditor.item - onNewValue: { -// function onNewValue(value) { - - switch (styleData.role) { + function onNewValue(value) { + switch (delegate.role) { case "angle": // default var num = parseFloat(value); if (isNaN(num)) { - changeModel(styleData.row, styleData.role, -1) + changeModel(delegate.row, delegate.role, -1) } else { num = (num + 90) % 360 - changeModel(styleData.row, styleData.role, num) + changeModel(delegate.row, delegate.role, num) } break; case "distance": @@ -219,9 +216,9 @@ Item { case "ptr": var num = parseFloat(value); if (isNaN(num)) { - changeModel(styleData.row, styleData.role, -1) + changeModel(delegate.row, delegate.role, -1) } else { - changeModel(styleData.row, styleData.role, num) + changeModel(delegate.row, delegate.role, num) } break; case "addTime": @@ -230,19 +227,19 @@ Item { var result = regexp.exec(str); if (result) { var num = parseInt(result[1], 10) * 3600 + parseInt(result[2], 10) * 60 + parseInt(result[3], 10); - changeModel(styleData.row, styleData.role, num) + changeModel(delegate.row, delegate.role, num) } else { var num = parseFloat(str); if (isNaN(num)) { - changeModel(styleData.row, styleData.role, 0) + changeModel(delegate.row, delegate.role, 0) } else { - changeModel(styleData.row, styleData.role, num) + changeModel(delegate.row, delegate.role, num) } } break; default: - changeModel(styleData.row, styleData.role, value) + changeModel(delegate.row, delegate.role, value) break; } @@ -250,32 +247,32 @@ Item { } sourceComponent: ( - styleData.role !== "tid" && - styleData.role !== "type" && - styleData.role !== "pid" && - styleData.role !== "flags" && - styleData.role !== "distance_sum" - ) && (styleData.selected) + delegate.role !== "tid" && + delegate.role !== "type" && + delegate.role !== "pid" && + delegate.role !== "flags" && + delegate.role !== "distance_sum" + ) && (delegate.selected) ? editor : null Component { id: editor - NativeTextInput { + TextInput { id: textinput signal newValue(string value); - color: styleData.textColor - text: getTextForRole(styleData.row, styleData.role, styleData.value); + color: delegate.textColor + text: getTextForRole(delegate.row, delegate.role, delegate.value); Keys.onUpPressed: { - if (styleData.role === "angle") { + if (delegate.role === "angle") { text =parseInt(text) +10 } } Keys.onDownPressed: { - if (styleData.role === "angle") { + if (delegate.role === "angle") { text = parseInt(text) - 10 } } @@ -322,11 +319,11 @@ Item { show = category_defaults.default_flags; break; case "angle": - var it = tracksModel.get(styleData.row); + var it = tracksModel.get(delegate.row); show = Math.round(it.computed_angle) break; case "distance": - var it = tracksModel.get(styleData.row); + var it = tracksModel.get(delegate.row); show = Math.round(it.computed_distance) break; case "radius": @@ -356,7 +353,7 @@ Item { break; case "distance_sum": var distance_sum = 0; - for (var i = 0; ((i < tracksModel.count) && (i <= styleData.row)); i++) { + for (var i = 0; ((i < tracksModel.count) && (i <= delegate.row)); i++) { var item = tracksModel.get(i); var distance = (item.distance !== -1) ? item.distance : item.computed_distance distance_sum += distance; diff --git a/src/qml/components/NativeText.qml b/src/qml/components/NativeText.qml deleted file mode 100644 index 8a6b721..0000000 --- a/src/qml/components/NativeText.qml +++ /dev/null @@ -1,5 +0,0 @@ -import QtQuick 2.9 - -Text { - renderType: Text.NativeRendering -} diff --git a/src/qml/components/NativeTextInput.qml b/src/qml/components/NativeTextInput.qml deleted file mode 100644 index 90493d2..0000000 --- a/src/qml/components/NativeTextInput.qml +++ /dev/null @@ -1,6 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 1.4 - -TextInput { - renderType: TextInput.NativeRendering -} diff --git a/src/qml/components/Ruler.qml b/src/qml/components/Ruler.qml index 8e59bc3..5994f3e 100644 --- a/src/qml/components/Ruler.qml +++ b/src/qml/components/Ruler.qml @@ -36,7 +36,7 @@ Item { smooth: true; } - NativeText { + Text { id: ruler_text anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.top diff --git a/src/qml/components/ShadowElement.qml b/src/qml/components/ShadowElement.qml index 72e8007..7d91bc3 100644 --- a/src/qml/components/ShadowElement.qml +++ b/src/qml/components/ShadowElement.qml @@ -1,11 +1,9 @@ -import QtGraphicalEffects 1.0 +import QtQuick.Effects -DropShadow { - cached: true - horizontalOffset: 3 - verticalOffset: 3 - radius: 8.0 - samples: 16 - color: "#80000000" - smooth: true -} +MultiEffect { + shadowEnabled: true + shadowHorizontalOffset: 3 + shadowVerticalOffset: 3 + shadowBlur: 0.5 // 0.0–1.0 + shadowColor: "#80000000" +} \ No newline at end of file diff --git a/src/qml/components/TextDialog.qml b/src/qml/components/TextDialog.qml index a146a8e..132c02a 100644 --- a/src/qml/components/TextDialog.qml +++ b/src/qml/components/TextDialog.qml @@ -14,7 +14,7 @@ Dialog { anchors.margins: 10; spacing: 15; - NativeText { + Text { id: questionText width: parent.width; From 26e9fd45ac49f2fa822e4593ff7f26d6f8159293 Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 12:02:20 +0200 Subject: [PATCH 2/7] Update CI --- .github/workflows/main.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 850efb7..a211d27 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,44 +1,37 @@ name: Build - on: [push] - - jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest] - version: ['5.15.2'] + version: ['6.8.1'] steps: - - uses: actions/checkout@v1 - + - uses: actions/checkout@v4 - name: Install Qt uses: jurplel/install-qt-action@v4 with: version: ${{ matrix.version }} host: 'windows' target: 'desktop' - arch: 'win64_msvc2019_64' + arch: 'win64_msvc2022_64' # modules: 'qtwebengine' tools: 'tools_opensslv3_x64,qt.tools.opensslv3.win_x64' - - name: Build if: startsWith(matrix.os, 'windows') run: | - call "%programfiles(x86)%\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + call "%programfiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" mkdir build cd build - cmake -DCMAKE_PREFIX_PATH="C:\Qt\5.15.2\msvc2019_64" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_BUILD_TYPE=RELEASE -DDEPLOY_QT_LIBRARIES=ON -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON .. + cmake -DCMAKE_PREFIX_PATH="C:\Qt\6.8.1\msvc2022_64" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_BUILD_TYPE=RELEASE -DDEPLOY_QT_LIBRARIES=ON -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON .. cmake --build . --config Release ctest -C Release . cp *.qm ./Release for /R c:\Qt\Tools\OpenSSL %%f in (*.dll) do copy %%f "%CD%\Release" shell: cmd - - - name: Archive build results uses: actions/upload-artifact@v4 with: name: editor - path: ./build/Release/* + path: ./build/Release/* \ No newline at end of file From 6e404208b99659a7649ee2b0ed8ecc3f9801cce5 Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 12:04:15 +0200 Subject: [PATCH 3/7] fix clang-format --- src/igc.h | 2 +- src/kmljsonconvertor.cpp | 2 +- src/main.cpp | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/igc.h b/src/igc.h index e1abe21..04906fc 100644 --- a/src/igc.h +++ b/src/igc.h @@ -1,8 +1,8 @@ #ifndef IGC__H #define IGC__H -#include #include +#include /// A single event from the igc file. /// the field type determines which subclass of Event diff --git a/src/kmljsonconvertor.cpp b/src/kmljsonconvertor.cpp index 767f86c..e9c78d9 100644 --- a/src/kmljsonconvertor.cpp +++ b/src/kmljsonconvertor.cpp @@ -1,6 +1,6 @@ +#include #include #include -#include #include "kmljsonconvertor.h" diff --git a/src/main.cpp b/src/main.cpp index ffd4a8d..bd7b089 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,7 +34,7 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, #if defined(Q_OS_LINUX) if (!QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) - .exists()) { + .exists()) { QDir().mkpath( QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); } @@ -142,8 +142,6 @@ int main(int argc, char* argv[]) QTranslator translator; QTranslator qtbasetranslator; - - if (translator.load(QLocale(), QLatin1String("editor"), QLatin1String("_"), QLatin1String("."))) { app.installTranslator(&translator); engine.rootContext()->setContextProperty("localeBcp", QLocale::system().bcp47Name()); From 5668287e0836fba69fbd2de5927d780cea0709e4 Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 12:06:54 +0200 Subject: [PATCH 4/7] Update for Qt6 --- cmake/DeployQt.cmake | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/cmake/DeployQt.cmake b/cmake/DeployQt.cmake index 1eda460..0470c4c 100644 --- a/cmake/DeployQt.cmake +++ b/cmake/DeployQt.cmake @@ -20,11 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -find_package(Qt5Core REQUIRED) +find_package(Qt6Core REQUIRED) -# Retrieve the absolute path to qmake and then use that path to find -# the windeployqt and macdeployqt binaries -get_target_property(_qmake_executable Qt5::qmake IMPORTED_LOCATION) +get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") @@ -37,29 +35,16 @@ if(APPLE AND NOT MACDEPLOYQT_EXECUTABLE) message(FATAL_ERROR "macdeployqt not found") endif() -# Add commands that copy the required Qt files to the same directory as the -# target after being built as well as including them in final installation function(windeployqt target) - - # Run windeployqt immediately after build add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}" --verbose 0 --qmldir \"${CMAKE_CURRENT_SOURCE_DIR}\" - --no-compiler-runtime \"$\" COMMENT "Deploying Qt..." ) - # windeployqt doesn't work correctly with the system runtime libraries, - # so we fall back to one of CMake's own modules for copying them over - - # Doing this with MSVC 2015 requires CMake 3.6+ - if((MSVC_VERSION VERSION_EQUAL 1900 OR MSVC_VERSION VERSION_GREATER 1900) - AND CMAKE_VERSION VERSION_LESS "3.6") - message(WARNING "Deploying with MSVC 2015+ requires CMake 3.6+") - endif() set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE) include(InstallRequiredSystemLibraries) foreach(lib ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}) @@ -70,10 +55,9 @@ function(windeployqt target) COMMENT "Copying ${filename}..." ) endforeach() + endfunction() -# Add commands that copy the required Qt files to the application bundle -# represented by the target. function(macdeployqt target) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${MACDEPLOYQT_EXECUTABLE}" @@ -84,4 +68,4 @@ function(macdeployqt target) ) endfunction() -mark_as_advanced(WINDEPLOYQT_EXECUTABLE MACDEPLOYQT_EXECUTABLE) \ No newline at end of file +mark_as_advanced(WINDEPLOYQT_EXECUTABLE MACDEPLOYQT_EXECUTABLE) From cbceed28b2ff71f0ba9242705a4643ccb975da5f Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 12:09:39 +0200 Subject: [PATCH 5/7] Update .github/workflows/clang-format-check.yml --- .github/workflows/clang-format-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index fccef25..b15e05e 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -9,5 +9,5 @@ jobs: - name: Run clang-format style check for C/C++/Protobuf programs. uses: jidicula/clang-format-action@v4.9.0 with: - clang-format-version: '18' + clang-format-version: '22' check-path: '.' From 924dd3aca7837341e343952578ff37cb0b3ba7cc Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 17 May 2026 13:00:00 +0200 Subject: [PATCH 6/7] fix warning --- src/gpxjsonconvertor.cpp | 8 ++++---- src/kmljsonconvertor.cpp | 8 ++++---- src/main.cpp | 35 ++++++++++++++++++----------------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/gpxjsonconvertor.cpp b/src/gpxjsonconvertor.cpp index a8b8136..70d7fee 100644 --- a/src/gpxjsonconvertor.cpp +++ b/src/gpxjsonconvertor.cpp @@ -52,7 +52,7 @@ QString GpxJsonConvertor::gpxToJSONString_local(QString filename) points = points + str; } - points.remove(points.count() - 1, 1); + points.remove(points.length() - 1, 1); QDomNodeList trks = root.elementsByTagName("trk"); @@ -81,11 +81,11 @@ QString GpxJsonConvertor::gpxToJSONString_local(QString filename) str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(latitude).arg(longitude); } - str.remove(str.count() - 1, 1); + str.remove(str.length() - 1, 1); poly = poly + QString("{\"name\": \"%1\", \"color\":\"%2\", \"points\":[%3]},").arg(encodeString(name)).arg(color).arg(str); } - poly.remove(poly.count() - 1, 1); + poly.remove(poly.length() - 1, 1); return QString("{\"points\": [%1], \"poly\": [%2]}").arg(points).arg(poly); } @@ -93,7 +93,7 @@ QString GpxJsonConvertor::gpxToJSONString_local(QString filename) QString GpxJsonConvertor::encodeString(const QString& value) { QString result = ""; - for (int i = 0; i < value.count(); i++) { + for (int i = 0; i < value.length(); i++) { ushort chr = value.at(i).unicode(); if (chr < 32) { switch (chr) { diff --git a/src/kmljsonconvertor.cpp b/src/kmljsonconvertor.cpp index e9c78d9..629e4f6 100644 --- a/src/kmljsonconvertor.cpp +++ b/src/kmljsonconvertor.cpp @@ -82,7 +82,7 @@ QString KmlJsonConvertor::kmlToJSONString_local(QString filename) str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(match.captured(2).toFloat()).arg(match.captured(1).toFloat()); pos = match.capturedEnd(); } - str.remove(str.count() - 1, 1); + str.remove(str.length() - 1, 1); poly = poly + QString("{\"name\": \"%1\", \"color\":\"%2\", " "\"points\":[%3], \"closed\": true},") .arg(nodeName) @@ -96,15 +96,15 @@ QString KmlJsonConvertor::kmlToJSONString_local(QString filename) str = str + QString("{\"lat\": %1,\"lon\":%2},").arg(match.captured(2).toFloat()).arg(match.captured(1).toFloat()); pos = match.capturedEnd(); } - str.remove(str.count() - 1, 1); + str.remove(str.length() - 1, 1); poly = poly + QString("{\"name\": \"%1\", \"color\":\"%2\", \"points\":[%3]},").arg(nodeName).arg(color).arg(str); } else { qDebug() << nodeName << "unknown node"; } } - points.remove(points.count() - 1, 1); - poly.remove(poly.count() - 1, 1); + points.remove(points.length() - 1, 1); + poly.remove(poly.length() - 1, 1); return QString("{\"points\": [%1], \"poly\": [%2]}").arg(points).arg(poly); } diff --git a/src/main.cpp b/src/main.cpp index bd7b089..5cb5aa1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,27 +26,23 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { - QString txt; + const QDateTime now = []() { + const QDateTime local = QDateTime::currentDateTime(); + return local.toOffsetFromUtc(local.offsetFromUtc()); + }(); - QDateTime now = QDateTime::currentDateTime(); - int offset = now.offsetFromUtc(); - now.setOffsetFromUtc(offset); + QString txt; #if defined(Q_OS_LINUX) - if (!QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) - .exists()) { - QDir().mkpath( - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - } - QFile outFile( - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + "editor.log"); -#elif (defined(Q_OS_WIN) || defined(Q_OS_WIN32) || defined(Q_OS_WIN64)) + const QString logDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + if (!QDir(logDir).exists()) + QDir().mkpath(logDir); + QFile outFile(logDir + QDir::separator() + "editor.log"); +#elif defined(Q_OS_WIN) || defined(Q_OS_WIN32) || defined(Q_OS_WIN64) QFile outFile("editor.log"); #else - QFile outfile("editor.log"); + QFile outFile("editor.log"); #endif - outFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); - QTextStream ts(&outFile); QTextStream std_out(stdout, QIODevice::WriteOnly); QTextStream std_err(stderr, QIODevice::WriteOnly); @@ -98,9 +94,14 @@ void myMessageHandler(QtMsgType type, const QMessageLogContext& context, std_err << txt << Qt::endl; break; } - ts << txt << Qt::endl; - outFile.close(); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + std_err << "Failed to open log file: " << outFile.errorString() << Qt::endl; + } else { + QTextStream ts(&outFile); + ts << txt << Qt::endl; + outFile.close(); + } } int main(int argc, char* argv[]) From 7f16f98bc8ff61fcce47ac5d7d2c77e292aa3fcb Mon Sep 17 00:00:00 2001 From: Jozef Mlich Date: Sun, 24 May 2026 11:46:38 +0200 Subject: [PATCH 7/7] Angle in table is incorrect, because it is angle of arival to gate, not departure --- src/qml/MainWindow.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qml/MainWindow.qml b/src/qml/MainWindow.qml index 1e8f5ab..dfcd3d2 100644 --- a/src/qml/MainWindow.qml +++ b/src/qml/MainWindow.qml @@ -1449,7 +1449,8 @@ ApplicationWindow { str += ""; str += "" + F.addSlashes(pt.name) + ""; - str += "" + displayAngle + "°"; +// str += "" + displayAngle + "°"; + str += ""; str += "" + displayDistance + ""; str += "" + displayCumulativeDistance + ""; str += "" + displayAddTime + "";