part.cpp 65.7 KB
Newer Older
1
2
3
4
/*
 * ark -- archiver for the KDE project
 *
 * Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
5
 * Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
6
 * Copyright (C) 2009-2012 Raphael Kubo da Costa <rakuco@FreeBSD.org>
7
 * Copyright (c) 2016 Vladyslav Batyrenko <mvlabat@gmail.com>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * 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.
 *
 */
24

Henrique Pinto's avatar
Henrique Pinto committed
25
#include "part.h"
26
#include "ark_debug.h"
27
28
#include "adddialog.h"
#include "overwritedialog.h"
29
#include "archiveformat.h"
Henrique Pinto's avatar
Henrique Pinto committed
30
#include "archivemodel.h"
Ragnar Thomsen's avatar
Ragnar Thomsen committed
31
#include "archivesortfiltermodel.h"
32
#include "archiveview.h"
Henrique Pinto's avatar
Henrique Pinto committed
33
#include "arkviewer.h"
34
#include "dnddbusinterfaceadaptor.h"
35
36
#include "infopanel.h"
#include "jobtracker.h"
37
#include "generalsettingspage.h"
38
39
40
41
42
43
#include "extractiondialog.h"
#include "extractionsettingspage.h"
#include "jobs.h"
#include "settings.h"
#include "previewsettingspage.h"
#include "propertiesdialog.h"
44
#include "pluginsettingspage.h"
45
#include "pluginmanager.h"
Henrique Pinto's avatar
Henrique Pinto committed
46

47
#include <KPluginMetaData>
Henrique Pinto's avatar
Henrique Pinto committed
48
#include <KActionCollection>
49
#include <KConfigGroup>
50
#include <KGuiItem>
51
#include <KIO/ApplicationLauncherJob>
52
#include <KIO/Job>
53
54
#include <KIO/JobUiDelegate>
#include <KIO/OpenUrlJob>
55
56
#include <KJobWidgets>
#include <KIO/StatJob>
57
#include <KMessageBox>
58
#include <KParts/OpenUrlArguments>
59
#include <KPluginFactory>
60
#include <KStandardGuiItem>
61
#include <KToggleAction>
Bhushan Shah's avatar
Bhushan Shah committed
62
#include <KLocalizedString>
63
#include <KXMLGUIFactory>
64

Henrique Pinto's avatar
Henrique Pinto committed
65
#include <QAction>
66
67
#include <QCursor>
#include <QHeaderView>
68
#include <QMenu>
69
#include <QMimeData>
70
#include <QMouseEvent>
71
#include <QScopedPointer>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
72
#include <QStatusBar>
73
#include <QPointer>
74
75
#include <QSplitter>
#include <QTimer>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
76
#include <QFileDialog>
Arnold Dumas's avatar
Arnold Dumas committed
77
#include <QIcon>
78
#include <QInputDialog>
Ragnar Thomsen's avatar
Ragnar Thomsen committed
79
#include <QFileSystemWatcher>
80
81
#include <QGroupBox>
#include <QPlainTextEdit>
Ragnar Thomsen's avatar
Ragnar Thomsen committed
82
#include <QPushButton>
83

84
85
using namespace Kerfuffle;

