fileopscontextmanageritem.cpp 14.9 KB
Newer Older
1
// vim: set tabstop=4 shiftwidth=4 expandtab:
2
3
/*
Gwenview: an image viewer
Aurélien Gâteau's avatar
Aurélien Gâteau committed
4
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
5
6
7
8
9
10
11
12
13
14
15
16
17

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
Dirk Mueller's avatar
Dirk Mueller committed
18
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20
21
22
23
24

*/
// Self
#include "fileopscontextmanageritem.h"

// Qt
25
#include <QAction>
26
27
#include <QApplication>
#include <QClipboard>
28
#include <QListView>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
29
#include <QMenu>
David Edmundson's avatar
David Edmundson committed
30
#include <QShortcut>
31

32
// KF
33
#include <kio_version.h>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
34
35
36
#include <KActionCollection>
#include <KActionCategory>
#include <KFileItem>
37
#include <KFileItemActions>
David Faure's avatar
David Faure committed
38
#include <KIO/Paste>
39
#include <KIO/PasteJob>
40
41
#include <KIO/RestoreJob>
#include <KIO/JobUiDelegate>
42
#include <KIOCore/KFileItemListProperties>
43
#include <KJobWidgets>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
44
#include <KLocalizedString>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
45
46
47
#include <KOpenWithDialog>
#include <KPropertiesDialog>
#include <KXMLGUIClient>
48
#include <KUrlMimeData>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
49
#include <KFileItemListProperties>
50
#include <KIO/OpenFileManagerWindowJob>
51
52
53
54
55
56
#if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
#include <KIO/ApplicationLauncherJob>
#include <KIO/JobUiDelegate>
#else
#include <KRun>
#endif
57

58
// Local
59
#include "dialogguard.h"
60
#include <lib/contextmanager.h>
61
#include <lib/eventwatcher.h>
62
#include <lib/gvdebug.h>
63
#include <lib/mimetypeutils.h>
64
#include "fileoperations.h"
65
66
#include "sidebar.h"

67
68
namespace Gwenview
{
69

70
71

QList<QUrl> FileOpsContextManagerItem::urlList() const
Aurélien Gâteau's avatar
Aurélien Gâteau committed
72
{
73
    return contextManager()->selectedFileItemList().urlList();
74
}
75

76
77
78
79
80
81
82
void FileOpsContextManagerItem::updateServiceList()
{
    // This code is inspired from
    // kdebase/apps/lib/konq/konq_menuactions.cpp

    // Get list of all distinct mimetypes in selection
    QStringList mimeTypes;
83
84
    const auto selectedFileItemList = contextManager()->selectedFileItemList();
    for (const KFileItem & item : selectedFileItemList) {
85
86
87
        const QString mimeType = item.mimetype();
        if (!mimeTypes.contains(mimeType)) {
            mimeTypes << mimeType;
88
89
90
        }
    }

91
92
93
#if KIO_VERSION >= QT_VERSION_CHECK(5, 83, 0)
    mServiceList = KFileItemActions::associatedApplications(mimeTypes);
#else
94
    mServiceList = KFileItemActions::associatedApplications(mimeTypes, QString());
95
#endif
96
}
97

98
99
QMimeData* FileOpsContextManagerItem::selectionMimeData()
{
100
    KFileItemList selectedFiles;
101

102
103
104
    // In Compare mode, restrict the returned mimedata to the focused image
    if (!mThumbnailView->isVisible()) {
        selectedFiles << KFileItem(contextManager()->currentUrl());
105
    } else {
106
        selectedFiles = contextManager()->selectedFileItemList();
107
108
    }

109
    return MimeTypeUtils::selectionMimeData(selectedFiles, MimeTypeUtils::ClipboardTarget);
110
111
112
113
114
115
116
117
118
119
120
}

QUrl FileOpsContextManagerItem::pasteTargetUrl() const
{
    // If only one folder is selected, paste inside it, otherwise paste in
    // current
    KFileItemList list = contextManager()->selectedFileItemList();
    if (list.count() == 1 && list.first().isDir()) {
        return list.first().url();
    } else {
        return contextManager()->currentDirUrl();
121
    }
122
}
123

124
125
static QAction* createSeparator(QObject* parent)
{
126
    auto* action = new QAction(parent);
127
128
    action->setSeparator(true);
    return action;
129
130
131
}

FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager* manager, QListView* thumbnailView, KActionCollection* actionCollection, KXMLGUIClient* client)
132
: AbstractContextManagerItem(manager)
133
{
134
135
136
137
138
    mThumbnailView = thumbnailView;
    mXMLGUIClient = client;
    mGroup = new SideBarGroup(i18n("File Operations"));
    setWidget(mGroup);
    EventWatcher::install(mGroup, QEvent::Show, this, SLOT(updateSideBarContent()));
139

140
141
    mInTrash = false;
    mNewFileMenu = new KNewFileMenu(Q_NULLPTR, QString(), this);
142

Laurent Montel's avatar
Laurent Montel committed
143
144
145
146
    connect(contextManager(), &ContextManager::selectionChanged,
            this, &FileOpsContextManagerItem::updateActions);
    connect(contextManager(), &ContextManager::currentDirUrlChanged,
            this, &FileOpsContextManagerItem::updateActions);
147

148
149
    auto* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
    auto* edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection);
