From 6c12a060290ec094faea22fadfaf6aa7fb2d5db2 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 15 Oct 2012 11:53:22 +0200 Subject: [PATCH] Editor: Added Dialog for read only files. Task-number: QTCREATORBUG-2851 Change-Id: Ic47a5a1833650e31b4e27d0a01259d6b398a6415 Reviewed-by: Orgad Shaneh Reviewed-by: Christian Stenger Reviewed-by: Eike Ziller --- src/libs/utils/fileutils.cpp | 6 + src/libs/utils/fileutils.h | 1 + src/plugins/coreplugin/coreplugin.pro | 3 + src/plugins/coreplugin/coreplugin.qbs | 3 + .../dialogs/readonlyfilesdialog.cpp | 491 ++++++++++++++++++ .../coreplugin/dialogs/readonlyfilesdialog.h | 109 ++++ .../coreplugin/dialogs/readonlyfilesdialog.ui | 145 ++++++ src/plugins/coreplugin/documentmanager.cpp | 88 +--- src/plugins/coreplugin/documentmanager.h | 12 +- .../editormanager/editormanager.cpp | 33 +- .../perforce/perforceversioncontrol.cpp | 2 +- src/plugins/qt4projectmanager/qt4nodes.cpp | 26 +- src/plugins/texteditor/basefilefind.cpp | 20 + src/plugins/texteditor/refactoringchanges.cpp | 16 + 14 files changed, 836 insertions(+), 119 deletions(-) create mode 100644 src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp create mode 100644 src/plugins/coreplugin/dialogs/readonlyfilesdialog.h create mode 100644 src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 5ad3fb809e6..d5193bdad8d 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -218,6 +218,12 @@ QString FileUtils::shortNativePath(const FileName &path) return path.toUserOutput(); } +bool FileUtils::makeWritable(const FileName &path) +{ + const QString fileName = path.toString(); + return QFile::setPermissions(fileName, QFile::permissions(fileName) | QFile::WriteUser); +} + QByteArray FileReader::fetchQrc(const QString &fileName) { QTC_ASSERT(fileName.startsWith(QLatin1Char(':')), return QByteArray()); diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index dec02723aca..4477938be55 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -96,6 +96,7 @@ public: static bool isFileNewerThan(const FileName &filePath, const QDateTime &timeStamp); static FileName resolveSymlinks(const FileName &path); static QString shortNativePath(const FileName &path); + static bool makeWritable(const FileName &path); }; class QTCREATOR_UTILS_EXPORT FileReader diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index c2a89de0ccb..64b3cbc20f7 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -49,6 +49,7 @@ SOURCES += mainwindow.cpp \ dialogs/settingsdialog.cpp \ actionmanager/commandmappings.cpp \ dialogs/shortcutsettings.cpp \ + dialogs/readonlyfilesdialog.cpp \ dialogs/openwithdialog.cpp \ progressmanager/progressmanager.cpp \ progressmanager/progressview.cpp \ @@ -134,6 +135,7 @@ HEADERS += mainwindow.h \ dialogs/newdialog.h \ dialogs/settingsdialog.h \ actionmanager/commandmappings.h \ + dialogs/readonlyfilesdialog.h \ dialogs/shortcutsettings.h \ dialogs/openwithdialog.h \ dialogs/iwizard.h \ @@ -202,6 +204,7 @@ HEADERS += mainwindow.h \ FORMS += dialogs/newdialog.ui \ actionmanager/commandmappings.ui \ dialogs/saveitemsdialog.ui \ + dialogs/readonlyfilesdialog.ui \ dialogs/openwithdialog.ui \ generalsettings.ui \ dialogs/externaltoolconfig.ui \ diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index 8501f6940a7..fc98dfaa4c1 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -187,6 +187,9 @@ QtcPlugin { "dialogs/openwithdialog.ui", "dialogs/promptoverwritedialog.cpp", "dialogs/promptoverwritedialog.h", + "dialogs/readonlyfilesdialog.cpp", + "dialogs/readonlyfilesdialog.h", + "dialogs/readonlyfilesdialog.ui", "dialogs/saveitemsdialog.cpp", "dialogs/saveitemsdialog.h", "dialogs/saveitemsdialog.ui", diff --git a/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp new file mode 100644 index 00000000000..1f0d05c9803 --- /dev/null +++ b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "fileiconprovider.h" +#include "readonlyfilesdialog.h" +#include "ui_readonlyfilesdialog.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Core { +namespace Internal { + +class ReadOnlyFilesDialogPrivate +{ +public: + ReadOnlyFilesDialogPrivate(IDocument *document = 0, bool useSaveAs = false); + ~ReadOnlyFilesDialogPrivate(); + + // Buttongroups containing the operation for one file. + struct ButtonGroupForFile + { + QString fileName; + QButtonGroup *group; + }; + QList buttonGroups; + + QMap setAllIndexForOperation; + // The version control systems for every file, if the file isn't in VCS the value is 0. + QHash versionControls; + + // Define if some specific operations should be allowed to make the files writable. + const bool useSaveAs; + bool useVCS; + + // Define if an error should be displayed when an operation fails. + bool showWarnings; + QString failWarning; + + // The document is necessary for the Save As operation. + IDocument *document; + + // Operation text for the tree widget header and combo box entries for + // modifying operations for all files. + const QString mixedText; + QString makeWritableText; + QString versionControlOpenText; + const QString saveAsText; +}; + +ReadOnlyFilesDialogPrivate::ReadOnlyFilesDialogPrivate(IDocument *document, bool displaySaveAs) + : useSaveAs(displaySaveAs) + , useVCS(false) + , showWarnings(false) + , document(document) + , mixedText(QApplication::translate("ReadOnlyFilesDialog", "Mixed")) + , makeWritableText(QApplication::translate("ReadOnlyFilesDialog", "Make Writable")) + , versionControlOpenText(QApplication::translate("ReadOnlyFilesDialog", "Open With VCS")) + , saveAsText(QApplication::translate("ReadOnlyFilesDialog", "Save As")) +{} + +ReadOnlyFilesDialogPrivate::~ReadOnlyFilesDialogPrivate() +{ + foreach (const ButtonGroupForFile &groupForFile, buttonGroups) + delete groupForFile.group; +} + +/*! + * \class ReadOnlyFilesDialog + * \brief Dialog to show a set of files which are classified as not writable. + * + * Automatically checks which operations are allowed to make the file writable. These operations + * are Make Writable which tries to set the file permissions in the file system, + * Open With Version Control System if the open operation is allowed by the version control system + * and Save As which is used to save the changes to a document in another file. + */ + +ReadOnlyFilesDialog::ReadOnlyFilesDialog(const QList &fileNames, QWidget *parent) + : QDialog(parent) + , d(new ReadOnlyFilesDialogPrivate) + , ui(new Ui::ReadOnlyFilesDialog) +{ + initDialog(fileNames); +} + +ReadOnlyFilesDialog::ReadOnlyFilesDialog(const QString &fileName, QWidget *parent) + : QDialog(parent) + , d(new ReadOnlyFilesDialogPrivate) + , ui(new Ui::ReadOnlyFilesDialog) +{ + initDialog(QStringList() << fileName); +} + +ReadOnlyFilesDialog::ReadOnlyFilesDialog(IDocument *document, QWidget *parent, + bool displaySaveAs) + : QDialog(parent) + , d(new ReadOnlyFilesDialogPrivate(document, displaySaveAs)) + , ui(new Ui::ReadOnlyFilesDialog) +{ + initDialog(QStringList() << document->fileName()); +} + +ReadOnlyFilesDialog::ReadOnlyFilesDialog(const QList documents, QWidget *parent) + : QDialog(parent) + , d(new ReadOnlyFilesDialogPrivate) + , ui(new Ui::ReadOnlyFilesDialog) +{ + QStringList files; + foreach (IDocument *document, documents) + files << document->fileName(); + initDialog(files); +} + +ReadOnlyFilesDialog::~ReadOnlyFilesDialog() +{ + delete ui; + delete d; +} + +/*! + * \brief Set a user defined message in the dialog. + * \internal + */ +void ReadOnlyFilesDialog::setMessage(const QString &message) +{ + ui->msgLabel->setText(message); +} + +/*! + * \brief Enable the error output to the user via a messageBox. + * \param warning Added to the dialog, should show possible consequences if the file is still read only. + * \internal + */ +void ReadOnlyFilesDialog::setShowFailWarning(bool show, const QString &warning) +{ + d->showWarnings = show; + d->failWarning = warning; +} + +/*! + * \brief Opens a message box with an error description according to the type. + * \internal + */ +void ReadOnlyFilesDialog::promptFailWarning(const QStringList &files, ReadOnlyResult type) const +{ + if (files.isEmpty()) + return; + QString title; + QString message; + QString details; + if (files.count() == 1) { + const QString file = files.first(); + switch (type) { + case RO_OpenVCS: { + if (IVersionControl *vc = d->versionControls[file]) { + const QString openText = vc->vcsOpenText().remove(QLatin1Char('&')); + title = tr("Failed To: %1 File").arg(openText); + message = tr("%1 file %2 from version control system %3 failed.\n") + .arg(openText) + .arg(QDir::toNativeSeparators(file)) + .arg(vc->displayName()); + message += d->failWarning; + } else { + title = tr("No Version Control System Found"); + message = tr("Cannot open file %1 from version control system.\n" + "No version control system found.\n") + .arg(QDir::toNativeSeparators(file)); + message += d->failWarning; + } + break; + } + case RO_MakeWritable: + title = tr("Cannot Set Permissions"); + message = tr("Cannot set permissions for %1 to writable.\n") + .arg(QDir::toNativeSeparators(file)); + message += d->failWarning; + break; + case RO_SaveAs: + title = tr("Cannot Save File"); + message = tr("Cannot save file %1\n").arg(QDir::toNativeSeparators(file)); + message += d->failWarning; + break; + default: + title = tr("Canceled Changing Permissions!"); + message = d->failWarning; + break; + } + } else { + title = tr("Could Not Change Permissions On Some Files!"); + message = d->failWarning; + message += tr("\nSee details for a complete list of files."); + details = files.join(QLatin1String("\n")); + } + QMessageBox msgBox(QMessageBox::Warning, title, message); + msgBox.setDetailedText(details); + msgBox.exec(); +} + +/*! + * \brief Executes the dialog. + * \return ReadOnlyResult which gives information about the operation which was used to make the files writable. + * \internal + * + * Also displays an error dialog when some operations can't be executed and the function + * setShowFailWarning was called. + */ +int ReadOnlyFilesDialog::exec() +{ + if (QDialog::exec() != QDialog::Accepted) + return RO_Cancel; + + ReadOnlyResult result; + QStringList failedToMakeWritable; + foreach (ReadOnlyFilesDialogPrivate::ButtonGroupForFile buttengroup, d->buttonGroups) { + result = static_cast(buttengroup.group->checkedId()); + switch (result) { + case RO_MakeWritable: + if (!Utils::FileUtils::makeWritable(Utils::FileName(QFileInfo(buttengroup.fileName)))) { + failedToMakeWritable << buttengroup.fileName; + continue; + } + break; + case RO_OpenVCS: + if (!d->versionControls[buttengroup.fileName]->vcsOpen(buttengroup.fileName)) { + failedToMakeWritable << buttengroup.fileName; + continue; + } + break; + case RO_SaveAs: + if (!EditorManager::instance()->saveDocumentAs(d->document)) { + failedToMakeWritable << buttengroup.fileName; + continue; + } + break; + default: + failedToMakeWritable << buttengroup.fileName; + continue; + } + if (!QFileInfo(buttengroup.fileName).isWritable()) + failedToMakeWritable << buttengroup.fileName; + } + if (!failedToMakeWritable.isEmpty()) { + if (d->showWarnings) + promptFailWarning(failedToMakeWritable, result); + } + return failedToMakeWritable.isEmpty() ? result : RO_Cancel; +} + +/*! + * \brief Creates a radio button in the column specified with type. + * \param group the created button will be added to this group. + * \return the created button. + * \internal + */ +QRadioButton* ReadOnlyFilesDialog::createRadioButtonForItem(QTreeWidgetItem *item, QButtonGroup *group, + ReadOnlyFilesDialog::ReadOnlyFilesTreeColumn type) + +{ + QRadioButton *radioButton = new QRadioButton(this); + group->addButton(radioButton, type); + item->setTextAlignment(type, Qt::AlignHCenter); + ui->treeWidget->setItemWidget(item, type, radioButton); + return radioButton; +} + +/*! + * \brief Checks the type of the select all combo box and change the user selection per file accordingly. + * \internal + */ +void ReadOnlyFilesDialog::setAll(int index) +{ + // If mixed is the current index, no need to change the user selection. + if (index == d->setAllIndexForOperation[-1/*mixed*/]) + return; + + // Get the selected type from the select all combo box. + ReadOnlyFilesTreeColumn type; + if (index == d->setAllIndexForOperation[MakeWritable]) + type = MakeWritable; + else if (index == d->setAllIndexForOperation[OpenWithVCS]) + type = OpenWithVCS; + else if (index == d->setAllIndexForOperation[SaveAs]) + type = SaveAs; + + // Check for every file if the selected operation is available and change it to the operation. + foreach (ReadOnlyFilesDialogPrivate::ButtonGroupForFile groupForFile, d->buttonGroups) { + QRadioButton *radioButton = qobject_cast (groupForFile.group->button(type)); + if (radioButton) + radioButton->setChecked(true); + } +} + +/*! + * \brief Updates the select all combo box depending on the selection in the tree widget the user made. + * \internal + */ +void ReadOnlyFilesDialog::updateSelectAll() +{ + int selectedOperation = -1; + foreach (ReadOnlyFilesDialogPrivate::ButtonGroupForFile groupForFile, d->buttonGroups) { + if (selectedOperation == -1) { + selectedOperation = groupForFile.group->checkedId(); + } else if (selectedOperation != groupForFile.group->checkedId()) { + ui->setAll->setCurrentIndex(0); + return; + } + } + ui->setAll->setCurrentIndex(d->setAllIndexForOperation[selectedOperation]); +} + +/*! + * \brief Adds files to the dialog and check for possible operation to make the file writable. + * \param fileNames A List of files which should be added to the dialog. + * \internal + */ +void ReadOnlyFilesDialog::initDialog(const QStringList &fileNames) +{ + ui->setupUi(this); + ui->buttonBox->addButton(tr("&Change Permission"), QDialogButtonBox::AcceptRole); + ui->buttonBox->addButton(QDialogButtonBox::Cancel); + + QString vcsOpenTextForAll; + QString vcsMakeWritableTextForAll; + bool useMakeWritable = false; + foreach (const QString &fileName, fileNames) { + const QFileInfo info = QFileInfo(fileName); + const QString visibleName = info.fileName(); + const QString directory = info.absolutePath(); + + // Setup a default entry with filename folder and make writable radio button. + QTreeWidgetItem *item = new QTreeWidgetItem(ui->treeWidget); + item->setText(FileName, visibleName); + item->setIcon(FileName, FileIconProvider::instance()->icon(QFileInfo(fileName))); + item->setText(Folder, Utils::FileUtils::shortNativePath(Utils::FileName(QFileInfo(directory)))); + QButtonGroup *radioButtonGroup = new QButtonGroup; + + // Add a button for opening the file with a version control system + // if the file is managed by an version control system which allows opening files. + IVersionControl *versionControlForFile = + ICore::vcsManager()->findVersionControlForDirectory(directory); + const bool fileManagedByVCS = versionControlForFile + && versionControlForFile->openSupportMode() != IVersionControl::NoOpen; + if (fileManagedByVCS) { + const QString vcsOpenTextForFile = + versionControlForFile->vcsOpenText().remove(QLatin1Char('&')); + const QString vcsMakeWritableTextforFile = + versionControlForFile->vcsMakeWritableText().remove(QLatin1Char('&')); + if (!d->useVCS) { + vcsOpenTextForAll = vcsOpenTextForFile; + vcsMakeWritableTextForAll = vcsMakeWritableTextforFile; + d->useVCS = true; + } else { + // If there are different open or make writable texts choose the default one. + if (vcsOpenTextForFile != vcsOpenTextForAll) + vcsOpenTextForAll.clear(); + if (vcsMakeWritableTextforFile != vcsMakeWritableTextForAll) + vcsMakeWritableTextForAll.clear(); + } + // Add make writable if it is supported by the reposetory. + if (versionControlForFile->openSupportMode() == IVersionControl::OpenOptional) { + useMakeWritable = true; + createRadioButtonForItem(item, radioButtonGroup, MakeWritable); + } + createRadioButtonForItem(item, radioButtonGroup, OpenWithVCS)->setChecked(true); + } else { + useMakeWritable = true; + createRadioButtonForItem(item, radioButtonGroup, MakeWritable)->setChecked(true); + } + // Add a Save As radio button if requested. + if (d->useSaveAs) + createRadioButtonForItem(item, radioButtonGroup, SaveAs); + // If the file is managed by a version control system save the vcs for this file. + d->versionControls[fileName] = fileManagedByVCS ? versionControlForFile : 0; + + // Also save the buttongroup for every file to get the result for each entry. + ReadOnlyFilesDialogPrivate::ButtonGroupForFile groupForFile; + groupForFile.fileName = fileName; + groupForFile.group = radioButtonGroup; + d->buttonGroups.append(groupForFile); + connect(radioButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(updateSelectAll())); + } + + // Apply the Mac file dialog style. + if (Utils::HostOsInfo::isMacHost()) + ui->treeWidget->setAlternatingRowColors(true); + + // Do not show any options to the user if he has no choice. + if (!d->useSaveAs && (!d->useVCS || !useMakeWritable)) { + ui->treeWidget->setColumnHidden(MakeWritable, true); + ui->treeWidget->setColumnHidden(OpenWithVCS, true); + ui->treeWidget->setColumnHidden(SaveAs, true); + ui->treeWidget->resizeColumnToContents(FileName); + ui->treeWidget->resizeColumnToContents(Folder); + ui->setAll->setVisible(false); + ui->setAllLabel->setVisible(false); + ui->verticalLayout->removeItem(ui->setAllLayout); + if (d->useVCS) + ui->msgLabel->setText(tr("The following files are not checked out by now.\n" + "Do you want to check out these files now?")); + return; + } + + // If there is just one file entry, there is no need to show the select all combo box + if (fileNames.count() < 2) { + ui->setAll->setVisible(false); + ui->setAllLabel->setVisible(false); + ui->verticalLayout->removeItem(ui->setAllLayout); + } + + // Add items to the Set all combo box. + ui->setAll->addItem(d->mixedText); + d->setAllIndexForOperation[-1/*mixed*/] = ui->setAll->count() - 1; + if (d->useVCS) { + // If the files are managed by just one version control system, the Open and Make Writable + // text for the specific system is used. + if (!vcsOpenTextForAll.isEmpty() && vcsOpenTextForAll != d->versionControlOpenText) { + d->versionControlOpenText = vcsOpenTextForAll; + ui->treeWidget->headerItem()->setText(OpenWithVCS, d->versionControlOpenText); + } + if (!vcsMakeWritableTextForAll.isEmpty() && vcsMakeWritableTextForAll != d->makeWritableText) { + d->makeWritableText = vcsMakeWritableTextForAll; + ui->treeWidget->headerItem()->setText(MakeWritable, d->makeWritableText); + } + ui->setAll->addItem(d->versionControlOpenText); + ui->setAll->setCurrentIndex(ui->setAll->count() - 1); + d->setAllIndexForOperation[OpenWithVCS] = ui->setAll->count() - 1; + } + if (useMakeWritable) { + ui->setAll->addItem(d->makeWritableText); + d->setAllIndexForOperation[MakeWritable] = ui->setAll->count() - 1; + if (ui->setAll->currentIndex() == -1) + ui->setAll->setCurrentIndex(ui->setAll->count() - 1); + } + if (d->useSaveAs) { + ui->setAll->addItem(d->saveAsText); + d->setAllIndexForOperation[SaveAs] = ui->setAll->count() - 1; + } + connect(ui->setAll, SIGNAL(activated(int)), this, SLOT(setAll(int))); + + // Filter which columns should be visible and resize them to content. + for (int i = 0; i < NumberOfColumns; ++i) { + if ((i == SaveAs && !d->useSaveAs) || (i == OpenWithVCS && !d->useVCS) + || (i == MakeWritable && !useMakeWritable)) { + ui->treeWidget->setColumnHidden(i, true); + continue; + } + ui->treeWidget->resizeColumnToContents(i); + } +} + +}// namespace Internal +}// namespace Core diff --git a/src/plugins/coreplugin/dialogs/readonlyfilesdialog.h b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.h new file mode 100644 index 00000000000..844167fc584 --- /dev/null +++ b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef READONLYFILESDIALOG_H +#define READONLYFILESDIALOG_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QButtonGroup; +class QTreeWidgetItem; +class QRadioButton; +QT_END_NAMESPACE + +namespace Core { +class IVersionControl; +class IDocument; + +namespace Internal { + +namespace Ui { class ReadOnlyFilesDialog; } + +class CORE_EXPORT ReadOnlyFilesDialog : public QDialog +{ + Q_OBJECT + +private: + enum ReadOnlyFilesTreeColumn { + MakeWritable = 0, + OpenWithVCS = 1, + SaveAs = 2, + FileName = 3, + Folder = 4, + NumberOfColumns + }; + +public: + enum ReadOnlyResult { + RO_Cancel = -1, + RO_OpenVCS = OpenWithVCS, + RO_MakeWritable = MakeWritable, + RO_SaveAs = SaveAs + }; + + explicit ReadOnlyFilesDialog(const QList &fileNames, + QWidget *parent = 0); + explicit ReadOnlyFilesDialog(const QString &fileName, + QWidget * parent = 0); + explicit ReadOnlyFilesDialog(IDocument *document, + QWidget * parent = 0, + bool displaySaveAs = false); + explicit ReadOnlyFilesDialog(const QList documents, + QWidget * parent = 0); + + ~ReadOnlyFilesDialog(); + + void setMessage(const QString &message); + void setShowFailWarning(bool show, const QString &warning = QString()); + + int exec(); + +private: + void initDialog(const QStringList &fileNames); + void promptFailWarning(const QStringList &files, ReadOnlyResult type) const; + QRadioButton *createRadioButtonForItem(QTreeWidgetItem *item, QButtonGroup *group, + ReadOnlyFilesDialog::ReadOnlyFilesTreeColumn type); + +private slots: + void setAll(int index); + void updateSelectAll(); + +private: + class ReadOnlyFilesDialogPrivate *d; + Ui::ReadOnlyFilesDialog *ui; +}; + +} // namespace Internal +} // namespace Core + +#endif // READONLYFILESDIALOG_H diff --git a/src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui new file mode 100644 index 00000000000..d0c496aba0d --- /dev/null +++ b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui @@ -0,0 +1,145 @@ + + + Core::Internal::ReadOnlyFilesDialog + + + + 0 + 0 + 639 + 217 + + + + Files Without Write Permissions + + + + + + The following files have no write permissions. Do you want to change the permissions? + + + + + + + QAbstractItemView::NoSelection + + + Qt::ElideLeft + + + 0 + + + true + + + false + + + 5 + + + + Make Writeable + + + + + Open With VCS + + + + + Save As + + + + + File Name + + + + + Path + + + + + + + + + + Select all, if possible: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::NoButton + + + + + + + + + buttonBox + accepted() + Core::Internal::ReadOnlyFilesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Core::Internal::ReadOnlyFilesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp index 0bc28d2701f..3619f771d2e 100644 --- a/src/plugins/coreplugin/documentmanager.cpp +++ b/src/plugins/coreplugin/documentmanager.cpp @@ -35,11 +35,12 @@ #include "ieditorfactory.h" #include "iexternaleditor.h" #include "idocument.h" -#include "iversioncontrol.h" #include "mimedatabase.h" #include "saveitemsdialog.h" #include "coreconstants.h" +#include "dialogs/readonlyfilesdialog.h" + #include #include #include @@ -58,7 +59,6 @@ #include #include #include -#include /*! \class Core::DocumentManager @@ -565,7 +565,9 @@ void DocumentManager::unexpectFileChange(const QString &fileName) \fn QList DocumentManager::saveModifiedFilesSilently(const QList &documents) Tries to save the files listed in \a documents. The \a cancelled argument is set to true - if the user cancelled the dialog. Returns the files that could not be saved. + if the user cancelled the dialog. Returns the files that could not be saved. If the files + listed in documents have no write permissions an additional dialog will be prompted to + query the user for these permissions. */ QList DocumentManager::saveModifiedDocumentsSilently(const QList &documents, bool *cancelled) { @@ -581,7 +583,8 @@ QList DocumentManager::saveModifiedDocumentsSilently(const QList DocumentManager::saveModifiedDocuments(const QList &documents, @@ -642,7 +645,25 @@ static QList saveModifiedFilesHelper(const QList &docu *alwaysSave = dia.alwaysSaveChecked(); documentsToSave = dia.itemsToSave(); } - + // Check for files without write permissions. + QList roDocuments; + foreach (IDocument *document, documentsToSave) { + if (document->isFileReadOnly()) + roDocuments << document; + } + if (!roDocuments.isEmpty()) { + Core::Internal::ReadOnlyFilesDialog roDialog(roDocuments, d->m_mainWindow); + roDialog.setShowFailWarning(true, QCoreApplication::translate( + "saveModifiedFilesHelper", + "Could not save the files.", + "error message")); + if (roDialog.exec() == Core::Internal::ReadOnlyFilesDialog::RO_Cancel) { + if (cancelled) + (*cancelled) = true; + notSaved = modifiedDocuments; + return notSaved; + } + } foreach (IDocument *document, documentsToSave) { if (!EditorManager::instance()->saveDocument(document)) { if (cancelled) @@ -742,7 +763,7 @@ QString DocumentManager::getSaveFileNameWithExtension(const QString &title, cons Asks the user for a new file name (Save File As) for /arg document. */ -QString DocumentManager::getSaveAsFileName(IDocument *document, const QString &filter, QString *selectedFilter) +QString DocumentManager::getSaveAsFileName(const IDocument *document, const QString &filter, QString *selectedFilter) { if (!document) return QLatin1String(""); @@ -804,61 +825,6 @@ QStringList DocumentManager::getOpenFileNames(const QString &filters, return files; } -DocumentManager::ReadOnlyAction - DocumentManager::promptReadOnlyFile(const QString &fileName, - const IVersionControl *versionControl, - QWidget *parent, - bool displaySaveAsButton) -{ - // Version Control: If automatic open is desired, open right away. - bool promptVCS = false; - if (versionControl && versionControl->openSupportMode() != IVersionControl::NoOpen) { - if (versionControl->settingsFlags() & IVersionControl::AutoOpen) - return RO_OpenVCS; - promptVCS = true; - } - - // Create message box. - QMessageBox msgBox(QMessageBox::Question, tr("File Is Read Only"), - tr("The file %1 is read only.").arg(QDir::toNativeSeparators(fileName)), - QMessageBox::Cancel, parent); - - QPushButton *vcsButton = 0; - if (promptVCS) { - vcsButton = msgBox.addButton(versionControl->vcsOpenText(), QMessageBox::AcceptRole); - } - - QString makeWritableText; - QPushButton *makeWritableButton = 0; - // If the VCS has OpenMandatory we don't show "Make Writable" - if (versionControl->openSupportMode() != IVersionControl::OpenMandatory) { - makeWritableText = versionControl->vcsMakeWritableText(); - if (makeWritableText.isEmpty()) - makeWritableText = tr("Make &Writable"); - makeWritableButton = msgBox.addButton(makeWritableText, QMessageBox::AcceptRole); - } - - QPushButton *saveAsButton = 0; - if (displaySaveAsButton) - saveAsButton = msgBox.addButton(tr("&Save As..."), QMessageBox::ActionRole); - - if (vcsButton) - msgBox.setDefaultButton(vcsButton); - else if (makeWritableButton) - msgBox.setDefaultButton(makeWritableButton); - - msgBox.exec(); - - QAbstractButton *clickedButton = msgBox.clickedButton(); - if (clickedButton == vcsButton) - return RO_OpenVCS; - if (clickedButton == makeWritableButton) - return RO_MakeWriteable; - if (displaySaveAsButton && clickedButton == saveAsButton) - return RO_SaveAs; - return RO_Cancel; -} - void DocumentManager::changedFile(const QString &fileName) { const bool wasempty = d->m_changedFiles.isEmpty(); diff --git a/src/plugins/coreplugin/documentmanager.h b/src/plugins/coreplugin/documentmanager.h index 145218b5a3e..d4f15591bc0 100644 --- a/src/plugins/coreplugin/documentmanager.h +++ b/src/plugins/coreplugin/documentmanager.h @@ -46,7 +46,6 @@ namespace Core { class IContext; class IDocument; -class IVersionControl; class CORE_EXPORT DocumentManager : public QObject { @@ -98,7 +97,7 @@ public: const QString &filter = QString(), QString *selectedFilter = 0); static QString getSaveFileNameWithExtension(const QString &title, const QString &pathIn, const QString &filter); - static QString getSaveAsFileName(IDocument *document, const QString &filter = QString(), + static QString getSaveAsFileName(const IDocument *document, const QString &filter = QString(), QString *selectedFilter = 0); static QList saveModifiedDocumentsSilently(const QList &documents, bool *cancelled = 0); @@ -108,15 +107,6 @@ public: const QString &alwaysSaveMessage = QString(), bool *alwaysSave = 0); - - // Helper to display a message dialog when encountering a read-only - // file, prompting the user about how to make it writeable. - enum ReadOnlyAction { RO_Cancel, RO_OpenVCS, RO_MakeWriteable, RO_SaveAs }; - static ReadOnlyAction promptReadOnlyFile(const QString &fileName, - const IVersionControl *versionControl, - QWidget *parent, - bool displaySaveAsButton = false); - static QString fileDialogLastVisitedDirectory(); static void setFileDialogLastVisitedDirectory(const QString &); diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 34500004e3f..3d77de1529a 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -1578,33 +1579,17 @@ MakeWritableResult EditorManager::makeFileWritable(IDocument *document) { if (!document) return Failed; - QString directory = QFileInfo(document->fileName()).absolutePath(); - IVersionControl *versionControl = ICore::vcsManager()->findVersionControlForDirectory(directory); - const QString &fileName = document->fileName(); - switch (DocumentManager::promptReadOnlyFile(fileName, versionControl, ICore::mainWindow(), document->isSaveAsAllowed())) { - case DocumentManager::RO_OpenVCS: - if (!versionControl->vcsOpen(fileName)) { - QMessageBox::warning(ICore::mainWindow(), tr("Cannot Open File"), tr("Cannot open the file for editing with SCC.")); - return Failed; - } - document->checkPermissions(); - return OpenedWithVersionControl; - case DocumentManager::RO_MakeWriteable: { - const bool permsOk = QFile::setPermissions(fileName, QFile::permissions(fileName) | QFile::WriteUser); - if (!permsOk) { - QMessageBox::warning(ICore::mainWindow(), tr("Cannot Set Permissions"), tr("Cannot set permissions to writable.")); - return Failed; - } - } - document->checkPermissions(); + ReadOnlyFilesDialog roDialog(document, ICore::mainWindow(), document->isSaveAsAllowed()); + switch (roDialog.exec()) { + case ReadOnlyFilesDialog::RO_MakeWritable: + case ReadOnlyFilesDialog::RO_OpenVCS: return MadeWritable; - case DocumentManager::RO_SaveAs : - return saveDocumentAs(document) ? SavedAs : Failed; - case DocumentManager::RO_Cancel: - break; + case ReadOnlyFilesDialog::RO_SaveAs: + return SavedAs; + default: + return Failed; } - return Failed; } bool EditorManager::saveDocumentAs(IDocument *documentParam) diff --git a/src/plugins/perforce/perforceversioncontrol.cpp b/src/plugins/perforce/perforceversioncontrol.cpp index 85acdebbce8..19303a66fa8 100644 --- a/src/plugins/perforce/perforceversioncontrol.cpp +++ b/src/plugins/perforce/perforceversioncontrol.cpp @@ -163,7 +163,7 @@ QString PerforceVersionControl::vcsGetRepositoryURL(const QString &) QString PerforceVersionControl::vcsOpenText() const { - return tr("&Edit (%1)").arg(displayName()); + return tr("&Edit"); } QString PerforceVersionControl::vcsMakeWritableText() const diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp index 0c34f545a4c..d0e63577edc 100644 --- a/src/plugins/qt4projectmanager/qt4nodes.cpp +++ b/src/plugins/qt4projectmanager/qt4nodes.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -1023,28 +1024,9 @@ bool Qt4PriFileNode::renameFile(const FileType fileType, const QString &filePath bool Qt4PriFileNode::priFileWritable(const QString &path) { - const QString dir = QFileInfo(path).dir().path(); - Core::IVersionControl *versionControl = Core::ICore::vcsManager()->findVersionControlForDirectory(dir); - switch (Core::DocumentManager::promptReadOnlyFile(path, versionControl, Core::ICore::mainWindow(), false)) { - case Core::DocumentManager::RO_OpenVCS: - if (!versionControl->vcsOpen(path)) { - QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Open File"), tr("Cannot open the file for editing with VCS.")); - return false; - } - break; - case Core::DocumentManager::RO_MakeWriteable: { - const bool permsOk = QFile::setPermissions(path, QFile::permissions(path) | QFile::WriteUser); - if (!permsOk) { - QMessageBox::warning(Core::ICore::mainWindow(), tr("Cannot Set Permissions"), tr("Cannot set permissions to writable.")); - return false; - } - break; - } - case Core::DocumentManager::RO_SaveAs: - case Core::DocumentManager::RO_Cancel: - return false; - } - return true; + Core::Internal::ReadOnlyFilesDialog roDialog(path, Core::ICore::mainWindow()); + roDialog.setShowFailWarning(true); + return roDialog.exec() != Core::Internal::ReadOnlyFilesDialog::RO_Cancel; } bool Qt4PriFileNode::saveModifiedEditors() diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index a29cc38626e..ac7025a8d10 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -363,7 +364,26 @@ QStringList BaseFileFind::replaceAll(const QString &text, foreach (const Find::SearchResultItem &item, items) changes[QDir::fromNativeSeparators(item.path.first())].append(item); + // Checking for files without write permissions QHashIterator > it(changes); + QSet roFiles; + while (it.hasNext()) { + it.next(); + const QFileInfo fileInfo(it.key()); + if (!fileInfo.isWritable()) + roFiles.insert(it.key()); + } + + // Query the user for permissions + if (!roFiles.isEmpty()) { + Core::Internal::ReadOnlyFilesDialog roDialog(roFiles.toList(), + Core::ICore::instance()->mainWindow()); + roDialog.setShowFailWarning(true, tr("Aborting replace.")); + if (roDialog.exec() == Core::Internal::ReadOnlyFilesDialog::RO_Cancel) + return QStringList(); + } + + it.toFront(); while (it.hasNext()) { it.next(); const QString fileName = it.key(); diff --git a/src/plugins/texteditor/refactoringchanges.cpp b/src/plugins/texteditor/refactoringchanges.cpp index a79b31d660f..f5638ae6005 100644 --- a/src/plugins/texteditor/refactoringchanges.cpp +++ b/src/plugins/texteditor/refactoringchanges.cpp @@ -30,13 +30,18 @@ #include "refactoringchanges.h" #include "basetexteditor.h" +#include +#include #include +#include #include +#include #include #include #include #include +#include using namespace TextEditor; @@ -319,6 +324,17 @@ void RefactoringFile::setOpenEditor(bool activate, int pos) void RefactoringFile::apply() { + // test file permissions + if (!QFileInfo(fileName()).isWritable()) { + const QString &path = fileName(); + Core::Internal::ReadOnlyFilesDialog roDialog(path, Core::ICore::mainWindow()); + const QString &failDetailText = QApplication::translate("RefactoringFile::apply", + "Refactoring cannot be applied."); + roDialog.setShowFailWarning(true, failDetailText); + if (roDialog.exec() == Core::Internal::ReadOnlyFilesDialog::RO_Cancel) + return; + } + // open / activate / goto position if (m_openEditor && !m_fileName.isEmpty()) { unsigned line = unsigned(-1), column = unsigned(-1);