86
87
88
namespace Ark
{

89
90
static quint32 s_instanceCounter = 1;

91
Part::Part(QWidget *parentWidget, QObject *parent, const KPluginMetaData &metaData, const QVariantList& args)
92
        : KParts::ReadWritePart(parent),
93
          m_splitter(nullptr),
94
          m_busy(false),
95
          m_jobTracker(nullptr)
Henrique Pinto's avatar
Henrique Pinto committed
96
{
97
    Q_UNUSED(args)
98
    setMetaData(metaData);
Henrique Pinto's avatar
Henrique Pinto committed
99

100
101
    new DndExtractAdaptor(this);

Lukáš Tinkl's avatar
Lukáš Tinkl committed
102
    const QString pathName = QStringLiteral("/DndExtract/%1").arg(s_instanceCounter++);
103
    if (!QDBusConnection::sessionBus().registerObject(pathName, this)) {
104
        qCCritical(ARK) << "Could not register a D-Bus object for drag'n'drop";
105
106
    }

107
108
109
    // m_vlayout is needed for later insertion of QMessageWidget
    QWidget *mainWidget = new QWidget;
    m_vlayout = new QVBoxLayout;
110
    m_model = new ArchiveModel(pathName, this);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
111
    m_filterModel = new ArchiveSortFilterModel(this);
112
    m_splitter = new QSplitter(Qt::Horizontal, parentWidget);
113
114
115
    m_view = new ArchiveView;
    m_infoPanel = new InfoPanel(m_model);

116
117
118
119
120
121
122
123
124
125
    // Add widgets for the comment field.
    m_commentView = new QPlainTextEdit();
    m_commentView->setReadOnly(true);
    m_commentView->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    m_commentBox = new QGroupBox(i18n("Comment"));
    m_commentBox->hide();
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addWidget(m_commentView);
    m_commentBox->setLayout(vbox);

126
    m_messageWidget = new KMessageWidget(parentWidget);
127
    m_messageWidget->setWordWrap(true);
128
129
    m_messageWidget->hide();

130
131
132
133
134
135
136
137
138
139
140
141
142
143
    m_commentMsgWidget = new KMessageWidget();
    m_commentMsgWidget->setText(i18n("Comment has been modified."));
    m_commentMsgWidget->setMessageType(KMessageWidget::Information);
    m_commentMsgWidget->setCloseButtonVisible(false);
    m_commentMsgWidget->hide();

    QAction *saveAction = new QAction(i18n("Save"), m_commentMsgWidget);
    m_commentMsgWidget->addAction(saveAction);
    connect(saveAction, &QAction::triggered, this, &Part::slotAddComment);

    m_commentBox->layout()->addWidget(m_commentMsgWidget);

    connect(m_commentView, &QPlainTextEdit::textChanged, this, &Part::slotCommentChanged);

144
145
    setWidget(mainWidget);
    mainWidget->setLayout(m_vlayout);
146

Ragnar Thomsen's avatar
Ragnar Thomsen committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    // Setup search widget.
    m_searchWidget = new QWidget(parentWidget);
    m_searchWidget->setVisible(false);
    m_searchWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
    QHBoxLayout *searchLayout = new QHBoxLayout;
    searchLayout->setContentsMargins(2, 2, 2, 2);
    m_vlayout->addWidget(m_searchWidget);
    m_searchWidget->setLayout(searchLayout);
    m_searchCloseButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-close")), QString(), m_searchWidget);
    m_searchCloseButton->setFlat(true);
    m_searchLineEdit = new QLineEdit(m_searchWidget);
    m_searchLineEdit->setClearButtonEnabled(true);
    m_searchLineEdit->setPlaceholderText(i18n("Type to search..."));
    mainWidget->installEventFilter(this);
    searchLayout->addWidget(m_searchCloseButton);
    searchLayout->addWidget(m_searchLineEdit);
    connect(m_searchCloseButton, &QPushButton::clicked, this, [=]() {
        m_searchWidget->hide();
        m_searchLineEdit->clear();
    });
    connect(m_searchLineEdit, &QLineEdit::textChanged, this, &Part::searchEdited);

169
170
    // Configure the QVBoxLayout and add widgets
    m_vlayout->setContentsMargins(0,0,0,0);
171
    m_vlayout->addWidget(m_messageWidget);
172
173
    m_vlayout->addWidget(m_splitter);

174
175
176
177
178
179
180
181
182
    // Vertical QSplitter for the file view and comment field.
    m_commentSplitter = new QSplitter(Qt::Vertical, parentWidget);
    m_commentSplitter->setOpaqueResize(false);
    m_commentSplitter->addWidget(m_view);
    m_commentSplitter->addWidget(m_commentBox);
    m_commentSplitter->setCollapsible(0, false);

    // Horizontal QSplitter for the file view and infopanel.
    m_splitter->addWidget(m_commentSplitter);
183
    m_splitter->addWidget(m_infoPanel);
184
185
186
187
188
189
190

    // Read settings from config file and show/hide infoPanel.
    if (!ArkSettings::showInfoPanel()) {
        m_infoPanel->hide();
    } else {
        m_splitter->setSizes(ArkSettings::splitterSizes());
    }
191

192
193
    setupView();
    setupActions();
Henrique Pinto's avatar
Henrique Pinto committed
194

195
196
197
    connect(m_view, &ArchiveView::entryChanged,
            this, &Part::slotRenameFile);

Laurent Montel's avatar
Laurent Montel committed
198
199
200
201
    connect(m_model, &ArchiveModel::loadingStarted,
            this, &Part::slotLoadingStarted);
    connect(m_model, &ArchiveModel::loadingFinished,
            this, &Part::slotLoadingFinished);
202
    connect(m_model, &ArchiveModel::droppedFiles,
203
            this, &Part::slotDroppedFiles);
Laurent Montel's avatar
Laurent Montel committed
204
205
    connect(m_model, &ArchiveModel::error,
            this, &Part::slotError);
206
207
    connect(m_model, &ArchiveModel::messageWidget,
            this, &Part::displayMsgWidget);
208

Laurent Montel's avatar
Laurent Montel committed
209
210
211
212
    connect(this, &Part::busy,
            this, &Part::setBusyGui);
    connect(this, &Part::ready,
            this, &Part::setReadyGui);
213
214
    connect(this, &KParts::ReadOnlyPart::urlChanged,
            this, &Part::setFileNameFromArchive);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
215
    connect(this, QOverload<>::of(&KParts::ReadOnlyPart::completed),
216
            this, &Part::setFileNameFromArchive);
217
218
    connect(this, QOverload<>::of(&KParts::ReadOnlyPart::completed),
            this, &Part::slotCompleted);
219
    connect(ArkSettings::self(), &KCoreConfigSkeleton::configChanged, this, &Part::updateActions);
220

221
    m_statusBarExtension = new KParts::StatusBarExtension(this);
222

Lukáš Tinkl's avatar
Lukáš Tinkl committed
223
    setXMLFile(QStringLiteral("ark_part.rc"));
Henrique Pinto's avatar
Henrique Pinto committed
224
225
226
227
}

Part::~Part()
{
228
    qDeleteAll(m_tmpExtractDirList);
229

230
    // Only save splitterSizes if infopanel is visible,
231
    // because we don't want to store zero size for infopanel.
232
233
234
235
236
    if (m_showInfoPanelAction->isChecked()) {
        ArkSettings::setSplitterSizes(m_splitter->sizes());
    }
    ArkSettings::setShowInfoPanel(m_showInfoPanelAction->isChecked());
    ArkSettings::self()->save();
237

238
    m_extractArchiveAction->menu()->deleteLater();
239
    m_extractAction->menu()->deleteLater();
Henrique Pinto's avatar
Henrique Pinto committed
240
241
}

242
243
244
245
246
247
248
249
250
251
QString Part::componentName() const
{
    // also the part ui.rc file is in the program folder
    // TODO: change the component name to "arkpart" by removing this method and
    // adapting the folder where the file is placed.
    // Needs a way to also move any potential custom user ui.rc files
    // from ark/ark_part.rc to arkpart/ark_part.rc
    return QStringLiteral("ark");
}

252
253
void Part::slotCommentChanged()
{
254
    if (!m_model->archive() || m_commentView->toPlainText().isEmpty()) {
255
256
257
        return;
    }

258
259
260
261
262
263
264
    if (m_commentMsgWidget->isHidden() && m_commentView->toPlainText() != m_model->archive()->comment()) {
        m_commentMsgWidget->animatedShow();
    } else if (m_commentMsgWidget->isVisible() && m_commentView->toPlainText() == m_model->archive()->comment()) {
        m_commentMsgWidget->hide();
    }
}

265
void Part::registerJob(KJob* job)
266
{
267
    if (!m_jobTracker) {
268
        m_jobTracker = new JobTracker(widget());
Elvis Angelaccio's avatar
Elvis Angelaccio committed
269
        m_statusBarExtension->addStatusBarItem(m_jobTracker->widget(nullptr), 0, true);
270
271
        m_jobTracker->widget(job)->show();
    }
272
273

    KIO::getJobTracker()->registerJob(job);
274
    m_jobTracker->registerJob(job);
275

Alexander Lohnau's avatar
Alexander Lohnau committed
276
    Q_EMIT busy();
277
    connect(job, &KJob::result, this, &Part::ready);
278
279
}

280
281
282
// TODO: KIO::mostLocalHere is used here to resolve some KIO URLs to local
// paths (e.g. desktop:/), but more work is needed to support extraction
// to non-local destinations. See bugs #189322 and #204323.
283
void Part::extractSelectedFilesTo(const QString& localPath)
284
{
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
285
286
287
    if (!m_model) {
        return;
    }
288

289
    const QUrl url = QUrl::fromUserInput(localPath, QString());
290

Ahmad Samir's avatar
Ahmad Samir committed
291
292
293
294
295
296
297
298
299
300
301
302
303
    auto doExtract = [this](const QString &destination) {
        qCDebug(ARK) << "Extract to" << destination;

        Kerfuffle::ExtractionOptions options;
        options.setDragAndDropEnabled(true);

        // Create and start the ExtractJob.
        ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(getSelectedIndexes())), destination, options);
        registerJob(job);
        connect(job, &KJob::result, this, &Part::slotExtractionDone);
        job->start();
    };