150

151
152
153
    mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut()));
    mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy()));
    mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste()));
154

Laurent Montel's avatar
Laurent Montel committed
155
    mCopyToAction = file->addAction(QStringLiteral("file_copy_to"), this, SLOT(copyTo()));
156
    mCopyToAction->setText(i18nc("Verb", "Copy To..."));
Laurent Montel's avatar
Laurent Montel committed
157
    mCopyToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
158
    actionCollection->setDefaultShortcut(mCopyToAction, Qt::Key_F7);
159

Laurent Montel's avatar
Laurent Montel committed
160
    mMoveToAction = file->addAction(QStringLiteral("file_move_to"), this, SLOT(moveTo()));
161
    mMoveToAction->setText(i18nc("Verb", "Move To..."));
162
    mMoveToAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))));
163
    actionCollection->setDefaultShortcut(mMoveToAction, Qt::Key_F8);
164

Laurent Montel's avatar
Laurent Montel committed
165
    mLinkToAction = file->addAction(QStringLiteral("file_link_to"), this, SLOT(linkTo()));
166
    mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To..."));
Laurent Montel's avatar
Laurent Montel committed
167
    mLinkToAction->setIcon(QIcon::fromTheme(QStringLiteral("link")));
168
    actionCollection->setDefaultShortcut(mLinkToAction, Qt::Key_F9);
169

Laurent Montel's avatar
Laurent Montel committed
170
    mRenameAction = file->addAction(QStringLiteral("file_rename"), this, SLOT(rename()));
171
    mRenameAction->setText(i18nc("Verb", "Rename..."));
Laurent Montel's avatar
Laurent Montel committed
172
    mRenameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
173
    actionCollection->setDefaultShortcut(mRenameAction, Qt::Key_F2);
174

Laurent Montel's avatar
Laurent Montel committed
175
    mTrashAction = file->addAction(QStringLiteral("file_trash"), this, SLOT(trash()));
176
    mTrashAction->setText(i18nc("Verb", "Trash"));
Laurent Montel's avatar
Laurent Montel committed
177
    mTrashAction->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
178
    actionCollection->setDefaultShortcut(mTrashAction, Qt::Key_Delete);
179

180
    mDelAction = file->addAction(KStandardAction::DeleteFile, this, SLOT(del()));
181

Laurent Montel's avatar
Laurent Montel committed
182
    mRestoreAction = file->addAction(QStringLiteral("file_restore"), this, SLOT(restore()));
183
    mRestoreAction->setText(i18n("Restore"));
Laurent Montel's avatar
Laurent Montel committed
184
    mRestoreAction->setIcon(QIcon::fromTheme(QStringLiteral("restoration")));
185

Laurent Montel's avatar
Laurent Montel committed
186
    mShowPropertiesAction = file->addAction(QStringLiteral("file_show_properties"), this, SLOT(showProperties()));
187
    mShowPropertiesAction->setText(i18n("Properties"));
Laurent Montel's avatar
Laurent Montel committed
188
    mShowPropertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
189
    actionCollection->setDefaultShortcut(mShowPropertiesAction, QKeySequence(Qt::ALT | Qt::Key_Return));
190

Laurent Montel's avatar
Laurent Montel committed
191
    mCreateFolderAction = file->addAction(QStringLiteral("file_create_folder"), this, SLOT(createFolder()));
192
    mCreateFolderAction->setText(i18n("Create Folder..."));
Laurent Montel's avatar
Laurent Montel committed
193
    mCreateFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
194

Laurent Montel's avatar
Laurent Montel committed
195
    mOpenWithAction = file->addAction(QStringLiteral("file_open_with"));
196
    mOpenWithAction->setText(i18n("Open With"));
197
    auto* menu = new QMenu;
198
    mOpenWithAction->setMenu(menu);
Laurent Montel's avatar
Laurent Montel committed
199
200
    connect(menu, &QMenu::aboutToShow, this, &FileOpsContextManagerItem::populateOpenMenu);
    connect(menu, &QMenu::triggered, this, &FileOpsContextManagerItem::openWith);
201

Laurent Montel's avatar
Laurent Montel committed
202
    mOpenContainingFolderAction = file->addAction(QStringLiteral("file_open_containing_folder"), this, SLOT(openContainingFolder()));
