diff --git a/app/config/appearancesettings.cpp b/app/config/appearancesettings.cpp index b7d5c6f..458a621 100644 --- a/app/config/appearancesettings.cpp +++ b/app/config/appearancesettings.cpp @@ -1,567 +1,553 @@ /* Copyright (C) 2008-2009 by Eike Hein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor appro- ved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 this program. If not, see http://www.gnu.org/licenses/. */ #include "appearancesettings.h" #include "settings.h" #include "skinlistdelegate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include AppearanceSettings::AppearanceSettings(QWidget* parent) : QWidget(parent) { setupUi(this); kcfg_Skin->hide(); kcfg_SkinInstalledWithKns->hide(); m_skins = new QStandardItemModel(this); m_skinListDelegate = new SkinListDelegate(this); skinList->setModel(m_skins); skinList->setItemDelegate(m_skinListDelegate); connect(skinList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateSkinSetting())); connect(skinList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateRemoveSkinButton())); connect(installButton, SIGNAL(clicked()), this, SLOT(installSkin())); connect(removeButton, SIGNAL(clicked()), this, SLOT(removeSelectedSkin())); installButton->setIcon(QIcon::fromTheme(QStringLiteral("folder"))); removeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); ghnsButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); m_knsConfigFileName = QLatin1String("yakuake.knsrc"); m_knsDownloadManager = new KNSCore::DownloadManager(m_knsConfigFileName); connect(ghnsButton, &QPushButton::clicked, this, &AppearanceSettings::getNewSkins); m_selectedSkinId = Settings::skin(); // Get all local skin directories. // One for manually installed skins, one for skins installed // through KNS3. m_localSkinsDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/yakuake/skins/"); m_knsSkinDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/yakuake/kns_skins/"); populateSkinList(); } AppearanceSettings::~AppearanceSettings() { } void AppearanceSettings::showEvent(QShowEvent* event) { populateSkinList(); if (skinList->currentIndex().isValid()) skinList->scrollTo(skinList->currentIndex()); QWidget::showEvent(event); } void AppearanceSettings::populateSkinList() { m_skins->clear(); QStringList allSkinLocations; allSkinLocations << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/yakuake/skins/"), QStandardPaths::LocateDirectory); allSkinLocations << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("/yakuake/kns_skins/"), QStandardPaths::LocateDirectory); Q_FOREACH (const QString& skinLocation, allSkinLocations) { populateSkinList(skinLocation); } m_skins->sort(0); updateRemoveSkinButton(); } void AppearanceSettings::populateSkinList(const QString& installLocation) { QDirIterator it(installLocation, QDir::Dirs | QDir::NoDotAndDotDot); while (it.hasNext()) { const QDir& skinDir(it.next()); if (skinDir.exists(QStringLiteral("title.skin")) && skinDir.exists(QStringLiteral("tabs.skin"))) { QStandardItem* skin = createSkinItem(skinDir.absolutePath()); if (!skin) continue; m_skins->appendRow(skin); if (skin->data(SkinId).toString() == m_selectedSkinId) skinList->setCurrentIndex(skin->index()); } } } QStandardItem* AppearanceSettings::createSkinItem(const QString& skinDir) { QString skinId = skinDir.section(QStringLiteral("/"), -1, -1); QString titleName, tabName, skinName; QString titleAuthor, tabAuthor, skinAuthor; QString titleIcon, tabIcon; QIcon skinIcon; // Check if the skin dir starts with the path where all // KNS3 skins are found in. bool isKnsSkin = skinDir.startsWith(m_knsSkinDir); KConfig titleConfig(skinDir + QStringLiteral("/title.skin"), KConfig::SimpleConfig); KConfigGroup titleDescription = titleConfig.group("Description"); KConfig tabConfig(skinDir + QStringLiteral("/tabs.skin"), KConfig::SimpleConfig); KConfigGroup tabDescription = tabConfig.group("Description"); titleName = titleDescription.readEntry("Skin", ""); titleAuthor = titleDescription.readEntry("Author", ""); titleIcon = skinDir + titleDescription.readEntry("Icon", ""); tabName = tabDescription.readEntry("Skin", ""); tabAuthor = tabDescription.readEntry("Author", ""); tabIcon = skinDir + tabDescription.readEntry("Icon", ""); skinName = titleName.isEmpty() ? tabName : titleName; skinAuthor = titleAuthor.isEmpty() ? tabAuthor : titleAuthor; titleIcon.isEmpty() ? skinIcon.addPixmap(tabIcon) : skinIcon.addPixmap(titleIcon); if (skinName.isEmpty() || skinAuthor.isEmpty()) skinName = skinId; if (skinAuthor.isEmpty()) skinAuthor = xi18nc("@item:inlistbox Unknown skin author", "Unknown"); QStandardItem* skin = new QStandardItem(skinName); skin->setData(skinId, SkinId); skin->setData(skinDir, SkinDir); skin->setData(skinName, SkinName); skin->setData(skinAuthor, SkinAuthor); skin->setData(skinIcon, SkinIcon); skin->setData(isKnsSkin, SkinInstalledWithKns); return skin; } void AppearanceSettings::updateSkinSetting() { QString skinId = skinList->currentIndex().data(SkinId).toString(); if (!skinId.isEmpty()) { m_selectedSkinId = skinId; kcfg_Skin->setText(skinId); kcfg_SkinInstalledWithKns->setChecked(skinList->currentIndex().data(SkinInstalledWithKns).toBool()); } } void AppearanceSettings::resetSelection() { m_selectedSkinId = Settings::skin(); QModelIndexList skins = m_skins->match(m_skins->index(0, 0), SkinId, Settings::skin(), 1, Qt::MatchExactly | Qt::MatchWrap); if (skins.count() > 0) skinList->setCurrentIndex(skins.at(0)); } void AppearanceSettings::installSkin() { QStringList mimeTypes; mimeTypes << QStringLiteral("application/x-compressed-tar"); mimeTypes << QStringLiteral("application/x-xz-compressed-tar"); mimeTypes << QStringLiteral("application/x-bzip-compressed-tar"); mimeTypes << QStringLiteral("application/zip"); mimeTypes << QStringLiteral("application/x-tar"); QFileDialog fileDialog(parentWidget()); fileDialog.setWindowTitle(i18nc("@title:window", "Select the skin archive to install")); fileDialog.setMimeTypeFilters(mimeTypes); fileDialog.setFileMode(QFileDialog::ExistingFile); QUrl skinUrl; if (fileDialog.exec() && !fileDialog.selectedUrls().isEmpty()) skinUrl = fileDialog.selectedUrls().at(0); else return; m_installSkinFile.open(); KIO::CopyJob *job = KIO::copy(skinUrl, QUrl::fromLocalFile(m_installSkinFile.fileName()), KIO::JobFlag::HideProgressInfo | KIO::JobFlag::Overwrite); connect(job, &KIO::CopyJob::result, [=](KJob *job) { if (job->error()) { job->uiDelegate()->showErrorMessage(); job->kill(); cleanupAfterInstall(); return; } installSkin(static_cast(job)->destUrl()); }); } void AppearanceSettings::installSkin(const QUrl& skinUrl) { QUrl skinArchiveUrl = QUrl(skinUrl); skinArchiveUrl.setScheme(QStringLiteral("tar")); KIO::ListJob* job = KIO::listRecursive(skinArchiveUrl, KIO::HideProgressInfo, false); connect(job, &KIO::ListJob::entries, [=](KIO::Job* /* job */, const KIO::UDSEntryList& list) { if (list.count() == 0) return; QListIterator i(list); while (i.hasNext()) m_installSkinFileList.append(i.next().stringValue(KIO::UDSEntry::UDS_NAME)); }); connect(job, &KIO::ListJob::result, [=](KJob *job) { if (!job->error()) - { - m_installSkinId = m_installSkinFileList.at(0); - - if (validateSkin(m_installSkinId, m_installSkinFileList)) - checkForExistingSkin(); - else - failInstall(xi18nc("@info", "Unable to locate required files in the skin archive.The archive appears to be invalid.")); - } + checkForExistingSkin(); else failInstall(xi18nc("@info", "Unable to list the skin archive contents.") + QStringLiteral("\n\n") + job->errorString()); }); } -bool AppearanceSettings::validateSkin(const QString &skinId, const QStringList& fileList) +bool AppearanceSettings::validateSkin(const QString& skinId, bool kns) { - bool titleFileFound = false; - bool tabsFileFound = false; - QString titleFileName = skinId + QStringLiteral("/title.skin"); - QString tabsFileName = skinId + QStringLiteral("/tabs.skin"); + QString dir = kns ? QStringLiteral("kns_skins/") : QStringLiteral("skins/"); - foreach (const QString& fileName, fileList) - { - if (fileName.endsWith(titleFileName)) - { - titleFileFound = true; - } - else if (fileName.endsWith(tabsFileName)) - { - tabsFileFound = true; - } - } + QString titlePath = QStandardPaths::locate(QStandardPaths::DataLocation, dir + skinId + QStringLiteral("/title.skin")); + QString tabsPath = QStandardPaths::locate(QStandardPaths::DataLocation, dir + skinId + QStringLiteral("/tabs.skin")); - return titleFileFound && tabsFileFound; + return !titlePath.isEmpty() && !tabsPath.isEmpty(); } void AppearanceSettings::checkForExistingSkin() { + m_installSkinId = m_installSkinFileList.at(0); + QModelIndexList skins = m_skins->match(m_skins->index(0, 0), SkinId, m_installSkinId, 1, Qt::MatchExactly | Qt::MatchWrap); int exists = skins.count(); foreach (const QModelIndex& skin, skins) { if (m_skins->item(skin.row())->data(SkinInstalledWithKns).toBool()) --exists; } if (exists > 0) { QString skinDir = skins.at(0).data(SkinDir).toString(); QFile skin(skinDir + QStringLiteral("titles.skin")); if (!skin.open(QIODevice::ReadWrite)) { failInstall(xi18nc("@info", "This skin appears to be already installed and you lack the required permissions to overwrite it.")); } else { skin.close(); int remove = KMessageBox::warningContinueCancel(parentWidget(), xi18nc("@info", "This skin appears to be already installed. Do you want to overwrite it?"), xi18nc("@title:window", "Skin Already Exists"), KGuiItem(xi18nc("@action:button", "Reinstall Skin"))); if (remove == KMessageBox::Continue) - { - KIO::DeleteJob* job = KIO::del(QUrl::fromLocalFile(skinDir), KIO::HideProgressInfo); - connect(job, SIGNAL(result(KJob*)), this, SLOT(installSkinArchive(KJob*))); - } + removeSkin(skinDir, [=](){ installSkinArchive(); }); else cleanupAfterInstall(); } } else installSkinArchive(); } -void AppearanceSettings::installSkinArchive(KJob* deleteJob) +void AppearanceSettings::removeSkin(const QString& skinDir, std::function successCallback) { - if (deleteJob && deleteJob->error()) - { - KMessageBox::error(parentWidget(), deleteJob->errorString(), xi18nc("@title:Window", "Could Not Delete Skin")); - return; - } + KIO::DeleteJob* job = KIO::del(QUrl::fromLocalFile(skinDir), KIO::HideProgressInfo); + connect(job, &KIO::DeleteJob::result, [=](KJob* deleteJob) { + if (deleteJob->error()) { + KMessageBox::error(parentWidget(), deleteJob->errorString(), xi18nc("@title:Window", "Could Not Delete Skin")); + } else if (successCallback) { + successCallback(); + } + }); +} +void AppearanceSettings::installSkinArchive() +{ KTar skinArchive(m_installSkinFile.fileName()); if (skinArchive.open(QIODevice::ReadOnly)) { const KArchiveDirectory* skinDir = skinArchive.directory(); skinDir->copyTo(m_localSkinsDir); skinArchive.close(); - populateSkinList(); + if (validateSkin(m_installSkinId, false)) + { + populateSkinList(); - if (Settings::skin() == m_installSkinId) - emit settingsChanged(); + if (Settings::skin() == m_installSkinId) + emit settingsChanged(); - cleanupAfterInstall(); + cleanupAfterInstall(); + } + else + { + removeSkin(m_localSkinsDir + m_installSkinId); + failInstall(xi18nc("@info", "Unable to locate required files in the skin archive.The archive appears to be invalid.")); + } } else failInstall(xi18nc("@info", "The skin archive file could not be opened.")); } void AppearanceSettings::failInstall(const QString& error) { KMessageBox::error(parentWidget(), error, xi18nc("@title:window", "Cannot Install Skin")); cleanupAfterInstall(); } void AppearanceSettings::cleanupAfterInstall() { m_installSkinId.clear(); m_installSkinFileList.clear(); if (m_installSkinFile.exists()) { m_installSkinFile.close(); m_installSkinFile.remove(); } } void AppearanceSettings::updateRemoveSkinButton() { if (m_skins->rowCount() <= 1) { removeButton->setEnabled(false); return; } QString skinDir; QVariant value = skinList->currentIndex().data(SkinDir); if (value.isValid()) skinDir = value.toString(); value = skinList->currentIndex().data(SkinInstalledWithKns); bool isKnsSkin = value.toBool(); // We don't allow the user to remove the default skin // or any skin which was installed through KNS3. if (skinDir.isEmpty() || isKnsSkin) { removeButton->setEnabled(false); return; } QFile titleSkin(skinDir + QStringLiteral("/title.skin")); if (!titleSkin.open(QIODevice::ReadWrite)) removeButton->setEnabled(false); else removeButton->setEnabled(true); titleSkin.close(); } void AppearanceSettings::removeSelectedSkin() { if (m_skins->rowCount() <= 1) return; - QString skinId = skinList->currentIndex().data(SkinId).toString(); QString skinDir = skinList->currentIndex().data(SkinDir).toString(); QString skinName = skinList->currentIndex().data(SkinName).toString(); QString skinAuthor = skinList->currentIndex().data(SkinAuthor).toString(); if (skinDir.isEmpty()) return; int remove = KMessageBox::warningContinueCancel(parentWidget(), xi18nc("@info", "Do you want to remove \"%1\" by %2?", skinName, skinAuthor), xi18nc("@title:window", "Remove Skin"), KStandardGuiItem::del()); if (remove == KMessageBox::Continue) - { - KIO::DeleteJob* job = KIO::del(QUrl::fromLocalFile(skinDir), KIO::HideProgressInfo); - connect(job, &KIO::DeleteJob::result, [=](KJob* deleteJob) { - if (deleteJob->error()) + removeSkin(skinDir, [=](){ + QString skinId = skinList->currentIndex().data(SkinId).toString(); + if (skinId == Settings::skin()) { - KMessageBox::error(parentWidget(), deleteJob->errorString(), xi18nc("@title:Window", "Could Not Delete Skin")); - } else { - if (skinId == Settings::skin()) - { - Settings::setSkin(QStringLiteral("default")); - Settings::setSkinInstalledWithKns(false); - Settings::self()->save(); - emit settingsChanged(); - } - - resetSelection(); - populateSkinList(); + Settings::setSkin(QStringLiteral("default")); + Settings::setSkinInstalledWithKns(false); + Settings::self()->save(); + emit settingsChanged(); } + + resetSelection(); + populateSkinList(); }); - } } QSet AppearanceSettings::extractKnsSkinIds(const QStringList& fileList) { QSet skinIdList; foreach (const QString& file, fileList) { // We only care about files/directories which are subdirectories of our KNS skins dir. if (file.startsWith(m_knsSkinDir, Qt::CaseInsensitive)) { // Get the relative filename (this removes the KNS install dir from the filename). QString relativeName = QString(file).remove(m_knsSkinDir, Qt::CaseInsensitive); // Get everything before the first slash - that should be our skins ID. - QString skinId = relativeName.section(QStringLiteral("/"), 0, QString::SectionSkipEmpty); + QString skinId = relativeName.section(QStringLiteral("/"), 0, 0, QString::SectionSkipEmpty); // Skip all other entries in the file list if we found what we were searching for. if (!skinId.isEmpty()) { // First remove all remaining slashes (as there could be leading or trailing ones). skinId = skinId.replace(QStringLiteral("/"), QString()); skinIdList.insert(skinId); } } } return skinIdList; } void AppearanceSettings::getNewSkins() { QPointer dialog = new KNS3::DownloadDialog(m_knsConfigFileName, this); // Show the KNS dialog. NOTE: We are NOT supposed to check the dialog's result, // because the actions are asynchronous (items are installed or uninstalled, // regardless whether the dialog's result is Accepted or Rejected)! dialog->exec(); if (dialog.isNull()) { return; } if (!dialog->installedEntries().empty()) { quint32 invalidEntryCount = 0; QString invalidSkinText; foreach (const KNS3::Entry &entry3, dialog->installedEntries()) { KNSCore::EntryInternal entry = KNSCore::EntryInternal::fromEntry(entry3); bool isValid = true; const QSet& skinIdList = extractKnsSkinIds(entry.installedFiles()); // Validate all skin IDs as each archive can contain multiple skins. foreach (const QString& skinId, skinIdList) { // Validate the current skin. - if (!validateSkin(skinId, entry.installedFiles())) + if (!validateSkin(skinId, true)) { isValid = false; } } // We'll add an error message for the whole KNS entry if // the current skin is marked as invalid. // We should not do this per skin as the user does not know that // there are more skins inside one archive. if (!isValid) { invalidEntryCount++; // The user needs to know the name of the skin which // was removed. invalidSkinText += QString(QStringLiteral("
  • %1
  • ")).arg(entry.name()); // Then remove the skin. m_knsDownloadManager->uninstallEntry(entry); } } // Are there any invalid entries? if (invalidEntryCount > 0) { failInstall(xi18ncp("@info", "The following skin is missing required files. Thus it was removed:
      %2
    ", "The following skins are missing required files. Thus they were removed:
      %2
    ", invalidEntryCount, invalidSkinText)); } } if (!dialog->changedEntries().isEmpty()) { // Reset the selection in case the currently selected // skin was removed. resetSelection(); // Re-populate the list of skins if the user changed something. populateSkinList(); } delete dialog; } diff --git a/app/config/appearancesettings.h b/app/config/appearancesettings.h index 95c116e..2fd4937 100644 --- a/app/config/appearancesettings.h +++ b/app/config/appearancesettings.h @@ -1,140 +1,141 @@ /* Copyright (C) 2008 by Eike Hein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor appro- ved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 this program. If not, see http://www.gnu.org/licenses/. */ #ifndef APPEARANCESETTINGS_H #define APPEARANCESETTINGS_H #include "ui_appearancesettings.h" #include #include class SkinListDelegate; class QStandardItem; class QStandardItemModel; namespace KNSCore { class DownloadManager; } class AppearanceSettings : public QWidget, private Ui::AppearanceSettings { Q_OBJECT public: explicit AppearanceSettings(QWidget* parent = 0); ~AppearanceSettings(); enum DataRole { SkinId = Qt::UserRole + 1, SkinDir = Qt::UserRole + 2, SkinName = Qt::UserRole + 3, SkinAuthor = Qt::UserRole + 4, SkinIcon = Qt::UserRole + 5, SkinInstalledWithKns = Qt::UserRole + 6 }; public Q_SLOTS: void resetSelection(); Q_SIGNALS: void settingsChanged(); protected: void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; private Q_SLOTS: void populateSkinList(); void updateSkinSetting(); void installSkin(); - void installSkinArchive(KJob* deleteJob = 0); + void installSkinArchive(); /** * Validates the given skin. * This method checks if the fileList of the skin * contains all required files. * * @param skinId The SkinID of the skin which will be validated. - * @param fileList The filelist of the skin. + * @param kns The skin has been installed from kns. * * @return True if the skin is valid, otherwise false. */ - bool validateSkin(const QString &skinId, const QStringList& fileList); + bool validateSkin(const QString &skinId, bool kns); /** * Extracts the skin IDs from the given fileList. * There can be multiple skins, but only one skin per directory. * The files need to be located in the m_knsSkinDir. * * @param fileList All files which were installed. * * @return The list of skin IDs which were extracted from the * given fileList. */ QSet extractKnsSkinIds(const QStringList& fileList); /** * Shows the KNS3 dialog where the user can download new skins. */ void getNewSkins(); void updateRemoveSkinButton(); void removeSelectedSkin(); private: QStandardItem* createSkinItem(const QString& skinDir); void populateSkinList(const QString& installationDirectory); void checkForExistingSkin(); + void removeSkin(const QString& skinDir, std::function successCallback = 0); void installSkin(const QUrl& skinUrl); void failInstall(const QString& error); void cleanupAfterInstall(); QString m_selectedSkinId; QStandardItemModel* m_skins; SkinListDelegate* m_skinListDelegate; QString m_localSkinsDir; QString m_knsSkinDir; QString m_installSkinId; QTemporaryFile m_installSkinFile; QStringList m_installSkinFileList; QString m_knsConfigFileName; KNSCore::DownloadManager* m_knsDownloadManager; }; #endif