304
    if (!url.isLocalFile() && !url.scheme().isEmpty()) {
Ahmad Samir's avatar
Ahmad Samir committed
305
306
        // Try to resolve the URL to a local path.
        KIO::StatJob *statJob = KIO::mostLocalUrl(url);
307

Ahmad Samir's avatar
Ahmad Samir committed
308
309
310
311
312
        connect(statJob, &KJob::result, this, [=]() {
            if (statJob->error()) {
                KMessageBox::error(widget(), statJob->errorString());
                return;
            }
313

Ahmad Samir's avatar
Ahmad Samir committed
314
315
316
317
318
319
320
321
322
            const QString udsLocalPath = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
            if (udsLocalPath.isEmpty()) { // The URL could not be resolved to a local path
                qCWarning(ARK) << "Ark cannot extract to non-local destination:" << localPath;
                KMessageBox::sorry(widget(), xi18nc("@info", "Ark can extract archives to local destinations only."));
                return;
            }

            doExtract(udsLocalPath);
        });
323

324
325
326
        return;
    }

Ahmad Samir's avatar
Ahmad Samir committed
327
    doExtract(localPath);
328
329
}

330
331
332
333
334
335
void Part::guiActivateEvent(KParts::GUIActivateEvent *event)
{
    // #357660: prevent parent's implementation from changing the window title.
    Q_UNUSED(event)
}

336
337
void Part::setupView()
{
338
339
    m_view->setContextMenuPolicy(Qt::CustomContextMenu);

Ragnar Thomsen's avatar
Ragnar Thomsen committed
340
341
342
343
    m_filterModel->setSourceModel(m_model);
    m_view->setModel(m_filterModel);
    m_filterModel->setFilterKeyColumn(0);
    m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
344

Laurent Montel's avatar
Laurent Montel committed
345
346
347
348
    connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &Part::updateActions);
    connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &Part::selectionChanged);
Harald Hvaal's avatar
Harald Hvaal committed
349

350
    connect(m_view, &QTreeView::activated, this, &Part::slotActivated);
351

Laurent Montel's avatar
Laurent Montel committed
352
    connect(m_view, &QWidget::customContextMenuRequested, this, &Part::slotShowContextMenu);
353
354
}

355
void Part::slotActivated(const QModelIndex &index)
356
{
357
358
    Q_UNUSED(index)

359
360
    // The activated signal is emitted when items are selected with the mouse,
    // so do nothing if CTRL or SHIFT key is pressed.
361
362
    if (QGuiApplication::keyboardModifiers() != Qt::ShiftModifier &&
        QGuiApplication::keyboardModifiers() != Qt::ControlModifier) {
363
        ArkSettings::defaultOpenAction() == ArkSettings::EnumDefaultOpenAction::Preview ? slotOpenEntry(Preview) : slotOpenEntry(OpenFile);
364
365
366
    }
}

