diff --git a/addons/project/kateprojecttreeviewcontextmenu.cpp b/addons/project/kateprojecttreeviewcontextmenu.cpp
--- a/addons/project/kateprojecttreeviewcontextmenu.cpp
+++ b/addons/project/kateprojecttreeviewcontextmenu.cpp
@@ -76,7 +76,7 @@
* Copy Path
*/
QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy File Path"));
-
+
/**
* Handle "open with",
* find correct mimetype to query for possible applications
@@ -104,7 +104,7 @@
/**
* File Properties Dialog
*/
- auto filePropertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-object-properties")), i18n("Properties"));
+ auto filePropertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-object-properties")), i18n("File Properties"));
/**
* Git menu
diff --git a/kate/CMakeLists.txt b/kate/CMakeLists.txt
--- a/kate/CMakeLists.txt
+++ b/kate/CMakeLists.txt
@@ -20,6 +20,7 @@
kateconfigdialog.cpp
kateconfigplugindialogpage.cpp
katedocmanager.cpp
+ katefileactions.cpp
katemainwindow.cpp
katepluginmanager.cpp
kateviewmanager.cpp
diff --git a/kate/data/kateui.rc b/kate/data/kateui.rc
--- a/kate/data/kateui.rc
+++ b/kate/data/kateui.rc
@@ -20,6 +20,17 @@
+
+
diff --git a/kate/katefileactions.h b/kate/katefileactions.h
new file mode 100644
--- /dev/null
+++ b/kate/katefileactions.h
@@ -0,0 +1,93 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2018 Gregor Mi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3, or any
+ * later version accepted by the membership of KDE e.V. (or its
+ * successor approved by the membership of KDE e.V.), which shall
+ * act as a proxy defined in Section 6 of version 3 of the license.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+**/
+
+#ifndef KATE_FILEACTIONS_H
+#define KATE_FILEACTIONS_H
+
+#include
+#include
+
+#include
+
+class QWidget;
+namespace KTextEditor
+{
+ class Document;
+}
+
+namespace KateFileActions
+{
+ /**
+ * Copies the file path to clipboard.
+ * If the document has no file, the clipboard will be emptied.
+ */
+ void copyFilePathToClipboard(KTextEditor::Document* document);
+
+ /**
+ * Tries to open and highlight the underlying url in the filemanager
+ */
+ void openContainingFolder(KTextEditor::Document* document);
+
+ /**
+ * Shows a Rename dialog to rename the file associated with the document.
+ * The document will be closed an reopened.
+ *
+ * Nothing is done if the document is nullptr or has no associated file.
+ *
+ * TODO: code was copied and adapted from ../addons/filetree/katefiletree.cpp
+ * (-> DUPLICATE CODE, the new code here should be also used there!)
+ */
+ void renameDocumentFile(QWidget* parent, KTextEditor::Document* document);
+
+ void openFilePropertiesDialog(KTextEditor::Document* document);
+
+ /**
+ * Asks the user if the file should really be deleted. If yes, the file
+ * is deleted from disk and the document closed.
+ *
+ * Nothing is done if the document is nullptr or has no associated file.
+ *
+ * TODO: code was copied and adapted from ../addons/filetree/katefiletree.cpp
+ * (-> DUPLICATE CODE, the new code here should be also used there!)
+ */
+ void deleteDocumentFile(QWidget* parent, KTextEditor::Document* document);
+
+ /**
+ * @returns a list of supported diff tools and if they are installed or not.
+ * pair.first is the filename of the executable and pair.second
+ * is true if the program is installed.
+ */
+ std::vector> supportedDiffTools();
+
+ /**
+ * Runs an external program to compare the underlying files of two given documents.
+ *
+ * @param diffExecutable tested to work with "kdiff3", "kompare", and "meld"
+ * (@see supportedDiffTools())
+ *
+ * TODO: handling when documentA or B is empty (currently this is prevented elsewhere)
+ * TODO: message if diffExecutable is not installed
+ * IDEA for later: compare with unsaved buffer data instead of file?
+ */
+ void compareWithExternalProgram(KTextEditor::Document* documentA, KTextEditor::Document* documentB, const QString& diffExecutable);
+}
+
+#endif
diff --git a/kate/katefileactions.cpp b/kate/katefileactions.cpp
new file mode 100644
--- /dev/null
+++ b/kate/katefileactions.cpp
@@ -0,0 +1,163 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2018 Gregor Mi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3, or any
+ * later version accepted by the membership of KDE e.V. (or its
+ * successor approved by the membership of KDE e.V.), which shall
+ * act as a proxy defined in Section 6 of version 3 of the license.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+**/
+
+#include "katefileactions.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+void KateFileActions::copyFilePathToClipboard(KTextEditor::Document* doc)
+{
+ QApplication::clipboard()->setText(doc->url().toDisplayString(QUrl::PreferLocalFile));
+}
+
+void KateFileActions::openContainingFolder(KTextEditor::Document* doc)
+{
+ KIO::highlightInFileManager({doc->url()});
+}
+
+void KateFileActions::openFilePropertiesDialog(KTextEditor::Document* doc)
+{
+ KFileItem fileItem(doc->url());
+ QDialog* dlg = new KPropertiesDialog(fileItem);
+ dlg->setAttribute(Qt::WA_DeleteOnClose);
+ dlg->show();
+}
+
+void KateFileActions::renameDocumentFile(QWidget* parent, KTextEditor::Document* doc)
+{
+ if (!doc) {
+ return;
+ }
+
+ const QUrl oldFileUrl = doc->url();
+
+ if (oldFileUrl.isEmpty()) { // NEW
+ return;
+ }
+
+ const QString oldFileName = doc->url().fileName();
+ bool ok = false;
+ QString newFileName = QInputDialog::getText(parent, // ADAPTED
+ i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok);
+ if (!ok) {
+ return;
+ }
+
+ QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
+ newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName);
+
+ if (!newFileUrl.isValid()) {
+ return;
+ }
+
+ if (!doc->closeUrl()) {
+ return;
+ }
+
+ doc->waitSaveComplete();
+
+ KIO::CopyJob* job = KIO::move(oldFileUrl, newFileUrl);
+ QSharedPointer sc(new QMetaObject::Connection());
+ auto success = [doc, sc] (KIO::Job*, const QUrl&, const QUrl &realNewFileUrl, const QDateTime&, bool, bool) {
+ doc->openUrl(realNewFileUrl);
+ doc->documentSavedOrUploaded(doc, true);
+ QObject::disconnect(*sc);
+ };
+ *sc = parent->connect(job, &KIO::CopyJob::copyingDone, doc, success);
+
+ if (!job->exec()) {
+ KMessageBox::sorry(parent, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString()));
+ doc->openUrl(oldFileUrl);
+ }
+}
+
+void KateFileActions::deleteDocumentFile(QWidget* parent, KTextEditor::Document* doc)
+{
+ if (!doc) {
+ return;
+ }
+
+ const auto&& url = doc->url();
+
+ if (url.isEmpty()) { // NEW
+ return;
+ }
+
+ bool go = (KMessageBox::warningContinueCancel(parent,
+ i18n("Do you really want to delete file \"%1\"?", url.toDisplayString()),
+ i18n("Delete file"),
+ KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("filetreedeletefile")
+ ) == KMessageBox::Continue);
+
+ if (!go) {
+ return;
+ }
+
+ if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) {
+ return; // no extra message, the internals of ktexteditor should take care of that.
+ }
+
+ if (url.isValid()) {
+ KIO::DeleteJob *job = KIO::del(url);
+ if (!job->exec()) {
+ KMessageBox::sorry(parent, i18n("File \"%1\" could not be deleted.", url.toDisplayString()));
+ }
+ }
+}
+
+std::vector> KateFileActions::supportedDiffTools()
+{
+ std::vector> resultList;
+
+ // TODO: check for program existence and adapt boolean value accordingly
+
+ resultList.push_back({ QStringLiteral("kdiff3"), true });
+ resultList.push_back({ QStringLiteral("kompare"), true });
+ resultList.push_back({ QStringLiteral("meld"), true });
+
+ return resultList;
+}
+
+void KateFileActions::compareWithExternalProgram(KTextEditor::Document* documentA, KTextEditor::Document* documentB, const QString& diffExecutable)
+{
+ // TODO: error handling, e.g. if executable not found
+
+ QProcess process;
+ QStringList arguments;
+ arguments << documentA->url().toLocalFile() << documentB->url().toLocalFile();
+ process.startDetached(diffExecutable, arguments);
+}
diff --git a/kate/katemainwindow.h b/kate/katemainwindow.h
--- a/kate/katemainwindow.h
+++ b/kate/katemainwindow.h
@@ -190,6 +190,7 @@
void slotEditToolbars();
void slotNewToolbarConfig();
void slotUpdateOpenWith();
+ void slotUpdateActionsNeedingUrl();
void slotOpenDocument(QUrl);
void slotDropEvent(QDropEvent *);
diff --git a/kate/katemainwindow.cpp b/kate/katemainwindow.cpp
--- a/kate/katemainwindow.cpp
+++ b/kate/katemainwindow.cpp
@@ -37,6 +37,7 @@
#include "kateupdatedisabler.h"
#include "katedebug.h"
#include "katecolorschemechooser.h"
+#include "katefileactions.h"
#include
#include
@@ -306,6 +307,64 @@
connect(a, SIGNAL(triggered()), KateApp::self()->documentManager(), SLOT(reloadAll()));
a->setWhatsThis(i18n("Reload all open documents."));
+ a = actionCollection()->addAction(QStringLiteral("file_copy_filepath"));
+ a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
+ a->setText(i18n("Copy File &Path"));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ auto&& view = viewManager()->activeView();
+ KateFileActions::copyFilePathToClipboard(view->document());
+ });
+ a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));
+
+ a = actionCollection()->addAction(QStringLiteral("file_open_containing_folder"));
+ a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder")));
+ a->setText(i18n("&Open Containing Folder"));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ auto&& view = viewManager()->activeView();
+ KateFileActions::openContainingFolder(view->document());
+ });
+ a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));
+
+ a = actionCollection()->addAction(QStringLiteral("file_rename"));
+ a->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
+ a->setText(i18nc("@action:inmenu", "Rename File..."));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ auto&& view = viewManager()->activeView();
+ KateFileActions::renameDocumentFile(this, view->document());
+ });
+ a->setWhatsThis(i18n("Renames the file belonging to the current document."));
+
+ a = actionCollection()->addAction(QStringLiteral("file_delete"));
+ a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete-shred")));
+ a->setText(i18nc("@action:inmenu", "Delete File"));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ auto&& view = viewManager()->activeView();
+ KateFileActions::deleteDocumentFile(this, view->document());
+ });
+ a->setWhatsThis(i18n("Deletes the file belonging to the current document."));
+
+ a = actionCollection()->addAction(QStringLiteral("file_properties"));
+ a->setIcon(QIcon::fromTheme(QStringLiteral("dialog-object-properties")));
+ a->setText(i18n("File Properties"));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ auto&& view = viewManager()->activeView();
+ KateFileActions::openFilePropertiesDialog(view->document());
+ });
+ a->setWhatsThis(i18n("Deletes the file belonging to the current document."));
+
+ a = actionCollection()->addAction(QStringLiteral("file_compare"));
+ a->setText(i18n("Compare"));
+ connect(a, &QAction::triggered, KateApp::self()->documentManager(),
+ [this]() {
+ QMessageBox::information(this, i18n("Compare"), i18n("Use the Tabbar context menu to compare two documents"));
+ });
+ a->setWhatsThis(i18n("Shows a hint how to compare documents."));
+
a = actionCollection()->addAction(QStringLiteral("file_close_orphaned"));
a->setText(i18n("Close Orphaned"));
connect(a, SIGNAL(triggered()), KateApp::self()->documentManager(), SLOT(closeOrphaned()));
@@ -368,6 +427,7 @@
connect(m_viewManager, SIGNAL(viewChanged(KTextEditor::View*)), this, SLOT(slotWindowActivated()));
connect(m_viewManager, SIGNAL(viewChanged(KTextEditor::View*)), this, SLOT(slotUpdateOpenWith()));
+ connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl);
connect(m_viewManager, SIGNAL(viewChanged(KTextEditor::View*)), this, SLOT(slotUpdateBottomViewBar()));
// re-route signals to our wrapper
@@ -669,6 +729,21 @@
}
}
+void KateMainWindow::slotUpdateActionsNeedingUrl()
+{
+ bool hasUrl = false;
+ auto&& view = viewManager()->activeView();
+ if (view) {
+ hasUrl = !view->document()->url().isEmpty();
+ }
+
+ action("file_copy_filepath")->setEnabled(hasUrl);
+ action("file_open_containing_folder")->setEnabled(hasUrl);
+ action("file_rename")->setEnabled(hasUrl);
+ action("file_delete")->setEnabled(hasUrl);
+ action("file_properties")->setEnabled(hasUrl);
+}
+
void KateMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (!event->mimeData()) {
diff --git a/kate/kateviewspace.cpp b/kate/kateviewspace.cpp
--- a/kate/kateviewspace.cpp
+++ b/kate/kateviewspace.cpp
@@ -23,6 +23,7 @@
#include "kateviewmanager.h"
#include "katedocmanager.h"
#include "kateapp.h"
+#include "katefileactions.h"
#include "katesessionmanager.h"
#include "katedebug.h"
#include "katetabbar.h"
@@ -32,12 +33,12 @@
#include
#include
#include
-#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -584,31 +585,90 @@
KTextEditor::Document *doc = m_docToTabId.key(id);
Q_ASSERT(doc);
+ auto addActionFromCollection = [this](QMenu* menu, const char* action_name) {
+ QAction* action = m_viewManager->mainWindow()->action(action_name);
+ return menu->addAction(action->icon(), action->text());
+ };
+
QMenu menu(this);
QAction *aCloseTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("&Close Document"));
QAction *aCloseOthers = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close-other")), i18n("Close Other &Documents"));
menu.addSeparator();
- QAction *aCopyPath = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy File &Path"));
- QAction *aOpenFolder = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("&Open Containing Folder"));
+ QAction *aCopyPath = addActionFromCollection(&menu, "file_copy_filepath");
+ QAction *aOpenFolder = addActionFromCollection(&menu, "file_open_containing_folder");
+ QAction *aFileProperties = addActionFromCollection(&menu, "file_properties");
+ menu.addSeparator();
+ QAction *aRenameFile = addActionFromCollection(&menu, "file_rename");
+ QAction *aDeleteFile = addActionFromCollection(&menu, "file_delete");
+ menu.addSeparator();
+ QMenu *mCompareWithActive = new QMenu(i18n("Compare with active document"), &menu);
+ mCompareWithActive->setIcon(QIcon::fromTheme(QStringLiteral("kompare")));
+ menu.addMenu(mCompareWithActive);
if (KateApp::self()->documentManager()->documentList().count() < 2) {
aCloseOthers->setEnabled(false);
}
+
if (doc->url().isEmpty()) {
aCopyPath->setEnabled(false);
aOpenFolder->setEnabled(false);
+ aRenameFile->setEnabled(false);
+ aDeleteFile->setEnabled(false);
+ aFileProperties->setEnabled(false);
+ mCompareWithActive->setEnabled(false);
+ }
+
+ auto activeDocument = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()->document(); // used for mCompareWithActive which is used with another tab which is not active
+ // both documents must have urls and must not be the same to have the compare feature enabled
+ if (activeDocument->url().isEmpty() || activeDocument == doc) {
+ mCompareWithActive->setEnabled(false);
+ }
+
+ if (mCompareWithActive->isEnabled()) {
+ auto&& supportedDiffTools = KateFileActions::supportedDiffTools();
+ if (std::all_of(supportedDiffTools.begin(), supportedDiffTools.end(),
+ [](std::pair& tool) { return !tool.second; })) {
+ QAction *naAction = mCompareWithActive->addAction(i18n("None of the supported diff tools is installed. Click for help."));
+ naAction->setData(QStringLiteral("help"));
+ } else {
+ for (auto&& diffTool : KateFileActions::supportedDiffTools()) {
+ if (diffTool.second) {
+ QAction *compareAction = mCompareWithActive->addAction(diffTool.first);
+ compareAction->setData(diffTool.first);
+ }
+ }
+ }
}
QAction *choice = menu.exec(globalPos);
+ if (!choice) {
+ return;
+ }
+
if (choice == aCloseTab) {
closeTabRequest(id);
} else if (choice == aCloseOthers) {
KateApp::self()->documentManager()->closeOtherDocuments(doc);
} else if (choice == aCopyPath) {
- QApplication::clipboard()->setText(doc->url().toDisplayString(QUrl::PreferLocalFile));
+ KateFileActions::copyFilePathToClipboard(doc);
} else if (choice == aOpenFolder) {
- KIO::highlightInFileManager({doc->url()});
+ KateFileActions::openContainingFolder(doc);
+ } else if (choice == aFileProperties) {
+ KateFileActions::openFilePropertiesDialog(doc);
+ } else if (choice == aRenameFile) {
+ KateFileActions::renameDocumentFile(this, doc);
+ } else if (choice == aDeleteFile) {
+ KateFileActions::deleteDocumentFile(this, doc);
+ } else if (choice->parent() == mCompareWithActive) {
+ QString actionData = choice->data().toString(); // "help" or name of the executable of the diff program
+ if (actionData == QStringLiteral("help")) {
+ QMessageBox::information(this, i18n("Help on Diff Tool"),
+ i18n("To use this feature, please install one of these programs: TODO: list of supportedPrograms"),
+ QMessageBox::StandardButton::Ok);
+ } else {
+ KateFileActions::compareWithExternalProgram(activeDocument, doc, actionData);
+ }
}
}