diff options
172 files changed, 3785 insertions, 0 deletions
diff --git a/src/WhatsApp.pro b/src/WhatsApp.pro new file mode 100644 index 0000000..95e6b33 --- /dev/null +++ b/src/WhatsApp.pro @@ -0,0 +1,87 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2020-03-26T13:53:21 +# +#------------------------------------------------- + +QT += core widgets webengine webenginewidgets + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + + +TARGET = whatsie +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Refer to the documentation for the +# deprecated API to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# No debug output in release mode +CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +CONFIG += c++11 + +# Set program version +VERSION = 1.0 +DEFINES += VERSIONSTR=\\\"$${VERSION}\\\" + +SOURCES += \ + downloadmanagerwidget.cpp \ + downloadwidget.cpp \ + elidedlabel.cpp \ + main.cpp \ + mainwindow.cpp \ + settingswidget.cpp \ + utils.cpp \ + webenginepage.cpp \ + widgets/scrolltext/scrolltext.cpp + +RESOURCES += \ + icons.qrc + +HEADERS += \ + common.h \ + downloadmanagerwidget.h \ + downloadwidget.h \ + elidedlabel.h \ + mainwindow.h \ + notificationpopup.h \ + requestinterceptor.h \ + settingswidget.h \ + utils.h \ + webenginepage.h \ + widgets/scrolltext/scrolltext.h + + +# Default rules for deployment. +isEmpty(PREFIX){ + PREFIX = /usr +} + +BINDIR = $$PREFIX/bin +DATADIR = $$PREFIX/share + +target.path = $$BINDIR + +icon.files = icons/linguist.png +icon.path = $$DATADIR/icons/hicolor/512x512/apps/ + +desktop.files = linguist.desktop +desktop.path = $$DATADIR/applications/ + +INSTALLS += target icon desktop + +FORMS += \ + certificateerrordialog.ui \ + downloadmanagerwidget.ui \ + downloadwidget.ui \ + passworddialog.ui \ + settingswidget.ui + diff --git a/src/certificateerrordialog.ui b/src/certificateerrordialog.ui new file mode 100644 index 0000000..a14a784 --- /dev/null +++ b/src/certificateerrordialog.ui @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CertificateErrorDialog</class>
+ <widget class="QDialog" name="CertificateErrorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>370</width>
+ <height>141</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>20</number>
+ </property>
+ <property name="rightMargin">
+ <number>20</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="m_iconLabel">
+ <property name="text">
+ <string>Icon</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_errorLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Error</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_infoLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>If you wish so, you may continue with an unverified certificate. Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.
+
+Do you wish to override the security check and continue ? </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CertificateErrorDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>CertificateErrorDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..261ee64 --- /dev/null +++ b/src/common.h @@ -0,0 +1,7 @@ +#ifndef COMMON_H +#define COMMON_H +#include <QString> + +QString defaultUserAgentStr = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.4389.114 Safari/537.36"; + +#endif // COMMON_H diff --git a/src/downloadmanagerwidget.cpp b/src/downloadmanagerwidget.cpp new file mode 100644 index 0000000..1a0fa27 --- /dev/null +++ b/src/downloadmanagerwidget.cpp @@ -0,0 +1,44 @@ +#include "downloadmanagerwidget.h"
+
+#include "downloadwidget.h"
+
+#include <QFileDialog>
+#include <QWebEngineDownloadItem>
+
+DownloadManagerWidget::DownloadManagerWidget(QWidget *parent)
+ : QWidget(parent)
+ , m_numDownloads(0)
+{
+ setupUi(this);
+}
+
+void DownloadManagerWidget::downloadRequested(QWebEngineDownloadItem *download)
+{
+ //Q_ASSERT(download && download->state() == QWebEngineDownloadItem::DownloadRequested);
+
+ QString path = QFileDialog::getSaveFileName(this, tr("Save as"), download->path(),tr("Any file (*)"),nullptr,QFileDialog::DontUseNativeDialog);
+ if (path.isEmpty())
+ return;
+
+ download->setPath(path);
+ download->accept();
+ add(new DownloadWidget(download));
+
+ show();
+}
+
+void DownloadManagerWidget::add(DownloadWidget *downloadWidget)
+{
+ connect(downloadWidget, &DownloadWidget::removeClicked, this, &DownloadManagerWidget::remove);
+ m_itemsLayout->insertWidget(0, downloadWidget, 0, Qt::AlignTop);
+ if (m_numDownloads++ == 0)
+ m_zeroItemsLabel->hide();
+}
+
+void DownloadManagerWidget::remove(DownloadWidget *downloadWidget)
+{
+ m_itemsLayout->removeWidget(downloadWidget);
+ downloadWidget->deleteLater();
+ if (--m_numDownloads == 0)
+ m_zeroItemsLabel->show();
+}
diff --git a/src/downloadmanagerwidget.h b/src/downloadmanagerwidget.h new file mode 100644 index 0000000..cca092b --- /dev/null +++ b/src/downloadmanagerwidget.h @@ -0,0 +1,83 @@ +/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DOWNLOADMANAGERWIDGET_H
+#define DOWNLOADMANAGERWIDGET_H
+
+#include "ui_downloadmanagerwidget.h"
+
+#include <QWidget>
+
+QT_BEGIN_NAMESPACE
+class QWebEngineDownloadItem;
+QT_END_NAMESPACE
+
+class DownloadWidget;
+
+// Displays a list of downloads.
+class DownloadManagerWidget final : public QWidget, public Ui::DownloadManagerWidget
+{
+ Q_OBJECT
+public:
+ explicit DownloadManagerWidget(QWidget *parent = nullptr);
+
+ // Prompts user with a "Save As" dialog. If the user doesn't cancel it, then
+ // the QWebEngineDownloadItem will be accepted and the DownloadManagerWidget
+ // will be shown on the screen.
+ void downloadRequested(QWebEngineDownloadItem *webItem);
+
+private:
+ void add(DownloadWidget *downloadWidget);
+ void remove(DownloadWidget *downloadWidget);
+
+ int m_numDownloads;
+};
+
+#endif // DOWNLOADMANAGERWIDGET_H
diff --git a/src/downloadmanagerwidget.ui b/src/downloadmanagerwidget.ui new file mode 100644 index 0000000..8b815d0 --- /dev/null +++ b/src/downloadmanagerwidget.ui @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadManagerWidget</class>
+ <widget class="QWidget" name="DownloadManagerWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>452</width>
+ <height>250</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Downloads</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="m_topLevelLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetNoConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QScrollArea" name="m_scrollArea">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="m_items">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>450</width>
+ <height>248</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="m_itemsLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="m_zeroItemsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">color: palette(shadow)</string>
+ </property>
+ <property name="text">
+ <string>No downloads</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/downloadwidget.cpp b/src/downloadwidget.cpp new file mode 100644 index 0000000..005ea1e --- /dev/null +++ b/src/downloadwidget.cpp @@ -0,0 +1,108 @@ +#include "downloadwidget.h"
+
+#include <QFileInfo>
+#include <QUrl>
+#include <QWebEngineDownloadItem>
+
+DownloadWidget::DownloadWidget(QWebEngineDownloadItem *download, QWidget *parent)
+ : QFrame(parent)
+ , m_download(download)
+ , m_timeAdded(QTime::currentTime())
+{
+ setupUi(this);
+ m_dstName->setText(QFileInfo(m_download->path()).fileName());
+ m_srcUrl->setText(m_download->url().toDisplayString());
+
+ connect(m_cancelButton, &QPushButton::clicked,
+ [this](bool) {
+ if (m_download->state() == QWebEngineDownloadItem::DownloadInProgress)
+ m_download->cancel();
+ else
+ emit removeClicked(this);
+ });
+
+ connect(m_download, &QWebEngineDownloadItem::downloadProgress,
+ this, &DownloadWidget::updateWidget);
+
+ connect(m_download, &QWebEngineDownloadItem::stateChanged,
+ this, &DownloadWidget::updateWidget);
+
+ updateWidget();
+}
+
+inline QString DownloadWidget::withUnit(qreal bytes)
+{
+ if (bytes < (1 << 10))
+ return tr("%L1 B").arg(bytes);
+ else if (bytes < (1 << 20))
+ return tr("%L1 KiB").arg(bytes / (1 << 10), 0, 'f', 2);
+ else if (bytes < (1 << 30))
+ return tr("%L1 MiB").arg(bytes / (1 << 20), 0, 'f', 2);
+ else
+ return tr("%L1 GiB").arg(bytes / (1 << 30), 0, 'f', 2);
+}
+
+void DownloadWidget::updateWidget()
+{
+ qreal totalBytes = m_download->totalBytes();
+ qreal receivedBytes = m_download->receivedBytes();
+ qreal bytesPerSecond = receivedBytes / m_timeAdded.elapsed() * 1000;
+
+ auto state = m_download->state();
+ switch (state) {
+ case QWebEngineDownloadItem::DownloadRequested:
+ Q_UNREACHABLE();
+ break;
+ case QWebEngineDownloadItem::DownloadInProgress:
+ if (totalBytes >= 0) {
+ m_progressBar->setValue(qRound(100 * receivedBytes / totalBytes));
+ m_progressBar->setDisabled(false);
+ m_progressBar->setFormat(
+ tr("%p% - %1 of %2 downloaded - %3/s")
+ .arg(withUnit(receivedBytes))
+ .arg(withUnit(totalBytes))
+ .arg(withUnit(bytesPerSecond)));
+ } else {
+ m_progressBar->setValue(0);
+ m_progressBar->setDisabled(false);
+ m_progressBar->setFormat(
+ tr("unknown size - %1 downloaded - %2/s")
+ .arg(withUnit(receivedBytes))
+ .arg(withUnit(bytesPerSecond)));
+ }
+ break;
+ case QWebEngineDownloadItem::DownloadCompleted:
+ m_progressBar->setValue(100);
+ m_progressBar->setDisabled(true);
+ m_progressBar->setFormat(
+ tr("completed - %1 downloaded - %2/s")
+ .arg(withUnit(receivedBytes))
+ .arg(withUnit(bytesPerSecond)));
+ break;
+ case QWebEngineDownloadItem::DownloadCancelled:
+ m_progressBar->setValue(0);
+ m_progressBar->setDisabled(true);
+ m_progressBar->setFormat(
+ tr("cancelled - %1 downloaded - %2/s")
+ .arg(withUnit(receivedBytes))
+ .arg(withUnit(bytesPerSecond)));
+ break;
+ case QWebEngineDownloadItem::DownloadInterrupted:
+ m_progressBar->setValue(0);
+ m_progressBar->setDisabled(true);
+ m_progressBar->setFormat(
+ tr("interrupted: %1")
+ .arg(m_download->interruptReasonString()));
+ break;
+ }
+
+ if (state == QWebEngineDownloadItem::DownloadInProgress) {
+ static QIcon cancelIcon(QStringLiteral(":/icons/stop-line.png"));
+ m_cancelButton->setIcon(cancelIcon);
+ m_cancelButton->setToolTip(tr("Stop downloading"));
+ } else {
+ static QIcon removeIcon(QStringLiteral(":/icons/close-fill.png"));
+ m_cancelButton->setIcon(removeIcon);
+ m_cancelButton->setToolTip(tr("Remove from list"));
+ }
+}
diff --git a/src/downloadwidget.h b/src/downloadwidget.h new file mode 100644 index 0000000..521d996 --- /dev/null +++ b/src/downloadwidget.h @@ -0,0 +1,88 @@ +/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DOWNLOADWIDGET_H
+#define DOWNLOADWIDGET_H
+
+#include "ui_downloadwidget.h"
+#include "elidedlabel.h"
+
+
+#include <QFrame>
+#include <QTime>
+
+QT_BEGIN_NAMESPACE
+class QWebEngineDownloadItem;
+QT_END_NAMESPACE
+
+// Displays one ongoing or finished download (QWebEngineDownloadItem).
+class DownloadWidget final : public QFrame, public Ui::DownloadWidget
+{
+ Q_OBJECT
+public:
+ // Precondition: The QWebEngineDownloadItem has been accepted.
+ explicit DownloadWidget(QWebEngineDownloadItem *download, QWidget *parent = nullptr);
+
+signals:
+ // This signal is emitted when the user indicates that they want to remove
+ // this download from the downloads list.
+ void removeClicked(DownloadWidget *self);
+
+private slots:
+ void updateWidget();
+
+private:
+ QString withUnit(qreal bytes);
+
+ QWebEngineDownloadItem *m_download;
+ QTime m_timeAdded;
+};
+
+#endif // DOWNLOADWIDGET_H
diff --git a/src/downloadwidget.ui b/src/downloadwidget.ui new file mode 100644 index 0000000..d405b8f --- /dev/null +++ b/src/downloadwidget.ui @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadWidget</class>
+ <widget class="QFrame" name="DownloadWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>526</width>
+ <height>97</height>
+ </rect>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QGridLayout" name="m_topLevelLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinAndMaxSize</enum>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="ElidedLabel" name="m_srcUrl">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="m_cancelButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="ElidedLabel" name="m_dstName">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QProgressBar" name="m_progressBar">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ElidedLabel</class>
+ <extends>QLabel</extends>
+ <header>elidedlabel.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/elidedlabel.cpp b/src/elidedlabel.cpp new file mode 100644 index 0000000..6c14af4 --- /dev/null +++ b/src/elidedlabel.cpp @@ -0,0 +1,49 @@ +#include "elidedlabel.h" + +#include <QDebug> +#include <QPainter> +#include <QResizeEvent> +#include <QStyle> + +ElidedLabel::ElidedLabel(QWidget* parent, Qt::WindowFlags f) + : QLabel(parent, f), m_elide_mode(Qt::ElideRight) { + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); +} + +ElidedLabel::ElidedLabel(const QString& txt, QWidget* parent, Qt::WindowFlags f) + : QLabel(txt, parent, f), m_elide_mode(Qt::ElideRight) { + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); +} + +ElidedLabel::ElidedLabel(const QString& txt, Qt::TextElideMode elideMode, QWidget* parent, Qt::WindowFlags f) + : QLabel(txt, parent, f), m_elide_mode(elideMode) { + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + +} + +void ElidedLabel::setText(const QString& txt) { + QLabel::setText(txt); + cacheElidedText(geometry().width()); + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); +} + + +void ElidedLabel::cacheElidedText(int w) { + m_cached_elided_text = fontMetrics().elidedText(text(), m_elide_mode, w, (buddy() == nullptr)? 0 : Qt::TextShowMnemonic); +} + +void ElidedLabel::resizeEvent(QResizeEvent* e) { + QLabel::resizeEvent(e); + cacheElidedText(e->size().width()); +} + +void ElidedLabel::paintEvent(QPaintEvent* e) { + if(m_elide_mode == Qt::ElideNone) { + QLabel::paintEvent(e); + } else { + QPainter p(this); + p.drawText(0, 0, geometry().width(), geometry().height(), + QStyle::visualAlignment(text().isRightToLeft()? Qt::RightToLeft : Qt::LeftToRight, alignment()) | ((buddy() == nullptr)? 0 : Qt::TextShowMnemonic), + m_cached_elided_text); + } +} diff --git a/src/elidedlabel.h b/src/elidedlabel.h new file mode 100644 index 0000000..1ce5210 --- /dev/null +++ b/src/elidedlabel.h @@ -0,0 +1,49 @@ +#pragma once +#include <QLabel> +#include <QPainter> +#include <QResizeEvent> +#include <QString> +#include <QFont> + +// A label that elides its text when not enough geometry is available to show all of the text. +// Currently only capable of one-line. +class ElidedLabel : public QLabel { + Q_OBJECT + +private: + Qt::TextElideMode m_elide_mode; + QString m_cached_elided_text; + + +public: + ElidedLabel(QWidget* parent = NULL, Qt::WindowFlags f = 0); + ElidedLabel(const QString& txt, QWidget* parent = NULL, Qt::WindowFlags f = 0); + ElidedLabel(const QString& txt, Qt::TextElideMode elideMode = Qt::ElideRight, QWidget* parent = NULL, Qt::WindowFlags f = 0); + +public: + // Set the elide mode used for displaying text. + inline void setElideMode(Qt::TextElideMode elideMode) { + m_elide_mode = elideMode; + updateGeometry(); + } + + // Get the elide mode currently used to display text. + inline Qt::TextElideMode elideMode() const { + return m_elide_mode; + } + + + +public: // QLabel overrides + void setText(const QString&); // note: not virtual so no polymorphism ... + + +protected: // QLabel overrides + virtual void paintEvent(QPaintEvent*) override; + virtual void resizeEvent(QResizeEvent*) override; + +protected: + // Cache the elided text so as to not recompute it every paint event + void cacheElidedText(int w); + +}; diff --git a/src/icons.qrc b/src/icons.qrc new file mode 100644 index 0000000..5a346e3 --- /dev/null +++ b/src/icons.qrc @@ -0,0 +1,147 @@ +<RCC> + <qresource prefix="/"> + <file>icons/texture.png</file> + <file>icons/arrow-left-circle-line.png</file> + <file>icons/refresh-line.png</file> + <file>icons/arrow-right-circle-line.png</file> + <file>icons/disabled-arrow-left-circle-line.png</file> + <file>icons/disabled-arrow-right-circle-line.png</file> + <file>icons/download-line.png</file> + <file>icons/image-2-line.png</file> + <file>icons/setting-line.png</file> + <file>icons/map-pin-line.png</file> + <file>icons/folder-open-line.png</file> + <file>icons/paypal-line.png</file> + <file>icons/links-line.png</file> + <file>icons/star-line.png</file> + <file>icons/folder-download-line.png</file> + <file>icons/lock-2-fill.png</file> + <file>icons/questionnaire-line.png</file> + <file>icons/volume-up-line.png</file> + <file>icons/swap-box-line.png</file> + <file>icons/clipboard-line.png</file> + <file>icons/close-fill.png</file> + <file>icons/archive-drawer-line.png</file> + <file>icons/file-copy-line.png</file> + <file>icons/translate-2.png</file> + <file>icons/magic-line.png</file> + <file>icons/twitter-line.png</file> + <file>icons/mail-add-line.png</file> + <file>icons/file-text-line.png</file> + <file>icons/facebook-box-line.png</file> + <file>icons/stop-line.png</file> + <file>icons/loader-2-fill.png</file> + <file>icons/share-line.png</file> + <file>icons/error-warning-line.png</file> + <file>icons/play-line.png</file> + <file>icons/time-line.png</file> + <file>icons/pause-line.png</file> + <file>icons/focus-3-line.png</file> + <file>icons/arrow-right-s-line.png</file> + <file>icons/arrow-left-s-line.png</file> + <file>icons/drag-move-fill.png</file> + <file>icons/volume-mute-line.png</file> + <file>icons/scissors-cut-line.png</file> + <file>icons/play-fill.png</file> + <file>icons/screenshot-2-line.png</file> + <file>icons/search-2-line.png</file> + <file>icons/youtube-line.png</file> + <file>icons/terminal-box-line.png</file> + <file>icons/sip-line.png</file> + <file>icons/shape-2-line.png</file> + <file>icons/record-circle-line.png</file> + <file>icons/repeat-2-fill.png</file> + <file>icons/camera-off-line.png</file> + <file>icons/link-unlink-m.png</file> + <file>icons/eye-line.png</file> + <file>icons/information-fill.png</file> + <file>icons/information-line.png</file> + <file>icons/delete-bin-5-line.png</file> + <file>icons/delete-bin-3-line.png</file> + <file>icons/movie-2-line.png</file> + <file>icons/music-2-line.png</file> + <file>icons/file-unknow-line.png</file> + <file>icons/arrow-left-line.png</file> + <file>icons/arrow-right-line.png</file> + <file>icons/filter-2-line.png</file> + <file>icons/arrow-up-s-line.png</file> + <file>icons/arrow-down-s-line.png</file> + <file>icons/account-pin-circle-line.png</file> + <file>icons/shopping-cart-line.png</file> + <file>icons/picture-in-picture-line.png</file> + <file>icons/home-8-line.png</file> + <file>icons/white/white_scissors-cut-line.png</file> + <file>icons/white/white_play-fill.png</file> + <file>icons/white/white_terminal-box-line.png</file> + <file>icons/white/white_shape-2-line.png</file> + <file>icons/white/white_sip-line.png</file> + <file>icons/white/white_record-circle-line.png</file> + <file>icons/white/stop-line.png</file> + <file>icons/white/white_arrow-right-line.png</file> + <file>icons/white/white_arrow-left-line.png</file> + <file>icons/white/white_picture-in-picture-line.png</file> + <file>icons/others/monitor.png</file> + <file>icons/others/wall_placeholder_180.jpg</file> + <file>icons/categories/devices-and-iot.png</file> + <file>icons/categories/social.png</file> + <file>icons/categories/server-and-cloud.png</file> + <file>icons/categories/security.png</file> + <file>icons/categories/productivity.png</file> + <file>icons/categories/photo-and-video.png</file> + <file>icons/categories/art-and-design.png</file> + <file>icons/categories/personalisation.png</file> + <file>icons/categories/news-and-weather.png</file> + <file>icons/categories/music-and-audio.png</file> + <file>icons/categories/health-and-fitness.png</file> + <file>icons/categories/games.png</file> + <file>icons/categories/finance.png</file> + <file>icons/categories/featured.png</file> + <file>icons/categories/entertainment.png</file> + <file>icons/categories/education.png</file> + <file>icons/categories/science.png</file> + <file>icons/categories/development.png</file> + <file>icons/categories/books-and-reference.png</file> + <file>icons/categories/utilities.png</file> + <file>icons/others/snapcraft.png</file> + <file>icons/mail-line.png</file> + <file>icons/app/icon-256.png</file> + <file>icons/app/icon-128.png</file> + <file>icons/app/icon-64.png</file> + <file>icons/app/icon-32.png</file> + <file>icons/app/icon-16.png</file> + <file>icons/app/icon-512.png</file> + <file>icons/app/resize.sh</file> + <file>icons/others/greendot.png</file> + <file>icons/anticlockwise-line.png</file> + <file>icons/arrow-go-back-line.png</file> + <file>icons/arrow-go-forward-line.png</file> + <file>icons/clockwise-line.png</file> + <file>icons/crop-line.png</file> + <file>icons/facebook-line.png</file> + <file>icons/printer-line.png</file> + <file>icons/save-line.png</file> + <file>icons/grid-line.png</file> + <file>icons/shape-line.png</file> + <file>icons/compare-image-line.png</file> + <file>icons/fx-line.png</file> + <file>icons/zoom-out-line.png</file> + <file>icons/zoom-out-fill.png</file> + <file>icons/zoom-in-line.png</file> + <file>icons/zoom-in-fill.png</file> + <file>icons/split-cells-vertical.png</file> + <file>icons/split-cells-horizontal.png</file> + <file>icons/fullscreen-line.png</file> + <file>icons/clockwise-fill.png</file> + <file>icons/anticlockwise-fill.png</file> + <file>icons/others/Histogram.png</file> + <file>icons/tiktok-downloader.png</file> + <file>icons/icon-512.xcf</file> + <file>icons/blur-off-line.png</file> + <file>icons/lock-line.png</file> + <file>icons/lock-unlock-line.png</file> + <file>icons/funds-line.png</file> + <file>icons/others/tt_tbc_head.png</file> + <file>icons/app/whatsapp-message.svg</file> + <file>icons/app/whatsapp.svg</file> + </qresource> +</RCC> diff --git a/src/icons/account-pin-circle-line.png b/src/icons/account-pin-circle-line.png Binary files differnew file mode 100644 index 0000000..1e70a3d --- /dev/null +++ b/src/icons/account-pin-circle-line.png diff --git a/src/icons/anticlockwise-fill.png b/src/icons/anticlockwise-fill.png Binary files differnew file mode 100644 index 0000000..908a15a --- /dev/null +++ b/src/icons/anticlockwise-fill.png diff --git a/src/icons/anticlockwise-line.png b/src/icons/anticlockwise-line.png Binary files differnew file mode 100644 index 0000000..8cd98de --- /dev/null +++ b/src/icons/anticlockwise-line.png diff --git a/src/icons/app/icon-128.png b/src/icons/app/icon-128.png Binary files differnew file mode 100644 index 0000000..f625123 --- /dev/null +++ b/src/icons/app/icon-128.png diff --git a/src/icons/app/icon-16.png b/src/icons/app/icon-16.png Binary files differnew file mode 100644 index 0000000..e3ada9b --- /dev/null +++ b/src/icons/app/icon-16.png diff --git a/src/icons/app/icon-256.png b/src/icons/app/icon-256.png Binary files differnew file mode 100644 index 0000000..2e5155d --- /dev/null +++ b/src/icons/app/icon-256.png diff --git a/src/icons/app/icon-32.png b/src/icons/app/icon-32.png Binary files differnew file mode 100644 index 0000000..8b47514 --- /dev/null +++ b/src/icons/app/icon-32.png diff --git a/src/icons/app/icon-512.png b/src/icons/app/icon-512.png Binary files differnew file mode 100644 index 0000000..7974220 --- /dev/null +++ b/src/icons/app/icon-512.png diff --git a/src/icons/app/icon-64.png b/src/icons/app/icon-64.png Binary files differnew file mode 100644 index 0000000..175c8fb --- /dev/null +++ b/src/icons/app/icon-64.png diff --git a/src/icons/app/resize.sh b/src/icons/app/resize.sh new file mode 100755 index 0000000..a2fd54b --- /dev/null +++ b/src/icons/app/resize.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +# Edit the settings below: + +# Output sizes - +# Please note the format: each size is wrapped with quotes, width and height are separated with space. +output=("16 16" "32 32" "64 64" "128 128" "256 256" "512 512" ) + +# If you frequently use the same source file (f.e. "~/Desktop/src.jpg"), +# set it in "default_src" +default_src=""; + +# If you frequently use the same destination +# (f.e. "~/Desktop/Some_folder/%.jpg"), set it in "default_dst" +# Destination must include "%", it will be replaced by output size, f.e. "800x600" +default_dst="%.png"; + +# Add signature? +default_sign='n' + +# If you frequently use the same signature file (f.e. "~/Desktop/sig.png"), +# set it in "default_sig" +default_sig=""; + +# Gravity is for cropping left/right edges for different proportions (center, east, west) +default_gravity="center" + +# Output JPG quality: maximum is 100 (recommended) +quality=100 + +# ====== +# Do not edit below. +# ====== +# Welcome to Smashing resizer! +# Written by Vlad Gerasimov from http://www.vladstudio.com +# +# This script takes one "source" image and saves it in different sizes. +# +# Requires: +# * imagemagick - http://www.imagemagick.org/ +# * python - I'm sure your PC already has it! + + + +# Unfortunately, bash is awful at math operations. +# We'll create a simple function that handles math for us. +# Example: $(math 2 * 2) + +function math(){ + echo $(python -c "from __future__ import division; print $@") +} + + + +# To make our script short and nice, here is the "save()" function. +# We'll use it to save each size. + +function save(){ + + # read target width and height from function parameters + local dst_w=${1} + local dst_h=${2} + + # calculate ratio + local ratio=$(math $dst_w/$dst_h); + + # calculate "intermediate" width and height + local inter_w=$(math "int(round($src_h*$ratio))") + local inter_h=${src_h} + + # which size we're saving now + #local size="${dst_w}x${dst_h}" + local size="${dst_w}" + echo "Saving ${size}..." + + #crop intermediate image (with target ratio) + convert ${src} -gravity ${gravity} -crop ${inter_w}x${inter_h}+0+0 +repage temp.psd + + # apply signature + if [ "${sign}" == "y" ]; then + convert temp.psd ${sig} -gravity southeast -geometry ${sig_w}x${sig_h}+24+48 -composite temp.psd + fi + + ## For best quality, I resize image 80%, sharpen 3 times, then repeat. + + # setup resize filter and unsharp parameters (calculated through trial and error) + local arguments="-interpolate bicubic -filter Lagrange" + local unsharp="-unsharp 0.4x0.4+0.4+0.008" + + # scale 80%, sharpen, repeat until less than 150% of target size + local current_w=${dst_w} + + # pardon moi for such ugly while condition! + while [ $(math "${current_w}/${dst_w} > 1.5") = "True" ]; do + current_w=$(math ${current_w}\*0\.80) + current_w=$(math "int(round(${current_w}))") + arguments="${arguments} -resize 80% +repage ${unsharp} ${unsharp} ${unsharp} " + done + + # final resize + arguments="${arguments} -resize ${dst_w}x${dst_h}! +repage ${unsharp} ${unsharp} ${unsharp} -density 72x72 +repage" + + # final convert! resize, sharpen, save + convert temp.psd ${arguments} -quality ${quality} ${dst/\%/${size}} + +} + +# Ask for source image, or use default value +echo "Enter path to source image, or hit Enter to keep default value (${default_src}): " +read src +src=${src:-${default_src}} + +# ask for destination path, or use default value +echo "Enter destination path, or hit Enter to keep default value (${default_dst})." +echo "must include % symbol, it will be replaced by output size, f.e. '800x600'" +read dst +dst=${dst:-${default_dst}} + +# Ask for signature image, or use default value +echo "Apply signature? (hit Enter for 'yes' or type 'n' for 'no') " +read sign +sign=${sign:-${default_sign}} + +# Ask for signature image, or use default value +echo "Enter path to signature image, or hit Enter to keep default value (${default_sig}): " +read sig +sig=${sig:-${default_sig}} + +# ask for gravity, or use default value +echo "Enter gravity for cropping left/right edges (center, east, west), or hit Enter to keep default value (${default_gravity}): " +read gravity +gravity=${gravity:-${default_gravity}} + + +# detect source image width and height +src_w=$(identify -format "%w" "${src}") +src_h=$(identify -format "%h" "${src}") + +# detect signature width and height +if [ "${sign}" == "y" ]; then + sig_w=$(identify -format "%w" "${sig}") + sig_h=$(identify -format "%h" "${sig}") +fi + +# loop throught output sizes and save each size +for i in "${output[@]}" +do + save ${i} +done + +# Delete temporary file +rm temp.psd + +# Done! +echo "Done!" diff --git a/src/icons/app/whatsapp-message.svg b/src/icons/app/whatsapp-message.svg new file mode 100644 index 0000000..7f56fa4 --- /dev/null +++ b/src/icons/app/whatsapp-message.svg @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="100%" + style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;" + version="1.1" + viewBox="0 0 24 24" + width="100%" + xml:space="preserve" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="whatsapp.svg"><metadata + id="metadata19"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs17"><filter + inkscape:label="Fill Background" + inkscape:menu="Fill and Transparency" + inkscape:menu-tooltip="Adds a colorizable opaque background" + style="color-interpolation-filters:sRGB;" + id="filter843"><feFlood + result="result1" + flood-color="rgb(255,0,0)" + flood-opacity="1" + id="feFlood845" /><feImage + xlink:href="" + result="result2" + id="feImage847" /><feBlend + result="result4" + in2="result1" + mode="multiply" + id="feBlend849" /><feMerge + result="result3" + id="feMerge851"><feMergeNode + in="result1" + id="feMergeNode853" /><feMergeNode + in="SourceGraphic" + id="feMergeNode855" /></feMerge></filter></defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1311" + inkscape:window-height="744" + id="namedview15" + showgrid="false" + inkscape:zoom="8.4606523" + inkscape:cx="9.4540046" + inkscape:cy="19.836975" + inkscape:window-x="55" + inkscape:window-y="24" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><rect + height="24" + id="Artboard1" + style="fill:none;" + width="24" + x="0" + y="0" /><g + id="g5"><g + id="g7"><path + d="M4.018,17.048c-0.96,-1.484 -1.518,-3.253 -1.518,-5.151c0,-5.243 4.257,-9.5 9.5,-9.5c5.243,0 9.5,4.257 9.5,9.5c0,5.243 -4.257,9.5 -9.5,9.5c-1.777,0 -3.44,-0.489 -4.863,-1.339l-4.637,1.545l1.518,-4.555Z" + style="fill:#f3f3f3;" + id="path9" /><path + d="M5.795,16.304c-0.886,-1.244 -1.407,-2.765 -1.407,-4.407c0,-4.201 3.411,-7.612 7.612,-7.612c4.201,0 7.612,3.411 7.612,7.612c0,4.201 -3.411,7.611 -7.612,7.611c-1.59,0 -3.066,-0.488 -4.288,-1.323l-2.862,0.954l0.945,-2.835Z" + style="fill:#00a82d;" + id="path11" /></g><path + d="M9.714,13.873c-1.124,-1.374 -1.874,-3.056 -2.109,-4.88c-0.063,-0.508 0.106,-1.018 0.461,-1.387c0.355,-0.369 0.858,-0.558 1.368,-0.515l0.049,0.005c0,0 0.561,0.15 0.868,0.233c0.122,0.033 0.219,0.124 0.26,0.243c0.138,0.41 0.464,1.373 0.618,1.826c0.05,0.147 0.004,0.31 -0.114,0.41c-0.233,0.196 -0.618,0.52 -0.858,0.723c-0.129,0.109 -0.17,0.29 -0.1,0.443c0.279,0.608 0.635,1.176 1.057,1.69c0.434,0.502 0.933,0.949 1.485,1.327c0.14,0.095 0.325,0.085 0.454,-0.024c0.241,-0.202 0.626,-0.526 0.858,-0.722c0.119,-0.1 0.287,-0.117 0.424,-0.043c0.42,0.228 1.314,0.712 1.694,0.918c0.111,0.06 0.185,0.172 0.196,0.297c0.029,0.317 0.083,0.895 0.083,0.895l-0.004,0.049c-0.044,0.51 -0.315,0.974 -0.739,1.261c-0.424,0.288 -0.955,0.368 -1.445,0.22c-1.772,-0.545 -3.313,-1.581 -4.479,-2.937l-0.027,-0.032Z" + style="fill:#f3f3f3;" + id="path13" /></g><path + style="fill:#ff0000;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:1.41420996;stroke-dasharray:none;stroke-opacity:1" + id="path904" + sodipodi:type="arc" + sodipodi:cx="17.079062" + sodipodi:cy="7.2755213" + sodipodi:rx="2.6593695" + sodipodi:ry="2.6593695" + sodipodi:start="0" + sodipodi:end="6.2744587" + sodipodi:open="true" + d="M 19.738431,7.2755213 A 2.6593695,2.6593695 0 0 1 17.084863,9.9348844 2.6593695,2.6593695 0 0 1 14.419717,7.2871249 2.6593695,2.6593695 0 0 1 17.061656,4.6162088 2.6593695,2.6593695 0 0 1 19.73833,7.2523143" + inkscape:transform-center-x="-0.23638866" + inkscape:transform-center-y="-0.70916464" /></svg>
\ No newline at end of file diff --git a/src/icons/app/whatsapp.svg b/src/icons/app/whatsapp.svg new file mode 100644 index 0000000..0b81505 --- /dev/null +++ b/src/icons/app/whatsapp.svg @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="100%" + style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;" + version="1.1" + viewBox="0 0 24 24" + width="100%" + xml:space="preserve" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="whatsapp.svg"><metadata + id="metadata19"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs + id="defs17"><filter + inkscape:label="Fill Background" + inkscape:menu="Fill and Transparency" + inkscape:menu-tooltip="Adds a colorizable opaque background" + style="color-interpolation-filters:sRGB;" + id="filter843"><feFlood + result="result1" + flood-color="rgb(255,0,0)" + flood-opacity="1" + id="feFlood845" /><feImage + xlink:href="" + result="result2" + id="feImage847" /><feBlend + result="result4" + in2="result1" + mode="multiply" + id="feBlend849" /><feMerge + result="result3" + id="feMerge851"><feMergeNode + in="result1" + id="feMergeNode853" /><feMergeNode + in="SourceGraphic" + id="feMergeNode855" /></feMerge></filter></defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1311" + inkscape:window-height="744" + id="namedview15" + showgrid="false" + inkscape:zoom="8.4606523" + inkscape:cx="-10.8163" + inkscape:cy="19.836975" + inkscape:window-x="55" + inkscape:window-y="24" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><rect + height="24" + id="Artboard1" + style="fill:none;" + width="24" + x="0" + y="0" /><g + id="g5"><g + id="g7"><path + d="M4.018,17.048c-0.96,-1.484 -1.518,-3.253 -1.518,-5.151c0,-5.243 4.257,-9.5 9.5,-9.5c5.243,0 9.5,4.257 9.5,9.5c0,5.243 -4.257,9.5 -9.5,9.5c-1.777,0 -3.44,-0.489 -4.863,-1.339l-4.637,1.545l1.518,-4.555Z" + style="fill:#f3f3f3;" + id="path9" /><path + d="M5.795,16.304c-0.886,-1.244 -1.407,-2.765 -1.407,-4.407c0,-4.201 3.411,-7.612 7.612,-7.612c4.201,0 7.612,3.411 7.612,7.612c0,4.201 -3.411,7.611 -7.612,7.611c-1.59,0 -3.066,-0.488 -4.288,-1.323l-2.862,0.954l0.945,-2.835Z" + style="fill:#00a82d;" + id="path11" /></g><path + d="M9.714,13.873c-1.124,-1.374 -1.874,-3.056 -2.109,-4.88c-0.063,-0.508 0.106,-1.018 0.461,-1.387c0.355,-0.369 0.858,-0.558 1.368,-0.515l0.049,0.005c0,0 0.561,0.15 0.868,0.233c0.122,0.033 0.219,0.124 0.26,0.243c0.138,0.41 0.464,1.373 0.618,1.826c0.05,0.147 0.004,0.31 -0.114,0.41c-0.233,0.196 -0.618,0.52 -0.858,0.723c-0.129,0.109 -0.17,0.29 -0.1,0.443c0.279,0.608 0.635,1.176 1.057,1.69c0.434,0.502 0.933,0.949 1.485,1.327c0.14,0.095 0.325,0.085 0.454,-0.024c0.241,-0.202 0.626,-0.526 0.858,-0.722c0.119,-0.1 0.287,-0.117 0.424,-0.043c0.42,0.228 1.314,0.712 1.694,0.918c0.111,0.06 0.185,0.172 0.196,0.297c0.029,0.317 0.083,0.895 0.083,0.895l-0.004,0.049c-0.044,0.51 -0.315,0.974 -0.739,1.261c-0.424,0.288 -0.955,0.368 -1.445,0.22c-1.772,-0.545 -3.313,-1.581 -4.479,-2.937l-0.027,-0.032Z" + style="fill:#f3f3f3;" + id="path13" /></g></svg>
\ No newline at end of file diff --git a/src/icons/archive-drawer-line.png b/src/icons/archive-drawer-line.png Binary files differnew file mode 100644 index 0000000..c770801 --- /dev/null +++ b/src/icons/archive-drawer-line.png diff --git a/src/icons/arrow-down-s-line.png b/src/icons/arrow-down-s-line.png Binary files differnew file mode 100644 index 0000000..ab85daa --- /dev/null +++ b/src/icons/arrow-down-s-line.png diff --git a/src/icons/arrow-go-back-line.png b/src/icons/arrow-go-back-line.png Binary files differnew file mode 100644 index 0000000..1081e0a --- /dev/null +++ b/src/icons/arrow-go-back-line.png diff --git a/src/icons/arrow-go-forward-line.png b/src/icons/arrow-go-forward-line.png Binary files differnew file mode 100644 index 0000000..ef65a0a --- /dev/null +++ b/src/icons/arrow-go-forward-line.png diff --git a/src/icons/arrow-left-circle-line.png b/src/icons/arrow-left-circle-line.png Binary files differnew file mode 100644 index 0000000..4d9d808 --- /dev/null +++ b/src/icons/arrow-left-circle-line.png diff --git a/src/icons/arrow-left-line.png b/src/icons/arrow-left-line.png Binary files differnew file mode 100644 index 0000000..411f8d5 --- /dev/null +++ b/src/icons/arrow-left-line.png diff --git a/src/icons/arrow-left-s-line.png b/src/icons/arrow-left-s-line.png Binary files differnew file mode 100644 index 0000000..6c34c72 --- /dev/null +++ b/src/icons/arrow-left-s-line.png diff --git a/src/icons/arrow-right-circle-line.png b/src/icons/arrow-right-circle-line.png Binary files differnew file mode 100644 index 0000000..a993751 --- /dev/null +++ b/src/icons/arrow-right-circle-line.png diff --git a/src/icons/arrow-right-line.png b/src/icons/arrow-right-line.png Binary files differnew file mode 100644 index 0000000..9085577 --- /dev/null +++ b/src/icons/arrow-right-line.png diff --git a/src/icons/arrow-right-s-line.png b/src/icons/arrow-right-s-line.png Binary files differnew file mode 100644 index 0000000..6c1aaa5 --- /dev/null +++ b/src/icons/arrow-right-s-line.png diff --git a/src/icons/arrow-up-s-line.png b/src/icons/arrow-up-s-line.png Binary files differnew file mode 100644 index 0000000..04b0500 --- /dev/null +++ b/src/icons/arrow-up-s-line.png diff --git a/src/icons/blur-off-line.png b/src/icons/blur-off-line.png Binary files differnew file mode 100644 index 0000000..1d20104 --- /dev/null +++ b/src/icons/blur-off-line.png diff --git a/src/icons/camera-off-line.png b/src/icons/camera-off-line.png Binary files differnew file mode 100644 index 0000000..f748ce5 --- /dev/null +++ b/src/icons/camera-off-line.png diff --git a/src/icons/categories/art-and-design.png b/src/icons/categories/art-and-design.png Binary files differnew file mode 100644 index 0000000..706dcdd --- /dev/null +++ b/src/icons/categories/art-and-design.png diff --git a/src/icons/categories/books-and-reference.png b/src/icons/categories/books-and-reference.png Binary files differnew file mode 100644 index 0000000..9684a3c --- /dev/null +++ b/src/icons/categories/books-and-reference.png diff --git a/src/icons/categories/development.png b/src/icons/categories/development.png Binary files differnew file mode 100644 index 0000000..b81758b --- /dev/null +++ b/src/icons/categories/development.png diff --git a/src/icons/categories/devices-and-iot.png b/src/icons/categories/devices-and-iot.png Binary files differnew file mode 100644 index 0000000..b81ee28 --- /dev/null +++ b/src/icons/categories/devices-and-iot.png diff --git a/src/icons/categories/education.png b/src/icons/categories/education.png Binary files differnew file mode 100644 index 0000000..5d474c4 --- /dev/null +++ b/src/icons/categories/education.png diff --git a/src/icons/categories/entertainment.png b/src/icons/categories/entertainment.png Binary files differnew file mode 100644 index 0000000..53bf12b --- /dev/null +++ b/src/icons/categories/entertainment.png diff --git a/src/icons/categories/featured.png b/src/icons/categories/featured.png Binary files differnew file mode 100644 index 0000000..0bc0e29 --- /dev/null +++ b/src/icons/categories/featured.png diff --git a/src/icons/categories/finance.png b/src/icons/categories/finance.png Binary files differnew file mode 100644 index 0000000..c6edb26 --- /dev/null +++ b/src/icons/categories/finance.png diff --git a/src/icons/categories/games.png b/src/icons/categories/games.png Binary files differnew file mode 100644 index 0000000..2578a5f --- /dev/null +++ b/src/icons/categories/games.png diff --git a/src/icons/categories/health-and-fitness.png b/src/icons/categories/health-and-fitness.png Binary files differnew file mode 100644 index 0000000..bf36890 --- /dev/null +++ b/src/icons/categories/health-and-fitness.png diff --git a/src/icons/categories/music-and-audio.png b/src/icons/categories/music-and-audio.png Binary files differnew file mode 100644 index 0000000..a226a08 --- /dev/null +++ b/src/icons/categories/music-and-audio.png diff --git a/src/icons/categories/news-and-weather.png b/src/icons/categories/news-and-weather.png Binary files differnew file mode 100644 index 0000000..97b5fe2 --- /dev/null +++ b/src/icons/categories/news-and-weather.png diff --git a/src/icons/categories/personalisation.png b/src/icons/categories/personalisation.png Binary files differnew file mode 100644 index 0000000..352226f --- /dev/null +++ b/src/icons/categories/personalisation.png diff --git a/src/icons/categories/photo-and-video.png b/src/icons/categories/photo-and-video.png Binary files differnew file mode 100644 index 0000000..554a3cb --- /dev/null +++ b/src/icons/categories/photo-and-video.png diff --git a/src/icons/categories/productivity.png b/src/icons/categories/productivity.png Binary files differnew file mode 100644 index 0000000..0bb33f4 --- /dev/null +++ b/src/icons/categories/productivity.png diff --git a/src/icons/categories/science.png b/src/icons/categories/science.png Binary files differnew file mode 100644 index 0000000..069e83a --- /dev/null +++ b/src/icons/categories/science.png diff --git a/src/icons/categories/security.png b/src/icons/categories/security.png Binary files differnew file mode 100644 index 0000000..22132a5 --- /dev/null +++ b/src/icons/categories/security.png diff --git a/src/icons/categories/server-and-cloud.png b/src/icons/categories/server-and-cloud.png Binary files differnew file mode 100644 index 0000000..0a69247 --- /dev/null +++ b/src/icons/categories/server-and-cloud.png diff --git a/src/icons/categories/social.png b/src/icons/categories/social.png Binary files differnew file mode 100644 index 0000000..39e5478 --- /dev/null +++ b/src/icons/categories/social.png diff --git a/src/icons/categories/utilities.png b/src/icons/categories/utilities.png Binary files differnew file mode 100644 index 0000000..95a642d --- /dev/null +++ b/src/icons/categories/utilities.png diff --git a/src/icons/clipboard-line.png b/src/icons/clipboard-line.png Binary files differnew file mode 100644 index 0000000..18ba32d --- /dev/null +++ b/src/icons/clipboard-line.png diff --git a/src/icons/clockwise-fill.png b/src/icons/clockwise-fill.png Binary files differnew file mode 100644 index 0000000..75a577d --- /dev/null +++ b/src/icons/clockwise-fill.png diff --git a/src/icons/clockwise-line.png b/src/icons/clockwise-line.png Binary files differnew file mode 100644 index 0000000..6bf7aca --- /dev/null +++ b/src/icons/clockwise-line.png diff --git a/src/icons/close-fill.png b/src/icons/close-fill.png Binary files differnew file mode 100644 index 0000000..63fbd29 --- /dev/null +++ b/src/icons/close-fill.png diff --git a/src/icons/compare-image-line.png b/src/icons/compare-image-line.png Binary files differnew file mode 100644 index 0000000..f080138 --- /dev/null +++ b/src/icons/compare-image-line.png diff --git a/src/icons/crop-line.png b/src/icons/crop-line.png Binary files differnew file mode 100644 index 0000000..edf9098 --- /dev/null +++ b/src/icons/crop-line.png diff --git a/src/icons/delete-bin-3-line.png b/src/icons/delete-bin-3-line.png Binary files differnew file mode 100644 index 0000000..bf62d84 --- /dev/null +++ b/src/icons/delete-bin-3-line.png diff --git a/src/icons/delete-bin-5-line.png b/src/icons/delete-bin-5-line.png Binary files differnew file mode 100644 index 0000000..947a403 --- /dev/null +++ b/src/icons/delete-bin-5-line.png diff --git a/src/icons/disabled-arrow-left-circle-line.png b/src/icons/disabled-arrow-left-circle-line.png Binary files differnew file mode 100644 index 0000000..67769ff --- /dev/null +++ b/src/icons/disabled-arrow-left-circle-line.png diff --git a/src/icons/disabled-arrow-right-circle-line.png b/src/icons/disabled-arrow-right-circle-line.png Binary files differnew file mode 100644 index 0000000..078e72a --- /dev/null +++ b/src/icons/disabled-arrow-right-circle-line.png diff --git a/src/icons/download-line.png b/src/icons/download-line.png Binary files differnew file mode 100644 index 0000000..a8f6cec --- /dev/null +++ b/src/icons/download-line.png diff --git a/src/icons/drag-move-fill.png b/src/icons/drag-move-fill.png Binary files differnew file mode 100644 index 0000000..1e8b211 --- /dev/null +++ b/src/icons/drag-move-fill.png diff --git a/src/icons/error-warning-line.png b/src/icons/error-warning-line.png Binary files differnew file mode 100644 index 0000000..9c6e522 --- /dev/null +++ b/src/icons/error-warning-line.png diff --git a/src/icons/eye-line.png b/src/icons/eye-line.png Binary files differnew file mode 100644 index 0000000..c856fa5 --- /dev/null +++ b/src/icons/eye-line.png diff --git a/src/icons/facebook-box-line.png b/src/icons/facebook-box-line.png Binary files differnew file mode 100644 index 0000000..c3c9a35 --- /dev/null +++ b/src/icons/facebook-box-line.png diff --git a/src/icons/facebook-line.png b/src/icons/facebook-line.png Binary files differnew file mode 100644 index 0000000..e0a5205 --- /dev/null +++ b/src/icons/facebook-line.png diff --git a/src/icons/file-copy-line.png b/src/icons/file-copy-line.png Binary files differnew file mode 100644 index 0000000..f07fbcb --- /dev/null +++ b/src/icons/file-copy-line.png diff --git a/src/icons/file-text-line.png b/src/icons/file-text-line.png Binary files differnew file mode 100644 index 0000000..93be6b6 --- /dev/null +++ b/src/icons/file-text-line.png diff --git a/src/icons/file-unknow-line.png b/src/icons/file-unknow-line.png Binary files differnew file mode 100644 index 0000000..30ceffc --- /dev/null +++ b/src/icons/file-unknow-line.png diff --git a/src/icons/filter-2-line.png b/src/icons/filter-2-line.png Binary files differnew file mode 100644 index 0000000..04b127c --- /dev/null +++ b/src/icons/filter-2-line.png diff --git a/src/icons/focus-3-line.png b/src/icons/focus-3-line.png Binary files differnew file mode 100644 index 0000000..acd8380 --- /dev/null +++ b/src/icons/focus-3-line.png diff --git a/src/icons/folder-download-line.png b/src/icons/folder-download-line.png Binary files differnew file mode 100644 index 0000000..a971c93 --- /dev/null +++ b/src/icons/folder-download-line.png diff --git a/src/icons/folder-open-line.png b/src/icons/folder-open-line.png Binary files differnew file mode 100644 index 0000000..7049366 --- /dev/null +++ b/src/icons/folder-open-line.png diff --git a/src/icons/fullscreen-line.png b/src/icons/fullscreen-line.png Binary files differnew file mode 100644 index 0000000..788249d --- /dev/null +++ b/src/icons/fullscreen-line.png diff --git a/src/icons/funds-line.png b/src/icons/funds-line.png Binary files differnew file mode 100644 index 0000000..3c38303 --- /dev/null +++ b/src/icons/funds-line.png diff --git a/src/icons/fx-line.png b/src/icons/fx-line.png Binary files differnew file mode 100644 index 0000000..e443c06 --- /dev/null +++ b/src/icons/fx-line.png diff --git a/src/icons/grid-line.png b/src/icons/grid-line.png Binary files differnew file mode 100644 index 0000000..dae2987 --- /dev/null +++ b/src/icons/grid-line.png diff --git a/src/icons/home-8-line.png b/src/icons/home-8-line.png Binary files differnew file mode 100644 index 0000000..5811472 --- /dev/null +++ b/src/icons/home-8-line.png diff --git a/src/icons/icon-512.xcf b/src/icons/icon-512.xcf Binary files differnew file mode 100644 index 0000000..b9275f6 --- /dev/null +++ b/src/icons/icon-512.xcf diff --git a/src/icons/image-2-line.png b/src/icons/image-2-line.png Binary files differnew file mode 100644 index 0000000..f1c2494 --- /dev/null +++ b/src/icons/image-2-line.png diff --git a/src/icons/information-fill.png b/src/icons/information-fill.png Binary files differnew file mode 100644 index 0000000..ddcd05d --- /dev/null +++ b/src/icons/information-fill.png diff --git a/src/icons/information-line.png b/src/icons/information-line.png Binary files differnew file mode 100644 index 0000000..8336ada --- /dev/null +++ b/src/icons/information-line.png diff --git a/src/icons/link-unlink-m.png b/src/icons/link-unlink-m.png Binary files differnew file mode 100644 index 0000000..9a4c56e --- /dev/null +++ b/src/icons/link-unlink-m.png diff --git a/src/icons/links-line.png b/src/icons/links-line.png Binary files differnew file mode 100644 index 0000000..c0e3eaf --- /dev/null +++ b/src/icons/links-line.png diff --git a/src/icons/loader-2-fill.png b/src/icons/loader-2-fill.png Binary files differnew file mode 100644 index 0000000..044be77 --- /dev/null +++ b/src/icons/loader-2-fill.png diff --git a/src/icons/lock-2-fill.png b/src/icons/lock-2-fill.png Binary files differnew file mode 100644 index 0000000..86b7d7c --- /dev/null +++ b/src/icons/lock-2-fill.png diff --git a/src/icons/lock-line.png b/src/icons/lock-line.png Binary files differnew file mode 100644 index 0000000..6370adc --- /dev/null +++ b/src/icons/lock-line.png diff --git a/src/icons/lock-unlock-line.png b/src/icons/lock-unlock-line.png Binary files differnew file mode 100644 index 0000000..d2bf8e9 --- /dev/null +++ b/src/icons/lock-unlock-line.png diff --git a/src/icons/magic-line.png b/src/icons/magic-line.png Binary files differnew file mode 100644 index 0000000..2422682 --- /dev/null +++ b/src/icons/magic-line.png diff --git a/src/icons/mail-add-line.png b/src/icons/mail-add-line.png Binary files differnew file mode 100644 index 0000000..d04eba5 --- /dev/null +++ b/src/icons/mail-add-line.png diff --git a/src/icons/mail-line.png b/src/icons/mail-line.png Binary files differnew file mode 100644 index 0000000..c2a13cc --- /dev/null +++ b/src/icons/mail-line.png diff --git a/src/icons/map-pin-line.png b/src/icons/map-pin-line.png Binary files differnew file mode 100644 index 0000000..159ca5b --- /dev/null +++ b/src/icons/map-pin-line.png diff --git a/src/icons/movie-2-line.png b/src/icons/movie-2-line.png Binary files differnew file mode 100644 index 0000000..dbfdf7f --- /dev/null +++ b/src/icons/movie-2-line.png diff --git a/src/icons/music-2-line.png b/src/icons/music-2-line.png Binary files differnew file mode 100644 index 0000000..d2edf8c --- /dev/null +++ b/src/icons/music-2-line.png diff --git a/src/icons/others/Histogram.png b/src/icons/others/Histogram.png Binary files differnew file mode 100644 index 0000000..d2bddb3 --- /dev/null +++ b/src/icons/others/Histogram.png diff --git a/src/icons/others/Omonitor.png b/src/icons/others/Omonitor.png Binary files differnew file mode 100644 index 0000000..f1c1a64 --- /dev/null +++ b/src/icons/others/Omonitor.png diff --git a/src/icons/others/greendot.png b/src/icons/others/greendot.png Binary files differnew file mode 100644 index 0000000..be914c6 --- /dev/null +++ b/src/icons/others/greendot.png diff --git a/src/icons/others/monitor.png b/src/icons/others/monitor.png Binary files differnew file mode 100644 index 0000000..7ba4759 --- /dev/null +++ b/src/icons/others/monitor.png diff --git a/src/icons/others/snapcraft.png b/src/icons/others/snapcraft.png Binary files differnew file mode 100644 index 0000000..da50180 --- /dev/null +++ b/src/icons/others/snapcraft.png diff --git a/src/icons/others/snapcraft.svg b/src/icons/others/snapcraft.svg new file mode 100644 index 0000000..a14c440 --- /dev/null +++ b/src/icons/others/snapcraft.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + aria-hidden="true" + focusable="false" + width="1em" + height="1em" + style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" + preserveAspectRatio="xMidYMid meet" + viewBox="0 0 24 24" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="snapcraft.svg"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1311" + inkscape:window-height="744" + id="namedview6" + showgrid="false" + inkscape:zoom="9.8333333" + inkscape:cx="12" + inkscape:cy="12" + inkscape:window-x="55" + inkscape:window-y="24" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /> + <path + d="M13.804 13.367V5.69l5.292 2.362l-5.292 5.315zM3.701 23.514l6.49-12.22l2.847 2.843L3.7 23.514zM0 .486l13.355 4.848v8.484L0 .486zm21.803 4.848H14.11L24 9.748z" + fill="#626262" + id="path4" + style="fill-opacity:1;fill:#9fa0a4" /> +</svg> diff --git a/src/icons/others/tt_tbc_head.png b/src/icons/others/tt_tbc_head.png Binary files differnew file mode 100644 index 0000000..20cac2c --- /dev/null +++ b/src/icons/others/tt_tbc_head.png diff --git a/src/icons/others/wall_placeholder_180.jpg b/src/icons/others/wall_placeholder_180.jpg Binary files differnew file mode 100644 index 0000000..d696b5f --- /dev/null +++ b/src/icons/others/wall_placeholder_180.jpg diff --git a/src/icons/pause-line.png b/src/icons/pause-line.png Binary files differnew file mode 100644 index 0000000..4be8f55 --- /dev/null +++ b/src/icons/pause-line.png diff --git a/src/icons/paypal-line.png b/src/icons/paypal-line.png Binary files differnew file mode 100644 index 0000000..f6ac01d --- /dev/null +++ b/src/icons/paypal-line.png diff --git a/src/icons/picture-in-picture-line.png b/src/icons/picture-in-picture-line.png Binary files differnew file mode 100644 index 0000000..4416bfd --- /dev/null +++ b/src/icons/picture-in-picture-line.png diff --git a/src/icons/play-fill.png b/src/icons/play-fill.png Binary files differnew file mode 100644 index 0000000..1ead6ba --- /dev/null +++ b/src/icons/play-fill.png diff --git a/src/icons/play-line.png b/src/icons/play-line.png Binary files differnew file mode 100644 index 0000000..9561499 --- /dev/null +++ b/src/icons/play-line.png diff --git a/src/icons/printer-line.png b/src/icons/printer-line.png Binary files differnew file mode 100644 index 0000000..fed36d3 --- /dev/null +++ b/src/icons/printer-line.png diff --git a/src/icons/questionnaire-line.png b/src/icons/questionnaire-line.png Binary files differnew file mode 100644 index 0000000..9c1fcc6 --- /dev/null +++ b/src/icons/questionnaire-line.png diff --git a/src/icons/record-circle-line.png b/src/icons/record-circle-line.png Binary files differnew file mode 100644 index 0000000..ad66257 --- /dev/null +++ b/src/icons/record-circle-line.png diff --git a/src/icons/refresh-line.png b/src/icons/refresh-line.png Binary files differnew file mode 100644 index 0000000..c105522 --- /dev/null +++ b/src/icons/refresh-line.png diff --git a/src/icons/repeat-2-fill.png b/src/icons/repeat-2-fill.png Binary files differnew file mode 100644 index 0000000..f32602a --- /dev/null +++ b/src/icons/repeat-2-fill.png diff --git a/src/icons/save-line.png b/src/icons/save-line.png Binary files differnew file mode 100644 index 0000000..b0206e9 --- /dev/null +++ b/src/icons/save-line.png diff --git a/src/icons/scissors-cut-line.png b/src/icons/scissors-cut-line.png Binary files differnew file mode 100644 index 0000000..3ee54d7 --- /dev/null +++ b/src/icons/scissors-cut-line.png diff --git a/src/icons/screenshot-2-line.png b/src/icons/screenshot-2-line.png Binary files differnew file mode 100644 index 0000000..567ce04 --- /dev/null +++ b/src/icons/screenshot-2-line.png diff --git a/src/icons/search-2-line.png b/src/icons/search-2-line.png Binary files differnew file mode 100644 index 0000000..453acd1 --- /dev/null +++ b/src/icons/search-2-line.png diff --git a/src/icons/setting-line.png b/src/icons/setting-line.png Binary files differnew file mode 100644 index 0000000..fcbd74c --- /dev/null +++ b/src/icons/setting-line.png diff --git a/src/icons/shape-2-line.png b/src/icons/shape-2-line.png Binary files differnew file mode 100644 index 0000000..a230b14 --- /dev/null +++ b/src/icons/shape-2-line.png diff --git a/src/icons/shape-line.png b/src/icons/shape-line.png Binary files differnew file mode 100644 index 0000000..d5944fb --- /dev/null +++ b/src/icons/shape-line.png diff --git a/src/icons/share-line.png b/src/icons/share-line.png Binary files differnew file mode 100644 index 0000000..22bf75d --- /dev/null +++ b/src/icons/share-line.png diff --git a/src/icons/shopping-cart-line.png b/src/icons/shopping-cart-line.png Binary files differnew file mode 100644 index 0000000..219b634 --- /dev/null +++ b/src/icons/shopping-cart-line.png diff --git a/src/icons/sip-line.png b/src/icons/sip-line.png Binary files differnew file mode 100644 index 0000000..16aeb6c --- /dev/null +++ b/src/icons/sip-line.png diff --git a/src/icons/split-cells-horizontal.png b/src/icons/split-cells-horizontal.png Binary files differnew file mode 100644 index 0000000..b180049 --- /dev/null +++ b/src/icons/split-cells-horizontal.png diff --git a/src/icons/split-cells-vertical.png b/src/icons/split-cells-vertical.png Binary files differnew file mode 100644 index 0000000..c741af8 --- /dev/null +++ b/src/icons/split-cells-vertical.png diff --git a/src/icons/star-line.png b/src/icons/star-line.png Binary files differnew file mode 100644 index 0000000..9f9a328 --- /dev/null +++ b/src/icons/star-line.png diff --git a/src/icons/stop-line.png b/src/icons/stop-line.png Binary files differnew file mode 100644 index 0000000..2bda682 --- /dev/null +++ b/src/icons/stop-line.png diff --git a/src/icons/swap-box-line.png b/src/icons/swap-box-line.png Binary files differnew file mode 100644 index 0000000..0f270b2 --- /dev/null +++ b/src/icons/swap-box-line.png diff --git a/src/icons/terminal-box-line.png b/src/icons/terminal-box-line.png Binary files differnew file mode 100644 index 0000000..3e66e7d --- /dev/null +++ b/src/icons/terminal-box-line.png diff --git a/src/icons/texture.png b/src/icons/texture.png Binary files differnew file mode 100644 index 0000000..12bae83 --- /dev/null +++ b/src/icons/texture.png diff --git a/src/icons/tiktok-downloader.png b/src/icons/tiktok-downloader.png Binary files differnew file mode 100644 index 0000000..7ca80f6 --- /dev/null +++ b/src/icons/tiktok-downloader.png diff --git a/src/icons/time-line.png b/src/icons/time-line.png Binary files differnew file mode 100644 index 0000000..590aa72 --- /dev/null +++ b/src/icons/time-line.png diff --git a/src/icons/translate-2.png b/src/icons/translate-2.png Binary files differnew file mode 100644 index 0000000..10a2c39 --- /dev/null +++ b/src/icons/translate-2.png diff --git a/src/icons/twitter-line.png b/src/icons/twitter-line.png Binary files differnew file mode 100644 index 0000000..4e5dad9 --- /dev/null +++ b/src/icons/twitter-line.png diff --git a/src/icons/volume-mute-line.png b/src/icons/volume-mute-line.png Binary files differnew file mode 100644 index 0000000..1877d6c --- /dev/null +++ b/src/icons/volume-mute-line.png diff --git a/src/icons/volume-up-line.png b/src/icons/volume-up-line.png Binary files differnew file mode 100644 index 0000000..3473479 --- /dev/null +++ b/src/icons/volume-up-line.png diff --git a/src/icons/white/stop-line.png b/src/icons/white/stop-line.png Binary files differnew file mode 100644 index 0000000..2cd918c --- /dev/null +++ b/src/icons/white/stop-line.png diff --git a/src/icons/white/white_arrow-left-line.png b/src/icons/white/white_arrow-left-line.png Binary files differnew file mode 100644 index 0000000..1ab063b --- /dev/null +++ b/src/icons/white/white_arrow-left-line.png diff --git a/src/icons/white/white_arrow-right-line.png b/src/icons/white/white_arrow-right-line.png Binary files differnew file mode 100644 index 0000000..e203184 --- /dev/null +++ b/src/icons/white/white_arrow-right-line.png diff --git a/src/icons/white/white_picture-in-picture-line.png b/src/icons/white/white_picture-in-picture-line.png Binary files differnew file mode 100644 index 0000000..ee4dc9a --- /dev/null +++ b/src/icons/white/white_picture-in-picture-line.png diff --git a/src/icons/white/white_play-fill.png b/src/icons/white/white_play-fill.png Binary files differnew file mode 100644 index 0000000..1ead6ba --- /dev/null +++ b/src/icons/white/white_play-fill.png diff --git a/src/icons/white/white_record-circle-line.png b/src/icons/white/white_record-circle-line.png Binary files differnew file mode 100644 index 0000000..91b34d5 --- /dev/null +++ b/src/icons/white/white_record-circle-line.png diff --git a/src/icons/white/white_scissors-cut-line.png b/src/icons/white/white_scissors-cut-line.png Binary files differnew file mode 100644 index 0000000..3ee54d7 --- /dev/null +++ b/src/icons/white/white_scissors-cut-line.png diff --git a/src/icons/white/white_shape-2-line.png b/src/icons/white/white_shape-2-line.png Binary files differnew file mode 100644 index 0000000..6be2c99 --- /dev/null +++ b/src/icons/white/white_shape-2-line.png diff --git a/src/icons/white/white_sip-line.png b/src/icons/white/white_sip-line.png Binary files differnew file mode 100644 index 0000000..cefb4e3 --- /dev/null +++ b/src/icons/white/white_sip-line.png diff --git a/src/icons/white/white_terminal-box-line.png b/src/icons/white/white_terminal-box-line.png Binary files differnew file mode 100644 index 0000000..f98536f --- /dev/null +++ b/src/icons/white/white_terminal-box-line.png diff --git a/src/icons/youtube-line.png b/src/icons/youtube-line.png Binary files differnew file mode 100644 index 0000000..96a7b80 --- /dev/null +++ b/src/icons/youtube-line.png diff --git a/src/icons/zoom-in-fill.png b/src/icons/zoom-in-fill.png Binary files differnew file mode 100644 index 0000000..aa40dfd --- /dev/null +++ b/src/icons/zoom-in-fill.png diff --git a/src/icons/zoom-in-line.png b/src/icons/zoom-in-line.png Binary files differnew file mode 100644 index 0000000..21c13e1 --- /dev/null +++ b/src/icons/zoom-in-line.png diff --git a/src/icons/zoom-out-fill.png b/src/icons/zoom-out-fill.png Binary files differnew file mode 100644 index 0000000..39f173f --- /dev/null +++ b/src/icons/zoom-out-fill.png diff --git a/src/icons/zoom-out-line.png b/src/icons/zoom-out-line.png Binary files differnew file mode 100644 index 0000000..8d5168b --- /dev/null +++ b/src/icons/zoom-out-line.png diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5971c92 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,45 @@ +#include <QApplication> +#include <QtWidgets> +#include <QtWebEngine> +#include <QWebEngineProfile> +#include <QWebEngineSettings> +#include <QSettings> +#include <QDebug> + +#include <mainwindow.h> +#include "common.h" + +extern QString defaultUserAgentStr; + +int main(int argc, char *argv[]) +{ + + //argv[argc++] = "--single-process"; + + QApplication app(argc, argv); + app.setWindowIcon(QIcon(":/icons/app/icon-256.png")); + + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO"; + if (!qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO) + && !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR") + && !qEnvironmentVariableIsSet("QT_SCALE_FACTOR") + && !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + } + + QApplication::setApplicationName("WhatSie"); + QApplication::setOrganizationName("org.keshavnrj.ubuntu"); + QApplication::setApplicationVersion(VERSIONSTR); + + QtWebEngine::initialize(); + + QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); + QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); + QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + + MainWindow window; + window.show(); + + return app.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..38ecbe6 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,551 @@ +#include "mainwindow.h" + + +extern QString defaultUserAgentStr; + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), + notificationsTitleRegExp("^\\([1-9]\\d*\\).*"), + trayIconRead(":/icons/app/whatsapp.svg"), + trayIconUnread(":/icons/app/whatsapp-message.svg") +{ + setWindowTitle(QApplication::applicationName()); + setWindowIcon(QIcon(":/icons/app/icon-256.png")); + setMinimumWidth(800); + setMinimumHeight(600); + readSettings(); + + createActions(); + createStatusBar(); + createTrayIcon(); + createWebEngine(); + + init_settingWidget(); + + lightPalette = qApp->palette(); + updateWindowTheme(); + + // quit application if the download manager window is the only remaining window + m_downloadManagerWidget.setAttribute(Qt::WA_QuitOnClose, false); + + +} + +void MainWindow::updatePageTheme() +{ + QString webPageTheme = "web"; //implies light + QString windowTheme = settings.value("windowTheme","light").toString(); + if(windowTheme == "dark"){ + webPageTheme = "web dark"; + } + if(webEngine && webEngine->page()){ + webEngine->page()->runJavaScript( + "document.querySelector('body').className='"+webPageTheme+"';", + [](const QVariant &result){ + qDebug() << "Value is: " << result.toString() << endl; + } + ); + } +} + +void MainWindow::updateWindowTheme() +{ + if(settings.value("windowTheme","light").toString() == "dark") + { + //TODO make dark palette match whatsapp dark theme + qApp->setStyle(QStyleFactory::create("fusion")); + QPalette palette; + palette.setColor(QPalette::Window,QColor("#131C21")); //whatsapp dark color + palette.setColor(QPalette::WindowText,Qt::white); + palette.setColor(QPalette::Disabled,QPalette::WindowText,QColor(127,127,127)); + palette.setColor(QPalette::Base,QColor(42,42,42)); + palette.setColor(QPalette::AlternateBase,QColor(66,66,66)); + palette.setColor(QPalette::ToolTipBase,Qt::white); + palette.setColor(QPalette::ToolTipText,QColor(53,53,53)); + palette.setColor(QPalette::Text,Qt::white); + palette.setColor(QPalette::Disabled,QPalette::Text,QColor(127,127,127)); + palette.setColor(QPalette::Dark,QColor(35,35,35)); + palette.setColor(QPalette::Shadow,QColor(20,20,20)); + palette.setColor(QPalette::Button,QColor(53,53,53)); + palette.setColor(QPalette::ButtonText,Qt::white); + palette.setColor(QPalette::Disabled,QPalette::ButtonText,QColor(127,127,127)); + palette.setColor(QPalette::BrightText,Qt::red); + palette.setColor(QPalette::Link,QColor("skyblue")); + palette.setColor(QPalette::Highlight,QColor(49,106,150)); + palette.setColor(QPalette::Disabled,QPalette::Highlight,QColor(80,80,80)); + palette.setColor(QPalette::HighlightedText,Qt::white); + palette.setColor(QPalette::Disabled,QPalette::HighlightedText,QColor(127,127,127)); + qApp->setPalette(palette); + this->webEngine->setStyleSheet("QWebEngineView{background:#131C21;}"); //whatsapp dark color + } + else{ + qApp->setPalette(lightPalette); + this->update(); + } +} + +void MainWindow::handleCookieAdded(const QNetworkCookie &cookie) +{ + qDebug() << cookie.toRawForm() << "\n\n\n"; +} + +void MainWindow::readSettings() +{ + restoreGeometry(settings.value("geometry").toByteArray()); + restoreState(settings.value("windowState").toByteArray()); +} + +void MainWindow::init_settingWidget() +{ + if(settingsWidget == nullptr) + { + settingsWidget = new SettingsWidget(this,webEngine->page()->profile()->cachePath() + ,webEngine->page()->profile()->persistentStoragePath()); + settingsWidget->setWindowTitle(QApplication::applicationName()+" | Settings"); + settingsWidget->setWindowFlags(Qt::Dialog); + + connect(settingsWidget,SIGNAL(updateWindowTheme()),this,SLOT(updateWindowTheme())); + connect(settingsWidget,SIGNAL(updatePageTheme()),this,SLOT(updatePageTheme())); + connect(settingsWidget,&SettingsWidget::muteToggled,[=](const bool checked) + { + this->toggleMute(checked); + }); + connect(settingsWidget,&SettingsWidget::userAgentChanged,[=](QString userAgentStr) + { + if(webEngine->page()->profile()->httpUserAgent() != userAgentStr) + { + settings.setValue("useragent",userAgentStr); + this->updateSettingsUserAgentWidget(); + this->askToReloadPage(); + } + }); + connect(settingsWidget,&SettingsWidget::autoPlayMediaToggled,[=](bool checked) + { QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); + + auto* webSettings = profile->settings(); + webSettings->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,checked); + + this->webEngine->page()->profile()->settings()->setAttribute( + QWebEngineSettings::PlaybackRequiresUserGesture, + checked); + }); + } +} + +void MainWindow::showSettings() +{ + if(webEngine == nullptr){ + QMessageBox::critical(this,QApplication::applicationName()+"| Error", + "Unable to initialize settings module.\nIs webengine initialized?"); + return; + } + if(!settingsWidget->isVisible()) + { + this->updateSettingsUserAgentWidget(); + settingsWidget->refresh(); + settingsWidget->showNormal(); + } +} + +void MainWindow::updateSettingsUserAgentWidget() +{ + settingsWidget->updateDefaultUAButton(this->webEngine->page()->profile()->httpUserAgent()); +} + +void MainWindow::askToReloadPage() +{ + QMessageBox msgBox; + msgBox.setWindowTitle(QApplication::applicationName()+" | Action required"); + msgBox.setInformativeText("Page needs to be reloaded to continue."); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + this->doAppReload(); +} + +void MainWindow::showAbout() +{ + QDialog *aboutDialog = new QDialog(this,Qt::Dialog); + aboutDialog->setWindowModality(Qt::WindowModal); + QVBoxLayout *layout = new QVBoxLayout; + QLabel *message = new QLabel(aboutDialog); + layout->addWidget(message); + connect(message,&QLabel::linkActivated,[=](const QString linkStr){ + if(linkStr.contains("about_qt")){ + qApp->aboutQt(); + }else{ + QDesktopServices::openUrl(QUrl(linkStr)); + } + }); + aboutDialog->setLayout(layout); + aboutDialog->setAttribute(Qt::WA_DeleteOnClose,true); + aboutDialog->show(); + + QString mes = + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'><img src=':/icons/app/icon-64.png' /></p>" + "<p align='center' style='-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'><br /></p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'>Designed and Developed</p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'>by <span style=' font-weight:600;'>Keshav Bhatt</span> <keshavnrj@gmail.com></p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'>Website: https://ktechpit.com</p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'>Runtime: <a href='http://about_qt'>Qt Toolkit</a></p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'>Version: "+QApplication::applicationVersion()+"</p>" + "<p align='center' style='-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'><br /></p>" + "<p align='center' style=' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'><a href='https://snapcraft.io/search?q=keshavnrj'>More Apps</p>" + "<p align='center' style='-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'><br /></p>"; + + QGraphicsOpacityEffect *eff = new QGraphicsOpacityEffect(this); + message->setGraphicsEffect(eff); + QPropertyAnimation *a = new QPropertyAnimation(eff,"opacity"); + a->setDuration(1000); + a->setStartValue(0); + a->setEndValue(1); + a->setEasingCurve(QEasingCurve::InCurve); + a->start(QPropertyAnimation::DeleteWhenStopped); + message->setText(mes); + message->show(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + + if(QSystemTrayIcon::isSystemTrayAvailable() && settings.value("closeButtonActionCombo",0).toInt() == 0){ + this->hide(); + event->ignore(); + notify(QApplication::applicationName(),"Application is minimized to system tray."); + return; + } + event->accept(); + qApp->quit(); + QMainWindow::closeEvent(event); +} + +void MainWindow::notify(QString title,QString message) +{ + if(settings.value("disableNotificationPopups",false).toBool() == true){ + return; + } + auto popup = new NotificationPopup(webEngine); + connect(popup,&NotificationPopup::notification_clicked,[=](){ + if(windowState()==Qt::WindowMinimized || windowState()!=Qt::WindowActive){ + activateWindow(); + raise(); + show(); + } + }); + popup->adjustSize(); + popup->present(title,message,QPixmap(":/icons/app/icon-64.png")); +} + +void MainWindow::createActions() +{ + + minimizeAction = new QAction(tr("Mi&nimize to tray"), this); + connect(minimizeAction, &QAction::triggered, this, &QWidget::hide); + addAction(minimizeAction); + + restoreAction = new QAction(tr("&Restore"), this); + connect(restoreAction, &QAction::triggered, this, &QWidget::showNormal); + addAction(restoreAction); + + reloadAction = new QAction(tr("Re&load"), this); + reloadAction->setShortcut(Qt::Key_F5); + connect(reloadAction, &QAction::triggered, this, &MainWindow::doReload); + addAction(reloadAction); + + settingsAction = new QAction(tr("Settings"), this); + connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettings); + + + aboutAction = new QAction(tr("About"), this); + connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout); + + quitAction = new QAction(tr("&Quit"), this); + quitAction->setShortcut(QKeySequence(Qt::Modifier::CTRL + Qt::Key_Q)); + connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit); + addAction(quitAction); +} + +void MainWindow::createStatusBar() +{ + QStatusBar *statusBar = new QStatusBar(this); + setStatusBar(statusBar); + statusBar->hide(); + this->statusBar = statusBar; +} + +void MainWindow::createTrayIcon() +{ + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(minimizeAction); + trayIconMenu->addAction(restoreAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(reloadAction); + trayIconMenu->addAction(settingsAction); + trayIconMenu->addAction(aboutAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(quitAction); + + trayIcon = new QSystemTrayIcon(trayIconRead, this); + trayIcon->setContextMenu(trayIconMenu); + + trayIcon->show(); + + connect(trayIcon, &QSystemTrayIcon::messageClicked, + this, &MainWindow::messageClicked); + connect(trayIcon, &QSystemTrayIcon::activated, + this, &MainWindow::iconActivated); +} + +void MainWindow::init_globalWebProfile() +{ + + QWebEngineProfile *profile = QWebEngineProfile::defaultProfile(); + auto* webSettings = profile->settings(); + webSettings->setAttribute(QWebEngineSettings::AutoLoadImages, true); + webSettings->setAttribute(QWebEngineSettings::JavascriptEnabled, true); + webSettings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); + webSettings->setAttribute(QWebEngineSettings::LocalStorageEnabled, true); + webSettings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + webSettings->setAttribute(QWebEngineSettings::XSSAuditingEnabled, true); + webSettings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true); + webSettings->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false); + webSettings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled,true); + webSettings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled ,true); + webSettings->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false); + webSettings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false); + //webSettings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true); + + // QObject::connect( + // QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, + // &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); + + QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, + true); + +} + +void MainWindow::createWebEngine() +{ + init_globalWebProfile(); + + QSizePolicy widgetSize; + widgetSize.setHorizontalPolicy(QSizePolicy::Expanding); + widgetSize.setVerticalPolicy(QSizePolicy::Expanding); + widgetSize.setHorizontalStretch(1); + widgetSize.setVerticalStretch(1); + + QWebEngineView *webEngine = new QWebEngineView(this); + setCentralWidget(webEngine); + webEngine->setSizePolicy(widgetSize); + webEngine->show(); + + this->webEngine = webEngine; + + + connect(webEngine, &QWebEngineView::titleChanged, + this, &MainWindow::handleWebViewTitleChanged); + connect(webEngine, &QWebEngineView::loadStarted, + this, &MainWindow::handleLoadStarted); + connect(webEngine, &QWebEngineView::loadProgress, + this, &MainWindow::handleLoadProgress); + connect(webEngine, &QWebEngineView::loadFinished, + this, &MainWindow::handleLoadFinished); + connect(webEngine, &QWebEngineView::renderProcessTerminated, + [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) { + QString status; + switch (termStatus) { + case QWebEnginePage::NormalTerminationStatus: + status = tr("Render process normal exit"); + break; + case QWebEnginePage::AbnormalTerminationStatus: + status = tr("Render process abnormal exit"); + break; + case QWebEnginePage::CrashedTerminationStatus: + status = tr("Render process crashed"); + break; + case QWebEnginePage::KilledTerminationStatus: + status = tr("Render process killed"); + break; + } + QMessageBox::StandardButton btn = QMessageBox::question(window(), status, + tr("Render process exited with code: %1\n" + "Do you want to reload the page ?").arg(statusCode)); + if (btn == QMessageBox::Yes) + QTimer::singleShot(0, [this] { this->webEngine->reload(); }); + }); + + QWebEngineCookieStore *browser_cookie_store = webEngine->page()->profile()->cookieStore(); + connect( browser_cookie_store, &QWebEngineCookieStore::cookieAdded, this, &MainWindow::handleCookieAdded ); + + createWebPage(false); +} + +void MainWindow::createWebPage(bool offTheRecord) +{ + if (offTheRecord && !m_otrProfile) { + m_otrProfile.reset(new QWebEngineProfile); + } + + auto profile = offTheRecord ? m_otrProfile.get() : QWebEngineProfile::defaultProfile(); + + profile->setHttpUserAgent(settings.value("useragent",defaultUserAgentStr).toString()); + + auto popup = new NotificationPopup(webEngine); + connect(popup,&NotificationPopup::notification_clicked,[=](){ + if(windowState()==Qt::WindowMinimized || windowState()!=Qt::WindowActive){ + activateWindow(); + raise(); + show(); + } + }); + profile->setNotificationPresenter([=] (std::unique_ptr<QWebEngineNotification> notification) + { + if(settings.value("disableNotificationPopups",false).toBool() == true){ + return; + } + popup->present(notification); + }); + + QWebEnginePage *page = new WebEnginePage(profile,webEngine); + if(settings.value("windowTheme","light").toString() == "dark"){ + page->setBackgroundColor(QColor("#F0F0F0")); //whatsapp bg color + } + webEngine->setPage(page); + //page should be set parent of profile to prevent + //Release of profile requested but WebEnginePage still not deleted. Expect troubles ! + profile->setParent(page); + //RequestInterceptor *interceptor = new RequestInterceptor(profile); + //profile->setUrlRequestInterceptor(interceptor); + page->setUrl(QUrl("https://web.whatsapp.com/")); + + connect(profile, &QWebEngineProfile::downloadRequested, + &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested); + + connect(webEngine->page(), SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), + this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest))); +} + +void MainWindow::fullScreenRequested(QWebEngineFullScreenRequest request) +{ + if (request.toggleOn()) + { + webEngine->showFullScreen(); + this->showFullScreen(); + request.accept(); + } else { + webEngine->showNormal(); + this->showNormal(); + request.accept(); + } +} + +void MainWindow::handleWebViewTitleChanged(QString title) +{ + setWindowTitle(title); + + if (notificationsTitleRegExp.exactMatch(title)) + { + trayIcon->setIcon(trayIconUnread); + setWindowIcon(trayIconUnread); + } + else + { + trayIcon->setIcon(trayIconRead); + setWindowIcon(trayIconRead); + } +} + +void MainWindow::handleLoadStarted() +{ + statusBar->show(); +} + +void MainWindow::handleLoadFinished(bool loaded) +{ + if(loaded){ + updatePageTheme(); + } +} + +void MainWindow::handleLoadProgress(int progress) +{ + if (progress >= 100) + { + statusBar->hide(); + } + else + { + statusBar->showMessage("Loading "+QString::number(progress)+"%"); + } +} + +//unused direct method to download file without having entry in download manager +void MainWindow::handleDownloadRequested(QWebEngineDownloadItem *download) +{ + QFileDialog dialog; + dialog.setParent(this); + dialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); + dialog.setFileMode(QFileDialog::FileMode::AnyFile); + QString suggestedFileName = QUrl(download->path()).fileName(); + dialog.selectFile(suggestedFileName); + + if (dialog.exec() && dialog.selectedFiles().size() > 0) + { + download->setPath(dialog.selectedFiles().at(0)); + download->accept(); + } +} + +void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) +{ + Q_UNUSED(reason); + if (isVisible()) { + hide(); + } else { + showNormal(); + } +} + +void MainWindow::messageClicked() +{ + if (isVisible()) { + hide(); + } else { + showNormal(); + } +} + +void MainWindow::doAppReload() +{ + if(this->webEngine->page()){ + this->webEngine->page()->disconnect(); + } + createWebPage(false); +} + +void MainWindow::doReload() +{ + this->webEngine->triggerPageAction(QWebEnginePage::ReloadAndBypassCache, false); +} + +void MainWindow::toggleMute(const bool checked) +{ + this->webEngine->page()->setAudioMuted(checked); +} + +// get value of page theme when page is loaded +QString MainWindow::getPageTheme() +{ + static QString theme = "web"; //implies light + if(webEngine && webEngine->page()) + { + webEngine->page()->runJavaScript( + "document.querySelector('body').className;", + [](const QVariant &result){ + theme = result.toString(); + } + ); + } + return theme; +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..367d18a --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,107 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QAction> +#include <QApplication> +#include <QFileDialog> +#include <QGraphicsOpacityEffect> +#include <QIcon> +#include <QMainWindow> +#include <QMenu> +#include <QMessageBox> +#include <QProgressBar> +#include <QRegExp> +#include <QSettings> +#include <QStatusBar> +#include <QStyle> +#include <QStyleFactory> +#include <QSystemTrayIcon> +#include <QWebEngineCookieStore> +#include <QWebEngineFullScreenRequest> +#include <QWebEngineProfile> +#include <QWebEngineView> +#include <QWebEngineSettings> + +#include "notificationpopup.h" +#include "requestinterceptor.h" +#include "settingswidget.h" +#include "webenginepage.h" + +#include "downloadmanagerwidget.h" + + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = nullptr); +public slots: + void updateWindowTheme(); + void updatePageTheme(); +protected: + void closeEvent(QCloseEvent *event) override; + +private: + QPalette lightPalette; + void createActions(); + void createStatusBar(); + void createTrayIcon(); + void createWebEngine(); + + QSettings settings; + + QRegExp notificationsTitleRegExp; + QIcon trayIconRead; + QIcon trayIconUnread; + + QAction *reloadAction; + QAction *minimizeAction; + QAction *restoreAction; + QAction *aboutAction; + QAction *settingsAction; + QAction *quitAction; + + QMenu *trayIconMenu; + QSystemTrayIcon *trayIcon; + + QWebEngineView *webEngine; + QStatusBar *statusBar; + + + SettingsWidget * settingsWidget = nullptr; + + //void reload(); + + DownloadManagerWidget m_downloadManagerWidget; + QScopedPointer<QWebEngineProfile> m_otrProfile; + + +private slots: + + void readSettings(); + void handleWebViewTitleChanged(QString title); + void handleLoadStarted(); + void handleLoadProgress(int progress); + void handleDownloadRequested(QWebEngineDownloadItem *download); + void iconActivated(QSystemTrayIcon::ActivationReason reason); + void messageClicked(); + void doReload(); + void showAbout(); + void notify(QString title, QString message); + void showSettings(); + void handleCookieAdded(const QNetworkCookie &cookie); + void handleLoadFinished(bool loaded); + + QString getPageTheme(); + void toggleMute(const bool checked); + void doAppReload(); + void askToReloadPage(); + void updateSettingsUserAgentWidget(); + void fullScreenRequested(QWebEngineFullScreenRequest request); + + void createWebPage(bool offTheRecord =false); + void init_settingWidget(); + void init_globalWebProfile(); +}; + +#endif // MAINWINDOW_H diff --git a/src/notificationpopup.h b/src/notificationpopup.h new file mode 100644 index 0000000..2246129 --- /dev/null +++ b/src/notificationpopup.h @@ -0,0 +1,144 @@ +#ifndef NOTIFICATIONPOPUP_H +#define NOTIFICATIONPOPUP_H + +#pragma once + +#include <QHBoxLayout> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QSpacerItem> +#include <QTimer> +#include <QVBoxLayout> +#include <QWebEngineNotification> +#include <QPropertyAnimation> +#include <QApplication> +#include <QDesktopWidget> +#include <QDebug> +#include "widgets/scrolltext/scrolltext.h" + +#include <memory> + +class NotificationPopup : public QWidget +{ + Q_OBJECT + + QLabel m_icon, m_title; ScrollText m_message; + std::unique_ptr<QWebEngineNotification> notification; + +public: + NotificationPopup(QWidget *parent) : QWidget(parent) + { + setWindowFlags(Qt::ToolTip); + auto rootLayout = new QHBoxLayout(this); + + rootLayout->addWidget(&m_icon); + + auto bodyLayout = new QVBoxLayout; + rootLayout->addLayout(bodyLayout); + + auto titleLayout = new QHBoxLayout; + bodyLayout->addLayout(titleLayout); + + titleLayout->addWidget(&m_title); + titleLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); + + auto close = new QPushButton(tr("Close")); + titleLayout->addWidget(close); + connect(close, &QPushButton::clicked, this, &NotificationPopup::onClosed); + + bodyLayout->addWidget(&m_message); + adjustSize(); + } + + void present(QString title,QString message,const QPixmap image) + { + m_title.setText("<b>" + title + "</b>"); + m_message.setText(message); + m_icon.setPixmap(image.scaledToHeight(m_icon.height(),Qt::SmoothTransformation)); + + this->adjustSize(); + qApp->processEvents(); + + int x = QApplication::desktop()->geometry().width()-(this->width()+10); + int y = 40; + + + QTimer::singleShot(10000,this,[=](){ + onClosed(); + }); + + QPropertyAnimation *a = new QPropertyAnimation(this,"pos"); + a->setDuration(200); + a->setStartValue(QApplication::desktop()->mapToGlobal(QPoint(x+this->width(),y))); + a->setEndValue(QApplication::desktop()->mapToGlobal(QPoint(x,y))); + a->setEasingCurve(QEasingCurve::Linear); + a->start(QPropertyAnimation::DeleteWhenStopped); + + this->show(); + + } + + void present(std::unique_ptr<QWebEngineNotification> &newNotification) + { + if (notification) { + notification->close(); + notification.reset(); + } + + notification.swap(newNotification); + + m_title.setText("<b>" + notification->title() + "</b>"); + m_message.setText(notification->message()); + m_icon.setPixmap(QPixmap::fromImage(notification->icon()).scaledToHeight(m_icon.height())); + + //show(); + notification->show(); + + connect(notification.get(), &QWebEngineNotification::closed, this, &NotificationPopup::onClosed); + QTimer::singleShot(10000, notification.get(), [&] () { onClosed(); }); + + this->adjustSize(); + qApp->processEvents(); + + int x = QApplication::desktop()->geometry().width()-(this->width()+10); + int y = 40; + + QPropertyAnimation *a = new QPropertyAnimation(this,"pos"); + a->setDuration(200); + a->setStartValue(QApplication::desktop()->mapToGlobal(QPoint(x+this->width(),y))); + a->setEndValue(QApplication::desktop()->mapToGlobal(QPoint(x,y))); + a->setEasingCurve(QEasingCurve::Linear); + a->start(QPropertyAnimation::DeleteWhenStopped); + this->show(); + } + +protected slots: + void onClosed() + { + hide(); + if(notification){ + notification->close(); + notification.reset(); + }else{ + this->deleteLater(); + } + } + +protected: + void mouseReleaseEvent(QMouseEvent *event) override + { + QWidget::mouseReleaseEvent(event); + if (event->button() == Qt::LeftButton) { + qDebug()<<"noti clicked"; + emit notification_clicked(); + if(notification ) + notification->click(); + onClosed(); + } + } +signals: + void notification_clicked(); +}; + +#endif // NOTIFICATIONPOPUP_H diff --git a/src/passworddialog.ui b/src/passworddialog.ui new file mode 100644 index 0000000..7f9bf99 --- /dev/null +++ b/src/passworddialog.ui @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PasswordDialog</class>
+ <widget class="QDialog" name="PasswordDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>399</width>
+ <height>148</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Authentication Required</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="0,0" columnminimumwidth="0,0">
+ <item row="0" column="0">
+ <widget class="QLabel" name="m_iconLabel">
+ <property name="text">
+ <string>Icon</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="m_infoLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Info</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="userLabel">
+ <property name="text">
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="m_userNameLineEdit"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="passwordLabel">
+ <property name="text">
+ <string>Password:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="m_passwordLineEdit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <zorder>userLabel</zorder>
+ <zorder>m_userNameLineEdit</zorder>
+ <zorder>passwordLabel</zorder>
+ <zorder>m_passwordLineEdit</zorder>
+ <zorder>buttonBox</zorder>
+ <zorder>m_iconLabel</zorder>
+ <zorder>m_infoLabel</zorder>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/requestinterceptor.h b/src/requestinterceptor.h new file mode 100644 index 0000000..95f2d48 --- /dev/null +++ b/src/requestinterceptor.h @@ -0,0 +1,41 @@ +#ifndef REQUESTINTERCEPTOR_H +#define REQUESTINTERCEPTOR_H +#include <QWebEnginePage> +#include <QDebug> +#include <QObject> +#include <QWebEngineView> +#include <QApplication> +#include <QWebEngineUrlRequestInfo> +#include <QWebEngineUrlRequestInterceptor> +#include <QSettings> + +class RequestInterceptor : public QWebEngineUrlRequestInterceptor +{ + Q_OBJECT + +signals: + void blocked(QString adUrl); +private: + QSettings m_settings; + +public: + RequestInterceptor(QObject *parent = nullptr) : QWebEngineUrlRequestInterceptor(parent) + { + } + + void interceptRequest(QWebEngineUrlRequestInfo &info) + { + QString reqUrlStr = info.requestUrl().toString(); + +// if(reqUrlStr.contains("mms-type=video")|| reqUrlStr.contains("stream/video?key")){ +// if(m_settings.value("disableVideos",false).toBool()){ +// info.block(true); +// qDebug()<<"INTERCEPTOR: Blocked video - "<<reqUrlStr; +// } +// }else{ +// qDebug()<<"INTERCEPTOR:"<<reqUrlStr; +// } + qDebug()<<"INTERCEPTOR:"<<reqUrlStr; + } +}; +#endif // REQUESTINTERCEPTOR_H diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp new file mode 100644 index 0000000..7f9a257 --- /dev/null +++ b/src/settingswidget.cpp @@ -0,0 +1,189 @@ +#include "settingswidget.h" +#include "ui_settingswidget.h" + +#include <QMessageBox> + +extern QString defaultUserAgentStr; + +SettingsWidget::SettingsWidget(QWidget *parent, QString engineCachePath, QString enginePersistentStoragePath) : + QWidget(parent), + ui(new Ui::SettingsWidget) +{ + ui->setupUi(this); + + ui->disableVideosCheckBox->hide();//impl later + + this->engineCachePath = engineCachePath; + this->enginePersistentStoragePath = enginePersistentStoragePath; + + ui->closeButtonActionComboBox->setCurrentIndex(settings.value("closeButtonActionCombo",0).toInt()); + ui->notificationCheckBox->setChecked(settings.value("disableNotificationPopups",false).toBool()); + ui->muteAudioCheckBox->setChecked(settings.value("muteAudio",false).toBool()); + ui->autoPlayMediaCheckBox->setChecked(settings.value("autoPlayMedia",false).toBool()); + ui->themeComboBox->setCurrentText(utils::toCamelCase(settings.value("windowTheme","light").toString())); + ui->userAgentLineEdit->setText(settings.value("useragent",defaultUserAgentStr).toString()); + + applyThemeQuirks(); + + ui->setUserAgent->setEnabled(false); +} + +SettingsWidget::~SettingsWidget() +{ + delete ui; +} + +void SettingsWidget::refresh() +{ + ui->cacheSize->setText(utils::refreshCacheSize(cachePath())); + ui->cookieSize->setText(utils::refreshCacheSize(persistentStoragePath())); +} + +void SettingsWidget::updateDefaultUAButton(const QString engineUA) +{ + bool isDefault = QString::compare(engineUA,defaultUserAgentStr,Qt::CaseInsensitive) == 0; + ui->defaultUserAgentButton->setEnabled(!isDefault); + + if(ui->userAgentLineEdit->text().trimmed().isEmpty()){ + ui->userAgentLineEdit->setText(engineUA); + } +} + + +QString SettingsWidget::cachePath() +{ + return engineCachePath; +} + +QString SettingsWidget::persistentStoragePath() +{ + return enginePersistentStoragePath; +} + +void SettingsWidget::on_deleteCache_clicked() +{ + QMessageBox msgBox; + msgBox.setText("This will delete app cache! Application cache makes application load faster."); + msgBox.setIconPixmap(QPixmap(":/icons/information-line.png").scaled(42,42,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + + msgBox.setInformativeText("Delete Application cache ?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int ret = msgBox.exec(); + switch (ret) { + case QMessageBox::Yes:{ + utils::delete_cache(this->cachePath()); + refresh(); + break; + } + case QMessageBox::No: + break; + } +} + +void SettingsWidget::on_deletePersistentData_clicked() +{ + QMessageBox msgBox; + msgBox.setText("This will delete app Persistent Data ! Persistent data includes persistent cookies, HTML5 local storage, and visited links."); + msgBox.setIconPixmap(QPixmap(":/icons/information-line.png").scaled(42,42,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + msgBox.setInformativeText("Delete Application Cookies ?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int ret = msgBox.exec(); + switch (ret) { + case QMessageBox::Yes:{ + utils::delete_cache(this->persistentStoragePath()); + refresh(); + break; + } + case QMessageBox::No: + break; + } +} + +void SettingsWidget::on_notificationCheckBox_toggled(bool checked) +{ + settings.setValue("disableNotificationPopups",checked); +} + +void SettingsWidget::on_themeComboBox_currentTextChanged(const QString &arg1) +{ + applyThemeQuirks(); + settings.setValue("windowTheme",QString(arg1).toLower()); + emit updateWindowTheme(); + emit updatePageTheme(); +} + + +void SettingsWidget::applyThemeQuirks() +{ + //little quirks + if(QString::compare(ui->themeComboBox->currentText(),"dark",Qt::CaseInsensitive)==0) + { + ui->label_7->setStyleSheet("color:#c2c5d1;padding: 0px 8px 0px 8px;background:transparent;"); + }else{ + ui->label_7->setStyleSheet("color:#1e1f21;padding: 0px 8px 0px 8px;background:transparent;"); + } +} + + +void SettingsWidget::on_muteAudioCheckBox_toggled(bool checked) +{ + settings.setValue("muteAudio",checked); + emit muteToggled(checked); +} + +void SettingsWidget::on_autoPlayMediaCheckBox_toggled(bool checked) +{ + settings.setValue("autoPlayMedia",checked); + emit autoPlayMediaToggled(checked); +} + +void SettingsWidget::on_defaultUserAgentButton_clicked() +{ + ui->userAgentLineEdit->setText(defaultUserAgentStr); + emit userAgentChanged(ui->userAgentLineEdit->text()); +} + +void SettingsWidget::on_userAgentLineEdit_textChanged(const QString &arg1) +{ + bool isDefault = QString::compare(arg1.trimmed(),defaultUserAgentStr,Qt::CaseInsensitive) == 0; + bool isPrevious= QString::compare(arg1.trimmed(),settings.value("useragent",defaultUserAgentStr).toString(),Qt::CaseInsensitive) == 0; + + if(isDefault == false && arg1.trimmed().isEmpty()==false) + { + ui->defaultUserAgentButton->setEnabled(false); + ui->setUserAgent->setEnabled(false); + } + if(isPrevious == false && arg1.trimmed().isEmpty() == false) + { + ui->setUserAgent->setEnabled(true); + ui->defaultUserAgentButton->setEnabled(true); + } + if(isPrevious){ + ui->defaultUserAgentButton->setEnabled(true); + ui->setUserAgent->setEnabled(false); + } +} + +void SettingsWidget::on_setUserAgent_clicked() +{ + if(ui->userAgentLineEdit->text().trimmed().isEmpty()){ + QMessageBox::information(this,QApplication::applicationName()+"| Error", + "Cannot set an empty UserAgent String."); + return; + } + emit userAgentChanged(ui->userAgentLineEdit->text()); +} + + + +void SettingsWidget::on_disableVideosCheckBox_toggled(bool checked) +{ + settings.setValue("disableVideos",checked); +} + +void SettingsWidget::on_closeButtonActionComboBox_currentIndexChanged(int index) +{ + settings.setValue("closeButtonActionCombo",index); +} diff --git a/src/settingswidget.h b/src/settingswidget.h new file mode 100644 index 0000000..a21a410 --- /dev/null +++ b/src/settingswidget.h @@ -0,0 +1,66 @@ +#ifndef SETTINGSWIDGET_H +#define SETTINGSWIDGET_H + +#include <QWidget> +#include <QSettings> +#include "utils.h" + + + +namespace Ui { +class SettingsWidget; +} + +class SettingsWidget : public QWidget +{ + Q_OBJECT + +signals: + void updateWindowTheme(); + void updatePageTheme(); + void muteToggled(const bool checked); + void autoPlayMediaToggled(const bool checked); + void userAgentChanged(QString userAgentStr); + +public: + explicit SettingsWidget(QWidget *parent = nullptr,QString engineCachePath = "", + QString enginePersistentStoragePath = ""); + ~SettingsWidget(); + +public slots: + void refresh(); + void updateDefaultUAButton(const QString engineUA); +private slots: + QString cachePath(); + QString persistentStoragePath(); + + void on_deleteCache_clicked(); + + void on_deletePersistentData_clicked(); + + void on_notificationCheckBox_toggled(bool checked); + + void on_themeComboBox_currentTextChanged(const QString &arg1); + + void applyThemeQuirks(); + void on_muteAudioCheckBox_toggled(bool checked); + + void on_defaultUserAgentButton_clicked(); + + void on_userAgentLineEdit_textChanged(const QString &arg1); + + void on_setUserAgent_clicked(); + + void on_autoPlayMediaCheckBox_toggled(bool checked); + + void on_disableVideosCheckBox_toggled(bool checked); + + void on_closeButtonActionComboBox_currentIndexChanged(int index); + +private: + Ui::SettingsWidget *ui; + QString engineCachePath,enginePersistentStoragePath; + QSettings settings; +}; + +#endif // SETTINGSWIDGET_H diff --git a/src/settingswidget.ui b/src/settingswidget.ui new file mode 100644 index 0000000..7318e5c --- /dev/null +++ b/src/settingswidget.ui @@ -0,0 +1,354 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SettingsWidget</class> + <widget class="QWidget" name="SettingsWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>539</width> + <height>447</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item alignment="Qt::AlignTop"> + <widget class="QWidget" name="headerWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background-color: qlineargradient(spread:reflect, x1:0, y1:1, x2:1, y2:1, stop:0 rgba(63, 129, 216, 255), stop:0.328358 rgba(63, 129, 216, 111), stop:0.61194 rgba(63, 129, 216, 48), stop:0.895522 rgba(63, 129, 216, 15)); +</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="label_6"> + <property name="styleSheet"> + <string notr="true">background:transparent; +padding: 8px 0px 8px 8px;</string> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="icons.qrc">:/icons/app/icon-64.png</pixmap> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>16</pointsize> + </font> + </property> + <property name="styleSheet"> + <string notr="true">QLabel{ +padding: 0px 8px 0px 8px; +background:transparent; +}</string> + </property> + <property name="text"> + <string>Application Settings</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="verticalWidget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox_8"> + <property name="title"> + <string>General settings</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="3" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Default close button action</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="closeButtonActionComboBox"> + <item> + <property name="text"> + <string>Minimize to tray</string> + </property> + </item> + <item> + <property name="text"> + <string>Quit Application</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item row="4" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Theme</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="themeComboBox"> + <item> + <property name="text"> + <string>Dark</string> + </property> + </item> + <item> + <property name="text"> + <string>Light</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item row="5" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>User Agent</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="userAgentLineEdit"/> + </item> + <item> + <widget class="QPushButton" name="setUserAgent"> + <property name="text"> + <string>Set</string> + </property> + <property name="icon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/categories/utilities.png</normaloff>:/icons/categories/utilities.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="defaultUserAgentButton"> + <property name="text"> + <string>Default</string> + </property> + <property name="icon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/refresh-line.png</normaloff>:/icons/refresh-line.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QCheckBox" name="notificationCheckBox"> + <property name="text"> + <string>Disable Notifications PopUp</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="muteAudioCheckBox"> + <property name="text"> + <string>Mute Audio</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="autoPlayMediaCheckBox"> + <property name="text"> + <string>Disable Auto Playback of Media</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="disableVideosCheckBox"> + <property name="text"> + <string>Disable loading videos</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_7"> + <property name="title"> + <string>Application Storage and Other Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="2"> + <widget class="QPushButton" name="deleteCache"> + <property name="text"> + <string>Clear</string> + </property> + <property name="icon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/delete-bin-3-line.png</normaloff>:/icons/delete-bin-3-line.png</iconset> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Application Cache</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="cacheSize"> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_11"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>Property</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_13"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>Option</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_12"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <italic>false</italic> + </font> + </property> + <property name="text"> + <string>Size</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="toolTip"> + <string><html><head/><body><p>Persistent data includes persistent cookies, HTML5 local storage, and visited links.</p></body></html></string> + </property> + <property name="text"> + <string>Persistent data</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="cookieSize"> + <property name="text"> + <string>-</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QPushButton" name="deletePersistentData"> + <property name="text"> + <string>Clear</string> + </property> + <property name="icon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/delete-bin-3-line.png</normaloff>:/icons/delete-bin-3-line.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="icons.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..543b837 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,162 @@ +#include "utils.h" +#include <QDateTime> +#include <QRegularExpression> + +utils::utils(QObject *parent) : QObject(parent) +{ + setParent(parent); +} + +utils::~utils() +{ + this->deleteLater(); +} + +//calculate dir size +quint64 utils::dir_size(const QString & directory) +{ + quint64 sizex = 0; + QFileInfo str_info(directory); + if (str_info.isDir()) + { + QDir dir(directory); + QFileInfoList list = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot); + for (int i = 0; i < list.size(); ++i) + { + QFileInfo fileInfo = list.at(i); + if(fileInfo.isDir()) + { + sizex += dir_size(fileInfo.absoluteFilePath()); + } + else{ + sizex += fileInfo.size(); + } + } + } + return sizex; +} + +//get the size of cache folder in human readble format +QString utils::refreshCacheSize(const QString cache_dir) +{ + qint64 cache_size = dir_size(cache_dir); + QString cache_unit; + if(cache_size > 1024*1024*1024) + { + cache_size = cache_size/(1024*1024*1024); + cache_unit = " GB"; + } + if(cache_size > 1024*1024) + { + cache_size = cache_size/(1024*1024); + cache_unit = " MB"; + } + else if(cache_size > 1024) + { + cache_size = cache_size/(1024); + cache_unit = " kB"; + } + else + { + cache_unit = " B"; + } + return QString::number(cache_size) + cache_unit; +} + +bool utils::delete_cache(const QString cache_dir) +{ + bool deleted = QDir(cache_dir).removeRecursively(); + QDir(cache_dir).mkpath(cache_dir); + return deleted; +} + +//returns string with first letter capitalized +QString utils::toCamelCase(const QString& s) +{ + QStringList parts = s.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.size(); ++i) + parts[i].replace(0, 1, parts[i][0].toUpper()); + return parts.join(" "); +} + +QString utils::generateRandomId(int length){ + + QString str = QUuid::createUuid().toString(); + str.remove(QRegularExpression("{|}|-")); + if(str.length()<length){ + while(str.length() != length){ + int required_char = length - str.length(); + str = str + str.append(genRand(required_char)); + } + } + if(str.length()>length){ + while(str.length() != length){ + str = str.remove(str.length()-1,1); + } + } + return str; +} + +QString utils::genRand(int length) +{ + QDateTime cd = QDateTime::currentDateTime(); + const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"+QString::number(cd.currentMSecsSinceEpoch()).remove(QRegExp("[^a-zA-Z\\d\\s]"))); + + const int randomStringLength = length; + QString randomString; + qsrand(cd.toTime_t()); + for(int i=0; i<randomStringLength; ++i) + { + int index = qrand() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + randomString = randomString.trimmed().simplified().remove(" "); + return randomString; +} + +QString utils::convertSectoDay(qint64 secs) +{ + int day = secs / (24 * 3600); + + secs = secs % (24 * 3600); + int hour = secs / 3600; + + secs %= 3600; + int minutes = secs / 60 ; + + secs %= 60; + int seconds = secs; + + QString days = QString::number(day) + " " + "days " + QString::number(hour) + + " " + "hours " + QString::number(minutes) + " " + + "minutes " + QString::number(seconds) + " " + + "seconds "; + return days; +} + +//static on demand path maker +QString utils::returnPath(QString pathname) +{ + QString _data_path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + if(!QDir(_data_path+"/"+pathname).exists()){ + QDir d(_data_path+"/"+pathname); + d.mkpath(_data_path+"/"+pathname); + } + return _data_path+"/"+pathname+"/"; +} + + +QString utils::htmlToPlainText(QString str){ + QString out; + QTextDocument text; + text.setHtml(str); + out = text.toPlainText(); + text.deleteLater(); + return out .replace("\\\"","'") + .replace("&","&") + .replace(">",">") + .replace("<","<") + .replace("'","'"); +} + diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..9d2520f --- /dev/null +++ b/src/utils.h @@ -0,0 +1,85 @@ +#ifndef UTILS_H +#define UTILS_H + +#include <QObject> +#include <QDebug> +#include <QFileInfo> +#include <QStandardPaths> +#include <QDir> +#include <QTextDocument> +#include <QUuid> + +class utils : public QObject +{ + Q_OBJECT + +public: + utils(QObject* parent=0); + virtual ~utils(); +public slots: + static QString refreshCacheSize(const QString cache_dir); + static bool delete_cache(const QString cache_dir); + static QString toCamelCase(const QString &s); + static QString generateRandomId(int length); + static QString genRand(int length); + static QString convertSectoDay(qint64 secs); + static QString returnPath(QString pathname); + + static QString EncodeXML ( const QString& encodeMe ){ + + QString temp; + int length = encodeMe.size(); + for (int index = 0; index < length; index++) + { + QChar character(encodeMe.at(index)); + + switch (character.unicode()) + { + case '&': + temp += "&"; break; + + case '\'': + temp += "'"; break; + + case '"': + temp += """; break; + + case '<': + temp += "<"; break; + + case '>': + temp += ">"; break; + + default: + temp += character; + break; + } + } + + return temp; + } + + static QString DecodeXML ( const QString& decodeMe ) { + + QString temp(decodeMe); + + temp.replace("&", "&"); + temp.replace("'", "'"); + temp.replace(""", "\""); + temp.replace("<", "<"); + temp.replace(">", ">"); + + return temp; + } + + static QString htmlToPlainText(QString str); + +private slots: + //use refreshCacheSize + static quint64 dir_size(const QString &directory); + + + +}; + +#endif // UTILS_H diff --git a/src/webenginepage.cpp b/src/webenginepage.cpp new file mode 100644 index 0000000..4478d6d --- /dev/null +++ b/src/webenginepage.cpp @@ -0,0 +1,252 @@ +#include "webenginepage.h" + +#include <QIcon> +#include <QStyle> + + +WebEnginePage::WebEnginePage(QWebEngineProfile *profile, QObject *parent) + : QWebEnginePage(profile, parent) +{ + // Connect signals and slots + connect(this, &QWebEnginePage::featurePermissionRequested, + this, &WebEnginePage::handleFeaturePermissionRequested); + connect(this, &QWebEnginePage::loadFinished, + this, &WebEnginePage::handleLoadFinished); + profile->setHttpUserAgent(profile->httpUserAgent().replace("QtWebEngine/5.13.0","")); + connect(this, &QWebEnginePage::authenticationRequired, this, &WebEnginePage::handleAuthenticationRequired); + connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebEnginePage::handleFeaturePermissionRequested); + connect(this, &QWebEnginePage::proxyAuthenticationRequired, this, &WebEnginePage::handleProxyAuthenticationRequired); + connect(this, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebEnginePage::handleRegisterProtocolHandlerRequested); + +#if !defined(QT_NO_SSL) || QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + connect(this, &QWebEnginePage::selectClientCertificate, this, &WebEnginePage::handleSelectClientCertificate); +#endif +} + +bool WebEnginePage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) +{ + qDebug() << "Navigation request: [" + url.toDisplayString() + "] " + type; + + if (QWebEnginePage::NavigationType::NavigationTypeLinkClicked == type) + { + QDesktopServices::openUrl(url); + return false; + } + + return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); +} + +QWebEnginePage *WebEnginePage::createWindow(QWebEnginePage::WebWindowType type) +{ + Q_UNUSED(type); + return new WebEnginePage(this->profile()); +} + + +inline QString questionForFeature(QWebEnginePage::Feature feature) +{ + switch (feature) { + case QWebEnginePage::Geolocation: + return WebEnginePage::tr("Allow %1 to access your location information?"); + case QWebEnginePage::MediaAudioCapture: + return WebEnginePage::tr("Allow %1 to access your microphone?"); + case QWebEnginePage::MediaVideoCapture: + return WebEnginePage::tr("Allow %1 to access your webcam?"); + case QWebEnginePage::MediaAudioVideoCapture: + return WebEnginePage::tr("Allow %1 to access your microphone and webcam?"); + case QWebEnginePage::MouseLock: + return WebEnginePage::tr("Allow %1 to lock your mouse cursor?"); + case QWebEnginePage::DesktopVideoCapture: + return WebEnginePage::tr("Allow %1 to capture video of your desktop?"); + case QWebEnginePage::DesktopAudioVideoCapture: + return WebEnginePage::tr("Allow %1 to capture audio and video of your desktop?"); + case QWebEnginePage::Notifications: + return WebEnginePage::tr("Allow %1 to show notification on your desktop?"); + } + return QString(); +} + +void WebEnginePage::handleFeaturePermissionRequested(const QUrl &securityOrigin, Feature feature) +{ + QString title = tr("Permission Request"); + QString question = questionForFeature(feature).arg(securityOrigin.host()); + if (!question.isEmpty() && QMessageBox::question(view()->window(), title, question) == QMessageBox::Yes) + setFeaturePermission(securityOrigin, feature, PermissionGrantedByUser); + else + setFeaturePermission(securityOrigin, feature, PermissionDeniedByUser); +} + +void WebEnginePage::handleLoadFinished(bool ok) +{ + Q_UNUSED(ok); + setFeaturePermission( + QUrl("https://web.whatsapp.com/"), + QWebEnginePage::Feature::Notifications, + QWebEnginePage::PermissionPolicy::PermissionGrantedByUser + ); + setFeaturePermission( + QUrl("https://web.whatsapp.com/"), + QWebEnginePage::Feature::MediaAudioCapture, + QWebEnginePage::PermissionPolicy::PermissionGrantedByUser + ); + setFeaturePermission( + QUrl("https://web.whatsapp.com/"), + QWebEnginePage::Feature::MediaVideoCapture, + QWebEnginePage::PermissionPolicy::PermissionGrantedByUser + ); + setFeaturePermission( + QUrl("https://web.whatsapp.com/"), + QWebEnginePage::Feature::MediaAudioVideoCapture, + QWebEnginePage::PermissionPolicy::PermissionGrantedByUser + ); +} + +void WebEnginePage::fullScreenRequestedByPage(QWebEngineFullScreenRequest request) +{ + qDebug()<<"Fullscreen"; + request.accept(); +} + +QStringList WebEnginePage::chooseFiles(QWebEnginePage::FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) +{ + qDebug()<<mode<<oldFiles<<acceptedMimeTypes; + QFileDialog::FileMode dialogMode; + if(mode == QWebEnginePage::FileSelectOpen) { + dialogMode = QFileDialog::ExistingFile; + } else { + dialogMode = QFileDialog::ExistingFiles; + } + + QFileDialog* dialog = new QFileDialog(); + + dialog->setFileMode(dialogMode); + dialog->setOption(QFileDialog::DontUseNativeDialog,true); + + QStringList mimeFilters = acceptedMimeTypes; + + if(acceptedMimeTypes.contains("image/*")){ + foreach(QByteArray mime,QImageReader::supportedImageFormats()){ + mimeFilters.append("image/"+mime); + } + } + + dialog->setMimeTypeFilters(mimeFilters); + + QStringList selectedFiles; + if(dialog->exec()) { + selectedFiles = dialog->selectedFiles(); + } + + return selectedFiles; +} + +bool WebEnginePage::certificateError(const QWebEngineCertificateError &error) +{ + QWidget *mainWindow = view()->window(); + if (error.isOverridable()) { + QDialog dialog(mainWindow); + dialog.setModal(true); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + Ui::CertificateErrorDialog certificateDialog; + certificateDialog.setupUi(&dialog); + certificateDialog.m_iconLabel->setText(QString()); + QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, mainWindow)); + certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); + certificateDialog.m_errorLabel->setText(error.errorDescription()); + dialog.setWindowTitle(tr("Certificate Error")); + return dialog.exec() == QDialog::Accepted; + } + + QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription()); + return false; +} + +void WebEnginePage::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) +{ + QWidget *mainWindow = view()->window(); + QDialog dialog(mainWindow); + dialog.setModal(true); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + + Ui::PasswordDialog passwordDialog; + passwordDialog.setupUi(&dialog); + + passwordDialog.m_iconLabel->setText(QString()); + QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, mainWindow)); + passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); + + QString introMessage(tr("Enter username and password for \"%1\" at %2") + .arg(auth->realm()).arg(requestUrl.toString().toHtmlEscaped())); + passwordDialog.m_infoLabel->setText(introMessage); + passwordDialog.m_infoLabel->setWordWrap(true); + + if (dialog.exec() == QDialog::Accepted) { + auth->setUser(passwordDialog.m_userNameLineEdit->text()); + auth->setPassword(passwordDialog.m_passwordLineEdit->text()); + } else { + // Set authenticator null if dialog is cancelled + *auth = QAuthenticator(); + } +} + +void WebEnginePage::handleProxyAuthenticationRequired(const QUrl &, QAuthenticator *auth, const QString &proxyHost) +{ + QWidget *mainWindow = view()->window(); + QDialog dialog(mainWindow); + dialog.setModal(true); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + + Ui::PasswordDialog passwordDialog; + passwordDialog.setupUi(&dialog); + + passwordDialog.m_iconLabel->setText(QString()); + QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, mainWindow)); + passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); + + QString introMessage = tr("Connect to proxy \"%1\" using:"); + introMessage = introMessage.arg(proxyHost.toHtmlEscaped()); + passwordDialog.m_infoLabel->setText(introMessage); + passwordDialog.m_infoLabel->setWordWrap(true); + + if (dialog.exec() == QDialog::Accepted) { + auth->setUser(passwordDialog.m_userNameLineEdit->text()); + auth->setPassword(passwordDialog.m_passwordLineEdit->text()); + } else { + // Set authenticator null if dialog is cancelled + *auth = QAuthenticator(); + } +} + +//! [registerProtocolHandlerRequested] +void WebEnginePage::handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request) +{ + auto answer = QMessageBox::question( + view()->window(), + tr("Permission Request"), + tr("Allow %1 to open all %2 links?") + .arg(request.origin().host()) + .arg(request.scheme())); + if (answer == QMessageBox::Yes) + request.accept(); + else + request.reject(); +} +//! [registerProtocolHandlerRequested] + +#if !defined(QT_NO_SSL) || QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) +void WebEnginePage::handleSelectClientCertificate(QWebEngineClientCertificateSelection selection) +{ + // Just select one. + selection.select(selection.certificates().at(0)); + + qDebug() << __FUNCTION__; + for(QSslCertificate cert : selection.certificates()) { + qDebug() << cert; + selection.select(cert); // select the first available cert + break; + } + qDebug() << selection.host(); +} +#endif + + diff --git a/src/webenginepage.h b/src/webenginepage.h new file mode 100644 index 0000000..7ec1a69 --- /dev/null +++ b/src/webenginepage.h @@ -0,0 +1,49 @@ +#ifndef WEBENGINEPAGE_H +#define WEBENGINEPAGE_H + + +#include <QFileDialog> +#include <QWebEngineFullScreenRequest> +#include <QWebEngineNotification> +#include <QWebEngineProfile> + +#include <QWebEnginePage> +#include <QDesktopServices> +#include <QMessageBox> +#include <QImageReader> +#include <QWebEngineCertificateError> +#include <QAuthenticator> + +#include <QWebEngineRegisterProtocolHandlerRequest> +#include <QWebEngineFullScreenRequest> + +#include "ui_certificateerrordialog.h" +#include "ui_passworddialog.h" + +class WebEnginePage : public QWebEnginePage +{ + Q_OBJECT +public: + WebEnginePage(QWebEngineProfile *profile, QObject *parent = nullptr); + +protected: + bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override; + QWebEnginePage* createWindow(QWebEnginePage::WebWindowType type) override; + bool certificateError(const QWebEngineCertificateError &error) override; + QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes); + +public slots: + void handleFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature); + void handleLoadFinished(bool ok); + +private slots: + void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth); + void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); + void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); +#if !defined(QT_NO_SSL) || QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); +#endif + void fullScreenRequestedByPage(QWebEngineFullScreenRequest request); +}; + +#endif // WEBENGINEPAGE_H diff --git a/src/widgets/scrolltext/scrolltext.cpp b/src/widgets/scrolltext/scrolltext.cpp new file mode 100644 index 0000000..00696af --- /dev/null +++ b/src/widgets/scrolltext/scrolltext.cpp @@ -0,0 +1,179 @@ +#include "scrolltext.h" +#include <QPainter> +#include <QHoverEvent> + +ScrollText::ScrollText(QWidget *parent) : + QWidget(parent), scrollPos(0) +{ + + staticText.setTextFormat(Qt::PlainText); + + setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + + setMinimumHeight(fontMetrics().height()+10); + setMaximumHeight(this->minimumHeight()+6); + + leftMargin = 0; //height() / 3; + + setSeparator(" "); + + connect(&timer, SIGNAL(timeout()), this, SLOT(timer_timeout())); + timer.setInterval(50); +} + +QString ScrollText::text() const +{ + return _text; +} + +void ScrollText::setText(QString text) +{ + _text = text; + updateText(); + update(); +} + +QString ScrollText::separator() const +{ + return _separator; +} + +void ScrollText::setSeparator(QString separator) +{ + _separator = separator; + updateText(); + update(); +} + +void ScrollText::setLeftMargin(int pixels) +{ + leftMargin = pixels; + update(); +} + +void ScrollText::pause() +{ + if(scrollEnabled){ + timer.stop(); + } +} + +void ScrollText::resume() +{ + if(scrollEnabled){ + timer.start(); + scrolledOnce = false; + } +} + +void ScrollText::updateText() +{ + scrolledOnce = false; + timer.stop(); + + singleTextWidth = fontMetrics().horizontalAdvance(_text); +// scrollEnabled = true; + scrollEnabled = (singleTextWidth > width() - leftMargin); + + if(scrollEnabled) + { + scrollPos = -64; + staticText.setText(_text + _separator); + timer.start(); + } + else{ + staticText.setText(_text); + } + staticText.prepare(QTransform(), font()); + //wholeTextSize = QSize(fontMetrics().width(staticText.text()), fontMetrics().height()); + wholeTextSize = QSize(fontMetrics().horizontalAdvance(staticText.text()), fontMetrics().height()); + +} + +void ScrollText::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + if(scrollEnabled) + { + buffer.fill(qRgba(0, 0, 0, 0)); + QPainter pb(&buffer); + pb.setPen(p.pen()); + pb.setFont(p.font()); + + int x = qMin(-scrollPos, 0) + leftMargin; + while(x < width()) + { + pb.drawStaticText(QPointF(x, (height() - wholeTextSize.height()) / 2) + QPoint(2, 2), staticText); + x += wholeTextSize.width(); + } + + //Apply Alpha Channel + pb.setCompositionMode(QPainter::CompositionMode_DestinationIn); + pb.setClipRect(width() - 15, 0, 15, height()); + pb.drawImage(0, 0, alphaChannel); + pb.setClipRect(0, 0, 15, height()); + //initial situation: don't apply alpha channel in the left half of the image at all; apply it more and more until scrollPos gets positive + if(scrollPos < 0) + pb.setOpacity((qreal)(qMax(-8, scrollPos) + 8) / 8.0); + pb.drawImage(0, 0, alphaChannel); + p.drawImage(0, 0, buffer); + } + else + { + p.drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, text()); +// p.drawStaticText(QPointF(leftMargin, (height() - wholeTextSize.height()) / 2), staticText); + } +} + +void ScrollText::resizeEvent(QResizeEvent*) +{ + //When the widget is resized, we need to update the alpha channel. + + alphaChannel = QImage(size(), QImage::Format_ARGB32_Premultiplied); + buffer = QImage(size(), QImage::Format_ARGB32_Premultiplied); + + //Create Alpha Channel: + if(width() > 64) + { + //create first scanline + QRgb* scanline1 = (QRgb*)alphaChannel.scanLine(0); + for(int x = 1; x < 16; ++x) + scanline1[x - 1] = scanline1[width() - x] = qRgba(0, 0, 0, x << 4); + for(int x = 15; x < width() - 15; ++x) + scanline1[x] = qRgb(0, 0, 0); + //copy scanline to the other ones + for(int y = 1; y < height(); ++y) + memcpy(alphaChannel.scanLine(y), (uchar*)scanline1, width() * 4); + } + else + alphaChannel.fill(qRgb(0, 0, 0)); + + + //Update scrolling state + bool newScrollEnabled = (singleTextWidth > width() - leftMargin); + if(newScrollEnabled != scrollEnabled) + updateText(); +} + +void ScrollText::timer_timeout() +{ + scrollPos = (scrollPos + 2) + % wholeTextSize.width(); + pauseAfterOneRotation(scrollPos); + update(); +} + +void ScrollText::pauseAfterOneRotation(int scrollPos) +{ + if(scrolledOnce == false && scrollPos+2==wholeTextSize.width()){ + scrolledOnce = true; + } + if(scrolledOnce){ + pause(); + } +} + + + + diff --git a/src/widgets/scrolltext/scrolltext.h b/src/widgets/scrolltext/scrolltext.h new file mode 100644 index 0000000..a660615 --- /dev/null +++ b/src/widgets/scrolltext/scrolltext.h @@ -0,0 +1,53 @@ +#ifndef SCROLLTEXT_H +#define SCROLLTEXT_H + +#include <QWidget> +#include <QStaticText> +#include <QTimer> + + +class ScrollText : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString separator READ separator WRITE setSeparator) + +public: + explicit ScrollText(QWidget *parent = 0); + +public slots: + QString text() const; + void setText(QString text); + + QString separator() const; + void setSeparator(QString separator); + void setLeftMargin(int pixels); + void pause(); + void resume(); + + +protected: + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + +private: + void updateText(); + QString _text; + QString _separator; + QStaticText staticText; + int singleTextWidth; + QSize wholeTextSize; + int leftMargin; + bool scrollEnabled; + int scrollPos; + QImage alphaChannel; + QImage buffer; + QTimer timer; + bool scrolledOnce = false; + +private slots: + virtual void timer_timeout(); + void pauseAfterOneRotation(int scrollPos); +}; + +#endif // SCROLLTEXT_H |