Henrique Pinto's avatar
Henrique Pinto committed
367
368
void Part::setupActions()
{
369
    m_showInfoPanelAction = new KToggleAction(i18nc("@action:inmenu", "Show Information Panel"), this);
370
    actionCollection()->addAction(QStringLiteral( "show-infopanel" ), m_showInfoPanelAction);
371
    m_showInfoPanelAction->setChecked(ArkSettings::showInfoPanel());
Laurent Montel's avatar
Laurent Montel committed
372
373
    connect(m_showInfoPanelAction, &QAction::triggered,
            this, &Part::slotToggleInfoPanel);
374

Laurent Montel's avatar
Laurent Montel committed
375
    m_saveAsAction = KStandardAction::saveAs(this, &Part::slotSaveAs, this);
376
    m_saveAsAction->setText(i18nc("@action:inmenu", "Save Copy As..."));
377
    actionCollection()->addAction(QStringLiteral("ark_file_save_as"), m_saveAsAction);
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
378

Ragnar Thomsen's avatar
Ragnar Thomsen committed
379
    m_openFileAction = actionCollection()->addAction(QStringLiteral("openfile"));
380
    m_openFileAction->setText(i18nc("open a file with external program", "&Open"));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
381
    m_openFileAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
382
    m_openFileAction->setToolTip(i18nc("@info:tooltip", "Click to open the selected file with the associated application"));
383
    connect(m_openFileAction, &QAction::triggered, this, [this]() { slotOpenEntry(OpenFile); });
Ragnar Thomsen's avatar
Ragnar Thomsen committed
384
385

    m_openFileWithAction = actionCollection()->addAction(QStringLiteral("openfilewith"));
386
    m_openFileWithAction->setText(i18nc("open a file with external program", "Open &With..."));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
387
    m_openFileWithAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
388
    m_openFileWithAction->setToolTip(i18nc("@info:tooltip", "Click to open the selected file with an external program"));
389
    connect(m_openFileWithAction, &QAction::triggered, this, [this]() { slotOpenEntry(OpenFileWith); });
390

391
    m_previewAction = actionCollection()->addAction(QStringLiteral("preview"));
392
    m_previewAction->setText(i18nc("to preview a file inside an archive", "Pre&view"));
393
    m_previewAction->setIcon(QIcon::fromTheme(QStringLiteral("document-preview-archive")));
394
    m_previewAction->setToolTip(i18nc("@info:tooltip", "Click to preview the selected file"));
395
    actionCollection()->setDefaultShortcut(m_previewAction, Qt::CTRL | Qt::Key_P);
396
    connect(m_previewAction, &QAction::triggered, this, [this]() { slotOpenEntry(Preview); });
397

398
399
    m_extractArchiveAction = actionCollection()->addAction(QStringLiteral("extract_all"));
    m_extractArchiveAction->setText(i18nc("@action:inmenu", "E&xtract All"));
400
401
    m_extractArchiveAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract")));
    m_extractArchiveAction->setToolTip(i18n("Click to open an extraction dialog, where you can choose how to extract all the files in the archive"));
402
    actionCollection()->setDefaultShortcut(m_extractArchiveAction, Qt::CTRL | Qt::SHIFT | Qt::Key_E);
403
    connect(m_extractArchiveAction, &QAction::triggered, this, &Part::slotExtractArchive);
404

405
406
407
    m_extractAction = actionCollection()->addAction(QStringLiteral("extract"));
    m_extractAction->setText(i18nc("@action:inmenu", "&Extract"));
    m_extractAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract")));
408
    actionCollection()->setDefaultShortcut(m_extractAction, Qt::CTRL | Qt::Key_E);
409
    m_extractAction->setToolTip(i18n("Click to open an extraction dialog, where you can choose to extract either all files or just the selected ones"));
410
    connect(m_extractAction, &QAction::triggered, this, &Part::slotShowExtractionDialog);
411

412
413
    m_addFilesAction = actionCollection()->addAction(QStringLiteral("add"));
    m_addFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert")));
414
    m_addFilesAction->setText(i18n("Add &Files..."));
415
    m_addFilesAction->setToolTip(i18nc("@info:tooltip", "Click to add files to the archive"));
416
    actionCollection()->setDefaultShortcut(m_addFilesAction, Qt::ALT | Qt::Key_A);
Elvis Angelaccio's avatar
Elvis Angelaccio committed
417
    connect(m_addFilesAction, &QAction::triggered, this, QOverload<>::of(&Part::slotAddFiles));
418

419
    m_renameFileAction = KStandardAction::renameFile(m_view, &ArchiveView::renameSelectedEntry, actionCollection());
420

421
    m_deleteFilesAction = KStandardAction::deleteFile(this, &Part::slotDeleteFiles, actionCollection());
422
    m_deleteFilesAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-remove")));
423
    actionCollection()->setDefaultShortcut(m_deleteFilesAction, Qt::Key_Delete);
424

425
426
427
    m_cutFilesAction = KStandardAction::cut(this, &Part::slotCutFiles, actionCollection());
    m_copyFilesAction = KStandardAction::copy(this, &Part::slotCopyFiles, actionCollection());
    m_pasteFilesAction = KStandardAction::paste(this, QOverload<>::of(&Part::slotPasteFiles), actionCollection());
428

Ragnar Thomsen's avatar
Ragnar Thomsen committed
429
430
431
    m_propertiesAction = actionCollection()->addAction(QStringLiteral("properties"));
    m_propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
    m_propertiesAction->setText(i18nc("@action:inmenu", "&Properties"));
432
    actionCollection()->setDefaultShortcut(m_propertiesAction, Qt::ALT | Qt::Key_Return);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
433
    m_propertiesAction->setToolTip(i18nc("@info:tooltip", "Click to see properties for archive"));
434
    connect(m_propertiesAction, &QAction::triggered, this, &Part::slotShowProperties);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
435

436
437
    m_editCommentAction = actionCollection()->addAction(QStringLiteral("edit_comment"));
    m_editCommentAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
438
    actionCollection()->setDefaultShortcut(m_editCommentAction, Qt::ALT | Qt::Key_C);
439
440
441
    m_editCommentAction->setToolTip(i18nc("@info:tooltip", "Click to add or edit comment"));
    connect(m_editCommentAction, &QAction::triggered, this, &Part::slotShowComment);

Ragnar Thomsen's avatar
Ragnar Thomsen committed
442
443
444
    m_testArchiveAction = actionCollection()->addAction(QStringLiteral("test_archive"));
    m_testArchiveAction->setIcon(QIcon::fromTheme(QStringLiteral("checkmark")));
    m_testArchiveAction->setText(i18nc("@action:inmenu", "&Test Integrity"));
445
    actionCollection()->setDefaultShortcut(m_testArchiveAction, Qt::ALT | Qt::Key_T);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
446
447
448
    m_testArchiveAction->setToolTip(i18nc("@info:tooltip", "Click to test the archive for integrity"));
    connect(m_testArchiveAction, &QAction::triggered, this, &Part::slotTestArchive);

449
    m_searchAction = KStandardAction::find(this, &Part::slotShowFind, actionCollection());
Ragnar Thomsen's avatar
Ragnar Thomsen committed
450

451
    updateActions();
452
    updateQuickExtractMenu(m_extractArchiveAction);
453
    updateQuickExtractMenu(m_extractAction);
Henrique Pinto's avatar
Henrique Pinto committed
454
455
456
457
}

