/*************************************************************************** miniCashConnect Copyright © 2022 christoph holzheuer c.holzheuer@sourceworx.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief MCConnectMainWindow::MCConnectMainWindow * @param parent */ MCConnectMainWindow::MCConnectMainWindow( QWidget* parent ) : MCMainWindowBase{ parent } { setupUi( this ); /// kommt aus der miniCash library setupDefaults(); _billingView->setupDefaults( _dataFileName, &_mainSettings ); /// das ist ein hack, der der Oberklasse 'MCMainwindowbase' den Zugriff ermöglicht. _inputViewProxy = _inputView; _inputView->setupDefaults( this, &_salesModel ); _editView->setupDefaults( this ); statusBar()->setFont( QFont( "Arial", 8 ) ); statusBar()->showMessage( QString( miniCash::copyConnect ) + miniCash::versionConnect ); //_helpViewer = new MCHelpViewer(); //_helpViewer->load( "file:///C:/HandbuchMiniCash.html" ); /// actions connect( _actionSetup, SIGNAL( triggered() ), this, SLOT( onSetup() ) ); connect( _actionSetupNetwork, SIGNAL( triggered() ), this, SLOT( onShowNetworkDialog() ) ); connect( _actionInputTransactions, SIGNAL( triggered() ), this, SLOT( onInputTransactions() ) ); connect( _actionViewTransactions, SIGNAL( triggered() ), this, SLOT( onViewTransactions() ) ); connect( _actionEditTransactions, SIGNAL( triggered() ), this, SLOT( onEditTransactions() ) ); connect( _actionCopySaleData, SIGNAL( triggered() ), this, SLOT( onCopyTransactions() ) ); connect( _actionStartBilling, SIGNAL( triggered() ), this, SLOT( onStartBilling() ) ); connect( _actionExit, SIGNAL( triggered() ), this, SLOT( onExit() ) ); connect( _actionExitConfirmed, SIGNAL( triggered() ), this, SLOT( onExitConfirmed() ) ); connect( _actionHelpAbout, SIGNAL( triggered() ), this, SLOT( onHelpAbout() ) ); connect( _actionHelpContents, SIGNAL( triggered() ), this, SLOT( onHelpManual() ) ); /// senden & empfangen sind asynchron, also schieben wir die in /// eigenen Threads _tcpSender.moveToThread( &_senderThread ); _tcpReceiver.moveToThread( &_receiverThread ); //connect( this, SIGNAL( stopNetwork() ), &_tcpSender, SLOT( onDiscardConnection() ) ); //connect( this, SIGNAL( stopNetwork() ), &_tcpReceiver, SLOT( onDiscardConnection() ) ); connect( this, SIGNAL( startNetwork() ), &_tcpSender, SLOT( onCreateConnection() ) ); connect( this, SIGNAL( startNetwork() ), &_tcpReceiver, SLOT( onCreateConnection() ) ); qRegisterMetaType("miniCash::CState"); qRegisterMetaType("QAbstractSocket::SocketError"); connect( &_tcpSender, SIGNAL( connectionChanged(miniCash::CState) ), this, SLOT( onStateChanged(miniCash::CState) ) ); connect( &_tcpReceiver, SIGNAL( connectionChanged(miniCash::CState) ), this, SLOT( onStateChanged(miniCash::CState) ) ); /// das SIG transactionCreated() stammt aus der Basisklass MCMainWindowBase, nette Möglichkleit, eine /// abstrakte Methode 'sendTransaction() = 0' zu ersetzen. connect( this, SIGNAL( transactionCreated(QString) ), &_tcpSender, SLOT( onSendTransaction(QString) ) ); connect( &_tcpReceiver, SIGNAL( newTransaction(QString) ), this, SLOT( onTransactionReceived(QString) ) ); /// catch network errors connect( &_tcpSender, SIGNAL( errorOccurred(QAbstractSocket::SocketError) ), this, SLOT( onConnectionError(QAbstractSocket::SocketError) ) ); connect( &_tcpReceiver, SIGNAL( acceptError(QAbstractSocket::SocketError) ), this, SLOT( onConnectionError(QAbstractSocket::SocketError) ) ); /// erstmal die Basis ... /// wenn wir das erste mal hier sind, defaults laden. if( !_mainSettings.contains( miniCash::keyReceiverHost ) ) { _mainSettings.setValue( miniCash::keyIsTcpReceiver, miniCash::isTcpReceiver ); _mainSettings.setValue( miniCash::keyIsTcpSender, miniCash::isTcpSender ); _mainSettings.setValue( miniCash::keyReceiverHost, "" ); _mainSettings.setValue( miniCash::keyReceiverPort, miniCash::receiverPort ); } connect( _netWidget, SIGNAL( showNetworkDialog() ), this, SLOT( onShowNetworkDialog() ) ); /// gleich beim Start NetDlg zeigen? //_isSender = _mainSettings.value( miniCash::keyIsTcpSender ).toBool(); //_host = _mainSettings.value( miniCash::keyReceiverHost ).toString(); //if( _isSender && _host.isEmpty() ) /// Ist schon Ok, den DLG immer anzuzeigen _showDlg = true; _isReceiver = _mainSettings.value( miniCash::keyIsTcpReceiver ).toBool(); _sideBar->appendAction( _actionInputTransactions ); if( _isReceiver ) _sideBar->appendAction( _actionViewTransactions ); _sideBar->appendAction( _actionEditTransactions ); _sideBar->appendAction( _actionStartBilling ); _sideBar->setCheckedAction( _actionInputTransactions ); } /** * @brief Destruktor */ MCConnectMainWindow::~MCConnectMainWindow() { _senderThread.exit(); _receiverThread.exit(); if( !_senderThread.wait( 3000 ) ) //Wait until it actually has terminated (max. 3 sec) { _senderThread.terminate(); //Thread didn't exit in time, probably deadlocked, terminate it! _senderThread.wait(); //We have to wait again here! } if( !_receiverThread.wait( 3000 ) ) //Wait until it actually has terminated (max. 3 sec) { _receiverThread.terminate(); //Thread didn't exit in time, probably deadlocked, terminate it! _receiverThread.wait(); //We have to wait again here! } } /** * @brief Überschreibt 'QMainWindow::showEvent', um ggf. einen NetworkSetup Dialog * einzuschmuggleln. * @param event */ void MCConnectMainWindow::showEvent( QShowEvent* event ) { /// 'onShowNetworkDialog' gehört eigentlich in den Konstruktor (falls der Hostname nicht gesetzt ist, /// wenn aber dort der NetworkSetup-Dialog aufgerufen wird, dann erscheint dieser _vor_ dem /// Hauptfenster. Um das zu vermeiden, schmuggeln wir das per 'showEvent' ein. QMainWindow::showEvent( event ); /// guard: der DLG soll nur einmal beim Start erscheinen if( !_showDlg ) return; /// Call slot via queued connection so it's called from the UI thread after this method has returned and the window has been shown QMetaObject::invokeMethod( this, &MCConnectMainWindow::onShowNetworkDialog, Qt::ConnectionType::QueuedConnection ); _showDlg = false; } void MCConnectMainWindow::onStateChanged( miniCash::CState state ) { /// Serverstate schlägt Clientstate, ist deswege numerisch höher, /// Errorstates werden woanders gesetzt if( state > _netWidget->connectionState() ) _netWidget->setConnectionState( state ); } /** * @brief setup dialog anzeigen * * Maske mit den Programmeinstellungen anzeigen: Ziellaufwerk etc. * */ void MCConnectMainWindow::onSetup() { MCSetupDialog( this, &_mainSettings ).exec(); /// alles kann geändert worden sein -> also nochmal los setupDefaults(); } void MCConnectMainWindow::onShowNetworkDialog() { int result = MCNetworkDialog( this, &_mainSettings ).exec(); if( QDialog::Rejected == result ) return; setupNetwork(); } /** * @brief Netzwerksettings initialisieren * * Wird direkt beim Programmstart aufgerufen, setzt die Vorgabewerte * fürs Networking. Das ist eine Ergänzung zu @see setupDefaults in der Basisklasse. */ void MCConnectMainWindow::setupNetwork() { //emit stopNetwork(); _isSender = _mainSettings.value( miniCash::keyIsTcpSender ).toBool(); _isReceiver = _mainSettings.value( miniCash::keyIsTcpReceiver ).toBool(); _host = _mainSettings.value( miniCash::keyReceiverHost ).toString(); _port = _mainSettings.value( miniCash::keyReceiverPort ).toInt(); bool useNetwork = _isSender || _isReceiver; /// gar kein Netz? if( !useNetwork ) return _netWidget->setConnectionState( miniCash::Disabled ); /* /// ... und nochmal Prüfen: Host, default ist ja leer if( _host.isEmpty() ) { int result = MCNetworkDialog( this, &_mainSettings ).exec(); if( QDialog::Rejected == result ) return; } */ /// Netz wird verwendet, verrbinden ... _netWidget->setConnectionState( miniCash::UnConnected ); /// bin ich Server, also Empfänger if( _isReceiver ) { /// (Re-)init server _tcpReceiver.setupConnection( _port ); if( !_receiverThread.isRunning() ) _receiverThread.start(); } /// bin ich Client, also Sender? if( _isSender ) { _tcpSender.setupConnection( _host, _port ); if( !_senderThread.isRunning() ) _senderThread.start(); } emit startNetwork(); } void MCConnectMainWindow::onConnectionError( QAbstractSocket::SocketError socketError ) { qDebug() << "socketError:" << socketError; switch( socketError ) { case QAbstractSocket::RemoteHostClosedError: QMessageBox::information(this, "QTCPServer", "RemoteHostClosedError:"); break; case QAbstractSocket::HostNotFoundError: QMessageBox::information(this, "QTCPServer", "The host was not found. Please check the host name and port settings."); break; case QAbstractSocket::ConnectionRefusedError: QMessageBox::information(this, "QTCPServer", "The connection was refused by the peer. Make sure QTCPServer is running, and check that the host name and port settings are correct."); break; default: QTcpSocket* socket = qobject_cast(sender()); QMessageBox::information(this, "QTCPServer", QString("The following error occurred: %1.").arg(socket->errorString())); break; } } /** * @brief Eingabemaske (wieder) einblenden, in den * Eingabemodus schalten */ void MCConnectMainWindow::onInputTransactions() { _contentWidget->setCurrentWidget( _inputView ); } /** * @brief TransactionView einblenden: Eingehende Transaktionen werden angezeigt */ void MCConnectMainWindow::onViewTransactions() { // Anim?? _contentWidget->setCurrentWidget( _transactionView ); } /** * @brief Transaktionsdaten korrigieren, z.B. Zahlendreher beheben oder Stornos * einbuchen. */ void MCConnectMainWindow::onEditTransactions() { qDebug() << " -- Edit Transactions: " << _dataFilePath; _editView->loadTransactions( _dataFilePath ); _contentWidget->setCurrentWidget( _editView ); } /** * @brief MCConnectMainWindow::onTransactionReceived * @param transaction: Transaktionen als String */ void MCConnectMainWindow::onTransactionReceived( const QString& transaction ) { _transactionView->onTransactionReceived( transaction ); } /** * @brief In den Abrechungsmodus schalten * * Abrechnung starten: Dazu wird das Hauptfenster auf die Abrechungsmaske * umgeschaltet */ void MCConnectMainWindow::onStartBilling() { _contentWidget->setCurrentWidget( _billingView ); } /** * @brief Kurzinfo anzeigen * */ void MCConnectMainWindow::onHelpAbout() { MCCAboutMe().exec(); }