From bcb3bb0fbab247cfb48dc4621359b539308670a6 Mon Sep 17 00:00:00 2001
From: Leandro Melo <leandro.melo@nokia.com>
Date: Tue, 7 Sep 2010 11:55:52 +0200
Subject: [PATCH] Allow the user to save as files with different extensions.

There is still an open issue in this fix since the new extension might be of a different mime type (and our editors are attached to it currently).
More details documented in the code.

Task-number: QTCREATORBUG-2094
Reviewed-by: Thorbjorn Lindeijer
---
 src/plugins/coreplugin/coreconstants.h        |  2 +
 .../coreplugin/dialogs/shortcutsettings.cpp   |  3 +-
 .../editormanager/editormanager.cpp           | 74 ++++++++++---------
 src/plugins/coreplugin/filemanager.cpp        | 64 ++++++++++++----
 src/plugins/coreplugin/filemanager.h          | 10 ++-
 5 files changed, 98 insertions(+), 55 deletions(-)

diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h
index ecc1c2a4bc2..13081d5ecc7 100644
--- a/src/plugins/coreplugin/coreconstants.h
+++ b/src/plugins/coreplugin/coreconstants.h
@@ -246,6 +246,8 @@ const char * const SETTINGS_CATEGORY_CORE_ICON = ":/core/images/category_core.pn
 const char * const SETTINGS_TR_CATEGORY_CORE = QT_TRANSLATE_NOOP("Core", "Environment");
 const char * const SETTINGS_ID_ENVIRONMENT = "A.General";
 
+const char * const ALL_FILES_FILTER      = QT_TRANSLATE_NOOP("Core", "All Files (*)");
+
 const int TARGET_ICON_SIZE = 32;
 
 } // namespace Constants
diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
index 59b5b2e8fe9..74abe1321e1 100644
--- a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
+++ b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp
@@ -264,8 +264,7 @@ void ShortcutSettings::exportAction()
     QString fileName = ICore::instance()->fileManager()->getSaveFileNameWithExtension(
         tr("Export Keyboard Mapping Scheme"),
         ICore::instance()->resourcePath() + "/schemes/",
-        tr("Keyboard Mapping Scheme (*.kms)"),
-        ".kms");
+        tr("Keyboard Mapping Scheme (*.kms)"));
     if (!fileName.isEmpty()) {
         CommandsFile cf(fileName);
         cf.exportCommands(m_scitems);
diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp
index c0e56ac3a8b..6b58cc7f850 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.cpp
+++ b/src/plugins/coreplugin/editormanager/editormanager.cpp
@@ -77,6 +77,8 @@
 #include <QtGui/QSplitter>
 #include <QtGui/QStackedLayout>
 
+#include <algorithm>
+
 Q_DECLARE_METATYPE(Core::IEditor*)
 
 enum { debugEditorManager=0 };
@@ -208,9 +210,6 @@ struct EditorManagerPrivate {
     QMap<QString, QVariant> m_editorStates;
     Internal::OpenEditorsViewFactory *m_openEditorsFactory;
 
-    QString fileFilters;
-    QString selectedFilter;
-
     OpenEditorsModel *m_editorModel;
     QString m_externalEditor;
 
@@ -1152,33 +1151,28 @@ QString EditorManager::getOpenWithEditorId(const QString &fileName,
     return selectedId;
 }
 
-static QString formatFileFilters(const Core::ICore *core, QString *selectedFilter)
+static QString formatFileFilters(const Core::ICore *core, QString *selectedFilter = 0)
 {
-    QString rc;
+    if (selectedFilter)
+        selectedFilter->clear();
 
-    // Compile list of filter strings
+    // Compile list of filter strings, sort, and remove duplicates (different mime types might
+    // generate the same filter).
     QStringList filters = core->mimeDatabase()->filterStrings();
-    filters.sort();
-    selectedFilter->clear();
     if (filters.empty())
-        return rc;
+        return QString();
+    filters.sort();
+    filters.erase(std::unique(filters.begin(), filters.end()), filters.end());
 
-    const QString filterSeparator = QLatin1String(";;");
-    foreach (const QString &filterString, filters) {
-        if (!rc.isEmpty())
-            rc += filterSeparator;
-        rc += filterString;
-    }
+    static const QString allFilesFilter =
+        QCoreApplication::translate("Core", Constants::ALL_FILES_FILTER);
+    if (selectedFilter)
+        *selectedFilter = allFilesFilter;
 
-    // prepend all files filter
-    // prepending instead of appending to work around a bug in Qt/Mac
-    QString allFilesFilter = EditorManager::tr("All Files (*)");
-    if (!rc.isEmpty())
-        allFilesFilter += filterSeparator;
-    rc.prepend(allFilesFilter);
-    *selectedFilter = allFilesFilter;
+    // Prepend all files filter (instead of appending to work around a bug in Qt/Mac).
+    filters.prepend(allFilesFilter);
 
-    return rc;
+    return filters.join(QLatin1String(";;"));
 }
 
 IEditor *EditorManager::openEditor(const QString &fileName, const QString &editorId,
@@ -1244,10 +1238,10 @@ bool EditorManager::openExternalEditor(const QString &fileName, const QString &e
 
 QStringList EditorManager::getOpenFileNames() const
 {
-    if (m_d->fileFilters.isEmpty())
-        m_d->fileFilters = formatFileFilters(m_d->m_core, &m_d->selectedFilter);
-    return ICore::instance()->fileManager()->getOpenFileNames(m_d->fileFilters,
-                                                              QString(), &m_d->selectedFilter);
+    QString selectedFilter;
+    const QString &fileFilters = formatFileFilters(m_d->m_core, &selectedFilter);
+    return ICore::instance()->fileManager()->getOpenFileNames(fileFilters,
+                                                              QString(), &selectedFilter);
 }
 
 
@@ -1475,23 +1469,35 @@ bool EditorManager::saveFileAs(IEditor *editor)
     if (!editor)
         return false;
 
-    QString absoluteFilePath = m_d->m_core->fileManager()->getSaveAsFileName(editor->file());
+    IFile *file = editor->file();
+    const QString &filter = formatFileFilters(m_d->m_core);
+    QString selectedFilter =
+        m_d->m_core->mimeDatabase()->findByFile(QFileInfo(file->fileName())).filterString();
+    const QString &absoluteFilePath =
+        m_d->m_core->fileManager()->getSaveAsFileName(file, filter, &selectedFilter);
+
     if (absoluteFilePath.isEmpty())
         return false;
-    if (absoluteFilePath != editor->file()->fileName()) {
+    if (absoluteFilePath != file->fileName()) {
         const QList<IEditor *> existList = editorsForFileName(absoluteFilePath);
         if (!existList.isEmpty()) {
             closeEditors(existList, false);
         }
     }
 
-    m_d->m_core->fileManager()->blockFileChange(editor->file());
-    const bool success = editor->file()->save(absoluteFilePath);
-    m_d->m_core->fileManager()->unblockFileChange(editor->file());
-    editor->file()->checkPermissions();
+    m_d->m_core->fileManager()->blockFileChange(file);
+    const bool success = file->save(absoluteFilePath);
+    m_d->m_core->fileManager()->unblockFileChange(file);
+    file->checkPermissions();
+
+    // @todo: There is an issue to be treated here. The new file might be of a different mime
+    // type than the original and thus require a different editor. An alternative strategy
+    // would be to close the current editor and open a new appropriate one, but this is not
+    // a good way out either (also the undo stack would be lost). Perhaps the best is to
+    // re-think part of the editors design.
 
     if (success && !editor->isTemporary())
-        m_d->m_core->fileManager()->addToRecentFiles(editor->file()->fileName());
+        m_d->m_core->fileManager()->addToRecentFiles(file->fileName());
 
     updateActions();
     return success;
diff --git a/src/plugins/coreplugin/filemanager.cpp b/src/plugins/coreplugin/filemanager.cpp
index 1551adc04ee..bb5bf60825f 100644
--- a/src/plugins/coreplugin/filemanager.cpp
+++ b/src/plugins/coreplugin/filemanager.cpp
@@ -37,6 +37,7 @@
 #include "mimedatabase.h"
 #include "saveitemsdialog.h"
 #include "vcsmanager.h"
+#include "coreconstants.h"
 
 #include <utils/qtcassert.h>
 #include <utils/pathchooser.h>
@@ -689,22 +690,45 @@ QList<IFile *> FileManager::saveModifiedFiles(const QList<IFile *> &files,
     return notSaved;
 }
 
-QString FileManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
-    const QString &fileFilter, const QString &extension)
+QString FileManager::getSaveFileName(const QString &title, const QString &pathIn,
+                                     const QString &filter, QString *selectedFilter)
 {
+    const QString &path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
     QString fileName;
     bool repeat;
     do {
         repeat = false;
-        const QString path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
-        fileName = QFileDialog::getSaveFileName(d->m_mainWindow, title, path, fileFilter);
-        if (!fileName.isEmpty() && !extension.isEmpty() && !fileName.endsWith(extension)) {
-            fileName.append(extension);
+        fileName = QFileDialog::getSaveFileName(
+            d->m_mainWindow, title, path, filter, selectedFilter, QFileDialog::DontConfirmOverwrite);
+        if (!fileName.isEmpty()) {
+            // If the selected filter is All Files (*) we leave the name exactly as the user
+            // specified. Otherwise the suffix must be one available in the selected filter. If
+            // the name already ends with such suffix nothing needs to be done. But if not, the
+            // first one from the filter is appended.
+            if (selectedFilter && *selectedFilter != QCoreApplication::translate(
+                    "Core", Constants::ALL_FILES_FILTER)) {
+                // Mime database creates filter strings like this: Anything here (*.foo *.bar)
+                QRegExp regExp(".*\\s+\\((.*)\\)$");
+                const int index = regExp.lastIndexIn(*selectedFilter);
+                bool suffixOk = false;
+                if (index != -1) {
+                    const QStringList &suffixes = regExp.cap(1).remove('*').split(' ');
+                    foreach (const QString &suffix, suffixes)
+                        if (fileName.endsWith(suffix)) {
+                            suffixOk = true;
+                            break;
+                        }
+                    if (!suffixOk && !suffixes.isEmpty())
+                        fileName.append(suffixes.at(0));
+                }
+            }
             if (QFile::exists(fileName)) {
                 if (QMessageBox::warning(d->m_mainWindow, tr("Overwrite?"),
-                        tr("An item named '%1' already exists at this location. Do you want to overwrite it?").arg(fileName),
-                        QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
+                    tr("An item named '%1' already exists at this location. "
+                       "Do you want to overwrite it?").arg(fileName),
+                    QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
                     repeat = true;
+                }
             }
         }
     } while (repeat);
@@ -713,12 +737,19 @@ QString FileManager::getSaveFileNameWithExtension(const QString &title, const QS
     return fileName;
 }
 
+QString FileManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
+                                                  const QString &filter)
+{
+    QString selected = filter;
+    return getSaveFileName(title, pathIn, filter, &selected);
+}
+
 /*!
     \fn QString FileManager::getSaveAsFileName(IFile *file)
 
     Asks the user for a new file name (Save File As) for /arg file.
 */
-QString FileManager::getSaveAsFileName(IFile *file)
+QString FileManager::getSaveAsFileName(IFile *file, const QString &filter, QString *selectedFilter)
 {
     if (!file)
         return QLatin1String("");
@@ -732,17 +763,20 @@ QString FileManager::getSaveAsFileName(IFile *file)
         if (!defaultPath.isEmpty())
             path = defaultPath;
     }
+
     QString filterString;
-    QString preferredSuffix;
-    if (const MimeType mt = Core::ICore::instance()->mimeDatabase()->findByFile(fi)) {
-        filterString = mt.filterString();
-        preferredSuffix = mt.preferredSuffix();
+    if (filter.isEmpty()) {
+        if (const MimeType &mt = Core::ICore::instance()->mimeDatabase()->findByFile(fi))
+            filterString = mt.filterString();
+        selectedFilter = &filterString;
+    } else {
+        filterString = filter;
     }
 
-    absoluteFilePath = getSaveFileNameWithExtension(tr("Save File As"),
+    absoluteFilePath = getSaveFileName(tr("Save File As"),
         path + QDir::separator() + fileName,
         filterString,
-        preferredSuffix);
+        selectedFilter);
     return absoluteFilePath;
 }
 
diff --git a/src/plugins/coreplugin/filemanager.h b/src/plugins/coreplugin/filemanager.h
index 9834bce17e5..0de99333084 100644
--- a/src/plugins/coreplugin/filemanager.h
+++ b/src/plugins/coreplugin/filemanager.h
@@ -87,10 +87,12 @@ public:
     QStringList getOpenFileNames(const QString &filters,
                                  const QString path = QString(),
                                  QString *selectedFilter = 0);
-
-    QString getSaveFileNameWithExtension(const QString &title, const QString &path,
-                                    const QString &fileFilter, const QString &extension);
-    QString getSaveAsFileName(IFile *file);
+    QString getSaveFileName(const QString &title, const QString &pathIn,
+                            const QString &filter = QString(), QString *selectedFilter = 0);
+    QString getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
+                                         const QString &filter);
+    QString getSaveAsFileName(IFile *file, const QString &filter = QString(),
+                              QString *selectedFilter = 0);
 
     QList<IFile *> saveModifiedFilesSilently(const QList<IFile *> &files);
     QList<IFile *> saveModifiedFiles(const QList<IFile *> &files,
-- 
GitLab