void Part::updateActions()
{
458
    const bool isWritable = isArchiveWritable();
Ragnar Thomsen's avatar
Ragnar Thomsen committed
459
    const Archive::Entry *entry = m_model->entryForIndex(m_filterModel->mapToSource(m_view->selectionModel()->currentIndex()));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
460
    int selectedEntriesCount = m_view->selectionModel()->selectedRows().count();
461

462
463
464
465
466
467
468
469
470
471
472
473
    // We disable adding files if the archive is encrypted but the password is
    // unknown (this happens when opening existing non-he password-protected
    // archives). If we added files they would not get encrypted resulting in an
    // archive with a mixture of encrypted and unencrypted files.
    const bool isEncryptedButUnknownPassword = m_model->archive() &&
                                               m_model->archive()->encryptionType() != Archive::Unencrypted &&
                                               m_model->archive()->password().isEmpty();

    if (isEncryptedButUnknownPassword) {
        m_addFilesAction->setToolTip(xi18nc("@info:tooltip",
                                            "Adding files to existing password-protected archives with no header-encryption is currently not supported."
                                            "<nl/><nl/>Extract the files and create a new archive if you want to add files."));
474
475
        m_testArchiveAction->setToolTip(xi18nc("@info:tooltip",
                                               "Testing password-protected archives with no header-encryption is currently not supported."));
476
477
    } else {
        m_addFilesAction->setToolTip(i18nc("@info:tooltip", "Click to add files to the archive"));
478
        m_testArchiveAction->setToolTip(i18nc("@info:tooltip", "Click to test the archive for integrity"));
479
480
    }

Ragnar Thomsen's avatar
Ragnar Thomsen committed
481
482
483
    // Figure out if entry size is larger than preview size limit.
    const int maxPreviewSize = ArkSettings::previewFileSizeLimit() * 1024 * 1024;
    const bool limit = ArkSettings::limitPreviewFileSize();
484
    bool isPreviewable = (!limit || (limit && entry != nullptr && entry->property("size").toLongLong() < maxPreviewSize));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
485

486
    const bool isDir = (entry == nullptr) ? false : entry->isDir();
Ragnar Thomsen's avatar
Ragnar Thomsen committed
487
488
    m_previewAction->setEnabled(!isBusy() &&
                                isPreviewable &&
489
                                !isDir &&
Ragnar Thomsen's avatar
Ragnar Thomsen committed
490
                                (selectedEntriesCount == 1));
491
492
    m_extractArchiveAction->setEnabled(!isBusy() &&
                                       (m_model->rowCount() > 0));
493
494
    m_extractAction->setEnabled(!isBusy() &&
                                (m_model->rowCount() > 0));
495
496
    m_saveAsAction->setEnabled(!isBusy() &&
                               m_model->rowCount() > 0);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
497
    m_addFilesAction->setEnabled(!isBusy() &&
498
499
                                 isWritable &&
                                 !isEncryptedButUnknownPassword);
Ragnar Thomsen's avatar
Ragnar Thomsen committed
500
501
502
503
504
    m_deleteFilesAction->setEnabled(!isBusy() &&
                                    isWritable &&
                                    (selectedEntriesCount > 0));
    m_openFileAction->setEnabled(!isBusy() &&
                                 isPreviewable &&
505
                                 !isDir &&
506
                                 (selectedEntriesCount == 1));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
507
508
    m_openFileWithAction->setEnabled(!isBusy() &&
                                     isPreviewable &&
509
                                     !isDir &&
510
                                     (selectedEntriesCount == 1));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
511
512
    m_propertiesAction->setEnabled(!isBusy() &&
                                   m_model->archive());
513

514
515
516
517
518
519
520
521
522
523
524
525
526
527
    m_renameFileAction->setEnabled(!isBusy() &&
                                   isWritable &&
                                   (selectedEntriesCount == 1));
    m_cutFilesAction->setEnabled(!isBusy() &&
                                 isWritable &&
                                 (selectedEntriesCount > 0));
    m_copyFilesAction->setEnabled(!isBusy() &&
                                  isWritable &&
                                  (selectedEntriesCount > 0));
    m_pasteFilesAction->setEnabled(!isBusy() &&
                                   isWritable &&
                                   (selectedEntriesCount == 0 || (selectedEntriesCount == 1 && isDir)) &&
                                   (m_model->filesToMove.count() > 0 || m_model->filesToCopy.count() > 0));

Ragnar Thomsen's avatar
Ragnar Thomsen committed
528
529
530
    m_searchAction->setEnabled(!isBusy() &&
                               m_model->rowCount() > 0);

531
532
533
    m_commentView->setEnabled(!isBusy());
    m_commentMsgWidget->setEnabled(!isBusy());

Ragnar Thomsen's avatar
Ragnar Thomsen committed
534
535
536
    m_editCommentAction->setEnabled(false);
    m_testArchiveAction->setEnabled(false);

537
538
539
540
541
542
    if (m_model->archive()) {
        const KPluginMetaData metadata = PluginManager().preferredPluginFor(m_model->archive()->mimeType())->metaData();
        bool supportsWriteComment = ArchiveFormat::fromMetadata(m_model->archive()->mimeType(), metadata).supportsWriteComment();
        m_editCommentAction->setEnabled(!isBusy() &&
                                        supportsWriteComment);
        m_commentView->setReadOnly(!supportsWriteComment);
543
544
        m_editCommentAction->setText(m_model->archive()->hasComment() ? i18nc("@action:inmenu mutually exclusive with Add &Comment", "Edit &Comment") :
                                                                        i18nc("@action:inmenu mutually exclusive with Edit &Comment", "Add &Comment"));
Ragnar Thomsen's avatar
Ragnar Thomsen committed
545
546
547

        bool supportsTesting = ArchiveFormat::fromMetadata(m_model->archive()->mimeType(), metadata).supportsTesting();
        m_testArchiveAction->setEnabled(!isBusy() &&
548
549
                                        supportsTesting &&
                                        !isEncryptedButUnknownPassword);
550
551
    } else {
        m_commentView->setReadOnly(true);
552
        m_editCommentAction->setText(i18nc("@action:inmenu mutually exclusive with Edit &Comment", "Add &Comment"));
553
554
555
556
557
558
559
    }
}

