28 #include "dnddbusinterfaceadaptor.h"
38 #include <KActionCollection>
39 #include <KApplication>
40 #include <KConfigGroup>
42 #include <KFileDialog>
45 #include <KIO/NetAccess>
47 #include <KInputDialog>
48 #include <KMessageBox>
49 #include <KPluginFactory>
51 #include <KSelectAction>
52 #include <KStandardDirs>
53 #include <KStandardGuiItem>
55 #include <KToggleAction>
59 #include <QHeaderView>
62 #include <QMouseEvent>
63 #include <QScopedPointer>
66 #include <QVBoxLayout>
67 #include <QWeakPointer>
68 #include <QtDBus/QtDBus>
70 using namespace Kerfuffle;
73 K_EXPORT_PLUGIN(Factory(
"ark"))
80 Part::Part(QWidget *parentWidget,
QObject *parent,
const QVariantList& args)
81 : KParts::ReadWritePart(parent),
87 setComponentData(Factory::componentData(),
false);
89 new DndExtractAdaptor(
this);
91 const QString pathName = QString(QLatin1String(
"/DndExtract/%1")).arg(s_instanceCounter++);
92 if (!QDBusConnection::sessionBus().registerObject(pathName,
this)) {
93 kFatal() <<
"Could not register a D-Bus object for drag'n'drop";
98 m_splitter =
new QSplitter(Qt::Horizontal, parentWidget);
99 setWidget(m_splitter);
104 m_splitter->addWidget(m_view);
105 m_splitter->addWidget(m_infoPanel);
108 if (splitterSizes.isEmpty()) {
109 splitterSizes.append(200);
110 splitterSizes.append(100);
112 m_splitter->setSizes(splitterSizes);
117 connect(m_model, SIGNAL(loadingStarted()),
118 this, SLOT(slotLoadingStarted()));
119 connect(m_model, SIGNAL(loadingFinished(
KJob*)),
120 this, SLOT(slotLoadingFinished(
KJob*)));
121 connect(m_model, SIGNAL(droppedFiles(QStringList,QString)),
122 this, SLOT(slotAddFiles(QStringList,QString)));
123 connect(m_model, SIGNAL(error(QString,QString)),
124 this, SLOT(slotError(QString,QString)));
126 connect(
this, SIGNAL(
busy()),
127 this, SLOT(setBusyGui()));
128 connect(
this, SIGNAL(
ready()),
129 this, SLOT(setReadyGui()));
130 connect(
this, SIGNAL(completed()),
131 this, SLOT(setFileNameFromArchive()));
133 m_statusBarExtension =
new KParts::StatusBarExtension(
this);
135 setXMLFile(QLatin1String(
"ark_part.rc" ));
142 m_extractFilesAction->menu()->deleteLater();
145 void Part::registerJob(
KJob* job)
149 m_statusBarExtension->addStatusBarItem(m_jobTracker->widget(0), 0,
true);
150 m_jobTracker->widget(job)->show();
152 m_jobTracker->registerJob(job);
155 connect(job, SIGNAL(result(
KJob*)),
this, SIGNAL(
ready()));
163 kDebug() <<
"Extract to " << localPath;
168 if (m_view->selectionModel()->selectedRows().count() != 1) {
169 m_view->selectionModel()->setCurrentIndex(m_view->currentIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
171 if (m_view->selectionModel()->selectedRows().count() != 1) {
175 QVariant internalRoot;
176 kDebug() <<
"valid " << m_view->currentIndex().parent().isValid();
177 if (m_view->currentIndex().parent().isValid()) {
181 if (internalRoot.isNull()) {
190 QList<QVariant> files = selectedFilesWithChildren();
191 if (files.isEmpty()) {
195 kDebug() <<
"selected files are " << files;
197 options[QLatin1String(
"PreservePaths" )] =
true;
198 if (!internalRoot.isNull()) {
199 options[QLatin1String(
"RootNode")] = internalRoot;
205 connect(job, SIGNAL(result(
KJob*)),
206 this, SLOT(slotExtractionDone(
KJob*)));
211 void Part::setupView()
213 m_view->setModel(m_model);
215 m_view->setSortingEnabled(
true);
217 connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
218 this, SLOT(updateActions()));
219 connect(m_view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
220 this, SLOT(selectionChanged()));
223 connect(m_view, SIGNAL(itemTriggered(QModelIndex)),
224 this, SLOT(slotPreview(QModelIndex)));
226 connect(m_model, SIGNAL(columnsInserted(QModelIndex,
int,
int)),
227 this, SLOT(adjustColumns()));
230 void Part::setupActions()
232 KToggleAction *showInfoPanelAction =
new KToggleAction(i18nc(
"@action:inmenu",
"Show information panel"),
this);
233 actionCollection()->addAction(QLatin1String(
"show-infopanel" ), showInfoPanelAction);
234 showInfoPanelAction->setChecked(m_splitter->sizes().at(1) > 0);
235 connect(showInfoPanelAction, SIGNAL(triggered(
bool)),
236 this, SLOT(slotToggleInfoPanel(
bool)));
238 m_saveAsAction = KStandardAction::saveAs(
this, SLOT(slotSaveAs()), actionCollection());
240 m_previewAction = actionCollection()->addAction(QLatin1String(
"preview" ));
241 m_previewAction->setText(i18nc(
"to preview a file inside an archive",
"Pre&view"));
242 m_previewAction->setIcon(KIcon( QLatin1String(
"document-preview-archive" )));
243 m_previewAction->setStatusTip(i18n(
"Click to preview the selected file"));
244 connect(m_previewAction, SIGNAL(triggered(
bool)),
245 this, SLOT(slotPreview()));
247 m_extractFilesAction = actionCollection()->addAction(QLatin1String(
"extract" ));
248 m_extractFilesAction->setText(i18n(
"E&xtract"));
249 m_extractFilesAction->setIcon(KIcon( QLatin1String(
"archive-extract" )));
250 m_extractFilesAction->setStatusTip(i18n(
"Click to open an extraction dialog, where you can choose to extract either all files or just the selected ones"));
251 m_extractFilesAction->setShortcut(QKeySequence( QLatin1String(
"Ctrl+E" ) ));
252 connect(m_extractFilesAction, SIGNAL(triggered(
bool)),
253 this, SLOT(slotExtractFiles()));
255 m_addFilesAction = actionCollection()->addAction(QLatin1String(
"add" ));
256 m_addFilesAction->setIcon(KIcon( QLatin1String(
"archive-insert" )));
257 m_addFilesAction->setText(i18n(
"Add &File..."));
258 m_addFilesAction->setStatusTip(i18n(
"Click to add files to the archive"));
259 connect(m_addFilesAction, SIGNAL(triggered(
bool)),
260 this, SLOT(slotAddFiles()));
262 m_addDirAction = actionCollection()->addAction(QLatin1String(
"add-dir" ));
263 m_addDirAction->setIcon(KIcon( QLatin1String(
"archive-insert-directory" )));
264 m_addDirAction->setText(i18n(
"Add Fo&lder..."));
265 m_addDirAction->setStatusTip(i18n(
"Click to add a folder to the archive"));
266 connect(m_addDirAction, SIGNAL(triggered(
bool)),
267 this, SLOT(slotAddDir()));
269 m_deleteFilesAction = actionCollection()->addAction(QLatin1String(
"delete" ));
270 m_deleteFilesAction->setIcon(KIcon( QLatin1String(
"archive-remove" )));
271 m_deleteFilesAction->setText(i18n(
"De&lete"));
272 m_deleteFilesAction->setShortcut(Qt::Key_Delete);
273 m_deleteFilesAction->setStatusTip(i18n(
"Click to delete the selected files"));
274 connect(m_deleteFilesAction, SIGNAL(triggered(
bool)),
275 this, SLOT(slotDeleteFiles()));
280 void Part::updateActions()
284 m_previewAction->setEnabled(!
isBusy() && (m_view->selectionModel()->selectedRows().count() == 1)
285 && isPreviewable(m_view->selectionModel()->currentIndex()));
286 m_extractFilesAction->setEnabled(!
isBusy() && (m_model->
rowCount() > 0));
287 m_addFilesAction->setEnabled(!
isBusy() && isWritable);
288 m_addDirAction->setEnabled(!
isBusy() && isWritable);
289 m_deleteFilesAction->setEnabled(!
isBusy() && (m_view->selectionModel()->selectedRows().count() > 0)
292 QMenu *menu = m_extractFilesAction->menu();
295 m_extractFilesAction->setMenu(menu);
296 connect(menu, SIGNAL(triggered(QAction*)),
297 this, SLOT(slotQuickExtractFiles(QAction*)));
302 QAction *extractTo = menu->addAction(i18n(
"Extract To..."));
303 extractTo->setIcon(m_extractFilesAction->icon());
304 extractTo->setStatusTip(m_extractFilesAction->statusTip());
305 connect(extractTo, SIGNAL(triggered(
bool)), SLOT(slotExtractFiles()));
307 menu->addSeparator();
309 QAction *header = menu->addAction(i18n(
"Quick Extract To..."));
310 header->setEnabled(
false);
311 header->setIcon(KIcon( QLatin1String(
"archive-extract" )));
314 while (menu->actions().size() > 3) {
315 menu->removeAction(menu->actions().last());
318 const KConfigGroup conf(KGlobal::config(),
"DirSelect Dialog");
319 const QStringList dirHistory = conf.readPathEntry(
"History Items", QStringList());
321 for (
int i = 0; i < qMin(10, dirHistory.size()); ++i) {
322 const KUrl dirUrl(dirHistory.at(i));
323 QAction *newAction = menu->addAction(dirUrl.pathOrUrl());
324 newAction->setData(dirUrl.pathOrUrl());
328 void Part::slotQuickExtractFiles(QAction *triggeredAction)
332 if (!triggeredAction->data().isNull()) {
333 kDebug() <<
"Extract to " << triggeredAction->data().toString();
335 const QString userDestination = triggeredAction->data().toString();
336 QString finalDestinationDirectory;
337 const QString detectedSubfolder = detectSubfolder();
339 if (!isSingleFolderArchive()) {
340 finalDestinationDirectory = userDestination +
341 QDir::separator() + detectedSubfolder;
342 QDir(userDestination).mkdir(detectedSubfolder);
344 finalDestinationDirectory = userDestination;
348 options[QLatin1String(
"PreservePaths" )] =
true;
349 QList<QVariant> files = selectedFiles();
353 connect(job, SIGNAL(result(
KJob*)),
354 this, SLOT(slotExtractionDone(
KJob*)));
360 bool Part::isPreviewable(
const QModelIndex& index)
const
365 void Part::selectionChanged()
367 m_infoPanel->
setIndexes(m_view->selectionModel()->selectedRows());
372 return new KAboutData(
"ark", 0, ki18n(
"ArkPart"),
"3.0");
377 const QString localFile(localFilePath());
378 const QFileInfo localFileInfo(localFile);
379 const bool creatingNewArchive =
380 arguments().metaData()[QLatin1String(
"createNewArchive")] == QLatin1String(
"true");
382 if (localFileInfo.isDir()) {
383 KMessageBox::error(NULL, i18nc(
"@info",
384 "<filename>%1</filename> is a directory.",
389 if (creatingNewArchive) {
390 if (localFileInfo.exists()) {
391 int overwrite = KMessageBox::questionYesNo(NULL, i18nc(
"@info",
"The archive <filename>%1</filename> already exists. Would you like to open it instead?", localFile), i18nc(
"@title:window",
"File Exists"), KGuiItem(i18n(
"Open File")), KStandardGuiItem::cancel());
393 if (overwrite == KMessageBox::No) {
398 if (!localFileInfo.exists()) {
399 KMessageBox::sorry(NULL, i18nc(
"@info",
"The archive <filename>%1</filename> was not found.", localFile), i18nc(
"@title:window",
"Error Opening Archive"));
406 if ((!archive) || ((creatingNewArchive) && (archive->isReadOnly()))) {
407 QStringList mimeTypeList;
408 QHash<QString, QString> mimeTypes;
410 if (creatingNewArchive) {
416 foreach(
const QString& mime, mimeTypeList) {
417 KMimeType::Ptr mimePtr(KMimeType::mimeType(mime));
420 mimeTypes[mime] = mimePtr->comment();
424 QStringList mimeComments(mimeTypes.values());
430 if (creatingNewArchive) {
431 item = KInputDialog::getItem(i18nc(
"@title:window",
"Invalid Archive Type"),
432 i18nc(
"@info",
"Ark cannot create archives of the type you have chosen.<nl/><nl/>Please choose another archive type below."),
433 mimeComments, 0,
false, &ok);
435 item = KInputDialog::getItem(i18nc(
"@title:window",
"Unable to Determine Archive Type"),
436 i18nc(
"@info",
"Ark was unable to determine the archive type of the filename.<nl/><nl/>Please choose the correct archive type below."),
443 if ((!ok) || (item.isEmpty())) {
451 KMessageBox::sorry(NULL, i18nc(
"@info",
"Ark was not able to open the archive <filename>%1</filename>. No plugin capable of handling the file was found.", localFile), i18nc(
"@title:window",
"Error Opening Archive"));
458 m_infoPanel->
setIndex(QModelIndex());
460 if (arguments().metaData()[QLatin1String(
"showExtractDialog" )] == QLatin1String(
"true" )) {
461 QTimer::singleShot(0,
this, SLOT(slotExtractFiles()));
477 void Part::slotLoadingStarted()
481 void Part::slotLoadingFinished(
KJob *job)
486 if (arguments().metaData()[QLatin1String(
"createNewArchive" )] != QLatin1String(
"true" )) {
487 KMessageBox::sorry(NULL, i18nc(
"@info",
"Loading the archive <filename>%1</filename> failed with the following error: <message>%2</message>", localFilePath(), job->errorText()), i18nc(
"@title:window",
"Error Opening Archive"));
491 m_view->sortByColumn(0, Qt::AscendingOrder);
492 m_view->expandToDepth(0);
495 m_view->header()->resizeSections(QHeaderView::ResizeToContents);
500 void Part::setReadyGui()
503 QApplication::restoreOverrideCursor();
505 m_view->setEnabled(
true);
509 void Part::setBusyGui()
512 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
514 m_view->setEnabled(
false);
518 void Part::setFileNameFromArchive()
520 const QString prettyName = url().fileName();
525 emit setWindowCaption(prettyName);
528 void Part::slotPreview()
530 slotPreview(m_view->selectionModel()->currentIndex());
533 void Part::slotPreview(
const QModelIndex & index)
535 if (!isPreviewable(index)) {
541 if (!entry.isEmpty()) {
543 options[QLatin1String(
"PreservePaths" )] =
true;
547 connect(job, SIGNAL(result(
KJob*)),
548 this, SLOT(slotPreviewExtracted(
KJob*)));
553 void Part::slotPreviewExtracted(
KJob *job)
560 m_model->
entryForIndex(m_view->selectionModel()->currentIndex());
563 m_previewDir.name() + QLatin1Char(
'/') + entry[
FileName].toString();
568 fullName.remove(QLatin1String(
"../"));
572 KMessageBox::error(widget(), job->errorString());
577 void Part::slotError(
const QString& errorMessage,
const QString& details)
579 if (details.isEmpty()) {
580 KMessageBox::error(widget(), errorMessage);
582 KMessageBox::detailedError(widget(), errorMessage, details);
586 bool Part::isSingleFolderArchive()
const
591 QString Part::detectSubfolder()
const
600 void Part::slotExtractFiles()
608 if (m_view->selectionModel()->selectedRows().count() > 0) {
612 dialog.data()->setSingleFolderArchive(isSingleFolderArchive());
613 dialog.data()->setSubfolder(detectSubfolder());
615 dialog.data()->setCurrentUrl(QFileInfo(m_model->
archive()->
fileName()).path());
617 if (dialog.data()->exec()) {
625 if (!dialog.data()->extractAllFiles()) {
626 files = selectedFilesWithChildren();
629 kDebug() <<
"Selected " << files;
633 if (dialog.data()->preservePaths()) {
634 options[QLatin1String(
"PreservePaths")] =
true;
637 options[QLatin1String(
"FollowExtractionDialogSettings")] =
true;
639 const QString destinationDirectory = dialog.data()->destinationDirectory().pathOrUrl();
643 connect(job, SIGNAL(result(
KJob*)),
644 this, SLOT(slotExtractionDone(
KJob*)));
649 delete dialog.data();
652 QList<QVariant> Part::selectedFilesWithChildren()
656 QModelIndexList toIterate = m_view->selectionModel()->selectedRows();
658 for (
int i = 0; i < toIterate.size(); ++i) {
659 QModelIndex index = toIterate.at(i);
661 for (
int j = 0; j < m_model->
rowCount(index); ++j) {
662 QModelIndex child = m_model->
index(j, 0, index);
663 if (!toIterate.contains(child)) {
670 foreach(
const QModelIndex & index, toIterate) {
672 if (entry.contains(InternalID)) {
679 QList<QVariant> Part::selectedFiles()
683 foreach(
const QModelIndex & index, m_view->selectionModel()->selectedRows()) {
690 foreach(
const QString &i, toSort) {
696 void Part::slotExtractionDone(
KJob* job)
700 KMessageBox::error(widget(), job->errorString());
703 Q_ASSERT(extractJob);
705 const bool followExtractionDialogSettings =
706 extractJob->
extractionOptions().value(QLatin1String(
"FollowExtractionDialogSettings"),
false).toBool();
707 if (!followExtractionDialogSettings) {
714 destinationDirectory.cleanPath();
716 KRun::runUrl(destinationDirectory, QLatin1String(
"inode/directory"), widget());
725 void Part::adjustColumns()
729 m_view->header()->setResizeMode(0, QHeaderView::ResizeToContents);
732 void Part::slotAddFiles(
const QStringList& filesToAdd,
const QString& path)
734 if (filesToAdd.isEmpty()) {
738 kDebug() <<
"Adding " << filesToAdd <<
" to " << path;
739 kDebug() <<
"Warning, for now the path argument is not implemented";
741 QStringList cleanFilesToAdd(filesToAdd);
742 for (
int i = 0; i < cleanFilesToAdd.size(); ++i) {
743 QString& file = cleanFilesToAdd[i];
744 if (QFileInfo(file).isDir()) {
745 if (!file.endsWith(QLatin1Char(
'/' ))) {
746 file += QLatin1Char(
'/' );
753 QString firstPath = cleanFilesToAdd.first();
754 if (firstPath.right(1) == QLatin1String(
"/" )) {
757 firstPath = QFileInfo(firstPath).dir().absolutePath();
759 kDebug() <<
"Detected relative path to be " << firstPath;
760 options[QLatin1String(
"GlobalWorkDir" )] = firstPath;
767 connect(job, SIGNAL(result(
KJob*)),
768 this, SLOT(slotAddFilesDone(
KJob*)));
773 void Part::slotAddFiles()
786 const QStringList filesToAdd =
787 KFileDialog::getOpenFileNames(KUrl(
"kfiledialog:///ArkAddFiles"),
788 QString(), widget()->parentWidget(),
789 i18nc(
"@title:window",
"Add Files"));
791 slotAddFiles(filesToAdd);
794 void Part::slotAddDir()
797 const QString dirToAdd = KFileDialog::getExistingDirectory(KUrl(
"kfiledialog:///ArkAddFiles"), widget(), i18nc(
"@title:window",
"Add Folder"));
799 if (!dirToAdd.isEmpty()) {
800 slotAddFiles(QStringList() << dirToAdd);
804 void Part::slotAddFilesDone(
KJob* job)
808 KMessageBox::error(widget(), job->errorString());
812 void Part::slotDeleteFilesDone(
KJob* job)
816 KMessageBox::error(widget(), job->errorString());
820 void Part::slotDeleteFiles()
824 const int reallyDelete =
825 KMessageBox::questionYesNo(NULL,
826 i18n(
"Deleting these files is not undoable. Are you sure you want to do this?"),
827 i18nc(
"@title:window",
"Delete files"),
828 KStandardGuiItem::del(),
829 KStandardGuiItem::cancel(),
831 KMessageBox::Dangerous | KMessageBox::Notify);
833 if (reallyDelete == KMessageBox::No) {
838 connect(job, SIGNAL(result(
KJob*)),
839 this, SLOT(slotDeleteFilesDone(
KJob*)));
844 void Part::slotToggleInfoPanel(
bool visible)
846 QList<int> splitterSizes;
851 splitterSizes = m_splitter->sizes();
853 splitterSizes[1] = 0;
856 m_splitter->setSizes(splitterSizes);
860 void Part::saveSplitterSizes()
866 void Part::slotSaveAs()
868 KUrl saveUrl = KFileDialog::getSaveUrl(KUrl(QLatin1String(
"kfiledialog:///ArkSaveAs/" ) + url().fileName()), QString(), widget());
870 if ((saveUrl.isValid()) && (!saveUrl.isEmpty())) {
871 if (KIO::NetAccess::exists(saveUrl, KIO::NetAccess::DestinationSide, widget())) {
872 int overwrite = KMessageBox::warningContinueCancel(widget(),
873 i18nc(
"@info",
"An archive named <filename>%1</filename> already exists. Are you sure you want to overwrite it?", saveUrl.fileName()),
875 KStandardGuiItem::overwrite());
877 if (overwrite != KMessageBox::Continue) {
882 KUrl srcUrl = KUrl::fromPath(localFilePath());
884 if (!QFile::exists(localFilePath())) {
885 if (url().isLocalFile()) {
886 KMessageBox::error(widget(),
887 i18nc(
"@info",
"The archive <filename>%1</filename> cannot be copied to the specified location. The archive does not exist anymore.", localFilePath()));
895 KIO::Job *copyJob = KIO::file_copy(srcUrl, saveUrl, -1, KIO::Overwrite);
897 if (!KIO::NetAccess::synchronousRun(copyJob, widget())) {
898 KMessageBox::error(widget(),
899 i18nc(
"@info",
"The archive could not be saved as <filename>%1</filename>. Try saving it to another location.", saveUrl.pathOrUrl()));
Kerfuffle::ExtractJob * extractFile(const QVariant &fileName, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
static void view(const QString &fileName, QWidget *parent=0)
The entry is a directory.
QStringList supportedWriteMimeTypes()
static bool openDestinationFolderAfterExtraction()
Get Open destination folder after extraction.
void setPrettyFileName(const QString &fileName)
Sets a different file name for the current open archive.
void setIndex(const QModelIndex &)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const
Kerfuffle::AddJob * addFiles(const QStringList &paths, const Kerfuffle::CompressionOptions &options=Kerfuffle::CompressionOptions())
void extractSelectedFilesTo(const QString &localPath)
static KAboutData * createAboutData()
QHash< int, QVariant > ArchiveEntry
void updateWithDefaults()
QStringList supportedMimeTypes()
bool isSingleFolderArchive()
static ArkSettings * self()
KJob * setArchive(Kerfuffle::Archive *archive)
QHash< QString, QVariant > CompressionOptions
These are the extra options for doing the compression.
Kerfuffle::Archive * archive() const
Kerfuffle::DeleteJob * deleteFiles(const QList< QVariant > &files)
static void setSplitterSizes(const QList< int > &v)
Set splitterSizes.
static void setSplitterSizesWithBothWidgets(const QList< int > &v)
Set splitterSizesWithBothWidgets.
QHash< QString, QVariant > ExtractionOptions
int rowCount(const QModelIndex &parent=QModelIndex()) const
void setIndexes(const QModelIndexList &list)
static QList< int > splitterSizes()
Get splitterSizes.
Kerfuffle::ExtractJob * extractFiles(const QList< QVariant > &files, const QString &destinationDir, const Kerfuffle::ExtractionOptions options=Kerfuffle::ExtractionOptions()) const
The entry's ID for Ark's internal manipulation.
static quint32 s_instanceCounter
static QList< int > splitterSizesWithBothWidgets()
Get splitterSizesWithBothWidgets.
static bool closeAfterExtraction()
Get Close Ark after extraction.
Kerfuffle::ArchiveEntry entryForIndex(const QModelIndex &index)