Add "Rename" to file system view

Renames the file on disk, notifies editors, and tries to change the file
in all open projects that had it (and support renaming).

Task-number: QTCREATORBUG-19209
Change-Id: I165e9468c7235f9f503a3820bda3eb00f3c086d0
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
This commit is contained in:
Eike Ziller
2017-11-29 16:18:01 +01:00
parent fe038630fb
commit 0c9c747d92
6 changed files with 142 additions and 10 deletions

View File

@@ -25,17 +25,22 @@
#include "foldernavigationwidget.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
#include "projectexplorericons.h"
#include "projectnodes.h"
#include "projecttree.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/diffservice.h>
#include <coreplugin/documentmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <coreplugin/fileiconprovider.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/fileiconprovider.h>
#include <coreplugin/fileutils.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <extensionsystem/pluginmanager.h>
@@ -44,8 +49,8 @@
#include <utils/algorithm.h>
#include <utils/filecrumblabel.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <utils/navigationtreeview.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QAction>
@@ -57,6 +62,7 @@
#include <QFileSystemModel>
#include <QHeaderView>
#include <QMenu>
#include <QMessageBox>
#include <QScrollBar>
#include <QSize>
#include <QTimer>
@@ -68,6 +74,7 @@ const int ID_ROLE = Qt::UserRole + 1;
const int SORT_ROLE = Qt::UserRole + 2;
const char PROJECTSDIRECTORYROOT_ID[] = "A.Projects";
const char C_FOLDERNAVIGATIONWIDGET[] = "ProjectExplorer.FolderNavigationWidget";
namespace ProjectExplorer {
namespace Internal {
@@ -111,8 +118,10 @@ class FolderNavigationModel : public QFileSystemModel
{
public:
explicit FolderNavigationModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::DropActions supportedDragActions() const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final;
Qt::DropActions supportedDragActions() const final;
Qt::ItemFlags flags(const QModelIndex &index) const final;
bool setData(const QModelIndex &index, const QVariant &value, int role) final;
};
FolderNavigationModel::FolderNavigationModel(QObject *parent) : QFileSystemModel(parent)
@@ -131,6 +140,72 @@ Qt::DropActions FolderNavigationModel::supportedDragActions() const
return Qt::MoveAction;
}
Qt::ItemFlags FolderNavigationModel::flags(const QModelIndex &index) const
{
if (index.isValid() && !fileInfo(index).isRoot())
return QFileSystemModel::flags(index) | Qt::ItemIsEditable;
return QFileSystemModel::flags(index);
}
static QVector<FolderNode *> renamableFolderNodes(const Utils::FileName &before,
const Utils::FileName &after)
{
QVector<FolderNode *> folderNodes;
ProjectTree::forEachNode([&](Node *node) {
if (node->nodeType() == NodeType::File && node->filePath() == before
&& node->parentFolderNode()
&& node->parentFolderNode()->renameFile(before.toString(), after.toString())) {
folderNodes.append(node->parentFolderNode());
}
});
return folderNodes;
}
bool FolderNavigationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
QTC_ASSERT(index.isValid() && parent(index).isValid() && index.column() == 0
&& role == Qt::EditRole && value.canConvert<QString>(),
return false);
const QString afterFileName = value.toString();
const QString beforeFilePath = filePath(index);
const QString parentPath = filePath(parent(index));
const QString afterFilePath = parentPath + '/' + afterFileName;
if (beforeFilePath == afterFilePath)
return false;
// need to rename through file system model, which takes care of not changing our selection
const bool success = QFileSystemModel::setData(index, value, role);
// for files we can do more than just rename on disk, for directories the user is on his/her own
if (success && fileInfo(index).isFile()) {
Core::DocumentManager::renamedFile(beforeFilePath, afterFilePath);
const QVector<FolderNode *> folderNodes
= renamableFolderNodes(Utils::FileName::fromString(beforeFilePath),
Utils::FileName::fromString(afterFilePath));
QVector<FolderNode *> failedNodes;
for (FolderNode *folder : folderNodes) {
if (!folder->canRenameFile(beforeFilePath, afterFilePath))
failedNodes.append(folder);
}
if (!failedNodes.isEmpty()) {
const QString projects
= Utils::transform<QList>(failedNodes,
[](FolderNode *n) {
return n->managingProject()->filePath().fileName();
})
.join(", ");
const QString errorMessage
= tr("The file \"%1\" was renamed to \"%2\", "
"but the following projects could not be automatically changed: %3")
.arg(beforeFilePath, afterFilePath, projects);
QTimer::singleShot(0, Core::ICore::instance(), [errorMessage] {
QMessageBox::warning(Core::ICore::dialogParent(),
ProjectExplorerPlugin::tr("Project Editing Failed"),
errorMessage);
});
}
}
return success;
}
static void showOnlyFirstColumn(QTreeView *view)
{
const int columnCount = view->header()->count();
@@ -166,6 +241,11 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
m_rootSelector(new QComboBox),
m_crumbLabel(new DelayedFileCrumbLabel(this))
{
m_context = new Core::IContext(this);
m_context->setContext(Core::Context(C_FOLDERNAVIGATIONWIDGET));
m_context->setWidget(this);
Core::ICore::addContextObject(m_context);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
m_fileSystemModel->setResolveSymlinks(false);
@@ -179,6 +259,7 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
setHiddenFilesFilter(false);
m_listView->setIconSize(QSize(16,16));
m_listView->setModel(m_fileSystemModel);
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_listView->setDragEnabled(true);
m_listView->setDragDropMode(QAbstractItemView::DragOnly);
showOnlyFirstColumn(m_listView);
@@ -248,6 +329,11 @@ FolderNavigationWidget::FolderNavigationWidget(QWidget *parent) : QWidget(parent
setAutoSynchronization(true);
}
FolderNavigationWidget::~FolderNavigationWidget()
{
Core::ICore::removeContextObject(m_context);
}
void FolderNavigationWidget::toggleAutoSynchronization()
{
setAutoSynchronization(!m_autoSync);
@@ -302,6 +388,13 @@ void FolderNavigationWidget::removeRootDirectory(const QString &id)
setCurrentEditor(Core::EditorManager::currentEditor());
}
void FolderNavigationWidget::editCurrentItem()
{
const QModelIndex current = m_listView->currentIndex();
if (m_fileSystemModel->flags(current) & Qt::ItemIsEditable)
m_listView->edit(current);
}
bool FolderNavigationWidget::autoSynchronization() const
{
return m_autoSync;
@@ -465,8 +558,10 @@ void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
fakeEntry.document = &document;
Core::EditorManager::addNativeDirAndOpenWithActions(&menu, &fakeEntry);
if (hasCurrentItem && !isDir) {
if (Core::DiffService::instance()) {
if (hasCurrentItem) {
if (m_fileSystemModel->flags(current) & Qt::ItemIsEditable)
menu.addAction(Core::ActionManager::command(Constants::RENAMEFILE)->action());
if (!isDir && Core::DiffService::instance()) {
menu.addAction(
TextEditor::TextDocument::createDiffAgainstCurrentFileAction(&menu, [filePath]() {
return filePath;
@@ -535,6 +630,7 @@ FolderNavigationWidgetFactory::FolderNavigationWidgetFactory()
&Core::DocumentManager::projectsDirectoryChanged,
this,
&FolderNavigationWidgetFactory::updateProjectsDirectoryRoot);
registerActions();
}
Core::NavigationView FolderNavigationWidgetFactory::createWidget()
@@ -616,6 +712,19 @@ void FolderNavigationWidgetFactory::updateProjectsDirectoryRoot()
Utils::Icons::PROJECT.icon()});
}
void FolderNavigationWidgetFactory::registerActions()
{
Core::Context context(C_FOLDERNAVIGATIONWIDGET);
auto rename = new QAction(this);
Core::ActionManager::registerAction(rename, Constants::RENAMEFILE, context);
connect(rename, &QAction::triggered, Core::ICore::instance(), [] {
Core::IContext *context = Core::ICore::currentContextObject();
QWidget *widget = context ? context->widget() : nullptr;
if (auto navWidget = qobject_cast<FolderNavigationWidget *>(widget))
navWidget->editCurrentItem();
});
}
int DelayedFileCrumbLabel::immediateHeightForWidth(int w) const
{
return Utils::FileCrumbLabel::heightForWidth(w);

View File

@@ -31,7 +31,10 @@
#include <QIcon>
#include <QWidget>
namespace Core { class IEditor; }
namespace Core {
class IContext;
class IEditor;
}
namespace Utils {
class NavigationTreeView;
@@ -79,6 +82,8 @@ signals:
private:
static int rootIndex(const QString &id);
void updateProjectsDirectoryRoot();
void registerActions();
static QVector<RootDirectory> m_rootDirectories;
};
@@ -88,6 +93,7 @@ class FolderNavigationWidget : public QWidget
Q_PROPERTY(bool autoSynchronization READ autoSynchronization WRITE setAutoSynchronization)
public:
explicit FolderNavigationWidget(QWidget *parent = nullptr);
~FolderNavigationWidget();
static QStringList projectFilesInDirectory(const QString &path);
@@ -100,6 +106,8 @@ public:
void insertRootDirectory(const FolderNavigationWidgetFactory::RootDirectory &directory);
void removeRootDirectory(const QString &id);
void editCurrentItem();
protected:
void contextMenuEvent(QContextMenuEvent *ev) override;
@@ -115,6 +123,7 @@ private:
void openProjectsInDirectory(const QModelIndex &index);
void setCrumblePath(const QModelIndex &index, const QModelIndex &);
Core::IContext *m_context = nullptr;
Utils::NavigationTreeView *m_listView = nullptr;
QFileSystemModel *m_fileSystemModel = nullptr;
QAction *m_filterHiddenFilesAction = nullptr;

View File

@@ -211,7 +211,6 @@ const char OPENTERMIANLHERE[] = "ProjectExplorer.OpenTerminalHere";
const char REMOVEFILE[] = "ProjectExplorer.RemoveFile";
const char DUPLICATEFILE[] = "ProjectExplorer.DuplicateFile";
const char DELETEFILE[] = "ProjectExplorer.DeleteFile";
const char RENAMEFILE[] = "ProjectExplorer.RenameFile";
const char DIFFFILE[] = "ProjectExplorer.DiffFile";
const char SETSTARTUP[] = "ProjectExplorer.SetStartup";
const char PROJECTTREE_COLLAPSE_ALL[] = "ProjectExplorer.CollapseAll";

View File

@@ -36,6 +36,8 @@ const char MODE_SESSION[] = "Project";
// Actions
const char BUILD[] = "ProjectExplorer.Build";
const char STOP[] = "ProjectExplorer.Stop";
const char RENAMEFILE[] = "ProjectExplorer.RenameFile";
// Context
const char C_PROJECT_TREE[] = "ProjectExplorer.ProjectTreeContext";

View File

@@ -392,6 +392,17 @@ bool ProjectTree::hasNode(const Node *node)
});
}
void ProjectTree::forEachNode(const std::function<void(Node *)> &task)
{
const QList<Project *> projects = SessionManager::projects();
for (Project *project : projects) {
if (ProjectNode *projectNode = project->rootProjectNode()) {
task(projectNode);
projectNode->forEachGenericNode(task);
}
}
}
void ProjectTree::hideContextMenu()
{
m_focusForContextMenu = nullptr;

View File

@@ -73,6 +73,8 @@ public:
static bool hasNode(const Node *node);
static void forEachNode(const std::function<void(Node *)> &task);
void collapseAll();
// for nodes to emit signals, do not call unless you are a node