diff MainWindow.cpp @ 1:0b3630a29ad8

Initial version based on previous repository. Project was ported to QT6 and in now cmake based.
author Ideenmodellierer <tiefenrauscher@web.de>
date Thu, 27 Nov 2025 18:40:28 +0100
parents
children e30f00f760d3
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MainWindow.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,536 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   MainWindow.cpp
+/// \brief  GUI for OSTC Companion.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "MainWindow.h"
+
+#include "Utils/LogAppender.h"
+#include "Utils/ProgressEvent.h"
+
+#include "ui_MainWindow.h"
+#include "SettingsDialog.h"
+#include "editLogDialog.h"
+
+#include <QString>
+#include <QDateTime>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <QProgressBar>
+#include <QSettings>
+#include <QTextCursor>
+#include "OSTCFrogOperations.h"
+#include "OSTC2cOperations.h"
+#include "OSTC2Operations.h"
+#include "OSTCSportOperations.h"
+#include "OSTC3Operations.h"
+#include "OSTC3pOperations.h"
+#include "OSTC4Operations.h"
+#include "OSTC_CR_Operations.h"
+
+extern QSettings* settings;
+
+//////////////////////////////////////////////////////////////////////////////
+
+class EXPORT LogWindow
+    : public LogAppender
+{
+    MainWindow* _window;
+
+    //---- The <<printing>> function -----------------------------------------
+    void operator()(const Log &log) override {
+        QString message = log.message;
+
+        message.replace("< ", "&lt; ")
+               .replace(" >", " &gt;");
+        if( ! message.isEmpty() )
+            _window->statusMessage(message);
+    }
+
+    //---- Reimplementing mandatory methds -----------------------------------
+    const char *type() const override {
+        return "File";
+    }
+    Log::Level defaultMinLevel() const override {
+        return Log::LEVEL_INFO;
+    }
+    const char* defaultFormat() const override {
+        return "%m";
+    }
+
+public:
+    LogWindow(MainWindow* window)
+      : LogAppender(0, NULL),
+        _window(window)
+    {}
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+MainWindow::MainWindow()
+  : QMainWindow(NULL),
+    _ui(new Ui::MainWindow),
+    _op(0)
+{
+    // Connect the Log system to this window:
+    new LogWindow(this);
+    // Connect the progress system to this window:
+    ProgressManager::getInstance()->setMainWindow(this);
+
+    _ui->setupUi(this);
+    _ui->progressBar->show();
+
+    _ui->editLog->setVisible(false);
+
+    // Auto-select last model:
+    QString model = settings->value("Interface/computerType").toString();
+        _ui->computerType->setCurrentIndex(0);
+
+    if( model == "ostc2c" )
+        _ui->computerType->setCurrentIndex(0);
+    else if( model == "hwOS (Bluetooth)" )
+        _ui->computerType->setCurrentIndex(1);
+    else if( model == "hwOS (USB)" )
+        _ui->computerType->setCurrentIndex(2);
+    else if( model == "ostc4" )
+        _ui->computerType->setCurrentIndex(3);
+
+    changeTypeSlot();
+
+#ifdef Q_OS_MAC
+    {
+        QMenuBar *menuBar = new QMenuBar(this);
+        QMenu* help = menuBar->addMenu(tr("&Help"));
+            help->addAction(tr("Preferences..."), this, SLOT(settingsSlot()));
+    }
+#endif
+
+    setWindowTitle(QString("OSTC Companion v%1.%2 %3")
+                       .arg(MAJOR_VERSION)
+                       .arg(MINOR_VERSION)  // kein sprintf nötig, arg konvertiert automatisch
+                       .arg(BETA_VERSION
+                                ? QString(" beta %1").arg(PATCH_VERSION)
+                                : QString::number(PATCH_VERSION))
+                   );
+
+   _ui->companionUrlL->setText( QString("<html>%1 "
+        "<a href='https://ostc-planner.net/wp/companion"
+#if defined(Q_OS_IOS)
+            "?os=ios"
+#elif  defined(Q_OS_OSX)
+            "?os=mac"
+#elif defined(Q_OS_WIN)
+            "?os=win"
+#elif defined(Q_OS_ANDROID)
+            "?os=android"
+#elif defined(Q_OS_LINUX)
+            "?os=linux"
+#else
+            "?os=other"
+#endif
+#if defined(Q_OS_DARWIN64) || defined(Q_OS_WIN64) || defined(Q_OS_LINUX )
+            "&arch=64"
+#else
+            "&arch=32"
+#endif
+            "&version=%2.%3.%4"
+            "&model=%6"
+          "'>ostc-planner.net/wp/companion"
+          "</a>"
+        "</html>")
+            .arg( tr("Official web site") )
+            .arg(QString::number(MAJOR_VERSION))
+            .arg(QString::number(MINOR_VERSION))
+            .arg(BETA_VERSION ? QString("beta%1").arg(PATCH_VERSION)
+                              : QString("%1")    .arg(PATCH_VERSION))
+            .arg(_op->model())
+    );
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+MainWindow::~MainWindow()
+{
+    delete _ui;
+    delete _op;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool MainWindow::event(QEvent* e)
+{
+    if( ProgressEvent* p = dynamic_cast<ProgressEvent*>(e) )
+    {
+        QProgressBar* w = _ui->progressBar;
+
+        if( p->current > p->maximum && p->maximum > 0)
+        {
+            w->setMaximum(p->maximum);  // Remove throttling mode, if any.
+            w->reset();
+        }
+        else
+        {
+            if( ! w->isEnabled() )
+                w->setEnabled(true);
+            if( w->maximum() != p->maximum )
+                w->setMaximum(p->maximum);  // Start throttling if max==0
+            w->setValue(p->current);
+        }
+        return true;
+    }
+
+    return QMainWindow::event(e);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::changeTypeSlot()
+{
+    QString name;
+
+    //---- Setup a new driver ------------------------------------------------
+    delete _op;
+    _op = 0;
+    switch( _ui->computerType->currentIndex() ) {
+    case 0: name = "ostc2c";
+            _op = new OSTC2cOperations;
+        break;
+    case 1:  name = "hwOS (USB)";
+             _op = new OSTC3Operations;
+        break;
+    case 2: name = "hwOS (Bluetooth)";
+             _op = new OSTC3pOperations;
+        break;
+    case 3: name = "ostc4";
+        _op = new OSTC4Operations;
+        break;
+
+    default:
+        qWarning("Internal error: unknown computer type");
+        return;
+    }
+    LOG_INFO(tr("%1 selected.").arg(_op->model()));
+
+    settings->setValue("Interface/computerType", name);
+    settings->sync();
+
+    // backword compatibility >= translate name if necessary
+    if ( name =="hwOS (Bluetooth)") name = "ostc3p";
+     if( name =="hwOS (USB)") name = "ostc3";
+
+
+    _ui->computerImage->setPixmap( QPixmap(":/Images/" + name + "_160x120.png"));
+
+    updateStatus();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::settingsSlot()
+{
+    Settings* s = new Settings(this, _op);
+    s->exec();
+    delete s;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::connectSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO("Connecting...");
+
+        //---- Already connected ? ----------------------------------------------
+        if( ! _op->description().isEmpty() )
+            _op->disconnect();
+
+        //---- (Re)connect ------------------------------------------------------
+        if( _op->connect() ) {
+
+            if( Settings::autoSetDateTime )
+                dateSlot();
+
+            LOG_INFO("Connected: " + _op->description() );
+        }
+        updateStatus();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::closeSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO("Disconnecting...");
+        if( _op->disconnect() )
+            LOG_INFO("Disconnected.");
+        updateStatus();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::updateStatus()
+{
+    bool ok = _op;
+    _ui->connectButton->setEnabled( ok );
+
+    // ON when supported but NOT connected, OFF once connected.
+    _ui->upgradeButton->setEnabled( ok
+                                && (_op->supported() & HardwareOperations::FIRMWARE)
+                                && !_op->serial().isOpen());
+
+    // Only allow buttons when connected:
+    ok &= _op->serial().isOpen();
+    _ui->dateButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::DATE) );
+    _ui->nameButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::NAME) );
+    _ui->iconButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::ICON) );
+    _ui->signalButton ->setEnabled( ok && (_op->supported() & HardwareOperations::SIGNAL_CHECK) );
+    _ui->closeButton  ->setEnabled( ok );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::dateSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        QDateTime date = QDateTime::currentDateTime();
+
+        LOG_INFO(tr("Settings date & time..."));
+        _op->setDate( date );
+        LOG_INFO( QString("Date set to %1")
+                 .arg(date.toString("yyyy/MM/dd hh:mm:ss")) );
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::nameSlot()
+{
+    Q_ASSERT( _op );
+    try {
+        LOG_INFO(tr("Settings name..."));
+
+        //---- Get old name, and reformat to multi-lines ---------------------
+        QString oldName = _op->description().section(", ", 2);
+        QString oldText;
+        QSize size = _op->nameSize();
+
+        for(int l=0; l<size.height(); ++l) {
+            QString line = oldName.left( size.width() ).leftJustified(size.width());
+            if( line.contains("\n") ) {
+                line = line.section("\n", 0, 0);
+                oldName = oldName.mid(line.length());
+            }
+            else {
+                oldName = oldName.mid(line.length());
+                if( oldName[0] == '\n' )
+                    oldName = oldName.mid(1);
+            }
+            oldText += line + "|\n";
+        }
+
+        //---- Ask user ------------------------------------------------------
+
+        QInputDialog* d = new QInputDialog(this);
+        d->setWindowTitle("Set Computer Name...");
+        d->setInputMode(QInputDialog::TextInput);
+        d->setOptions(QInputDialog::UsePlainTextEditForTextInput);
+        d->setTextValue(oldText);
+
+        QPlainTextEdit* edit = d->findChild<QPlainTextEdit*>();
+        assert(edit);
+        edit->setStyleSheet(
+            "background-color: black;"
+            "color: green;"
+            "font: 14pt 'Courier New';"
+        );
+
+        if( d->exec() != QDialog::Accepted )
+            return;
+
+        QString newText = d->textValue();
+        delete d;
+
+        //---- Reformat to single padded string ------------------------------
+        QStringList lines = newText.split("\n");
+        QString name;
+        for(int l=0; l<size.height(); ++l) {
+            if( l < lines.count() )
+                name += lines[l].leftJustified(size.width(), ' ', true);
+            else
+                name += QString(size.width(), ' ');
+        }
+
+        //---- Send result ---------------------------------------------------
+        _op->setName( name );
+        _op->getIdentity();
+        LOG_INFO( QString("Name set to '%1'")
+            .arg(_op->description().section(',', 2)).replace("\n", "|") );
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::iconSlot()
+{
+    Q_ASSERT( _op );
+    try {
+        LOG_INFO(tr("Settings icons..."));
+
+        QString fileName = QFileDialog::getOpenFileName(this,
+                                                        "Icon File...",
+                                                        QString(),
+                                                        "Images (*.gif *.png *.tif *.tiff *.jpg *.jpeg *.ico *.svg);;"
+                                                        "GIF Image (*.gif);;"
+                                                        "PNG Image (*.png);;"
+                                                        "TIFF Image (*.tif *.tiff);;"
+                                                        "JPEG Image (*.jpg *.jpeg);;"
+                                                        "Icon Image (*.ico);;"
+                                                        "SVG Image (*.svg);;"
+                                                        "Anything (*.*)");
+        if( ! fileName.isEmpty() )
+            _op->setIcons(fileName);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::upgradeSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO(tr("Upgrading firmware..."));
+
+        QString hexFile = QFileDialog::getOpenFileName(0,
+                                                       "Hex File...",
+                                                       Settings::currentPath,
+                                                       _op->firmwareTemplate());
+        if( hexFile.isEmpty() )
+            return;
+
+        Settings::currentPath = QFileInfo(hexFile).absoluteDir().path();
+        Settings::save();
+
+        if( _op )
+            _op->upgradeFW(hexFile);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::statusMessage(const QString& msg)
+{
+    {   // Move cursor to end of document.
+        QTextCursor c = _ui->console->textCursor();
+        c.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
+        _ui->console->setTextCursor(c);
+    }
+    _ui->console->appendHtml(msg);
+    _ui->console->ensureCursorVisible();
+
+    qApp->processEvents(QEventLoop::AllEvents, 50);
+}
+
+void MainWindow::retranslate()
+{
+    _ui->retranslateUi(this);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+void MainWindow::on_signalButton_clicked()
+{
+    Q_ASSERT( _op );
+
+    try {
+            LOG_INFO(tr("Request Bluetooth signal strength..."));
+            _op->getSignal();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+
+void MainWindow::on_editLog_clicked()
+{
+    EditLogDialog* eL = new EditLogDialog(this, _op);
+    eL->exec();
+    delete eL;
+}