diff --git a/src/gui/discoverfeedsbutton.cpp b/src/gui/discoverfeedsbutton.cpp new file mode 100755 index 000000000..0204adc5a --- /dev/null +++ b/src/gui/discoverfeedsbutton.cpp @@ -0,0 +1,92 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#include "gui/discoverfeedsbutton.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "gui/dialogs/formmain.h" +#include "gui/tabwidget.h" +#include "gui/feedmessageviewer.h" +#include "gui/feedsview.h" +#include "core/feedsmodel.h" +#include "services/abstract/serviceroot.h" + +#include + + +DiscoverFeedsButton::DiscoverFeedsButton(QWidget *parent) : QToolButton(parent), m_addresses(QStringList()) { + setEnabled(false); + setIcon(qApp->icons()->fromTheme(QSL("application-rss+xml"))); + setPopupMode(QToolButton::InstantPopup); +} + +DiscoverFeedsButton::~DiscoverFeedsButton() { +} + +void DiscoverFeedsButton::clearFeedAddresses() { + setFeedAddresses(QStringList()); +} + +void DiscoverFeedsButton::setFeedAddresses(const QStringList &addresses) { + setEnabled(!addresses.isEmpty()); + setToolTip(addresses.isEmpty() ? + tr("This website does not contain any feeds.") : + tr("Click me to add feeds from this website.\nThis website contains %n feed(s).", 0, addresses.size())); + + if (menu() == nullptr) { + // Initialize the menu. + setMenu(new QMenu(this)); + connect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(linkTriggered(QAction*))); + connect(menu(), SIGNAL(aboutToShow()), this, SLOT(fillMenu())); + } + + menu()->hide(); + m_addresses = addresses; +} + +void DiscoverFeedsButton::linkTriggered(QAction *action) { + const QString url = action->property("url").toString(); + ServiceRoot *root = static_cast(action->property("root").value()); + + if (root->supportsFeedAdding()) { + root->addNewFeed(url); + } + else { + qApp->showGuiMessage(tr("Not supported"), + tr("Given account does not support adding feeds."), + QSystemTrayIcon::Warning, + qApp->mainForm(), true); + } +} + +void DiscoverFeedsButton::fillMenu() { + menu()->clear(); + + foreach (const ServiceRoot *root, qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) { + QMenu *root_menu = menu()->addMenu(root->icon(), root->title()); + + foreach (const QString &url, m_addresses) { + if (root->supportsFeedAdding()) { + QAction *url_action = root_menu->addAction(root->icon(), url); + + url_action->setProperty("url", url); + url_action->setProperty("root", QVariant::fromValue((void*) root)); + } + } + } +} diff --git a/src/gui/discoverfeedsbutton.h b/src/gui/discoverfeedsbutton.h new file mode 100755 index 000000000..2f22d3336 --- /dev/null +++ b/src/gui/discoverfeedsbutton.h @@ -0,0 +1,45 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#ifndef DISCOVERFEEDSBUTTON_H +#define DISCOVERFEEDSBUTTON_H + +#include + + +class DiscoverFeedsButton : public QToolButton { + Q_OBJECT + + public: + // Constructors. + explicit DiscoverFeedsButton(QWidget *parent = 0); + virtual ~DiscoverFeedsButton(); + + // Feed addresses manipulators. + void clearFeedAddresses(); + void setFeedAddresses(const QStringList &addresses); + + private slots: + // User chose any of addresses. + void linkTriggered(QAction *action); + void fillMenu(); + + private: + QStringList m_addresses; +}; + +#endif // DISCOVERFEEDSBUTTON_H diff --git a/src/gui/locationlineedit.cpp b/src/gui/locationlineedit.cpp new file mode 100755 index 000000000..134c78379 --- /dev/null +++ b/src/gui/locationlineedit.cpp @@ -0,0 +1,53 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#include "gui/locationlineedit.h" + +#include "network-web/googlesuggest.h" + +#include + + +LocationLineEdit::LocationLineEdit(QWidget *parent) + : BaseLineEdit(parent), m_mouseSelectsAllText(true), m_googleSuggest(new GoogleSuggest(this)) { + setPlaceholderText(tr("Website address goes here")); + connect(this, SIGNAL(submitted(QString)), m_googleSuggest, SLOT(preventSuggest())); +} + +LocationLineEdit::~LocationLineEdit() { +} + +void LocationLineEdit::focusOutEvent(QFocusEvent *event) { + BaseLineEdit::focusOutEvent(event); + + // User now left text box, when he enters it again and clicks, + // then all text should be selected. + m_mouseSelectsAllText = true; +} + +void LocationLineEdit::mousePressEvent(QMouseEvent *event) { + if (m_mouseSelectsAllText) { + event->ignore(); + selectAll(); + + // User clicked and all text was selected. + m_mouseSelectsAllText = false; + } + else { + BaseLineEdit::mousePressEvent(event); + } +} diff --git a/src/gui/locationlineedit.h b/src/gui/locationlineedit.h new file mode 100755 index 000000000..7fbe4ae8f --- /dev/null +++ b/src/gui/locationlineedit.h @@ -0,0 +1,44 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#ifndef LOCATIONLINEEDIT_H +#define LOCATIONLINEEDIT_H + +#include "gui/baselineedit.h" + + +class WebBrowser; +class GoogleSuggest; + +class LocationLineEdit : public BaseLineEdit { + Q_OBJECT + + public: + // Constructors and destructors. + explicit LocationLineEdit(QWidget *parent = 0); + virtual ~LocationLineEdit(); + + protected: + void focusOutEvent(QFocusEvent *event); + void mousePressEvent(QMouseEvent *event); + + private: + bool m_mouseSelectsAllText; + GoogleSuggest *m_googleSuggest; +}; + +#endif // LOCATIONLINEEDIT_H diff --git a/src/gui/webbrowser.cpp b/src/gui/webbrowser.cpp index aadea7831..5f26ac3ad 100755 --- a/src/gui/webbrowser.cpp +++ b/src/gui/webbrowser.cpp @@ -48,7 +48,7 @@ void WebBrowser::createConnections() { // Change location textbox status according to webpage status. connect(m_webView, SIGNAL(loadStarted()), this, SLOT(onLoadingStarted())); - //connect(m_webView, SIGNAL(loadProgress(int)), this, SLOT(onLoadingProgress(int))); + connect(m_webView, SIGNAL(loadProgress(int)), this, SLOT(onLoadingProgress(int))); connect(m_webView, SIGNAL(loadFinished(bool)), this, SLOT(onLoadingFinished(bool))); // Forward title/icon changes. @@ -191,15 +191,28 @@ void WebBrowser::initializeLayout() { m_toolBar->addAction(act_discover); m_toolBar->addWidget(m_txtLocation); + m_loadingProgress = new QProgressBar(this); + m_loadingProgress->setFixedHeight(5); + m_loadingProgress->setMinimum(0); + m_loadingProgress->setTextVisible(false); + m_loadingProgress->setMaximum(100); + m_loadingProgress->setAttribute(Qt::WA_TranslucentBackground); + // Setup layout. m_layout->addWidget(m_toolBar); m_layout->addWidget(m_webView); + m_layout->addWidget(m_loadingProgress); m_layout->setMargin(0); m_layout->setSpacing(0); } void WebBrowser::onLoadingStarted() { m_btnDiscoverFeeds->clearFeedAddresses(); + m_loadingProgress->show(); +} + +void WebBrowser::onLoadingProgress(int progress) { + m_loadingProgress->setValue(progress); } void WebBrowser::onLoadingFinished(bool success) { @@ -213,6 +226,9 @@ void WebBrowser::onLoadingFinished(bool success) { else { m_btnDiscoverFeeds->clearFeedAddresses(); } + + m_loadingProgress->hide(); + m_loadingProgress->setValue(0); } void WebBrowser::markMessageAsRead(int id, bool read) { diff --git a/src/gui/webbrowser.h b/src/gui/webbrowser.h index c5d810488..7f80cf678 100755 --- a/src/gui/webbrowser.h +++ b/src/gui/webbrowser.h @@ -67,6 +67,7 @@ class WebBrowser : public TabContent { private slots: void updateUrl(const QUrl &url); void onLoadingStarted(); + void onLoadingProgress(int progress); void onLoadingFinished(bool success); void receiveMessageStatusChangeRequest(int message_id, WebPage::MessageStatusChange change); @@ -87,6 +88,7 @@ class WebBrowser : public TabContent { WebViewer *m_webView; LocationLineEdit *m_txtLocation; DiscoverFeedsButton *m_btnDiscoverFeeds; + QProgressBar *m_loadingProgress; QAction *m_actionBack; QAction *m_actionForward; diff --git a/src/network-web/googlesuggest.cpp b/src/network-web/googlesuggest.cpp new file mode 100755 index 000000000..70107b2ee --- /dev/null +++ b/src/network-web/googlesuggest.cpp @@ -0,0 +1,204 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +// 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 Digia Plc and its Subsidiary(-ies) 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." + +#include "network-web/googlesuggest.h" + +#include "definitions/definitions.h" +#include "network-web/silentnetworkaccessmanager.h" +#include "gui/locationlineedit.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +GoogleSuggest::GoogleSuggest(LocationLineEdit *editor, QObject *parent) + : QObject(parent), editor(editor), popup(new QListWidget()), m_enteredText(QString()) { + popup->setWindowFlags(Qt::Popup); + popup->setFocusPolicy(Qt::NoFocus); + popup->setFocusProxy(editor); + popup->setMouseTracking(true); + popup->setSelectionBehavior(QAbstractItemView::SelectRows); + popup->setFrameStyle(QFrame::Box | QFrame::Plain); + popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + popup->installEventFilter(this); + + timer = new QTimer(this); + timer->setSingleShot(true); + timer->setInterval(500); + + connect(popup.data(), SIGNAL(itemClicked(QListWidgetItem*)), SLOT(doneCompletion())); + connect(timer, SIGNAL(timeout()), SLOT(autoSuggest())); + connect(editor, SIGNAL(textEdited(QString)), timer, SLOT(start())); +} + +GoogleSuggest::~GoogleSuggest() { +} + +bool GoogleSuggest::eventFilter(QObject *object, QEvent *event) { + if (object != popup.data()) { + return false; + } + + if (event->type() == QEvent::MouseButtonPress) { + popup->hide(); + editor->setFocus(); + return true; + } + + if (event->type() == QEvent::KeyPress) { + bool consumed = false; + const int key = static_cast(event)->key(); + + switch (key) { + case Qt::Key_Enter: + case Qt::Key_Return: + doneCompletion(); + consumed = true; + + case Qt::Key_Escape: + editor->setFocus(); + popup->hide(); + consumed = true; + + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + break; + + default: + editor->setFocus(); + editor->event(event); + popup->hide(); + break; + } + + return consumed; + } + + return false; +} + +void GoogleSuggest::showCompletion(const QStringList &choices) { + if (choices.isEmpty()) { + return; + } + + popup->setUpdatesEnabled(false); + popup->clear(); + + foreach (const QString &choice, choices) { + new QListWidgetItem(choice, popup.data()); + } + + popup->setCurrentItem(popup->item(0)); + popup->adjustSize(); + popup->setUpdatesEnabled(true); + popup->resize(editor->width(), popup->sizeHintForRow(0) * qMin(7, choices.count()) + 3); + popup->move(editor->mapToGlobal(QPoint(0, editor->height()))); + popup->setFocus(); + popup->show(); +} + +void GoogleSuggest::doneCompletion() { + timer->stop(); + popup->hide(); + editor->setFocus(); + + QListWidgetItem *item = popup->currentItem(); + + if (item != nullptr) { + editor->submit(QString(GOOGLE_SEARCH_URL).arg(item->text())); + } +} + +void GoogleSuggest::preventSuggest() { + timer->stop(); +} + +void GoogleSuggest::autoSuggest() { + m_enteredText = QUrl::toPercentEncoding(editor->text()); + QString url = QString(GOOGLE_SUGGEST_URL).arg(m_enteredText); + + connect(SilentNetworkAccessManager::instance()->get(QNetworkRequest(QString(url))), SIGNAL(finished()), + this, SLOT(handleNetworkData())); +} + +void GoogleSuggest::handleNetworkData() { + QScopedPointer reply(static_cast(sender())); + + if (!reply->error()) { + QStringList choices; + QDomDocument xml; + QByteArray response = reply->readAll(); + + const QTextCodec *c = QTextCodec::codecForUtfText(response); + xml.setContent(c->toUnicode(response)); + + QDomNodeList suggestions = xml.elementsByTagName(QSL("suggestion")); + + for (int i = 0; i < suggestions.size(); i++) { + const QDomElement element = suggestions.at(i).toElement(); + + if (element.attributes().contains(QSL("data"))) { + choices.append(element.attribute(QSL("data"))); + } + } + + if (choices.isEmpty()) { + choices.append(m_enteredText); + } + + showCompletion(choices); + } +} diff --git a/src/network-web/googlesuggest.h b/src/network-web/googlesuggest.h new file mode 100755 index 000000000..5c07a794b --- /dev/null +++ b/src/network-web/googlesuggest.h @@ -0,0 +1,82 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2016 by Martin Rotter +// +// RSS Guard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RSS Guard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +// 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 Digia Plc and its Subsidiary(-ies) 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." + +#ifndef GOOGLESUGGEST_H +#define GOOGLESUGGEST_H + +#include + + +class LocationLineEdit; +class QNetworkReply; +class QTimer; +class QListWidget; +class QNetworkAccessManager; + +class GoogleSuggest : public QObject { + Q_OBJECT + + public: + // Constructors. + explicit GoogleSuggest(LocationLineEdit *editor, QObject *parent = 0); + virtual ~GoogleSuggest(); + + bool eventFilter(QObject *object, QEvent *event); + void showCompletion(const QStringList &choices); + + public slots: + void doneCompletion(); + void preventSuggest(); + void autoSuggest(); + void handleNetworkData(); + + private: + LocationLineEdit *editor; + QScopedPointer popup; + QTimer *timer; + QString m_enteredText; +}; + +#endif // GOOGLESUGGEST_H