diff --git a/plugins/extensions/resourcemanager/CMakeLists.txt b/plugins/extensions/resourcemanager/CMakeLists.txt index c4526d49f3..4700afeda7 100644 --- a/plugins/extensions/resourcemanager/CMakeLists.txt +++ b/plugins/extensions/resourcemanager/CMakeLists.txt @@ -1,31 +1,47 @@ -find_package(KF5NewStuffCore) +find_package(KF5NewStuffCore ${KF5_DEP_VERSION} CONFIG REQUIRED) set_package_properties(KF5NewStuffCore PROPERTIES DESCRIPTION "The KNewStuff library implements collaborative data sharing for applications." URL "https://api.kde.org/frameworks/knewstuff/html/index.html" TYPE REQUIRED PURPOSE "Required to implement sharing functionality with share.krita.org and krita") +find_package(KF5Attica ${KF5_DEP_VERSION} CONFIG REQUIRED) +set_package_properties(KF5Attica PROPERTIES DESCRIPTION "A Qt library that implements the Open Collaboration Services API" + PURPOSE "Support for KF5NewStuffCore to get the contents." + URL "https://projects.kde.org/attica" + TYPE REQUIRED + ) + set(kritaresourcemanager_SOURCES resourcemanager.cpp dlg_create_bundle.cpp dlg_bundle_manager.cpp + + widgetquestionlistener.cpp + entrydetailsdialog.cpp + itemsgridviewdelegate.cpp + itemsviewbasedelegate.cpp + itemsviewdelegate.cpp + itemsview.cpp + imagepreviewwidget.cpp dlg_content_downloader.cpp ) ki18n_wrap_ui(kritaresourcemanager_SOURCES wdgdlgcreatebundle.ui wdgdlgbundlemanager.ui + wdgdlgcontentdownloader.ui ) install( FILES data/kritapresets.knsrc datakritabrushes.knsrc data/kritapatterns.knsrc data/kritagradients.knsrc DESTINATION ${CONFIG_INSTALL_DIR} ) add_library(kritaresourcemanager MODULE ${kritaresourcemanager_SOURCES}) target_link_libraries(kritaresourcemanager kritawidgets kritaui kritalibpaintop KF5::NewStuffCore) install(TARGETS kritaresourcemanager DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install(FILES resourcemanager.xmlgui DESTINATION ${DATA_INSTALL_DIR}/kritaplugins) diff --git a/plugins/extensions/resourcemanager/dlg_content_downloader.cpp b/plugins/extensions/resourcemanager/dlg_content_downloader.cpp index d975896ad3..1251127e0a 100644 --- a/plugins/extensions/resourcemanager/dlg_content_downloader.cpp +++ b/plugins/extensions/resourcemanager/dlg_content_downloader.cpp @@ -1,145 +1,388 @@ /* - * Copyright (c) 2016 Aniketh Girish anikethgireesh@gmail.com + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#include -#include -#include -#include -#include - #include "dlg_content_downloader.h" +#include "dlg_content_downloader_p.h" + +#include "itemsviewbasedelegate_p.h" +#include "itemsviewdelegate_p.h" +#include "itemsgridviewdelegate_p.h" + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace KNS3; -class KNSResourceDownloader::Private +DlgContentDownloader::DlgContentDownloader(QWidget *parent) + : QWidget(parent) + , d(new DlgContentDownloaderPrivate(this)) { -public: - Private() : {} - const QString knsrc; - KoResourceServer m_resourcesServer; -}; + const Qstring name = QCoreApplication::applicationName(); + init(name + ".knsrc") +} -KNSResourceDownloader::KNSResourceDownloader(const KNSCore::EntryInternal& entry, const KNSCore::Author& author, QStringList categories, QObject* parent) - : KoResourceServer(parent) - , m_categories(std::move(categories)) - , m_author(author) - , m_entry(entry) - , d( new Private(this) ) +DlgContentDownloader::DlgContentDownloader(const QString &knsrc, QWidget *parent) + : QWidget(parent) + , d(new DlgContentDownloaderPrivate(this)) { - const QString fileName = QFileInfo(d->knsrc).fileName(); - setName(fileName); - setObjectName(d->knsrc); + init(knsrc); + +} - m_categories = QStringList{ fileName }; +void DlgContentDownloader::init(const QString &knsrc) +{ + d->init(knsrc); } -KNSResourceDownloader::~KNSResourceDownloader() +DlgContentDownloader::~DlgContentDownloader() { delete d; } -//Need to see which is need the name or the packageName. +void DlgContentDownloader::setTitle(const Qstring &title) +{ + d->ui.m_titleWidget->setText(title); +} -QString KNSResourceDownloader::name() +void DlgContentDownloader::title() const { - return m_entry.name(); + return d->ui.m_titleWidget->text(); } -QString KNSResourceDownloader::packagename() +KNSCore::Engine *DlgContentDownloader::engine() { - return m_entry.uniqueId(); + return d->engine; } -QString KNSResourceDownloader::author() +Entry::List DlgContentDownloader::changedEntries() { - return m_author.name(); + Entry::List entries; + foreach (const KNSCore::EntryInternal &e, d->changedEntries) { + entries.append(EntryPrivate::fromInternal(&e)); + } + return entries; +} + +Entry::List DlgContentDownloader::installedEntries() +{ + Entry::List entries; + foreach (const KNSCore::EntryInternal &e, d->changedEntries) { + if (e.status() == Entry::Installed) { + entries.append(EntryPrivate::fromInternal(&e)); + } + } + return entries; } -QStringList KNSResourceDownloader::categories() +DlgContentDownloaderPrivate::DlgContentDownloaderPrivate(DlgContentDownloader *q) + : q(q) + , engine(new KNSCore::Engine) + , model(new KNSCore::ItemsModel(engine)) + , messageTimer(nullptr) + , dialogMode(false) { - return m_categories; } -int KNSResourceDownloader::downloadcount() +DlgContentDownloaderPrivate::~DlgContentDownloaderPrivate() { - const auto downloadInfo = m_entry.downloadLinkInformationList(); - return downloadInfo.isEmpty() ? 0 : downloadInfo.at(0).size; + delete messageTimer; + delete delegate; + delete model; + delete engine; } -QString KNSResourceDownloader::source() +void DlgContentDownloaderPrivate::slotResetMessage() { - return m_entry.providerId(); + ui.m_titleWidget->setComment(QString()); } -QString KNSResourceDownloader::license() +void DlgContentDownloaderPrivate::slotNetworkTimeout() { - return m_entry.license(); + displayMessage(i18n("Timeout. Please Check your Internet connection."), KTitleWidget::ErrrorMessage); } -QString KNSResourceDownloader::details() +void DlgContentDownloaderPrivate::sortingChanged() { - QString ret = m_entry.summary(); - if (m_entry.shortSummary().isEmpty()) { //ret.isEmpty() ?? - const int newLine = ret.indexOf(QLatin1Char('\n')); - if (newLine < 0) - ret.clear(); - else - ret = ret.mid(newLine+1).trimmed(); + KNSCore::Provider::SortMode sortMode = KNSCore::Provider::Newest; + KNSCore::Provider::Filter filter = KNSCore::Provider::None; + if (ui.ratingRadio->isChecked()) { + sortMode = KNSCore::Provider::Rating; + } else if (ui.mostDownloadsRadio->isChecked()) { + sortMode = KNSCore::Provider::Downloads; + } else if (ui.installedRadio->isChecked()) { + filter = KNSCore::Provider::Installed; } - ret = ret.replace(QLatin1Char('\r'), QString()); - ret = ret.replace(QStringLiteral("[li]"), QStringLiteral("\n* ")); - ret = ret.replace(QRegularExpression(QStringLiteral("\\[/?[a-z]*\\]")), QString()); - return ret; + + model->clearEntries(); + if (filter == KNSCore::Provider::Installed) { + ui.m_searchEdit->clear(); + } + ui.m_searchEdit->setEnabled(filter != KNSCore::Provider::Installed); + + engine->setSortMode(sortMode); + engine->setFilter(filter); } -void KNSResourceDownloader::downloadResources(KoResourceServer *app) +void DlgContentDownloaderPrivate::slotUpdateSearch() { - KNS3::DownloadWidget dialog(d->knsrc, this); - dialog.exec(); + if (searchTerm == ui.m_searchEdit->text().trimmed()) { + return; + } + searchTerm = ui.m_searchEdit->text().trimmed(); +} - foreach (const KNS3::Entry& e, dialog.changedEntries()) { +void DlgContentDownloaderPrivate::slotSearchTextChanged() +{ + if (searchTerm == ui.m_searchEdit->text().trimmed()) { + return; + } + searchTerm = ui.m_searchEdit->text().trimmed(); + engine->setSearchTerm(ui.m_searchEdit->text().trimmed()); +} - foreach (const QString &file, e.installedFiles()) { - QFileInfo fi(file); - d->m_resourcesServer.importResourceFile(fi.absolutePath()+'/'+fi.fileName() , false); //need to rewrite according to MVC. - } +void DlgContentDownloaderPrivate::slotCategoryChanged(int idx) +{ + if (idx == 0) { + // All Categories item selected, reset filter + engine->setCategoriesFilter(QStringList()); + } - foreach (const QString &file, e.uninstalledFiles()) { - QFileInfo fi(file); - d->m_resourcesServer.removeResourceFile(fi.absolutePath()+'/'+fi.fileName()); //need to rewrite according to MVC. + else { + QString category = ui.m_categoryCombo->currentData().toString(); + + if (!category.isEmpty()) { + QStringList filter(category); + engine->setCategoriesFilter(filter); } + } +} + +void DlgContentDownloaderPrivate::slotEntryChanged(const KNSCore::EntryInternal &entry) +{ + changedEntries.insert(entry); + model->slotEntryChanged(entry); +} + +void DlgContentDownloaderPrivate::slotPayloadFailed(const KNSCore::EntryInternal &entry) +{ + KMessageBox::error(nullptr, i18n("Could not install %1", entry.name()), + i18n("Get Hot New Stuff!")); +} + +void DlgContentDownloaderPrivate::slotPayloadLoaded(QUrl url) +{ + Q_UNUSED(url) +} + +void DlgContentDownloaderPrivate::init(const QString &configFile) +{ + m_configFile = configFile; + ui.setupUi(q); + ui.m_titleWidget->setVisible(false); + ui.closeButton->setVisible(dialogMode); + ui.backButton->setVisible(false); + KStandardGuiItem::assign(ui.backButton, KStandardGuiItem::Back); + q->connect(ui.backButton, &QPushButton::clicked, this, &DlgContentDownloaderPrivate::slotShowOverview); + + q->connect(engine, &KNSCore::Engine::signalMessage, this, &DlgContentDownloaderPrivate::slotShowMessage); + + q->connect(engine, &KNSCore::Engine::signalProvidersLoaded, this, &DlgContentDownloaderPrivate::slotProvidersLoaded); + // Entries have been fetched and should be shown: + q->connect(engine, &KNSCore::Engine::signalEntriesLoaded, this, &DlgContentDownloaderPrivate::slotEntriesLoaded); - } + // An entry has changes - eg because it was installed + q->connect(engine, &KNSCore::Engine::signalEntryChanged, this, &DlgContentDownloaderPrivate::slotEntryChanged); + q->connect(engine, &KNSCore::Engine::signalResetView, model, &KNSCore::ItemsModel::clearEntries); + q->connect(engine, &KNSCore::Engine::signalEntryPreviewLoaded, + model, &KNSCore::ItemsModel::slotEntryPreviewLoaded); + engine->init(configFile); + + delegate = new ItemsViewDelegate(ui.m_listView, engine, q); + ui.m_listView->setItemDelegate(delegate); + ui.m_listView->setModel(model); + + ui.iconViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons"))); + ui.iconViewButton->setToolTip(i18n("Icons view mode")); + ui.listViewButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); + ui.listViewButton->setToolTip(i18n("Details view mode")); + + q->connect(ui.listViewButton, &QPushButton::clicked, this, &DlgContentDownloaderPrivate::slotListViewListMode); + q->connect(ui.iconViewButton, &QPushButton::clicked, this, &DlgContentDownloaderPrivate::slotListViewIconMode); + + q->connect(ui.newestRadio, &QRadioButton::clicked, this, &DlgContentDownloaderPrivate::sortingChanged); + q->connect(ui.ratingRadio, &QRadioButton::clicked, this, &DlgContentDownloaderPrivate::sortingChanged); + q->connect(ui.mostDownloadsRadio, &QRadioButton::clicked, this, &DlgContentDownloaderPrivate::sortingChanged); + q->connect(ui.installedRadio, &QRadioButton::clicked, this, &DlgContentDownloaderPrivate::sortingChanged); + + q->connect(ui.m_searchEdit, &KLineEdit::textChanged, this, &DlgContentDownloaderPrivate::slotSearchTextChanged); + q->connect(ui.m_searchEdit, &KLineEdit::editingFinished, this, &DlgContentDownloaderPrivate::slotUpdateSearch); + + QStringList categories = engine->categories(); + if (categories.size() < 2) { + ui.m_categoryLabel->setVisible(false); + ui.m_categoryCombo->setVisible(false); + } else { + ui.m_categoryCombo->addItem(i18n("All Categories")); + //NOTE: categories will be populated when we will get metadata from the server + } + + connect(engine, &KNSCore::Engine::signalCategoriesMetadataLoded, + this, [this](const QList &categories) { + for (auto data : categories) { + if (!data.displayName.isEmpty()) { + ui.m_categoryCombo->addItem(data.displayName, data.name); + } else { + ui.m_categoryCombo->addItem(data.name, data.name); + } + } + }); + ui.detailsStack->widget(0)->layout()->setMargin(0); + ui.detailsStack->widget(1)->layout()->setMargin(0); + + q->connect(ui.m_categoryCombo, static_cast(&KComboBox::activated), + this, &DlgContentDownloaderPrivate::slotCategoryChanged); + + // let the search line edit trap the enter key, otherwise it closes the dialog + ui.m_searchEdit->setTrapReturnKey(true); + + q->connect(ui.m_listView->verticalScrollBar(), &QScrollBar::valueChanged, this, &DlgContentDownloaderPrivate::scrollbarValueChanged); + q->connect(ui.m_listView, SIGNAL(doubleClicked(QModelIndex)), delegate, SLOT(slotDetailsClicked(QModelIndex))); + + details = new EntryDetails(engine, &ui); + q->connect(delegate, &KNS3::ItemsViewBaseDelegate::signalShowDetails, this, &DlgContentDownloaderPrivate::slotShowDetails); + + slotShowOverview(); } -void KNSResourceDownloader::setKnsrcFile(const QString &knsrcFileArg) +void DlgContentDownloaderPrivate::slotListViewListMode() { - d->knsrc = knsrcFileArg; + ui.listViewButton->setChecked(true); + ui.iconViewButton->setChecked(false); + setListViewMode(QListView::ListMode); } +void DlgContentDownloaderPrivate::slotListViewIconMode() +{ + ui.listViewButton->setChecked(false); + ui.iconViewButton->setChecked(true); + setListViewMode(QListView::IconMode); +} -KoResourceServer * KNSResourceDownloader::koresourceserver() const +void DlgContentDownloaderPrivate::setListViewMode(QListView::ViewMode mode) { - return qobject_cast(parent()); + if (ui.m_listView->viewMode() == mode) { + return; + } + + ItemsViewBaseDelegate *oldDelegate = delegate; + if (mode == QListView::ListMode) { + delegate = new ItemsViewDelegate(ui.m_listView, engine, q); + ui.m_listView->setViewMode(QListView::ListMode); + ui.m_listView->setResizeMode(QListView::Fixed); + } else { + delegate = new ItemsGridViewDelegate(ui.m_listView, engine, q); + ui.m_listView->setViewMode(QListView::IconMode); + ui.m_listView->setResizeMode(QListView::Adjust); + } + ui.m_listView->setItemDelegate(delegate); + delete oldDelegate; + q->connect(ui.m_listView, SIGNAL(doubleClicked(QModelIndex)), delegate, SLOT(slotDetailsClicked(QModelIndex))); + q->connect(delegate, &ItemsViewBaseDelegate::signalShowDetails, this, &DlgContentDownloaderPrivate::slotShowDetails); } -KNSCore::EntryInternal KNSResourceDownloader::entry() const +void DlgContentDownloaderPrivate::slotProvidersLoaded() { - return m_entry; + engine->reloadEntries(); +} + +void DlgContentDownloaderPrivate::slotEntriesLoaded(const KNSCore::EntryInternal::List &entries) +{ + foreach (const KNSCore::EntryInternal &entry, entries) { + if (!categories.contains(entry.category())) { + categories.insert(entry.category()); + } + } + model->slotEntriesLoaded(entries); } + +void DlgContentDownloaderPrivate::slotShowMessage(const QString& msg) +{ + displayMessage(msg, KTitleWidget::InfoMessage); +} + +void DlgContentDownloaderPrivate::displayMessage(const QString &msg, KTitleWidget::MessageType type, int timeOutMs) +{ + if (!messageTimer) { + messageTimer = new QTimer; + messageTimer->setSingleShot(true); + q->connect(messageTimer, &QTimer::timeout, this, &DlgContentDownloaderPrivate::slotResetMessage); + } + // stop the pending timer if present + messageTimer->stop(); + + // set text to messageLabel + ui.m_titleWidget->setComment(msg, type); + + // single shot the resetColors timer (and create it if null) + if (timeOutMs > 0) { + messageTimer->start(timeOutMs); + } +} + +void DlgContentDownloaderPrivate::slotShowDetails(const KNSCore::EntryInternal &entry) +{ + if (!entry.isValid()) { + return; + } + titleText = ui.m_titleWidget->text(); + + ui.backButton->setVisible(true); + ui.detailsStack->setCurrentIndex(1); + ui.descriptionScrollArea->verticalScrollBar()->setValue(0); + ui.preview1->setImage(QImage()); + ui.preview2->setImage(QImage()); + ui.preview3->setImage(QImage()); + ui.previewBig->setImage(QImage()); + details->setEntry(entry); +} + +void DlgContentDownloaderPrivate::slotShowOverview() +{ + ui.backButton->setVisible(false); + + ui.updateButton->setVisible(false); + ui.installButton->setVisible(false); + ui.becomeFanButton->setVisible(false); + ui.uninstallButton->setVisible(false); + + ui.detailsStack->setCurrentIndex(0); + ui.m_titleWidget->setText(titleText); +} + + +#include "moc_dlg_content_downloader.cpp" diff --git a/plugins/extensions/resourcemanager/dlg_content_downloader.h b/plugins/extensions/resourcemanager/dlg_content_downloader.h index eded39b708..530b0c0dee 100644 --- a/plugins/extensions/resourcemanager/dlg_content_downloader.h +++ b/plugins/extensions/resourcemanager/dlg_content_downloader.h @@ -1,86 +1,117 @@ /* - * Copyright (c) 2016 Aniketh Girish anikethgireesh@gmail.com + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DLG_CONTENT_DOWNLOADER_H #define DLG_CONTENT_DOWNLOADER_H #include -#include //The backend for resource selection +#include "dlg_content_downloader_p.h" //KNS Includes -#include -#include -#include +#include -class Private; -class KNSResourceDownloader: public KoResourceServer +namespace KNSCore { -Q_OBJECT -public: - explicit KNSResourceDownloader(const KNSCore::EntryInternal & c, const KNSCore::Author & a, QStringList categories, const QString &knsrc, QObject* parent); - ~KNSResourceDownloader(); - - // KoResourceServer::state state() override; this is to verify the state of the content. need to implement, ie, enumaration stating that if the package is downloading, processing etc. - - QString name(); - QString packagename() const; // Have a doubt if name() and this isn't similar? KNSCore issue - -// QVariant icon() const override; - - QString author(); - QStringList categories(); //Need to implement. No idea yet. - int downloadcount(); //no of downloads done - QString source(); //orgin / providerID - QString license(); - QString details(); //Long description - -// QString iconName() const { return m_iconName; } - - void downloadResources(KoResourceServer* app); +class Engine; +} -// void removeResources(KoResourceServer* res); +class DlgContentDownloaderPrivate; - void setKnsrcFile(const QString& knsrcFileArg); - -// QUrl thumbnailUrl() override; // If needed. -// QUrl screenshotUrl() override; // If needed. -// void fetchScreenshots() override; //if needed. - - KoResourceServer* koresourceserver() const; - - KNSCore::EntryInternal entry() const; - KNSCore::Engine* engine() const { return m_engine; } +class DlgContentDownloader: public QWidget +{ +Q_OBJECT +public: + /** + * Create a ContentDownloadDialog that lets the user install, update and uninstall + * contents. It will try to find a appname.knsrc file with the configuration. + * Appname is the name of your application as provided in the about data-> + * + * @param parent the parent of the dialog + */ + explicit DlgContentDownloader(QWidget *parent = nullptr); + + /** + * Create a DownloadDialog that lets the user install, update and uninstall + * contents. Manually specify the name of a .knsrc file where the + * KHotNewStuff configuration can be found. + * + * @param knsrc the name of the configuration file + * @param parent the parent of the dialog + */ + explicit DlgContentDownloader(const QString &knsrc, QWidget *parent = nullptr); + + /** + * destructor + */ + ~DlgContentDownloader(); + + /** + * The list of entries with changed status (installed/uninstalled) + * @return the list of entries + */ + KNS3::Entry::List changedEntries(); + + /** + * The list of entries that have been newly installed + * @return the list of entries + */ + KNS3::Entry::List installedEntries(); + + /** + * Set the title for display purposes in the widget's title. + * @param title the title of the application (or category or whatever) + */ + void setTitle(const QString &title); + + /** + * Get the current title + * @return the current title + */ + QString title() const; + + KNSCore::Engine *engine(); private: - const QStringList m_categories; -// QString m_iconName; + void init(const QString &knsrc); //knsrc is the configfile - KNSCore::EntryInternal m_entry; - KNSCore::Author m_author; - KNSCore::Engine* m_engine; + DlgContentDownloaderPrivate *const d; + Q_DISABLE_COPY(DlgContentDownloader) - class Private; + Q_PRIVATE_SLOT(d, void slotListViewListMode()) + Q_PRIVATE_SLOT(d, void slotListViewIconMode()) - Private * const d; + Q_PRIVATE_SLOT(d, void slotProvidersLoaded()) + Q_PRIVATE_SLOT(d, void slotEntriesLoaded(const KNSCore::EntryInternal::List &entries)) + Q_PRIVATE_SLOT(d, void slotEntryChanged(const KNSCore::EntryInternal &entry)) + Q_PRIVATE_SLOT(d, void slotShowDetails(const KNSCore::EntryInternal &entry)) + Q_PRIVATE_SLOT(d, void slotShowOverview()) -}; + Q_PRIVATE_SLOT(d, void slotPayloadFailed(const KNSCore::EntryInternal &entry)) + Q_PRIVATE_SLOT(d, void slotPayloadLoaded(QUrl url)) + Q_PRIVATE_SLOT(d, void slotResetMessage()) + Q_PRIVATE_SLOT(d, void slotNetworkTimeout()) + Q_PRIVATE_SLOT(d, void sortingChanged()) + Q_PRIVATE_SLOT(d, void slotSearchTextChanged()) + Q_PRIVATE_SLOT(d, void slotUpdateSearch()) + Q_PRIVATE_SLOT(d, void slotCategoryChanged(int)) +}; #endif // DLG_CONTENT_DOWNLOADER_H diff --git a/plugins/extensions/resourcemanager/dlg_content_downloader_p.h b/plugins/extensions/resourcemanager/dlg_content_downloader_p.h new file mode 100644 index 0000000000..0ec2558c11 --- /dev/null +++ b/plugins/extensions/resourcemanager/dlg_content_downloader_p.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef DLG_CONTENT_DOWNLOADER_P_H +#define DLG_CONTENT_DOWNLOADER_P_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "entrydetailsdialog_p.h" +#include "itemsviewbasedelegate_p.h" + +#include "ui_wdgdlgcontentdownloader.h" + +namespace Ui { +class WdgDlgContentDownloader; +} + +class DlgContentDownloader; + +class DlgContentDownloaderPrivate : public QObject +{ + Q_OBJECT +public: + DlgContentDownloader *q; + EntryDetails *details; + + // The engine that does all the work + KNSCore::Engine *engine; + Ui::WdgDlgContentDownloader ui; + // Model to show the entries + KNSCore::ItemsModel *model; + // Timeout for messge display + QTimer *messageTimer; + + ItemsViewBaseDelegate *delegate; + + QString searchTerm; + QSet changedEntries; + + QSet categories; + QSet providers; + + QString titleText; + QString m_knsrc; + bool dialogMode; + + explicit DlgContentDownloaderPrivate(DlgContentDownloader *q); + ~DlgContentDownloaderPrivate(); + + void init(const QString &knsrc); + void slotShowMessage(const QString& msg); + void displayMessage(const QString &msg, KTitleWidget::MessageType type, int timeOutMs = 0); + + void slotProvidersLoaded(); + void slotEntriesLoaded(const KNSCore::EntryInternal::List &entries); + void slotEntryChanged(const KNSCore::EntryInternal &entry); + + void slotShowDetails(const KNSCore::EntryInternal &entry); + void slotShowOverview(); + + void slotPayloadFailed(const KNSCore::EntryInternal &entry); + void slotPayloadLoaded(QUrl url); + + void slotResetMessage(); + void slotNetworkTimeout(); + void sortingChanged(); + void slotSearchTextChanged(); + void slotUpdateSearch(); + void slotCategoryChanged(int); + + void slotListViewListMode(); + void slotListViewIconMode(); + void setListViewMode(QListView::ViewMode mode); + +}; + +class EntryPrivate : public QSharedData +{ +public: + KNSCore::EntryInternal e; + static KNS3::Entry fromInternal(const KNSCore::EntryInternal* internal) + { + KNS3::Entry e; + e.d->e = *internal; + return e; + } +}; + +#endif // DLG_CONTENT_DOWNLOADER_P_H diff --git a/plugins/extensions/resourcemanager/entrydetailsdialog.cpp b/plugins/extensions/resourcemanager/entrydetailsdialog.cpp new file mode 100644 index 0000000000..0cc81dd355 --- /dev/null +++ b/plugins/extensions/resourcemanager/entrydetailsdialog.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "entrydetailsdialog_p.h" + +#include + +#include + +#include +#include + +using namespace KNS3; + +EntryDetails::EntryDetails(KNSCore::Engine *engine, Ui::WdgDlgContentDownloader *widget) + : QObject(widget->m_listView), m_engine(engine), ui(widget) +{ + init(); +} + +EntryDetails::~EntryDetails() +{ +} + +void EntryDetails::init() +{ + connect(ui->preview1, &ImagePreviewWidget::clicked, this, &EntryDetails::preview1Selected); + connect(ui->preview2, &ImagePreviewWidget::clicked, this, &EntryDetails::preview2Selected); + connect(ui->preview3, &ImagePreviewWidget::clicked, this, &EntryDetails::preview3Selected); + + ui->ratingWidget->setMaxRating(10); + ui->ratingWidget->setHalfStepsEnabled(true); + + updateButtons(); + connect(ui->installButton, &QAbstractButton::clicked, this, &EntryDetails::install); + connect(ui->uninstallButton, &QAbstractButton::clicked, this, &EntryDetails::uninstall); + // updating is the same as installing + connect(ui->updateButton, &QAbstractButton::clicked, this, &EntryDetails::install); +// connect(ui->becomeFanButton, &QAbstractButton::clicked, this, &EntryDetails::becomeFan); + + ui->installButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); + ui->updateButton->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update"))); + ui->uninstallButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); + + connect(m_engine, &KNSCore::Engine::signalEntryDetailsLoaded, + this, &EntryDetails::entryChanged); + connect(m_engine, &KNSCore::Engine::signalEntryChanged, + this, &EntryDetails::entryStatusChanged); + connect(m_engine, &KNSCore::Engine::signalEntryPreviewLoaded, + this, &EntryDetails::slotEntryPreviewLoaded); +} + +void EntryDetails::setEntry(const KNSCore::EntryInternal &entry) +{ + m_entry = entry; + // immediately show something + entryChanged(m_entry); + // fetch more preview images + m_engine->loadDetails(m_entry); +} + +void EntryDetails::entryChanged(const KNSCore::EntryInternal &entry) +{ + if (ui->detailsStack->currentIndex() == 0) { + return; + } + m_entry = entry; + +// if (!m_engine->userCanBecomeFan(m_entry)) { +// ui->becomeFanButton->setEnabled(false); +// } + + ui->m_titleWidget->setText(i18n("Details for %1", m_entry.name())); + if (!m_entry.author().homepage().isEmpty()) { + ui->authorLabel->setText("" + m_entry.author().name() + ""); + } else if (!m_entry.author().email().isEmpty()) { + ui->authorLabel->setText("" + m_entry.author().name() + ""); + } else { + ui->authorLabel->setText(m_entry.author().name()); + } + + QString summary = KNSCore::replaceBBCode(m_entry.summary()).replace('\n', QLatin1String("
")); + QString changelog = KNSCore::replaceBBCode(m_entry.changelog()).replace('\n', QLatin1String("
")); + + QString description = "" + summary; + if (!changelog.isEmpty()) { + description += "

" + i18n("Changelog:") + "
" + changelog + "

"; + } + description += QLatin1String(""); + ui->descriptionLabel->setText(description); + + QString homepageText("" + + i18nc("A link to the description of this Get Hot New Stuff item", "Homepage") + ""); + +// if (!m_entry.donationLink().isEmpty()) { +// homepageText += "
" + i18nc("A link to make a donation for a Get Hot New Stuff item (opens a web browser)", "Make a donation") + ""; +// } +// if (!m_entry.knowledgebaseLink().isEmpty()) { +// homepageText += "
" +// + i18ncp("A link to the knowledgebase (like a forum) (opens a web browser)", "Knowledgebase (no entries)", "Knowledgebase (%1 entries)", m_entry.numberKnowledgebaseEntries()) + ""; +// } +// ui->homepageLabel->setText(homepageText); +// ui->homepageLabel->setToolTip(i18nc("Tooltip for a link in a dialog", "Opens in a browser window")); + + if (m_entry.rating() > 0) { + ui->ratingWidget->setVisible(true); + disconnect(ui->ratingWidget, static_cast(&KRatingWidget::ratingChanged), + this, &EntryDetails::ratingChanged); + // Most of the voting is 20 - 80, so rate 20 as 0 stars and 80 as 5 stars + int rating = qMax(0, qMin(10, (m_entry.rating() - 20) / 6)); + ui->ratingWidget->setRating(rating); + connect(ui->ratingWidget, static_cast(&KRatingWidget::ratingChanged), + this, &EntryDetails::ratingChanged); + } else { + ui->ratingWidget->setVisible(false); + } + + bool hideSmallPreviews = m_entry.previewUrl(KNSCore::EntryInternal::PreviewSmall2).isEmpty() + && m_entry.previewUrl(KNSCore::EntryInternal::PreviewSmall3).isEmpty(); + + ui->preview1->setVisible(!hideSmallPreviews); + ui->preview2->setVisible(!hideSmallPreviews); + ui->preview3->setVisible(!hideSmallPreviews); + + // in static xml we often only get a small preview, use that in details + if (m_entry.previewUrl(KNSCore::EntryInternal::PreviewBig1).isEmpty() && !m_entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) { + m_entry.setPreviewUrl(m_entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1), KNSCore::EntryInternal::PreviewBig1); + m_entry.setPreviewImage(m_entry.previewImage(KNSCore::EntryInternal::PreviewSmall1), KNSCore::EntryInternal::PreviewBig1); + } + + for (int type = KNSCore::EntryInternal::PreviewSmall1; type <= KNSCore::EntryInternal::PreviewBig3; ++type) { + if (m_entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) { + ui->previewBig->setVisible(false); + } else + + if (!m_entry.previewUrl((KNSCore::EntryInternal::PreviewType)type).isEmpty()) { + if (m_entry.previewImage((KNSCore::EntryInternal::PreviewType)type).isNull()) { + m_engine->loadPreview(m_entry, (KNSCore::EntryInternal::PreviewType)type); + } else { + slotEntryPreviewLoaded(m_entry, (KNSCore::EntryInternal::PreviewType)type); + } + } + } + + updateButtons(); +} + +void EntryDetails::entryStatusChanged(const KNSCore::EntryInternal &entry) +{ + Q_UNUSED(entry); + updateButtons(); +} + +void EntryDetails::updateButtons() +{ + if (ui->detailsStack->currentIndex() == 0) { + return; + } + ui->installButton->setVisible(false); + ui->uninstallButton->setVisible(false); + ui->updateButton->setVisible(false); + + switch (m_entry.status()) { + case Entry::Installed: + ui->uninstallButton->setVisible(true); + ui->uninstallButton->setEnabled(true); + break; + case Entry::Updateable: + ui->updateButton->setVisible(true); + ui->updateButton->setEnabled(true); + ui->uninstallButton->setVisible(true); + ui->uninstallButton->setEnabled(true); + break; + + case Entry::Invalid: + case Entry::Downloadable: + ui->installButton->setVisible(true); + ui->installButton->setEnabled(true); + break; + + case Entry::Installing: + ui->installButton->setVisible(true); + ui->installButton->setEnabled(false); + break; + case Entry::Updating: + ui->updateButton->setVisible(true); + ui->updateButton->setEnabled(false); + ui->uninstallButton->setVisible(true); + ui->uninstallButton->setEnabled(false); + break; + case Entry::Deleted: + ui->installButton->setVisible(true); + ui->installButton->setEnabled(true); + break; + } + + if (ui->installButton->menu()) { + QMenu *buttonMenu = ui->installButton->menu(); + buttonMenu->clear(); + ui->installButton->setMenu(nullptr); + buttonMenu->deleteLater(); + } + if (ui->installButton->isVisible() && m_entry.downloadLinkCount() > 1) { + QMenu *installMenu = new QMenu(ui->installButton); + foreach (KNSCore::EntryInternal::DownloadLinkInformation info, m_entry.downloadLinkInformationList()) { + QString text = info.name; + if (!info.distributionType.trimmed().isEmpty()) { + text + " (" + info.distributionType.trimmed() + ')'; + } + QAction *installAction = installMenu->addAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), text); + installAction->setData(info.id); + } + ui->installButton->setMenu(installMenu); + } +} + +void EntryDetails::install() +{ + m_engine->install(m_entry); +} + +void EntryDetails::uninstall() +{ + m_engine->uninstall(m_entry); +} + +void EntryDetails::slotEntryPreviewLoaded(const KNSCore::EntryInternal &entry, KNSCore::EntryInternal::PreviewType type) +{ + if (!(entry == m_entry)) { + return; + } + + switch (type) { + case KNSCore::EntryInternal::PreviewSmall1: + ui->preview1->setImage(entry.previewImage(KNSCore::EntryInternal::PreviewSmall1)); + break; + case KNSCore::EntryInternal::PreviewSmall2: + ui->preview2->setImage(entry.previewImage(KNSCore::EntryInternal::PreviewSmall2)); + break; + case KNSCore::EntryInternal::PreviewSmall3: + ui->preview3->setImage(entry.previewImage(KNSCore::EntryInternal::PreviewSmall3)); + break; + case KNSCore::EntryInternal::PreviewBig1: + m_currentPreview = entry.previewImage(KNSCore::EntryInternal::PreviewBig1); + ui->previewBig->setImage(m_currentPreview); + break; + default: + break; + } +} + +void EntryDetails::preview1Selected() +{ + previewSelected(0); +} + +void EntryDetails::preview2Selected() +{ + previewSelected(1); +} + +void EntryDetails::preview3Selected() +{ + previewSelected(2); +} + +void EntryDetails::previewSelected(int current) +{ + KNSCore::EntryInternal::PreviewType type = static_cast(KNSCore::EntryInternal::PreviewBig1 + current); + m_currentPreview = m_entry.previewImage(type); + ui->previewBig->setImage(m_currentPreview); +} + +void EntryDetails::ratingChanged(uint rating) +{ + m_engine->vote(m_entry, rating * 10); +} diff --git a/plugins/extensions/resourcemanager/entrydetailsdialog_p.h b/plugins/extensions/resourcemanager/entrydetailsdialog_p.h new file mode 100644 index 0000000000..b4bdc5c468 --- /dev/null +++ b/plugins/extensions/resourcemanager/entrydetailsdialog_p.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ENTRYDETAILSDIALOG_P_H +#define ENTRYDETAILSDIALOG_P_H + +#include + +#include + +#include "ui_wdgdlgcontentdownloader.h" + +class QListWidgetItem; + +namespace Ui { +class WdgDlgContentDownloader; +} + +namespace KNSCore +{ +class Engine; +} + +class EntryDetails : public QObject +{ + Q_OBJECT +public: + EntryDetails(KNSCore::Engine *engine, Ui::WdgDlgContentDownloader *widget); + ~EntryDetails(); + +public Q_SLOTS: + void setEntry(const KNSCore::EntryInternal &entry); + +private Q_SLOTS: + void slotEntryPreviewLoaded(const KNSCore::EntryInternal &entry, KNSCore::EntryInternal::PreviewType type); + void install(); + void uninstall(); + + void ratingChanged(uint rating); +// void becomeFan(); + // more details loaded + void entryChanged(const KNSCore::EntryInternal &entry); + // installed/updateable etc + void entryStatusChanged(const KNSCore::EntryInternal &entry); + void updateButtons(); + + void preview1Selected(); + void preview2Selected(); + void preview3Selected(); + +private: + void init(); + void previewSelected(int current); + + KNSCore::Engine *m_engine; + Ui::WdgDlgContentDownloader *ui; + KNSCore::EntryInternal m_entry; + QImage m_currentPreview; + QListWidgetItem *m_previewItem1; + QListWidgetItem *m_previewItem2; + QListWidgetItem *m_previewItem3; +}; + +#endif // ENTRYDETAILSDIALOG_P_H diff --git a/plugins/extensions/resourcemanager/imagepreviewwidget.cpp b/plugins/extensions/resourcemanager/imagepreviewwidget.cpp new file mode 100644 index 0000000000..11abe65b39 --- /dev/null +++ b/plugins/extensions/resourcemanager/imagepreviewwidget.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "imagepreviewwidget_p.h" + +#include +#include + +#include + +#include + +using namespace KNS3; + +ImagePreviewWidget::ImagePreviewWidget(QWidget *parent) : + QWidget(parent) +{ + //installEventFilter(this); + + QString framefile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/knewstuff/pics/thumb_frame.png")); + m_frameImage = QPixmap(framefile); +} + +void ImagePreviewWidget::setImage(const QImage &preview) +{ + m_image = preview; + m_scaledImage = QImage(); + updateGeometry(); + repaint(); +} + +void ImagePreviewWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + emit clicked(); +} + +void ImagePreviewWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_scaledImage = QImage(); + repaint(); +} + +void ImagePreviewWidget::paintEvent(QPaintEvent * /*event*/) +{ + if (m_image.isNull()) { + return; + } + + QPainter painter(this); + int margin = painter.fontMetrics().height() / 2; + //painter.drawImage(contentsRect(), m_image); + + int width = contentsRect().width(); + int height = contentsRect().height(); + + if (m_scaledImage.isNull()) { + QSize scaled = QSize(qMin(width - 2 * margin, m_image.width() * 2), qMin(height - 2 * margin, m_image.height() * 2)); + m_scaledImage = m_image.scaled(scaled, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + QPoint point; + + point.setX(contentsRect().left() + ((width - m_scaledImage.width()) / 2)); + point.setY(contentsRect().top() + ((height - m_scaledImage.height()) / 2)); + + QPoint framePoint(point.x() - 5, point.y() - 5); + painter.drawPixmap(framePoint, m_frameImage.scaled(m_scaledImage.width() + 10, m_scaledImage.height() + 10)); + painter.drawImage(point, m_scaledImage); +} + +QSize ImagePreviewWidget::sizeHint() const +{ + if (m_image.isNull()) { + return QSize(); + } + QSize sh = m_image.size(); + sh.scale(maximumSize(), Qt::KeepAspectRatio); + return sh; +} diff --git a/plugins/extensions/resourcemanager/imagepreviewwidget_p.h b/plugins/extensions/resourcemanager/imagepreviewwidget_p.h new file mode 100644 index 0000000000..8a127f277e --- /dev/null +++ b/plugins/extensions/resourcemanager/imagepreviewwidget_p.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef IMAGEPREVIEWWIDGET_P_H +#define IMAGEPREVIEWWIDGET_P_H + +#include +#include + +class ImagePreviewWidget : public QWidget +{ + Q_OBJECT +public: + explicit ImagePreviewWidget(QWidget *parent = nullptr); + + void setImage(const QImage &preview); + + QSize sizeHint() const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void clicked(); + +protected: + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + +private: + QImage m_image; + QImage m_scaledImage; + QPixmap m_frameImage; +}; + +#endif // IMAGEPREVIEWWIDGET_P_H diff --git a/plugins/extensions/resourcemanager/itemsgridviewdelegate.cpp b/plugins/extensions/resourcemanager/itemsgridviewdelegate.cpp new file mode 100644 index 0000000000..ba1e86ab12 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsgridviewdelegate.cpp @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "itemsgridviewdelegate_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace KNS3; + +enum { DelegateTitleLabel, DelegateAuthorLabel, DelegateDownloadCounterLabel, + DelegateGridRatingWidget + }; + +ItemsGridViewDelegate::ItemsGridViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent) + : ItemsViewBaseDelegate(itemView, engine, parent) + , m_elementYPos(0) +{ + createOperationBar(); +} + +ItemsGridViewDelegate::~ItemsGridViewDelegate() +{ +} + +QList ItemsGridViewDelegate::createItemWidgets(const QModelIndex &index) const +{ + Q_UNUSED(index) + + QList m_widgetList; + KSqueezedTextLabel *titleLabel = new KSqueezedTextLabel(); + titleLabel->setOpenExternalLinks(true); + titleLabel->setTextElideMode(Qt::ElideRight); + // not so nice - work around constness to install the event filter + ItemsGridViewDelegate *delegate = const_cast(this); + titleLabel->installEventFilter(delegate); + m_widgetList << titleLabel; + + KSqueezedTextLabel *authorLabel = new KSqueezedTextLabel(); + authorLabel->setTextElideMode(Qt::ElideRight); + m_widgetList << authorLabel; + + KSqueezedTextLabel *downloadCounterLabel = new KSqueezedTextLabel(); + downloadCounterLabel->setTextElideMode(Qt::ElideRight); + m_widgetList << downloadCounterLabel; + + KRatingWidget *rating = new KRatingWidget(); + rating->setMaxRating(10); + rating->setHalfStepsEnabled(true); + m_widgetList << rating; + + return m_widgetList; +} + +void ItemsGridViewDelegate::updateItemWidgets(const QList widgets, + const QStyleOptionViewItem &option, + const QPersistentModelIndex &index) const +{ + const KNSCore::ItemsModel *model = qobject_cast(index.model()); + if (!model) { + return; + } + + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + int elementYPos = KNSCore::PreviewHeight + ItemMargin + FrameThickness * 2; + + //setup rating widget + KRatingWidget *rating = qobject_cast(widgets.at(DelegateGridRatingWidget)); + if (rating) { + if (entry.rating() > 0) { + rating->setToolTip(i18n("Rating: %1%", entry.rating())); + // assume all entries come with rating 0..100 but most are in the range 20 - 80, so 20 is 0 stars, 80 is 5 stars + rating->setRating((entry.rating() - 20) * 10 / 60); + //make the rating widget smaller than the one at list view + int newWidth = 68; + QSize size(newWidth, 15); + rating->resize(size); + //put rating widget under image rectangle + rating->move((ItemGridWidth - newWidth) / 2, elementYPos); + elementYPos += rating->height(); + } else { + //is it better to stay visible? + rating->setVisible(false); + } + } + elementYPos += ItemMargin; + + //setup title label + QLabel *titleLabel = qobject_cast(widgets.at(DelegateTitleLabel)); + if (titleLabel != nullptr) { + titleLabel->setWordWrap(true); + titleLabel->setAlignment(Qt::AlignHCenter); + //titleLabel->setFrameStyle(QFrame::Panel); + titleLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height() * 2)); + titleLabel->move((ItemGridWidth - titleLabel->width()) / 2, elementYPos); + + QString title; + QUrl link = qvariant_cast(entry.homepage()); + if (!link.isEmpty()) { + title += "" + entry.name() + "\n"; + } else { + title += "" + entry.name() + ""; + } + + const auto downloadInfo = entry.downloadLinkInformationList(); + if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) { + QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000); + title += i18nc("Show the size of the file in a list", "
Size: %1", sizeString); + } + + titleLabel->setText(title); + elementYPos += titleLabel->height(); + } + //setup author label + QLabel *authorLabel = qobject_cast(widgets.at(DelegateAuthorLabel)); + if (authorLabel != nullptr) { + authorLabel->setWordWrap(true); + authorLabel->setAlignment(Qt::AlignHCenter); + authorLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height())); + authorLabel->move((ItemGridWidth - authorLabel->width()) / 2, elementYPos); + + QString text; + QString authorName = entry.author().name(); + QString email = entry.author().email(); + QString authorPage = entry.author().homepage(); + + if (!authorName.isEmpty()) { + if (!authorPage.isEmpty()) { + text += "

" + i18nc("Show the author of this item in a list", "By %1", " " + authorName + "") + "

\n"; + } else if (!email.isEmpty()) { + text += "

" + i18nc("Show the author of this item in a list", "By %1", authorName) + " " + email + "

\n"; + } else { + text += "

" + i18nc("Show the author of this item in a list", "By %1", authorName) + "

\n"; + } + } + authorLabel->setText(text); + elementYPos += authorLabel->height(); + } + elementYPos += ItemMargin; + + //setup download label + QLabel *downloadLabel = qobject_cast(widgets.at(DelegateDownloadCounterLabel)); + if (downloadLabel != nullptr) { + downloadLabel->setWordWrap(true); + downloadLabel->setAlignment(Qt::AlignHCenter); + downloadLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height())); + downloadLabel->move((ItemGridWidth - downloadLabel->width()) / 2, elementYPos); + + unsigned int fans = entry.numberFans(); + unsigned int downloads = entry.downloadCount(); + + QString text; + QString fanString; + QString downloadString; + if (fans > 0) { + fanString = i18ncp("fan as in supporter", "1 fan", "%1 fans", fans); + } + if (downloads > 0) { + downloadString = i18np("1 download", "%1 downloads", downloads); + } + if (downloads > 0 || fans > 0) { + text += "

" + downloadString; + if (downloads > 0 && fans > 0) { + text += QLatin1String(", "); + } + text += fanString + QLatin1String("

"); + } + downloadLabel->setText(text); + elementYPos += downloadLabel->height(); + } + elementYPos += ItemMargin; + m_elementYPos = elementYPos; +} + +void ItemsGridViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (option.state & QStyle::State_MouseOver) { + QModelIndex focIndex = focusedIndex(); + if (m_oldIndex != focIndex || !m_operationBar->isVisible()) { + ItemsGridViewDelegate *delegate = const_cast(this); + + delegate->displayOperationBar(option.rect, index); + delegate->m_oldIndex = focIndex; + } + } else { + QModelIndex focindex = focusedIndex(); + if (!focindex.isValid()) { + m_operationBar->hide(); + } + } + + QStyle *style = QApplication::style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); + + painter->save(); + + if (option.state & QStyle::State_Selected) { + painter->setPen(QPen(option.palette.highlightedText().color())); + } else { + painter->setPen(QPen(option.palette.text().color())); + } + + const KNSCore::ItemsModel *realmodel = qobject_cast(index.model()); + + if (realmodel->hasPreviewImages()) { + int width = option.rect.width(); + + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) { + ; + } else { + QPoint centralPoint(option.rect.left() + width / 2, option.rect.top() + ItemMargin + FrameThickness + KNSCore::PreviewHeight / 2); + QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1); + if (!image.isNull()) { + QPoint previewPoint(centralPoint.x() - image.width() / 2, centralPoint.y() - image.height() / 2); + painter->drawImage(previewPoint, image); + + QPixmap frameImageScaled = m_frameImage.scaled(image.width() + FrameThickness * 2, image.height() + FrameThickness * 2); + QPoint framePoint(centralPoint.x() - frameImageScaled.width() / 2, centralPoint.y() - frameImageScaled.height() / 2); + painter->drawPixmap(framePoint, frameImageScaled); + } else { + QPoint thumbnailPoint(option.rect.left() + ((width - KNSCore::PreviewWidth - FrameThickness * 2) / 2), option.rect.top() + ItemMargin); + QRect rect(thumbnailPoint, QSize(KNSCore::PreviewWidth + FrameThickness * 2, KNSCore::PreviewHeight + FrameThickness * 2)); + painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview")); + } + } + } + + painter->restore(); +} + +QSize ItemsGridViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + + QSize size; + + size.setWidth(ItemGridWidth); + size.setHeight(qMax(option.fontMetrics.height() * 13, ItemGridHeight)); // up to 6 lines of text, and two margins + return size; +} + +void ItemsGridViewDelegate::createOperationBar() +{ + m_operationBar = new QWidget(this->itemView()->viewport()); + + m_detailsButton = new QToolButton(); + m_detailsButton->setToolButtonStyle(Qt::ToolButtonFollowStyle); + m_detailsButton->setPopupMode(QToolButton::InstantPopup); + m_detailsButton->setToolTip(i18n("Details")); + m_detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo"))); + setBlockedEventTypes(m_detailsButton, QList() << QEvent::MouseButtonPress + << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); + connect(m_detailsButton, &QToolButton::clicked, + this, static_cast(&ItemsGridViewDelegate::slotDetailsClicked)); + + m_installButton = new QToolButton(); + m_installButton->setToolButtonStyle(Qt::ToolButtonFollowStyle); + m_installButton->setPopupMode(QToolButton::InstantPopup); + + setBlockedEventTypes(m_installButton, QList() << QEvent::MouseButtonPress + << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); + connect(m_installButton, &QAbstractButton::clicked, this, &ItemsGridViewDelegate::slotInstallClicked); + connect(m_installButton, &QToolButton::triggered, this, &ItemsGridViewDelegate::slotInstallActionTriggered); + + if (m_installButton->menu()) { + QMenu *buttonMenu = m_installButton->menu(); + buttonMenu->clear(); + m_installButton->setMenu(nullptr); + buttonMenu->deleteLater(); + } + + QHBoxLayout *layout = new QHBoxLayout(m_operationBar); + + layout->setSpacing(1); + layout->addWidget(m_installButton); + layout->addWidget(m_detailsButton); + + m_operationBar->adjustSize(); + m_operationBar->hide(); +} + +void ItemsGridViewDelegate::displayOperationBar(const QRect &rect, const QModelIndex &index) +{ + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + if (m_installButton != nullptr) { + if (m_installButton->menu() != nullptr) { + QMenu *buttonMenu = m_installButton->menu(); + buttonMenu->clear(); + m_installButton->setMenu(nullptr); + buttonMenu->deleteLater(); + } + + bool installable = false; + bool enabled = true; + QString text; + QIcon icon; + + switch (entry.status()) { + case Entry::Installed: + text = i18n("Uninstall"); + icon = m_iconDelete; + break; + case Entry::Updateable: + text = i18n("Update"); + icon = m_iconUpdate; + installable = true; + break; + case Entry::Installing: + text = i18n("Installing"); + enabled = false; + icon = m_iconUpdate; + break; + case Entry::Updating: + text = i18n("Updating"); + enabled = false; + icon = m_iconUpdate; + break; + case Entry::Downloadable: + text = i18n("Install"); + icon = m_iconInstall; + installable = true; + break; + case Entry::Deleted: + text = i18n("Install Again"); + icon = m_iconInstall; + installable = true; + break; + default: + text = i18n("Install"); + } + m_installButton->setToolTip(text); + m_installButton->setIcon(icon); + m_installButton->setEnabled(enabled); + if (installable && entry.downloadLinkCount() > 1) { + QMenu *installMenu = new QMenu(m_installButton); + foreach (const KNSCore::EntryInternal::DownloadLinkInformation &info, entry.downloadLinkInformationList()) { + QString text = info.name; + if (!info.distributionType.trimmed().isEmpty()) { + text + " (" + info.distributionType.trimmed() + ')'; + } + QAction *installAction = installMenu->addAction(m_iconInstall, text); + installAction->setData(QPoint(index.row(), info.id)); + } + m_installButton->setMenu(installMenu); + } + + m_operationBar->move(rect.left() + (ItemGridWidth - m_operationBar->width()) / 2, rect.top() + m_elementYPos); + m_operationBar->show(); + } +} diff --git a/plugins/extensions/resourcemanager/itemsgridviewdelegate_p.h b/plugins/extensions/resourcemanager/itemsgridviewdelegate_p.h new file mode 100644 index 0000000000..8a22e4f4a7 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsgridviewdelegate_p.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ITEMSGRIDVIEWDELEGATE_P_H +#define ITEMSGRIDVIEWDELEGATE_P_H + +#include "itemsviewbasedelegate_p.h" + +class QToolButton; + +static const int ItemGridHeight = 202; +static const int ItemGridWidth = 158; + +static const int FrameThickness = 5; +static const int ItemMargin = 2; +class ItemsGridViewDelegate: public ItemsViewBaseDelegate +{ + Q_OBJECT +public: + explicit ItemsGridViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent = nullptr); + ~ItemsGridViewDelegate(); + + // paint the item at index with all its attributes shown + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + + // get the list of widgets + QList createItemWidgets(const QModelIndex &index) const Q_DECL_OVERRIDE; + + // update the widgets + virtual void updateItemWidgets(const QList widgets, + const QStyleOptionViewItem &option, + const QPersistentModelIndex &index) const Q_DECL_OVERRIDE; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + +private: + void createOperationBar(); + void displayOperationBar(const QRect &rect, const QModelIndex &index); + +private: + QWidget *m_operationBar; + QToolButton *m_detailsButton; + QToolButton *m_installButton; + + QModelIndex m_oldIndex; + mutable int m_elementYPos; +}; + +#endif // ITEMSGRIDVIEWDELEGATE_P_H diff --git a/plugins/extensions/resourcemanager/itemsview.cpp b/plugins/extensions/resourcemanager/itemsview.cpp new file mode 100644 index 0000000000..7fb076bee5 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsview.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "itemsview_p.h" + +#include + +ItemsView::ItemsView(QWidget *parent) + :QListView(parent) +{ +} + +void ItemsView::wheelEvent(QWheelEvent *event) +{ + // this is a workaround because scrolling by mouse wheel is broken in Qt list views for big items + verticalScrollBar()->setSingleStep(10); + QListView::wheelEvent(event); +} diff --git a/plugins/extensions/resourcemanager/itemsview_p.h b/plugins/extensions/resourcemanager/itemsview_p.h new file mode 100644 index 0000000000..9604447222 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsview_p.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef ITEMSVIEW_P_H +#define ITEMSVIEW_P_H + +#include + +class ItemsView: public QListView +{ +public: + explicit ItemsView(QWidget *parent = nullptr); + +protected: + void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; +}; + +#endif // ITEMSVIEW_P_H diff --git a/plugins/extensions/resourcemanager/itemsviewbasedelegate.cpp b/plugins/extensions/resourcemanager/itemsviewbasedelegate.cpp new file mode 100644 index 0000000000..7deaf28a02 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsviewbasedelegate.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "itemsviewbasedelegate_p.h" + +#include + +#include "entrydetailsdialog_p.h" + +#include +#include + +using namespace KNS3; + +ItemsViewBaseDelegate::ItemsViewBaseDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent) + : KWidgetItemDelegate(itemView, parent) + , m_engine(engine) + , m_itemView(itemView) + , m_iconInvalid(QIcon::fromTheme(QStringLiteral("dialog-error"))) + , m_iconInstall(QIcon::fromTheme(QStringLiteral("dialog-ok"))) + , m_iconUpdate(QIcon::fromTheme(QStringLiteral("system-software-update"))) + , m_iconDelete(QIcon::fromTheme(QStringLiteral("edit-delete"))) + , m_noImage(SmallIcon(QStringLiteral("image-missing"), KIconLoader::SizeLarge, KIconLoader::DisabledState)) +{ + QString framefile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/knewstuff/pics/thumb_frame.png")); + m_frameImage = QPixmap(framefile); +} + +ItemsViewBaseDelegate::~ItemsViewBaseDelegate() +{ +} + +bool ItemsViewBaseDelegate::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonDblClick) { + slotDetailsClicked(); + return true; + } + + return KWidgetItemDelegate::eventFilter(watched, event); +} + +void ItemsViewBaseDelegate::slotLinkClicked(const QString &url) +{ + Q_UNUSED(url) + QModelIndex index = focusedIndex(); + Q_ASSERT(index.isValid()); + + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + m_engine->contactAuthor(entry); +} + +void ItemsViewBaseDelegate::slotInstallClicked() +{ + QModelIndex index = focusedIndex(); + if (index.isValid()) { + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + if (!entry.isValid()) { + return; + } + + if (entry.status() == Entry::Installed) { + m_engine->uninstall(entry); + } else { + m_engine->install(entry); + } + } +} + +void ItemsViewBaseDelegate::slotInstallActionTriggered(QAction *action) +{ + if (action->data().isNull()) + return; + + QPoint rowDownload = action->data().toPoint(); + int row = rowDownload.x(); + QModelIndex index = m_itemView->model()->index(row, 0); + if (index.isValid()) { + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + m_engine->install(entry, rowDownload.y()); + } +} + +void ItemsViewBaseDelegate::slotDetailsClicked() +{ + QModelIndex index = focusedIndex(); + slotDetailsClicked(index); +} + +void ItemsViewBaseDelegate::slotDetailsClicked(const QModelIndex &index) +{ + if (index.isValid()) { + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + if (!entry.isValid()) { + return; + } + emit signalShowDetails(entry); + } +} diff --git a/plugins/extensions/resourcemanager/itemsviewbasedelegate_p.h b/plugins/extensions/resourcemanager/itemsviewbasedelegate_p.h new file mode 100644 index 0000000000..4ef589b691 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsviewbasedelegate_p.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ITEMSVIEWBASEDELEGATE_P_H +#define ITEMSVIEWBASEDELEGATE_P_H + +#include +#include +#include +#include +#include + +#include +#include + +#include + +class ItemsViewBaseDelegate: public KWidgetItemDelegate +{ + Q_OBJECT +public: + explicit ItemsViewBaseDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent = nullptr); + virtual ~ItemsViewBaseDelegate(); + // paint the item at index with all its attributes shown + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE = 0; + + // get the list of widgets + QList createItemWidgets(const QModelIndex &index) const Q_DECL_OVERRIDE = 0; + + // update the widgets + void updateItemWidgets(const QList widgets, + const QStyleOptionViewItem &option, + const QPersistentModelIndex &index) const Q_DECL_OVERRIDE = 0; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE = 0; + +Q_SIGNALS: + void signalShowDetails(const KNSCore::EntryInternal &entry); + +protected Q_SLOTS: + bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; + void slotInstallClicked(); + void slotInstallActionTriggered(QAction *action); + void slotLinkClicked(const QString &url); + void slotDetailsClicked(const QModelIndex &index); + void slotDetailsClicked(); + +protected: + KNSCore::Engine *m_engine; + QAbstractItemView *m_itemView; + QIcon m_iconInvalid; + QIcon m_iconDownloadable; + QIcon m_iconInstall; + QIcon m_iconUpdate; + QIcon m_iconDelete; + QPixmap m_frameImage; + QPixmap m_noImage; + QSize m_buttonSize; +}; + +#endif // ITEMSVIEWBASEDELEGATE_P_H diff --git a/plugins/extensions/resourcemanager/itemsviewdelegate.cpp b/plugins/extensions/resourcemanager/itemsviewdelegate.cpp new file mode 100644 index 0000000000..4f096352d4 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsviewdelegate.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "itemsviewdelegate_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "entrydetailsdialog_p.h" + +using namespace KNS3; + +enum { DelegateLabel, DelegateInstallButton, DelegateDetailsButton, DelegateRatingWidget }; + +ItemsViewDelegate::ItemsViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent) + : ItemsViewBaseDelegate(itemView, engine, parent) +{ +} + +ItemsViewDelegate::~ItemsViewDelegate() +{ +} + +QList ItemsViewDelegate::createItemWidgets(const QModelIndex &index) const +{ + Q_UNUSED(index); + QList list; + + QLabel *infoLabel = new QLabel(); + infoLabel->setOpenExternalLinks(true); + // not so nice - work around constness to install the event filter + ItemsViewDelegate *delegate = const_cast(this); + infoLabel->installEventFilter(delegate); + list << infoLabel; + + QToolButton *installButton = new QToolButton(); + installButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + list << installButton; + setBlockedEventTypes(installButton, QList() << QEvent::MouseButtonPress + << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); + connect(installButton, &QAbstractButton::clicked, this, &ItemsViewDelegate::slotInstallClicked); + connect(installButton, &QToolButton::triggered, this, &ItemsViewDelegate::slotInstallActionTriggered); + + QToolButton *detailsButton = new QToolButton(); + detailsButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + list << detailsButton; + setBlockedEventTypes(detailsButton, QList() << QEvent::MouseButtonPress + << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); + connect(detailsButton, &QToolButton::clicked, + this, static_cast(&ItemsViewDelegate::slotDetailsClicked)); + + KRatingWidget *rating = new KRatingWidget(); + rating->setMaxRating(10); + rating->setHalfStepsEnabled(true); + list << rating; + //connect(rating, SIGNAL(ratingChanged(uint)), this, SLOT()); + + return list; +} + +void ItemsViewDelegate::updateItemWidgets(const QList widgets, + const QStyleOptionViewItem &option, + const QPersistentModelIndex &index) const +{ + const KNSCore::ItemsModel *model = qobject_cast(index.model()); + if (!model) { + return; + } + + const KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + + // setup the install button + int margin = option.fontMetrics.height() / 2; + int right = option.rect.width(); + + QToolButton *installButton = qobject_cast(widgets.at(DelegateInstallButton)); + if (installButton != nullptr) { + + if (installButton->menu()) { + QMenu *buttonMenu = installButton->menu(); + buttonMenu->clear(); + installButton->setMenu(nullptr); + buttonMenu->deleteLater(); + } + + bool installable = false; + bool enabled = true; + QString text; + QIcon icon; + + switch (entry.status()) { + case Entry::Installed: + text = i18n("Uninstall"); + icon = m_iconDelete; + break; + case Entry::Updateable: + text = i18n("Update"); + icon = m_iconUpdate; + installable = true; + break; + case Entry::Installing: + text = i18n("Installing"); + enabled = false; + icon = m_iconUpdate; + break; + case Entry::Updating: + text = i18n("Updating"); + enabled = false; + icon = m_iconUpdate; + break; + case Entry::Downloadable: + text = i18n("Install"); + icon = m_iconInstall; + installable = true; + break; + case Entry::Deleted: + text = i18n("Install Again"); + icon = m_iconInstall; + installable = true; + break; + default: + text = i18n("Install"); + } + installButton->setText(text); + installButton->setEnabled(enabled); + installButton->setIcon(icon); + installButton->setPopupMode(QToolButton::InstantPopup); + + if (installable && entry.downloadLinkCount() > 1) { + QMenu *installMenu = new QMenu(installButton); + foreach (const KNSCore::EntryInternal::DownloadLinkInformation &info, entry.downloadLinkInformationList()) { + QString text = info.name; + if (!info.distributionType.trimmed().isEmpty()) { + text += " (" + info.distributionType.trimmed() + ')'; + } + QAction *installAction = installMenu->addAction(m_iconInstall, text); + installAction->setData(QPoint(index.row(), info.id)); + } + installButton->setMenu(installMenu); + } else if (entry.status() == Entry::Installed && m_engine->hasAdoptionCommand()) { + QMenu* m = new QMenu(installButton); + m->addAction(i18n("Use"), m, [this, entry](){ + QStringList args = KShell::splitArgs(m_engine->adoptionCommand(entry)); + QProcess::startDetached(args.takeFirst(), args); + }); + installButton->setPopupMode(QToolButton::MenuButtonPopup); + installButton->setMenu(m); + } + } + + QToolButton *detailsButton = qobject_cast(widgets.at(DelegateDetailsButton)); + if (detailsButton) { + detailsButton->setText(i18n("Details")); + detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo"))); + } + + if (installButton && detailsButton) { + if (m_buttonSize.width() < installButton->sizeHint().width()) { + const_cast(m_buttonSize) = QSize( + qMax(option.fontMetrics.height() * 7, + qMax(installButton->sizeHint().width(), detailsButton->sizeHint().width())), + installButton->sizeHint().height()); + } + installButton->resize(m_buttonSize); + installButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() * 1.5); + detailsButton->resize(m_buttonSize); + detailsButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() / 2); + } + + QLabel *infoLabel = qobject_cast(widgets.at(DelegateLabel)); + infoLabel->setWordWrap(true); + if (infoLabel != nullptr) { + if (model->hasPreviewImages()) { + // move the text right by kPreviewWidth + margin pixels to fit the preview + infoLabel->move(KNSCore::PreviewWidth + margin * 2, 0); + infoLabel->resize(QSize(option.rect.width() - KNSCore::PreviewWidth - (margin * 6) - m_buttonSize.width(), option.fontMetrics.height() * 7)); + + } else { + infoLabel->move(margin, 0); + infoLabel->resize(QSize(option.rect.width() - (margin * 4) - m_buttonSize.width(), option.fontMetrics.height() * 7)); + } + + QString text = QLatin1String("\n" + "