void Part::slotShowComment()
{
    if (!m_commentBox->isVisible()) {
        m_commentBox->show();
560
        m_commentSplitter->setSizes(QList<int>() << static_cast<int>(m_view->height() * 0.6) << 1);
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
    }
    m_commentView->setFocus();
}

void Part::slotAddComment()
{
    CommentJob *job = m_model->archive()->addComment(m_commentView->toPlainText());
    if (!job) {
        return;
    }
    registerJob(job);
    job->start();
    m_commentMsgWidget->hide();
    if (m_commentView->toPlainText().isEmpty()) {
        m_commentBox->hide();
    }
577
578
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
579
580
581
582
583
584
585
586
587
588
589
void Part::slotTestArchive()
{
    TestJob *job = m_model->archive()->testArchive();
    if (!job) {
        return;
    }
    registerJob(job);
    connect(job, &KJob::result, this, &Part::slotTestingDone);
    job->start();
}

590
591
592
593
594
bool Part::isArchiveWritable() const
{
    return isReadWrite() && m_model->archive() && !m_model->archive()->isReadOnly();
}

595
596
597
598
599
bool Part::isCreatingNewArchive() const
{
    return arguments().metaData()[QStringLiteral("createNewArchive")] == QLatin1String("true");
}

Elvis Angelaccio's avatar
Elvis Angelaccio committed
600
601
602
603
604
void Part::createArchive()
{
    const QString fixedMimeType = arguments().metaData()[QStringLiteral("fixedMimeType")];
    m_model->createEmptyArchive(localFilePath(), fixedMimeType, m_model);

Laurent Montel's avatar
Laurent Montel committed
605
    if (arguments().metaData().contains(QStringLiteral("volumeSize"))) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
        m_model->archive()->setMultiVolume(true);
    }

    const QString password = arguments().metaData()[QStringLiteral("encryptionPassword")];
    if (!password.isEmpty()) {
        m_model->encryptArchive(password,
                                arguments().metaData()[QStringLiteral("encryptHeader")] == QLatin1String("true"));
    }
}

void Part::loadArchive()
{
    const QString fixedMimeType = arguments().metaData()[QStringLiteral("fixedMimeType")];
    auto job = m_model->loadArchive(localFilePath(), fixedMimeType, m_model);

    if (job) {
        registerJob(job);
        job->start();
    } else {
        updateActions();
    }
}

629
630
631
632
633
634
635
636
637
void Part::resetArchive()
{
    m_view->setDropsEnabled(false);
    m_model->reset();
    closeUrl();
    setFileNameFromArchive();
    updateActions();
}

638
639
640
641
642
void Part::resetGui()
{
    m_messageWidget->hide();
    m_commentView->clear();
    m_commentBox->hide();
643
    m_infoPanel->updateWithDefaults();
644
645
    // Also reset format-specific compression options.
    m_compressionOptions = CompressionOptions();
646
647
}

Ragnar Thomsen's avatar
Ragnar Thomsen committed
648
649
650
651
652
653
654
655
656
657
658
void Part::slotTestingDone(KJob* job)
{
    if (job->error() && job->error() != KJob::KilledJobError) {
        KMessageBox::error(widget(), job->errorString());
    } else if (static_cast<TestJob*>(job)->testSucceeded()) {
        KMessageBox::information(widget(), i18n("The archive passed the integrity test."), i18n("Test Results"));
    } else {
        KMessageBox::error(widget(), i18n("The archive failed the integrity test."), i18n("Test Results"));
    }
}

659
660
661
662
663
664
665
666
void Part::updateQuickExtractMenu(QAction *extractAction)
{
    if (!extractAction) {
        return;
    }

    QMenu *menu = extractAction->menu();

667
    if (!menu) {
668
669
        menu = new QMenu();
        extractAction->setMenu(menu);
Laurent Montel's avatar
Laurent Montel committed
670
671
        connect(menu, &QMenu::triggered,
                this, &Part::slotQuickExtractFiles);
672
673

        // Remember to keep this action's properties as similar to
674
        // extractAction's as possible (except where it does not make
675
        // sense, such as the text or the shortcut).
676
        QAction *extractTo = menu->addAction(i18n("Extract To..."));
677
678
679
680
681
682
683
684
685
686
        extractTo->setIcon(extractAction->icon());
        extractTo->setToolTip(extractAction->toolTip());

        if (extractAction == m_extractArchiveAction) {
            connect(extractTo, &QAction::triggered,
                    this, &Part::slotExtractArchive);
        } else {
            connect(extractTo, &QAction::triggered,
                    this, &Part::slotShowExtractionDialog);
        }
687

688
        menu->addSeparator();
689

690
        QAction *header = menu->addAction(i18n("Quick Extract To..."));
691
        header->setEnabled(false);
692
        header->setIcon(QIcon::fromTheme(QStringLiteral("archive-extract")));
693
694
    }

695
    while (menu->actions().size() > 3) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
696
        menu->removeAction(menu->actions().constLast());
697
698
    }

699
700
    const KConfigGroup conf(KSharedConfig::openConfig(), "ExtractDialog");
    const QStringList dirHistory = conf.readPathEntry("DirHistory", QStringList());
