view 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 source

//////////////////////////////////////////////////////////////////////////////
/// \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;
}