comparison 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
comparison
equal deleted inserted replaced
0:76ccd6ce50c0 1:0b3630a29ad8
1 //////////////////////////////////////////////////////////////////////////////
2 /// \file MainWindow.cpp
3 /// \brief GUI for OSTC Companion.
4 /// \author JD Gascuel.
5 ///
6 /// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
7 /// $Id$
8 //////////////////////////////////////////////////////////////////////////////
9 //
10 // BSD 2-Clause License:
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions
14 // are met:
15 //
16 // 1. Redistributions of source code must retain the above copyright notice,
17 // this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright notice,
20 // this list of conditions and the following disclaimer in the documentation
21 // and/or other materials provided with the distribution.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33 // THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //////////////////////////////////////////////////////////////////////////////
36
37 #include "MainWindow.h"
38
39 #include "Utils/LogAppender.h"
40 #include "Utils/ProgressEvent.h"
41
42 #include "ui_MainWindow.h"
43 #include "SettingsDialog.h"
44 #include "editLogDialog.h"
45
46 #include <QString>
47 #include <QDateTime>
48 #include <QFileDialog>
49 #include <QInputDialog>
50 #include <QMenu>
51 #include <QMenuBar>
52 #include <QMessageBox>
53 #include <QPlainTextEdit>
54 #include <QProgressBar>
55 #include <QSettings>
56 #include <QTextCursor>
57 #include "OSTCFrogOperations.h"
58 #include "OSTC2cOperations.h"
59 #include "OSTC2Operations.h"
60 #include "OSTCSportOperations.h"
61 #include "OSTC3Operations.h"
62 #include "OSTC3pOperations.h"
63 #include "OSTC4Operations.h"
64 #include "OSTC_CR_Operations.h"
65
66 extern QSettings* settings;
67
68 //////////////////////////////////////////////////////////////////////////////
69
70 class EXPORT LogWindow
71 : public LogAppender
72 {
73 MainWindow* _window;
74
75 //---- The <<printing>> function -----------------------------------------
76 void operator()(const Log &log) override {
77 QString message = log.message;
78
79 message.replace("< ", "&lt; ")
80 .replace(" >", " &gt;");
81 if( ! message.isEmpty() )
82 _window->statusMessage(message);
83 }
84
85 //---- Reimplementing mandatory methds -----------------------------------
86 const char *type() const override {
87 return "File";
88 }
89 Log::Level defaultMinLevel() const override {
90 return Log::LEVEL_INFO;
91 }
92 const char* defaultFormat() const override {
93 return "%m";
94 }
95
96 public:
97 LogWindow(MainWindow* window)
98 : LogAppender(0, NULL),
99 _window(window)
100 {}
101 };
102
103 //////////////////////////////////////////////////////////////////////////////
104
105 MainWindow::MainWindow()
106 : QMainWindow(NULL),
107 _ui(new Ui::MainWindow),
108 _op(0)
109 {
110 // Connect the Log system to this window:
111 new LogWindow(this);
112 // Connect the progress system to this window:
113 ProgressManager::getInstance()->setMainWindow(this);
114
115 _ui->setupUi(this);
116 _ui->progressBar->show();
117
118 _ui->editLog->setVisible(false);
119
120 // Auto-select last model:
121 QString model = settings->value("Interface/computerType").toString();
122 _ui->computerType->setCurrentIndex(0);
123
124 if( model == "ostc2c" )
125 _ui->computerType->setCurrentIndex(0);
126 else if( model == "hwOS (Bluetooth)" )
127 _ui->computerType->setCurrentIndex(1);
128 else if( model == "hwOS (USB)" )
129 _ui->computerType->setCurrentIndex(2);
130 else if( model == "ostc4" )
131 _ui->computerType->setCurrentIndex(3);
132
133 changeTypeSlot();
134
135 #ifdef Q_OS_MAC
136 {
137 QMenuBar *menuBar = new QMenuBar(this);
138 QMenu* help = menuBar->addMenu(tr("&Help"));
139 help->addAction(tr("Preferences..."), this, SLOT(settingsSlot()));
140 }
141 #endif
142
143 setWindowTitle(QString("OSTC Companion v%1.%2 %3")
144 .arg(MAJOR_VERSION)
145 .arg(MINOR_VERSION) // kein sprintf nötig, arg konvertiert automatisch
146 .arg(BETA_VERSION
147 ? QString(" beta %1").arg(PATCH_VERSION)
148 : QString::number(PATCH_VERSION))
149 );
150
151 _ui->companionUrlL->setText( QString("<html>%1 "
152 "<a href='https://ostc-planner.net/wp/companion"
153 #if defined(Q_OS_IOS)
154 "?os=ios"
155 #elif defined(Q_OS_OSX)
156 "?os=mac"
157 #elif defined(Q_OS_WIN)
158 "?os=win"
159 #elif defined(Q_OS_ANDROID)
160 "?os=android"
161 #elif defined(Q_OS_LINUX)
162 "?os=linux"
163 #else
164 "?os=other"
165 #endif
166 #if defined(Q_OS_DARWIN64) || defined(Q_OS_WIN64) || defined(Q_OS_LINUX )
167 "&arch=64"
168 #else
169 "&arch=32"
170 #endif
171 "&version=%2.%3.%4"
172 "&model=%6"
173 "'>ostc-planner.net/wp/companion"
174 "</a>"
175 "</html>")
176 .arg( tr("Official web site") )
177 .arg(QString::number(MAJOR_VERSION))
178 .arg(QString::number(MINOR_VERSION))
179 .arg(BETA_VERSION ? QString("beta%1").arg(PATCH_VERSION)
180 : QString("%1") .arg(PATCH_VERSION))
181 .arg(_op->model())
182 );
183 }
184
185
186 //////////////////////////////////////////////////////////////////////////////
187
188 MainWindow::~MainWindow()
189 {
190 delete _ui;
191 delete _op;
192 }
193
194 //////////////////////////////////////////////////////////////////////////////
195
196 bool MainWindow::event(QEvent* e)
197 {
198 if( ProgressEvent* p = dynamic_cast<ProgressEvent*>(e) )
199 {
200 QProgressBar* w = _ui->progressBar;
201
202 if( p->current > p->maximum && p->maximum > 0)
203 {
204 w->setMaximum(p->maximum); // Remove throttling mode, if any.
205 w->reset();
206 }
207 else
208 {
209 if( ! w->isEnabled() )
210 w->setEnabled(true);
211 if( w->maximum() != p->maximum )
212 w->setMaximum(p->maximum); // Start throttling if max==0
213 w->setValue(p->current);
214 }
215 return true;
216 }
217
218 return QMainWindow::event(e);
219 }
220
221 //////////////////////////////////////////////////////////////////////////////
222
223 void MainWindow::changeTypeSlot()
224 {
225 QString name;
226
227 //---- Setup a new driver ------------------------------------------------
228 delete _op;
229 _op = 0;
230 switch( _ui->computerType->currentIndex() ) {
231 case 0: name = "ostc2c";
232 _op = new OSTC2cOperations;
233 break;
234 case 1: name = "hwOS (USB)";
235 _op = new OSTC3Operations;
236 break;
237 case 2: name = "hwOS (Bluetooth)";
238 _op = new OSTC3pOperations;
239 break;
240 case 3: name = "ostc4";
241 _op = new OSTC4Operations;
242 break;
243
244 default:
245 qWarning("Internal error: unknown computer type");
246 return;
247 }
248 LOG_INFO(tr("%1 selected.").arg(_op->model()));
249
250 settings->setValue("Interface/computerType", name);
251 settings->sync();
252
253 // backword compatibility >= translate name if necessary
254 if ( name =="hwOS (Bluetooth)") name = "ostc3p";
255 if( name =="hwOS (USB)") name = "ostc3";
256
257
258 _ui->computerImage->setPixmap( QPixmap(":/Images/" + name + "_160x120.png"));
259
260 updateStatus();
261 }
262
263 //////////////////////////////////////////////////////////////////////////////
264
265 void MainWindow::settingsSlot()
266 {
267 Settings* s = new Settings(this, _op);
268 s->exec();
269 delete s;
270 }
271
272 //////////////////////////////////////////////////////////////////////////////
273
274 void MainWindow::connectSlot()
275 {
276 Q_ASSERT( _op );
277
278 try {
279 LOG_INFO("Connecting...");
280
281 //---- Already connected ? ----------------------------------------------
282 if( ! _op->description().isEmpty() )
283 _op->disconnect();
284
285 //---- (Re)connect ------------------------------------------------------
286 if( _op->connect() ) {
287
288 if( Settings::autoSetDateTime )
289 dateSlot();
290
291 LOG_INFO("Connected: " + _op->description() );
292 }
293 updateStatus();
294 }
295 catch(const std::exception& e) {
296 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
297 .arg(tr("Error"))
298 .arg(e.what()) );
299 }
300 }
301
302 //////////////////////////////////////////////////////////////////////////////
303
304 void MainWindow::closeSlot()
305 {
306 Q_ASSERT( _op );
307
308 try {
309 LOG_INFO("Disconnecting...");
310 if( _op->disconnect() )
311 LOG_INFO("Disconnected.");
312 updateStatus();
313 }
314 catch(const std::exception& e) {
315 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
316 .arg(tr("Error"))
317 .arg(e.what()) );
318 }
319 }
320
321 //////////////////////////////////////////////////////////////////////////////
322
323 void MainWindow::updateStatus()
324 {
325 bool ok = _op;
326 _ui->connectButton->setEnabled( ok );
327
328 // ON when supported but NOT connected, OFF once connected.
329 _ui->upgradeButton->setEnabled( ok
330 && (_op->supported() & HardwareOperations::FIRMWARE)
331 && !_op->serial().isOpen());
332
333 // Only allow buttons when connected:
334 ok &= _op->serial().isOpen();
335 _ui->dateButton ->setEnabled( ok && (_op->supported() & HardwareOperations::DATE) );
336 _ui->nameButton ->setEnabled( ok && (_op->supported() & HardwareOperations::NAME) );
337 _ui->iconButton ->setEnabled( ok && (_op->supported() & HardwareOperations::ICON) );
338 _ui->signalButton ->setEnabled( ok && (_op->supported() & HardwareOperations::SIGNAL_CHECK) );
339 _ui->closeButton ->setEnabled( ok );
340 }
341
342 //////////////////////////////////////////////////////////////////////////////
343
344 void MainWindow::dateSlot()
345 {
346 Q_ASSERT( _op );
347
348 try {
349 QDateTime date = QDateTime::currentDateTime();
350
351 LOG_INFO(tr("Settings date & time..."));
352 _op->setDate( date );
353 LOG_INFO( QString("Date set to %1")
354 .arg(date.toString("yyyy/MM/dd hh:mm:ss")) );
355 }
356 catch(const std::exception& e) {
357 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
358 .arg(tr("Error"))
359 .arg(e.what()) );
360 }
361 }
362
363
364 //////////////////////////////////////////////////////////////////////////////
365
366 void MainWindow::nameSlot()
367 {
368 Q_ASSERT( _op );
369 try {
370 LOG_INFO(tr("Settings name..."));
371
372 //---- Get old name, and reformat to multi-lines ---------------------
373 QString oldName = _op->description().section(", ", 2);
374 QString oldText;
375 QSize size = _op->nameSize();
376
377 for(int l=0; l<size.height(); ++l) {
378 QString line = oldName.left( size.width() ).leftJustified(size.width());
379 if( line.contains("\n") ) {
380 line = line.section("\n", 0, 0);
381 oldName = oldName.mid(line.length());
382 }
383 else {
384 oldName = oldName.mid(line.length());
385 if( oldName[0] == '\n' )
386 oldName = oldName.mid(1);
387 }
388 oldText += line + "|\n";
389 }
390
391 //---- Ask user ------------------------------------------------------
392
393 QInputDialog* d = new QInputDialog(this);
394 d->setWindowTitle("Set Computer Name...");
395 d->setInputMode(QInputDialog::TextInput);
396 d->setOptions(QInputDialog::UsePlainTextEditForTextInput);
397 d->setTextValue(oldText);
398
399 QPlainTextEdit* edit = d->findChild<QPlainTextEdit*>();
400 assert(edit);
401 edit->setStyleSheet(
402 "background-color: black;"
403 "color: green;"
404 "font: 14pt 'Courier New';"
405 );
406
407 if( d->exec() != QDialog::Accepted )
408 return;
409
410 QString newText = d->textValue();
411 delete d;
412
413 //---- Reformat to single padded string ------------------------------
414 QStringList lines = newText.split("\n");
415 QString name;
416 for(int l=0; l<size.height(); ++l) {
417 if( l < lines.count() )
418 name += lines[l].leftJustified(size.width(), ' ', true);
419 else
420 name += QString(size.width(), ' ');
421 }
422
423 //---- Send result ---------------------------------------------------
424 _op->setName( name );
425 _op->getIdentity();
426 LOG_INFO( QString("Name set to '%1'")
427 .arg(_op->description().section(',', 2)).replace("\n", "|") );
428 }
429 catch(const std::exception& e) {
430 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
431 .arg(tr("Error"))
432 .arg(e.what()) );
433 }
434 }
435
436 //////////////////////////////////////////////////////////////////////////////
437
438 void MainWindow::iconSlot()
439 {
440 Q_ASSERT( _op );
441 try {
442 LOG_INFO(tr("Settings icons..."));
443
444 QString fileName = QFileDialog::getOpenFileName(this,
445 "Icon File...",
446 QString(),
447 "Images (*.gif *.png *.tif *.tiff *.jpg *.jpeg *.ico *.svg);;"
448 "GIF Image (*.gif);;"
449 "PNG Image (*.png);;"
450 "TIFF Image (*.tif *.tiff);;"
451 "JPEG Image (*.jpg *.jpeg);;"
452 "Icon Image (*.ico);;"
453 "SVG Image (*.svg);;"
454 "Anything (*.*)");
455 if( ! fileName.isEmpty() )
456 _op->setIcons(fileName);
457 }
458 catch(const std::exception& e) {
459 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
460 .arg(tr("Error"))
461 .arg(e.what()) );
462 }
463 }
464
465 //////////////////////////////////////////////////////////////////////////////
466
467 void MainWindow::upgradeSlot()
468 {
469 Q_ASSERT( _op );
470
471 try {
472 LOG_INFO(tr("Upgrading firmware..."));
473
474 QString hexFile = QFileDialog::getOpenFileName(0,
475 "Hex File...",
476 Settings::currentPath,
477 _op->firmwareTemplate());
478 if( hexFile.isEmpty() )
479 return;
480
481 Settings::currentPath = QFileInfo(hexFile).absoluteDir().path();
482 Settings::save();
483
484 if( _op )
485 _op->upgradeFW(hexFile);
486 }
487 catch(const std::exception& e) {
488 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
489 .arg(tr("Error"))
490 .arg(e.what()) );
491 }
492 }
493
494 //////////////////////////////////////////////////////////////////////////////
495
496 void MainWindow::statusMessage(const QString& msg)
497 {
498 { // Move cursor to end of document.
499 QTextCursor c = _ui->console->textCursor();
500 c.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
501 _ui->console->setTextCursor(c);
502 }
503 _ui->console->appendHtml(msg);
504 _ui->console->ensureCursorVisible();
505
506 qApp->processEvents(QEventLoop::AllEvents, 50);
507 }
508
509 void MainWindow::retranslate()
510 {
511 _ui->retranslateUi(this);
512 }
513
514 //////////////////////////////////////////////////////////////////////////////
515 void MainWindow::on_signalButton_clicked()
516 {
517 Q_ASSERT( _op );
518
519 try {
520 LOG_INFO(tr("Request Bluetooth signal strength..."));
521 _op->getSignal();
522 }
523 catch(const std::exception& e) {
524 LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
525 .arg(tr("Error"))
526 .arg(e.what()) );
527 }
528 }
529
530
531 void MainWindow::on_editLog_clicked()
532 {
533 EditLogDialog* eL = new EditLogDialog(this, _op);
534 eL->exec();
535 delete eL;
536 }