diff --git a/containments/desktop/plugins/folder/foldermodel.cpp b/containments/desktop/plugins/folder/foldermodel.cpp index c3ec2c46c..426e9f414 100644 --- a/containments/desktop/plugins/folder/foldermodel.cpp +++ b/containments/desktop/plugins/folder/foldermodel.cpp @@ -1,2107 +1,2107 @@ /*************************************************************************** * Copyright (C) 2006 David Faure * * Copyright (C) 2008 Fredrik Höglund * * Copyright (C) 2008 Rafael Fernández López * * Copyright (C) 2011 Marco Martin * * Copyright (C) 2014 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) any later version. * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "foldermodel.h" #include "itemviewadapter.h" #include "positioner.h" #include "screenmapper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel") DirLister::DirLister(QObject *parent) : KDirLister(parent) { } DirLister:: ~DirLister() { } void DirLister::handleError(KIO::Job *job) { if (!autoErrorHandlingEnabled()) { emit error(job->errorString()); return; } KDirLister::handleError(job); } FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent), m_dirWatch(nullptr), m_dragInProgress(false), m_urlChangedWhileDragging(false), m_dropTargetPositionsCleanup(new QTimer(this)), m_previewGenerator(nullptr), m_viewAdapter(nullptr), m_actionCollection(this), m_newMenu(nullptr), m_fileItemActions(nullptr), m_usedByContainment(false), m_locked(true), m_sortMode(0), m_sortDesc(false), m_sortDirsFirst(true), m_parseDesktopFiles(false), m_previews(false), m_filterMode(NoFilter), m_filterPatternMatchAll(true), m_screenMapper(ScreenMapper::instance()), m_complete(false), m_screenUsed(false) { //needed to pass the job around with qml qmlRegisterType(); DirLister *dirLister = new DirLister(this); dirLister->setDelayedMimeTypes(true); dirLister->setAutoErrorHandlingEnabled(false, nullptr); connect(dirLister, &DirLister::error, this, &FolderModel::dirListFailed); connect(dirLister, &KCoreDirLister::itemsDeleted, this, &FolderModel::evictFromIsDirCache); connect(dirLister, &KCoreDirLister::started, this, std::bind(&FolderModel::setStatus, this, Status::Listing)); void (KCoreDirLister::*myCompletedSignal)() = &KCoreDirLister::completed; QObject::connect(dirLister, myCompletedSignal, this, [this] { setStatus(Status::Ready); emit listingCompleted(); }); void (KCoreDirLister::*myCanceledSignal)() = &KCoreDirLister::canceled; QObject::connect(dirLister, myCanceledSignal, this, [this] { setStatus(Status::Canceled); emit listingCanceled(); }); m_dirModel = new KDirModel(this); m_dirModel->setDirLister(dirLister); m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable); // If we have dropped items queued for moving, go unsorted now. connect(this, &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() { if (!m_dropTargetPositions.isEmpty()) { setSortMode(-1); } }); // Position dropped items at the desired target position. connect(this, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { for (int i = first; i <= last; ++i) { const auto idx = index(i, 0, parent); const auto url = itemForIndex(idx).url(); auto it = m_dropTargetPositions.find(url.fileName()); if (it != m_dropTargetPositions.end()) { const auto pos = it.value(); m_dropTargetPositions.erase(it); emit move(pos.x(), pos.y(), {url}); } } }); /* * Dropped files may not actually show up as new files, e.g. when we overwrite * an existing file. Or files that fail to be listed by the dirLister, or... * To ensure we don't grow the map indefinitely, clean it up periodically. * The cleanup timer is (re)started whenever we modify the map. We use a quite * high interval of 10s. This should ensure, that we don't accidentally wipe * the mapping when we actually still want to use it. Since the time between * adding an entry in the map and it showing up in the model should be * small, this should rarely, if ever happen. */ m_dropTargetPositionsCleanup->setInterval(10000); m_dropTargetPositionsCleanup->setSingleShot(true); connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() { if (!m_dropTargetPositions.isEmpty()) { qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions; m_dropTargetPositions.clear(); } }); m_selectionModel = new QItemSelectionModel(this, this); connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &FolderModel::selectionChanged); setSourceModel(m_dirModel); setSortLocaleAware(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setDynamicSortFilter(true); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); createActions(); } FolderModel::~FolderModel() { if (m_usedByContainment) { // disconnect so we don't handle signals from the screen mapper when // removeScreen is called m_screenMapper->disconnect(this); m_screenMapper->removeScreen(m_screen, resolvedUrl()); } } QHash< int, QByteArray > FolderModel::roleNames() const { return staticRoleNames(); } QHash< int, QByteArray > FolderModel::staticRoleNames() { QHash roleNames; roleNames[Qt::DisplayRole] = "display"; roleNames[Qt::DecorationRole] = "decoration"; roleNames[BlankRole] = "blank"; roleNames[OverlaysRole] = "overlays"; roleNames[SelectedRole] = "selected"; roleNames[IsDirRole] = "isDir"; roleNames[IsLinkRole] = "isLink"; roleNames[IsHiddenRole] = "isHidden"; roleNames[UrlRole] = "url"; roleNames[LinkDestinationUrl] = "linkDestinationUrl"; roleNames[SizeRole] = "size"; roleNames[TypeRole] = "type"; return roleNames; } void FolderModel::classBegin() { } void FolderModel::componentComplete() { m_complete = true; invalidate(); } void FolderModel::invalidateIfComplete() { if (!m_complete) { return; } invalidate(); } void FolderModel::invalidateFilterIfComplete() { if (!m_complete) { return; } invalidateFilter(); } void FolderModel::newFileMenuItemCreated(const QUrl &url) { if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); m_dropTargetPositions.insert(url.fileName(), m_menuPosition); m_menuPosition = {}; m_dropTargetPositionsCleanup->start(); } } QString FolderModel::url() const { return m_url; } void FolderModel::setUrl(const QString& url) { const QUrl &resolvedNewUrl = resolve(url); if (url == m_url) { m_dirModel->dirLister()->updateDirectory(resolvedNewUrl); return; } const auto oldUrl = resolvedUrl(); beginResetModel(); m_url = url; m_isDirCache.clear(); m_dirModel->dirLister()->openUrl(resolvedNewUrl); clearDragImages(); m_dragIndexes.clear(); endResetModel(); emit urlChanged(); emit resolvedUrlChanged(); m_errorString.clear(); emit errorStringChanged(); if (m_dirWatch) { delete m_dirWatch; m_dirWatch = nullptr; } if (resolvedNewUrl.isValid()) { m_dirWatch = new KDirWatch(this); connect(m_dirWatch, &KDirWatch::created, this, &FolderModel::iconNameChanged); connect(m_dirWatch, &KDirWatch::dirty, this, &FolderModel::iconNameChanged); m_dirWatch->addFile(resolvedNewUrl.toLocalFile() + QLatin1String("/.directory")); } if (m_dragInProgress) { m_urlChangedWhileDragging = true; } emit iconNameChanged(); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->removeScreen(m_screen, oldUrl); m_screenMapper->addScreen(m_screen, resolvedUrl()); } } QUrl FolderModel::resolvedUrl() const { return m_dirModel->dirLister()->url(); } QUrl FolderModel::resolve(const QString& url) { QUrl resolvedUrl; if (url.startsWith(QLatin1Char('~'))) { resolvedUrl = QUrl::fromLocalFile(KShell::tildeExpand(url)); } else { resolvedUrl = QUrl::fromUserInput(url); } return resolvedUrl; } QString FolderModel::iconName() const { const KFileItem rootItem(m_dirModel->dirLister()->url()); if (!rootItem.isFinalIconKnown()) { rootItem.determineMimeType(); } return rootItem.iconName(); } FolderModel::Status FolderModel::status() const { return m_status; } void FolderModel::setStatus(Status status) { if (m_status != status) { m_status = status; emit statusChanged(); } } QString FolderModel::errorString() const { return m_errorString; } bool FolderModel::dragging() const { return m_dragInProgress; } bool FolderModel::usedByContainment() const { return m_usedByContainment; } void FolderModel::setUsedByContainment(bool used) { if (m_usedByContainment != used) { m_usedByContainment = used; QAction *action = m_actionCollection.action(QStringLiteral("refresh")); if (action) { action->setText(m_usedByContainment ? i18n("&Refresh Desktop") : i18n("&Refresh View")); action->setIcon(m_usedByContainment ? QIcon::fromTheme(QStringLiteral("user-desktop")) : QIcon::fromTheme(QStringLiteral("view-refresh"))); } m_screenMapper->disconnect(this); connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete); connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete); emit usedByContainmentChanged(); } } bool FolderModel::locked() const { return m_locked; } void FolderModel::setLocked(bool locked) { if (m_locked != locked) { m_locked = locked; emit lockedChanged(); } } void FolderModel::dirListFailed(const QString& error) { m_errorString = error; emit errorStringChanged(); } int FolderModel::sortMode() const { return m_sortMode; } void FolderModel::setSortMode(int mode) { if (m_sortMode != mode) { m_sortMode = mode; if (mode == -1 /* Unsorted */) { setDynamicSortFilter(false); } else { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); setDynamicSortFilter(true); } emit sortModeChanged(); } } bool FolderModel::sortDesc() const { return m_sortDesc; } void FolderModel::setSortDesc(bool desc) { if (m_sortDesc != desc) { m_sortDesc = desc; if (m_sortMode != -1 /* Unsorted */) { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } emit sortDescChanged(); } } bool FolderModel::sortDirsFirst() const { return m_sortDirsFirst; } void FolderModel::setSortDirsFirst(bool enable) { if (m_sortDirsFirst != enable) { m_sortDirsFirst = enable; if (m_sortMode != -1 /* Unsorted */) { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } emit sortDirsFirstChanged(); } } bool FolderModel::parseDesktopFiles() const { return m_parseDesktopFiles; } void FolderModel::setParseDesktopFiles(bool enable) { if (m_parseDesktopFiles != enable) { m_parseDesktopFiles = enable; emit parseDesktopFilesChanged(); } } QObject* FolderModel::viewAdapter() const { return m_viewAdapter; } void FolderModel::setViewAdapter(QObject* adapter) { if (m_viewAdapter != adapter) { KAbstractViewAdapter *abstractViewAdapter = dynamic_cast(adapter); m_viewAdapter = abstractViewAdapter; if (m_viewAdapter && !m_previewGenerator) { m_previewGenerator = new KFilePreviewGenerator(abstractViewAdapter, this); m_previewGenerator->setPreviewShown(m_previews); m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); } emit viewAdapterChanged(); } } bool FolderModel::previews() const { return m_previews; } void FolderModel::setPreviews(bool previews) { if (m_previews != previews) { m_previews = previews; if (m_previewGenerator) { m_previewGenerator->setPreviewShown(m_previews); } emit previewsChanged(); } } QStringList FolderModel::previewPlugins() const { return m_previewPlugins; } void FolderModel::setPreviewPlugins(const QStringList& previewPlugins) { QStringList effectivePlugins = previewPlugins; if (effectivePlugins.isEmpty()) { effectivePlugins = KIO::PreviewJob::defaultPlugins(); } if (m_effectivePreviewPlugins != effectivePlugins) { m_effectivePreviewPlugins = effectivePlugins; if (m_previewGenerator) { m_previewGenerator->setPreviewShown(false); m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); m_previewGenerator->setPreviewShown(true); } } if (m_previewPlugins != previewPlugins) { m_previewPlugins = previewPlugins; emit previewPluginsChanged(); } } int FolderModel::filterMode() const { return m_filterMode; } void FolderModel::setFilterMode(int filterMode) { if (m_filterMode != (FilterMode)filterMode) { m_filterMode = (FilterMode)filterMode; invalidateFilterIfComplete(); emit filterModeChanged(); } } QString FolderModel::filterPattern() const { return m_filterPattern; } void FolderModel::setFilterPattern(const QString &pattern) { if (m_filterPattern == pattern) { return; } m_filterPattern = pattern; m_filterPatternMatchAll = (pattern == QLatin1String("*")); const QStringList patterns = pattern.split(QLatin1Char(' ')); m_regExps.clear(); m_regExps.reserve(patterns.count()); foreach (const QString &pattern, patterns) { QRegExp rx(pattern); rx.setPatternSyntax(QRegExp::Wildcard); rx.setCaseSensitivity(Qt::CaseInsensitive); m_regExps.append(rx); } invalidateFilterIfComplete(); emit filterPatternChanged(); } QStringList FolderModel::filterMimeTypes() const { return m_mimeSet.toList(); } void FolderModel::setFilterMimeTypes(const QStringList &mimeList) { const QSet &set = QSet::fromList(mimeList); if (m_mimeSet != set) { m_mimeSet = set; invalidateFilterIfComplete(); emit filterMimeTypesChanged(); } } void FolderModel::setScreen(int screen) { m_screenUsed = (screen != -1); if (!m_screenUsed || m_screen == screen) return; m_screen = screen; if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->addScreen(screen, resolvedUrl()); } emit screenChanged(); } bool FolderModel::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) // Catching Shift modifier usage on open context menus to swap the // Trash/Delete actions. if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Shift) { m_actionCollection.action(QStringLiteral("trash"))->setVisible(false); m_actionCollection.action(QStringLiteral("del"))->setVisible(true); } } else if (event->type() == QEvent::KeyRelease) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Shift) { m_actionCollection.action(QStringLiteral("trash"))->setVisible(true); m_actionCollection.action(QStringLiteral("del"))->setVisible(false); } } return false; } KFileItem FolderModel::rootItem() const { return m_dirModel->dirLister()->rootItem(); } void FolderModel::up() { const QUrl &up = KIO::upUrl(resolvedUrl()); if (up.isValid()) { setUrl(up.toString()); } } void FolderModel::cd(int row) { if (row < 0) { return; } const QModelIndex idx = index(row, 0); bool isDir = data(idx, IsDirRole).toBool(); if (isDir) { const KFileItem item = itemForIndex(idx); if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { setUrl(file.readUrl()); } } else { setUrl(item.targetUrl().toString()); } } } void FolderModel::run(int row) { if (row < 0) { return; } KFileItem item = itemForIndex(index(row, 0)); QUrl url(item.targetUrl()); // FIXME TODO: This can go once we depend on a KIO w/ fe1f50caaf2. if (url.scheme().isEmpty()) { url.setScheme(QStringLiteral("file")); } KRun *run = new KRun(url, nullptr); // On desktop:/ we want to be able to run .desktop files right away, // otherwise ask for security reasons. We also don't use the targetUrl() // from above since we don't want the resolved /home/foo/Desktop URL. run->setShowScriptExecutionPrompt(item.url().scheme() != QLatin1String("desktop") || item.url().adjusted(QUrl::RemoveFilename).path() != QLatin1String("/")); } void FolderModel::runSelected() { if (!m_selectionModel->hasSelection()) { return; } if (m_selectionModel->selectedIndexes().count() == 1) { run(m_selectionModel->selectedIndexes().constFirst().row()); return; } KFileItemActions fileItemActions(this); KFileItemList items; foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) { // Skip over directories. if (!index.data(IsDirRole).toBool()) { items << itemForIndex(index); } } fileItemActions.runPreferredApplications(items, QString()); } void FolderModel::rename(int row, const QString& name) { if (row < 0) { return; } QModelIndex idx = index(row, 0); m_dirModel->setData(mapToSource(idx), name, Qt::EditRole); } int FolderModel::fileExtensionBoundary(int row) { const QModelIndex idx = index(row, 0); const QString &name = data(idx, Qt::DisplayRole).toString(); int boundary = name.length(); if (data(idx, IsDirRole).toBool()) { return boundary; } QMimeDatabase db; const QString &ext = db.suffixForFileName(name); if (ext.isEmpty()) { boundary = name.lastIndexOf(QLatin1Char('.')); if (boundary < 1) { boundary = name.length(); } } else { boundary -= ext.length() + 1; } return boundary; } bool FolderModel::hasSelection() const { return m_selectionModel->hasSelection(); } bool FolderModel::isSelected(int row) { if (row < 0) { return false; } return m_selectionModel->isSelected(index(row, 0)); } void FolderModel::setSelected(int row) { if (row < 0) { return; } m_selectionModel->select(index(row, 0), QItemSelectionModel::Select); } void FolderModel::toggleSelected(int row) { if (row < 0) { return; } m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle); } void FolderModel::setRangeSelected(int anchor, int to) { if (anchor < 0 || to < 0) { return; } QItemSelection selection(index(anchor, 0), index(to, 0)); m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); } void FolderModel::updateSelection(const QVariantList &rows, bool toggle) { QItemSelection newSelection; int iRow = -1; foreach (const QVariant &row, rows) { iRow = row.toInt(); if (iRow < 0) { return; } const QModelIndex &idx = index(iRow, 0); newSelection.select(idx, idx); } if (toggle) { QItemSelection pinnedSelection = m_pinnedSelection; pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle); m_selectionModel->select(pinnedSelection, QItemSelectionModel::ClearAndSelect); } else { m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect); } } void FolderModel::clearSelection() { if (m_selectionModel->hasSelection()) { m_selectionModel->clear(); } } void FolderModel::pinSelection() { m_pinnedSelection = m_selectionModel->selection(); } void FolderModel::unpinSelection() { m_pinnedSelection = QItemSelection(); } void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image) { if (row < 0) { return; } delete m_dragImages.take(row); DragImage *dragImage = new DragImage(); dragImage->row = row; dragImage->rect = QRect(x, y, width, height); dragImage->image = image.value(); dragImage->blank = false; m_dragImages.insert(row, dragImage); } void FolderModel::clearDragImages() { qDeleteAll(m_dragImages); m_dragImages.clear(); } void FolderModel::setDragHotSpotScrollOffset(int x, int y) { m_dragHotSpotScrollOffset.setX(x); m_dragHotSpotScrollOffset.setY(y); } QPoint FolderModel::dragCursorOffset(int row) { DragImage *image = m_dragImages.value(row); if (!image) { return QPoint(0, 0); } return image->cursorOffset; } void FolderModel::addDragImage(QDrag *drag, int x, int y) { if (!drag || m_dragImages.isEmpty()) { return; } QRegion region; foreach (DragImage *image, m_dragImages) { image->blank = isBlank(image->row); image->rect.translate(-m_dragHotSpotScrollOffset.x(), -m_dragHotSpotScrollOffset.y()); if (!image->blank && !image->image.isNull()) { region = region.united(image->rect); } } QRect rect = region.boundingRect(); QPoint offset = rect.topLeft(); rect.translate(-offset.x(), -offset.y()); QImage dragImage(rect.size(), QImage::Format_RGBA8888); dragImage.fill(Qt::transparent); QPainter painter(&dragImage); QPoint pos; foreach (DragImage *image, m_dragImages) { if (!image->blank && !image->image.isNull()) { pos = image->rect.translated(-offset.x(), -offset.y()).topLeft(); image->cursorOffset.setX(pos.x() - (x - offset.x())); image->cursorOffset.setY(pos.y() - (y - offset.y())); painter.drawImage(pos, image->image); } // FIXME HACK: Operate on copy. image->rect.translate(m_dragHotSpotScrollOffset.x(), m_dragHotSpotScrollOffset.y()); } drag->setPixmap(QPixmap::fromImage(dragImage)); drag->setHotSpot(QPoint(x - offset.x(), y - offset.y())); } void FolderModel::dragSelected(int x, int y) { if (m_dragInProgress) { return; } m_dragInProgress = true; emit draggingChanged(); m_urlChangedWhileDragging = false; // Avoid starting a drag synchronously in a mouse handler or interferes with // child event filtering in parent items (and thus e.g. press-and-hold hand- // ling in a containment). QMetaObject::invokeMethod(this, "dragSelectedInternal", Qt::QueuedConnection, Q_ARG(int, x), Q_ARG(int, y)); } void FolderModel::dragSelectedInternal(int x, int y) { if (!m_viewAdapter || !m_selectionModel->hasSelection()) { m_dragInProgress = false; emit draggingChanged(); return; } ItemViewAdapter *adapter = qobject_cast(m_viewAdapter); QQuickItem *item = qobject_cast(adapter->adapterView()); QDrag *drag = new QDrag(item); addDragImage(drag, x, y); m_dragIndexes = m_selectionModel->selectedIndexes(); std::sort(m_dragIndexes.begin(), m_dragIndexes.end()); // TODO: Optimize to emit contiguous groups. emit dataChanged(m_dragIndexes.first(), m_dragIndexes.last(), QVector() << BlankRole); QModelIndexList sourceDragIndexes; sourceDragIndexes.reserve(m_dragIndexes.count()); foreach (const QModelIndex &index, m_dragIndexes) { sourceDragIndexes.append(mapToSource(index)); } drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes)); // Due to spring-loading (aka auto-expand), the URL might change // while the drag is in-flight - in that case we don't want to // unnecessarily emit dataChanged() for (possibly invalid) indices // after it ends. const QUrl currentUrl(m_dirModel->dirLister()->url()); item->grabMouse(); drag->exec(supportedDragActions()); item->ungrabMouse(); m_dragInProgress = false; emit draggingChanged(); m_urlChangedWhileDragging = false; if (m_dirModel->dirLister()->url() == currentUrl) { const QModelIndex first(m_dragIndexes.first()); const QModelIndex last(m_dragIndexes.last()); m_dragIndexes.clear(); // TODO: Optimize to emit contiguous groups. emit dataChanged(first, last, QVector() << BlankRole); } } static bool isDropBetweenSharedViews(const QList &urls, const QUrl &folderUrl) { for (const auto &url : urls) { if (folderUrl.adjusted(QUrl::StripTrailingSlash) != url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)) { return false; } } return true; } void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row) { QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value()); if (!mimeData) { return; } QModelIndex idx; KFileItem item; if (row > -1 && row < rowCount()) { idx = index(row, 0); item = itemForIndex(idx); } QUrl dropTargetUrl; // So we get to run mostLocalUrl() over the current URL. if (item.isNull()) { item = rootItem(); } if (item.isNull()) { dropTargetUrl = m_dirModel->dirLister()->url(); } else if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { dropTargetUrl = QUrl(file.readUrl()); } else { dropTargetUrl = item.mostLocalUrl(); } } else { dropTargetUrl = item.mostLocalUrl(); } auto dropTargetFolderUrl = dropTargetUrl; if (dropTargetFolderUrl.fileName() == QLatin1Char('.')) { // the target URL for desktop:/ is e.g. 'file://home/user/Desktop/.' dropTargetFolderUrl = dropTargetFolderUrl.adjusted(QUrl::RemoveFilename); } // use dropTargetUrl to resolve desktop:/ to the actual file location which is also used by the mime data /* QMimeData operates on local URLs, but the dir lister and thus screen mapper and positioner may * use a fancy scheme like desktop:/ instead. Ensure we always use the latter to properly map URLs, * i.e. go from file:///home/user/Desktop/file to desktop:/file */ auto mappableUrl = [this, dropTargetFolderUrl](const QUrl &url) -> QUrl { if (dropTargetFolderUrl != m_dirModel->dirLister()->url()) { QString mappedUrl = url.toString(); const auto local = dropTargetFolderUrl.toString(); const auto internal = m_dirModel->dirLister()->url().toString(); if (mappedUrl.startsWith(local)) { mappedUrl.replace(0, local.size(), internal); } return ScreenMapper::stringToUrl(mappedUrl); } return url; }; const int x = dropEvent->property("x").toInt(); const int y = dropEvent->property("y").toInt(); const QPoint dropPos = {x, y}; if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) { if (m_locked || mimeData->urls().isEmpty()) { return; } setSortMode(-1); for (const auto &url : mimeData->urls()) { m_dropTargetPositions.insert(url.fileName(), dropPos); m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); } emit move(x, y, mimeData->urls()); return; } if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments({dropTargetUrl.toDisplayString(QUrl::PreferLocalFile)}); QDBusConnection::sessionBus().call(message, QDBus::NoBlock); return; } if (idx.isValid() && !(flags(idx) & Qt::ItemIsDropEnabled)) { return; } // Catch drops from a Task Manager and convert to usable URL. if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) { QList urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))}; mimeData->setUrls(urls); } if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { if (isDropBetweenSharedViews(mimeData->urls(), dropTargetFolderUrl)) { setSortMode(-1); for (const auto &url : mimeData->urls()) { m_dropTargetPositions.insert(url.fileName(), dropPos); m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); } m_dropTargetPositionsCleanup->start(); return; } } Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); auto pos = target->mapToScene(dropPos).toPoint(); pos = target->window()->mapToGlobal(pos); QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers); ev.setDropAction(proposedAction); KIO::DropJob *dropJob = KIO::drop(&ev, dropTargetUrl); dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true); // The QMimeData we extract from the DropArea's drop event is deleted as soon as this method // ends but we need to keep a copy for when popupMenuAboutToShow fires. QMimeData *mimeCopy = new QMimeData(); for (const QString &format : mimeData->formats()) { mimeCopy->setData(format, mimeData->data(format)); } connect(dropJob, &KIO::DropJob::popupMenuAboutToShow, this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) { emit popupMenuAboutToShow(dropJob, mimeCopy, x, y); mimeCopy->deleteLater(); }); /* * Position files that come from a drag'n'drop event at the drop event * target position. To do so, we first listen to copy job to figure out * the target URL. Then we store the position of this drop event in the * hash and eventually trigger a move request when we get notified about * the new file event from the source model. */ connect(dropJob, &KIO::DropJob::copyJobStarted, this, [this, dropPos, dropTargetUrl](KIO::CopyJob* copyJob) { auto map = [this, dropPos, dropTargetUrl](const QUrl &targetUrl) { m_dropTargetPositions.insert(targetUrl.fileName(), dropPos); m_dropTargetPositionsCleanup->start(); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { // assign a screen for the item before the copy is actually done, so // filterAcceptsRow doesn't assign the default screen to it QUrl url = resolvedUrl(); // if the folderview's folder is a standard path, just use the targetUrl for mapping if (targetUrl.toString().startsWith(url.toString())) { m_screenMapper->addMapping(targetUrl, m_screen, ScreenMapper::DelayedSignal); } else if (targetUrl.toString().startsWith(dropTargetUrl.toString())) { // if the folderview's folder is a special path, like desktop:// , we need to convert // the targetUrl file:// path to a desktop:/ path for mapping auto destPath = dropTargetUrl.path(); auto filePath = targetUrl.path(); if (filePath.startsWith(destPath)) { url.setPath(filePath.remove(0, destPath.length())); m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); } } } }; // remember drop target position for target URL and forget about the source URL connect(copyJob, &KIO::CopyJob::copyingDone, this, [ map](KIO::Job *, const QUrl &, const QUrl &targetUrl, const QDateTime &, bool, bool) { map(targetUrl); }); connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, [ map](KIO::Job *, const QUrl &, const QString &, const QUrl &targetUrl) { map(targetUrl); }); }); } void FolderModel::dropCwd(QObject* dropEvent) { QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value()); if (!mimeData) { return; } if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments(QVariantList() << m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile).toString()); QDBusConnection::sessionBus().call(message, QDBus::NoBlock); } else { Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); QDropEvent ev(QPoint(), possibleActions, mimeData, buttons, modifiers); ev.setDropAction(proposedAction); KIO::DropJob *dropJob = KIO::drop(&ev, m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile)); dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList indices = selected.indexes(); indices.append(deselected.indexes()); QVector roles; roles.append(SelectedRole); foreach(const QModelIndex &index, indices) { emit dataChanged(index, index, roles); } if (!m_selectionModel->hasSelection()) { clearDragImages(); } else { foreach (const QModelIndex &idx, deselected.indexes()) { delete m_dragImages.take(idx.row()); } } updateActions(); } bool FolderModel::isBlank(int row) const { if (row < 0) { return true; } return data(index(row, 0), BlankRole).toBool(); } QVariant FolderModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == BlankRole) { return m_dragIndexes.contains(index); } else if (role == OverlaysRole) { const KFileItem item = itemForIndex(index); return item.overlays(); } else if (role == SelectedRole) { return m_selectionModel->isSelected(index); } else if (role == IsDirRole) { return isDir(mapToSource(index), m_dirModel); } else if (role == IsLinkRole) { const KFileItem item = itemForIndex(index); return item.isLink(); } else if (role == IsHiddenRole) { const KFileItem item = itemForIndex(index); return item.isHidden(); } else if (role == UrlRole) { return itemForIndex(index).url(); } else if (role == LinkDestinationUrl) { const KFileItem item = itemForIndex(index); if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { return file.readUrl(); } } return item.targetUrl(); } else if (role == SizeRole) { bool isDir = data(index, IsDirRole).toBool(); if (!isDir) { return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 1)), Qt::DisplayRole); } } else if (role == TypeRole) { return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 6)), Qt::DisplayRole); } else if (role == FileNameRole) { return itemForIndex(index).url().fileName(); } return QSortFilterProxyModel::data(index, role); } int FolderModel::indexForUrl(const QUrl& url) const { return mapFromSource(m_dirModel->indexForUrl(url)).row(); } KFileItem FolderModel::itemForIndex(const QModelIndex &index) const { return m_dirModel->itemForIndex(mapToSource(index)); } bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const { KFileItem item = dirModel->itemForIndex(index); if (item.isDir()) { return true; } auto it = m_isDirCache.constFind(item.url()); if (it != m_isDirCache.constEnd()) { return *it; } if (m_parseDesktopFiles && item.isDesktopFile()) { // Check if the desktop file is a link to a directory KDesktopFile file(item.targetUrl().path()); if (!file.hasLinkType()) { return false; } const QUrl url(file.readUrl()); // Check if we already have a running StatJob for this URL. if (m_isDirJobs.contains(item.url())) { return false; } // Assume the root folder of a protocol is always a folder. // This avoids spinning up e.g. trash KIO slave just to check whether trash:/ is a folder. if (url.path() == QLatin1String("/")) { m_isDirCache.insert(item.url(), true); return true; } if (KProtocolInfo::protocolClass(url.scheme()) != QLatin1String(":local")) { return false; } KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); job->setProperty("org.kde.plasma.folder_url", item.url()); job->setSide(KIO::StatJob::SourceSide); job->setDetails(0); connect(job, &KJob::result, this, &FolderModel::statResult); m_isDirJobs.insert(item.url(), job); } return false; } void FolderModel::statResult(KJob *job) { KIO::StatJob *statJob = static_cast(job); const QUrl &url = statJob->property("org.kde.plasma.folder_url").toUrl(); const QModelIndex &idx = index(indexForUrl(url), 0); if (idx.isValid() && statJob->error() == KJob::NoError) { m_isDirCache[url] = statJob->statResult().isDir(); emit dataChanged(idx, idx, QVector() << IsDirRole); } m_isDirJobs.remove(url); } void FolderModel::evictFromIsDirCache(const KFileItemList& items) { foreach (const KFileItem &item, items) { m_screenMapper->removeFromMap(item.url()); m_isDirCache.remove(item.url()); } } bool FolderModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const KDirModel *dirModel = static_cast(sourceModel()); if (m_sortDirsFirst || left.column() == KDirModel::Size) { bool leftIsDir = isDir(left, dirModel); bool rightIsDir = isDir(right, dirModel); if (leftIsDir && !rightIsDir) { return (sortOrder() == Qt::AscendingOrder); } if (!leftIsDir && rightIsDir) { return (sortOrder() == Qt::DescendingOrder); } } const KFileItem leftItem = dirModel->data(left, KDirModel::FileItemRole).value(); const KFileItem rightItem = dirModel->data(right, KDirModel::FileItemRole).value(); const int column = left.column(); int result = 0; switch (column) { case KDirModel::Size: { if (isDir(left, dirModel) && isDir(right, dirModel)) { const int leftChildCount = dirModel->data(left, KDirModel::ChildCountRole).toInt(); const int rightChildCount = dirModel->data(right, KDirModel::ChildCountRole).toInt(); if (leftChildCount < rightChildCount) result = -1; else if (leftChildCount > rightChildCount) result = +1; } else { const KIO::filesize_t leftSize = leftItem.size(); const KIO::filesize_t rightSize = rightItem.size(); if (leftSize < rightSize) result = -1; else if (leftSize > rightSize) result = +1; } break; } case KDirModel::ModifiedTime: { const long long leftTime = leftItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); const long long rightTime = rightItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); if (leftTime < rightTime) result = -1; else if (leftTime > rightTime) result = +1; break; } case KDirModel::Type: result = QString::compare(dirModel->data(left, Qt::DisplayRole).toString(), dirModel->data(right, Qt::DisplayRole).toString()); break; default: break; } if (result != 0) return result < 0; QCollator collator; result = collator.compare(leftItem.text(), rightItem.text()); if (result != 0) return result < 0; result = collator.compare(leftItem.name(), rightItem.name()); if (result != 0) return result < 0; return QString::compare(leftItem.url().url(), rightItem.url().url(), Qt::CaseSensitive); } Qt::DropActions FolderModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } Qt::DropActions FolderModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } inline bool FolderModel::matchMimeType(const KFileItem &item) const { if (m_mimeSet.isEmpty()) { return false; } if (m_mimeSet.contains(QLatin1String("all/all")) || m_mimeSet.contains(QLatin1String("all/allfiles"))) { return true; } const QString mimeType = item.determineMimeType().name(); return m_mimeSet.contains(mimeType); } inline bool FolderModel::matchPattern(const KFileItem &item) const { if (m_filterPatternMatchAll) { return true; } const QString name = item.name(); QListIterator i(m_regExps); while (i.hasNext()) { if (i.next().exactMatch(name)) { return true; } } return false; } bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const KDirModel *dirModel = static_cast(sourceModel()); const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent)); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { const QUrl url = item.url(); const int screen = m_screenMapper->screenForItem(url); // don't do anything if the folderview is not associated with a screen if (m_screenUsed && screen == -1) { // The item is not associated with a screen, probably because this is the first // time we see it or the folderview was previously used as a regular applet. // Associated with this folderview if the view is on the first available screen if (m_screen == m_screenMapper->firstAvailableScreen(resolvedUrl())) { m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); } else { return false; } } else if (m_screen != screen) { // the item belongs to a different screen, filter it out return false; } } if (m_filterMode == NoFilter) { return true; } if (m_filterMode == FilterShowMatches) { return (matchPattern(item) && matchMimeType(item)); } else { return !(matchPattern(item) && matchMimeType(item)); } } void FolderModel::createActions() { KIO::FileUndoManager *manager = KIO::FileUndoManager::self(); QAction *cut = KStandardAction::cut(this, &FolderModel::cut, this); QAction *copy = KStandardAction::copy(this, &FolderModel::copy, this); QAction *undo = KStandardAction::undo(manager, &KIO::FileUndoManager::undo, this); undo->setEnabled(manager->undoAvailable()); undo->setShortcutContext(Qt::WidgetShortcut); connect(manager, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool))); connect(manager, &KIO::FileUndoManager::undoTextChanged, this, &FolderModel::undoTextChanged); QAction *paste = KStandardAction::paste(this, &FolderModel::paste, this); QAction *pasteTo = KStandardAction::paste(this, &FolderModel::pasteTo, this); QAction *refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Refresh View"), this); refresh->setShortcut(QKeySequence(QKeySequence::Refresh)); connect(refresh, &QAction::triggered, this, &FolderModel::refresh); QAction *rename = KStandardAction::renameFile(this, &FolderModel::requestRename, this); QAction *trash = KStandardAction::moveToTrash(this, &FolderModel::moveSelectedToTrash, this); QAction *emptyTrash = new QAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("&Empty Trash"), this); connect(emptyTrash, &QAction::triggered, this, &FolderModel::emptyTrashBin); QAction *restoreFromTrash = new QAction(i18nc("Restore from trash", "Restore"), this); connect(restoreFromTrash, &QAction::triggered, this, &FolderModel::restoreSelectedFromTrash); QAction *del = KStandardAction::deleteFile(this, &FolderModel::deleteSelected, this); QAction *actOpen = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("&Open"), this); connect(actOpen, &QAction::triggered, this, &FolderModel::openSelected); m_actionCollection.addAction(QStringLiteral("open"), actOpen); m_actionCollection.addAction(QStringLiteral("cut"), cut); m_actionCollection.addAction(QStringLiteral("undo"), undo); m_actionCollection.addAction(QStringLiteral("copy"), copy); m_actionCollection.addAction(QStringLiteral("paste"), paste); m_actionCollection.addAction(QStringLiteral("pasteto"), pasteTo); m_actionCollection.addAction(QStringLiteral("refresh"), refresh); m_actionCollection.addAction(QStringLiteral("rename"), rename); m_actionCollection.addAction(QStringLiteral("trash"), trash); m_actionCollection.addAction(QStringLiteral("del"), del); m_actionCollection.addAction(QStringLiteral("restoreFromTrash"), restoreFromTrash); m_actionCollection.addAction(QStringLiteral("emptyTrash"), emptyTrash); m_newMenu = new KNewFileMenu(&m_actionCollection, QStringLiteral("newMenu"), this); m_newMenu->setModal(false); connect(m_newMenu, &KNewFileMenu::directoryCreated, this, &FolderModel::newFileMenuItemCreated); connect(m_newMenu, &KNewFileMenu::fileCreated, this, &FolderModel::newFileMenuItemCreated); m_copyToMenu = new KFileCopyToMenu(nullptr); } QAction* FolderModel::action(const QString &name) const { return m_actionCollection.action(name); } QObject* FolderModel::newMenu() const { return m_newMenu->menu(); } void FolderModel::updateActions() { const QModelIndexList indexes = m_selectionModel->selectedIndexes(); KFileItemList items; QList urls; bool hasRemoteFiles = false; bool isTrashLink = false; const bool isTrash = (resolvedUrl().scheme() == QLatin1String("trash")); if (indexes.isEmpty()) { items << rootItem(); } else { items.reserve(indexes.count()); urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { hasRemoteFiles |= item.localPath().isEmpty(); items.append(item); urls.append(item.url()); } } } KFileItemListProperties itemProperties(items); // Check if we're showing the menu for the trash link if (items.count() == 1 && items.at(0).isDesktopFile()) { KDesktopFile file(items.at(0).localPath()); if (file.hasLinkType() && file.readUrl() == QLatin1String("trash:/")) { isTrashLink = true; } } if (m_newMenu) { m_newMenu->checkUpToDate(); - m_newMenu->setPopupFiles(m_dirModel->dirLister()->url()); + m_newMenu->setPopupFiles(QList() << m_dirModel->dirLister()->url()); // we need to set here as well, when the menu is shown via AppletInterface::eventFilter m_menuPosition = QCursor::pos(); if (QAction *newMenuAction = m_actionCollection.action(QStringLiteral("newMenu"))) { newMenuAction->setEnabled(itemProperties.supportsWriting()); newMenuAction->setVisible(!isTrash); } } if (QAction *emptyTrash = m_actionCollection.action(QStringLiteral("emptyTrash"))) { if (isTrash || isTrashLink) { emptyTrash->setVisible(true); emptyTrash->setEnabled(!isTrashEmpty()); } else { emptyTrash->setVisible(false); } } if (QAction *restoreFromTrash = m_actionCollection.action(QStringLiteral("restoreFromTrash"))) { restoreFromTrash->setVisible(isTrash); } if (QAction *moveToTrash = m_actionCollection.action(QStringLiteral("trash"))) { moveToTrash->setVisible(!hasRemoteFiles && itemProperties.supportsMoving()); } if (QAction *del = m_actionCollection.action(QStringLiteral("del"))) { del->setVisible(itemProperties.supportsDeleting()); } if (QAction *cut = m_actionCollection.action(QStringLiteral("cut"))) { cut->setEnabled(itemProperties.supportsDeleting()); cut->setVisible(!isTrash); } if (QAction *paste = m_actionCollection.action(QStringLiteral("paste"))) { bool enable = false; const QString pasteText = KIO::pasteActionText(QApplication::clipboard()->mimeData(), &enable, rootItem()); if (enable) { paste->setText(pasteText); paste->setEnabled(true); } else { paste->setText(i18n("&Paste")); paste->setEnabled(false); } if (QAction *pasteTo = m_actionCollection.action(QStringLiteral("pasteto"))) { pasteTo->setVisible(itemProperties.isDirectory() && itemProperties.supportsWriting()); pasteTo->setEnabled(paste->isEnabled()); pasteTo->setText(paste->text()); } } if (QAction *rename = m_actionCollection.action(QStringLiteral("rename"))) { rename->setEnabled(itemProperties.supportsMoving()); rename->setVisible(!isTrash); } } void FolderModel::openContextMenu(QQuickItem *visualParent, Qt::KeyboardModifiers modifiers) { if (m_usedByContainment && !KAuthorized::authorize(QStringLiteral("action/kdesktop_rmb"))) { return; } updateActions(); const QModelIndexList indexes = m_selectionModel->selectedIndexes(); QMenu *menu = new QMenu(); if (!m_fileItemActions) { m_fileItemActions = new KFileItemActions(this); m_fileItemActions->setParentWidget(QApplication::desktop()); } if (indexes.isEmpty()) { menu->addAction(m_actionCollection.action(QStringLiteral("newMenu"))); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); menu->addAction(m_actionCollection.action(QStringLiteral("undo"))); menu->addAction(m_actionCollection.action(QStringLiteral("refresh"))); menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); menu->addSeparator(); KFileItemListProperties itemProperties(KFileItemList() << rootItem()); m_fileItemActions->setItemListProperties(itemProperties); menu->addAction(m_fileItemActions->preferredOpenWithAction(QString())); } else { KFileItemList items; QList urls; items.reserve(indexes.count()); urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { items.append(item); urls.append(item.url()); } } KFileItemListProperties itemProperties(items); // Start adding the actions: // "Open" and "Open with" actions m_fileItemActions->setItemListProperties(itemProperties); m_fileItemActions->addOpenWithActionsTo(menu); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("cut"))); menu->addAction(m_actionCollection.action(QStringLiteral("copy"))); menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("rename"))); menu->addAction(m_actionCollection.action(QStringLiteral("restoreFromTrash"))); KConfigGroup cg(KSharedConfig::openConfig(), "KDE"); bool showDeleteCommand = cg.readEntry("ShowDeleteCommand", false); menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); QAction *trashAction = m_actionCollection.action(QStringLiteral("trash")); menu->addAction(trashAction); trashAction->setVisible(!modifiers.testFlag(Qt::ShiftModifier)); QAction *deleteAction = m_actionCollection.action(QStringLiteral("del")); menu->addAction(deleteAction); deleteAction->setVisible(showDeleteCommand || !trashAction->isVisible()); menu->addSeparator(); // Service actions m_fileItemActions->addServiceActionsTo(menu); menu->addSeparator(); // Plugin actions m_fileItemActions->addPluginActionsTo(menu); // Copy To, Move To KSharedConfig::Ptr dolphin = KSharedConfig::openConfig(QStringLiteral("dolphinrc")); if (KConfigGroup(dolphin, "General").readEntry("ShowCopyMoveMenu", false)) { m_copyToMenu->setUrls(urls); m_copyToMenu->setReadOnly(!itemProperties.supportsMoving()); m_copyToMenu->addActionsTo(menu); menu->addSeparator(); } // Properties if (KPropertiesDialog::canDisplay(items)) { menu->addSeparator(); QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Properties"), menu); act->setShortcuts({Qt::ALT + Qt::Key_Return, Qt::ALT + Qt::Key_Enter}); QObject::connect(act, &QAction::triggered, this, &FolderModel::openPropertiesDialog); menu->addAction(act); } } if (visualParent) { m_menuPosition = visualParent->mapToGlobal(QPointF(0, visualParent->height())).toPoint(); } else { m_menuPosition = QCursor::pos(); } // Used to monitor Shift modifier usage while the menu is open, to // swap the Trash and Delete actions. menu->installEventFilter(this); menu->setAttribute(Qt::WA_TranslucentBackground); menu->winId(); //force surface creation before ensurePolish call in menu::Popup which happens before show menu->popup(m_menuPosition); connect(menu, &QMenu::aboutToHide, [menu]() { menu->deleteLater(); }); } void FolderModel::openPropertiesDialog() { const QModelIndexList indexes = m_selectionModel->selectedIndexes(); if (indexes.isEmpty()) { return; } KFileItemList items; items.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { items.append(item); } } if (!KPropertiesDialog::canDisplay(items)) { return; } KPropertiesDialog::showDialog(items, nullptr, false /*non modal*/); } void FolderModel::linkHere(const QUrl &sourceUrl) { KIO::CopyJob *job = KIO::link(sourceUrl, m_dirModel->dirLister()->url()); KIO::FileUndoManager::self()->recordCopyJob(job); } QList FolderModel::selectedUrls() const { const auto indexes = m_selectionModel->selectedIndexes(); QList urls; urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { urls.append(itemForIndex(index).url()); } return urls; } void FolderModel::copy() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("copy"))) { if (!action->isEnabled()) { return; } } QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); QApplication::clipboard()->setMimeData(mimeData); } void FolderModel::cut() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("cut"))) { if (!action->isEnabled()) { return; } } QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void FolderModel::paste() { if (QAction *action = m_actionCollection.action(QStringLiteral("paste"))) { if (!action->isEnabled()) { return; } } KIO::paste(QApplication::clipboard()->mimeData(), m_dirModel->dirLister()->url()); } void FolderModel::pasteTo() { const QList urls = selectedUrls(); Q_ASSERT(urls.count() == 1); KIO::paste(QApplication::clipboard()->mimeData(), urls.first()); } void FolderModel::refresh() { m_errorString.clear(); emit errorStringChanged(); m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url()); } QObject *FolderModel::appletInterface() const { return m_appletInterface; } void FolderModel::setAppletInterface(QObject *appletInterface) { if (m_appletInterface != appletInterface) { Q_ASSERT(!m_appletInterface); m_appletInterface = appletInterface; if (appletInterface) { Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); if (applet) { Plasma::Containment *containment = applet->containment(); if (containment) { Plasma::Corona *corona = containment->corona(); if (corona) { m_screenMapper->setCorona(corona); } setScreen(containment->screen()); connect(containment, &Plasma::Containment::screenChanged, this, &FolderModel::setScreen); } } } emit appletInterfaceChanged(); } } void FolderModel::moveSelectedToTrash() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("trash"))) { if (!action->isEnabled()) { return; } } const QList urls = selectedUrls(); KIO::JobUiDelegate uiDelegate; if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::trash(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl(QStringLiteral("trash:/")), job); } } void FolderModel::deleteSelected() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("del"))) { if (!action->isEnabled()) { return; } } const QList urls = selectedUrls(); KIO::JobUiDelegate uiDelegate; if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::del(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::openSelected() { if (!m_selectionModel->hasSelection()) { return; } const QList urls = selectedUrls(); for (const QUrl &url : urls) { (void) new KRun(url, nullptr); } } void FolderModel::undo() { if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { // trigger() doesn't check enabled and would crash if invoked nonetheless. if (action->isEnabled()) { action->trigger(); } } } void FolderModel::emptyTrashBin() { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(QApplication::desktop()); if (uiDelegate.askDeleteConfirmation(QList(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::emptyTrash(); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::restoreSelectedFromTrash() { if (!m_selectionModel->hasSelection()) { return; } const auto &urls = selectedUrls(); KIO::RestoreJob *job = KIO::restoreFromTrash(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } bool FolderModel::isTrashEmpty() { KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); return trashConfig.group("Status").readEntry("Empty", true); } void FolderModel::undoTextChanged(const QString &text) { if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { action->setText(text); } } diff --git a/kcms/access/kcmaccess.cpp b/kcms/access/kcmaccess.cpp index a36c1496a..bc90a3302 100644 --- a/kcms/access/kcmaccess.cpp +++ b/kcms/access/kcmaccess.cpp @@ -1,569 +1,568 @@ /* Copyright 2000 Matthias Hölzer-Klüpfel Copyright 2014 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kcmaccess.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define XK_MISCELLANY #define XK_XKB_KEYS #include K_PLUGIN_FACTORY(KAccessConfigFactory, registerPlugin();) -K_EXPORT_PLUGIN(KAccessConfigFactory("kcmaccess")) QString mouseKeysShortcut(Display *display) { // Calculate the keycode KeySym sym = XK_MouseKeys_Enable; KeyCode code = XKeysymToKeycode(display, sym); if (code == 0) { sym = XK_Pointer_EnableKeys; code = XKeysymToKeycode(display, sym); if (code == 0) return QString(); // No shortcut available? } // Calculate the modifiers by searching the keysym in the X keyboard mapping XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd); if (!xkbdesc) return QString(); // Failed to obtain the mapping from server bool found = false; unsigned char modifiers = 0; int groups = XkbKeyNumGroups(xkbdesc, code); for (int grp = 0; grp < groups && !found; grp++) { int levels = XkbKeyGroupWidth(xkbdesc, code, grp); for (int level = 0; level < levels && !found; level++) { if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) { // keysym found => determine modifiers int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp]; XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]); for (int i = 0; i < type->map_count && !found; i++) { if (type->map[i].active && (type->map[i].level == level)) { modifiers = type->map[i].mods.mask; found = true; } } } } } XkbFreeClientMap(xkbdesc, 0, true); if (!found) return QString(); // Somehow the keycode -> keysym mapping is flawed XEvent ev; ev.type = KeyPress; ev.xkey.display = display; ev.xkey.keycode = code; ev.xkey.state = 0; int key; KKeyServer::xEventToQt(&ev, &key); QString keyname = QKeySequence(key).toString(); unsigned int AltMask = KKeyServer::modXAlt(); unsigned int WinMask = KKeyServer::modXMeta(); unsigned int NumMask = KKeyServer::modXNumLock(); unsigned int ScrollMask = KKeyServer::modXScrollLock(); unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L); unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L); unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L); unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift) | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock); unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask; AltGrMask &= ~mods; MetaMask &= ~(mods | AltGrMask); SuperMask &= ~(mods | AltGrMask | MetaMask); HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask); if ((modifiers & AltGrMask) != 0) keyname = i18n("AltGraph") + QLatin1Char('+') + keyname; if ((modifiers & HyperMask) != 0) keyname = i18n("Hyper") + QLatin1Char('+') + keyname; if ((modifiers & SuperMask) != 0) keyname = i18n("Super") + QLatin1Char('+') + keyname; if ((modifiers & WinMask) != 0) keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname; if ((modifiers & AltMask) != 0) keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname; if ((modifiers & ControlMask) != 0) keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname; if ((modifiers & ShiftMask) != 0) keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname; QString result; if ((modifiers & ScrollMask) != 0) if ((modifiers & LockMask) != 0) if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock, CapsLock and ScrollLock are active", keyname); else result = i18n("Press %1 while CapsLock and ScrollLock are active", keyname); else if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock and ScrollLock are active", keyname); else result = i18n("Press %1 while ScrollLock is active", keyname); else if ((modifiers & LockMask) != 0) if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock and CapsLock are active", keyname); else result = i18n("Press %1 while CapsLock is active", keyname); else if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock is active", keyname); else result = i18n("Press %1", keyname); return result; } KAccessConfig::KAccessConfig(QWidget *parent, const QVariantList& args) : KCModule(parent, args) { KAboutData *about = new KAboutData(QStringLiteral("kcmaccess"), i18n("KDE Accessibility Tool"), QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(c) 2000, Matthias Hoelzer-Kluepfel")); about->addAuthor(i18n("Matthias Hoelzer-Kluepfel"), i18n("Author") , QStringLiteral("hoelzer@kde.org")); setAboutData(about); ui.setupUi(this); connect(ui.soundButton, &QPushButton::clicked, this, &KAccessConfig::selectSound); connect(ui.customBell, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.systemBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.customBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.soundEdit, &QLineEdit::textChanged, this, &KAccessConfig::configChanged); connect(ui.invertScreen, &QRadioButton::clicked, this, &KAccessConfig::configChanged); connect(ui.flashScreen, &QRadioButton::clicked, this, &KAccessConfig::configChanged); connect(ui.visibleBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.visibleBell, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.colorButton, &KColorButton::clicked, this, &KAccessConfig::changeFlashScreenColor); connect(ui.invertScreen, &QRadioButton::clicked, this, &KAccessConfig::invertClicked); connect(ui.flashScreen, &QRadioButton::clicked, this, &KAccessConfig::flashClicked); connect(ui.duration, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); // modifier key settings ------------------------------- connect(ui.stickyKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeysLock, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeysAutoOff, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.stickyKeysBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.toggleKeysBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyModifiers, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyModifiers, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.kNotifyModifiersButton, &QPushButton::clicked, this, &KAccessConfig::configureKNotify); // key filter settings --------------------------------- connect(ui.slowKeysDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.slowKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.slowKeysPressBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeysAcceptBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeysRejectBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeysDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.bounceKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeysRejectBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); // gestures -------------------------------------------- QString shortcut = mouseKeysShortcut(QX11Info::display()); if (shortcut.isEmpty()) ui.gestures->setToolTip(i18n("Here you can activate keyboard gestures that turn on the following features: \n" "Sticky keys: Press Shift key 5 consecutive times\n" "Slow keys: Hold down Shift for 8 seconds")); else ui.gestures->setToolTip(i18n("Here you can activate keyboard gestures that turn on the following features: \n" "Mouse Keys: %1\n" "Sticky keys: Press Shift key 5 consecutive times\n" "Slow keys: Hold down Shift for 8 seconds", shortcut)); connect(ui.gestures, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.timeout, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.timeout, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.timeoutDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.accessxBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.gestureConfirmation, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyAccess, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyAccess, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.kNotifyAccessButton, &QPushButton::clicked, this, &KAccessConfig::configureKNotify); // keynboard navigation connect(ui.mouseKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.mk_delay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_interval, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_time_to_max, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_max_speed, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_curve, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); // screen reader connect(ui.screenReaderEnabled, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.launchOrcaConfiguration, &QPushButton::clicked, this, &KAccessConfig::launchOrcaConfiguration); } KAccessConfig::~KAccessConfig() { } void KAccessConfig::configureKNotify() { KNotifyConfigWidget::configure(this, QStringLiteral("kaccess")); } void KAccessConfig::launchOrcaConfiguration() { const QStringList gsettingArgs = { QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("true") }; int ret = QProcess::execute(QStringLiteral("gsettings"), gsettingArgs); if (ret) { const QString errorStr = QLatin1String("gsettings ") + gsettingArgs.join(QLatin1Char(' ')); ui.orcaLaunchFeedbackLabel->setText(i18n("Could not set gsettings for Orca: \"%1\" failed", errorStr)); return; } qint64 pid = 0; bool started = QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--setup")}, QString(), &pid); if (!started) { ui.orcaLaunchFeedbackLabel->setText(i18n("Error: Could not launch \"orca --setup\"")); } } void KAccessConfig::changeFlashScreenColor() { ui.invertScreen->setChecked(false); ui.flashScreen->setChecked(true); configChanged(); } void KAccessConfig::selectSound() { const QStringList list = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("sound/")); QString start; if (!list.isEmpty()) start = list[0]; const QString fname = QFileDialog::getOpenFileName(this, QString(), start); if (!fname.isEmpty()) ui.soundEdit->setText(fname); } void KAccessConfig::configChanged() { emit changed(true); } void KAccessConfig::load() { KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Bell"); ui.systemBell->setChecked(cg.readEntry("SystemBell", true)); ui.customBell->setChecked(cg.readEntry("ArtsBell", false)); ui.soundEdit->setText(cg.readPathEntry("ArtsBellFile", QString())); ui.visibleBell->setChecked(cg.readEntry("VisibleBell", false)); ui.invertScreen->setChecked(cg.readEntry("VisibleBellInvert", true)); ui.flashScreen->setChecked(!ui.invertScreen->isChecked()); ui.colorButton->setColor(cg.readEntry("VisibleBellColor", QColor(Qt::red))); ui.duration->setValue(cg.readEntry("VisibleBellPause", 500)); KConfigGroup keyboardGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Keyboard"); ui.stickyKeys->setChecked(keyboardGroup.readEntry("StickyKeys", false)); ui.stickyKeysLock->setChecked(keyboardGroup.readEntry("StickyKeysLatch", true)); ui.stickyKeysAutoOff->setChecked(keyboardGroup.readEntry("StickyKeysAutoOff", false)); ui.stickyKeysBeep->setChecked(keyboardGroup.readEntry("StickyKeysBeep", true)); ui.toggleKeysBeep->setChecked(keyboardGroup.readEntry("ToggleKeysBeep", false)); ui.kNotifyModifiers->setChecked(keyboardGroup.readEntry("kNotifyModifiers", false)); ui.slowKeys->setChecked(keyboardGroup.readEntry("SlowKeys", false)); ui.slowKeysDelay->setValue(keyboardGroup.readEntry("SlowKeysDelay", 500)); ui.slowKeysPressBeep->setChecked(keyboardGroup.readEntry("SlowKeysPressBeep", true)); ui.slowKeysAcceptBeep->setChecked(keyboardGroup.readEntry("SlowKeysAcceptBeep", true)); ui.slowKeysRejectBeep->setChecked(keyboardGroup.readEntry("SlowKeysRejectBeep", true)); ui.bounceKeys->setChecked(keyboardGroup.readEntry("BounceKeys", false)); ui.bounceKeysDelay->setValue(keyboardGroup.readEntry("BounceKeysDelay", 500)); ui.bounceKeysRejectBeep->setChecked(keyboardGroup.readEntry("BounceKeysRejectBeep", true)); ui.gestures->setChecked(keyboardGroup.readEntry("Gestures", false)); ui.timeout->setChecked(keyboardGroup.readEntry("AccessXTimeout", false)); ui.timeoutDelay->setValue(keyboardGroup.readEntry("AccessXTimeoutDelay", 30)); ui.accessxBeep->setChecked(keyboardGroup.readEntry("AccessXBeep", true)); ui.gestureConfirmation->setChecked(keyboardGroup.readEntry("GestureConfirmation", false)); ui.kNotifyAccess->setChecked(keyboardGroup.readEntry("kNotifyAccess", false)); KConfigGroup mouseGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Mouse"); ui.mouseKeys->setChecked(mouseGroup.readEntry("MouseKeys", false)); ui.mk_delay->setValue(mouseGroup.readEntry("MKDelay", 160)); const int interval = mouseGroup.readEntry("MKInterval", 5); ui.mk_interval->setValue(interval); // Default time to reach maximum speed: 5000 msec int time_to_max = mouseGroup.readEntry("MKTimeToMax", (5000+interval/2)/interval); time_to_max = mouseGroup.readEntry("MK-TimeToMax", time_to_max*interval); ui.mk_time_to_max->setValue(time_to_max); // Default maximum speed: 1000 pixels/sec // (The old default maximum speed from KDE <= 3.4 // (100000 pixels/sec) was way too fast) long max_speed = mouseGroup.readEntry("MKMaxSpeed", interval); max_speed = max_speed * 1000 / interval; if (max_speed > 2000) { max_speed = 2000; } max_speed = mouseGroup.readEntry("MK-MaxSpeed", int(max_speed)); ui.mk_max_speed->setValue(max_speed); ui.mk_curve->setValue(mouseGroup.readEntry("MKCurve", 0)); KConfigGroup screenReaderGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "ScreenReader"); ui.screenReaderEnabled->setChecked(screenReaderGroup.readEntry("Enabled", false)); checkAccess(); emit changed(false); } void KAccessConfig::save() { KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Bell"); cg.writeEntry("SystemBell", ui.systemBell->isChecked()); cg.writeEntry("ArtsBell", ui.customBell->isChecked()); cg.writePathEntry("ArtsBellFile", ui.soundEdit->text()); cg.writeEntry("VisibleBell", ui.visibleBell->isChecked()); cg.writeEntry("VisibleBellInvert", ui.invertScreen->isChecked()); cg.writeEntry("VisibleBellColor", ui.colorButton->color()); cg.writeEntry("VisibleBellPause", ui.duration->value()); cg.sync(); KConfigGroup keyboardGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Keyboard"); keyboardGroup.writeEntry("StickyKeys", ui.stickyKeys->isChecked()); keyboardGroup.writeEntry("StickyKeysLatch", ui.stickyKeysLock->isChecked()); keyboardGroup.writeEntry("StickyKeysAutoOff", ui.stickyKeysAutoOff->isChecked()); keyboardGroup.writeEntry("StickyKeysBeep", ui.stickyKeysBeep->isChecked()); keyboardGroup.writeEntry("ToggleKeysBeep", ui.toggleKeysBeep->isChecked()); keyboardGroup.writeEntry("kNotifyModifiers", ui.kNotifyModifiers->isChecked()); keyboardGroup.writeEntry("SlowKeys", ui.slowKeys->isChecked()); keyboardGroup.writeEntry("SlowKeysDelay", ui.slowKeysDelay->value()); keyboardGroup.writeEntry("SlowKeysPressBeep", ui.slowKeysPressBeep->isChecked()); keyboardGroup.writeEntry("SlowKeysAcceptBeep", ui.slowKeysAcceptBeep->isChecked()); keyboardGroup.writeEntry("SlowKeysRejectBeep", ui.slowKeysRejectBeep->isChecked()); keyboardGroup.writeEntry("BounceKeys", ui.bounceKeys->isChecked()); keyboardGroup.writeEntry("BounceKeysDelay", ui.bounceKeysDelay->value()); keyboardGroup.writeEntry("BounceKeysRejectBeep", ui.bounceKeysRejectBeep->isChecked()); keyboardGroup.writeEntry("Gestures", ui.gestures->isChecked()); keyboardGroup.writeEntry("AccessXTimeout", ui.timeout->isChecked()); keyboardGroup.writeEntry("AccessXTimeoutDelay", ui.timeoutDelay->value()); keyboardGroup.writeEntry("AccessXBeep", ui.accessxBeep->isChecked()); keyboardGroup.writeEntry("GestureConfirmation", ui.gestureConfirmation->isChecked()); keyboardGroup.writeEntry("kNotifyAccess", ui.kNotifyAccess->isChecked()); keyboardGroup.sync(); KConfigGroup mouseGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Mouse"); const int interval = ui.mk_interval->value(); mouseGroup.writeEntry("MouseKeys", ui.mouseKeys->isChecked()); mouseGroup.writeEntry("MKDelay", ui.mk_delay->value()); mouseGroup.writeEntry("MKInterval", interval); mouseGroup.writeEntry("MK-TimeToMax", ui.mk_time_to_max->value()); mouseGroup.writeEntry("MKTimeToMax", (ui.mk_time_to_max->value() + interval/2)/interval); mouseGroup.writeEntry("MK-MaxSpeed", ui.mk_max_speed->value()); mouseGroup.writeEntry("MKMaxSpeed", (ui.mk_max_speed->value()*interval + 500)/1000); mouseGroup.writeEntry("MKCurve", ui.mk_curve->value()); mouseGroup.sync(); KConfigGroup screenReaderGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "ScreenReader"); screenReaderGroup.writeEntry("Enabled", ui.screenReaderEnabled->isChecked()); if (ui.systemBell->isChecked() || ui.customBell->isChecked() || ui.visibleBell->isChecked()) { KConfig _cfg(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cfg(&_cfg, "General"); cfg.writeEntry("UseSystemBell", true); cfg.sync(); } // make kaccess reread the configuration // turning a11y features off needs to be done by kaccess // so run it to clear any enabled features and it will exit if it should KToolInvocation::startServiceByDesktopName(QStringLiteral("kaccess")); emit changed(false); } void KAccessConfig::defaults() { ui.systemBell->setChecked(true); ui.customBell->setChecked(false); ui.soundEdit->setText(QString()); ui.visibleBell->setChecked(false); ui.invertScreen->setChecked(true); ui.flashScreen->setChecked(false); ui.colorButton->setColor(QColor(Qt::red)); ui.duration->setValue(500); ui.slowKeys->setChecked(false); ui.slowKeysDelay->setValue(500); ui.slowKeysPressBeep->setChecked(true); ui.slowKeysAcceptBeep->setChecked(true); ui.slowKeysRejectBeep->setChecked(true); ui.bounceKeys->setChecked(false); ui.bounceKeysDelay->setValue(500); ui.bounceKeysRejectBeep->setChecked(true); ui.stickyKeys->setChecked(false); ui.stickyKeysLock->setChecked(true); ui.stickyKeysAutoOff->setChecked(false); ui.stickyKeysBeep->setChecked(true); ui.toggleKeysBeep->setChecked(false); ui.kNotifyModifiers->setChecked(false); ui.gestures->setChecked(false); ui.timeout->setChecked(false); ui.timeoutDelay->setValue(30); ui.accessxBeep->setChecked(true); ui.gestureConfirmation->setChecked(true); ui.kNotifyAccess->setChecked(false); ui.mouseKeys->setChecked(false); ui.mk_delay->setValue(160); ui.mk_interval->setValue(5); ui.mk_time_to_max->setValue(5000); ui.mk_max_speed->setValue(1000); ui.mk_curve->setValue(0); ui.screenReaderEnabled->setChecked(false); checkAccess(); emit changed(true); } void KAccessConfig::invertClicked() { ui.flashScreen->setChecked(false); } void KAccessConfig::flashClicked() { ui.invertScreen->setChecked(false); } void KAccessConfig::checkAccess() { bool custom = ui.customBell->isChecked(); ui.soundEdit->setEnabled(custom); ui.soundButton->setEnabled(custom); ui.soundLabel->setEnabled(custom); bool visible = ui.visibleBell->isChecked(); ui.invertScreen->setEnabled(visible); ui.flashScreen->setEnabled(visible); ui.colorButton->setEnabled(visible); ui.duration->setEnabled(visible); bool sticky = ui.stickyKeys->isChecked(); ui.stickyKeysLock->setEnabled(sticky); ui.stickyKeysAutoOff->setEnabled(sticky); ui.stickyKeysBeep->setEnabled(sticky); bool slow = ui.slowKeys->isChecked(); ui.slowKeysDelay->setEnabled(slow); ui.slowKeysPressBeep->setEnabled(slow); ui.slowKeysAcceptBeep->setEnabled(slow); ui.slowKeysRejectBeep->setEnabled(slow); bool bounce = ui.bounceKeys->isChecked(); ui.bounceKeysDelay->setEnabled(bounce); ui.bounceKeysRejectBeep->setEnabled(bounce); bool useTimeout = ui.timeout->isChecked(); ui.timeoutDelay->setEnabled(useTimeout); } extern "C" { /* This one gets called by kcminit */ Q_DECL_EXPORT void kcminit_access() { KToolInvocation::startServiceByDesktopName(QStringLiteral("kaccess")); } } #include "kcmaccess.moc" diff --git a/kcms/dateandtime/main.cpp b/kcms/dateandtime/main.cpp index 07f47a6a0..8aec086d7 100644 --- a/kcms/dateandtime/main.cpp +++ b/kcms/dateandtime/main.cpp @@ -1,212 +1,212 @@ /* * main.cpp * * Copyright (C) 1998 Luca Montecchiani * Copyright (C) 2015 David Edmundson * * 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) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "main.h" #include #include #include #include #include #include #include #include #include #include "dtime.h" #include "helper.h" #include #include #include "timedated_interface.h" K_PLUGIN_FACTORY(KlockModuleFactory, registerPlugin();) -K_EXPORT_PLUGIN(KlockModuleFactory("kcmkclock")) + KclockModule::KclockModule(QWidget *parent, const QVariantList &) : KCModule(parent) { auto reply = QDBusConnection::systemBus().call(QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus"), QStringLiteral("ListActivatableNames"))); if (!reply.arguments().isEmpty() && reply.arguments().first().value().contains(QLatin1String("org.freedesktop.timedate1"))) { m_haveTimedated = true; } KAboutData *about = new KAboutData(QStringLiteral("kcmclock"), i18n("KDE Clock Control Module"), QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(c) 1996 - 2001 Luca Montecchiani")); about->addAuthor(i18n("Luca Montecchiani"), i18n("Original author"), QStringLiteral("m.luca@usa.net")); about->addAuthor(i18n("Paul Campbell"), i18n("Current Maintainer"), QStringLiteral("paul@taniwha.com")); about->addAuthor(i18n("Benjamin Meyer"), i18n("Added NTP support"), QStringLiteral("ben+kcmclock@meyerhome.net")); setAboutData( about ); setQuickHelp( i18n("

Date & Time

This control module can be used to set the system date and" " time. As these settings do not only affect you as a user, but rather the whole system, you" " can only change these settings when you start the System Settings as root. If you do not have" " the root password, but feel the system time should be corrected, please contact your system" " administrator.")); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(KDialog::spacingHint()); dtime = new Dtime(this, m_haveTimedated); layout->addWidget(dtime); connect(dtime, SIGNAL(timeChanged(bool)), this, SIGNAL(changed(bool))); setButtons(Help|Apply); if (m_haveTimedated) { setAuthAction(KAuth::Action(QStringLiteral("org.freedesktop.timedate1.set-time"))); } else { //auth action name will be automatically guessed from the KCM name qWarning() << "Timedated not found, using legacy saving mode"; setNeedsAuthorization(true); } } bool KclockModule::kauthSave() { QVariantMap helperargs; helperargs[QStringLiteral("ntp")] = true; helperargs[QStringLiteral("ntpServers")] = dtime->ntpServers(); helperargs[QStringLiteral("ntpEnabled")] = dtime->ntpEnabled(); if (!dtime->ntpEnabled()) { QDateTime newTime = dtime->userTime(); qDebug() << "Set date to " << dtime->userTime(); helperargs[QStringLiteral("date")] = true; helperargs[QStringLiteral("newdate")] = QString::number(newTime.toTime_t()); helperargs[QStringLiteral("olddate")] = QString::number(::time(nullptr)); } QString selectedTimeZone = dtime->selectedTimeZone(); if (!selectedTimeZone.isEmpty()) { helperargs[QStringLiteral("tz")] = true; helperargs[QStringLiteral("tzone")] = selectedTimeZone; } else { helperargs[QStringLiteral("tzreset")] = true; // make the helper reset the timezone } Action action = authAction(); action.setArguments(helperargs); ExecuteJob *job = action.execute(); bool rc = job->exec(); if (!rc) { KMessageBox::error(this, i18n("Unable to authenticate/execute the action: %1, %2", job->error(), job->errorString())); } return rc; } bool KclockModule::timedatedSave() { OrgFreedesktopTimedate1Interface timedateIface(QStringLiteral("org.freedesktop.timedate1"), QStringLiteral("/org/freedesktop/timedate1"), QDBusConnection::systemBus()); bool rc = true; //final arg in each method is "user-interaction" i.e whether it's OK for polkit to ask for auth //we cannot send requests up front then block for all replies as we need NTP to be disabled before we can make a call to SetTime //timedated processes these in parallel and will return an error otherwise auto reply = timedateIface.SetNTP(dtime->ntpEnabled(), true); reply.waitForFinished(); if (reply.isError()) { KMessageBox::error(this, i18n("Unable to change NTP settings")); qWarning() << "Failed to enable NTP" << reply.error().name() << reply.error().message(); rc = false; } if (!dtime->ntpEnabled()) { qint64 timeDiff = dtime->userTime().toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(); //*1000 for milliseconds -> microseconds auto reply = timedateIface.SetTime(timeDiff * 1000, true, true); reply.waitForFinished(); if (reply.isError()) { KMessageBox::error(this, i18n("Unable to set current time")); qWarning() << "Failed to set current time" << reply.error().name() << reply.error().message(); rc = false; } } QString selectedTimeZone = dtime->selectedTimeZone(); if (!selectedTimeZone.isEmpty()) { auto reply = timedateIface.SetTimezone(selectedTimeZone, true); reply.waitForFinished(); if (reply.isError()) { KMessageBox::error(this, i18n("Unable to set timezone")); qWarning() << "Failed to set timezone" << reply.error().name() << reply.error().message(); rc = false; } } return rc; } void KclockModule::save() { setDisabled(true); bool success = false; if (m_haveTimedated) { success = timedatedSave(); } else { success = kauthSave(); } if (success) { QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/kde/kcmshell_clock"), QStringLiteral("org.kde.kcmshell_clock"), QStringLiteral("clockUpdated")); QDBusConnection::sessionBus().send(msg); } // NOTE: super nasty hack #1 // Try to work around time mismatch between KSystemTimeZones' update of local // timezone and reloading of data, so that the new timezone is taken into account. // The Ultimate solution to this would be if KSTZ emitted a signal when a new // local timezone was found. // setDisabled(false) happens in load(), since QTimer::singleShot is non-blocking if (!m_haveTimedated) { QTimer::singleShot(5000, this, SLOT(load())); } else { load(); } } void KclockModule::load() { dtime->load(); setDisabled(false); } #include "main.moc" diff --git a/kcms/desktoppaths/globalpaths.cpp b/kcms/desktoppaths/globalpaths.cpp index 6fe69eaea..c8f268dbd 100644 --- a/kcms/desktoppaths/globalpaths.cpp +++ b/kcms/desktoppaths/globalpaths.cpp @@ -1,456 +1,456 @@ /** * Copyright (c) Martin R. Jones 1996 * Copyright (c) Bernd Wuebben 1998 * Copyright (c) Christian Tibirna 1998 * Copyright 1998-2007 David Faure * Copyright (c) 2010 Matthias Fuchs * * 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) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // // // "Desktop Options" Tab for KDesktop configuration // // (c) Martin R. Jones 1996 // (c) Bernd Wuebben 1998 // // Layouts // (c) Christian Tibirna 1998 // Port to KControl, split from Misc Tab, Port to KControl2 // (c) David Faure 1998 // Desktop menus, paths // (c) David Faure 2000 // Own #include "globalpaths.h" // Qt #include #include #include #include #include #include #include #include // KDE #include #include #include //KDELibs4Support #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(KCM_DESKTOPPATH, "kcm_desktoppath") K_PLUGIN_FACTORY(KcmDesktopPathsFactory, registerPlugin();) //----------------------------------------------------------------------------- static QUrl desktopLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); } static QUrl autostartLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/autostart")); } static QUrl documentsLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); } static QUrl downloadLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); } static QUrl moviesLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)); } static QUrl picturesLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } static QUrl musicLocation() { return QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)); } DesktopPathConfig::DesktopPathConfig(QWidget *parent, const QVariantList &) : KCModule( parent ) { QFormLayout *lay = new QFormLayout(this); lay->setVerticalSpacing(0); lay->setContentsMargins(0, 0, 0, 0); setQuickHelp( i18n("

