Commit 19663fee authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Added 'Open with ->Qt Designer' in Project Explorer.



Added IExternalEditor which knows a kind and a mimetype.
Make EditorManager and ProjectExplorer "Open With" query
the interface and add the respective kinds.
Add "openExternalEditor" to EditorManager.
Add External editors for Designer and Linguist,
making use of Mac 'open' or Designer's Tcp socket mechanism
to ensure files are opened in the same instance (per Qt version).

Task-number: 249392
Reviewed-by: default avatarcon <qtc-committer@nokia.com>
parent 398451b9
......@@ -39,6 +39,7 @@ SOURCES += mainwindow.cpp \
editormanager/editorview.cpp \
editormanager/openeditorsview.cpp \
editormanager/openeditorswindow.cpp \
editormanager/iexternaleditor.cpp \
actionmanager/actionmanager.cpp \
actionmanager/command.cpp \
actionmanager/actioncontainer.cpp \
......@@ -99,6 +100,7 @@ HEADERS += mainwindow.h \
editormanager/openeditorsview.h \
editormanager/openeditorswindow.h \
editormanager/ieditor.h \
editormanager/iexternaleditor.h \
editormanager/ieditorfactory.h \
actionmanager/actioncontainer.h \
actionmanager/actionmanager.h \
......
......@@ -44,6 +44,7 @@
#include <coreplugin/uniqueidmanager.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/editormanager/ieditorfactory.h>
#include <coreplugin/editormanager/iexternaleditor.h>
#include <coreplugin/baseview.h>
#include <coreplugin/imode.h>
......@@ -439,7 +440,7 @@ void EditorManager::init()
context << m_d->m_core->uniqueIDManager()->uniqueIdentifier("QtCreator.OpenDocumentsView");
m_d->m_coreListener = new EditorClosingCoreListener(this);
pluginManager()->addObject(m_d->m_coreListener);
m_d->m_openEditorsFactory = new OpenEditorsViewFactory();
......@@ -894,22 +895,23 @@ void EditorManager::activateEditor(Core::Internal::EditorView *view, Core::IEdit
}
}
/* Find editors for a mimetype, best matching at the front
* of the list. Recurse over the parent classes of the mimetype to
* find them. */
/* For something that has a 'QStringList mimeTypes' (IEditorFactory
* or IExternalEditor), find the one best matching the mimetype passed in.
* Recurse over the parent classes of the mimetype to find them. */
template <class EditorFactoryLike>
static void mimeTypeFactoryRecursion(const MimeDatabase *db,
const MimeType &mimeType,
const QList<IEditorFactory*> &allFactories,
const QList<EditorFactoryLike*> &allFactories,
bool firstMatchOnly,
QList<IEditorFactory*> *list)
QList<EditorFactoryLike*> *list)
{
typedef QList<IEditorFactory*> EditorFactoryList;
typedef typename QList<EditorFactoryLike*>::const_iterator EditorFactoryLikeListConstIterator;
// Loop factories to find type
const QString type = mimeType.type();
const EditorFactoryList::const_iterator fcend = allFactories.constEnd();
for (EditorFactoryList::const_iterator fit = allFactories.constBegin(); fit != fcend; ++fit) {
const EditorFactoryLikeListConstIterator fcend = allFactories.constEnd();
for (EditorFactoryLikeListConstIterator fit = allFactories.constBegin(); fit != fcend; ++fit) {
// Exclude duplicates when recursing over xml or C++ -> C -> text.
IEditorFactory *factory = *fit;
EditorFactoryLike *factory = *fit;
if (!list->contains(factory) && factory->mimeTypes().contains(type)) {
list->push_back(*fit);
if (firstMatchOnly)
......@@ -917,7 +919,7 @@ static void mimeTypeFactoryRecursion(const MimeDatabase *db,
break;
}
}
// Any parent classes? -> recurse
// Any parent mime type classes? -> recurse
QStringList parentTypes = mimeType.subClassesOf();
if (parentTypes.empty())
return;
......@@ -939,6 +941,29 @@ EditorManager::EditorFactoryList
return rc;
}
EditorManager::ExternalEditorList
EditorManager::externalEditors(const MimeType &mimeType, bool bestMatchOnly) const
{
ExternalEditorList rc;
const ExternalEditorList allEditors = pluginManager()->getObjects<IExternalEditor>();
mimeTypeFactoryRecursion(m_d->m_core->mimeDatabase(), mimeType, allEditors, bestMatchOnly, &rc);
if (debugEditorManager)
qDebug() << Q_FUNC_INFO << mimeType.type() << " returns " << rc;
return rc;
}
/* For something that has a 'QString kind' (IEditorFactory
* or IExternalEditor), find the one matching a kind. */
template <class EditorFactoryLike>
inline EditorFactoryLike *findByKind(ExtensionSystem::PluginManager *pm,
const QString &kind)
{
foreach(EditorFactoryLike *efl, pm->getObjects<EditorFactoryLike>())
if (kind == efl->kind())
return efl;
return 0;
}
IEditor *EditorManager::createEditor(const QString &editorKind,
const QString &fileName)
{
......@@ -959,14 +984,8 @@ IEditor *EditorManager::createEditor(const QString &editorKind,
factories = editorFactories(mimeType, true);
} else {
// Find by editor kind
const EditorFactoryList allFactories = pluginManager()->getObjects<IEditorFactory>();
const EditorFactoryList::const_iterator acend = allFactories.constEnd();
for (EditorFactoryList::const_iterator ait = allFactories.constBegin(); ait != acend; ++ait) {
if (editorKind == (*ait)->kind()) {
factories.push_back(*ait);
break;
}
}
if (IEditorFactory *factory = findByKind<IEditorFactory>(pluginManager(), editorKind))
factories.push_back(factory);
}
if (factories.empty()) {
qWarning("%s: unable to find an editor factory for the file '%s', editor kind '%s'.",
......@@ -1002,27 +1021,40 @@ void EditorManager::addEditor(IEditor *editor, bool isDuplicate)
// Run the OpenWithDialog and return the editor kind
// selected by the user.
QString EditorManager::getOpenWithEditorKind(const QString &fileName) const
QString EditorManager::getOpenWithEditorKind(const QString &fileName,
bool *isExternalEditor) const
{
QStringList editorKinds;
// Collect editors that can open the file
if (const MimeType mt = m_d->m_core->mimeDatabase()->findByFile(fileName)) {
const EditorFactoryList editors = editorFactories(mt, false);
const int size = editors.size();
for (int i = 0; i < size; i++) {
editorKinds.push_back(editors.at(i)->kind());
}
const MimeType mt = m_d->m_core->mimeDatabase()->findByFile(fileName);
if (!mt)
return QString();
QStringList allEditorKinds;
QStringList externalEditorKinds;
// Built-in
const EditorFactoryList editors = editorFactories(mt, false);
const int size = editors.size();
for (int i = 0; i < size; i++) {
allEditorKinds.push_back(editors.at(i)->kind());
}
if (editorKinds.empty())
// External editors
const ExternalEditorList exEditors = externalEditors(mt, false);
const int esize = exEditors.size();
for (int i = 0; i < esize; i++) {
externalEditorKinds.push_back(exEditors.at(i)->kind());
allEditorKinds.push_back(exEditors.at(i)->kind());
}
if (allEditorKinds.empty())
return QString();
// Run dialog.
OpenWithDialog dialog(fileName, m_d->m_core->mainWindow());
dialog.setEditors(editorKinds);
dialog.setEditors(allEditorKinds);
dialog.setCurrentEditor(0);
if (dialog.exec() != QDialog::Accepted)
return QString();
return dialog.editor();
const QString selectedKind = dialog.editor();
if (isExternalEditor)
*isExternalEditor = externalEditorKinds.contains(selectedKind);
return selectedKind;
}
static QString formatFileFilters(const Core::ICore *core, QString *selectedFilter)
......@@ -1093,6 +1125,20 @@ IEditor *EditorManager::openEditor(const QString &fileName, const QString &edito
return editor;
}
bool EditorManager::openExternalEditor(const QString &fileName, const QString &editorKind)
{
IExternalEditor *ee = findByKind<IExternalEditor>(pluginManager(), editorKind);
if (!ee)
return false;
QString errorMessage;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
const bool ok = ee->startEditor(fileName, &errorMessage);
QApplication::restoreOverrideCursor();
if (!ok)
QMessageBox::critical(m_d->m_core->mainWindow(), tr("Opening File"), errorMessage);
return ok;
}
QStringList EditorManager::getOpenFileNames() const
{
QString dir;
......@@ -1440,7 +1486,7 @@ void EditorManager::addCurrentPositionToNavigationHistory(const QByteArray &save
return;
if (!editor->file())
return;
QString fileName = editor->file()->fileName();
QByteArray state;
if (saveState.isNull()) {
......@@ -1577,7 +1623,6 @@ QByteArray EditorManager::saveState() const
stream << m_d->m_editorStates;
QList<EditorModel::Entry> entries = m_d->m_editorModel->entries();
stream << entries.count();
......
......@@ -49,6 +49,7 @@ class IContext;
class ICore;
class IEditor;
class IEditorFactory;
class IExternalEditor;
class MimeType;
class IFile;
class IMode;
......@@ -93,6 +94,7 @@ class CORE_EXPORT EditorManager : public QWidget
public:
typedef QList<IEditorFactory*> EditorFactoryList;
typedef QList<IExternalEditor*> ExternalEditorList;
explicit EditorManager(ICore *core, QWidget *parent);
virtual ~EditorManager();
......@@ -108,10 +110,11 @@ public:
IEditor *openEditor(const QString &fileName,
const QString &editorKind = QString(),
OpenEditorFlags flags = 0);
bool openExternalEditor(const QString &fileName, const QString &editorKind);
QStringList getOpenFileNames() const;
QString getOpenWithEditorKind(const QString &fileName) const;
QString getOpenWithEditorKind(const QString &fileName, bool *isExternalEditor = 0) const;
void ensureEditorManagerVisible();
IEditor *newFile(const QString &editorKind,
......@@ -167,6 +170,7 @@ public:
void hideEditorStatusBar(const QString &kind);
EditorFactoryList editorFactories(const MimeType &mimeType, bool bestMatchOnly = true) const;
ExternalEditorList externalEditors(const MimeType &mimeType, bool bestMatchOnly = true) const;
void setExternalEditor(const QString &);
QString externalEditor() const;
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
**************************************************************************/
#include "iexternaleditor.h"
/*!
\class Core::IExternalEditor
\mainclass
\brief Core::IExternalEditor allows for registering an external
Editor in the \gui{Open With...} dialogs .
*/
/*!
\fn IExternalEditor::IExternalEditor(QObject *parent)
\internal
*/
/*!
\fn IExternalEditor::~IExternalEditor()
\internal
*/
/*!
\fn QStringList IExternalEditor::mimeTypes() const
Returns the mime type the editor supports
*/
/*!
\fn QString IExternalEditor::kind() const
Returns the editor kind (identifying string).
*/
/*!
\fn bool IExternalEditor::startEditor(const QString &fileName, QString *errorMessage) = 0;
Opens the editor with \param fileName. Returns true on success or false
on failure along with the error in \param errorMessage.
*/
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
**************************************************************************/
#ifndef IEXTERNALEDITOR_H
#define IEXTERNALEDITOR_H
#include <coreplugin/core_global.h>
#include <QtCore/QObject>
namespace Core {
class CORE_EXPORT IExternalEditor : public QObject
{
Q_OBJECT
public:
explicit IExternalEditor(QObject *parent = 0) : QObject(parent) {}
virtual ~IExternalEditor() {}
virtual QStringList mimeTypes() const = 0;
virtual QString kind() const = 0;
virtual bool startEditor(const QString &fileName, QString *errorMessage) = 0;
};
} // namespace Core
#endif // IEXTERNALEDITOR_H
......@@ -910,10 +910,15 @@ void MainWindow::openFileWith()
{
QStringList fileNames = editorManager()->getOpenFileNames();
foreach (const QString &fileName, fileNames) {
const QString editorKind = editorManager()->getOpenWithEditorKind(fileName);
bool isExternal;
const QString editorKind = editorManager()->getOpenWithEditorKind(fileName, &isExternal);
if (editorKind.isEmpty())
continue;
editorManager()->openEditor(fileName, editorKind);
if (isExternal) {
editorManager()->openExternalEditor(fileName, editorKind);
} else {
editorManager()->openEditor(fileName, editorKind);
}
}
}
......
......@@ -70,6 +70,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/editormanager/ieditorfactory.h>
#include <coreplugin/editormanager/iexternaleditor.h>
#include <coreplugin/findplaceholder.h>
#include <coreplugin/basefilewizard.h>
#include <coreplugin/mainwindow.h>
......@@ -93,7 +94,8 @@
#include <QtGui/QMessageBox>
Q_DECLARE_METATYPE(QSharedPointer<ProjectExplorer::RunConfiguration>);
Q_DECLARE_METATYPE(Core::IEditorFactory *);
Q_DECLARE_METATYPE(Core::IEditorFactory*);
Q_DECLARE_METATYPE(Core::IExternalEditor*);
namespace {
bool debug = false;
......@@ -1901,6 +1903,7 @@ void ProjectExplorerPlugin::runConfigurationMenuTriggered(QAction *action)
void ProjectExplorerPlugin::populateOpenWithMenu()
{
typedef QList<Core::IEditorFactory*> EditorFactoryList;
typedef QList<Core::IExternalEditor*> ExternalEditorList;
m_openWithMenu->clear();
......@@ -1910,7 +1913,8 @@ void ProjectExplorerPlugin::populateOpenWithMenu()
Core::ICore *core = Core::ICore::instance();
if (const Core::MimeType mt = core->mimeDatabase()->findByFile(QFileInfo(fileName))) {
const EditorFactoryList factories = core->editorManager()->editorFactories(mt, false);
anyMatches = !factories.empty();
const ExternalEditorList externalEditors = core->editorManager()->externalEditors(mt, false);
anyMatches = !factories.empty() || !externalEditors.empty();
if (anyMatches) {
const QList<Core::IEditor *> editorsOpenForFile = core->editorManager()->editorsForFileName(fileName);
// Add all suitable editors
......@@ -1930,8 +1934,13 @@ void ProjectExplorerPlugin::populateOpenWithMenu()
}
action->setEnabled(enabled);
}
} // for editor factories
// Add all suitable external editors
foreach (Core::IExternalEditor *externalEditor, externalEditors) {
QAction * const action = m_openWithMenu->addAction(externalEditor->kind());
action->setData(qVariantFromValue(externalEditor));
}
}
} // matches
}
m_openWithMenu->setEnabled(anyMatches);
}
......@@ -1942,14 +1951,18 @@ void ProjectExplorerPlugin::openWithMenuTriggered(QAction *action)
qWarning() << "ProjectExplorerPlugin::openWithMenuTriggered no action, can't happen.";
return;
}
Core::IEditorFactory * const editorFactory = qVariantValue<Core::IEditorFactory *>(action->data());
if (!editorFactory) {
qWarning() << "Editor Factory not attached to action, can't happen"<<editorFactory;
Core::EditorManager *em = Core::EditorManager::instance();
const QVariant data = action->data();
if (qVariantCanConvert<Core::IEditorFactory *>(data)) {
Core::IEditorFactory *factory = qVariantValue<Core::IEditorFactory *>(data);
em->openEditor(currentNode()->path(), factory->kind());
em->ensureEditorManagerVisible();
return;
}
Core::EditorManager *em = Core::EditorManager::instance();
em->openEditor(currentNode()->path(), editorFactory->kind());
em->ensureEditorManagerVisible();
if (qVariantCanConvert<Core::IExternalEditor *>(data)) {
Core::IExternalEditor *externalEditor = qVariantValue<Core::IExternalEditor *>(data);
em->openExternalEditor(currentNode()->path(), externalEditor->kind());
}
}
void ProjectExplorerPlugin::updateSessionMenu()
......
......@@ -10,4 +10,9 @@
<comment>Qt Project include file</comment>
<glob pattern="*.pri"/>
</mime-type>
<mime-type type="application/x-linguist">
<sub-class-of type="application/xml"/>
<comment>message catalog</comment>
<glob pattern="*.ts"/>
</mime-type>
</mime-info>
#include "externaleditors.h"
#include "qt4project.h"
#include "qt4projectmanagerconstants.h"
#include "qtversionmanager.h"
#include <utils/synchronousprocess.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
#include <designer/designerconstants.h>
#include <QtCore/QProcess>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
#include <QtCore/QSignalMapper>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
enum { debug = 0 };
namespace Qt4ProjectManager {
namespace Internal {
// Figure out the Qt4 project used by the file if any
static Qt4Project *qt4ProjectFor(const QString &fileName)
{
ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance();
if (ProjectExplorer::Project *baseProject = pe->session()->projectForFile(fileName))
if (Qt4Project *project = qobject_cast<Qt4Project*>(baseProject))
return project;
return 0;
}
// ------------ Messages
static inline QString msgStartFailed(const QString &binary, QStringList arguments)
{
arguments.push_front(binary);
return ExternalQtEditor::tr("Unable to start \"%1\"").arg(arguments.join(QString(QLatin1Char(' '))));
}
static inline QString msgAppNotFound(const QString &kind)
{
return ExternalQtEditor::tr("The application \"%1\" could not be found.").arg(kind);
}
// -- Commands and helpers
#ifdef Q_OS_MAC
static const char *linguistBinaryC = "Linguist";
static const char *designerBinaryC = "Designer";
// Mac: Change the call 'Foo.app/Contents/MacOS/Foo <file>' to
// 'open Foo.app <file>'. Do this ONLY if you do not want to have
// command line arguments
static void createMacOpenCommand(QString *binary, QStringList *arguments)
{
const int appFolderIndex = binary->lastIndexOf(QLatin1String("/Contents/MacOS/"));
if (appFolderIndex != -1) {
binary->truncate(appFolderIndex);
arguments->push_front(*binary);
*binary = QLatin1String("open");
}
}
#else
static const char *linguistBinaryC = "linguist";
static const char *designerBinaryC = "designer";
#endif
static const char *designerKindC = "Qt Designer";
// -------------- ExternalQtEditor
ExternalQtEditor::ExternalQtEditor(const QString &kind,
const QString &mimetype,
QObject *parent) :
Core::IExternalEditor(parent),
m_mimeTypes(mimetype),
m_kind(kind)
{
}
QStringList ExternalQtEditor::mimeTypes() const
{
return m_mimeTypes;
}
QString ExternalQtEditor::kind() const
{
return m_kind;
}
bool ExternalQtEditor::getEditorLaunchData(const QString &fileName,
QtVersionCommandAccessor commandAccessor,
const QString &fallbackBinary,
const QStringList &additionalArguments,
bool useMacOpenCommand,
EditorLaunchData *data,
QString *errorMessage) const
{
const Qt4Project *project = qt4ProjectFor(fileName);
// Get the binary either from the current Qt version of the project or Path
if (project) {
const QtVersion *qtVersion= project->qtVersion(project->activeBuildConfiguration());
data->binary = (qtVersion->*commandAccessor)();
data->workingDirectory = QFileInfo(project->file()->fileName()).absolutePath();
} else {
data->workingDirectory.clear();
data->binary = Core::Utils::SynchronousProcess::locateBinary(fallbackBinary);
}
if (data->binary.isEmpty()) {
*errorMessage = msgAppNotFound(kind());
return false;
}
// Setup binary + arguments, use Mac Open if appropriate
data->arguments = additionalArguments;
data->arguments.push_back(fileName);
#ifdef Q_OS_MAC
if (useMacOpenCommand)
createMacOpenCommand(&binary, &(data->arguments));