"); + + QUrl link = qvariant_cast(entry.homepage()); + if (!link.isEmpty()) { + text += "

" + entry.name() + "

\n"; + } else { + text += entry.name(); + } + + const auto downloadInfo = entry.downloadLinkInformationList(); + if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) { + QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000); + text += i18nc("Show the size of the file in a list", "

Size: %1

", sizeString); + } + + text += QLatin1String("

\n"); + + QString authorName = entry.author().name(); + QString email = entry.author().email(); + QString authorPage = entry.author().homepage(); + + if (!authorName.isEmpty()) { + if (!authorPage.isEmpty()) { + text += "

" + i18nc("Show the author of this item in a list", "By %1", " " + authorName + "") + "

\n"; + } else if (!email.isEmpty()) { + text += "

" + i18nc("Show the author of this item in a list", "By %1", authorName) + " " + email + "

\n"; + } else { + text += "

" + i18nc("Show the author of this item in a list", "By %1", authorName) + "

\n"; + } + } + + QString summary = "

" + option.fontMetrics.elidedText(entry.summary(), + Qt::ElideRight, infoLabel->width() * 3) + "

\n"; + text += summary; + + unsigned int downloads = entry.downloadCount(); + + QString downloadString; + + if (downloads > 0) { + downloadString = i18np("1 download", "%1 downloads", downloads); + } + if (downloads > 0) { + text += "

