diff options
author | David Schulz <david.schulz@digia.com> | 2012-10-15 11:53:22 +0200 |
---|---|---|
committer | David Schulz <david.schulz@digia.com> | 2013-04-08 10:16:11 +0200 |
commit | 6c12a060290ec094faea22fadfaf6aa7fb2d5db2 (patch) | |
tree | 25102dc3520f8c68c525a0e3032095c01692a2ec /src | |
parent | f9c31b4c6b0c748690d4f58fc39a590b8c88173c (diff) | |
download | qt-creator-6c12a060290ec094faea22fadfaf6aa7fb2d5db2.tar.gz |
Editor: Added Dialog for read only files.
Task-number: QTCREATORBUG-2851
Change-Id: Ic47a5a1833650e31b4e27d0a01259d6b398a6415
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Christian Stenger <christian.stenger@digia.com>
Reviewed-by: Eike Ziller <eike.ziller@digia.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/libs/utils/fileutils.cpp | 6 | ||||
-rw-r--r-- | src/libs/utils/fileutils.h | 1 | ||||
-rw-r--r-- | src/plugins/coreplugin/coreplugin.pro | 3 | ||||
-rw-r--r-- | src/plugins/coreplugin/coreplugin.qbs | 3 | ||||
-rw-r--r-- | src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp | 491 | ||||
-rw-r--r-- | src/plugins/coreplugin/dialogs/readonlyfilesdialog.h | 109 | ||||
-rw-r--r-- | src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui | 145 | ||||
-rw-r--r-- | src/plugins/coreplugin/documentmanager.cpp | 88 | ||||
-rw-r--r-- | src/plugins/coreplugin/documentmanager.h | 12 | ||||
-rw-r--r-- | src/plugins/coreplugin/editormanager/editormanager.cpp | 33 | ||||
-rw-r--r-- | src/plugins/perforce/perforceversioncontrol.cpp | 2 | ||||
-rw-r--r-- | src/plugins/qt4projectmanager/qt4nodes.cpp | 26 | ||||
-rw-r--r-- | src/plugins/texteditor/basefilefind.cpp | 20 | ||||
-rw-r--r-- | src/plugins/texteditor/refactoringchanges.cpp | 16 |
14 files changed, 836 insertions, 119 deletions
diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 5ad3fb809e..d5193bdad8 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 dec02723ac..4477938be5 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 c2a89de0cc..64b3cbc20f 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 8501f6940a..fc98dfaa4c 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 0000000000..1f0d05c980 --- /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 <coreplugin/editormanager/editormanager.h> +#include <coreplugin/icore.h> +#include <coreplugin/idocument.h> +#include <coreplugin/iversioncontrol.h> +#include <coreplugin/vcsmanager.h> + +#include <utils/fileutils.h> +#include <utils/hostosinfo.h> + +#include <QDir> +#include <QFileInfo> +#include <QMap> +#include <QMessageBox> +#include <QPushButton> +#include <QRadioButton> + +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 <ButtonGroupForFile> buttonGroups; + + QMap <int, int> setAllIndexForOperation; + // The version control systems for every file, if the file isn't in VCS the value is 0. + QHash <QString, IVersionControl*> 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<QString> &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<IDocument *> 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<ReadOnlyResult>(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<QRadioButton*> (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 0000000000..844167fc58 --- /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 <coreplugin/core_global.h> + +#include <QDialog> +#include <QHash> + +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<QString> &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<IDocument *> 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 0000000000..d0c496aba0 --- /dev/null +++ b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.ui @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Core::Internal::ReadOnlyFilesDialog</class> + <widget class="QDialog" name="Core::Internal::ReadOnlyFilesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>639</width> + <height>217</height> + </rect> + </property> + <property name="windowTitle"> + <string>Files Without Write Permissions</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="msgLabel"> + <property name="text"> + <string>The following files have no write permissions. Do you want to change the permissions?</string> + </property> + </widget> + </item> + <item> + <widget class="QTreeWidget" name="treeWidget"> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + <property name="textElideMode"> + <enum>Qt::ElideLeft</enum> + </property> + <property name="indentation"> + <number>0</number> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="columnCount"> + <number>5</number> + </property> + <column> + <property name="text"> + <string>Make Writeable</string> + </property> + </column> + <column> + <property name="text"> + <string>Open With VCS</string> + </property> + </column> + <column> + <property name="text"> + <string>Save As</string> + </property> + </column> + <column> + <property name="text"> + <string notr="true">File Name</string> + </property> + </column> + <column> + <property name="text"> + <string>Path</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="setAllLayout"> + <item> + <widget class="QLabel" name="setAllLabel"> + <property name="text"> + <string>Select all, if possible: </string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="setAll"/> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::NoButton</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Core::Internal::ReadOnlyFilesDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Core::Internal::ReadOnlyFilesDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp index 0bc28d2701..3619f771d2 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 <utils/hostosinfo.h> #include <utils/qtcassert.h> #include <utils/pathchooser.h> @@ -58,7 +59,6 @@ #include <QMainWindow> #include <QMenu> #include <QMessageBox> -#include <QPushButton> /*! \class Core::DocumentManager @@ -565,7 +565,9 @@ void DocumentManager::unexpectFileChange(const QString &fileName) \fn QList<IDocument*> DocumentManager::saveModifiedFilesSilently(const QList<IDocument*> &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<IDocument *> DocumentManager::saveModifiedDocumentsSilently(const QList<IDocument *> &documents, bool *cancelled) { @@ -581,7 +583,8 @@ QList<IDocument *> DocumentManager::saveModifiedDocumentsSilently(const QList<ID of modified files (in this context). The \a cancelled argument is set to true if the user cancelled the dialog, \a alwaysSave is set to match the selection of the user, if files should - always automatically be saved. + always automatically 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. Returns the files that have not been saved. */ QList<IDocument *> DocumentManager::saveModifiedDocuments(const QList<IDocument *> &documents, @@ -642,7 +645,25 @@ static QList<IDocument *> saveModifiedFilesHelper(const QList<IDocument *> &docu *alwaysSave = dia.alwaysSaveChecked(); documentsToSave = dia.itemsToSave(); } - + // Check for files without write permissions. + QList<IDocument *> 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 <i>%1</i> 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 145218b5a3..d4f15591bc 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<IDocument *> saveModifiedDocumentsSilently(const QList<IDocument *> &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 34500004e3..3d77de1529 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -52,6 +52,7 @@ #include <coreplugin/modemanager.h> #include <coreplugin/settingsdatabase.h> #include <coreplugin/variablemanager.h> +#include <coreplugin/dialogs/readonlyfilesdialog.h> #include <extensionsystem/pluginmanager.h> @@ -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 85acdebbce..19303a66fa 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 0c34f545a4..d0e63577ed 100644 --- a/src/plugins/qt4projectmanager/qt4nodes.cpp +++ b/src/plugins/qt4projectmanager/qt4nodes.cpp @@ -43,6 +43,7 @@ #include <coreplugin/icore.h> #include <coreplugin/iversioncontrol.h> #include <coreplugin/vcsmanager.h> +#include <coreplugin/dialogs/readonlyfilesdialog.h> #include <projectexplorer/buildmanager.h> #include <projectexplorer/projectexplorer.h> @@ -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 a29cc38626..ac7025a8d1 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -34,6 +34,7 @@ #include <coreplugin/icore.h> #include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/progressmanager/futureprogress.h> +#include <coreplugin/dialogs/readonlyfilesdialog.h> #include <coreplugin/documentmanager.h> #include <texteditor/basetexteditor.h> #include <texteditor/refactoringchanges.h> @@ -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<QString, QList<Find::SearchResultItem> > it(changes); + QSet<QString> 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 a79b31d660..f5638ae600 100644 --- a/src/plugins/texteditor/refactoringchanges.cpp +++ b/src/plugins/texteditor/refactoringchanges.cpp @@ -30,13 +30,18 @@ #include "refactoringchanges.h" #include "basetexteditor.h" +#include <coreplugin/icore.h> +#include <coreplugin/dialogs/readonlyfilesdialog.h> #include <utils/qtcassert.h> +#include <utils/fileutils.h> #include <QFile> +#include <QFileInfo> #include <QTextBlock> #include <QTextCursor> #include <QTextDocument> #include <QDebug> +#include <QApplication> 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); |