diff options
author | Friedemann Kleint <Friedemann.Kleint@nokia.com> | 2009-05-19 14:54:52 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@nokia.com> | 2009-05-19 14:54:52 +0200 |
commit | 19663fee41994b27d07bd22c1b466520ff22a5ac (patch) | |
tree | ffa053624459b55e1bcf3dffc151d5e8db845acc /src/plugins/qt4projectmanager/externaleditors.cpp | |
parent | 398451b9d554cee61e1620032e5280cfca9474ad (diff) | |
download | qt-creator-19663fee41994b27d07bd22c1b466520ff22a5ac.tar.gz |
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: con <qtc-committer@nokia.com>
Diffstat (limited to 'src/plugins/qt4projectmanager/externaleditors.cpp')
-rw-r--r-- | src/plugins/qt4projectmanager/externaleditors.cpp | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/src/plugins/qt4projectmanager/externaleditors.cpp b/src/plugins/qt4projectmanager/externaleditors.cpp new file mode 100644 index 0000000000..d7f668992b --- /dev/null +++ b/src/plugins/qt4projectmanager/externaleditors.cpp @@ -0,0 +1,254 @@ +#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)); +#else + Q_UNUSED(useMacOpenCommand) +#endif + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << data->binary << data->arguments; + return true; +} + +bool ExternalQtEditor::startEditorProcess(const EditorLaunchData &data, QString *errorMessage) +{ + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << data.binary << data.arguments << data.workingDirectory; + qint64 pid = 0; + if (!QProcess::startDetached(data.binary, data.arguments, data.workingDirectory, &pid)) { + *errorMessage = msgStartFailed(data.binary, data.arguments); + return false; + } + return true; +} + +// --------------- LinguistExternalEditor +LinguistExternalEditor::LinguistExternalEditor(QObject *parent) : + ExternalQtEditor(QLatin1String("Qt Linguist"), + QLatin1String(Qt4ProjectManager::Constants::LINGUIST_MIMETYPE), + parent) +{ +} + +bool LinguistExternalEditor::startEditor(const QString &fileName, QString *errorMessage) +{ + EditorLaunchData data; + return getEditorLaunchData(fileName, &QtVersion::linguistCommand, + QLatin1String(linguistBinaryC), + QStringList(), true, &data, errorMessage) + && startEditorProcess(data, errorMessage); +} + +// --------------- MacDesignerExternalEditor, using Mac 'open' +MacDesignerExternalEditor::MacDesignerExternalEditor(QObject *parent) : + ExternalQtEditor(QLatin1String(designerKindC), + QLatin1String(Qt4ProjectManager::Constants::FORM_MIMETYPE), + parent) +{ +} + +bool MacDesignerExternalEditor::startEditor(const QString &fileName, QString *errorMessage) +{ + EditorLaunchData data; + return getEditorLaunchData(fileName, &QtVersion::designerCommand, + QLatin1String(designerBinaryC), + QStringList(), true, &data, errorMessage) + && startEditorProcess(data, errorMessage); + return false; +} + +// --------------- DesignerExternalEditor with Designer Tcp remote control. +DesignerExternalEditor::DesignerExternalEditor(QObject *parent) : + ExternalQtEditor(QLatin1String(designerKindC), + QLatin1String(Designer::Constants::FORM_MIMETYPE), + parent), + m_terminationMapper(0) +{ +} + +void DesignerExternalEditor::processTerminated(const QString &binary) +{ + const ProcessCache::iterator it = m_processCache.find(binary); + if (it == m_processCache.end()) + return; + // Make sure socket is closed and cleaned, remove from cache + QTcpSocket *socket = it.value(); + m_processCache.erase(it); // Note that closing will cause the slot to be retriggered + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << binary << socket->state(); + if (socket->state() == QAbstractSocket::ConnectedState) + socket->close(); + socket->deleteLater(); +} + +bool DesignerExternalEditor::startEditor(const QString &fileName, QString *errorMessage) +{ + EditorLaunchData data; + // Find the editor binary + if (!getEditorLaunchData(fileName, &QtVersion::designerCommand, + QLatin1String(designerBinaryC), + QStringList(), false, &data, errorMessage)) { + return false; + } + // Known one? + const ProcessCache::iterator it = m_processCache.find(data.binary); + if (it != m_processCache.end()) { + // Process is known, write to its socket to cause it to open the file + if (debug) + qDebug() << Q_FUNC_INFO << "\nWriting to socket:" << data.binary << fileName; + QTcpSocket *socket = it.value(); + if (!socket->write(fileName.toUtf8() + '\n')) { + *errorMessage = tr("Qt Designer is not responding (%1).").arg(socket->errorString()); + return false; + } + return true; + } + // No process yet. Create socket & launch the process + QTcpServer server; + if (!server.listen(QHostAddress::LocalHost)) { + *errorMessage = tr("Unable to create server socket: %1").arg(server.errorString()); + return false; + } + const quint16 port = server.serverPort(); + if (debug) + qDebug() << Q_FUNC_INFO << "\nLaunching server:" << port << data.binary << fileName; + // Start first one with file and socket as '-client port file' + // Wait for the socket listening + data.arguments.push_front(QString::number(port)); + data.arguments.push_front(QLatin1String("-client")); + + if (!startEditorProcess(data, errorMessage)) + return false; + // Set up signal mapper to track process termination via socket + if (!m_terminationMapper) { + m_terminationMapper = new QSignalMapper(this); + connect(m_terminationMapper, SIGNAL(mapped(QString)), this, SLOT(processTerminated(QString))); + } + // Insert into cache if socket is created, else try again next time + if (server.waitForNewConnection(3000)) { + QTcpSocket *socket = server.nextPendingConnection(); + socket->setParent(this); + m_processCache.insert(data.binary, socket); + m_terminationMapper->setMapping(socket, data.binary); + connect(socket, SIGNAL(disconnected()), m_terminationMapper, SLOT(map())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), m_terminationMapper, SLOT(map())); + } + return true; +} + +} // namespace Internal +} // namespace Qt4ProjectManager |