From 2ba2bb9ce8694701263eb43d33dd9fc4555e6cc1 Mon Sep 17 00:00:00 2001 From: Ximi1970 Date: Wed, 22 Jan 2020 23:14:46 +0100 Subject: [PATCH] Added system tray icon and number, preferences, debug, --- app/README.references.txt | 1 + app/SysTray-X/SysTray-X.pro | 19 +- app/SysTray-X/SysTray-X.qrc | 1 + app/SysTray-X/debugwidget.cpp | 130 +++++++++ app/SysTray-X/debugwidget.h | 140 +++++++++ app/SysTray-X/debugwidget.ui | 99 +++++++ app/SysTray-X/files/icons/LICENSE | 2 +- app/SysTray-X/files/icons/blank-icon.png | Bin 0 -> 22153 bytes app/SysTray-X/main.cpp | 12 +- app/SysTray-X/mainwindow.cpp | 38 --- app/SysTray-X/mainwindow.h | 60 ---- app/SysTray-X/mainwindow.ui | 67 ----- app/SysTray-X/preferences.cpp | 137 +++++++++ app/SysTray-X/preferences.h | 155 ++++++++++ app/SysTray-X/preferences.ui | 207 ++++++++++++++ app/SysTray-X/preferencesdialog.cpp | 191 +++++++++++++ app/SysTray-X/preferencesdialog.h | 143 ++++++++++ app/SysTray-X/systrayx.cpp | 193 +++++++++++++ app/SysTray-X/systrayx.h | 119 ++++++++ app/SysTray-X/systrayxicon.cpp | 184 ++++++++++++ app/SysTray-X/systrayxicon.h | 132 +++++++++ app/SysTray-X/systrayxlink.cpp | 345 ++++++++++++++++++----- app/SysTray-X/systrayxlink.h | 108 ++++++- webext/background.js | 205 +++++++++++--- webext/css/options.css | 8 + webext/js/options_accounts.js | 8 - webext/js/options_iconform.js | 36 +++ webext/options.html | 42 ++- webext/options.js | 328 +++++++++++++++++---- 29 files changed, 2730 insertions(+), 380 deletions(-) create mode 100644 app/SysTray-X/debugwidget.cpp create mode 100644 app/SysTray-X/debugwidget.h create mode 100644 app/SysTray-X/debugwidget.ui create mode 100644 app/SysTray-X/files/icons/blank-icon.png delete mode 100644 app/SysTray-X/mainwindow.cpp delete mode 100644 app/SysTray-X/mainwindow.h delete mode 100644 app/SysTray-X/mainwindow.ui create mode 100644 app/SysTray-X/preferences.cpp create mode 100644 app/SysTray-X/preferences.h create mode 100644 app/SysTray-X/preferences.ui create mode 100644 app/SysTray-X/preferencesdialog.cpp create mode 100644 app/SysTray-X/preferencesdialog.h create mode 100644 app/SysTray-X/systrayx.cpp create mode 100644 app/SysTray-X/systrayx.h create mode 100644 app/SysTray-X/systrayxicon.cpp create mode 100644 app/SysTray-X/systrayxicon.h create mode 100644 webext/js/options_iconform.js diff --git a/app/README.references.txt b/app/README.references.txt index 6d0d86c..98a57a7 100644 --- a/app/README.references.txt +++ b/app/README.references.txt @@ -1,3 +1,4 @@ Native messaging https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging + diff --git a/app/SysTray-X/SysTray-X.pro b/app/SysTray-X/SysTray-X.pro index 4792009..38ab0f3 100644 --- a/app/SysTray-X/SysTray-X.pro +++ b/app/SysTray-X/SysTray-X.pro @@ -125,15 +125,24 @@ message("Version: "$$VERSION_MAJOR"."$$VERSION_MINOR"."$$VERSION_PATCH) SOURCES += \ main.cpp \ - mainwindow.cpp \ - systrayxlink.cpp + systrayxlink.cpp \ + systrayxicon.cpp \ + systrayx.cpp \ + debugwidget.cpp \ + preferencesdialog.cpp \ + preferences.cpp HEADERS += \ - mainwindow.h \ - systrayxlink.h + systrayxlink.h \ + systrayxicon.h \ + systrayx.h \ + debugwidget.h \ + preferencesdialog.h \ + preferences.h FORMS += \ - mainwindow.ui + debugwidget.ui \ + preferences.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin diff --git a/app/SysTray-X/SysTray-X.qrc b/app/SysTray-X/SysTray-X.qrc index 6fafe49..37b9c8d 100644 --- a/app/SysTray-X/SysTray-X.qrc +++ b/app/SysTray-X/SysTray-X.qrc @@ -1,5 +1,6 @@ files/icons/SysTray-X.png + files/icons/blank-icon.png diff --git a/app/SysTray-X/debugwidget.cpp b/app/SysTray-X/debugwidget.cpp new file mode 100644 index 0000000..836a91e --- /dev/null +++ b/app/SysTray-X/debugwidget.cpp @@ -0,0 +1,130 @@ +#include "debugwidget.h" +#include "ui_debugwidget.h" + +/* + * Local includes + */ +#include "preferences.h" + +/* + * Qt includes + */ + + +/* + * Constructor + */ +DebugWidget::DebugWidget( Preferences *pref, QWidget *parent ) : QWidget( parent ), m_ui( new Ui::DebugWidget ) +{ + m_ui->setupUi( this ); + + /* + * Store the preferences + */ + m_pref = pref; +} + + +/* + * Set the debug message + */ +void DebugWidget::setDebugMessage( QString message ) +{ + m_ui->messageLabel->setText( message ); +} + + +/* + * Set the raw data length + */ +void DebugWidget::setRawDataLength( int length ) +{ + m_ui->rawDataLengthLabel->setText( QString::number( length ) ); +} + + +/* + * Set the raw received message + */ +void DebugWidget::setErrorDataMessage( const QString &message ) +{ + m_ui->rawDataLabel->setText( message ); +} + + +/* + * Set the number of unread mail + */ +void DebugWidget::setUnreadMail( int unread ) +{ + m_ui->unreadMailLabel->setText( QString::number( unread ) ); +} + + +/* + * Set the link error message + */ +void DebugWidget::setError( const QString &error ) +{ + m_ui->errorLabel->setText( error ); +} + + +/* + * Handle a debug state change signal + */ +void DebugWidget::slotDebugChange() +{ + this->setVisible( m_pref->getDebug() ); +} + + +/* + * Handle a debug message signal + */ +void DebugWidget::slotDebugMessage( QString message ) +{ + setDebugMessage( message ); +} + + +/* + * Handle received message length + */ +void DebugWidget::slotReceivedMessageLength( qint32 msglen ) +{ + setRawDataLength( msglen ); +} + + +/* + * Display received message + */ +void DebugWidget::slotReceivedMessage( QByteArray message ) +{ + setErrorDataMessage( QString( message ) ); + + /* + * Reply + */ +// QByteArray reply = QString( "\"Hallo other world!\"" ).toUtf8(); +// emit signalWriteMessage( reply ); +} + + +/* + * Handle unread mail signal + */ +void DebugWidget::slotUnreadMail( int unread_mail ) +{ + setUnreadMail( unread_mail ); +} + + +/* + * Handle a receive error + */ +void DebugWidget::slotReceiveError( QString error ) +{ + setError( error ); +} diff --git a/app/SysTray-X/debugwidget.h b/app/SysTray-X/debugwidget.h new file mode 100644 index 0000000..a4aae96 --- /dev/null +++ b/app/SysTray-X/debugwidget.h @@ -0,0 +1,140 @@ +#ifndef DEBUGWIDGET_H +#define DEBUGWIDGET_H + +/* + * Local includes + */ + +/* + * Qt includes + */ +#include + +/* + * Predefines + */ +class Preferences; + +/* + * Namespace + */ +namespace Ui { + class DebugWidget; +} + +/** + * @brief The DebugWidget class + */ +class DebugWidget : public QWidget +{ + Q_OBJECT + + public: + + /** + * @brief DebugWidget. Constructor. + * + * @param parent My parent. + */ + explicit DebugWidget( Preferences *pref, QWidget *parent = nullptr ); + + /** + * @brief setDebugMessage. Display a debug message. + * + * @param message The message to display. + */ + void setDebugMessage( QString message ); + + /** + * @brief setRawDataLength. Display the error data length. + * + * @param length The raw data length. + */ + void setRawDataLength( int length ); + + /** + * @brief setRswDataMessage. Display the raw data. + * + * @param message The raw data messsage. + */ + void setErrorDataMessage( const QString &message ); + + /** + * @brief setUnreadMail. Set the number of unread mails. + * + * @param unread The number of unread mails. + */ + void setUnreadMail( int unread ); + + /** + * @brief setError. Set the error message. + * + * @param error The error message. + */ + void setError( const QString &error ); + + signals: + + /** + * @brief signalWriteMessage + * + * @param message + */ + void signalWriteMessage( QByteArray message ); + + public slots: + + /** + * @brief slotDebugChange. The debug state changed. + */ + void slotDebugChange(); + + /** + * @brief slotReceivedMessageLength + * + * @param msglen + */ + void slotDebugMessage( QString message ); + + /** + * @brief slotReceivedMessageLength + * + * @param msglen + */ + void slotReceivedMessageLength( qint32 msglen ); + + /** + * @brief slotReceivedMessage + * + * @param message + */ + void slotReceivedMessage( QByteArray message ); + + /** + * @brief slotSetUnreadMail. Slot for handling unread mail signals. + * + * @param unread_mail The number of unread mails. + */ + void slotUnreadMail( int unread_mail ); + + /** + * @brief slotReceiveError. Handle receive error signal. + * + * @param error Error message. + */ + void slotReceiveError( QString error ); + + private: + + /** + * @brief m_ui. Pointer to the widget. + */ + Ui::DebugWidget *m_ui; + + /** + * @brief m_pref. Pointer to ther preferences. + */ + Preferences *m_pref; +}; + +#endif // DEBUGWIDGET_H diff --git a/app/SysTray-X/debugwidget.ui b/app/SysTray-X/debugwidget.ui new file mode 100644 index 0000000..2cd02d9 --- /dev/null +++ b/app/SysTray-X/debugwidget.ui @@ -0,0 +1,99 @@ + + + DebugWidget + + + + 0 + 0 + 400 + 300 + + + + SysTray-X Debug + + + + + 10 + 10 + 381 + 281 + + + + + + + 0 + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Unread mail: + + + + + + + + + + + + + + + + diff --git a/app/SysTray-X/files/icons/LICENSE b/app/SysTray-X/files/icons/LICENSE index c4e7bdc..b47ed98 100644 --- a/app/SysTray-X/files/icons/LICENSE +++ b/app/SysTray-X/files/icons/LICENSE @@ -1 +1 @@ -The icon used here is taken from the "Miscellany Web icons" set by Maria & Guillem (https://www.iconfinder.com/andromina), and is used under the Creative Commons (Attribution 3.0 Unported) license. +The SysTray-X / mail icon used here is taken from the "Miscellany Web icons" set by Maria & Guillem (https://www.iconfinder.com/andromina), and is used under the Creative Commons (Attribution 3.0 Unported) license. diff --git a/app/SysTray-X/files/icons/blank-icon.png b/app/SysTray-X/files/icons/blank-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..be5c8fa9a35e15fd4eb64c1896b325687fa2883d GIT binary patch literal 22153 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 literal 0 HcmV?d00001 diff --git a/app/SysTray-X/main.cpp b/app/SysTray-X/main.cpp index b48f94e..2c269b8 100644 --- a/app/SysTray-X/main.cpp +++ b/app/SysTray-X/main.cpp @@ -1,11 +1,17 @@ -#include "mainwindow.h" +/* + * Local includes + */ +#include "systrayx.h" + +/* + * Qt includes + */ #include int main(int argc, char *argv[]) { QApplication a(argc, argv); - MainWindow w; - w.show(); + SysTrayX systrayx; return a.exec(); } diff --git a/app/SysTray-X/mainwindow.cpp b/app/SysTray-X/mainwindow.cpp deleted file mode 100644 index b4e8bc6..0000000 --- a/app/SysTray-X/mainwindow.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - connect( &m_systray_x_link, &SysTrayXLink::signalReceivedMessageLength, this, &MainWindow::slotReceivedMessageLength ); - connect( &m_systray_x_link, &SysTrayXLink::signalReceivedMessage, this, &MainWindow::slotReceivedMessage ); - - connect( this, &MainWindow::signalWriteMessage, &m_systray_x_link, &SysTrayXLink::slotLinkWrite ); -} - - -MainWindow::~MainWindow() -{ - delete ui; -} - - -void MainWindow::slotReceivedMessageLength( qint32 msglen ) -{ - ui->label_length->setText(QString::number( msglen ) ); -} - - -void MainWindow::slotReceivedMessage( QByteArray message ) -{ - ui->label_message->setText( QString::fromStdString( message.toStdString() ) ); - - /* - * Reply - */ - QByteArray reply = QString( "\"Hallo other world!\"" ).toUtf8(); - emit signalWriteMessage( reply ); -} diff --git a/app/SysTray-X/mainwindow.h b/app/SysTray-X/mainwindow.h deleted file mode 100644 index 6dd3ed0..0000000 --- a/app/SysTray-X/mainwindow.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -/* - * Local includes - */ -#include "systrayxlink.h" - -/* - * Qt includes - */ -#include -#include - - - -namespace Ui { - class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - - public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - - private slots: - - /** - * @brief slotReceivedMessageLength - * - * @param msglen - */ - void slotReceivedMessageLength( qint32 msglen ); - - /** - * @brief slotReceivedMessage - * - * @param message - */ - void slotReceivedMessage( QByteArray message ); - - signals: - - /** - * @brief signalWriteMessage - * - * @param message - */ - void signalWriteMessage( QByteArray message ); - - private: - Ui::MainWindow *ui; - - SysTrayXLink m_systray_x_link; -}; - -#endif // MAINWINDOW_H diff --git a/app/SysTray-X/mainwindow.ui b/app/SysTray-X/mainwindow.ui deleted file mode 100644 index 148e884..0000000 --- a/app/SysTray-X/mainwindow.ui +++ /dev/null @@ -1,67 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 400 - 300 - - - - MainWindow - - - - - - 50 - 10 - 271 - 51 - - - - Data here - - - - - - 50 - 80 - 271 - 51 - - - - Data here - - - - - - - 0 - 0 - 400 - 30 - - - - - - TopToolBarArea - - - false - - - - - - - - diff --git a/app/SysTray-X/preferences.cpp b/app/SysTray-X/preferences.cpp new file mode 100644 index 0000000..0f52a96 --- /dev/null +++ b/app/SysTray-X/preferences.cpp @@ -0,0 +1,137 @@ +#include "preferences.h" + + +/** + * @brief Preferences. Constructor. + */ +Preferences::Preferences( QObject *parent ) : QObject( parent ) +{ + /* + * Initialize + */ + m_app_pref_changed = false; + + m_icon_type = PREF_BLANK_ICON; + m_icon_mime = "image/png"; + m_icon_data = QByteArray(); + + m_debug = false; +} + + +/* + * Get the icon type. + */ +Preferences::IconType Preferences::getIconType() const +{ + return m_icon_type; +} + + +/* + * Get the icon mime. + */ +bool Preferences::getAppPrefChanged() const +{ + return m_app_pref_changed; +} + + +/* + * Control the sending of preferences changes to the add-on + */ +void Preferences::setAppPrefChanged( bool state ) +{ + if( m_app_pref_changed != state ) + { + m_app_pref_changed = state; + } +} + + +/* + * Set the icon type. + */ +void Preferences::setIconType( IconType icon_type ) +{ + if( m_icon_type != icon_type) + { + m_icon_type = icon_type; + + /* + * Tell the world the new preference + */ + emit signalIconTypeChange(); + } +} + + +/* + * Get the icon mime. + */ +const QString& Preferences::getIconMime() const +{ + return m_icon_mime; +} + + +/* + * Set the icon mime. + */ +void Preferences::setIconMime( const QString& icon_mime ) +{ + m_icon_mime = icon_mime; +} + + +/* + * Get the icon data. + */ +const QByteArray& Preferences::getIconData() const +{ + return m_icon_data; +} + + +/* + * Set the icon data. + */ +void Preferences::setIconData( const QByteArray& icon_data ) +{ + + if( m_icon_data != icon_data ) + { + m_icon_data = icon_data; + + /* + * Tell the world the new preference + */ + emit signalIconDataChange(); + } +} + + +/* + * Get the icon data. + */ +bool Preferences::getDebug() const +{ + return m_debug; +} + + +/* + * Set the icon data. + */ +void Preferences::setDebug( bool state ) +{ + if( m_debug != state ) + { + m_debug = state; + + /* + * Tell the world the new preference + */ + emit signalDebugChange(); + } +} diff --git a/app/SysTray-X/preferences.h b/app/SysTray-X/preferences.h new file mode 100644 index 0000000..c7460f9 --- /dev/null +++ b/app/SysTray-X/preferences.h @@ -0,0 +1,155 @@ +#ifndef PREFERENCES_H +#define PREFERENCES_H + +/* + * Local includes + */ + +/* + * Qt includes + */ +#include +#include +#include + +/** + * @brief The Preferences class. Class to hold the preferences. + */ +class Preferences : public QObject +{ + Q_OBJECT + + public: + + /* + * Icon types + */ + enum IconType { + PREF_BLANK_ICON = 0, + PREF_NEWMAIL_ICON, + PREF_CUSTOM_ICON + }; + + public: + + /** + * @brief Preferences. Constructor. + */ + Preferences( QObject *parent = nullptr ); + + /** + * @brief getAppPrefChanged. Control for sending changes to the add-on. + * + * @return The state + */ + bool getAppPrefChanged() const; + + /** + * @brief setAppPrefChanged. Control for sending changes to the add-on. + * + * @param state The state + */ + void setAppPrefChanged( bool state ); + + /** + * @brief getIconType. Get the icon type. + * + * @return The icon type. + */ + IconType getIconType() const; + + /** + * @brief setIconType. Set the icon type. + * + * @param The icon type. + */ + void setIconType( IconType icon_type ); + + /** + * @brief getIconMime. Get the icon mime. + * + * @return The icon mime. + */ + const QString& getIconMime() const; + + /** + * @brief setIconMime. Set the icon mime. + * + * @param The icon mime. + */ + void setIconMime( const QString& icon_mime ); + + /** + * @brief getIconData. Get the icon data. + * + * @return The icon data. + */ + const QByteArray& getIconData() const; + + /** + * @brief setIconData. Set the icon data. + * + * @param The icon data. + */ + void setIconData( const QByteArray& icon_data ); + + /** + * @brief getDebug. Get the debug windows state. + * + * @return The state. + */ + bool getDebug() const; + + /** + * @brief setDebug. Set the debug windows state. + * + * @param The state. + */ + void setDebug( bool state ); + + signals: + + /** + * @brief signalIconTypeChange. Signal a icon type change. + */ + void signalIconTypeChange(); + + /** + * @brief signalIconDataChange. Signal a icon data change. + */ + void signalIconDataChange(); + + /** + * @brief signalDebugChange. Signal a debug state change. + */ + void signalDebugChange(); + + private: + + /** + * @brief m_app_pref_changed. Control for sending changes to the add-on. + */ + bool m_app_pref_changed; + + /** + * @brief m_icon_type. Selected icon type. + */ + IconType m_icon_type; + + /** + * @brief m_icon_mime. Selected icon mime. + */ + QString m_icon_mime; + + /** + * @brief m_icon_data. Binary data icon image. + */ + QByteArray m_icon_data; + + /** + * @brief m_debug. Display debug window. + */ + bool m_debug; +}; + +#endif // PREFERENCES_H diff --git a/app/SysTray-X/preferences.ui b/app/SysTray-X/preferences.ui new file mode 100644 index 0000000..832bb1c --- /dev/null +++ b/app/SysTray-X/preferences.ui @@ -0,0 +1,207 @@ + + + PreferencesDialog + + + + 0 + 0 + 553 + 544 + + + + SysTray-X Preferences + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Mail notification icon + + + + + + + + QLayout::SetFixedSize + + + + + Blank icon + + + true + + + iconTypeGroup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetFixedSize + + + + + New mail icon + + + iconTypeGroup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QLayout::SetMaximumSize + + + + + Custom icon + + + iconTypeGroup + + + + + + + + + + + + + + Choose + + + false + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Display debug window + + + + + + + + + buttonBox + rejected() + PreferencesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + diff --git a/app/SysTray-X/preferencesdialog.cpp b/app/SysTray-X/preferencesdialog.cpp new file mode 100644 index 0000000..03d9b4f --- /dev/null +++ b/app/SysTray-X/preferencesdialog.cpp @@ -0,0 +1,191 @@ +#include "preferencesdialog.h" +#include "ui_preferences.h" + +/* + * Local includes + */ +#include "systrayxlink.h" + +/* + * Qt includes + */ +#include +#include +#include +#include +#include + +/* + * Constructor + */ +PreferencesDialog::PreferencesDialog( SysTrayXLink *link, Preferences *pref, QWidget *parent ) : QDialog( parent ), m_ui( new Ui::PreferencesDialog ) +{ + m_ui->setupUi( this ); + + /* + * Store link adn preferences + */ + m_link = link; + m_pref = pref; + + /* + * Set button Ids + */ + m_ui->iconTypeGroup->setId( m_ui->blankRadioButton, Preferences::PREF_BLANK_ICON ); + m_ui->iconTypeGroup->setId( m_ui->newMailButton, Preferences::PREF_NEWMAIL_ICON ); + m_ui->iconTypeGroup->setId( m_ui->customRadioButton, Preferences::PREF_CUSTOM_ICON ); + + /* + * Set defaults + */ + m_tmp_icon_data = QByteArray(); + m_tmp_icon_mime = QString(); + + /* + * Signals and slots + */ + connect( m_ui->chooseCustomButton, &QPushButton::clicked, this, &PreferencesDialog::slotFileSelect ); + connect( m_ui->buttonBox, &QDialogButtonBox::accepted, this, &PreferencesDialog::slotAccept ); +} + + +/* + * Set the debug state + */ +void PreferencesDialog::setDebug( bool state ) +{ + m_ui->debugWindowCheckBox->setChecked( state ); +} + + +/* + * Set the icon type + */ +void PreferencesDialog::setIconType( Preferences::IconType icon_type ) +{ + ( m_ui->iconTypeGroup->button( icon_type ) )->setChecked( true ); +} + +/* + * Set the icon + */ +void PreferencesDialog::setIcon( const QString& icon_mime, const QByteArray& icon ) +{ + /* + * Store the new icon + */ + m_tmp_icon_mime = icon_mime; + m_tmp_icon_data = icon; + + /* + * Display the new icon + */ + setIcon(); +} + + +/* + * Set the icon + */ +void PreferencesDialog::setIcon() +{ + /* + * Convert data to pixmap + */ + QPixmap pixmap; + pixmap.loadFromData( m_tmp_icon_data ); + + /* + * Display the icon + */ + m_ui->imageLabel->setPixmap( pixmap.scaledToHeight( m_ui->chooseCustomButton->size().height() ) ); +} + + +/* + * Handle the accept signal + */ +void PreferencesDialog::slotAccept() +{ + /* + * Settings changed by app + */ + m_pref->setAppPrefChanged( true ); + + /* + * Get all the selected values and store them in the preferences + */ + m_pref->setIconType( static_cast< Preferences::IconType >( m_ui->iconTypeGroup->checkedId() ) ); + m_pref->setIconMime( m_tmp_icon_mime ); + m_pref->setIconData( m_tmp_icon_data ); + + m_pref->setDebug( m_ui->debugWindowCheckBox->isChecked() ); + + /* + * Settings changed by app + */ + m_pref->setAppPrefChanged( false ); + + /* + * Tell the base + */ + QDialog::accept(); +} + + +/* + * Handle the choose button + */ +void PreferencesDialog::slotFileSelect() +{ + QFileDialog file_dialog( this, tr( "Open Image" ), "", tr( "Image Files (*.png *.jpg *.bmp)" ) ); + + if( file_dialog.exec() ) + { + QFile file( file_dialog.selectedFiles()[ 0 ] ); + file.open( QIODevice::ReadOnly ); + m_tmp_icon_data = file.readAll(); + file.close(); + + QMimeType type = QMimeDatabase().mimeTypeForData( m_tmp_icon_data ); + m_tmp_icon_mime = type.name(); + + /* + * Display the icon + */ + setIcon(); + } +} + + +/* + * Handle the debug change signal + */ +void PreferencesDialog::slotDebugChange() +{ + setDebug( m_pref->getDebug() ); +} + + +/* + * Handle the icon type change signal + */ +void PreferencesDialog::slotIconTypeChange() +{ + setIconType( m_pref->getIconType() ); +} + + +/* + * Handle the icon data change signal + */ +void PreferencesDialog::slotIconDataChange() +{ + m_tmp_icon_mime = m_pref->getIconMime(); + m_tmp_icon_data = m_pref->getIconData(); + + /* + * Display the icon + */ + setIcon(); +} diff --git a/app/SysTray-X/preferencesdialog.h b/app/SysTray-X/preferencesdialog.h new file mode 100644 index 0000000..1a90513 --- /dev/null +++ b/app/SysTray-X/preferencesdialog.h @@ -0,0 +1,143 @@ +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +/* + * Local includes + */ +#include "preferences.h" + +/* + * Qt includes + */ +#include +#include + +/* + * Predefines + */ +class SysTrayXLink; + +/* + * Namespace + */ +namespace Ui { + class PreferencesDialog; +} + + +/** + * @brief The PreferencesDialog class. Handles the preferences. + */ +class PreferencesDialog : public QDialog +{ + Q_OBJECT + + public: + + /** + * @brief PreferencesDialog + */ + PreferencesDialog( SysTrayXLink *link, Preferences *pref, QWidget *parent = nullptr ); + + private: + + /** + * @brief setDebug. Set the debug state. + * + * @param state The state. + */ + void setDebug( bool state ); + + /** + * @brief setIconType. Set the icon type. + * + * @param icon_type The icon type. + */ + void setIconType( Preferences::IconType icon_type ); + + /** + * @brief setIcon. Set the icon. + * + * @param icon The icon mime. + * @param icon The icon data. + */ + void setIcon( const QString& icon_mime, const QByteArray& icon ); + + /** + * @brief setIcon. Set the icon. + */ + void setIcon(); + + signals: + + /** + * @brief signalDebugMessage. Signal a debug message. + * + * @param message The message. + */ + void signalDebugMessage( QString message ); + + /** + * @brief signalUpdateSysTray. Signal to update the system tray icon. + */ + void signalUpdateSysTrayIcon(); + + public slots: + + /** + * @brief slotDebugChange. Slot for handling debug change signals. + */ + void slotDebugChange(); + + /** + * @brief slotIconTypeChange. Slot for handling icon type change signals. + */ + void slotIconTypeChange(); + + /** + * @brief slotIconDataChange. Slot for handling icon data change signals. + */ + void slotIconDataChange(); + + private slots: + + /** + * @brief slotAccept. Store the preferences on the accept signal. + */ + void slotAccept(); + + /** + * @brief slotFileSelect. Handle the choose custom button click. + */ + void slotFileSelect(); + + private: + + /** + * @brief m_ui. Pointer to the dialog. + */ + Ui::PreferencesDialog *m_ui; + + /** + * @brief m_link. Pointer to the link. + */ + SysTrayXLink *m_link; + + /** + * @brief m_pref. Pointer to the preferences storage. + */ + Preferences *m_pref; + + /** + * @brief m_tmp_icon_mime. Temporary storage for icon mime. + */ + QString m_tmp_icon_mime; + + /** + * @brief m_tmp_icon_data. Temporary storage for icon data. + */ + QByteArray m_tmp_icon_data; + +}; + +#endif // PREFERENCESDIALOG_H diff --git a/app/SysTray-X/systrayx.cpp b/app/SysTray-X/systrayx.cpp new file mode 100644 index 0000000..a3a4261 --- /dev/null +++ b/app/SysTray-X/systrayx.cpp @@ -0,0 +1,193 @@ +#include "systrayx.h" + +/* + * Local includes + */ +#include "debugwidget.h" +#include "preferencesdialog.h" +#include "systrayxlink.h" +#include "systrayxicon.h" + +/* + * Qt includes + */ +#include +#include + +/* + * Constants + */ +const QString SysTrayX::JSON_PREF_REQUEST = "{\"preferences\":{}}"; + + +/* + * Constructor + */ +SysTrayX::SysTrayX( QObject *parent ) : QObject( parent ) +{ + /* + * Setup preferences storage + */ + m_preferences = new Preferences(); + + /* + * Setup the link + */ + m_link = new SysTrayXLink( m_preferences ); + + /* + * Setup preferences dialog + */ + m_pref_dialog = new PreferencesDialog( m_link, m_preferences ); + + /* + * Setup tray icon + */ + createTrayIcon(); + m_tray_icon->show(); + + /* + * Setup debug window + */ + m_debug = new DebugWidget( m_preferences ); + if( m_preferences->getDebug() ) { + m_debug->show(); + } + +// connect( m_trayIcon, &QSystemTrayIcon::messageClicked, this, &SysTrayX::messageClicked); + connect( m_tray_icon, &QSystemTrayIcon::activated, this, &SysTrayX::iconActivated); + + /* + * Connect debug link signals + */ + connect( m_link, &SysTrayXLink::signalReceivedMessageLength, m_debug, &DebugWidget::slotReceivedMessageLength ); + connect( m_link, &SysTrayXLink::signalReceivedMessage, m_debug, &DebugWidget::slotReceivedMessage ); + + connect( m_link, &SysTrayXLink::signalUnreadMail, m_debug, &DebugWidget::slotUnreadMail ); + + connect( m_link, &SysTrayXLink::signalLinkReceiveError, m_debug, &DebugWidget::slotReceiveError ); + + connect( m_debug, &DebugWidget::signalWriteMessage, m_link, &SysTrayXLink::slotLinkWrite ); + + connect( m_pref_dialog, &PreferencesDialog::signalDebugMessage, m_debug, &DebugWidget::slotDebugMessage ); + connect( m_tray_icon, &SysTrayXIcon::signalDebugMessage, m_debug, &DebugWidget::slotDebugMessage ); + connect( m_link, &SysTrayXLink::signalDebugMessage, m_debug, &DebugWidget::slotDebugMessage ); + + + /* + * Connect preferences signals + */ + connect( m_preferences, &Preferences::signalIconTypeChange, m_tray_icon, &SysTrayXIcon::slotIconTypeChange ); + connect( m_preferences, &Preferences::signalIconDataChange, m_tray_icon, &SysTrayXIcon::slotIconDataChange ); + + connect( m_preferences, &Preferences::signalIconTypeChange, m_pref_dialog, &PreferencesDialog::slotIconTypeChange ); + connect( m_preferences, &Preferences::signalIconDataChange, m_pref_dialog, &PreferencesDialog::slotIconDataChange ); + connect( m_preferences, &Preferences::signalDebugChange, m_pref_dialog, &PreferencesDialog::slotDebugChange ); + + connect( m_preferences, &Preferences::signalIconTypeChange, m_link, &SysTrayXLink::slotIconTypeChange ); + connect( m_preferences, &Preferences::signalIconDataChange, m_link, &SysTrayXLink::slotIconDataChange ); + connect( m_preferences, &Preferences::signalDebugChange, m_link, &SysTrayXLink::slotDebugChange ); + + connect( m_preferences, &Preferences::signalDebugChange, m_debug, &DebugWidget::slotDebugChange ); + + /* + * Connect link signals + */ + connect( m_link, &SysTrayXLink::signalUnreadMail, m_tray_icon, &SysTrayXIcon::slotSetUnreadMail ); + + /* + * Request preferences from add-on + */ + getPreferences(); +} + + +/* + * Send a preferences request + */ +void SysTrayX::getPreferences() +{ + /* + * Request preferences from add-on + */ + QByteArray request = QString( SysTrayX::JSON_PREF_REQUEST ).toUtf8(); + emit signalWriteMessage( request ); +} + + +/* + * Handle a click on the system tray icon + */ +void SysTrayX::iconActivated( QSystemTrayIcon::ActivationReason reason ) +{ + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::MiddleClick: + break; + default: + ; + } +} + + +/* + * Create the actions for the system tray icon menu + */ +void SysTrayX::createActions() +{ +/* + m_minimizeAction = new QAction(tr("Mi&nimize"), this); + connect( m_minimizeAction, &QAction::triggered, this, &QWidget::hide ); + + m_maximizeAction = new QAction(tr("Ma&ximize"), this); + connect( m_maximizeAction, &QAction::triggered, this, &QWidget::showMaximized ); + + m_restoreAction = new QAction(tr("&Restore"), this); + connect( m_restoreAction, &QAction::triggered, this, &QWidget::showNormal ); +*/ + + m_pref_action = new QAction(tr("&Preferences"), this); + connect( m_pref_action, &QAction::triggered, m_pref_dialog, &PreferencesDialog::showNormal ); + + m_quit_action = new QAction(tr("&Quit"), this); + connect( m_quit_action, &QAction::triggered, qApp, &QCoreApplication::quit ); + +} + + +/* + * Create the system tray icon + */ +void SysTrayX::createTrayIcon() +{ + /* + * Setup menu actions + */ + createActions(); + + /* + * Setup menu + */ + m_tray_icon_menu = new QMenu(); +// m_trayIconMenu->addAction( m_minimizeAction ); +// m_trayIconMenu->addAction( m_maximizeAction ); +// m_trayIconMenu->addAction( m_restoreAction ); + + m_tray_icon_menu->addAction( m_pref_action ); + m_tray_icon_menu->addSeparator(); + m_tray_icon_menu->addAction( m_quit_action ); + + /* + * Create system tray icon + */ + m_tray_icon = new SysTrayXIcon( m_link, m_preferences ); + m_tray_icon->setContextMenu( m_tray_icon_menu ); + + /* + * Set icon + */ + m_tray_icon->setIconMime( m_preferences->getIconMime() ); + m_tray_icon->setIconData( m_preferences->getIconData() ); + m_tray_icon->setIconType( m_preferences->getIconType() ); +} diff --git a/app/SysTray-X/systrayx.h b/app/SysTray-X/systrayx.h new file mode 100644 index 0000000..849a0a2 --- /dev/null +++ b/app/SysTray-X/systrayx.h @@ -0,0 +1,119 @@ +#ifndef SYSTRAYX_H +#define SYSTRAYX_H + +/* + * Local includes + */ +#include "ui_debugwidget.h" +#include "preferences.h" + +/* + * Qt includes + */ +#include +#include + +/* + * Predefines + */ +class QAction; + +class DebugWidget; +class PreferencesDialog; +class SysTrayXIcon; +class SysTrayXLink; + +/** + * @brief The SysTrayX class + */ +class SysTrayX : public QObject +{ + Q_OBJECT + + public: + + static const QString JSON_PREF_REQUEST; + + public: + + /** + * @brief SysTrayX. Constructor. + * + * @param parent My parent. + */ + explicit SysTrayX( QObject *parent = nullptr ); + + private: + + /** + * @brief SysTrayX::getPreferences + */ + void getPreferences(); + + /** + * @brief iconActivated + * @param reason + */ + void iconActivated( QSystemTrayIcon::ActivationReason reason ); + + /** + * @brief createTrayIcon. Create the system tray icon. + */ + void createTrayIcon(); + + /** + * @brief createActions. Create the menu actions. + */ + void createActions(); + + signals: + + /** + * @brief signalWriteMessage + * + * @param message + */ + void signalWriteMessage( QByteArray message ); + + private slots: + + private: + + /** + * @brief m_preferences. Pointer to the preferences storage. + */ + Preferences *m_preferences; + + /** + * @brief m_debug + */ + DebugWidget *m_debug; + + /** + * @brief m_link. Pointer to the link object. + */ + SysTrayXLink *m_link; + + /** + * @brief m_pref_dialog. Pointer to the preferences dialog. + */ + PreferencesDialog *m_pref_dialog; + + /** + * @brief m_tray_icon. Pointer to the system tray icon. + */ + SysTrayXIcon *m_tray_icon; + + /** + * @brief m_tray_icon_menu. Pointer to the tray icon menu. + */ + QMenu *m_tray_icon_menu; + + /** + * @brief m_xxxx_action. Pointer to the menu actions. + */ + QAction *m_pref_action; + QAction *m_quit_action; +}; + +#endif // SYSTRAYX_H diff --git a/app/SysTray-X/systrayxicon.cpp b/app/SysTray-X/systrayxicon.cpp new file mode 100644 index 0000000..b3889bc --- /dev/null +++ b/app/SysTray-X/systrayxicon.cpp @@ -0,0 +1,184 @@ +#include "systrayxicon.h" + +/* + * Local includes + */ +#include "preferences.h" + +/* + * System includes + */ +#include "systrayxlink.h" + +/* + * Qt includes + */ +#include + + +/* + * Constructor + */ +SysTrayXIcon::SysTrayXIcon( SysTrayXLink *link, Preferences *pref, QObject *parent ) : QSystemTrayIcon( QIcon(), parent ) +{ + /* + * Initialize + */ + m_link = link; + m_pref = pref; + + m_unread_mail = 0; +} + + +/* + * Set the icon type + */ +void SysTrayXIcon::setIconType( Preferences::IconType icon_type ) +{ + if( icon_type != m_icon_type ) + { + /* + * Store the new value + */ + m_icon_type = icon_type; + + /* + * Render and set a new icon in the tray + */ + renderIcon(); + } +} + + +/* + * Set the icon mime + */ +void SysTrayXIcon::setIconMime( const QString& icon_mime ) +{ + if( m_icon_mime != icon_mime ) + { + /* + * Store the new value + */ + m_icon_mime = icon_mime; + } +} + + +/* + * Set the icon type + */ +void SysTrayXIcon::setIconData( const QByteArray& icon_data ) +{ + if( m_icon_data != icon_data ) + { + /* + * Store the new value + */ + m_icon_data = icon_data; + + /* + * Render and set a new icon in the tray + */ + renderIcon(); + } +} + + +/* + * Set the number of unread mails + */ +void SysTrayXIcon::setUnreadMail( int unread_mail ) +{ + if( unread_mail != m_unread_mail ) { + + /* + * Store the new value + */ + m_unread_mail = unread_mail; + + /* + * Render and set a new icon in the tray + */ + renderIcon(); + } +} + + +/* + * Set and render the icon in the system tray + */ +void SysTrayXIcon::renderIcon() +{ + QPixmap pixmap; + + switch( m_icon_type ) + { + case Preferences::PREF_BLANK_ICON: + case Preferences::PREF_NEWMAIL_ICON: + { + pixmap = QPixmap( ":/files/icons/blank-icon.png" ); + break; + } + + case Preferences::PREF_CUSTOM_ICON: + { + pixmap.loadFromData( m_icon_data ); + break; + } + } + + QString number = QString::number( m_unread_mail ); + + if( m_unread_mail > 0 ) + { + /* + * Paint the number + */ + QPainter painter( &pixmap ); + + painter.setFont( QFont("Sans") ); + + double factor = pixmap.width() / ( 3 * painter.fontMetrics().width( number ) ); + QFont font = painter.font(); + font.setPointSizeF( font.pointSizeF() * factor ); + font.setBold( true ); + painter.setFont( font ); + + painter.drawText( pixmap.rect(), Qt::AlignCenter, QString::number( m_unread_mail ) ); + } + + /* + * Set the tray icon + */ + QSystemTrayIcon::setIcon( QIcon( pixmap ) ); +} + + +/* + * Handle unread mail signal + */ +void SysTrayXIcon::slotSetUnreadMail( int unread_mail ) +{ + setUnreadMail( unread_mail ); +} + + +/* + * Handle the icon type change signal + */ +void SysTrayXIcon::slotIconTypeChange() +{ + setIconType( m_pref->getIconType() ); +} + + +/* + * Handle the icon data change signal + */ +void SysTrayXIcon::slotIconDataChange() +{ + setIconMime( m_pref->getIconMime() ); + setIconData( m_pref->getIconData() ); +} diff --git a/app/SysTray-X/systrayxicon.h b/app/SysTray-X/systrayxicon.h new file mode 100644 index 0000000..478892a --- /dev/null +++ b/app/SysTray-X/systrayxicon.h @@ -0,0 +1,132 @@ +#ifndef SYSTRAYXICON_H +#define SYSTRAYXICON_H + +/* + * Local includes + */ +#include "preferences.h" + +/* + * Qt includes + */ +#include + +/* + * Predefines + */ +class SysTrayXLink; + + +/** + * @brief The systrayxtray class. The system tray icon. + */ +class SysTrayXIcon : public QSystemTrayIcon +{ + Q_OBJECT + + public: + + /** + * @brief SysTrayXIcon. Constructor. + * + * @param parent My parent. + */ + SysTrayXIcon( SysTrayXLink *link, Preferences *pref, QObject *parent = nullptr ); + + /** + * @brief setIconType. Set the sytem tray icon type. + * + * @param icon_type The icon type + */ + void setIconType( Preferences::IconType icon_type ); + + /** + * @brief setIconMime. Set the sytem tray icon mime. + * + * @param icon_mime The icon mime + */ + void setIconMime( const QString& icon_mime ); + + /** + * @brief setIconData. Set the custom icon data. + * + * @param icon_data The icon data. + */ + void setIconData( const QByteArray& icon_data ); + + /** + * @brief setUnreadMail. Set the number of unread mails. + * + * @param unread_mail The number of unread mails. + */ + void setUnreadMail( int unread_mail ); + + signals: + + /** + * @brief signalDebugMessage. Signal a debug message. + * + * @param message The message. + */ + void signalDebugMessage( QString message ); + + public slots: + + /** + * @brief slotSetUnreadMail. Slot for handling unread mail signals. + * + * @param unread_mail The number of unread mails. + */ + void slotSetUnreadMail( int unread_mail ); + + /** + * @brief slotIconTypeChange. Slot for handling icon type change signals. + */ + void slotIconTypeChange(); + + /** + * @brief slotIconDataChange. Slot for handling icon data change signals. + */ + void slotIconDataChange(); + + private: + + /** + * @brief setIcon. Set a new rendered icon. + */ + void renderIcon(); + + private: + + /** + * @brief m_link. Pointer to the link. + */ + SysTrayXLink *m_link; + + /** + * @brief m_pref Pointer to the preferences storage. + */ + Preferences *m_pref; + + /** + * @brief m_icon_type. Storage for the icon type. + */ + Preferences::IconType m_icon_type; + + /** + * @brief m_icon_mime. Storage for the icon mime. + */ + QString m_icon_mime; + + /** + * @brief m_icon_data. Storage for the icon. + */ + QByteArray m_icon_data; + + /** + * @brief m_unread_mail. Storage for the number of unread mails. + */ + int m_unread_mail; +}; + +#endif // SYSTRAYXICON_H diff --git a/app/SysTray-X/systrayxlink.cpp b/app/SysTray-X/systrayxlink.cpp index 098aaf5..07e2a31 100644 --- a/app/SysTray-X/systrayxlink.cpp +++ b/app/SysTray-X/systrayxlink.cpp @@ -3,52 +3,60 @@ /* * Local includes */ +#include "preferences.h" + +/* + * System includes + */ #include /* * Qt includes */ #include +#include +#include #include +#include +#include #include - /* * Constructor */ -SysTrayXLink::SysTrayXLink() +SysTrayXLink::SysTrayXLink( Preferences *pref ) { /* - * Open stdin + * Store preferences + */ + m_pref = pref; + + /* + * Open stdin */ m_stdin = new QFile( this ); m_stdin->open( stdin, QIODevice::ReadOnly ); /* - * Open stdout + * Open stdout */ m_stdout = new QFile( this ); m_stdout->open( stdout, QIODevice::WriteOnly ); /* - * Setup the notifiers + * Open dump.txt */ - m_notifierLinkRead = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this ); - connect( m_notifierLinkRead, &QSocketNotifier::activated, this, &SysTrayXLink::slotLinkRead ); + m_dump = new QFile( "dump.txt", this ); + m_dump->open( QIODevice::WriteOnly ); - m_notifierLinkReadException = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Exception, this ); - connect( m_notifierLinkReadException, &QSocketNotifier::activated, this, &SysTrayXLink::slotLinkReadException ); - - - QDataStream out( m_stdout ); - - char reply[] = "\"Hallo other World!\""; - qint32 replylen = sizeof( reply ) - 1; - - - out.writeRawData( reinterpret_cast< char* >( &replylen ), sizeof( qint32 ) ); - out.writeRawData( reply, replylen ); + /* + * Setup the notifiers + */ + m_notifier_link_read = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this ); + connect( m_notifier_link_read, &QSocketNotifier::activated, this, &SysTrayXLink::slotLinkRead ); + m_notifier_link_read_exception = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Exception, this ); + connect( m_notifier_link_read_exception, &QSocketNotifier::activated, this, &SysTrayXLink::slotLinkReadException ); } @@ -58,7 +66,7 @@ SysTrayXLink::SysTrayXLink() SysTrayXLink::~SysTrayXLink() { /* - * Cleanup + * Cleanup */ m_stdin->close(); delete m_stdin; @@ -66,66 +74,18 @@ SysTrayXLink::~SysTrayXLink() m_stdout->close(); delete m_stdout; - delete m_notifierLinkRead; - delete m_notifierLinkReadException; + m_dump->close(); + delete m_dump; + + delete m_notifier_link_read; + delete m_notifier_link_read_exception; } /* - * Read the input + * Write a message to the link */ -void SysTrayXLink::slotLinkRead() -{ - QDataStream in( m_stdin ); - - qint32 msglen; - int status1 = in.readRawData( reinterpret_cast< char* >( &msglen ), sizeof( qint32 ) ); - - emit signalReceivedMessageLength(msglen); - - QByteArray message(msglen + 1, 0 ); - int status2 = in.readRawData( message.data(), msglen ); - - emit signalReceivedMessage( message ); - - if( ( status1 == 4 ) && ( status2 == msglen ) ) - { - //dummy - } - -/* - QDataStream out( m_stdout ); - - char reply[] = "\"Hallo other World!\""; - qint32 replylen = sizeof( reply ) - 1; - - int status3 = out.writeRawData( reinterpret_cast< char* >( &replylen ), sizeof( qint32 ) ); - int status4 = out.writeRawData( reply, replylen ); - m_stdout->flush(); - - if( status3 && status4 ) - { - //dummy - } -*/ -} - - -/* - * Handle notifier exception - */ -void SysTrayXLink::slotLinkReadException() -{ - // Something went wrong -} - - - - -/* - * Read the input - */ -void SysTrayXLink::slotLinkWrite( QByteArray message ) +void SysTrayXLink::linkWrite( const QByteArray& message ) { QDataStream out( m_stdout ); @@ -135,8 +95,241 @@ void SysTrayXLink::slotLinkWrite( QByteArray message ) m_stdout->flush(); + if( status1 && status2 ) { -//dummy + //error handling? + } +} + + +/* + * Send the preferences to the add-on + */ +void SysTrayXLink::sendPreferences() +{ + /* + * Enacode the preferences into a JSON doc + */ + EncodePreferences( *m_pref ); + + + + QFile dump("/home/maxime/dumpJSON_app2addon.txt"); + dump.open(QIODevice::WriteOnly ); + dump.write( m_pref_json_doc.toJson( QJsonDocument::Compact ).data(), m_pref_json_doc.toJson( QJsonDocument::Compact ).length() ); + dump.close(); + + + + /* + * Send them to the add-on + */ + linkWrite( m_pref_json_doc.toJson( QJsonDocument::Compact ) ); +} + + +/* + * Decode JSON message + */ +void SysTrayXLink::DecodeMessage( const QByteArray& message ) +{ + QJsonParseError jsonError; + QJsonDocument jsonResponse = QJsonDocument::fromJson( message, &jsonError ); + + if( jsonError.error == QJsonParseError::NoError ) + { + QJsonObject jsonObject = jsonResponse.object(); + + if( jsonObject.contains( "unreadMail" ) && jsonObject[ "unreadMail" ].isDouble() ) + { + int unreadMail = jsonObject[ "unreadMail" ].toInt(); + emit signalUnreadMail( unreadMail ); + } + + if( jsonObject.contains( "preferences" ) && jsonObject[ "preferences" ].isObject() ) + { + + + QFile dump("/home/maxime/dumpJSON_addon2app.txt"); + dump.open(QIODevice::WriteOnly ); + dump.write( message.data(), message.length() ); + dump.close(); + + + + DecodePreferences( jsonObject[ "preferences" ].toObject() ); + } + } + else + { + emit signalLinkReceiveError( jsonError.errorString() ); + } +} + + +/* + * Decode preferences from JSON message + */ +void SysTrayXLink::DecodePreferences( const QJsonObject& pref ) +{ + /* + * Check the received object + */ + if( pref.contains( "iconType" ) && pref[ "iconType" ].isString() ) + { + Preferences::IconType icon_type = static_cast< Preferences::IconType >( pref[ "iconType" ].toString().toInt() ); + + /* + * Store the new icon type + */ + m_pref->setIconType( icon_type ); + } + + if( pref.contains( "iconMime" ) && pref[ "iconMime" ].isString() ) + { + QString icon_mime = pref[ "iconMime" ].toString(); + + /* + * Store the new icon mime + */ + m_pref->setIconMime( icon_mime ); + } + + if( pref.contains( "icon" ) && pref[ "icon" ].isString() ) + { + QString icon_base64 = pref[ "icon" ].toString(); + +/* + QFile dump("xxxxx"); + dump.open(QIODevice::WriteOnly ); + dump.write( icon_base64.toUtf8().data(), icon_base64.toUtf8().length() ); + dump.close(); +*/ + + /* + * Store the new icon data + */ + m_pref->setIconData( QByteArray::fromBase64( icon_base64.toUtf8() ) ); + } + + if( pref.contains( "debug" ) && pref[ "debug" ].isString() ) + { + bool debug = pref[ "debug" ].toString() == "true"; + + /* + * Store the new debug state + */ + m_pref->setDebug( debug ); + } +} + + +/* + * Encode preferences to JSON message + */ +void SysTrayXLink::EncodePreferences( const Preferences& pref ) +{ + /* + * Setup the preferences JSON + */ + QJsonObject prefObject; + prefObject.insert("debug", QJsonValue::fromVariant( QString( pref.getDebug() ? "true" : "false" ) ) ); + prefObject.insert("iconType", QJsonValue::fromVariant( QString::number( pref.getIconType() ) ) ); + prefObject.insert("iconMime", QJsonValue::fromVariant( pref.getIconMime() ) ); + prefObject.insert("icon", QJsonValue::fromVariant( QString( pref.getIconData().toBase64() ) ) ); + + QJsonObject preferencesObject; + preferencesObject.insert("preferences", prefObject ); + + /* + * Store the new document + */ + m_pref_json_doc = QJsonDocument( preferencesObject ); +} + + +/* + * Read the input + */ +void SysTrayXLink::slotLinkRead() +{ + QDataStream in( m_stdin ); + + qint32 msglen; + int status1 = in.readRawData( reinterpret_cast< char* >( &msglen ), sizeof( qint32 ) ); + + emit signalReceivedMessageLength( msglen ); + + QByteArray message(msglen, 0 ); + int status2 = in.readRawData( message.data(), msglen ); + + emit signalReceivedMessage( message ); + + m_dump->write( message ); + + /* + * Decode the message + */ + DecodeMessage( message ); + + + if( ( status1 == 4 ) && ( status2 == msglen ) ) + { + //error handling? + } +} + + +/* + * Handle read notifier exception + */ +void SysTrayXLink::slotLinkReadException() +{ + // Something went wrong +} + + +/* + * write the output + */ +void SysTrayXLink::slotLinkWrite( QByteArray message ) +{ + linkWrite( message ); +} + + + +/* + * Handle a debug state change signal + */ +void SysTrayXLink::slotDebugChange() +{ + if( m_pref->getAppPrefChanged() ) + { + sendPreferences(); + } +} + +/* + * Handle the icon type change signal + */ +void SysTrayXLink::slotIconTypeChange() +{ + if( m_pref->getAppPrefChanged() ) + { + sendPreferences(); + } +} + + +/* + * Handle the icon data change signal + */ +void SysTrayXLink::slotIconDataChange() +{ + if( m_pref->getAppPrefChanged() ) + { + sendPreferences(); } } diff --git a/app/SysTray-X/systrayxlink.h b/app/SysTray-X/systrayxlink.h index bca2efa..c4e8d7a 100644 --- a/app/SysTray-X/systrayxlink.h +++ b/app/SysTray-X/systrayxlink.h @@ -4,11 +4,13 @@ /* * Local includes */ +#include "preferences.h" /* * Qt includes */ #include +#include /* * Predefines @@ -28,27 +30,77 @@ class SysTrayXLink : public QObject /** * @brief SysTrayXLink. Constructor, destructor. */ - SysTrayXLink(); + SysTrayXLink( Preferences *pref ); ~SysTrayXLink(); + /** + * @brief linkWrite. Write a message to the link. + * + * @param message Message to be written. + */ + void linkWrite( const QByteArray& message ); + + /** + * @brief sendPreferences. Send the preferences to the add-on. + */ + void sendPreferences(); + + private: + + /** + * @brief MessageDecode. Decode a JSON message. + * + * @param message The message. + */ + void DecodeMessage( const QByteArray& message ); + + /** + * @brief DecodePreferences. Decode a JSON preference object. + * + * @param pref The JSON preferences. + */ + void DecodePreferences( const QJsonObject& pref ); + + /** + * @brief EncodePreferences. Encode the preferences into a JSON document. + * + * @param pref The preferences. + */ + void EncodePreferences( const Preferences& pref ); + public slots: + /** + * @brief slotDebugChange. Handle a change in debug state. + */ + void slotDebugChange(); + /** * @brief slotLinkWrite. Write the link. */ - void slotLinkWrite( QByteArray message ); + void slotLinkWrite( QByteArray message ); + + /** + * @brief slotIconTypeChange. Slot for handling icon type change signals. + */ + void slotIconTypeChange(); + + /** + * @brief slotIconDataChange. Slot for handling icon data change signals. + */ + void slotIconDataChange(); private slots: /** * @brief slotLinkRead. Read the link. */ - void slotLinkRead(); + void slotLinkRead(); /** * @brief slotLinkReadException. Handle a read link exception. */ - void slotLinkReadException(); + void slotLinkReadException(); signals: @@ -57,17 +109,43 @@ class SysTrayXLink : public QObject * * @param msglen */ - void signalReceivedMessageLength(qint32 msglen); + void signalReceivedMessageLength( qint32 msglen ); /** * @brief signalReceivedMessage * * @param message */ - void signalReceivedMessage(QByteArray message); + void signalReceivedMessage( QByteArray message ); + + /** + * @brief signalLinkReceiveError. Cannot parse received JSON message. + * + * @param error JSON error message + */ + void signalLinkReceiveError( QString error ); + + /** + * @brief signalDebugMessage. Signal a debug message. + * + * @param message The message. + */ + void signalDebugMessage( QString message ); + + /** + * @brief signalUnreadMail. Signal numder of unread mails. + * + * @param unreadMail The number of unread mails. + */ + void signalUnreadMail( int unread_mail ); private: + /** + * @brief m_pref. Pointer to the preferences storage. + */ + Preferences *m_pref; + /** * @brief m_stdin. Pointer to stdin file. */ @@ -79,14 +157,24 @@ class SysTrayXLink : public QObject QFile *m_stdout; /** - * @brief m_notifierLinkRead. Pointers to the link read data notifier. + * @brief m_dump. Pointer to dump file. */ - QSocketNotifier *m_notifierLinkRead; + QFile *m_dump; /** - * @brief m_notifierLinkReadException. Pointers to the link read exception notifier. + * @brief m_notifier_link_read. Pointers to the link read data notifier. */ - QSocketNotifier *m_notifierLinkReadException; + QSocketNotifier *m_notifier_link_read; + + /** + * @brief m_notifier_link_read_exception. Pointers to the link read exception notifier. + */ + QSocketNotifier *m_notifier_link_read_exception; + + /** + * @brief m_pref_json_doc. Temporary storage for the preferences to be send. + */ + QJsonDocument m_pref_json_doc; }; #endif // SYSTRAYXLINK_H diff --git a/webext/background.js b/webext/background.js index 4e7854e..6959fce 100644 --- a/webext/background.js +++ b/webext/background.js @@ -13,26 +13,62 @@ SysTrayX.Messaging = { ], init: function() { - if (this.initialized) { - console.log("Messaging already initialized"); - return; - } console.log("Enabling Messaging"); + // Get the accounts from the storage + SysTrayX.Messaging.getAccounts(); + browser.storage.onChanged.addListener(SysTrayX.Messaging.storageChanged); + + // Send preferences to app + SysTrayX.Messaging.sendPreferences(); + // this.unReadMessages(this.unreadFiltersTest).then(this.unreadCb); - window.setInterval(SysTrayX.Messaging.pollAccounts, 1000); + window.setInterval(SysTrayX.Messaging.pollAccounts, 10000); this.initialized = true; }, + // + // Handle a storage change + // + storageChanged: function(changes, area) { + console.debug("Changes in store"); + + // Get the new preferences + SysTrayX.Messaging.getAccounts(); + + if ("addonprefchanged" in changes && changes["addonprefchanged"].newValue) { + console.debug("Sending preference"); + + // + // Send new preferences to the app + // + SysTrayX.Messaging.sendPreferences(); + + // Reset flag + browser.storage.sync.set({ + addonprefchanged: false + }); + } + + /* + var changedItems = Object.keys(changes); + for (var item of changedItems) { + console.log(item + " has changed:"); + console.log("Old value: "); + console.log(changes[item].oldValue); + console.log("New value: "); + console.log(changes[item].newValue); + } +*/ + }, + // // Poll the accounts // pollAccounts: function() { console.debug("Polling"); - SysTrayX.Messaging.getAccounts(); - // // Get the unread nessages of the selected accounts // @@ -40,14 +76,16 @@ SysTrayX.Messaging = { let filtersAttr = filtersDiv.getAttribute("data-filters"); let filters = JSON.parse(filtersAttr); - SysTrayX.Messaging.unReadMessages(filters).then( - SysTrayX.Messaging.unreadCb - ); + if (filters.length > 0) { + SysTrayX.Messaging.unReadMessages(filters).then( + SysTrayX.Messaging.unreadCb + ); + } }, // - // Use the messages API to get the unread messages (Promise) - // Be aware that the data is only avaiable inside the callback + // Use the messages API to get the unread messages (Promise) + // Be aware that the data is only avaiable inside the callback // unReadMessages: async function(filters) { let unreadMessages = 0; @@ -68,36 +106,55 @@ SysTrayX.Messaging = { }, // - // Callback for unReadMessages + // Callback for unReadMessages // unreadCb: function(count) { - console.log("SysTrayX unread " + count); + SysTrayX.Link.postSysTrayXMessage({ unreadMail: count }); + }, + + sendPreferences: function() { + console.debug("Send preferences"); + + let getter = browser.storage.sync.get([ + "debug", + "iconType", + "iconMime", + "icon" + ]); + getter.then(this.sendPreferencesStorage, this.onSendPreferecesStorageError); + }, + + sendPreferencesStorage: function(result) { + console.debug("Get preferences from storage"); + + let debug = result.debug || "false"; + let iconType = result.iconType || "0"; + let iconMime = result.iconMime || "image/png"; + let icon = result.icon || []; + + console.log("Debug" + debug); + console.log("Type" + iconType); + console.log("Mime" + iconMime); + console.log(icon); + + // Send it to the app + SysTrayX.Link.postSysTrayXMessage({ + preferences: { + debug: debug, + iconType: iconType, + iconMime: iconMime, + icon: icon + } + }); + }, + + onSendIconStorageError: function(error) { + console.log(`GetIcon Error: ${error}`); }, // - // Get the accounts from the storage and - // make them available in the background HTML + // Get the accounts from the storage // - getAccountsStorage: function(result) { - console.debug("Get accounts from storage"); - - let accounts = result.accounts || []; - - // Store them in the background HTML - let accountsDiv = document.getElementById("accounts"); - accountsDiv.setAttribute("data-accounts", JSON.stringify(accounts)); - - let filters = result.filters || []; - - // Store them in the background HTML - let filtersDiv = document.getElementById("filters"); - filtersDiv.setAttribute("data-filters", JSON.stringify(filters)); - }, - - onGetAccountsStorageError: function(error) { - console.log(`GetAccounts Error: ${error}`); - }, - getAccounts: function() { console.debug("Get accounts"); @@ -113,42 +170,98 @@ SysTrayX.Messaging = { let accounts = JSON.parse(accountsAttr); console.debug("Accounts poll: " + accounts.length); } + }, + + // + // Get the accounts from the storage and + // make them available in the background HTML + // + getAccountsStorage: function(result) { + console.debug("Get accounts from storage"); + + let accounts = result.accounts || []; + + // Store them in the background HTML + let accountsDiv = document.getElementById("accounts"); + accountsDiv.setAttribute("data-accounts", JSON.stringify(accounts)); + + let filters = result.filters || []; + + // Store them in the background HTML + let filtersDiv = document.getElementById("filters"); + filtersDiv.setAttribute("data-filters", JSON.stringify(filters)); + }, + + onGetAccountsStorageError: function(error) { + console.log(`GetAccounts Error: ${error}`); } }; // -// Link object, handles the native messaging to the system tray app +// Link object. Handles the native messaging to the system tray app // SysTrayX.Link = { portSysTrayX: undefined, init: function() { - // Connect to the app + // Connect to the app this.portSysTrayX = browser.runtime.connectNative("SysTray_X"); // Listen for messages from the app. this.portSysTrayX.onMessage.addListener( SysTrayX.Link.receiveSysTrayXMessage ); - - // Setup test loop - window.setInterval(SysTrayX.Link.postSysTrayXMessage, 1000); }, - postSysTrayXMessage: function() { - console.log("Sending: Hallo World!"); - SysTrayX.Link.portSysTrayX.postMessage("Hallo World!"); - // SysTrayX.Link.portSysTrayX.postMessage({ key: "Hallo", value: "World!" }); + postSysTrayXMessage: function(object) { + // Send object (will be stringified by postMessage) + SysTrayX.Link.portSysTrayX.postMessage(object); }, receiveSysTrayXMessage: function(response) { console.log("Received: " + response); + + if (response["preferences"]) { + // Store the preferences from the app + console.log("Preferences received"); + + let iconMime = response["preferences"].iconMime; + if (iconMime) { + browser.storage.sync.set({ + iconMime: iconMime + }); + } + + let icon = response["preferences"].icon; + if (icon) { + browser.storage.sync.set({ + icon: icon + }); + } + + let iconType = response["preferences"].iconType; + if (iconType) { + browser.storage.sync.set({ + iconType: iconType + }); + } + + let debug = response["preferences"].debug; + if (debug) { + browser.storage.sync.set({ + debug: debug + }); + } + } } }; console.log("Starting SysTray-X"); -SysTrayX.Messaging.init(); +// Setup the link first SysTrayX.Link.init(); +// Main start +SysTrayX.Messaging.init(); + console.log("Done"); diff --git a/webext/css/options.css b/webext/css/options.css index 6d90408..87f6ebb 100644 --- a/webext/css/options.css +++ b/webext/css/options.css @@ -100,3 +100,11 @@ ul, .active { display: block; } + +.custom-icon-container { + display: flex; +} + +.custom-icon-container > div { + margin: 10px; +} diff --git a/webext/js/options_accounts.js b/webext/js/options_accounts.js index 75d592a..f256749 100644 --- a/webext/js/options_accounts.js +++ b/webext/js/options_accounts.js @@ -4,15 +4,7 @@ SysTrayX.Accounts = { initialized: false, init: function() { - if (this.initialized) { - console.log("Accounts already initialized"); - return; - } - console.log("Enabling Accounts"); - this.getAccounts().then(this.getAccountsCb); - - this.initialized = true; }, /* diff --git a/webext/js/options_iconform.js b/webext/js/options_iconform.js new file mode 100644 index 0000000..eca3786 --- /dev/null +++ b/webext/js/options_iconform.js @@ -0,0 +1,36 @@ +function fileSelected() { + let input = document.getElementById("selectedFileIconType"); + + if (input.files.length > 0) { + console.debug("Selected file: " + input.files[0].name); + console.debug("Selected file type: " + input.files[0].type); + } + + function storeFile() { + let buffer = new Uint8Array(fr.result); + + let binary = ""; + let len = buffer.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(buffer[i]); + } + + let base64 = window.btoa(binary); + let iconDiv = document.getElementById("icon"); + iconDiv.setAttribute("data-icon", base64); + iconDiv.setAttribute("data-icon-mime", input.files[0].type); + + let image = document.getElementById("customIconImage"); + image.setAttribute("src", `data:${input.files[0].type};base64,${base64}` ); + + console.log(base64); + } + + fr = new FileReader(); + fr.onload = storeFile; + fr.readAsArrayBuffer(input.files[0]); +} + +document + .getElementById("selectedFileIconType") + .addEventListener("change", fileSelected); diff --git a/webext/options.html b/webext/options.html index cae3755..775b2f9 100644 --- a/webext/options.html +++ b/webext/options.html @@ -9,7 +9,13 @@ - + +
+
+ Display debug window
+
+
+
@@ -21,7 +27,7 @@

