File indexing completed on 2024-05-05 04:19:15
0001 // vim: set tabstop=4 shiftwidth=4 expandtab: 0002 /* 0003 Gwenview: an image viewer 0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License 0008 as published by the Free Software Foundation; either version 2 0009 of the License, or (at your option) any later version. 0010 0011 This program is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 GNU General Public License for more details. 0015 0016 You should have received a copy of the GNU General Public License 0017 along with this program; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0019 0020 */ 0021 // Self 0022 #include "fileopscontextmanageritem.h" 0023 0024 // Qt 0025 #include <QAction> 0026 #include <QApplication> 0027 #include <QClipboard> 0028 #include <QListView> 0029 #include <QMenu> 0030 #include <QShortcut> 0031 0032 // KF 0033 #include <KActionCategory> 0034 #include <KActionCollection> 0035 #include <KFileItem> 0036 #include <KFileItemActions> 0037 #include <KFileItemListProperties> 0038 #include <KIO/ApplicationLauncherJob> 0039 #include <KIO/JobUiDelegate> 0040 #include <KIO/JobUiDelegateFactory> 0041 #include <KIO/OpenFileManagerWindowJob> 0042 #include <KIO/Paste> 0043 #include <KIO/PasteJob> 0044 #include <KIO/RestoreJob> 0045 #include <KJobWidgets> 0046 #include <KLocalizedString> 0047 #include <KOpenWithDialog> 0048 #include <KPropertiesDialog> 0049 #include <KUrlMimeData> 0050 #include <KXMLGUIClient> 0051 0052 // Local 0053 #include "fileoperations.h" 0054 #include "sidebar.h" 0055 #include <lib/contextmanager.h> 0056 #include <lib/eventwatcher.h> 0057 #include <lib/gvdebug.h> 0058 #include <lib/mimetypeutils.h> 0059 0060 namespace Gwenview 0061 { 0062 QList<QUrl> FileOpsContextManagerItem::urlList() const 0063 { 0064 return contextManager()->selectedFileItemList().urlList(); 0065 } 0066 0067 void FileOpsContextManagerItem::updateServiceList() 0068 { 0069 // This code is inspired from 0070 // kdebase/apps/lib/konq/konq_menuactions.cpp 0071 0072 // Get list of all distinct mimetypes in selection 0073 QStringList mimeTypes; 0074 const auto selectedFileItemList = contextManager()->selectedFileItemList(); 0075 for (const KFileItem &item : selectedFileItemList) { 0076 const QString mimeType = item.mimetype(); 0077 if (!mimeTypes.contains(mimeType)) { 0078 mimeTypes << mimeType; 0079 } 0080 } 0081 0082 mServiceList = KFileItemActions::associatedApplications(mimeTypes); 0083 } 0084 0085 QMimeData *FileOpsContextManagerItem::selectionMimeData() 0086 { 0087 KFileItemList selectedFiles; 0088 0089 // In Compare mode, restrict the returned mimedata to the focused image 0090 if (!mThumbnailView->isVisible()) { 0091 selectedFiles << KFileItem(contextManager()->currentUrl()); 0092 } else { 0093 selectedFiles = contextManager()->selectedFileItemList(); 0094 } 0095 0096 return MimeTypeUtils::selectionMimeData(selectedFiles, MimeTypeUtils::ClipboardTarget); 0097 } 0098 0099 QUrl FileOpsContextManagerItem::pasteTargetUrl() const 0100 { 0101 // If only one folder is selected, paste inside it, otherwise paste in 0102 // current 0103 const KFileItemList list = contextManager()->selectedFileItemList(); 0104 if (list.count() == 1 && list.first().isDir()) { 0105 return list.first().url(); 0106 } else { 0107 return contextManager()->currentDirUrl(); 0108 } 0109 } 0110 0111 static QAction *createSeparator(QObject *parent) 0112 { 0113 auto action = new QAction(parent); 0114 action->setSeparator(true); 0115 return action; 0116 } 0117 0118 FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager *manager, 0119 QListView *thumbnailView, 0120 KActionCollection *actionCollection, 0121 KXMLGUIClient *client) 0122 : AbstractContextManagerItem(manager) 0123 { 0124 mThumbnailView = thumbnailView; 0125 mXMLGUIClient = client; 0126 mGroup = new SideBarGroup(i18n("File Operations")); 0127 setWidget(mGroup); 0128 EventWatcher::install(mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); 0129 0130 mInTrash = false; 0131 mNewFileMenu = new KNewFileMenu(this); 0132 0133 connect(contextManager(), &ContextManager::selectionChanged, this, &FileOpsContextManagerItem::updateActions); 0134 connect(contextManager(), &ContextManager::currentDirUrlChanged, this, &FileOpsContextManagerItem::updateActions); 0135 0136 auto file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); 0137 auto edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection); 0138 0139 mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut())); 0140 mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy())); 0141 mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste())); 0142 0143 mCopyToAction = file->addAction(QStringLiteral("file_copy_to"), this, SLOT(copyTo())); 0144 mCopyToAction->setText(i18nc("Verb", "Copy To...")); 0145 mCopyToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0146 actionCollection->setDefaultShortcut(mCopyToAction, Qt::Key_F7); 0147 0148 mMoveToAction = file->addAction(QStringLiteral("file_move_to"), this, SLOT(moveTo())); 0149 mMoveToAction->setText(i18nc("Verb", "Move To...")); 0150 mMoveToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump")))); 0151 actionCollection->setDefaultShortcut(mMoveToAction, Qt::Key_F8); 0152 0153 mLinkToAction = file->addAction(QStringLiteral("file_link_to"), this, SLOT(linkTo())); 0154 mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To...")); 0155 mLinkToAction->setIcon(QIcon::fromTheme(QStringLiteral("link"))); 0156 actionCollection->setDefaultShortcut(mLinkToAction, Qt::Key_F9); 0157 0158 mRenameAction = file->addAction(QStringLiteral("file_rename"), this, SLOT(rename())); 0159 mRenameAction->setText(i18nc("Verb", "Rename...")); 0160 mRenameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); 0161 actionCollection->setDefaultShortcut(mRenameAction, Qt::Key_F2); 0162 0163 mTrashAction = file->addAction(QStringLiteral("file_trash"), this, SLOT(trash())); 0164 mTrashAction->setText(i18nc("Verb", "Trash")); 0165 mTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); 0166 actionCollection->setDefaultShortcut(mTrashAction, Qt::Key_Delete); 0167 0168 mDelAction = file->addAction(KStandardAction::DeleteFile, this, SLOT(del())); 0169 0170 mRestoreAction = file->addAction(QStringLiteral("file_restore"), this, SLOT(restore())); 0171 mRestoreAction->setText(i18n("Restore")); 0172 mRestoreAction->setIcon(QIcon::fromTheme(QStringLiteral("restoration"))); 0173 0174 mShowPropertiesAction = file->addAction(QStringLiteral("file_show_properties"), this, SLOT(showProperties())); 0175 mShowPropertiesAction->setText(i18n("Properties")); 0176 mShowPropertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); 0177 actionCollection->setDefaultShortcut(mShowPropertiesAction, QKeySequence(Qt::ALT | Qt::Key_Return)); 0178 0179 mCreateFolderAction = file->addAction(QStringLiteral("file_create_folder"), this, SLOT(createFolder())); 0180 mCreateFolderAction->setText(i18n("Create Folder...")); 0181 mCreateFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); 0182 0183 mOpenInNewWindowAction = file->addAction(QStringLiteral("file_open_in_new_window")); 0184 mOpenInNewWindowAction->setText(i18nc("@action:inmenu", "Open in New Window")); 0185 mOpenInNewWindowAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); 0186 connect(mOpenInNewWindowAction, &QAction::triggered, this, &FileOpsContextManagerItem::openInNewWindow); 0187 0188 mOpenWithAction = file->addAction(QStringLiteral("file_open_with")); 0189 mOpenWithAction->setText(i18n("Open With")); 0190 mOpenWithAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); 0191 auto menu = new QMenu; 0192 mOpenWithAction->setMenu(menu); 0193 connect(menu, &QMenu::aboutToShow, this, &FileOpsContextManagerItem::populateOpenMenu); 0194 connect(menu, &QMenu::triggered, this, &FileOpsContextManagerItem::openWith); 0195 0196 mOpenContainingFolderAction = file->addAction(QStringLiteral("file_open_containing_folder"), this, SLOT(openContainingFolder())); 0197 mOpenContainingFolderAction->setText(i18n("Open Containing Folder")); 0198 mOpenContainingFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); 0199 0200 mRegularFileActionList << mRenameAction << mTrashAction << mDelAction << createSeparator(this) << mCopyToAction << mMoveToAction << mLinkToAction 0201 << createSeparator(this) << mOpenInNewWindowAction << mOpenWithAction << mOpenContainingFolderAction << mShowPropertiesAction 0202 << createSeparator(this) << mCreateFolderAction; 0203 0204 mTrashFileActionList << mRestoreAction << mDelAction << createSeparator(this) << mShowPropertiesAction; 0205 0206 connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FileOpsContextManagerItem::updatePasteAction); 0207 0208 updatePasteAction(); 0209 // Delay action update because it must happen *after* main window has called 0210 // createGUI(), otherwise calling mXMLGUIClient->plugActionList() will 0211 // fail. 0212 QMetaObject::invokeMethod(this, &FileOpsContextManagerItem::updateActions, Qt::QueuedConnection); 0213 } 0214 0215 FileOpsContextManagerItem::~FileOpsContextManagerItem() 0216 { 0217 delete mOpenWithAction->menu(); 0218 } 0219 0220 void FileOpsContextManagerItem::updateActions() 0221 { 0222 const int count = contextManager()->selectedFileItemList().count(); 0223 const bool selectionNotEmpty = count > 0; 0224 const bool urlIsValid = contextManager()->currentUrl().isValid(); 0225 const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid(); 0226 0227 mInTrash = contextManager()->currentDirUrl().scheme() == QLatin1String("trash"); 0228 0229 mCutAction->setEnabled(selectionNotEmpty); 0230 mCopyAction->setEnabled(selectionNotEmpty); 0231 mCopyToAction->setEnabled(selectionNotEmpty); 0232 mMoveToAction->setEnabled(selectionNotEmpty); 0233 mLinkToAction->setEnabled(selectionNotEmpty); 0234 mTrashAction->setEnabled(selectionNotEmpty); 0235 mRestoreAction->setEnabled(selectionNotEmpty); 0236 mDelAction->setEnabled(selectionNotEmpty); 0237 mOpenInNewWindowAction->setEnabled(selectionNotEmpty); 0238 mOpenWithAction->setEnabled(selectionNotEmpty); 0239 mRenameAction->setEnabled(count == 1); 0240 mOpenContainingFolderAction->setEnabled(selectionNotEmpty); 0241 0242 mCreateFolderAction->setEnabled(dirUrlIsValid); 0243 mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid); 0244 0245 mXMLGUIClient->unplugActionList(QStringLiteral("file_action_list")); 0246 QList<QAction *> &list = mInTrash ? mTrashFileActionList : mRegularFileActionList; 0247 mXMLGUIClient->plugActionList(QStringLiteral("file_action_list"), list); 0248 0249 updateSideBarContent(); 0250 } 0251 0252 void FileOpsContextManagerItem::updatePasteAction() 0253 { 0254 const QMimeData *mimeData = QApplication::clipboard()->mimeData(); 0255 bool enable; 0256 KFileItem destItem(pasteTargetUrl()); 0257 const QString text = KIO::pasteActionText(mimeData, &enable, destItem); 0258 mPasteAction->setEnabled(enable); 0259 mPasteAction->setText(text); 0260 } 0261 0262 void FileOpsContextManagerItem::updateSideBarContent() 0263 { 0264 if (!mGroup->isVisible()) { 0265 return; 0266 } 0267 0268 mGroup->clear(); 0269 0270 // Some actions we want to exist in a general sense so they're accessible 0271 // via the menu structure and with keyboard shortcuts, but we don't want 0272 // them to appear in the sidebar because they're too dangerous, little-used, 0273 // and/or contribute to the main window being too tall on short screens; see 0274 // https://bugs.kde.org/458987. 0275 const QList<QAction *> itemsToOmit = {mDelAction, mCreateFolderAction, mOpenInNewWindowAction}; 0276 0277 QList<QAction *> &list = mInTrash ? mTrashFileActionList : mRegularFileActionList; 0278 for (QAction *action : qAsConst(list)) { 0279 if (action->isEnabled() && !action->isSeparator() && !itemsToOmit.contains(action)) { 0280 mGroup->addAction(action); 0281 } 0282 } 0283 } 0284 0285 void FileOpsContextManagerItem::showProperties() 0286 { 0287 const KFileItemList list = contextManager()->selectedFileItemList(); 0288 if (!list.isEmpty()) { 0289 KPropertiesDialog::showDialog(list, mGroup); 0290 } else { 0291 const QUrl url = contextManager()->currentDirUrl(); 0292 KPropertiesDialog::showDialog(url, mGroup); 0293 } 0294 } 0295 0296 void FileOpsContextManagerItem::cut() 0297 { 0298 QMimeData *mimeData = selectionMimeData(); 0299 KIO::setClipboardDataCut(mimeData, true); 0300 QApplication::clipboard()->setMimeData(mimeData); 0301 } 0302 0303 void FileOpsContextManagerItem::copy() 0304 { 0305 QMimeData *mimeData = selectionMimeData(); 0306 KIO::setClipboardDataCut(mimeData, false); 0307 QApplication::clipboard()->setMimeData(mimeData); 0308 } 0309 0310 void FileOpsContextManagerItem::paste() 0311 { 0312 KIO::Job *job = KIO::paste(QApplication::clipboard()->mimeData(), pasteTargetUrl()); 0313 KJobWidgets::setWindow(job, mGroup); 0314 } 0315 0316 void FileOpsContextManagerItem::trash() 0317 { 0318 FileOperations::trash(urlList(), mGroup); 0319 } 0320 0321 void FileOpsContextManagerItem::del() 0322 { 0323 FileOperations::del(urlList(), mGroup); 0324 } 0325 0326 void FileOpsContextManagerItem::restore() 0327 { 0328 KIO::RestoreJob *job = KIO::restoreFromTrash(urlList()); 0329 KJobWidgets::setWindow(job, mGroup); 0330 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 0331 } 0332 0333 void FileOpsContextManagerItem::copyTo() 0334 { 0335 FileOperations::copyTo(urlList(), widget(), contextManager()); 0336 } 0337 0338 void FileOpsContextManagerItem::moveTo() 0339 { 0340 FileOperations::moveTo(urlList(), widget(), contextManager()); 0341 } 0342 0343 void FileOpsContextManagerItem::linkTo() 0344 { 0345 FileOperations::linkTo(urlList(), widget(), contextManager()); 0346 } 0347 0348 void FileOpsContextManagerItem::rename() 0349 { 0350 if (mThumbnailView->isVisible()) { 0351 QModelIndex index = mThumbnailView->currentIndex(); 0352 mThumbnailView->edit(index); 0353 } else { 0354 FileOperations::rename(urlList().constFirst(), mGroup, contextManager()); 0355 contextManager()->slotSelectionChanged(); 0356 } 0357 } 0358 0359 void FileOpsContextManagerItem::createFolder() 0360 { 0361 const QUrl url = contextManager()->currentDirUrl(); 0362 mNewFileMenu->setParentWidget(mGroup); 0363 mNewFileMenu->setWorkingDirectory(url); 0364 mNewFileMenu->createDirectory(); 0365 } 0366 0367 void FileOpsContextManagerItem::populateOpenMenu() 0368 { 0369 QMenu *openMenu = mOpenWithAction->menu(); 0370 qDeleteAll(openMenu->actions()); 0371 0372 updateServiceList(); 0373 0374 int idx = -1; 0375 for (const KService::Ptr &service : qAsConst(mServiceList)) { 0376 ++idx; 0377 if (service->name() == QLatin1String("Gwenview")) { 0378 continue; 0379 } 0380 QString text = service->name().replace('&', "&&"); 0381 QAction *action = openMenu->addAction(text); 0382 action->setIcon(QIcon::fromTheme(service->icon())); 0383 action->setData(idx); 0384 } 0385 0386 openMenu->addSeparator(); 0387 QAction *action = openMenu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("Other Application...")); 0388 action->setData(-1); 0389 } 0390 0391 void FileOpsContextManagerItem::openInNewWindow() 0392 { 0393 const QList<QUrl> urls = urlList(); 0394 if (urls.isEmpty()) { 0395 return; 0396 } 0397 0398 KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.gwenview")); 0399 if (!service) { 0400 return; 0401 } 0402 0403 auto job = new KIO::ApplicationLauncherJob(service); 0404 job->setUrls(urls); 0405 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); 0406 job->start(); 0407 } 0408 0409 void FileOpsContextManagerItem::openWith(QAction *action) 0410 { 0411 Q_ASSERT(action); 0412 KService::Ptr service; 0413 const QList<QUrl> list = urlList(); 0414 0415 bool ok; 0416 int idx = action->data().toInt(&ok); 0417 GV_RETURN_IF_FAIL(ok); 0418 if (idx != -1) { 0419 service = mServiceList.at(idx); 0420 } 0421 // If service is null, ApplicationLauncherJob will invoke the open-with dialog 0422 auto job = new KIO::ApplicationLauncherJob(service); 0423 job->setUrls(list); 0424 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup)); 0425 job->start(); 0426 } 0427 0428 void FileOpsContextManagerItem::openContainingFolder() 0429 { 0430 KIO::highlightInFileManager(urlList()); 0431 } 0432 0433 } // namespace 0434 0435 #include "moc_fileopscontextmanageritem.cpp"