view MainWindow.cpp @ 12:ac837fe1d590

Switch implementation for reqex class and added RFCOMM as label for Bluetooth based connection by Linux
author Ideenmodellierer
date Mon, 12 Jan 2026 13:58:41 +0000
parents 21ce6187d32e
children e47e0f59101d
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 "SettingsDialog.h"
#include "editlogdialog.h"
#include "ui_MainWindow.h"

#include <QDateTime>
#include <QFileDialog>
#include <QInputDialog>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QProgressBar>
#include <QSettings>
#include <QString>
#include <QTextCursor>
#include "OSTC2Operations.h"
#include "OSTC2cOperations.h"
#include "OSTC3Operations.h"
#include "OSTC3pOperations.h"
#include "OSTC4Operations.h"
#include "OSTCFrogOperations.h"
#include "OSTCSportOperations.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)));
}

//////////////////////////////////////////////////////////////////////////////

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";
    if (name == "ostc 4/5")
        name = "ostc4";

    _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::CompanionFeatures(HardwareOperations::FIRMWARE))
        && !_op->serial().isOpen());

    // Only allow buttons when connected:
    ok &= _op->serial().isOpen();
    _ui->dateButton->setEnabled(ok && _op->supported().testFlag(HardwareOperations::DATE));
    _ui->nameButton->setEnabled(ok && _op->supported().testFlag(HardwareOperations::NAME));
    _ui->iconButton->setEnabled(ok); // && _op->supported().testFlag(HardwareOperations::ICON));
    _ui->signalButton->setEnabled(ok && _op->supported().testFlag(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 (*.bmp);;"
                                                        "BMP Image (*.bmp);;"
                                                        "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;
}