701
702

    for (int i = 0; i < qMin(10, dirHistory.size()); ++i) {
703
        const QString dir = QUrl(dirHistory.value(i)).toString(QUrl::RemoveScheme | QUrl::NormalizePathSegments | QUrl::PreferLocalFile);
704
705
706
707
        if (QDir(dir).exists()) {
            QAction *newAction = menu->addAction(dir);
            newAction->setData(dir);
        }
708
    }
709
710
711
712
}

void Part::slotQuickExtractFiles(QAction *triggeredAction)
{
713
714
715
    // #190507: triggeredAction->data.isNull() means it's the "Extract to..."
    //          action, and we do not want it to run here
    if (!triggeredAction->data().isNull()) {
716
        QString userDestination = triggeredAction->data().toString();
717
718
        QString finalDestinationDirectory;
        const QString detectedSubfolder = detectSubfolder();
719
        qCDebug(ARK) << "Detected subfolder" << detectedSubfolder;
720

721
        if (m_model->archive()->hasMultipleTopLevelEntries()) {
722
723
724
725
            if (!userDestination.endsWith(QDir::separator())) {
                userDestination.append(QDir::separator());
            }
            finalDestinationDirectory = userDestination + detectedSubfolder;
726
            QDir(userDestination).mkdir(detectedSubfolder);
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
727
728
729
        } else {
            finalDestinationDirectory = userDestination;
        }
730

731
        qCDebug(ARK) << "Extracting to:" << finalDestinationDirectory;
732

Ragnar Thomsen's avatar
Ragnar Thomsen committed
733
        ExtractJob *job = m_model->extractFiles(filesAndRootNodesForIndexes(addChildren(getSelectedIndexes())), finalDestinationDirectory, ExtractionOptions());
734
735
        registerJob(job);

Laurent Montel's avatar
Laurent Montel committed
736
737
        connect(job, &KJob::result,
                this, &Part::slotExtractionDone);
738
739
740

        job->start();
    }
Henrique Pinto's avatar
Henrique Pinto committed
741
}
742

743
744
void Part::selectionChanged()
{
Ragnar Thomsen's avatar
Ragnar Thomsen committed
745
746
747
748
749
750
    m_infoPanel->setIndexes(getSelectedIndexes());
}

QModelIndexList Part::getSelectedIndexes()
{
    QModelIndexList list;
751
752
    const auto selectedRows = m_view->selectionModel()->selectedRows();
    for (const QModelIndex &i : selectedRows) {
Ragnar Thomsen's avatar
Ragnar Thomsen committed
753
754
755
        list.append(m_filterModel->mapToSource(i));
    }
    return list;
756
757
}

758
759
760
void Part::readCompressionOptions()
{
    // Store options from CreateDialog if they are set.
Laurent Montel's avatar
Laurent Montel committed
761
    if (!m_compressionOptions.isCompressionLevelSet() && arguments().metaData().contains(QStringLiteral("compressionLevel"))) {
762
763
        m_compressionOptions.setCompressionLevel(arguments().metaData()[QStringLiteral("compressionLevel")].toInt());
    }
Laurent Montel's avatar
Laurent Montel committed
764
    if (m_compressionOptions.compressionMethod().isEmpty() && arguments().metaData().contains(QStringLiteral("compressionMethod"))) {
765
766
        m_compressionOptions.setCompressionMethod(arguments().metaData()[QStringLiteral("compressionMethod")]);
    }
Laurent Montel's avatar
Laurent Montel committed
767
    if (m_compressionOptions.encryptionMethod().isEmpty() && arguments().metaData().contains(QStringLiteral("encryptionMethod"))) {
768
769
        m_compressionOptions.setEncryptionMethod(arguments().metaData()[QStringLiteral("encryptionMethod")]);
    }
Laurent Montel's avatar
Laurent Montel committed
770
    if (!m_compressionOptions.isVolumeSizeSet() && arguments().metaData().contains(QStringLiteral("volumeSize"))) {
771
772
773
774
775
776
777
778
779
780
        m_compressionOptions.setVolumeSize(arguments().metaData()[QStringLiteral("volumeSize")].toULong());
    }

    const auto compressionMethods = m_model->archive()->property("compressionMethods").toStringList();
    qCDebug(ARK) << "compmethods:" << compressionMethods;
    if (compressionMethods.size() == 1) {
        m_compressionOptions.setCompressionMethod(compressionMethods.first());
    }
}

Henrique Pinto's avatar
Henrique Pinto committed
781
782
bool Part::openFile()
{
783
    qCDebug(ARK) << "Attempting to open archive" << localFilePath();
784

785
786
    resetGui();

787
    if (!isLocalFileValid()) {
788
789
790
        return false;
    }

791
    if (isCreatingNewArchive()) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
792
        createArchive();
793
        return true;
794
795
    }

796
797
798
    loadArchive();
    // Loading is async, we don't know yet whether we got a valid archive.
    return false;
Henrique Pinto's avatar
Henrique Pinto committed
799
800
801
802
}

bool Part::saveFile()
{
803
    return true;
Henrique Pinto's avatar
Henrique Pinto committed
804
}
805

806
807
808
809
810
bool Part::isBusy() const
{
    return m_busy;
}

811
812
813
814
815
816
817
KConfigSkeleton *Part::config() const
{
    return ArkSettings::self();
}

QList<Kerfuffle::SettingsPage*> Part::settingsPages(QWidget *parent) const
{
818
    QList<SettingsPage*> pages;
819
820
821
822
    pages.append(new GeneralSettingsPage(parent, i18nc("@title:tab", "General"), QStringLiteral("utilities-file-archiver")));
    pages.append(new ExtractionSettingsPage(parent, i18nc("@title:tab", "Extraction"), QStringLiteral("preferences-desktop-icons")));
    pages.append(new PluginSettingsPage(parent, i18nc("@title:tab", "Plugins"), QStringLiteral("preferences-plugin")));
    pages.append(new PreviewSettingsPage(parent, i18nc("@title:tab", "Previews"), QStringLiteral("image-jpeg")));
823
824
825
826

    return pages;
}