" + downloadString; + if (downloads > 0) { + text += QLatin1String(", "); + } + } + + text += QLatin1String(""); + // use simplified to get rid of newlines etc + text = KNSCore::replaceBBCode(text).simplified(); + infoLabel->setText(text); + } + + KRatingWidget *rating = qobject_cast(widgets.at(DelegateRatingWidget)); + if (rating) { + if (entry.rating() > 0) { + rating->setToolTip(i18n("Rating: %1%", entry.rating())); + // assume all entries come with rating 0..100 but most are in the range 20 - 80, so 20 is 0 stars, 80 is 5 stars + rating->setRating((entry.rating() - 20) * 10 / 60); + // put the rating label below the install button + rating->move(right - installButton->width() - margin, option.rect.height() / 2 + installButton->height() / 2); + rating->resize(m_buttonSize); + } else { + rating->setVisible(false); + } + } +} + +// draws the preview +void ItemsViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int margin = option.fontMetrics.height() / 2; + + QStyle *style = QApplication::style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); + + painter->save(); + + if (option.state & QStyle::State_Selected) { + painter->setPen(QPen(option.palette.highlightedText().color())); + } else { + painter->setPen(QPen(option.palette.text().color())); + } + + const KNSCore::ItemsModel *realmodel = qobject_cast(index.model()); + + if (realmodel->hasPreviewImages()) { + int height = option.rect.height(); + QPoint point(option.rect.left() + margin, option.rect.top() + ((height - KNSCore::PreviewHeight) / 2)); + + KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); + if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) { + // paint the no preview icon + //point.setX((PreviewWidth - m_noImage.width())/2 + 5); + //point.setY(option.rect.top() + ((height - m_noImage.height()) / 2)); + //painter->drawPixmap(point, m_noImage); + } else { + QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1); + if (!image.isNull()) { + point.setX((KNSCore::PreviewWidth - image.width()) / 2 + 5); + point.setY(option.rect.top() + ((height - image.height()) / 2)); + painter->drawImage(point, image); + + QPoint framePoint(point.x() - 5, point.y() - 5); + painter->drawPixmap(framePoint, m_frameImage.scaled(image.width() + 10, image.height() + 10)); + } else { + QRect rect(point, QSize(KNSCore::PreviewWidth, KNSCore::PreviewHeight)); + painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview")); + } + } + + } + painter->restore(); +} + +QSize ItemsViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + + QSize size; + + size.setWidth(option.fontMetrics.height() * 4); + size.setHeight(qMax(option.fontMetrics.height() * 7, KNSCore::PreviewHeight)); // up to 6 lines of text, and two margins + return size; +} diff --git a/plugins/extensions/resourcemanager/itemsviewdelegate_p.h b/plugins/extensions/resourcemanager/itemsviewdelegate_p.h new file mode 100644 index 0000000000..7873e7a208 --- /dev/null +++ b/plugins/extensions/resourcemanager/itemsviewdelegate_p.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ITEMSVIEWDELEGATE_P_H +#define ITEMSVIEWDELEGATE_P_H + +#include "itemsviewbasedelegate_p.h" + +class ItemsViewDelegate: public ItemsViewBaseDelegate +{ + Q_OBJECT +public: + explicit ItemsViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent = nullptr); + ~ItemsViewDelegate(); + + // paint the item at index with all its attributes shown + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + + // get the list of widgets + QList createItemWidgets(const QModelIndex &index) const Q_DECL_OVERRIDE; + + // update the widgets + virtual void updateItemWidgets(const QList widgets, + const QStyleOptionViewItem &option, + const QPersistentModelIndex &index) const Q_DECL_OVERRIDE; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; +}; + +#endif // ITEMSVIEWDELEGATE_P_H diff --git a/plugins/extensions/resourcemanager/wdgdlgcontentdownloader.ui b/plugins/extensions/resourcemanager/wdgdlgcontentdownloader.ui new file mode 100644 index 0000000000..e9597232a5 --- /dev/null +++ b/plugins/extensions/resourcemanager/wdgdlgcontentdownloader.ui @@ -0,0 +1,509 @@ + + + DownloadWidget + + + + 0 + 0 + 724 + 572 + + + + + + + + + + 0 + + + + + + + true + + + + 2 + 0 + + + + true + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + + + + + 10 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Category: + + + m_categoryCombo + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Order by: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Search: + + + m_searchEdit + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Enter search phrase here + + + true + + + + + + + true + + + + + + + true + + + true + + + false + + + + + + + + + + + + + true + + + + + 0 + 0 + 678 + 478 + + + + + 0 + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100 + 100 + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100 + 100 + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100 + 100 + + + + + + + + + 0 + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + + + + + + 0 + 0 + + + + Author: + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + + + + + 0 + 10 + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + + + + + + + 0 + 0 + + + + + 300 + 100 + + + + + 300 + 300 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + + + + + true + + + Update + + + + + + + true + + + Uninstall + + + + + + + Become a Fan + + + + + + + Install + + + + + + + + + + + + + KTitleWidget + QWidget +