203
    mOpenContainingFolderAction->setText(i18n("Open Containing Folder"));
Laurent Montel's avatar
Laurent Montel committed
204
    mOpenContainingFolderAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder")));
205

206
207
208
209
    mRegularFileActionList
            << mRenameAction
            << mTrashAction
            << mDelAction
210
            << createSeparator(this)
211
212
213
            << mCopyToAction
            << mMoveToAction
            << mLinkToAction
214
            << createSeparator(this)
215
            << mOpenWithAction
216
            << mOpenContainingFolderAction
217
            << mShowPropertiesAction
218
            << createSeparator(this)
219
            << mCreateFolderAction
220
221
            ;

222
223
224
    mTrashFileActionList
            << mRestoreAction
            << mDelAction
225
            << createSeparator(this)
226
            << mShowPropertiesAction
227
228
            ;

Laurent Montel's avatar
Laurent Montel committed
229
230
    connect(QApplication::clipboard(), &QClipboard::dataChanged,
            this, &FileOpsContextManagerItem::updatePasteAction);
231

232
    updatePasteAction();
233
    // Delay action update because it must happen *after* main window has called
234
    // createGUI(), otherwise calling mXMLGUIClient->plugActionList() will
235
    // fail.
236
    QMetaObject::invokeMethod(this, &FileOpsContextManagerItem::updateActions, Qt::QueuedConnection);
237
238
}

239
240
FileOpsContextManagerItem::~FileOpsContextManagerItem()
{
241
    delete mOpenWithAction->menu();
242
243
}

244
245
246
247
248
249
250
void FileOpsContextManagerItem::updateActions()
{
    const int count = contextManager()->selectedFileItemList().count();
    const bool selectionNotEmpty = count > 0;
    const bool urlIsValid = contextManager()->currentUrl().isValid();
    const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid();

251
    mInTrash = contextManager()->currentDirUrl().scheme() == "trash";
252

253
254
255
256
257
258
259
260
261
262
    mCutAction->setEnabled(selectionNotEmpty);
    mCopyAction->setEnabled(selectionNotEmpty);
    mCopyToAction->setEnabled(selectionNotEmpty);
    mMoveToAction->setEnabled(selectionNotEmpty);
    mLinkToAction->setEnabled(selectionNotEmpty);
    mTrashAction->setEnabled(selectionNotEmpty);
    mRestoreAction->setEnabled(selectionNotEmpty);
    mDelAction->setEnabled(selectionNotEmpty);
    mOpenWithAction->setEnabled(selectionNotEmpty);
    mRenameAction->setEnabled(count == 1);
263
    mOpenContainingFolderAction->setEnabled(selectionNotEmpty);
264

265
266
    mCreateFolderAction->setEnabled(dirUrlIsValid);
    mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid);
267

268
269
270
    mXMLGUIClient->unplugActionList("file_action_list");
    QList<QAction*>& list = mInTrash ? mTrashFileActionList : mRegularFileActionList;
    mXMLGUIClient->plugActionList("file_action_list", list);
271
272

    updateSideBarContent();
273
274
}

275
276
void FileOpsContextManagerItem::updatePasteAction()
{
David Faure's avatar
David Faure committed
277
278
    const QMimeData *mimeData = QApplication::clipboard()->mimeData();
    bool enable;
279
    KFileItem destItem(pasteTargetUrl());
David Faure's avatar
David Faure committed
280
    const QString text = KIO::pasteActionText(mimeData, &enable, destItem);
281
282
    mPasteAction->setEnabled(enable);
    mPasteAction->setText(text);
283
284
}

285
286
void FileOpsContextManagerItem::updateSideBarContent()
{
287
    if (!mGroup->isVisible()) {
288
289
290
        return;
    }

291
292
    mGroup->clear();
    QList<QAction*>& list = mInTrash ? mTrashFileActionList : mRegularFileActionList;
293
    for (QAction * action : qAsConst(list)) {
294
        if (action->isEnabled() && !action->isSeparator()) {
295
            mGroup->addAction(action);
296
297
        }
    }
298
299
}

300
301
302
303
void FileOpsContextManagerItem::showProperties()
{
    KFileItemList list = contextManager()->selectedFileItemList();
    if (list.count() > 0) {
304
        KPropertiesDialog::showDialog(list, mGroup);
305
    } else {
David Edmundson's avatar
David Edmundson committed
306
        QUrl url = contextManager()->currentDirUrl();
307
        KPropertiesDialog::showDialog(url, mGroup);
308
    }
309
310
}

311
312
void FileOpsContextManagerItem::cut()
{
313
    QMimeData* mimeData = selectionMimeData();
David Faure's avatar
David Faure committed
314
    KIO::setClipboardDataCut(mimeData, true);
315
    QApplication::clipboard()->setMimeData(mimeData);
316
317
}