827
828
829
830
831
832
833
834
835
836
837
838
bool Part::isLocalFileValid()
{
    const QString localFile = localFilePath();
    const QFileInfo localFileInfo(localFile);

    if (localFileInfo.isDir()) {
        displayMsgWidget(KMessageWidget::Error, xi18nc("@info",
                                                       "<filename>%1</filename> is a directory.",
                                                       localFile));
        return false;
    }

839
    if (isCreatingNewArchive()) {
840
        if (localFileInfo.exists()) {
841
842
843
844
            if (!confirmAndDelete(localFile)) {
                displayMsgWidget(KMessageWidget::Error, xi18nc("@info",
                                                               "Could not overwrite <filename>%1</filename>. Check whether you have write permission.",
                                                               localFile));
845
846
847
                return false;
            }
        }
848

849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
        displayMsgWidget(KMessageWidget::Information, xi18nc("@info", "The archive <filename>%1</filename> will be created as soon as you add a file.", localFile));
    } else {
        if (!localFileInfo.exists()) {
            displayMsgWidget(KMessageWidget::Error, xi18nc("@info", "The archive <filename>%1</filename> was not found.", localFile));
            return false;
        }

        if (!localFileInfo.isReadable()) {
            displayMsgWidget(KMessageWidget::Error, xi18nc("@info", "The archive <filename>%1</filename> could not be loaded, as it was not possible to read from it.", localFile));
            return false;
        }
    }

    return true;
}

865
866
867
868
869
870
871
872
bool Part::confirmAndDelete(const QString &targetFile)
{
    QFileInfo targetInfo(targetFile);
    const auto buttonCode = KMessageBox::warningYesNo(widget(),
                                                      xi18nc("@info",
                                                             "The archive <filename>%1</filename> already exists. Do you wish to overwrite it?",
                                                             targetInfo.fileName()),
                                                      i18nc("@title:window", "File Exists"),
873
                                                      KStandardGuiItem::overwrite(),
874
875
876
877
878
879
880
881
882
883
884
                                                      KStandardGuiItem::cancel());

    if (buttonCode != KMessageBox::Yes || !targetInfo.isWritable()) {
        return false;
    }

    qCDebug(ARK) << "Removing file" << targetFile;

    return QFile(targetFile).remove();
}

885
void Part::slotCompleted()
886
{
887
888
889
890
    if (isCreatingNewArchive()) {
        m_view->setDropsEnabled(true);
        updateActions();
        return;
Raphael Kubo da Costa's avatar
Raphael Kubo da Costa committed
891
    }
892

893
    // Existing archive, setup the view for it.
894
    m_view->sortByColumn(0, Qt::AscendingOrder);
895
    m_view->expandIfSingleFolder();
896
    m_view->header()->resizeSections(QHeaderView::ResizeToContents);
897
    m_view->setDropsEnabled(isArchiveWritable());
898
899

    if (!m_model->archive()->comment().isEmpty()) {
900
        m_commentView->setPlainText(m_model->archive()->comment());
901
        slotShowComment();
902
903
904
905
    } else {
        m_commentView->clear();
        m_commentBox->hide();
    }
906

907
908
909
910
    if (m_model->rowCount() == 0) {
        qCWarning(ARK) << "No entry listed by the plugin";
        displayMsgWidget(KMessageWidget::Warning, xi18nc("@info", "The archive is empty or Ark could not open its content."));
    } else if (m_model->rowCount() == 1) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
911
        if (m_model->archive()->mimeType().inherits(QStringLiteral("application/x-cd-image")) &&
912
            m_model->entryForIndex(m_model->index(0, 0))->fullPath() == QLatin1String("README.TXT")) {
913
914
915
916
            qCWarning(ARK) << "Detected ISO image with UDF filesystem";
            displayMsgWidget(KMessageWidget::Warning, xi18nc("@info", "Ark does not currently support ISO files with UDF filesystem."));
        }
    }
917
918
919
920

    if (arguments().metaData()[QStringLiteral("showExtractDialog")] == QLatin1String("true")) {
        QTimer::singleShot(0, this, &Part::slotShowExtractionDialog);
    }
921
922
923
924
925
926
927
928
929
930
931
932
933

    updateActions();
}

void Part::slotLoadingStarted()
{
    m_model->filesToMove.clear();
    m_model->filesToCopy.clear();
}

void Part::slotLoadingFinished(KJob *job)
{
    if (!job->error()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
934
        Q_EMIT completed();
935
936
937
938
        return;
    }

    // Loading failed or was canceled by the user (e.g. password dialog rejected).
Alexander Lohnau's avatar
Alexander Lohnau committed
939
    Q_EMIT canceled(job->errorString());
940
941
    resetArchive();

942
943
944
945
946
    if (job->error() != KJob::KilledJobError) {
        displayMsgWidget(KMessageWidget::Error, xi18nc("@info", "Loading the archive <filename>%1</filename> failed with the following error:<nl/><message>%2</message>",
                                                       localFilePath(),
                                                       job->errorString()));
    }
947
948
949
950
}

void Part::setReadyGui()
{
951
952
    QApplication::restoreOverrideCursor();
    m_busy = false;
953
954
955
956
957

    if (m_statusBarExtension->statusBar()) {
        m_statusBarExtension->statusBar()->hide();
    }

958
959
    m_view->setEnabled(true);
    updateActions();
960
961
962
963
}

void Part::setBusyGui()
{
964
965
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    m_busy = true;
966
967
968
969
970

    if (m_statusBarExtension->statusBar()) {
        m_statusBarExtension->statusBar()->show();
    }

971
972
    m_view->setEnabled(false);
    updateActions();
973