Windows

Windows options here

-

Please select your opton:

+

Please select your option:

Option 1
Option 2
Option 3
@@ -30,22 +36,46 @@ Check 1
Check 2
Check 3
+
-
+

Icon

-

Icon options here.

+ +

Please select your option:

+ + + + + + + + + + + + + +
Blank icon
New mail icon
Custom icon + + +
+ +
+ + +
-
+

Included accounts

    -
    +

    diff --git a/webext/options.js b/webext/options.js index f5744f7..c5cf65b 100644 --- a/webext/options.js +++ b/webext/options.js @@ -1,94 +1,302 @@ -function saveOptions(e) { - e.preventDefault(); +var SysTrayX = {}; - console.debug("Save preferences"); +SysTrayX.SaveOptions = { + start: function(e) { + e.preventDefault(); - browser.storage.sync.set({ - optionsRadioTest: document.querySelector( - 'input[name="options_test"]:checked' - ).value, - optionsCheck1: document.querySelector('input[name="check1"]').checked, - optionsCheck2: document.querySelector('input[name="check2"]').checked, - optionsCheck3: document.querySelector('input[name="check3"]').checked - }); + console.debug("Save preferences"); - /* - * Save accounts and filters - */ + browser.storage.sync.set({ + optionsRadioTest: document.querySelector( + 'input[name="options_test"]:checked' + ).value, + optionsCheck1: document.querySelector('input[name="check1"]').checked, + optionsCheck2: document.querySelector('input[name="check2"]').checked, + optionsCheck3: document.querySelector('input[name="check3"]').checked + }); - console.debug("Store accounts and filters"); + // + // Save accounts and filters + // - let treeBase = document.getElementById("accountsTree"); - let inputs = treeBase.querySelectorAll("input"); - let accounts = []; - let filters = []; - for (let i = 0; i < inputs.length; ++i) { - let account = JSON.parse(inputs[i].value); - let checked = inputs[i].checked; - accounts.push({ ...account, checked: checked }); + console.debug("Store accounts and filters"); - if (checked) { - let inboxMailFolder = account.folders.find(obj => obj.type === "inbox"); + let treeBase = document.getElementById("accountsTree"); + let inputs = treeBase.querySelectorAll("input"); + let accounts = []; + let filters = []; + for (let i = 0; i < inputs.length; ++i) { + let account = JSON.parse(inputs[i].value); + let checked = inputs[i].checked; + accounts.push({ ...account, checked: checked }); - if (inboxMailFolder) { - console.debug("Filter Id: " + inboxMailFolder.accountId); - console.debug("Filter Path: " + inboxMailFolder.path); + if (checked) { + let inboxMailFolder = account.folders.find(obj => obj.type === "inbox"); - filters.push({ - unread: true, - folder: inboxMailFolder - }); + if (inboxMailFolder) { + console.debug("Filter Id: " + inboxMailFolder.accountId); + console.debug("Filter Path: " + inboxMailFolder.path); + + filters.push({ + unread: true, + folder: inboxMailFolder + }); + } } } + + // Store accounts + browser.storage.sync.set({ + accounts: accounts + }); + + // Store query filters + browser.storage.sync.set({ + filters: filters + }); + + console.debug("Store accounts and filters done"); + + // + // Save debug state + // + let debug = document.querySelector('input[name="debug"]').checked; + browser.storage.sync.set({ + debug: `${debug}` + }); + console.debug("Store debug state: " + `${debug}`); + + // + // Save icon preferences + // + + console.debug("Store icon preferences"); + + let iconType = document.querySelector('input[name="iconType"]:checked') + .value; + + // Store icon type + browser.storage.sync.set({ + iconType: iconType + }); + + let iconDiv = document.getElementById("icon"); + let iconBase64 = iconDiv.getAttribute("data-icon"); + let iconMime = iconDiv.getAttribute("data-icon-mime"); + + // Store icon (base64) + browser.storage.sync.set({ + iconMime: iconMime, + icon: iconBase64 + }); + + console.debug("Store icon preferences done"); + + // Mark add-on preferences changed + browser.storage.sync.set({ + addonprefchanged: true + }); } +}; - // Store accounts - browser.storage.sync.set({ - accounts: accounts - }); +SysTrayX.RestoreOptions = { + start: function() { + console.debug("Restore preferences"); - // Store query filters - browser.storage.sync.set({ - filters: filters - }); + // + // Test 1 + // + let getting = browser.storage.sync.get("optionsRadioTest"); + getting.then( + SysTrayX.RestoreOptions.setCurrentRadioChoice, + SysTrayX.RestoreOptions.onError + ); - console.debug("Store accounts and filters done"); -} + // + // Test 2 + // + getting = browser.storage.sync.get([ + "optionsCheck1", + "optionsCheck2", + "optionsCheck3" + ]); + getting.then( + SysTrayX.RestoreOptions.setCurrentCheckChoice, + SysTrayX.RestoreOptions.onError + ); -function restoreOptions() { - console.debug("Restore preferences"); + console.debug("Restore icon preferences"); - function setCurrentRadioChoice(result) { + // + // Restore debug state + // + let getDebug = browser.storage.sync.get("debug"); + getDebug.then( + SysTrayX.RestoreOptions.setDebug, + SysTrayX.RestoreOptions.onDebugError + ); + + // + // Restore icon type + // + let getIconType = browser.storage.sync.get("iconType"); + getIconType.then( + SysTrayX.RestoreOptions.setIconType, + SysTrayX.RestoreOptions.onIconTypeError + ); + + // + // Restore icon + // + let getIcon = browser.storage.sync.get(["iconMime", "icon"]); + getIcon.then( + SysTrayX.RestoreOptions.setIcon, + SysTrayX.RestoreOptions.onIconError + ); + + console.debug("Restore icon preferences done"); + }, + + // + // Test 1 Callback + // + setCurrentRadioChoice: function(result) { let selector = result.optionsRadioTest || "Option1"; let radioButton = document.querySelector(`[value=${selector}]`); radioButton.checked = true; - } + }, - function setCurrentCheckChoice(result) { + // + // Test 2 Callback + // + setCurrentCheckChoice: function(result) { let checkbox1 = document.querySelector('[name="check1"]'); checkbox1.checked = result.optionsCheck1 || false; let checkbox2 = document.querySelector('[name="check2"]'); checkbox2.checked = result.optionsCheck2 || false; let checkbox3 = document.querySelector('[name="check3"]'); checkbox3.checked = result.optionsCheck3 || false; - } + }, - function onError(error) { + // + // Test 1+2 error callback + // + onError: function(error) { console.log(`Error: ${error}`); + }, + + // + // Restore debug state callbacks + // + setDebug: function(result) { + let debug = result.debug || "false"; + + console.debug("Debug: " + debug); + + let checkbox = document.querySelector(`input[name="debug"]`); + checkbox.checked = (debug === "true"); + }, + + onDebugError: function(error) { + console.log(`Debug Error: ${error}`); + }, + + // + // Restore icon type callbacks + // + setIconType: function(result) { + let iconType = result.iconType || "0"; + let radioButton = document.querySelector(`[value="${iconType}"]`); + radioButton.checked = true; + }, + + onIconTypeError: function(error) { + console.log(`Icon type Error: ${error}`); + }, + + // + // Restore icon + // + setIconMime: function(result) { + let iconMime = result.iconMime || "image/png"; + + let iconDiv = document.getElementById("icon"); + iconDiv.setAttribute("data-icon-mime", iconMime); + }, + + setIconData: function(result) { + let iconBase64 = result.icon || ""; + + let iconDiv = document.getElementById("icon"); + iconDiv.setAttribute("data-icon", iconBase64); + }, + + updateIconImage: function() { + let iconDiv = document.getElementById("icon"); + icon_mime = iconDiv.getAttribute("data-icon-mime"); + icon_data = iconDiv.getAttribute("data-icon"); + + let image = document.getElementById("customIconImage"); + image.setAttribute("src", `data:${icon_mime};base64,${icon_data}`); + }, + + setIcon: function(result) { + SysTrayX.RestoreOptions.setIconMime(result); + SysTrayX.RestoreOptions.setIconData(result); + + SysTrayX.RestoreOptions.updateIconImage(); + }, + + onIconError: function(error) { + console.log(`Icon Error: ${error}`); } +}; - var getting = browser.storage.sync.get("optionsRadioTest"); - getting.then(setCurrentRadioChoice, onError); +SysTrayX.StorageChanged = { + changed: function(changes, area) { + // Try to keep the preferences of the add-on and the app in sync + let changedItems = Object.keys(changes); - var getting = browser.storage.sync.get([ - "optionsCheck1", - "optionsCheck2", - "optionsCheck3" - ]); - getting.then(setCurrentCheckChoice, onError); -} + let changed_icon = false; + let changed_icon_mime = false; + for (let item of changedItems) { + if (item === "iconMime") { + SysTrayX.RestoreOptions.setIconMime({ + iconMime: changes[item].newValue + }); + } + if (item === "icon") { + SysTrayX.RestoreOptions.setIcon({ icon: changes[item].newValue }); + changed_icon = true; + } + if (item === "iconType") { + SysTrayX.RestoreOptions.setIconType({ + iconType: changes[item].newValue + }); + changed_icon_mime = true; + } + if (item === "debug") { + SysTrayX.RestoreOptions.setDebug({ + debug: changes[item].newValue + }); + } + } -document.addEventListener("DOMContentLoaded", restoreOptions); + if (changed_icon_mime && changed_icon) { + SysTrayX.RestoreOptions.updateIconImage(); + } + + // + // Update element + // + document.getElementById("debugselect").className = "active"; + document.getElementById("iconselect").className = "active"; + } +}; + +document.addEventListener("DOMContentLoaded", SysTrayX.RestoreOptions.start); document .querySelector('[name="saveform"]') - .addEventListener("submit", saveOptions); + .addEventListener("submit", SysTrayX.SaveOptions.start); + +browser.storage.onChanged.addListener(SysTrayX.StorageChanged.changed);