ktitlewidget.h
+ + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + ItemsView + QListView +
itemsview_p.h
+
+ + KRatingWidget + QWidget +
kratingwidget.h
+ 1 +
+ + ImagePreviewWidget + QWidget +
imagepreviewwidget_p.h
+ 1 +
+ + + + diff --git a/plugins/extensions/resourcemanager/widgetquestionlistener.cpp b/plugins/extensions/resourcemanager/widgetquestionlistener.cpp new file mode 100644 index 0000000000..e7fc79b747 --- /dev/null +++ b/plugins/extensions/resourcemanager/widgetquestionlistener.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "widgetquestionlistener.h" + +#include + +#include +#include +#include + +class WidgetQuestionListenerHelper { +public: + WidgetQuestionListenerHelper() : q(nullptr) {} + ~WidgetQuestionListenerHelper() { delete q; } + WidgetQuestionListener *q; +}; +Q_GLOBAL_STATIC(WidgetQuestionListenerHelper, s_widgetQuestionListener) + +WidgetQuestionListener* WidgetQuestionListener::instance() +{ + if(!s_widgetQuestionListener()->q) { + new WidgetQuestionListener; + } + return s_widgetQuestionListener()->q; +} + +WidgetQuestionListener::WidgetQuestionListener() + : KNSCore::QuestionListener(nullptr) +{ + s_widgetQuestionListener()->q = this; +} + +WidgetQuestionListener::~WidgetQuestionListener() +{ +} + +void WidgetQuestionListener::askQuestion(KNSCore::Question* question) +{ + switch(question->questionType()) + { + case KNSCore::Question::SelectFromListQuestion: + { + bool ok = false; + question->setResponse(QInputDialog::getItem(nullptr, question->title(), question->question(), question->list(), 0, false, &ok)); + if(ok) { + question->setResponse(KNSCore::Question::OKResponse); + } + else { + question->setResponse(KNSCore::Question::CancelResponse); + } + } + break; + case KNSCore::Question::ContinueCancelQuestion: + { + KMessageBox::ButtonCode response = KMessageBox::warningContinueCancel(nullptr, question->question(), question->title()); + if(response == KMessageBox::Continue) { + question->setResponse(KNSCore::Question::ContinueResponse); + } + else { + question->setResponse(KNSCore::Question::CancelResponse); + } + } + break; + case KNSCore::Question::PasswordQuestion: + { + KPasswordDialog dlg; + dlg.setPrompt(question->question()); + if(dlg.exec()) { + question->setResponse(dlg.password()); + question->setResponse(KNSCore::Question::ContinueResponse); + } + else { + question->setResponse(KNSCore::Question::CancelResponse); + } + } + break; + case KNSCore::Question::YesNoQuestion: + default: + { + KMessageBox::ButtonCode response = KMessageBox::questionYesNo(nullptr, question->question(), question->title()); + if(response == KMessageBox::Yes) { + question->setResponse(KNSCore::Question::YesResponse); + } + else { + question->setResponse(KNSCore::Question::NoResponse); + } + } + break; + } +} diff --git a/plugins/extensions/resourcemanager/widgetquestionlistener.h b/plugins/extensions/resourcemanager/widgetquestionlistener.h new file mode 100644 index 0000000000..6d8125f730 --- /dev/null +++ b/plugins/extensions/resourcemanager/widgetquestionlistener.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Aniketh Girish anikethgireesh@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef WIDGETQUESTIONLISTENER_H +#define WIDGETQUESTIONLISTENER_H + +#include + +class WidgetQuestionListener : public KNSCore::QuestionListener +{ + Q_OBJECT + Q_DISABLE_COPY(WidgetQuestionListener) +public: + static WidgetQuestionListener* instance(); + virtual ~WidgetQuestionListener(); + + Q_SLOT virtual void askQuestion(KNSCore::Question* question) Q_DECL_OVERRIDE; +private: + WidgetQuestionListener(); +}; + +#endif // WIDGETQUESTIONLISTENER_H