Paths

\n" "This module allows you to choose where in the filesystem the " "files on your desktop should be stored.\n" "Use the \"Whats This?\" (Shift+F1) to get help on specific options.")); urDesktop = addRow(lay, i18n("Desktop path:"), i18n("This folder contains all the files" " which you see on your desktop. You can change the location of this" " folder if you want to, and the contents will move automatically" " to the new location as well.")); urAutostart = addRow(lay, i18n("Autostart path:"), i18n("This folder contains applications or" " links to applications (shortcuts) that you want to have started" " automatically whenever the session starts. You can change the location of this" " folder if you want to, and the contents will move automatically" " to the new location as well.")); urDocument = addRow(lay, i18n("Documents path:"), i18n("This folder will be used by default to " "load or save documents from or to.")); urDownload = addRow(lay, i18n("Downloads path:"), i18n("This folder will be used by default to " "save your downloaded items.")); urMovie = addRow(lay, i18n("Movies path:"), i18n("This folder will be used by default to " "load or save movies from or to.")); urPicture = addRow(lay, i18n("Pictures path:"), i18n("This folder will be used by default to " "load or save pictures from or to.")); urMusic = addRow(lay, i18n("Music path:"), i18n("This folder will be used by default to " "load or save music from or to.")); } KUrlRequester* DesktopPathConfig::addRow(QFormLayout *lay, const QString& label, const QString& whatsThis) { KUrlRequester* ur = new KUrlRequester(this); ur->setMode(KFile::Directory | KFile::LocalOnly); lay->addRow(label, ur); connect(ur, SIGNAL(textChanged(QString)), this, SLOT(changed())); lay->labelForField(ur)->setWhatsThis(whatsThis); ur->setWhatsThis(whatsThis); return ur; } void DesktopPathConfig::load() { // Desktop Paths urDesktop->setUrl(desktopLocation()); urAutostart->setUrl(autostartLocation()); urDocument->setUrl(documentsLocation()); urDownload->setUrl(downloadLocation()); urMovie->setUrl(moviesLocation()); urPicture->setUrl(picturesLocation()); urMusic->setUrl(musicLocation()); emit changed(false); } void DesktopPathConfig::defaults() { // Desktop Paths - keep defaults in sync with kglobalsettings.cpp urDesktop->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Desktop"))); urAutostart->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/.config/autostart"))); urDocument->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Documents"))); urDownload->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Downloads"))); urMovie->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Movies"))); urPicture->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Pictures"))); urMusic->setUrl(QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/Music"))); } // the following method is copied from kdelibs/kdecore/config/kconfiggroup.cpp static bool cleanHomeDirPath( QString &path, const QString &homeDir ) { #ifdef Q_WS_WIN //safer if (!QDir::convertSeparators(path).startsWith(QDir::convertSeparators(homeDir))) return false; #else if (!path.startsWith(homeDir)) return false; #endif int len = homeDir.length(); // replace by "$HOME" if possible if (len && (path.length() == len || path[len] == '/')) { path.replace(0, len, QStringLiteral("$HOME")); return true; } else return false; } // TODO this functionality is duplicated in libkonq - keep it only there and export static QString translatePath( QString path ) // krazy:exclude=passbyvalue { // keep only one single '/' at the beginning - needed for cleanHomeDirPath() while (path.length() >= 2 && path[0] == '/' && path[1] == '/') path.remove(0,1); // we probably should escape any $ ` and \ characters that may occur in the path, but the Qt code that reads back // the file doesn't unescape them so not much point in doing so // All of the 3 following functions to return the user's home directory // can return different paths. We have to test all them. const QString homeDir0 = QFile::decodeName(qgetenv("HOME")); const QString homeDir1 = QDir::homePath(); const QString homeDir2 = QDir(homeDir1).canonicalPath(); if (cleanHomeDirPath(path, homeDir0) || cleanHomeDirPath(path, homeDir1) || cleanHomeDirPath(path, homeDir2) ) { // qCDebug(KCM_DESKTOPPATH) << "Path was replaced\n"; } return path; } void DesktopPathConfig::save() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup configGroup( config, "Paths" ); bool pathChanged = false; bool autostartMoved = false; QUrl desktopURL(desktopLocation()); QUrl autostartURL(autostartLocation()); QUrl newAutostartURL = urAutostart->url(); if ( !urDesktop->url().matches( desktopURL, QUrl::StripTrailingSlash ) ) { // Test which other paths were inside this one (as it is by default) // and for each, test where it should go. // * Inside destination -> let them be moved with the desktop (but adjust name if necessary) // * Not inside destination -> move first // !!! qCDebug(KCM_DESKTOPPATH) << "desktopURL=" << desktopURL; QString urlDesktop = urDesktop->url().toLocalFile(); if ( !urlDesktop.endsWith(QLatin1Char('/'))) urlDesktop+=QLatin1Char('/'); if ( desktopURL.isParentOf( autostartURL ) ) { qCDebug(KCM_DESKTOPPATH) << "Autostart is on the desktop"; // Either the Autostart field wasn't changed (-> need to update it) if ( newAutostartURL.matches(autostartURL, QUrl::StripTrailingSlash) ) { // Hack. It could be in a subdir inside desktop. Hmmm... Argl. urAutostart->setUrl(QUrl::fromLocalFile(urlDesktop + QStringLiteral("Autostart/"))); qCDebug(KCM_DESKTOPPATH) << "Autostart is moved with the desktop"; autostartMoved = true; } // or it has been changed (->need to move it from here) else { QUrl futureAutostartURL = QUrl::fromLocalFile(urlDesktop + QStringLiteral("Autostart/")); if ( newAutostartURL.matches( futureAutostartURL, QUrl::StripTrailingSlash ) ) autostartMoved = true; else autostartMoved = moveDir( autostartLocation(), urAutostart->url(), i18n("Autostart") ); } } if ( moveDir( desktopLocation(), QUrl::fromLocalFile( urlDesktop ), i18n("Desktop") ) ) { //save in XDG path const QString userDirsFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/user-dirs.dirs")); KConfig xdgUserConf( userDirsFile, KConfig::SimpleConfig ); KConfigGroup g( &xdgUserConf, "" ); g.writeEntry( "XDG_DESKTOP_DIR", QString("\"" + translatePath( urlDesktop ) + "\"") ); pathChanged = true; } } if ( !newAutostartURL.matches( autostartURL, QUrl::StripTrailingSlash ) ) { if (!autostartMoved) autostartMoved = moveDir( autostartLocation(), urAutostart->url(), i18n("Autostart") ); if (autostartMoved) { configGroup.writePathEntry( "Autostart", urAutostart->url().toLocalFile(), KConfigBase::Normal | KConfigBase::Global ); pathChanged = true; } } config->sync(); if (xdgSavePath(urDocument, documentsLocation(), "XDG_DOCUMENTS_DIR", i18n("Documents"))) pathChanged = true; if (xdgSavePath(urDownload, downloadLocation(), "XDG_DOWNLOAD_DIR", i18n("Downloads"))) pathChanged = true; if (xdgSavePath(urMovie, moviesLocation(), "XDG_VIDEOS_DIR", i18n("Movies"))) pathChanged = true; if (xdgSavePath(urPicture, picturesLocation(), "XDG_PICTURES_DIR", i18n("Pictures"))) pathChanged = true; if (xdgSavePath(urMusic, musicLocation(), "XDG_MUSIC_DIR", i18n("Music"))) pathChanged = true; if (pathChanged) { qCDebug(KCM_DESKTOPPATH) << "sending message SettingsChanged"; KGlobalSettings::self()->emitChange(KGlobalSettings::SettingsChanged, KGlobalSettings::SETTINGS_PATHS); } } bool DesktopPathConfig::xdgSavePath(KUrlRequester* ur, const QUrl& currentUrl, const char* xdgKey, const QString& type) { QUrl newUrl = ur->url(); //url might be empty, use QDir::homePath (the default for xdg) then if (!newUrl.isValid()) { newUrl = QUrl(QUrl::fromLocalFile(QDir::homePath())); } if (!newUrl.matches(currentUrl, QUrl::StripTrailingSlash)) { const QString path = newUrl.toLocalFile(); if (!QDir(path).exists()) { // Check permissions if (QDir().mkpath(path)) { QDir().rmdir(path); // rmdir again, so that we get a fast rename } else { - KMessageBox::sorry(this, KIO::buildErrorString(KIO::ERR_COULD_NOT_MKDIR, path)); + KMessageBox::sorry(this, KIO::buildErrorString(KIO::ERR_CANNOT_MKDIR, path)); ur->setUrl(currentUrl); // revert return false; } } if (moveDir(currentUrl, newUrl, type)) { //save in XDG user-dirs.dirs config file, this is where KGlobalSettings/QDesktopServices reads from. const QString userDirsFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/user-dirs.dirs")); KConfig xdgUserConf(userDirsFile, KConfig::SimpleConfig); KConfigGroup g(&xdgUserConf, ""); g.writeEntry(xdgKey, QString("\"" + translatePath(path) + "\"")); return true; } } return false; } bool DesktopPathConfig::moveDir( const QUrl & src, const QUrl & dest, const QString & type ) { if (!src.isLocalFile() || !dest.isLocalFile()) return true; if (!QFile::exists(src.toLocalFile())) return true; // Do not move $HOME! #193057 const QString translatedPath = translatePath(src.toLocalFile()); if (translatedPath == QLatin1String("$HOME") || translatedPath == QLatin1String("$HOME/")) { return true; } m_ok = true; QString question; KGuiItem yesItem; KGuiItem noItem; if (QFile::exists(dest.toLocalFile())) { // TODO: check if the src dir is empty? Nothing to move, then... question = i18n("The path for '%1' has been changed.\nDo you want the files to be moved from '%2' to '%3'?", type, src.toLocalFile(), dest.toLocalFile()); yesItem = KGuiItem(i18nc("Move files from old to new place", "Move")); noItem = KGuiItem(i18nc("Use the new directory but do not move files", "Do not Move")); } else { question = i18n("The path for '%1' has been changed.\nDo you want to move the directory '%2' to '%3'?", type, src.toLocalFile(), dest.toLocalFile()); yesItem = KGuiItem(i18nc("Move the directory", "Move")); noItem = KGuiItem(i18nc("Use the new directory but do not move anything", "Do not Move")); } // Ask for confirmation before moving the files if (KMessageBox::questionYesNo(this, question, i18n("Confirmation Required"), yesItem, noItem) == KMessageBox::Yes ) { if (QFile::exists(dest.toLocalFile())) { // Destination already exists -- should always be the case, for most types, // but maybe not for the complex autostart case (to be checked...) m_copyToDest = dest; m_copyFromSrc = src; KIO::ListJob* job = KIO::listDir( src ); job->setAutoDelete(false); // see below KJobWidgets::setWindow(job, this); job->uiDelegate()->setAutoErrorHandlingEnabled(true); connect(job, &KIO::ListJob::entries, this, &DesktopPathConfig::slotEntries); // slotEntries will move every file/subdir individually into the dest job->exec(); if (m_ok) { QDir().rmdir(src.toLocalFile()); // hopefully it's empty by now } delete job; } else { qCDebug(KCM_DESKTOPPATH) << "Direct move from" << src << "to" << dest; KIO::Job * job = KIO::move( src, dest ); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DesktopPathConfig::slotResult); job->exec(); } } qCDebug(KCM_DESKTOPPATH) << "DesktopPathConfig::slotResult returning " << m_ok; return m_ok; } void DesktopPathConfig::slotEntries(KIO::Job*, const KIO::UDSEntryList& list) { QListIterator it(list); while (it.hasNext()) { KFileItem file(it.next(), m_copyFromSrc, true, true); qCDebug(KCM_DESKTOPPATH) << file.url(); if (file.url() == m_copyFromSrc || file.name() == QLatin1String("..")) { continue; } KIO::Job * moveJob = KIO::move(file.url(), m_copyToDest); KJobWidgets::setWindow(moveJob, this); connect(moveJob, &KIO::Job::result, this, &DesktopPathConfig::slotResult); moveJob->exec(); // sub-event loop here. : the main job is not autodeleted because it would be deleted here } } void DesktopPathConfig::slotResult( KJob * job ) { if (job->error()) { if ( job->error() != KIO::ERR_DOES_NOT_EXIST ) m_ok = false; // If the source doesn't exist, no wonder we couldn't move the dir. // In that case, trust the user and set the new setting in any case. static_cast(job)->uiDelegate()->showErrorMessage(); } } #include "globalpaths.moc" diff --git a/kcms/kfontinst/dbus/FontInst.cpp b/kcms/kfontinst/dbus/FontInst.cpp index 21c37b285..3564eb1b6 100644 --- a/kcms/kfontinst/dbus/FontInst.cpp +++ b/kcms/kfontinst/dbus/FontInst.cpp @@ -1,974 +1,974 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2009 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. 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 #include #include #include #include "FontInst.h" #include "fontinstadaptor.h" #include "Misc.h" #include "Fc.h" #include "WritingSystems.h" #include "Utils.h" #include "FontinstIface.h" #define KFI_DBUG qDebug() << time(nullptr) namespace KFI { static void decompose(const QString &name, QString &family, QString &style) { int commaPos=name.lastIndexOf(','); family=-1==commaPos ? name : name.left(commaPos); style=-1==commaPos ? KFI_WEIGHT_REGULAR : name.mid(commaPos+2); } static bool isSystem=false; static Folder theFolders[FontInst::FOLDER_COUNT]; static const int constSystemReconfigured=-1; static const int constConnectionsTimeout = 30 * 1000; static const int constFontListTimeout = 10 * 1000; typedef void (*SignalHandler)(int); static void registerSignalHandler(SignalHandler handler) { if (!handler) handler = SIG_DFL; sigset_t mask; sigemptyset(&mask); #ifdef SIGSEGV signal(SIGSEGV, handler); sigaddset(&mask, SIGSEGV); #endif #ifdef SIGFPE signal(SIGFPE, handler); sigaddset(&mask, SIGFPE); #endif #ifdef SIGILL signal(SIGILL, handler); sigaddset(&mask, SIGILL); #endif #ifdef SIGABRT signal(SIGABRT, handler); sigaddset(&mask, SIGABRT); #endif sigprocmask(SIG_UNBLOCK, &mask, nullptr); } void signalHander(int) { static bool inHandler=false; if(!inHandler) { inHandler=true; theFolders[isSystem ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER].saveDisabled(); inHandler=false; } } FontInst::FontInst() { isSystem=Misc::root(); registerTypes(); new FontinstAdaptor(this); QDBusConnection bus=QDBusConnection::sessionBus(); KFI_DBUG << "Connecting to session bus"; if(!bus.registerService(OrgKdeFontinstInterface::staticInterfaceName())) { KFI_DBUG << "Failed to register service!"; ::exit(-1); } if(!bus.registerObject(FONTINST_PATH, this)) { KFI_DBUG << "Failed to register object!"; ::exit(-1); } registerSignalHandler(signalHander); itsConnectionsTimer=new QTimer(this); itsFontListTimer=new QTimer(this); connect(itsConnectionsTimer, &QTimer::timeout, this, &FontInst::connectionsTimeout); connect(itsFontListTimer, &QTimer::timeout, this, &FontInst::fontListTimeout); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].init(FOLDER_SYS==i, isSystem); updateFontList(false); } FontInst::~FontInst() { for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].saveDisabled(); } void FontInst::list(int folders, int pid) { KFI_DBUG << folders << pid; itsConnections.insert(pid); updateFontList(false); QList fonts; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) if(0==folders || folders&(1<start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); emit fontList(pid, fonts); } void FontInst::statFont(const QString &name, int folders, int pid) { KFI_DBUG << name << folders << pid; bool checkSystem=0==folders || folders&SYS_MASK || isSystem, checkUser=0==folders || (folders&USR_MASK && !isSystem); FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; itsConnections.insert(pid); if( (checkSystem && findFont(name, FOLDER_SYS, fam, st)) || (checkUser && findFont(name, FOLDER_USER, fam, st, !checkSystem)) ) { Family rv((*fam).name()); rv.add(*st); KFI_DBUG << "Found font, emit details..."; emit fontStat(pid, rv); } else { KFI_DBUG << "Font not found, emit empty details..."; emit fontStat(pid, Family(name)); } } void FontInst::install(const QString &file, bool createAfm, bool toSystem, int pid, bool checkConfig) { KFI_DBUG << file << createAfm << toSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || toSystem ? FOLDER_SYS : FOLDER_USER; Family font; Utils::EFileType type=Utils::check(file, font); int result=Utils::FILE_BITMAP==type && !FC::bitmapsEnabled() ? STATUS_BITMAPS_DISABLED : Utils::FILE_INVALID==type ? STATUS_NOT_FONT_FILE : STATUS_OK; if(STATUS_OK==result) { if(Utils::FILE_AFM!=type && Utils::FILE_PFM!=type) for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT) && STATUS_OK==result; ++i) if(theFolders[i].contains(font.name(), (*font.styles().begin()).value())) result=STATUS_ALREADY_INSTALLED; if(STATUS_OK==result) { QString name(Misc::modifyName(Misc::getFile(file))), destFolder(Misc::getDestFolder(theFolders[folder].location(), name)); result=Utils::FILE_AFM!=type && Utils::FILE_PFM!=type && Misc::fExists(destFolder+name) ? (int)KIO::ERR_FILE_ALREADY_EXIST : (int)STATUS_OK; if(STATUS_OK==result) { if(toSystem && !isSystem) { KFI_DBUG << "Send request to system helper" << file << destFolder << name; QVariantMap args; args["method"] = "install"; args["file"] = file; args["name"] = name; args["destFolder"] = destFolder; args["createAfm"] = createAfm; args["type"] = (int)type; result=performAction(args); } else { if(!Misc::dExists(destFolder)) result=Misc::createDir(destFolder) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED; if(STATUS_OK==result) result=QFile::copy(file, destFolder+name) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED; if(STATUS_OK==result) { Misc::setFilePerms(QFile::encodeName(destFolder+name)); if((Utils::FILE_SCALABLE==type || Utils::FILE_PFM==type) && createAfm) Utils::createAfm(destFolder+name, type); } } if(STATUS_OK==result && Utils::FILE_AFM!=type && Utils::FILE_PFM!=type) { StyleCont::ConstIterator st(font.styles().begin()); FileCont::ConstIterator f((*st).files().begin()); File df(destFolder+name, (*f).foundry(), (*f).index()); (*st).clearFiles(); (*st).add(df); theFolders[folder].add(font); theFolders[folder].addModifiedDir(destFolder); emit fontsAdded(Families(font, FOLDER_SYS==folder)); } } } } emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::uninstall(const QString &family, quint32 style, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << fromSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { Family del((*fam).name()); Style s((*st).value(), (*st).scalable(), (*st).writingSystems()); FileCont files((*st).files()); FileCont::ConstIterator it(files.begin()), end(files.end()); if(fromSystem && !isSystem) { QStringList fileList; bool wasDisabled(false); for(; it!=end; ++it) { fileList.append((*it).path()); theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*it).path())); if(!wasDisabled && Misc::isHidden(Misc::getFile((*it).path()))) wasDisabled=true; } QVariantMap args; args["method"] = "uninstall"; args["files"] = fileList; result=performAction(args); if(STATUS_OK==result) { FileCont empty; s.setFiles(files); (*st).setFiles(empty); if(wasDisabled) theFolders[FOLDER_SYS].setDisabledDirty(); } } else { for(; it!=end; ++it) if(!Misc::fExists((*it).path()) || QFile::remove((*it).path())) { // Also remove any AFM or PFM files... QStringList other; Misc::getAssociatedFiles((*it).path(), other); QStringList::ConstIterator oit(other.constBegin()), oend(other.constEnd()); for(; oit!=oend; ++oit) QFile::remove(*oit); theFolders[folder].addModifiedDir(Misc::getDir((*it).path())); (*st).remove(*it); s.add(*it); if(!theFolders[folder].disabledDirty() && Misc::isHidden(Misc::getFile((*it).path()))) theFolders[folder].setDisabledDirty(); } } if(STATUS_OK==result) { if((*st).files().isEmpty()) { (*fam).remove(*st); if((*fam).styles().isEmpty()) theFolders[folder].removeFont(*fam); } else result=STATUS_PARTIAL_DELETE; del.add(s); } emit fontsRemoved(Families(del, FOLDER_SYS==folder)); } KFI_DBUG << "status" << result; emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::uninstall(const QString &name, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << name << fromSystem << pid << checkConfig; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; if(findFont(name, fromSystem || isSystem ? FOLDER_SYS : FOLDER_USER, fam, st)) uninstall((*fam).name(), (*st).value(), fromSystem, pid, checkConfig); else emit status(pid, KIO::ERR_DOES_NOT_EXIST); } void FontInst::move(const QString &family, quint32 style, bool toSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << toSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); if(isSystem) emit status(pid, KIO::ERR_UNSUPPORTED_ACTION); else { FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; EFolder from=toSystem ? FOLDER_USER : FOLDER_SYS, to=toSystem ? FOLDER_SYS : FOLDER_USER; if(findFont(family, style, from, fam, st)) { FileCont::ConstIterator it((*st).files().begin()), end((*st).files().end()); QStringList files; for(; it!=end; ++it) { files.append((*it).path()); theFolders[from].addModifiedDir(Misc::getDir((*it).path())); // Actual 'to' folder does not really matter, as we only want to call fc-cache // ...actual folders only matter for xreating fonts.dir, etc, and we wont be doing this... theFolders[to].addModifiedDir(theFolders[to].location()); } QVariantMap args; args["method"] = "move"; args["files"] = files; args["toSystem"] = toSystem; args["dest"] = theFolders[to].location(); args["uid"] = getuid(); args["gid"] = getgid(); int result=performAction(args); if(STATUS_OK==result) updateFontList(); emit status(pid, result); } else { KFI_DBUG << "does not exist"; emit status(pid, KIO::ERR_DOES_NOT_EXIST); } } itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } static bool renameFontFile(const QString &from, const QString &to, int uid=-1, int gid=-1) { if (!QFile::rename(from, to)) return false; QByteArray dest(QFile::encodeName(to)); Misc::setFilePerms(dest); if(-1!=uid && -1!=gid) ::chown(dest.data(), uid, gid); return true; } void FontInst::enable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << inSystem << pid << checkConfig; toggle(true, family, style, inSystem, pid, checkConfig); } void FontInst::disable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << inSystem << pid << checkConfig; toggle(false, family, style, inSystem, pid, checkConfig); } void FontInst::removeFile(const QString &family, quint32 style, const QString &file, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << file << fromSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); // First find the family/style EFolder folder=isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { // Family/style found - now check that the requested file is *within* the same folder as one // of the files linked to this font... FileCont files((*st).files()); FileCont::ConstIterator it(files.begin()), end(files.end()); QString dir(Misc::getDir(file)); result=KIO::ERR_DOES_NOT_EXIST; for(; it!=end && STATUS_OK!=result; ++it) if(Misc::getDir((*it).path())==dir) result=STATUS_OK; if(STATUS_OK==result) { // OK, found folder - so can now proceed to delete the file... if(fromSystem && !isSystem) { QVariantMap args; args["method"] = "removeFile"; args["file"] = file; result=performAction(args); } else { result=Misc::fExists(file) ? QFile::remove(file) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED : (int)KIO::ERR_DOES_NOT_EXIST; } if(STATUS_OK==result) theFolders[folder].addModifiedDir(dir); } } emit status(pid, result); } void FontInst::reconfigure(int pid, bool force) { KFI_DBUG << pid << force; bool sysModified(theFolders[FOLDER_SYS].isModified()); saveDisabled(); KFI_DBUG << theFolders[FOLDER_USER].isModified() << sysModified; if(!isSystem && (force || theFolders[FOLDER_USER].isModified())) theFolders[FOLDER_USER].configure(force); if(sysModified) { if(isSystem) { theFolders[FOLDER_SYS].configure(); } else { QVariantMap args; args["method"] = "reconfigure"; performAction(args); theFolders[FOLDER_SYS].clearModified(); } } itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); updateFontList(); emit status(pid, isSystem ? constSystemReconfigured : STATUS_OK); } QString FontInst::folderName(bool sys) { return theFolders[sys || isSystem ? FOLDER_SYS : FOLDER_USER].location(); } void FontInst::saveDisabled() { if(isSystem) theFolders[FOLDER_SYS].saveDisabled(); else for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) if(FOLDER_SYS==i && !isSystem) { if(theFolders[i].disabledDirty()) { QVariantMap args; args["method"] = "saveDisabled"; performAction(args); theFolders[i].saveDisabled(); } } else theFolders[i].saveDisabled(); } void FontInst::connectionsTimeout() { bool canExit(true); KFI_DBUG << "exiting"; checkConnections(); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) { if(theFolders[i].disabledDirty()) canExit=false; theFolders[i].saveDisabled(); } if(0==itsConnections.count()) { if(canExit) qApp->exit(0); else // Try again later... itsConnectionsTimer->start(constConnectionsTimeout); } } void FontInst::fontListTimeout() { updateFontList(true); itsFontListTimer->start(constFontListTimeout); } void FontInst::updateFontList(bool emitChanges) { // For some reason just the "!FcConfigUptoDate(0)" check does not always work :-( FcBool fcModified=!FcConfigUptoDate(nullptr); if(fcModified || theFolders[FOLDER_SYS].fonts().isEmpty() || (!isSystem && theFolders[FOLDER_USER].fonts().isEmpty()) || theFolders[FOLDER_SYS].disabledDirty() || (!isSystem && theFolders[FOLDER_USER].disabledDirty())) { KFI_DBUG << "Need to refresh font lists"; if(fcModified) { KFI_DBUG << "Re-init FC"; if(!FcInitReinitialize()) KFI_DBUG << "Re-init failed????"; } Folder::Flat old[FOLDER_COUNT]; if(emitChanges) { KFI_DBUG << "Flatten existing font lists"; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) old[i]=theFolders[i].flatten(); } saveDisabled(); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].clearFonts(); KFI_DBUG << "update list of fonts"; FcPattern *pat = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_FAMILYLANG, FC_WEIGHT, FC_LANG, FC_CHARSET, FC_SCALABLE, #ifndef KFI_FC_NO_WIDTHS FC_WIDTH, #endif FC_SLANT, FC_INDEX, FC_FOUNDRY, (void*)nullptr); FcFontSet *list=FcFontList(nullptr, pat, os); FcPatternDestroy(pat); FcObjectSetDestroy(os); theFolders[FOLDER_SYS].loadDisabled(); if(!isSystem) theFolders[FOLDER_USER].loadDisabled(); if(list) { QString home(Misc::dirSyntax(QDir::homePath())); for (int i = 0; i < list->nfont; i++) { QString fileName(Misc::fileSyntax(FC::getFcString(list->fonts[i], FC_FILE))); if(!fileName.isEmpty() && Misc::fExists(fileName)) // && 0!=fileName.indexOf(constDefomaLocation)) { QString family, foundry; quint32 styleVal; int index; qulonglong writingSystems(WritingSystems::instance()->get(list->fonts[i])); FcBool scalable=FcFalse; if(FcResultMatch!=FcPatternGetBool(list->fonts[i], FC_SCALABLE, 0, &scalable)) scalable=FcFalse; FC::getDetails(list->fonts[i], family, styleVal, index, foundry); FamilyCont::ConstIterator fam=theFolders[isSystem || 0!=fileName.indexOf(home) ? FOLDER_SYS : FOLDER_USER].addFont(Family(family)); StyleCont::ConstIterator style=(*fam).add(Style(styleVal)); FileCont::ConstIterator file=(*style).add(File(fileName, foundry, index)); (*style).setWritingSystems((*style).writingSystems()|writingSystems); if(scalable) (*style).setScalable(); } } FcFontSetDestroy(list); } if(emitChanges) { KFI_DBUG << "Look for differences"; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) { KFI_DBUG << "Flatten, and take copies..."; Folder::Flat newList=theFolders[i].flatten(), onlyNew=newList; KFI_DBUG << "Determine differences..."; onlyNew.subtract(old[i]); old[i].subtract(newList); KFI_DBUG << "Emit changes..."; Families families=onlyNew.build(isSystem || i==FOLDER_SYS); if(!families.items.isEmpty()) emit fontsAdded(families); families=old[i].build(isSystem || i==FOLDER_SYS); if(!families.items.isEmpty()) emit fontsRemoved(families); } } KFI_DBUG << "updated list of fonts"; } } void FontInst::toggle(bool enable, const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || inSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { FileCont files((*st).files()), toggledFiles; FileCont::ConstIterator it(files.begin()), end(files.end()); QHash movedFonts; QHash movedAssoc; QSet modifiedDirs; for(; it!=end && STATUS_OK==result; ++it) { QString to=Misc::getDir((*it).path())+ QString(enable ? Misc::unhide(Misc::getFile((*it).path())) : Misc::hide(Misc::getFile((*it).path()))); if(to!=(*it).path()) { KFI_DBUG << "MOVE:" << (*it).path() << " to " << to; // If the font is a system font, and we're not root, then just go through the actions here - so // that we can build the list of changes that would happen... if((inSystem && !isSystem) || renameFontFile((*it).path(), to)) { modifiedDirs.insert(Misc::getDir(enable ? to : (*it).path())); toggledFiles.insert(File(to, (*it).foundry(), (*it).index())); // Now try to move an associated AFM or PFM files... QStringList assoc; movedFonts[*it]=to; Misc::getAssociatedFiles((*it).path(), assoc); QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd()); for(; ait!=aend && STATUS_OK==result; ++ait) { to=Misc::getDir(*ait)+ QString(enable ? Misc::unhide(Misc::getFile(*ait)) : Misc::hide(Misc::getFile(*ait))); if(to!=*ait) { if((inSystem && !isSystem) || renameFontFile(*ait, to)) { movedAssoc[*ait]=to; } else { result=KIO::ERR_WRITE_ACCESS_DENIED; } } } } else { result=KIO::ERR_WRITE_ACCESS_DENIED; } } } if(inSystem && !isSystem) { Family toggleFam((*fam).name()); toggleFam.add(*st); QVariantMap args; args["method"] = "toggle"; QString xml; QTextStream str(&xml); toggleFam.toXml(false, str); args["xml"] = xml; args["enable"] = enable; result=performAction(args); } if(STATUS_OK==result) { Family addFam((*fam).name()), delFam((*fam).name()); Style addStyle((*st).value(), (*st).scalable(), (*st).writingSystems()), delStyle((*st).value(), (*st).scalable(), (*st).writingSystems()); addStyle.setFiles(toggledFiles); addFam.add(addStyle); delStyle.setFiles(files); delFam.add(delStyle); (*st).setFiles(toggledFiles); theFolders[folder].addModifiedDirs(modifiedDirs); emit fontsAdded(Families(addFam, FOLDER_SYS==folder)); emit fontsRemoved(Families(delFam, FOLDER_SYS==folder)); theFolders[folder].setDisabledDirty(); } else // un-move fonts! { QHash::ConstIterator fit(movedFonts.constBegin()), fend(movedFonts.constEnd()); QHash::ConstIterator ait(movedAssoc.constBegin()), aend(movedAssoc.constEnd()); for(; fit!=fend; ++fit) renameFontFile(fit.value(), fit.key().path()); for(; ait!=aend; ++ait) renameFontFile(ait.value(), ait.key()); } } emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::addModifedSysFolders(const Family &family) { StyleCont::ConstIterator style(family.styles().begin()), styleEnd(family.styles().end()); for(; style!=styleEnd; ++style) { FileCont::ConstIterator file((*style).files().begin()), fileEnd((*style).files().end()); for(; file!=fileEnd; ++file) theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*file).path())); } } void FontInst::checkConnections() { KFI_DBUG; QSet::ConstIterator it(itsConnections.begin()), end(itsConnections.end()); QSet remove; for(; it!=end; ++it) if(0!=kill(*it, 0)) remove.insert(*it); itsConnections.subtract(remove); } bool FontInst::findFontReal(const QString &family, const QString &style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st) { KFI_DBUG; Family f(family); fam=theFolders[folder].fonts().find(f); if(theFolders[folder].fonts().end()==fam) return false; StyleCont::ConstIterator end((*fam).styles().end()); for(st=(*fam).styles().begin(); st!=end; ++st) if(FC::createStyleName((*st).value())==style) return true; return false; } bool FontInst::findFont(const QString &font, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList) { KFI_DBUG; QString family, style; decompose(font, family, style); if(!findFontReal(family, style, folder, fam, st)) { if(updateList) { // Not found, so refresh font list and try again... updateFontList(); return findFontReal(family, style, folder, fam, st); } else { return false; } } return true; } bool FontInst::findFontReal(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st) { KFI_DBUG; fam=theFolders[folder].fonts().find(Family(family)); if(theFolders[folder].fonts().end()==fam) return false; else { st=(*fam).styles().find(style); return (*fam).styles().end()!=st; } } bool FontInst::findFont(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList) { KFI_DBUG; if(!findFontReal(family, style, folder, fam, st)) { if(updateList) { // Not found, so refresh font list and try again... updateFontList(); return findFontReal(family, style, folder, fam, st); } else { return false; } } return true; } int FontInst::performAction(const QVariantMap &args) { KAuth::Action action("org.kde.fontinst.manage"); action.setHelperId("org.kde.fontinst"); action.setArguments(args); KFI_DBUG << "Call " << args["method"].toString() << " on helper"; itsFontListTimer->stop(); itsConnectionsTimer->stop(); KAuth::ExecuteJob* j = action.execute(); j->exec(); if (j->error()) { qWarning() << "kauth action failed" << j->errorString() << j->errorText(); //error is a KAuth::ActionReply::Error rest of this code expects KIO error codes which are extended by EStatus switch (j->error()) { case KAuth::ActionReply::Error::UserCancelledError: return KIO::ERR_USER_CANCELED; case KAuth::ActionReply::Error::AuthorizationDeniedError: /*fall through*/ case KAuth::ActionReply::Error::NoSuchActionError: - return KIO::ERR_COULD_NOT_AUTHENTICATE; + return KIO::ERR_CANNOT_AUTHENTICATE; default: return KIO::ERR_INTERNAL; } return KIO::ERR_INTERNAL; } KFI_DBUG << "Success!"; return STATUS_OK; } } diff --git a/kcms/kfontinst/kcmfontinst/JobRunner.cpp b/kcms/kfontinst/kcmfontinst/JobRunner.cpp index a867c01c0..18720505b 100644 --- a/kcms/kfontinst/kcmfontinst/JobRunner.cpp +++ b/kcms/kfontinst/kcmfontinst/JobRunner.cpp @@ -1,809 +1,809 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "JobRunner.h" #include "KfiConstants.h" #include "Misc.h" #include "Fc.h" #include "ActionLabel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config-fontinst.h" #define CFG_GROUP "Runner Dialog" #define CFG_DONT_SHOW_FINISHED_MSG "DontShowFinishedMsg" namespace KFI { Q_GLOBAL_STATIC(FontInstInterface, theInterface) FontInstInterface * CJobRunner::dbus() { return theInterface; } QString CJobRunner::folderName(bool sys) { if(!theInterface) return QString(); QDBusPendingReply reply=theInterface->folderName(sys); reply.waitForFinished(); return reply.isError() ? QString() : reply.argumentAt<0>(); } void CJobRunner::startDbusService() { if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(OrgKdeFontinstInterface::staticInterfaceName())) { const QString fontinst = QStringLiteral(KFONTINST_LIB_EXEC_DIR"/fontinst"); qDebug() << "Service " << OrgKdeFontinstInterface::staticInterfaceName() << " not registered, starting" << fontinst; QProcess::startDetached(fontinst, QStringList()); } } static const int constDownloadFailed=-1; static const int constInterfaceCheck=5*1000; static void decode(const QUrl &url, Misc::TFont &font, bool &system) { font=FC::decode(url); QUrlQuery query(url); system = (query.hasQueryItem("sys") && query.queryItemValue("sys") == QLatin1String("true")); } QUrl CJobRunner::encode(const QString &family, quint32 style, bool system) { QUrl url(FC::encode(family, style)); url.addQueryItem("sys", system ? "true" : "false"); return url; } enum EPages { PAGE_PROGRESS, PAGE_SKIP, PAGE_ERROR, PAGE_CANCEL, PAGE_COMPLETE }; enum Response { RESP_CONTINUE, RESP_AUTO, RESP_CANCEL }; static void addIcon(QGridLayout *layout, QFrame *page, const char *iconName, int iconSize) { QLabel *icon=new QLabel(page); icon->setPixmap(QIcon::fromTheme(iconName).pixmap(iconSize)); icon->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); layout->addWidget(icon, 0, 0); } CJobRunner::CJobRunner(QWidget *parent, int xid) : QDialog(parent), itsIt(itsUrls.end()), itsEnd(itsIt), itsAutoSkip(false), itsCancelClicked(false), itsModified(false), itsTempDir(nullptr) { setModal(true); if(nullptr==parent && 0!=xid) XSetTransientForHint(QX11Info::display(), winId(), xid); itsButtonBox = new QDialogButtonBox; connect(itsButtonBox, &QDialogButtonBox::clicked, this, &CJobRunner::slotButtonClicked); itsSkipButton = new QPushButton(i18n("Skip")); itsButtonBox->addButton(itsSkipButton, QDialogButtonBox::ActionRole); itsSkipButton->hide(); itsAutoSkipButton = new QPushButton(i18n("AutoSkip")); itsButtonBox->addButton(itsAutoSkipButton, QDialogButtonBox::ActionRole); itsAutoSkipButton->hide(); itsStack = new QStackedWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(itsStack); mainLayout->addWidget(itsButtonBox); QStyleOption option; option.initFrom(this); int iconSize=style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this); QFrame *page = new QFrame(itsStack); QGridLayout *layout=new QGridLayout(page); itsStatusLabel=new QLabel(page); itsProgress=new QProgressBar(page); // itsStatusLabel->setWordWrap(true); layout->addWidget(itsActionLabel = new CActionLabel(this), 0, 0, 2, 1); layout->addWidget(itsStatusLabel, 0, 1); layout->addWidget(itsProgress, 1, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0); itsStack->insertWidget(PAGE_PROGRESS, page); page=new QFrame(itsStack); layout=new QGridLayout(page); itsSkipLabel=new QLabel(page); itsSkipLabel->setWordWrap(true); addIcon(layout, page, "dialog-error", iconSize); layout->addWidget(itsSkipLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); itsStack->insertWidget(PAGE_SKIP, page); page=new QFrame(itsStack); layout=new QGridLayout(page); itsErrorLabel=new QLabel(page); itsErrorLabel->setWordWrap(true); addIcon(layout, page, "dialog-error", iconSize); layout->addWidget(itsErrorLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); itsStack->insertWidget(PAGE_ERROR, page); page=new QFrame(itsStack); layout=new QGridLayout(page); QLabel *cancelLabel=new QLabel(i18n("

Cancel?

Are you sure you wish to cancel?

"), page); cancelLabel->setWordWrap(true); addIcon(layout, page, "dialog-warning", iconSize); layout->addWidget(cancelLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); itsStack->insertWidget(PAGE_CANCEL, page); if(KSharedConfig::openConfig(KFI_UI_CFG_FILE)->group(CFG_GROUP).readEntry(CFG_DONT_SHOW_FINISHED_MSG, false)) itsDontShowFinishedMsg= nullptr; else { page=new QFrame(itsStack); layout=new QGridLayout(page); QLabel *finishedLabel=new QLabel(i18n("

Finished

" "

Please note that any open applications will need to be " "restarted in order for any changes to be noticed.

"), page); finishedLabel->setWordWrap(true); addIcon(layout, page, "dialog-information", iconSize); layout->addWidget(finishedLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); itsDontShowFinishedMsg = new QCheckBox(i18n("Do not show this message again"), page); itsDontShowFinishedMsg->setChecked(false); layout->addItem(new QSpacerItem(0, layout->spacing(), QSizePolicy::Fixed, QSizePolicy::Fixed), 2, 0); layout->addWidget(itsDontShowFinishedMsg, 3, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 4, 0); itsStack->insertWidget(PAGE_COMPLETE, page); } QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &CJobRunner::dbusServiceOwnerChanged); connect(dbus(), &OrgKdeFontinstInterface::status, this, &CJobRunner::dbusStatus); setMinimumSize(420, 160); } CJobRunner::~CJobRunner() { delete itsTempDir; } void CJobRunner::getAssociatedUrls(const QUrl &url, QList &list, bool afmAndPfm, QWidget *widget) { QString ext(url.path()); int dotPos(ext.lastIndexOf('.')); bool check(false); if(-1==dotPos) // Hmm, no extension - check anyway... check=true; else // Cool, got an extension - see if it is a Type1 font... { ext=ext.mid(dotPos+1); check=0==ext.compare("pfa", Qt::CaseInsensitive) || 0==ext.compare("pfb", Qt::CaseInsensitive); } if(check) { const char *afm[]={"afm", "AFM", "Afm", nullptr}, *pfm[]={"pfm", "PFM", "Pfm", nullptr}; bool gotAfm(false), localFile(url.isLocalFile()); int e; for(e=0; afm[e]; ++e) { QUrl statUrl(url); statUrl.setPath(Misc::changeExt(url.path(), afm[e])); bool urlExists = false; if (localFile) { urlExists = Misc::fExists(statUrl.toLocalFile()); } else { auto job = KIO::stat(statUrl); KJobWidgets::setWindow(job, widget); job->exec(); urlExists = !job->error(); } if (urlExists) { list.append(statUrl); gotAfm=true; break; } } if(afmAndPfm || !gotAfm) for(e=0; pfm[e]; ++e) { QUrl statUrl(url); statUrl.setPath(Misc::changeExt(url.path(), pfm[e])); bool urlExists = false; if (localFile) { urlExists = Misc::fExists(statUrl.toLocalFile()); } else { auto job = KIO::stat(statUrl); KJobWidgets::setWindow(job, widget); job->exec(); urlExists = !job->error(); } if (urlExists) { list.append(statUrl); break; } } } } static void addEnableActions(CJobRunner::ItemList &urls) { CJobRunner::ItemList modified; CJobRunner::ItemList::ConstIterator it(urls.constBegin()), end(urls.constEnd()); for(; it!=end; ++it) { if((*it).isDisabled) { CJobRunner::Item item(*it); item.fileName=QLatin1String("--"); modified.append(item); } modified.append(*it); } urls=modified; } int CJobRunner::exec(ECommand cmd, const ItemList &urls, bool destIsSystem) { itsAutoSkip=itsCancelClicked=itsModified=false; switch(cmd) { case CMD_INSTALL: setWindowTitle(i18n("Installing")); break; case CMD_DELETE: setWindowTitle(i18n("Uninstalling")); break; case CMD_ENABLE: setWindowTitle(i18n("Enabling")); break; case CMD_MOVE: setWindowTitle(i18n("Moving")); break; case CMD_UPDATE: setWindowTitle(i18n("Updating")); itsModified=true; break; case CMD_REMOVE_FILE: setWindowTitle(i18n("Removing")); break; default: case CMD_DISABLE: setWindowTitle(i18n("Disabling")); } itsDestIsSystem=destIsSystem; itsUrls=urls; if(CMD_INSTALL==cmd) std::sort(itsUrls.begin(), itsUrls.end()); // Sort list of fonts so that we have type1 fonts followed by their metrics... else if(CMD_MOVE==cmd) addEnableActions(itsUrls); itsIt=itsUrls.constBegin(); itsEnd=itsUrls.constEnd(); itsPrev=itsEnd; itsProgress->setValue(0); itsProgress->setRange(0, itsUrls.count()+1); itsProgress->show(); itsCmd=cmd; itsCurrentFile=QString(); itsStatusLabel->setText(QString()); setPage(PAGE_PROGRESS); QTimer::singleShot(0, this, &CJobRunner::doNext); QTimer::singleShot(constInterfaceCheck, this, &CJobRunner::checkInterface); itsActionLabel->startAnimation(); int rv=QDialog::exec(); if(itsTempDir) { delete itsTempDir; itsTempDir= nullptr; } return rv; } void CJobRunner::doNext() { if(itsIt==itsEnd/* || CMD_UPDATE==itsCmd*/) { if(itsModified) { // Force reconfig if command was already set to update... dbus()->reconfigure(getpid(), CMD_UPDATE==itsCmd); itsCmd=CMD_UPDATE; itsStatusLabel->setText(i18n("Updating font configuration. Please wait...")); itsProgress->setValue(itsProgress->maximum()); emit configuring(); } else { itsActionLabel->stopAnimation(); if(PAGE_ERROR!=itsStack->currentIndex()) reject(); } } else { Misc::TFont font; bool system; switch(itsCmd) { case CMD_INSTALL: { itsCurrentFile=fileName((*itsIt)); if(itsCurrentFile.isEmpty()) // Failed to download... dbusStatus(getpid(), constDownloadFailed); else { // Create AFM if this is a PFM, and the previous was not the AFM for this font... bool createAfm=Item::TYPE1_PFM==(*itsIt).type && (itsPrev==itsEnd || (*itsIt).fileName!=(*itsPrev).fileName || Item::TYPE1_AFM!=(*itsPrev).type); dbus()->install(itsCurrentFile, createAfm, itsDestIsSystem, getpid(), false); } break; } case CMD_DELETE: decode(*itsIt, font, system); dbus()->uninstall(font.family, font.styleInfo, system, getpid(), false); break; case CMD_ENABLE: decode(*itsIt, font, system); dbus()->enable(font.family, font.styleInfo, system, getpid(), false); break; case CMD_DISABLE: decode(*itsIt, font, system); dbus()->disable(font.family, font.styleInfo, system, getpid(), false); break; case CMD_MOVE: decode(*itsIt, font, system); // To 'Move' a disabled font, we first need to enable it. To accomplish this, JobRunner creates a 'fake' entry // with the filename "--" if((*itsIt).fileName==QLatin1String("--")) { setWindowTitle(i18n("Enabling")); dbus()->enable(font.family, font.styleInfo, system, getpid(), false); } else { if(itsPrev!=itsEnd && (*itsPrev).fileName==QLatin1String("--")) setWindowTitle(i18n("Moving")); dbus()->move(font.family, font.styleInfo, itsDestIsSystem, getpid(), false); } break; case CMD_REMOVE_FILE: decode(*itsIt, font, system); dbus()->removeFile(font.family, font.styleInfo, (*itsIt).fileName, system, getpid(), false); break; default: break; } itsStatusLabel->setText(CMD_INSTALL==itsCmd ? (*itsIt).url() : FC::createName(FC::decode(*itsIt))); itsProgress->setValue(itsProgress->value()+1); // Keep copy of this iterator - so that can check whether AFM should be created. itsPrev=itsIt; } } void CJobRunner::checkInterface() { if(itsIt==itsUrls.constBegin() && !FontInst::isStarted(dbus())) { setPage(PAGE_ERROR, i18n("Unable to start backend.")); itsActionLabel->stopAnimation(); itsIt=itsEnd; } } void CJobRunner::dbusServiceOwnerChanged(const QString &name, const QString &from, const QString &to) { if(to.isEmpty() && !from.isEmpty() && name==QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()) && itsIt!=itsEnd) { setPage(PAGE_ERROR, i18n("Backend died, but has been restarted. Please try again.")); itsActionLabel->stopAnimation(); itsIt=itsEnd; } } void CJobRunner::dbusStatus(int pid, int status) { if(pid!=getpid()) return; if(CMD_UPDATE==itsCmd) { setPage(PAGE_COMPLETE); return; } itsLastDBusStatus=status; if(itsCancelClicked) { itsActionLabel->stopAnimation(); setPage(PAGE_CANCEL); return; /* if(RESP_CANCEL==itsResponse) itsIt=itsEnd; itsCancelClicked=false; setPage(PAGE_PROGRESS); itsActionLabel->startAnimation(); */ } // itsIt will equal itsEnd if user decided to cancel the current op if(itsIt==itsEnd) { doNext(); } else if (0==status) { itsModified=true; ++itsIt; doNext(); } else { bool cont(itsAutoSkip && itsUrls.count()>1); QString currentName((*itsIt).fileName); if(!cont) { itsActionLabel->stopAnimation(); if(FontInst::STATUS_SERVICE_DIED==status) { setPage(PAGE_ERROR, errorString(status)); itsIt=itsEnd; } else { ItemList::ConstIterator lastPartOfCurrent(itsIt), next(itsIt==itsEnd ? itsEnd : itsIt+1); // If we're installing a Type1 font, and its already installed - then we need to skip past AFM/PFM if(next!=itsEnd && Item::TYPE1_FONT==(*itsIt).type && (*next).fileName==currentName && (Item::TYPE1_AFM==(*next).type || Item::TYPE1_PFM==(*next).type)) { next++; if(next!=itsEnd && (*next).fileName==currentName && (Item::TYPE1_AFM==(*next).type || Item::TYPE1_PFM==(*next).type)) next++; } if(1==itsUrls.count() || next==itsEnd) setPage(PAGE_ERROR, errorString(status)); else { setPage(PAGE_SKIP, errorString(status)); return; } } } contineuToNext(cont); } } void CJobRunner::contineuToNext(bool cont) { itsActionLabel->startAnimation(); if(cont) { if(CMD_INSTALL==itsCmd && Item::TYPE1_FONT==(*itsIt).type) // Did we error on a pfa/pfb? if so, exclude the afm/pfm... { QString currentName((*itsIt).fileName); ++itsIt; // Skip afm/pfm if(itsIt!=itsEnd && (*itsIt).fileName==currentName && (Item::TYPE1_AFM==(*itsIt).type || Item::TYPE1_PFM==(*itsIt).type)) ++itsIt; // Skip pfm/afm if(itsIt!=itsEnd && (*itsIt).fileName==currentName && (Item::TYPE1_AFM==(*itsIt).type || Item::TYPE1_PFM==(*itsIt).type)) ++itsIt; } else ++itsIt; } else { itsUrls.empty(); itsIt=itsEnd=itsUrls.constEnd(); } doNext(); } void CJobRunner::slotButtonClicked(QAbstractButton *button) { switch(itsStack->currentIndex()) { case PAGE_PROGRESS: if(itsIt!=itsEnd) itsCancelClicked=true; break; case PAGE_SKIP: setPage(PAGE_PROGRESS); if (button == itsSkipButton) { contineuToNext(true); } else if (button == itsAutoSkipButton) { itsAutoSkip=true; contineuToNext(true); } else { contineuToNext(false); } break; case PAGE_CANCEL: if(button == itsButtonBox->button(QDialogButtonBox::Yes)) itsIt=itsEnd; itsCancelClicked=false; setPage(PAGE_PROGRESS); itsActionLabel->startAnimation(); // Now continue... dbusStatus(getpid(), itsLastDBusStatus); break; case PAGE_COMPLETE: if(itsDontShowFinishedMsg) { KConfigGroup grp(KSharedConfig::openConfig(KFI_UI_CFG_FILE)->group(CFG_GROUP)); grp.writeEntry(CFG_DONT_SHOW_FINISHED_MSG, itsDontShowFinishedMsg->isChecked()); } case PAGE_ERROR: QDialog::accept(); break; } } void CJobRunner::closeEvent(QCloseEvent *e) { if(PAGE_COMPLETE!=itsStack->currentIndex()) { e->ignore(); slotButtonClicked(PAGE_CANCEL==itsStack->currentIndex() ? itsButtonBox->button(QDialogButtonBox::No) : itsButtonBox->button(QDialogButtonBox::Cancel)); } } void CJobRunner::setPage(int page, const QString &msg) { itsStack->setCurrentIndex(page); switch(page) { case PAGE_PROGRESS: itsButtonBox->setStandardButtons(QDialogButtonBox::Cancel); itsSkipButton->hide(); itsAutoSkipButton->hide(); break; case PAGE_SKIP: itsSkipLabel->setText(i18n("

Error

")+QLatin1String("

")+msg+QLatin1String("

")); itsButtonBox->setStandardButtons(QDialogButtonBox::Cancel); itsSkipButton->show(); itsAutoSkipButton->show(); break; case PAGE_ERROR: itsErrorLabel->setText(i18n("

Error

")+QLatin1String("

")+msg+QLatin1String("

")); itsButtonBox->setStandardButtons(QDialogButtonBox::Cancel); itsSkipButton->hide(); itsAutoSkipButton->hide(); break; case PAGE_CANCEL: itsButtonBox->setStandardButtons(QDialogButtonBox::Yes|QDialogButtonBox::No); itsSkipButton->hide(); itsAutoSkipButton->hide(); break; case PAGE_COMPLETE: if(!itsDontShowFinishedMsg || itsDontShowFinishedMsg->isChecked()) { QDialog::accept(); } else { itsButtonBox->setStandardButtons(QDialogButtonBox::Close); itsSkipButton->hide(); itsAutoSkipButton->hide(); } break; } } QString CJobRunner::fileName(const QUrl &url) { if(url.isLocalFile()) return url.toLocalFile(); else { auto job = KIO::mostLocalUrl(url); job->exec(); QUrl local = job->mostLocalUrl(); if(local.isLocalFile()) return local.toLocalFile(); // Yipee! no need to download!! else { // Need to do actual download... if(!itsTempDir) { itsTempDir=new QTemporaryDir(QDir::tempPath() + "/fontinst"); itsTempDir->setAutoRemove(true); } QString tempName(itsTempDir->filePath(Misc::getFile(url.path()))); auto job = KIO::file_copy(url, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite); if (job->exec()) return tempName; else return QString(); } } } QString CJobRunner::errorString(int value) const { Misc::TFont font(FC::decode(*itsIt)); QString urlStr; if(CMD_REMOVE_FILE==itsCmd) urlStr=(*itsIt).fileName; else if(font.family.isEmpty()) urlStr=(*itsIt).url(); else urlStr=FC::createName(font.family, font.styleInfo); switch(value) { case constDownloadFailed: return i18n("Failed to download %1", urlStr); case FontInst::STATUS_SERVICE_DIED: return i18n("System backend died. Please try again.
%1", urlStr); case FontInst::STATUS_BITMAPS_DISABLED: return i18n("%1 is a bitmap font, and these have been disabled on your system.", urlStr); case FontInst::STATUS_ALREADY_INSTALLED: return i18n("%1 contains the font %2, which is already installed on your system.", urlStr, FC::getName(itsCurrentFile)); case FontInst::STATUS_NOT_FONT_FILE: return i18n("%1 is not a font.", urlStr); case FontInst::STATUS_PARTIAL_DELETE: return i18n("Could not remove all files associated with %1", urlStr); case FontInst::STATUS_NO_SYS_CONNECTION: return i18n("Failed to start the system daemon.
%1", urlStr); case KIO::ERR_FILE_ALREADY_EXIST: { QString name(Misc::modifyName(Misc::getFile((*itsIt).fileName))), destFolder(Misc::getDestFolder(folderName(itsDestIsSystem), name)); return i18n("%1 already exists.", destFolder+name); } case KIO::ERR_DOES_NOT_EXIST: return i18n("%1 does not exist.", urlStr); case KIO::ERR_WRITE_ACCESS_DENIED: return i18n("Permission denied.
%1", urlStr); case KIO::ERR_UNSUPPORTED_ACTION: return i18n("Unsupported action.
%1", urlStr); - case KIO::ERR_COULD_NOT_AUTHENTICATE: + case KIO::ERR_CANNOT_AUTHENTICATE: return i18n("Authentication failed.
%1", urlStr); default: return i18n("Unexpected error while processing: %1", urlStr); } } CJobRunner::Item::Item(const QUrl &u, const QString &n, bool dis) : QUrl(u), name(n), fileName(Misc::getFile(u.path())), isDisabled(dis) { type=Misc::checkExt(fileName, "pfa") || Misc::checkExt(fileName, "pfb") ? TYPE1_FONT : Misc::checkExt(fileName, "afm") ? TYPE1_AFM : Misc::checkExt(fileName, "pfm") ? TYPE1_PFM : OTHER_FONT; if(OTHER_FONT!=type) { int pos(fileName.lastIndexOf('.')); if(-1!=pos) fileName.truncate(pos); } } CJobRunner::Item::Item(const QString &file, const QString &family, quint32 style, bool system) : QUrl(CJobRunner::encode(family, style, system)), fileName(file), type(OTHER_FONT) { } bool CJobRunner::Item::operator<(const Item &o) const { int nameComp(fileName.compare(o.fileName)); return nameComp<0 || (0==nameComp && type * * ---- * * 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) any later version. * * 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; see the file COPYING. 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 #include #include #include #include #include #include #include #include "KioFonts.h" #include "KfiConstants.h" #include "FontInstInterface.h" #include "FontInst.h" #include "Fc.h" #include "Misc.h" #include "XmlStrings.h" #include "Family.h" #include "Style.h" #include "File.h" #include "debug.h" #define MAX_IPC_SIZE (1024*32) #define KFI_DBUG qCDebug(KCM_KFONTINST_KIO) << '(' << time(NULL) << ')' static const int constReconfigTimeout = 10; extern "C" { Q_DECL_EXPORT int kdemain(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "Usage: kio_" KFI_KIO_FONTS_PROTOCOL " protocol domain-socket1 domain-socket2\n"); exit(-1); } QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("kio_" KFI_KIO_FONTS_PROTOCOL); KFI::CKioFonts slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } } namespace KFI { inline bool isSysFolder(const QString &folder) { return i18n(KFI_KIO_FONTS_SYS)==folder || KFI_KIO_FONTS_SYS==folder; } inline bool isUserFolder(const QString &folder) { return i18n(KFI_KIO_FONTS_USER)==folder || KFI_KIO_FONTS_USER==folder; } static CKioFonts::EFolder getFolder(const QStringList &list) { if(list.size()>0) { QString folder=list[0]; if(isSysFolder(folder)) return CKioFonts::FOLDER_SYS; else if(isUserFolder(folder)) return CKioFonts::FOLDER_USER; return CKioFonts::FOLDER_UNKNOWN; } return CKioFonts::FOLDER_ROOT; } static int getSize(const QString &file) { QT_STATBUF buff; QByteArray f(QFile::encodeName(file)); if(-1!=QT_LSTAT(f.constData(), &buff)) { if (S_ISLNK(buff.st_mode)) { char buffer2[1000]; int n=readlink(f.constData(), buffer2, 999); if(n!= -1) buffer2[n]='\0'; if(-1==QT_STAT(f.constData(), &buff)) return -1; } return buff.st_size; } return -1; } static bool writeAll(int fd, const char *buf, size_t len) { while(len>0) { ssize_t written=write(fd, buf, len); if (written<0 && EINTR!=errno) return false; buf+=written; len-=written; } return true; } static bool isScalable(const QString &str) { return Misc::checkExt(str, "ttf") || Misc::checkExt(str, "otf") || Misc::checkExt(str, "ttc") || Misc::checkExt(str, "pfa") || Misc::checkExt(str, "pfb"); } static const char * const constExtensions[]= {".ttf", KFI_FONTS_PACKAGE, ".otf", ".pfa", ".pfb", ".ttc", ".pcf", ".pcf.gz", ".bdf", ".bdf.gz", nullptr }; static QString removeKnownExtension(const QUrl &url) { QString fname(url.fileName()); int pos; for(int i=0; constExtensions[i]; ++i) if(-1!=(pos=fname.lastIndexOf(QString::fromLatin1(constExtensions[i]), -1, Qt::CaseInsensitive))) return fname.left(pos); return fname; } CKioFonts::CKioFonts(const QByteArray &pool, const QByteArray &app) : KIO::SlaveBase(KFI_KIO_FONTS_PROTOCOL, pool, app) , itsInterface(new FontInstInterface()) , itsTempDir(nullptr) { KFI_DBUG; } CKioFonts::~CKioFonts() { KFI_DBUG; delete itsInterface; delete itsTempDir; } void CKioFonts::listDir(const QUrl &url) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder=Misc::root() ? FOLDER_SYS : getFolder(pathList); KIO::UDSEntry entry; int size=0; switch(folder) { case FOLDER_ROOT: KFI_DBUG << "List root folder"; size=2; totalSize(2); createUDSEntry(entry, FOLDER_SYS); listEntry(entry); createUDSEntry(entry, FOLDER_USER); listEntry(entry); break; case FOLDER_SYS: case FOLDER_USER: size=listFolder(entry, folder); break; default: break; } if(FOLDER_UNKNOWN!=folder) { finished(); } else error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } void CKioFonts::put(const QUrl &url, int /*permissions*/, KIO::JobFlags /*flags*/) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder(getFolder(pathList)); if(!Misc::root() && FOLDER_ROOT==folder) error(KIO::ERR_SLAVE_DEFINED, i18n("Can only install fonts to either \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); else if(Misc::isPackage(url.fileName())) error(KIO::ERR_SLAVE_DEFINED, i18n("You cannot install a fonts package directly.\n" "Please extract %1, and install the components individually.", url.toDisplayString())); else { if(!itsTempDir) { itsTempDir=new QTemporaryDir(QDir::tempPath() + QString::fromLatin1("/kio_fonts_")+QString::number(getpid())); itsTempDir->setAutoRemove(true); } QString tempFile(itsTempDir->filePath(url.fileName())); QFile dest(tempFile); if (dest.open(QIODevice::WriteOnly)) { int result; // Loop until we got 0 (end of data) do { QByteArray buffer; dataReq(); // Request for data result = readData(buffer); if(result > 0 && !writeAll(dest.handle(), buffer.constData(), buffer.size())) { if(ENOSPC==errno) // disk full { error(KIO::ERR_DISK_FULL, dest.fileName()); result = -2; // means: remove dest file } else { - error(KIO::ERR_COULD_NOT_WRITE, dest.fileName()); + error(KIO::ERR_CANNOT_WRITE, dest.fileName()); result = -1; } } } while(result>0); if (result<0) { dest.close(); ::exit(255); } handleResp(itsInterface->install(tempFile, Misc::root() || FOLDER_SYS==folder), url.fileName(), tempFile, FOLDER_SYS==folder); QFile::remove(tempFile); } else error(EACCES==errno ? KIO::ERR_WRITE_ACCESS_DENIED : KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest.fileName()); } } void CKioFonts::get(const QUrl &url) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder(getFolder(pathList)); Family family(getFont(url, folder)); if(!family.name().isEmpty() && 1==family.styles().count()) { StyleCont::ConstIterator style(family.styles().begin()); FileCont::ConstIterator it((*style).files().begin()), end((*style).files().end()); // // The thumbnail job always downloads non-local files to /tmp/... and passes this file name to // the thumbnail creator. However, in the case of fonts which are split among many files, this // wont work. Therefore, when the thumbnail code asks for the font to download, just return // the family and style info for enabled fonts, and the filename for disabled fonts. This way // the font-thumbnail creator can read this and just ask Xft/fontconfig for the font data. if("1"==metaData("thumbnail")) { QByteArray array; QTextStream stream(&array, QIODevice::WriteOnly); emit mimeType("text/plain"); bool hidden(true); for(; it!=end && hidden; ++it) if(!Misc::isHidden(Misc::getFile((*it).path()))) hidden=false; if(hidden) { // // OK, its a disabled font - if possible try to return the location of the font file // itself. bool found=false; it=(*style).files().begin(); end=(*style).files().end(); for(; it!=end && hidden; ++it) if(isScalable((*it).path())) { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return FILE: " << (*it).path() << " / " << (*it).index(); stream << KFI_PATH_KEY << (*it).path() << endl << KFI_FACE_KEY << (*it).index() << endl; found=true; break; } if(!found) { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return Url: " << url; stream << url.toDisplayString(); } } else { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return DETAILS: " << family.name() << " / " << (*style).value(); stream << KFI_NAME_KEY << family.name() << endl << KFI_STYLE_KEY << (*style).value() << endl; } totalSize(array.size()); data(array); processedSize(array.size()); data(QByteArray()); processedSize(array.size()); finished(); KFI_DBUG << "Finished thumbnail..."; return; } QSet files; QString realPath; QT_STATBUF buff; bool multiple=false; for(; it!=end; ++it) { QStringList assoc; files.insert((*it).path()); Misc::getAssociatedFiles((*it).path(), assoc); QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd()); for(; ait!=aend; ++ait) files.insert(*ait); } if(1==files.count()) realPath=(*files.begin()); else // Font is made up of multiple files - so create .zip of them all! { QTemporaryFile tmpFile; if(tmpFile.open()) { KZip zip(tmpFile.fileName()); tmpFile.setAutoRemove(false); realPath=tmpFile.fileName(); if(zip.open(QIODevice::WriteOnly)) { QMap map=Misc::getFontFileMap(files); QMap::ConstIterator it(map.constBegin()), end(map.constEnd()); for(; it!=end; ++it) zip.addLocalFile(it.value(), it.key()); multiple=true; zip.close(); } } } QByteArray realPathC(QFile::encodeName(realPath)); KFI_DBUG << "real: " << realPathC; if (-2==QT_STAT(realPathC.constData(), &buff)) error(EACCES==errno ? KIO::ERR_ACCESS_DENIED : KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); else if (S_ISDIR(buff.st_mode)) error(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); else if (!S_ISREG(buff.st_mode)) error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); else { int fd = QT_OPEN(realPathC.constData(), O_RDONLY); if (fd < 0) error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); else { // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // This code can be optimized by using QFileInfo instead of buff above // and passing it to mimeTypeForFile() instead of realPath. QMimeDatabase db; emit mimeType(db.mimeTypeForFile(realPath).name()); totalSize(buff.st_size); KIO::filesize_t processed=0; char buffer[MAX_IPC_SIZE]; QByteArray array; while(1) { int n=::read(fd, buffer, MAX_IPC_SIZE); if (-1==n) { if (EINTR==errno) continue; - error(KIO::ERR_COULD_NOT_READ, url.toDisplayString()); + error(KIO::ERR_CANNOT_READ, url.toDisplayString()); ::close(fd); if(multiple) ::unlink(realPathC); return; } if (0==n) break; // Finished array=array.fromRawData(buffer, n); data(array); array.clear(); processed+=n; processedSize(processed); } data(QByteArray()); ::close(fd); processedSize(buff.st_size); finished(); } } if(multiple) ::unlink(realPathC); } else - error(KIO::ERR_COULD_NOT_READ, url.toDisplayString()); + error(KIO::ERR_CANNOT_READ, url.toDisplayString()); } void CKioFonts::copy(const QUrl &, const QUrl &, int, KIO::JobFlags) { error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot copy fonts")); } void CKioFonts::rename(const QUrl &, const QUrl &, KIO::JobFlags) { error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot move fonts")); } void CKioFonts::del(const QUrl &url, bool isFile) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder(getFolder(pathList)); QString name(removeKnownExtension(url)); if(!isFile) error(KIO::ERR_SLAVE_DEFINED, i18n("Only fonts may be deleted.")); else if(!Misc::root() && FOLDER_ROOT==folder) error(KIO::ERR_SLAVE_DEFINED, i18n("Can only remove fonts from either \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); else if(!name.isEmpty()) handleResp(itsInterface->uninstall(name, Misc::root() || FOLDER_SYS==folder), name); else error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } void CKioFonts::statFont(const QUrl &url) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder=getFolder(pathList); KIO::UDSEntry entry; bool ok=true; switch(pathList.count()) { case 0: createUDSEntry(entry, FOLDER_ROOT); break; case 1: if(Misc::root()) ok=createStatEntry(entry, url, FOLDER_SYS); else if(FOLDER_SYS==folder || FOLDER_USER==folder) createUDSEntry(entry, folder); else { error(KIO::ERR_SLAVE_DEFINED, i18n("Please specify \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); return; } break; default: ok=createStatEntry(entry, url, folder); } if(ok) { statEntry(entry); finished(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } } void CKioFonts::special(const QByteArray &a) { if(!a.isEmpty()) error(KIO::ERR_UNSUPPORTED_ACTION, i18n("No special methods supported.")); else { setTimeoutSpecialCommand(-1); itsInterface->reconfigure(); } } int CKioFonts::listFolder(KIO::UDSEntry &entry, EFolder folder) { KFI_DBUG << folder; int styleCount(0); KFI::Families families(itsInterface->list(FOLDER_SYS==folder)); FamilyCont::ConstIterator family(families.items.begin()), end(families.items.end()); KFI_DBUG << "Num families:" << families.items.count(); for(; family!=end; ++family) { StyleCont::ConstIterator styleIt((*family).styles().begin()), styleEnd((*family).styles().end()); styleCount+=(*family).styles().count(); for(; styleIt!=styleEnd; ++styleIt) { createUDSEntry(entry, folder, *family, *styleIt); listEntry(entry); } } totalSize(styleCount); return styleCount; } QString CKioFonts::getUserName(uid_t uid) { if (!itsUserCache.contains(uid)) { struct passwd *user = getpwuid(uid); if(user) itsUserCache.insert(uid, QString::fromLatin1(user->pw_name)); else return QString::number(uid); } return itsUserCache[uid]; } QString CKioFonts::getGroupName(gid_t gid) { if (!itsGroupCache.contains(gid)) { struct group *grp = getgrgid(gid); if(grp) itsGroupCache.insert(gid, QString::fromLatin1(grp->gr_name)); else return QString::number(gid); } return itsGroupCache[gid]; } bool CKioFonts::createStatEntry(KIO::UDSEntry &entry, const QUrl &url, EFolder folder) { Family fam(getFont(url, folder)); if(!fam.name().isEmpty() && 1==fam.styles().count()) { createUDSEntry(entry, folder, fam, *fam.styles().begin()); return true; } return false; } void CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder) { KFI_DBUG << QString(FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER)); entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, FOLDER_ROOT==folder || Misc::root() ? i18n("Fonts") : FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER)); entry.insert(KIO::UDSEntry::UDS_ACCESS, !Misc::root() && FOLDER_SYS==folder ? 0444 : 0744); entry.insert(KIO::UDSEntry::UDS_USER, Misc::root() || FOLDER_SYS==folder ? QString::fromLatin1("root") : getUserName(getuid())); entry.insert(KIO::UDSEntry::UDS_GROUP, Misc::root() || FOLDER_SYS==folder ? QString::fromLatin1("root") : getGroupName(getgid())); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); } bool CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder, const Family &family, const Style &style) { int size=0; QString name(FC::createName(family.name(), style.value())); FileCont::ConstIterator file(style.files().begin()), fileEnd(style.files().end()); QList files; bool hidden=true, haveExtraFiles=false; KFI_DBUG << name; for(; file!=fileEnd; ++file) { size+=getSize((*file).path()); // TODO: Make scalable a property of the file? // Then isScalable() is not needed!!! if(isScalable((*file).path())) files.prepend(*file); else files.append(*file); if(hidden && !Misc::isHidden(Misc::getFile((*file).path()))) hidden=false; QStringList assoc; Misc::getAssociatedFiles((*file).path(), assoc); QStringList::ConstIterator oit(assoc.constBegin()), oend(assoc.constEnd()); if(!haveExtraFiles && !assoc.isEmpty()) haveExtraFiles=true; for(; oit!=oend; ++oit) size+=getSize(*oit); } entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME, name); entry.insert(KIO::UDSEntry::UDS_SIZE, size); entry.insert(UDS_EXTRA_FC_STYLE, style.value()); QList::ConstIterator it(files.constBegin()), end(files.constEnd()); for(; it!=end; ++it) { QByteArray cPath(QFile::encodeName((*it).path())); QT_STATBUF buff; if(-1!=QT_LSTAT(cPath, &buff)) { QString fileName(Misc::getFile((*it).path())), mt; int dotPos(fileName.lastIndexOf('.')); QString extension(-1==dotPos ? QString() : fileName.mid(dotPos)); if(QString::fromLatin1(".gz")==extension) { dotPos=fileName.lastIndexOf('.', dotPos-1); extension=-1==dotPos ? QString() : fileName.mid(dotPos); } if(QString::fromLatin1(".ttf")==extension || QString::fromLatin1(".ttc")==extension) mt="application/x-font-ttf"; else if(QString::fromLatin1(".otf")==extension) mt="application/x-font-otf"; else if(QString::fromLatin1(".pfa")==extension || QString::fromLatin1(".pfb")==extension) mt="application/x-font-type1"; else if(QString::fromLatin1(".pcf.gz")==extension || QString::fromLatin1(".pcf")==extension) mt="application/x-font-pcf"; else if(QString::fromLatin1(".bdf.gz")==extension || QString::fromLatin1(".bdf")==extension) mt="application/x-font-bdf"; else { // File extension check failed, use QMimeDatabase to read contents... QMimeDatabase db; QMimeType mime = db.mimeTypeForFile((*it).path()); QStringList patterns = mime.globPatterns(); mt = mime.name(); if(patterns.size()>0) extension=(*patterns.begin()).remove("*"); } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode&S_IFMT); entry.insert(KIO::UDSEntry::UDS_ACCESS, buff.st_mode&07777); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); entry.insert(KIO::UDSEntry::UDS_USER, getUserName(buff.st_uid)); entry.insert(KIO::UDSEntry::UDS_GROUP, getGroupName(buff.st_gid)); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, mt); if(hidden) { entry.insert(KIO::UDSEntry::UDS_HIDDEN, 1); entry.insert(UDS_EXTRA_FILE_NAME, (*it).path()); entry.insert(UDS_EXTRA_FILE_FACE, (*it).path()); } QString path(QString::fromLatin1("/")); if(!Misc::root()) { path+=FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER); path+=QString::fromLatin1("/"); } path+=name; if(files.count()>1 || haveExtraFiles) path+=QString::fromLatin1(KFI_FONTS_PACKAGE); else path+=extension; QUrl url(QUrl::fromLocalFile(path)); url.setScheme(KFI_KIO_FONTS_PROTOCOL); entry.insert(KIO::UDSEntry::UDS_URL, url.url()); return true; } } return false; } Family CKioFonts::getFont(const QUrl &url, EFolder folder) { QString name(removeKnownExtension(url)); KFI_DBUG << url << name; return itsInterface->statFont(name, FOLDER_SYS==folder); } void CKioFonts::handleResp(int resp, const QString &file, const QString &tempFile, bool destIsSystem) { switch(resp) { case FontInst::STATUS_NO_SYS_CONNECTION: error(KIO::ERR_SLAVE_DEFINED, i18n("Failed to start the system daemon")); break; case FontInst::STATUS_SERVICE_DIED: error(KIO::ERR_SLAVE_DEFINED, i18n("Backend died")); break; case FontInst::STATUS_BITMAPS_DISABLED: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 is a bitmap font, and these have been disabled on your system.", file)); break; case FontInst::STATUS_ALREADY_INSTALLED: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 contains the font %2, which is already installed on your system.", file, FC::getName(tempFile))); break; case FontInst::STATUS_NOT_FONT_FILE: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 is not a font.", file)); break; case FontInst::STATUS_PARTIAL_DELETE: error(KIO::ERR_SLAVE_DEFINED, i18n("Could not remove all files associated with %1", file)); break; case KIO::ERR_FILE_ALREADY_EXIST: { QString name(Misc::modifyName(file)), destFolder(Misc::getDestFolder(itsInterface->folderName(destIsSystem), name)); error(KIO::ERR_SLAVE_DEFINED, i18n("%1 already exists.", destFolder+name)); break; } case FontInst::STATUS_OK: finished(); break; default: error(resp, file); } if(FontInst::STATUS_OK==resp) setTimeoutSpecialCommand(constReconfigTimeout); } } diff --git a/kcms/kfontinst/viewpart/FontViewPart.cpp b/kcms/kfontinst/viewpart/FontViewPart.cpp index d8ac65a70..f62a270d0 100644 --- a/kcms/kfontinst/viewpart/FontViewPart.cpp +++ b/kcms/kfontinst/viewpart/FontViewPart.cpp @@ -1,578 +1,577 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FontViewPart.h" #include "Misc.h" #include "KfiConstants.h" #include "FcEngine.h" #include "PreviewSelectAction.h" #include "FontInstInterface.h" #include "FontInst.h" #include "config-workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include "config-fontinst.h" // Enable the following to allow printing of non-installed fonts. Does not seem to work :-( //#define KFI_PRINT_APP_FONTS namespace KFI { static QString getFamily(const QString &font) { int commaPos=font.lastIndexOf(','); return -1==commaPos ? font : font.left(commaPos); } K_PLUGIN_FACTORY(CFontViewPartFactory, registerPlugin();) -K_EXPORT_PLUGIN(CFontViewPartFactory("kfontview")) CFontViewPart::CFontViewPart(QWidget *parentWidget, QObject *parent, const QList &) : KParts::ReadOnlyPart(parent), itsConfig(KSharedConfig::openConfig()), itsProc(nullptr), itsTempDir(nullptr), itsInterface(new FontInstInterface()), itsOpening(false) { // create browser extension (for printing when embedded into browser) itsExtension = new BrowserExtension(this); itsFrame=new QFrame(parentWidget); QFrame *previewFrame=new QFrame(itsFrame); QWidget *controls=new QWidget(itsFrame); // QGroupBox *metaBox=new QGroupBox(i18n("Information:"), controls); itsFaceWidget=new QWidget(controls); QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, itsFrame); QBoxLayout *previewLayout=new QBoxLayout(QBoxLayout::LeftToRight, previewFrame), *controlsLayout=new QBoxLayout(QBoxLayout::LeftToRight, controls), *faceLayout=new QBoxLayout(QBoxLayout::LeftToRight, itsFaceWidget); // QBoxLayout *metaLayout=new QBoxLayout(QBoxLayout::LeftToRight, metaBox); // itsMetaLabel=new QLabel(metaBox); // itsMetaLabel->setAlignment(Qt::AlignTop); // metaLayout->addWidget(itsMetaLabel); previewLayout->setContentsMargins(0, 0, 0, 0); previewLayout->setSpacing(0); faceLayout->setContentsMargins(0, 0, 0, 0); controlsLayout->setContentsMargins(0, 0, 0, 0); previewLayout->setSpacing(0); itsFrame->setFrameShape(QFrame::NoFrame); itsFrame->setFocusPolicy(Qt::ClickFocus); previewFrame->setFrameShape(QFrame::StyledPanel); previewFrame->setFrameShadow(QFrame::Sunken); KAboutData aboutData(KFI_NAME, i18n("FontViewPart"), WORKSPACE_VERSION_STRING); setComponentData(aboutData); itsPreview=new CFontPreview(previewFrame); itsPreview->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); itsFaceLabel=new QLabel(i18n("Show Face:"), itsFaceWidget); itsFaceSelector=new QSpinBox(itsFaceWidget); itsFaceSelector->setValue(1); itsInstallButton=new QPushButton(i18n("Install..."), controls); itsInstallButton->setEnabled(false); previewLayout->addWidget(itsPreview); faceLayout->addWidget(itsFaceLabel); faceLayout->addWidget(itsFaceSelector); faceLayout->addItem(new QSpacerItem(faceLayout->spacing(), 0, QSizePolicy::Fixed, QSizePolicy::Fixed)); itsFaceWidget->hide(); itsPreview->engine()->readConfig(*itsConfig); //controlsLayout->addWidget(metaBox); //controlsLayout->addStretch(2); controlsLayout->addWidget(itsFaceWidget); controlsLayout->addStretch(1); controlsLayout->addWidget(itsInstallButton); mainLayout->addWidget(previewFrame); mainLayout->addWidget(controls); connect(itsPreview, &CFontPreview::status, this, &CFontViewPart::previewStatus); connect(itsInstallButton, &QAbstractButton::clicked, this, &CFontViewPart::install); connect(itsFaceSelector, SIGNAL(valueChanged(int)), SLOT(showFace(int))); itsChangeTextAction=actionCollection()->addAction("changeText"); itsChangeTextAction->setIcon(QIcon::fromTheme("edit-rename")); itsChangeTextAction->setText(i18n("Change Text...")); connect(itsChangeTextAction, &QAction::triggered, this, &CFontViewPart::changeText); CPreviewSelectAction *displayTypeAction=new CPreviewSelectAction(this, CPreviewSelectAction::BlocksAndScripts); actionCollection()->addAction("displayType", displayTypeAction); connect(displayTypeAction, &CPreviewSelectAction::range, this, &CFontViewPart::displayType); QAction *zoomIn=actionCollection()->addAction(KStandardAction::ZoomIn, itsPreview, SLOT(zoomIn())), *zoomOut=actionCollection()->addAction(KStandardAction::ZoomOut, itsPreview, SLOT(zoomOut())); connect(itsPreview, &CFontPreview::atMax, zoomIn, &QAction::setDisabled); connect(itsPreview, &CFontPreview::atMin, zoomOut, &QAction::setDisabled); setXMLFile("kfontviewpart.rc"); setWidget(itsFrame); itsExtension->enablePrint(false); FontInst::registerTypes(); connect(itsInterface, &OrgKdeFontinstInterface::status, this, &CFontViewPart::dbusStatus); connect(itsInterface, &OrgKdeFontinstInterface::fontStat, this, &CFontViewPart::fontStat); } CFontViewPart::~CFontViewPart() { delete itsTempDir; itsTempDir=nullptr; delete itsInterface; itsInterface=nullptr; } static inline QUrl mostLocalUrl(const QUrl &url, QWidget *widget) { auto job = KIO::mostLocalUrl(url); KJobWidgets::setWindow(job, widget); job->exec(); return job->mostLocalUrl(); } bool CFontViewPart::openUrl(const QUrl &url) { if (!url.isValid() || !closeUrl()) return false; // itsMetaLabel->setText(QString()); // itsMetaInfo.clear(); itsFontDetails=FC::decode(url); if(!itsFontDetails.family.isEmpty() || KFI_KIO_FONTS_PROTOCOL==url.scheme() || mostLocalUrl(url, itsFrame).isLocalFile()) { setUrl(url); emit started(nullptr); setLocalFilePath(this->url().path()); bool ret=openFile(); if (ret) emit completed(); return ret; } else return ReadOnlyPart::openUrl(url); } bool CFontViewPart::openFile() { // NOTE: Can't do the real open here, as we don't seem to be able to use KIO::NetAccess functions // during initial start-up. Bug report 111535 indicates that calling "konqueror " crashes. itsInstallButton->setEnabled(false); QTimer::singleShot(0, this, &CFontViewPart::timeout); return true; } static inline bool statUrl(const QUrl &url, KIO::UDSEntry *udsEntry) { auto job = KIO::stat(url); job->exec(); if (job->error()) return false; *udsEntry = job->statResult(); return true; } void CFontViewPart::timeout() { if(!itsInstallButton) return; bool isFonts(KFI_KIO_FONTS_PROTOCOL==url().scheme()), showFs(false), package(false); int fileIndex(-1); QString fontFile; // itsMetaUrl=url(); delete itsTempDir; itsTempDir=nullptr; itsOpening=true; if(!itsFontDetails.family.isEmpty()) { emit setWindowCaption(FC::createName(itsFontDetails.family, itsFontDetails.styleInfo)); fontFile=FC::getFile(url()); fileIndex=FC::getIndex(url()); } else if(isFonts) { KIO::UDSEntry udsEntry; bool found = statUrl(url(), &udsEntry); if(!found) { // Check if url is "fonts:/ if so try fonts:/System/, then fonts:/Personal QStringList pathList(url().adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); if(pathList.count()==1) { found = statUrl(QUrl(QString("fonts:/"+i18n(KFI_KIO_FONTS_SYS)+QLatin1Char('/')+pathList[0])), &udsEntry); if (!found) found = statUrl(QUrl(QString("fonts:/"+i18n(KFI_KIO_FONTS_USER)+QLatin1Char('/')+pathList[0])), &udsEntry); } } if(found) { if(udsEntry.numberValue(KIO::UDSEntry::UDS_HIDDEN, 0)) { fontFile=udsEntry.stringValue(UDS_EXTRA_FILE_NAME); fileIndex=udsEntry.numberValue(UDS_EXTRA_FILE_FACE, 0); } itsFontDetails.family=getFamily(udsEntry.stringValue(KIO::UDSEntry::UDS_NAME)); itsFontDetails.styleInfo=udsEntry.numberValue(UDS_EXTRA_FC_STYLE); emit setWindowCaption(udsEntry.stringValue(KIO::UDSEntry::UDS_NAME)); } else { previewStatus(false); return; } } else { QString path(localFilePath()); // Is this a application/vnd.kde.fontspackage file? If so, extract 1 scalable font... if((package=Misc::isPackage(path))) { KZip zip(path); if(zip.open(QIODevice::ReadOnly)) { const KArchiveDirectory *zipDir=zip.directory(); if(zipDir) { QStringList fonts(zipDir->entries()); if(!fonts.isEmpty()) { QStringList::ConstIterator it(fonts.begin()), end(fonts.end()); for(; it!=end; ++it) { const KArchiveEntry *entry=zipDir->entry(*it); if(entry && entry->isFile()) { delete itsTempDir; itsTempDir=new QTemporaryDir(QDir::tempPath() + "/" KFI_TMP_DIR_PREFIX); itsTempDir->setAutoRemove(true); ((KArchiveFile *)entry)->copyTo(itsTempDir->path()); QMimeDatabase db; QString mime(db.mimeTypeForFile(itsTempDir->filePath(entry->name())).name()); if(mime=="font/ttf" || mime=="font/otf" || mime=="application/x-font-ttf" || mime=="application/x-font-otf" || mime=="application/x-font-type1") { fontFile=itsTempDir->filePath(entry->name()); //setLocalFilePath(itsTempDir->path()+QLatin1Char('/')+entry->name()); // itsMetaUrl=QUrl::fromLocalFile(localFilePath()); break; } else ::unlink(QFile::encodeName(itsTempDir->filePath(entry->name())).data()); } } } } } } } itsInstallButton->setEnabled(false); if(itsFontDetails.family.isEmpty()) emit setWindowCaption(url().toDisplayString()); else FcInitReinitialize(); itsPreview->showFont(!package && itsFontDetails.family.isEmpty() ? localFilePath() : fontFile.isEmpty() ? itsFontDetails.family : fontFile, itsFontDetails.styleInfo, fileIndex); if(!isFonts && itsPreview->engine()->getNumIndexes()>1) { showFs=true; itsFaceSelector->setRange(1, itsPreview->engine()->getNumIndexes()); itsFaceSelector->setSingleStep(1); itsFaceSelector->blockSignals(true); itsFaceSelector->setValue(1); itsFaceSelector->blockSignals(false); } itsFaceWidget->setVisible(showFs); } void CFontViewPart::previewStatus(bool st) { if(itsOpening) { bool printable(false); if(st) { checkInstallable(); if(Misc::app(KFI_PRINTER).isEmpty()) printable=false; if(KFI_KIO_FONTS_PROTOCOL==url().scheme()) printable=!Misc::isHidden(url()); else if(!FC::decode(url()).family.isEmpty()) printable=!Misc::isHidden(FC::getFile(url())); #ifdef KFI_PRINT_APP_FONTS else { // TODO: Make this work! Plus, printing of disabled TTF/OTF's should also be possible! KMimeType::Ptr mime=KMimeType::findByUrl(QUrl::fromLocalFile(localFilePath()), 0, false, true); printable=mime->is("application/x-font-ttf") || mime->is("application/x-font-otf"); } #endif } itsExtension->enablePrint(st && printable); itsOpening=false; } itsChangeTextAction->setEnabled(st); // if(st) // getMetaInfo(itsFaceSelector->isVisible() && itsFaceSelector->value()>0 // ? itsFaceSelector->value()-1 : 0); // else if(!st) KMessageBox::error(itsFrame, i18n("Could not read font.")); } void CFontViewPart::install() { if(!itsProc || QProcess::NotRunning==itsProc->state()) { QStringList args; if(!itsProc) itsProc=new QProcess(this); else itsProc->kill(); QString title = QGuiApplication::applicationDisplayName(); if (title.isEmpty()) title = QCoreApplication::applicationName(); args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << url().toDisplayString(); connect(itsProc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(installlStatus())); itsProc->start(Misc::app(KFI_INSTALLER), args); itsInstallButton->setEnabled(false); } } void CFontViewPart::installlStatus() { checkInstallable(); } void CFontViewPart::dbusStatus(int pid, int status) { if(pid==getpid() && FontInst::STATUS_OK!=status) itsInstallButton->setEnabled(false); } void CFontViewPart::fontStat(int pid, const KFI::Family &font) { if(pid==getpid()) itsInstallButton->setEnabled(!Misc::app(KFI_INSTALLER).isEmpty() && font.styles().count()==0); } void CFontViewPart::changeText() { bool status; QString oldStr(itsPreview->engine()->getPreviewString()), newStr(QInputDialog::getText(itsFrame, i18n("Preview String"), i18n("Please enter new string:"), QLineEdit::Normal, oldStr, &status)); if(status && newStr!=oldStr) { itsPreview->engine()->setPreviewString(newStr); itsPreview->engine()->writeConfig(*itsConfig); itsPreview->showFont(); } } void CFontViewPart::print() { QStringList args; QString title = QGuiApplication::applicationDisplayName(); if (title.isEmpty()) title = QCoreApplication::applicationName(); if(!itsFontDetails.family.isEmpty()) { args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << "--size" << "0" << "--pfont" << QString(itsFontDetails.family+','+QString().setNum(itsFontDetails.styleInfo)); } #ifdef KFI_PRINT_APP_FONTS else args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << "--size " << "0" << localFilePath() << QString().setNum(KFI_NO_STYLE_INFO); #endif if(!args.isEmpty()) QProcess::startDetached(Misc::app(KFI_PRINTER), args); } void CFontViewPart::displayType(const QList &range) { itsPreview->setUnicodeRange(range); itsChangeTextAction->setEnabled(0==range.count()); } void CFontViewPart::showFace(int face) { itsPreview->showFace(face-1); } void CFontViewPart::checkInstallable() { if(itsFontDetails.family.isEmpty()) { if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(OrgKdeFontinstInterface::staticInterfaceName())) QProcess::startDetached(QLatin1String(KFONTINST_LIB_EXEC_DIR"/fontinst"), QStringList()); itsInstallButton->setEnabled(false); itsInterface->statFont(itsPreview->engine()->descriptiveName(), FontInst::SYS_MASK|FontInst::USR_MASK, getpid()); } } #if 0 void CFontViewPart::getMetaInfo(int face) { if(itsMetaInfo[face].isEmpty()) { // Pass as much inofmration as possible to analyzer... if(KFI_KIO_FONTS_PROTOCOL!=itsMetaUrl.protocol()) { itsMetaUrl.removeQueryItem(KFI_KIO_FACE); if(face>0) itsMetaUrl.addQueryItem(KFI_KIO_FACE, QString().setNum(face)); } KFileMetaInfo meta(itsMetaUrl); if(meta.isValid() && meta.keys().count()) { QStringList keys(meta.keys()); QStringList::const_iterator it(keys.begin()), end(keys.end()); itsMetaInfo[face]=""; for(; it!=end; ++it) { KFileMetaInfoItem mi(meta.item(*it)); itsMetaInfo[face]+=""; } itsMetaInfo[face]+="
"+mi.name()+"
"+ mi.value().toString()+"
"; itsMetaLabel->setText(itsMetaInfo[face]); } else itsMetaLabel->setText(i18n("

No information

")); } else itsMetaLabel->setText(itsMetaInfo[face]); } #endif BrowserExtension::BrowserExtension(CFontViewPart *parent) : KParts::BrowserExtension(parent) { setURLDropHandlingEnabled(true); } void BrowserExtension::enablePrint(bool enable) { if(enable!=isActionEnabled("print") && (!enable || !Misc::app(KFI_PRINTER).isEmpty())) emit enableAction("print", enable); } void BrowserExtension::print() { if(!Misc::app(KFI_PRINTER).isEmpty()) static_cast(parent())->print(); } } #include "FontViewPart.moc" diff --git a/kcms/standard_actions/standard_actions_module.cpp b/kcms/standard_actions/standard_actions_module.cpp index 6ecdce5be..e325b6afe 100644 --- a/kcms/standard_actions/standard_actions_module.cpp +++ b/kcms/standard_actions/standard_actions_module.cpp @@ -1,163 +1,162 @@ /* * Copyright 2008 Michael Jansen * * 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) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "standard_actions_module.h" #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(StandardActionsModuleFactory, registerPlugin();) -K_EXPORT_PLUGIN(StandardActionsModuleFactory("kcm_standard_actions")) static void dressUpAction(QAction *action, KStandardShortcut::StandardShortcut shortcutId) { // Remember the shortcutId so we know where to save changes. action->setData(shortcutId); // We have to manually adjust the action. We want to show the // hardcoded default and the user set shortcut. But action currently // only contain the active shortcuts as default shortcut. So we // have to fill it correctly const auto hardcoded = KStandardShortcut::hardcodedDefaultShortcut(shortcutId); const auto active = KStandardShortcut::shortcut(shortcutId); // Set the hardcoded default shortcut as default shortcut action->setProperty("defaultShortcuts", QVariant::fromValue(hardcoded)); action->setShortcuts(active); } StandardActionsModule::StandardActionsModule( QWidget *parent, const QVariantList &args ) : KCModule(parent, args ) ,m_editor(nullptr) ,m_actionCollection(nullptr) { KAboutData *about = new KAboutData(QStringLiteral("kcm_standard_actions"), i18n("Standard Shortcuts"), QStringLiteral("0.1"), QString(), KAboutLicense::GPL); setAboutData(about); // Configure the KCM KCModule::setButtons(KCModule::Buttons(KCModule::Default | KCModule::Apply | KCModule::Help)); // Create and configure the editor m_editor = new KShortcutsEditor(this, KShortcutsEditor::WidgetAction | KShortcutsEditor::WindowAction | KShortcutsEditor::ApplicationAction); // there will be no global actions, so make sure that column is hidden connect(m_editor, &KShortcutsEditor::keyChange, this, &StandardActionsModule::keyChanged); // Make a layout QVBoxLayout *global = new QVBoxLayout; global->addWidget(m_editor); setLayout(global); } StandardActionsModule::~StandardActionsModule() {} void StandardActionsModule::defaults() { m_editor->allDefault(); } void StandardActionsModule::keyChanged() { emit changed(true); } void StandardActionsModule::load() { // Create a collection to handle the shortcuts m_actionCollection = new KActionCollection( this, QStringLiteral("kcm_standard_actions")); // Keeps track of which shortcut IDs have been added QSet shortcutIdsAdded; // Put all shortcuts for standard actions into the collection Q_FOREACH(KStandardAction::StandardAction id, KStandardAction::actionIds()) { KStandardShortcut::StandardShortcut shortcutId = KStandardAction::shortcutForActionId(id); // If the StandardShortcutId is AccelNone skip configuration for this // action. if (shortcutId == KStandardShortcut::AccelNone || shortcutIdsAdded.contains(shortcutId)) { continue; } // Create the action QAction *action = KStandardAction::create(id, nullptr, nullptr, m_actionCollection); dressUpAction(action, shortcutId); shortcutIdsAdded << shortcutId; } // Put in the remaining standard shortcuts too... for(int i = int(KStandardShortcut::AccelNone) + 1; i < KStandardShortcut::StandardShortcutCount; ++i) { KStandardShortcut::StandardShortcut shortcutId = static_cast(i); if(!shortcutIdsAdded.contains(shortcutId)) { QAction *action = new QAction(KStandardShortcut::label(shortcutId), this); action->setWhatsThis(KStandardShortcut::whatsThis(shortcutId)); dressUpAction(action, shortcutId); m_actionCollection->addAction(KStandardShortcut::name(shortcutId), action); } } // Hand the collection to the editor m_editor->addCollection(m_actionCollection, i18n("Standard Shortcuts")); } void StandardActionsModule::save() { m_editor->commit(); Q_FOREACH(QAction* action, m_actionCollection->actions()) { KStandardShortcut::saveShortcut( static_cast(action->data().toInt()) , action->shortcuts()); } KSharedConfig::openConfig()->sync(); KConfigGroup cg(KSharedConfig::openConfig(), "Shortcuts"); cg.sync(); QString title = i18n("Standard Actions successfully saved"); QString message = i18n( "The changes have been saved. Please note that:" "
  • Applications need to be restarted to see the changes.
  • " "
  • This change could introduce shortcut conflicts in some applications.
  • " "
" ); KMessageBox::information(this, message, title, QStringLiteral("shortcuts_saved_info")); } #include "standard_actions_module.moc"