318
319
void FileOpsContextManagerItem::copy()
{
320
    QMimeData* mimeData = selectionMimeData();
David Faure's avatar
David Faure committed
321
    KIO::setClipboardDataCut(mimeData, false);
322
    QApplication::clipboard()->setMimeData(mimeData);
323
324
}

325
326
void FileOpsContextManagerItem::paste()
{
327
328
    KIO::Job *job = KIO::paste(QApplication::clipboard()->mimeData(), pasteTargetUrl());
    KJobWidgets::setWindow(job, mGroup);
329
330
}

331
332
void FileOpsContextManagerItem::trash()
{
333
    FileOperations::trash(urlList(), mGroup);
334
335
}

336
337
void FileOpsContextManagerItem::del()
{
338
    FileOperations::del(urlList(), mGroup);
339
340
}

341
342
void FileOpsContextManagerItem::restore()
{
343
344
    KIO::RestoreJob *job = KIO::restoreFromTrash(urlList());
    KJobWidgets::setWindow(job, mGroup);
345
    job->uiDelegate()->setAutoErrorHandlingEnabled(true);
346
347
}

348
349
void FileOpsContextManagerItem::copyTo()
{
350
    FileOperations::copyTo(urlList(), widget(), contextManager());
351
352
}

353
354
void FileOpsContextManagerItem::moveTo()
{
355
    FileOperations::moveTo(urlList(), widget(), contextManager());
Aurélien Gâteau's avatar
Aurélien Gâteau committed
356
357
}

358
359
void FileOpsContextManagerItem::linkTo()
{
360
    FileOperations::linkTo(urlList(), widget(), contextManager());
Aurélien Gâteau's avatar
Aurélien Gâteau committed
361
362
}

363
364
void FileOpsContextManagerItem::rename()
{
365
366
367
    if (mThumbnailView->isVisible()) {
        QModelIndex index = mThumbnailView->currentIndex();
        mThumbnailView->edit(index);
368
    } else {
369
        FileOperations::rename(urlList().constFirst(), mGroup, contextManager());
370
        contextManager()->slotSelectionChanged();
371
    }
372
373
}

374
375
void FileOpsContextManagerItem::createFolder()
{
David Edmundson's avatar
David Edmundson committed
376
    QUrl url = contextManager()->currentDirUrl();
377
378
379
    mNewFileMenu->setParentWidget(mGroup);
    mNewFileMenu->setPopupFiles(QList<QUrl>() << url);
    mNewFileMenu->createDirectory();
380
381
}

382
383
void FileOpsContextManagerItem::populateOpenMenu()
{
384
    QMenu* openMenu = mOpenWithAction->menu();
385
    qDeleteAll(openMenu->actions());
386

387
    updateServiceList();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
388

389
    int idx = 0;
390
    for (const KService::Ptr & service : qAsConst(mServiceList)) {
391
392
        QString text = service->name().replace('&', "&&");
        QAction* action = openMenu->addAction(text);
David Edmundson's avatar
David Edmundson committed
393
        action->setIcon(QIcon::fromTheme(service->icon()));
394
395
        action->setData(idx);
        ++idx;
396
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
397

398
    openMenu->addSeparator();
399
400
    QAction* action = openMenu->addAction(i18n("Other Application..."));
    action->setData(-1);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
401
402
}

403
404
405
406
void FileOpsContextManagerItem::openWith(QAction* action)
{
    Q_ASSERT(action);
    KService::Ptr service;
407
    QList<QUrl> list = urlList();
408

409
410
411
    bool ok;
    int idx = action->data().toInt(&ok);
    GV_RETURN_IF_FAIL(ok);
412
413
414
415
416
417
418
419
420
421
#if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
    if (idx != -1) {
        service = mServiceList.at(idx);
    }
    // If service is null, ApplicationLauncherJob will invoke the open-with dialog
    auto *job = new KIO::ApplicationLauncherJob(service);
    job->setUrls(list);
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mGroup));
    job->start();
#else
422
    if (idx == -1) {
423
        // Other Application...
424
425
        DialogGuard<KOpenWithDialog> dlg(list, mGroup);
        if (!dlg->exec()) {
426
427
            return;
        }
428
        service = dlg->service();
429
430
431

        if (!service) {
            // User entered a custom command
432
433
            Q_ASSERT(!dlg->text().isEmpty());
            KRun::run(dlg->text(), list, mGroup);
434
435
436
            return;
        }
    } else {
437
        service = mServiceList.at(idx);
438
439
440
    }

    Q_ASSERT(service);
441
    KRun::runService(*service, list, mGroup);
442
#endif
Aurélien Gâteau's avatar
Aurélien Gâteau committed
443
444
}

445
446
447
448
449
void FileOpsContextManagerItem::openContainingFolder()
{
    KIO::highlightInFileManager(urlList());
}

450
} // namespace