diff options
Diffstat (limited to 'src/plugins')
321 files changed, 14477 insertions, 3286 deletions
diff --git a/src/plugins/bineditor/bineditor.cpp b/src/plugins/bineditor/bineditor.cpp index 0a6dbcfbb2..23b5d04ffb 100644 --- a/src/plugins/bineditor/bineditor.cpp +++ b/src/plugins/bineditor/bineditor.cpp @@ -1042,7 +1042,7 @@ bool BinEditor::event(QEvent *e) { selEnd = selStart + 1; byteCount = 1; } - if (byteCount <= 8) { + if (m_hexCursor && byteCount <= 8) { const QPoint &startPoint = offsetToPos(selStart); const QPoint &endPoint = offsetToPos(selEnd); const QPoint expandedEndPoint @@ -1138,14 +1138,25 @@ void BinEditor::keyPressEvent(QKeyEvent *e) } break; case Qt::Key_Home: - setCursorPosition((e->modifiers() & Qt::ControlModifier) ? - 0 : (m_cursorPosition/16 * 16), moveMode); + if (e->modifiers() & Qt::ControlModifier) { + if (m_inLazyMode) + emit startOfFileRequested(editorInterface()); + else + setCursorPosition(0); + } else { + setCursorPosition(m_cursorPosition/16 * 16, moveMode); + } break; case Qt::Key_End: - setCursorPosition((e->modifiers() & Qt::ControlModifier) ? - (m_size-1) : (m_cursorPosition/16 * 16 + 15), moveMode); + if (e->modifiers() & Qt::ControlModifier) { + if (m_inLazyMode) + emit endOfFileRequested(editorInterface()); + else + setCursorPosition(m_size - 1); + } else { + setCursorPosition(m_cursorPosition/16 * 16 + 15, moveMode); + } break; - default: if (m_readOnly) break; @@ -1380,7 +1391,7 @@ void BinEditor::jumpToAddress(quint64 address) { if (address >= m_baseAddr && address < m_baseAddr + m_data.size()) setCursorPosition(address - m_baseAddr); - else + else if (m_inLazyMode) emit newRangeRequested(editorInterface(), address); } @@ -1392,7 +1403,7 @@ void BinEditor::setNewWindowRequestAllowed() QPoint BinEditor::offsetToPos(int offset) { const int x = m_labelWidth + (offset % 16) * m_columnWidth; - const int y = (offset / 16) * m_lineHeight; + const int y = (offset / 16 - verticalScrollBar()->value()) * m_lineHeight; return QPoint(x, y); } diff --git a/src/plugins/bineditor/bineditor.h b/src/plugins/bineditor/bineditor.h index cea7b8d168..70241a7e63 100644 --- a/src/plugins/bineditor/bineditor.h +++ b/src/plugins/bineditor/bineditor.h @@ -84,7 +84,7 @@ public: }; int cursorPosition() const; - void setCursorPosition(int pos, MoveMode moveMode = MoveAnchor); + Q_INVOKABLE void setCursorPosition(int pos, MoveMode moveMode = MoveAnchor); void jumpToAddress(quint64 address); void setModified(bool); @@ -133,6 +133,8 @@ Q_SIGNALS: void lazyDataRequested(Core::IEditor *editor, quint64 block, bool synchronous); void newWindowRequested(quint64 address); void newRangeRequested(Core::IEditor *, quint64 address); + void startOfFileRequested(Core::IEditor *); + void endOfFileRequested(Core::IEditor *); protected: void scrollContentsBy(int dx, int dy); diff --git a/src/plugins/bineditor/bineditorplugin.cpp b/src/plugins/bineditor/bineditorplugin.cpp index b6fa501118..b7858db4f3 100644 --- a/src/plugins/bineditor/bineditorplugin.cpp +++ b/src/plugins/bineditor/bineditorplugin.cpp @@ -154,6 +154,8 @@ public: return result; } + void replace(const QString &, const QString &, + Find::IFindSupport::FindFlags) { } bool replaceStep(const QString &, const QString &, Find::IFindSupport::FindFlags) { return false;} int replaceAll(const QString &, const QString &, @@ -180,6 +182,10 @@ public: this, SLOT(provideData(Core::IEditor *, quint64))); connect(m_editor, SIGNAL(newRangeRequested(Core::IEditor*,quint64)), this, SLOT(provideNewRange(Core::IEditor*,quint64))); + connect(m_editor, SIGNAL(startOfFileRequested(Core::IEditor*)), this, + SLOT(handleStartOfFileRequested(Core::IEditor*))); + connect(m_editor, SIGNAL(endOfFileRequested(Core::IEditor*)), this, + SLOT(handleEndOfFileRequested(Core::IEditor*))); } ~BinEditorFile() {} @@ -209,7 +215,7 @@ public: && file.open(QIODevice::ReadOnly)) { m_fileName = fileName; qint64 maxRange = 64 * 1024 * 1024; - if (file.isSequential() && file.size() <= maxRange) { + if (file.size() <= maxRange) { m_editor->setData(file.readAll()); } else { m_editor->setLazyData(offset, maxRange); @@ -241,6 +247,14 @@ private slots: open(m_fileName, offset); } + void handleStartOfFileRequested(Core::IEditor *) { + open(m_fileName, 0); + } + + void handleEndOfFileRequested(Core::IEditor *) { + open(m_fileName, QFileInfo(m_fileName).size() - 1); + } + public: void setFilename(const QString &filename) { diff --git a/src/plugins/cmakeprojectmanager/cmakehighlighter.cpp b/src/plugins/cmakeprojectmanager/cmakehighlighter.cpp index b42a570586..39172a36a4 100644 --- a/src/plugins/cmakeprojectmanager/cmakehighlighter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakehighlighter.cpp @@ -46,7 +46,7 @@ static bool isVariable(const QString &word) CMakeHighlighter::CMakeHighlighter(QTextDocument *document) : - QSyntaxHighlighter(document) + TextEditor::SyntaxHighlighter(document) { } diff --git a/src/plugins/cmakeprojectmanager/cmakehighlighter.h b/src/plugins/cmakeprojectmanager/cmakehighlighter.h index dce5acf057..5c76a21807 100644 --- a/src/plugins/cmakeprojectmanager/cmakehighlighter.h +++ b/src/plugins/cmakeprojectmanager/cmakehighlighter.h @@ -30,6 +30,7 @@ #ifndef CMAKEHIGHLIGHTER_H #define CMAKEHIGHLIGHTER_H +#include <texteditor/syntaxhighlighter.h> #include <QtCore/QtAlgorithms> #include <QtGui/QSyntaxHighlighter> #include <QtGui/QTextCharFormat> @@ -40,7 +41,7 @@ namespace Internal { /* This is a simple syntax highlighter for CMake files. * It highlights variables, commands, strings and comments. * Multi-line strings and variables inside strings are also recognized. */ -class CMakeHighlighter : public QSyntaxHighlighter +class CMakeHighlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT public: diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 8e0107ed40..f589e8703c 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -34,7 +34,6 @@ #include "modemanager.h" #include "fileiconprovider.h" #include "designmode.h" -#include "ssh/ne7sshobject.h" #include <extensionsystem/pluginmanager.h> @@ -89,7 +88,6 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) m_designMode = new DesignMode(editorManager); addObject(m_designMode); - Ne7SshObject::instance(); } return success; } @@ -110,10 +108,10 @@ void CorePlugin::fileOpenRequest(const QString &f) remoteCommand(QStringList(), QStringList(f)); } -void CorePlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() { m_mainWindow->aboutToShutdown(); - Ne7SshObject::removeInstance(); + return SynchronousShutdown; } Q_EXPORT_PLUGIN(CorePlugin) diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index bd60255047..af0f85f94b 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -49,7 +49,7 @@ public: virtual bool initialize(const QStringList &arguments, QString *errorMessage = 0); virtual void extensionsInitialized(); - virtual void aboutToShutdown(); + virtual ShutdownFlag aboutToShutdown(); virtual void remoteCommand(const QStringList & /* options */, const QStringList &args); public slots: diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index 3b607d037b..ad81f39dc6 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -85,10 +85,27 @@ SOURCES += mainwindow.cpp \ editormanager/systemeditor.cpp \ designmode.cpp \ editortoolbar.cpp \ - ssh/ne7sshobject.cpp \ - ssh/sshconnection.cpp \ + helpmanager.cpp \ + ssh/sshsendfacility.cpp \ + ssh/sshremoteprocess.cpp \ + ssh/sshpacketparser.cpp \ + ssh/sshpacket.cpp \ + ssh/sshoutgoingpacket.cpp \ ssh/sshkeygenerator.cpp \ - helpmanager.cpp + ssh/sshkeyexchange.cpp \ + ssh/sshincomingpacket.cpp \ + ssh/sshcryptofacility.cpp \ + ssh/sshconnection.cpp \ + ssh/sshchannelmanager.cpp \ + ssh/sshchannel.cpp \ + ssh/sshcapabilities.cpp \ + ssh/sftppacket.cpp \ + ssh/sftpoutgoingpacket.cpp \ + ssh/sftpoperation.cpp \ + ssh/sftpincomingpacket.cpp \ + ssh/sftpdefs.cpp \ + ssh/sftpchannel.cpp \ + ssh/sshdelayedsignal.cpp HEADERS += mainwindow.h \ editmode.h \ @@ -171,10 +188,33 @@ HEADERS += mainwindow.h \ editormanager/systemeditor.h \ designmode.h \ editortoolbar.h \ - ssh/ne7sshobject.h \ - ssh/sshconnection.h \ + helpmanager.h \ + ssh/sshsendfacility_p.h \ + ssh/sshremoteprocess.h \ + ssh/sshremoteprocess_p.h \ + ssh/sshpacketparser_p.h \ + ssh/sshpacket_p.h \ + ssh/sshoutgoingpacket_p.h \ ssh/sshkeygenerator.h \ - helpmanager.h + ssh/sshkeyexchange_p.h \ + ssh/sshincomingpacket_p.h \ + ssh/sshexception_p.h \ + ssh/ssherrors.h \ + ssh/sshcryptofacility_p.h \ + ssh/sshconnection.h \ + ssh/sshconnection_p.h \ + ssh/sshchannelmanager_p.h \ + ssh/sshchannel_p.h \ + ssh/sshcapabilities_p.h \ + ssh/sshbotanconversions_p.h \ + ssh/sftppacket_p.h \ + ssh/sftpoutgoingpacket_p.h \ + ssh/sftpoperation_p.h \ + ssh/sftpincomingpacket_p.h \ + ssh/sftpdefs.h \ + ssh/sftpchannel.h \ + ssh/sftpchannel_p.h \ + ssh/sshdelayedsignal_p.h FORMS += dialogs/newdialog.ui \ actionmanager/commandmappings.ui \ diff --git a/src/plugins/coreplugin/coreplugin_dependencies.pri b/src/plugins/coreplugin/coreplugin_dependencies.pri index 8726e1be08..e908601b22 100644 --- a/src/plugins/coreplugin/coreplugin_dependencies.pri +++ b/src/plugins/coreplugin/coreplugin_dependencies.pri @@ -1,3 +1,3 @@ include(../../libs/extensionsystem/extensionsystem.pri) include(../../libs/utils/utils.pri) -include(../../libs/3rdparty/net7ssh/net7ssh.pri) +include(../../libs/3rdparty/botan/botan.pri) diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index a9eb40b686..fc3ee7d4e0 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -1080,7 +1080,7 @@ IEditor *EditorManager::createEditor(const QString &editorId, mimeType = m_d->m_core->mimeDatabase()->findByType(QLatin1String("text/plain")); } // open text files > 48 MB in binary editor - if (fileInfo.size() > qint64(3) << 24 && mimeType.type().startsWith(QLatin1String("text"))) + if (fileInfo.size() > maxTextFileSize() && mimeType.type().startsWith(QLatin1String("text"))) mimeType = m_d->m_core->mimeDatabase()->findByType(QLatin1String("application/octet-stream")); factories = editorFactories(mimeType, true); } else { @@ -1389,13 +1389,22 @@ EditorManager::ReadOnlyAction QWidget *parent, bool displaySaveAsButton) { + // Version Control: If automatic open is desired, open right away. + bool promptVCS = false; + if (versionControl && versionControl->supportsOperation(IVersionControl::OpenOperation)) { + 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)), + tr("The file <i>%1</i> is read only.").arg(QDir::toNativeSeparators(fileName)), QMessageBox::Cancel, parent); - QPushButton *sccButton = 0; - if (versionControl && versionControl->supportsOperation(IVersionControl::OpenOperation)) - sccButton = msgBox.addButton(tr("Open with VCS (%1)").arg(versionControl->displayName()), QMessageBox::AcceptRole); + QPushButton *vcsButton = 0; + if (promptVCS) + vcsButton = msgBox.addButton(tr("Open with VCS (%1)").arg(versionControl->displayName()), QMessageBox::AcceptRole); QPushButton *makeWritableButton = msgBox.addButton(tr("Make writable"), QMessageBox::AcceptRole); @@ -1403,11 +1412,11 @@ EditorManager::ReadOnlyAction if (displaySaveAsButton) saveAsButton = msgBox.addButton(tr("Save as ..."), QMessageBox::ActionRole); - msgBox.setDefaultButton(sccButton ? sccButton : makeWritableButton); + msgBox.setDefaultButton(vcsButton ? vcsButton : makeWritableButton); msgBox.exec(); QAbstractButton *clickedButton = msgBox.clickedButton(); - if (clickedButton == sccButton) + if (clickedButton == vcsButton) return RO_OpenVCS; if (clickedButton == makeWritableButton) return RO_MakeWriteable; @@ -2004,5 +2013,10 @@ void EditorManager::gotoOtherSplit() } } } + +qint64 EditorManager::maxTextFileSize() +{ + return (qint64(3) << 24); +} //===================EditorClosingCoreListener====================== diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h index e488f040ea..541c5d3355 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.h +++ b/src/plugins/coreplugin/editormanager/editormanager.h @@ -199,6 +199,8 @@ public: QWidget *parent, bool displaySaveAsButton = false); + static qint64 maxTextFileSize(); + signals: void currentEditorChanged(Core::IEditor *editor); void editorCreated(Core::IEditor *editor, const QString &fileName); diff --git a/src/plugins/coreplugin/editortoolbar.cpp b/src/plugins/coreplugin/editortoolbar.cpp index 2e6fa7f0ae..9242dc3db1 100644 --- a/src/plugins/coreplugin/editortoolbar.cpp +++ b/src/plugins/coreplugin/editortoolbar.cpp @@ -274,7 +274,7 @@ void EditorToolBar::listContextMenu(QPoint pos) if (fileName.isEmpty()) return; QMenu menu; - menu.addAction(tr("Copy full path to clipboard")); + menu.addAction(tr("Copy Full Path to Clipboard")); if (menu.exec(m_editorList->mapToGlobal(pos))) { QApplication::clipboard()->setText(QDir::toNativeSeparators(fileName)); } diff --git a/src/plugins/coreplugin/filemanager.cpp b/src/plugins/coreplugin/filemanager.cpp index de29e822b7..de89e4dc8e 100644 --- a/src/plugins/coreplugin/filemanager.cpp +++ b/src/plugins/coreplugin/filemanager.cpp @@ -745,7 +745,11 @@ QStringList FileManager::getOpenFileNames(const QString &filters, const QString pathIn, QString *selectedFilter) { - const QString path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn; + QString path = pathIn; + if (path.isEmpty()) { + if (!d->m_currentFile.isEmpty()) + path = QFileInfo(d->m_currentFile).absoluteFilePath(); + } const QStringList files = QFileDialog::getOpenFileNames(d->m_mainWindow, tr("Open File"), path, filters, @@ -1009,7 +1013,7 @@ QString FileManager::currentFile() const QString FileManager::fileDialogInitialDirectory() const { if (!d->m_currentFile.isEmpty()) - return QFileInfo(d->m_currentFile).absoluteFilePath(); + return QFileInfo(d->m_currentFile).absolutePath(); return d->m_lastVisitedDirectory; } diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp index 7d2ebc92c4..02bd88acff 100644 --- a/src/plugins/coreplugin/helpmanager.cpp +++ b/src/plugins/coreplugin/helpmanager.cpp @@ -240,6 +240,13 @@ QUrl HelpManager::findFile(const QUrl &url) const return m_helpEngine->findFile(url); } +QByteArray HelpManager::fileData(const QUrl &url) const +{ + if (m_needsSetup) + return QByteArray(); + return m_helpEngine->fileData(url); +} + void HelpManager::handleHelpRequest(const QString &url) { emit helpRequested(QUrl(url)); diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h index 7df6c55659..a309643092 100644 --- a/src/plugins/coreplugin/helpmanager.h +++ b/src/plugins/coreplugin/helpmanager.h @@ -38,6 +38,7 @@ #include <QtCore/QStringList> #include <QtCore/QUrl> #include <QtCore/QVariant> +#include <QtCore/QByteArray> QT_FORWARD_DECLARE_CLASS(QHelpEngineCore) QT_FORWARD_DECLARE_CLASS(QSqlQuery) @@ -64,6 +65,7 @@ public: QStringList findKeywords(const QString &key, int maxHits = INT_MAX) const; QUrl findFile(const QUrl &url) const; + QByteArray fileData(const QUrl &url) const; void handleHelpRequest(const QString &url); QStringList registeredNamespaces() const; diff --git a/src/plugins/coreplugin/iversioncontrol.h b/src/plugins/coreplugin/iversioncontrol.h index 0cd29f9438..c402c1fb77 100644 --- a/src/plugins/coreplugin/iversioncontrol.h +++ b/src/plugins/coreplugin/iversioncontrol.h @@ -34,13 +34,20 @@ #include <QtCore/QObject> #include <QtCore/QString> +#include <QtCore/QFlags> namespace Core { class CORE_EXPORT IVersionControl : public QObject { Q_OBJECT + Q_ENUMS(SettingsFlag Operation) public: + enum SettingsFlag { + AutoOpen = 0x1 + }; + Q_DECLARE_FLAGS(SettingsFlags, SettingsFlag) + enum Operation { AddOperation, DeleteOperation, OpenOperation, MoveOperation, CreateRepositoryOperation, @@ -77,6 +84,12 @@ public: virtual bool vcsOpen(const QString &fileName) = 0; /*! + * Returns settings. + */ + + virtual SettingsFlags settingsFlags() const { return 0; } + + /*! * Called after a file has been added to a project If the version control * needs to know which files it needs to track you should reimplement this * function, e.g. 'p4 add', 'cvs add', 'svn add'. @@ -137,6 +150,8 @@ signals: // virtual bool sccManaged(const QString &filename) = 0; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Core::IVersionControl::SettingsFlags) + } // namespace Core #endif // IVERSIONCONTROL_H diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index e2edbfce8b..e8dfa3af65 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -70,6 +70,7 @@ #include <coreplugin/settingsdatabase.h> #include <utils/pathchooser.h> #include <utils/stylehelper.h> +#include <utils/stringutils.h> #include <extensionsystem/pluginmanager.h> #include <QtCore/QDebug> @@ -238,7 +239,6 @@ void MainWindow::setOverrideColor(const QColor &color) MainWindow::~MainWindow() { - hide(); ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); pm->removeObject(m_shortcutSettings); pm->removeObject(m_generalSettings); @@ -1134,6 +1134,7 @@ void MainWindow::aboutToShutdown() disconnect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(updateFocusWidget(QWidget*,QWidget*))); m_activeContext = 0; + hide(); } static const char *settingsGroup = "MainWindow"; @@ -1253,7 +1254,8 @@ void MainWindow::aboutToShowRecentFiles() bool hasRecentFiles = false; foreach (const QString &fileName, m_fileManager->recentFiles()) { hasRecentFiles = true; - QAction *action = aci->menu()->addAction(fileName); + QAction *action = aci->menu()->addAction( + Utils::withTildeHomePath(fileName)); action->setData(fileName); connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); } diff --git a/src/plugins/coreplugin/progressmanager/progressmanager_win.cpp b/src/plugins/coreplugin/progressmanager/progressmanager_win.cpp index 05fc9dacd4..882a642ba5 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager_win.cpp +++ b/src/plugins/coreplugin/progressmanager/progressmanager_win.cpp @@ -95,7 +95,7 @@ void Core::Internal::ProgressManagerPrivate::setApplicationLabel(const QString & font.setPointSize(font.pointSize()-2); p.setFont(font); p.drawText(QRect(QPoint(0,0), pix.size()), Qt::AlignHCenter|Qt::AlignCenter, text); - pITask->SetOverlayIcon(winId, pix.toWinHICON(), text.utf16()); + pITask->SetOverlayIcon(winId, pix.toWinHICON(), (wchar_t*)text.utf16()); } } diff --git a/src/plugins/coreplugin/ssh/ne7sshobject.cpp b/src/plugins/coreplugin/ssh/ne7sshobject.cpp deleted file mode 100644 index 9f94a55b97..0000000000 --- a/src/plugins/coreplugin/ssh/ne7sshobject.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of Qt Creator. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "ne7sshobject.h" - -#include <QtCore/QMutexLocker> - -#include <ne7ssh.h> - -namespace Core { -namespace Internal { - -Ne7SshObject *Ne7SshObject::instance() -{ - if (!m_instance) - m_instance = new Ne7SshObject; - return m_instance; -} - -void Ne7SshObject::removeInstance() -{ - delete m_instance; -} - -Ne7SshObject::Ptr Ne7SshObject::get() -{ - QMutexLocker locker(&m_mutex); - QSharedPointer<ne7ssh> shared = m_weakRef.toStrongRef(); - if (!shared) { - shared = QSharedPointer<ne7ssh>(new ne7ssh); - m_weakRef = shared; - } - return shared; -} - -Ne7SshObject::Ne7SshObject() -{ -} - -Ne7SshObject *Ne7SshObject::m_instance = 0; - -} // namespace Internal -} // namespace Core diff --git a/src/plugins/coreplugin/ssh/ne7sshobject.h b/src/plugins/coreplugin/ssh/ne7sshobject.h deleted file mode 100644 index 77d601ec86..0000000000 --- a/src/plugins/coreplugin/ssh/ne7sshobject.h +++ /dev/null @@ -1,80 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of Qt Creator. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef NE7SSHOBJECT_H -#define NE7SSHOBJECT_H - -#include <coreplugin/core_global.h> - -#include <QtCore/QMutex> -#include <QtCore/QSharedPointer> -#include <QtCore/QWeakPointer> - -class ne7ssh; - -namespace Core { -namespace Internal { - -class Ne7SshObject -{ -public: - typedef QSharedPointer<ne7ssh> Ptr; - - static Ne7SshObject *instance(); - static void removeInstance(); - - Ptr get(); - -private: - Ne7SshObject(); - Ne7SshObject(const Ne7SshObject &); - Ne7SshObject &operator=(const Ne7SshObject &); - - static Ne7SshObject *m_instance; - - QWeakPointer<ne7ssh> m_weakRef; - QMutex m_mutex; -}; - -} // namespace Internal -} // namespace Core - -#endif // NE7SSHOBJECT_H diff --git a/src/plugins/coreplugin/ssh/sftpchannel.cpp b/src/plugins/coreplugin/ssh/sftpchannel.cpp new file mode 100644 index 0000000000..2cb31a3bce --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpchannel.cpp @@ -0,0 +1,756 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpchannel.h" +#include "sftpchannel_p.h" + +#include "sshdelayedsignal_p.h" +#include "sshexception_p.h" +#include "sshsendfacility_p.h" + +#include <QtCore/QFile> +#include <QtCore/QWeakPointer> + +namespace Core { + +namespace Internal { +namespace { + const quint32 ProtocolVersion = 3; + + QString errorMessage(const QString &serverMessage, + const QString &alternativeMessage) + { + return serverMessage.isEmpty() ? alternativeMessage : serverMessage; + } + + QString errorMessage(const SftpStatusResponse &response, + const QString &alternativeMessage) + { + return response.status == SSH_FX_OK ? QString() + : errorMessage(response.errorString, alternativeMessage); + } +} // anonymous namespace +} // namespace Internal + +SftpChannel::SftpChannel(quint32 channelId, + Internal::SshSendFacility &sendFacility) + : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this)) +{ +} + +SftpChannel::State SftpChannel::state() const +{ + switch (d->channelState()) { + case Internal::AbstractSshChannel::Inactive: + return Uninitialized; + case Internal::AbstractSshChannel::SessionRequested: + return Initializing; + case Internal::AbstractSshChannel::CloseRequested: + return Closing; + case Internal::AbstractSshChannel::Closed: + return Closed; + case Internal::AbstractSshChannel::SessionEstablished: + return d->m_sftpState == Internal::SftpChannelPrivate::Initialized + ? Initialized : Initializing; + default: + Q_ASSERT(!"Oh no, we forgot to handle a channel state!"); + return Closed; // For the compiler. + } +} + +void SftpChannel::initialize() +{ + d->requestSessionStart(); + d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested; +} + +void SftpChannel::closeChannel() +{ + d->closeChannel(); +} + +SftpJobId SftpChannel::listDirectory(const QString &path) +{ + return d->createJob(Internal::SftpListDir::Ptr( + new Internal::SftpListDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::createDirectory(const QString &path) +{ + return d->createJob(Internal::SftpMakeDir::Ptr( + new Internal::SftpMakeDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::removeDirectory(const QString &path) +{ + return d->createJob(Internal::SftpRmDir::Ptr( + new Internal::SftpRmDir(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::removeFile(const QString &path) +{ + return d->createJob(Internal::SftpRm::Ptr( + new Internal::SftpRm(++d->m_nextJobId, path))); +} + +SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath, + const QString &newPath) +{ + return d->createJob(Internal::SftpRename::Ptr( + new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath))); +} + +SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode) +{ + return d->createJob(Internal::SftpCreateFile::Ptr( + new Internal::SftpCreateFile(++d->m_nextJobId, path, mode))); +} + +SftpJobId SftpChannel::uploadFile(const QString &localFilePath, + const QString &remoteFilePath, SftpOverwriteMode mode) +{ + QSharedPointer<QFile> localFile(new QFile(localFilePath)); + if (!localFile->open(QIODevice::ReadOnly)) + return SftpInvalidJob; + return d->createJob(Internal::SftpUpload::Ptr( + new Internal::SftpUpload(++d->m_nextJobId, remoteFilePath, localFile, mode))); +} + +SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, + const QString &localFilePath, SftpOverwriteMode mode) +{ + QSharedPointer<QFile> localFile(new QFile(localFilePath)); + if (mode == SftpSkipExisting && localFile->exists()) + return SftpInvalidJob; + QIODevice::OpenMode openMode = QIODevice::WriteOnly; + if (mode == SftpOverwriteExisting) + openMode |= QIODevice::Truncate; + else if (mode == SftpAppendToExisting) + openMode |= QIODevice::Append; + if (!localFile->open(openMode)) + return SftpInvalidJob; + return d->createJob(Internal::SftpDownload::Ptr( + new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile))); +} + +SftpChannel::~SftpChannel() +{ + delete d; +} + + +namespace Internal { + +SftpChannelPrivate::SftpChannelPrivate(quint32 channelId, + SshSendFacility &sendFacility, SftpChannel *sftp) + : AbstractSshChannel(channelId, sendFacility), + m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp) +{ +} + +SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job) +{ + if (m_sftp->state() != SftpChannel::Initialized) + return SftpInvalidJob; + m_jobs.insert(job->jobId, job); + sendData(job->initialPacket(m_outgoingPacket).rawData()); + return job->jobId; +} + +void SftpChannelPrivate::handleChannelSuccess() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("sftp subsystem initialized"); +#endif + sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData()); + m_sftpState = InitSent; +} + +void SftpChannelPrivate::handleChannelFailure() +{ + if (m_sftpState != SubsystemRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_FAILURE packet."); + } + createDelayedInitFailedSignal(SSH_TR("Server could not start sftp subsystem.")); + closeChannel(); +} + +void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data) +{ + m_incomingData += data; + m_incomingPacket.consumeData(m_incomingData); + while (m_incomingPacket.isComplete()) { + handleCurrentPacket(); + m_incomingPacket.clear(); + m_incomingPacket.consumeData(m_incomingData); + } +} + +void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data) +{ + qWarning("Unexpected extended data '%s' of type %d on SFTP channel.", + data.data(), type); +} + +void SftpChannelPrivate::handleCurrentPacket() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("Handling SFTP packet of type %d", m_incomingPacket.type()); +#endif + switch (m_incomingPacket.type()) { + case SSH_FXP_VERSION: + handleServerVersion(); + break; + case SSH_FXP_HANDLE: + handleHandle(); + break; + case SSH_FXP_NAME: + handleName(); + break; + case SSH_FXP_STATUS: + handleStatus(); + break; + case SSH_FXP_DATA: + handleReadData(); + break; + case SSH_FXP_ATTRS: + handleAttrs(); + break; + default: + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected packet.", + SSH_TR("Unexpected packet of type %d.").arg(m_incomingPacket.type())); + } +} + +void SftpChannelPrivate::handleServerVersion() +{ + checkChannelActive(); + if (m_sftpState != InitSent) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_VERSION packet."); + } + +#ifdef CREATOR_SSH_DEBUG + qDebug("sftp init received"); +#endif + const quint32 serverVersion = m_incomingPacket.extractServerVersion(); + if (serverVersion != ProtocolVersion) { + createDelayedInitFailedSignal(SSH_TR("Protocol version mismatch: Expected %1, got %2") + .arg(serverVersion).arg(ProtocolVersion)); + closeChannel(); + } else { + m_sftpState = Initialized; + createDelayedInitializedSignal(); + } +} + +void SftpChannelPrivate::handleHandle() +{ + const SftpHandleResponse &response = m_incomingPacket.asHandleResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + const QSharedPointer<AbstractSftpOperationWithHandle> job + = it.value().dynamicCast<AbstractSftpOperationWithHandle>(); + if (job.isNull()) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_HANDLE packet."); + } + if (job->state != AbstractSftpOperationWithHandle::OpenRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_HANDLE packet."); + } + job->remoteHandle = response.handle; + job->state = AbstractSftpOperationWithHandle::Open; + + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: + handleLsHandle(it); + break; + case AbstractSftpOperation::CreateFile: + handleCreateFileHandle(it); + break; + case AbstractSftpOperation::Download: + handleGetHandle(it); + break; + case AbstractSftpOperation::Upload: + handlePutHandle(it); + break; + default: + Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!"); + } +} + +void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it) +{ + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + sendData(m_outgoingPacket.generateReadDir(op->remoteHandle, + op->jobId).rawData()); +} + +void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it) +{ + SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>(); + sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle, + op->jobId).rawData()); +} + +void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it) +{ + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + sendData(m_outgoingPacket.generateFstat(op->remoteHandle, + op->jobId).rawData()); + op->statRequested = true; +} + +void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it) +{ + SftpUpload::Ptr op = it.value().staticCast<SftpUpload>(); + + // OpenSSH does not implement the RFC's append functionality, so we + // have to emulate it. + if (op->mode == SftpAppendToExisting) { + sendData(m_outgoingPacket.generateFstat(op->remoteHandle, + op->jobId).rawData()); + op->statRequested = true; + } else { + spawnWriteRequests(it); + } +} + +void SftpChannelPrivate::handleStatus() +{ + const SftpStatusResponse &response = m_incomingPacket.asStatusResponse(); +#ifdef CREATOR_SSH_DEBUG + qDebug("%s: status = %d", Q_FUNC_INFO, response.status); +#endif + JobMap::Iterator it = lookupJob(response.requestId); + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: + handleLsStatus(it, response); + break; + case AbstractSftpOperation::Download: + handleGetStatus(it, response); + break; + case AbstractSftpOperation::Upload: + handlePutStatus(it, response); + break; + case AbstractSftpOperation::MakeDir: + case AbstractSftpOperation::RmDir: + case AbstractSftpOperation::Rm: + case AbstractSftpOperation::Rename: + case AbstractSftpOperation::CreateFile: + handleStatusGeneric(it, response); + break; + } +} + +void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + AbstractSftpOperation::Ptr op = it.value(); + const QString error = errorMessage(response, SSH_TR("Unknown error.")); + createDelayedJobFinishedSignal(op->jobId, error); + m_jobs.erase(it); +} + +void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + switch (op->state) { + case SftpListDir::OpenRequested: + createDelayedJobFinishedSignal(op->jobId, errorMessage(response.errorString, + SSH_TR("Remote directory could not be opened for reading."))); + m_jobs.erase(it); + break; + case SftpListDir::Open: + if (response.status != SSH_FX_EOF) + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to list remote directory contents."))); + op->state = SftpListDir::CloseRequested; + sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle, + op->jobId).rawData()); + break; + case SftpListDir::CloseRequested: + if (!op->hasError) { + const QString error = errorMessage(response, + SSH_TR("Failed to close remote directory.")); + createDelayedJobFinishedSignal(op->jobId, error); + } + m_jobs.erase(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + switch (op->state) { + case SftpDownload::OpenRequested: + createDelayedJobFinishedSignal(op->jobId, + errorMessage(response.errorString, + SSH_TR("Failed to open remote file for reading."))); + m_jobs.erase(it); + break; + case SftpDownload::Open: + if (op->statRequested) { + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to stat remote file."))); + sendTransferCloseHandle(op, response.requestId); + } else { + if ((response.status != SSH_FX_EOF || response.requestId != op->eofId) + && !op->hasError) + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to read remote file."))); + finishTransferRequest(it); + } + break; + case SftpDownload::CloseRequested: + Q_ASSERT(op->inFlightCount == 1); + if (!op->hasError) { + if (response.status == SSH_FX_OK) + createDelayedJobFinishedSignal(op->jobId); + else + reportRequestError(op, errorMessage(response.errorString, + SSH_TR("Failed to close remote file."))); + } + removeTransferRequest(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpUpload::Ptr job = it.value().staticCast<SftpUpload>(); + switch (job->state) { + case SftpUpload::OpenRequested: + createDelayedJobFinishedSignal(job->jobId, + errorMessage(response.errorString, + SSH_TR("Failed to open remote file for writing."))); + m_jobs.erase(it); + break; + case SftpUpload::Open: + if (response.status == SSH_FX_OK) { + sendWriteRequest(it); + } else if(!job->hasError) { + reportRequestError(job, errorMessage(response.errorString, + SSH_TR("Failed to write remote file."))); + finishTransferRequest(it); + } + break; + case SftpUpload::CloseRequested: + Q_ASSERT(job->inFlightCount == 1); + if (!job->hasError) { + const QString error = errorMessage(response, + SSH_TR("Failed to close remote file.")); + createDelayedJobFinishedSignal(job->jobId, error); + } + m_jobs.erase(it); + break; + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_STATUS packet."); + } +} + +void SftpChannelPrivate::handleName() +{ + const SftpNameResponse &response = m_incomingPacket.asNameResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + switch (it.value()->type()) { + case AbstractSftpOperation::ListDir: { + SftpListDir::Ptr op = it.value().staticCast<SftpListDir>(); + if (op->state != SftpListDir::Open) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_NAME packet."); + } + + for (int i = 0; i < response.files.count(); ++i) { + const SftpFile &file = response.files.at(i); + createDelayedDataAvailableSignal(op->jobId, file.fileName); + } + sendData(m_outgoingPacket.generateReadDir(op->remoteHandle, + op->jobId).rawData()); + break; + } + default: + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_NAME packet."); + } +} + +void SftpChannelPrivate::handleReadData() +{ + const SftpDataResponse &response = m_incomingPacket.asDataResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + if (it.value()->type() != AbstractSftpOperation::Download) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_DATA packet."); + } + + SftpDownload::Ptr op = it.value().staticCast<SftpDownload>(); + if (op->hasError) { + finishTransferRequest(it); + return; + } + + if (!op->localFile->seek(op->offsets[response.requestId])) { + reportRequestError(op, op->localFile->errorString()); + finishTransferRequest(it); + return; + } + + if (op->localFile->write(response.data) != response.data.size()) { + reportRequestError(op, op->localFile->errorString()); + finishTransferRequest(it); + return; + } + + if (op->offset >= op->fileSize && op->fileSize != 0) + finishTransferRequest(it); + else + sendReadRequest(op, response.requestId); +} + +void SftpChannelPrivate::handleAttrs() +{ + const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse(); + JobMap::Iterator it = lookupJob(response.requestId); + AbstractSftpTransfer::Ptr transfer + = it.value().dynamicCast<AbstractSftpTransfer>(); + if (!transfer || transfer->state != AbstractSftpTransfer::Open + || !transfer->statRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_FXP_ATTRS packet."); + } + Q_ASSERT(transfer->type() == AbstractSftpOperation::Upload + || transfer->type() == AbstractSftpOperation::Download); + + if (transfer->type() == AbstractSftpOperation::Download) { + SftpDownload::Ptr op = transfer.staticCast<SftpDownload>(); + if (response.attrs.sizePresent) { + op->fileSize = response.attrs.size; + } else { + op->fileSize = 0; + op->eofId = op->jobId; + } + op->statRequested = false; + spawnReadRequests(op); + } else { + SftpUpload::Ptr op = transfer.staticCast<SftpUpload>(); + if (response.attrs.sizePresent) { + op->offset = response.attrs.size; + spawnWriteRequests(it); + } else { + reportRequestError(op, SSH_TR("Cannot append to remote file: " + "Server does not support file size attribute.")); + sendTransferCloseHandle(op, op->jobId); + } + } +} + +SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id) +{ + JobMap::Iterator it = m_jobs.find(id); + if (it == m_jobs.end()) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid request id in SFTP packet."); + } + return it; +} + +void SftpChannelPrivate::closeHook() +{ + createClosedSignal(); +} + +void SftpChannelPrivate::handleOpenSuccessInternal() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("SFTP session started"); +#endif + m_sendFacility.sendSftpPacket(remoteChannel()); + m_sftpState = SubsystemRequested; +} + +void SftpChannelPrivate::handleOpenFailureInternal() +{ + if (channelState() != SessionRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet."); + } + createDelayedInitFailedSignal(SSH_TR("Server could not start session.")); +} + +void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job, + quint32 requestId) +{ + Q_ASSERT(job->eofId == SftpInvalidJob); + sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset, + AbstractSftpPacket::MaxDataSize, requestId).rawData()); + job->offsets[requestId] = job->offset; + job->offset += AbstractSftpPacket::MaxDataSize; + if (job->offset >= job->fileSize) + job->eofId = requestId; +} + +void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job, + const QString &error) +{ + createDelayedJobFinishedSignal(job->jobId, error); + job->hasError = true; +} + +void SftpChannelPrivate::finishTransferRequest(const JobMap::Iterator &it) +{ + AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>(); + if (job->inFlightCount == 1) + sendTransferCloseHandle(job, it.key()); + else + removeTransferRequest(it); +} + +void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job, + quint32 requestId) +{ + sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle, + requestId).rawData()); + job->state = SftpDownload::CloseRequested; +} + +void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it) +{ + --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount; + m_jobs.erase(it); +} + +void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it) +{ + SftpUpload::Ptr job = it.value().staticCast<SftpUpload>(); + QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize); + if (job->localFile->error() != QFile::NoError) { + if (!job->hasError) { + reportRequestError(job, SSH_TR("Error reading local file: %1") + .arg(job->localFile->errorString())); + } + finishTransferRequest(it); + } else if (data.isEmpty()) { + finishTransferRequest(it); + } else { + sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle, + job->offset, data, it.key()).rawData()); + job->offset += AbstractSftpPacket::MaxDataSize; + } +} + +void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it) +{ + SftpUpload::Ptr op = it.value().staticCast<SftpUpload>(); + op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize); + sendWriteRequest(it); + for (int i = 1; i < op->inFlightCount; ++i) + sendWriteRequest(m_jobs.insert(++m_nextJobId, op)); +} + +void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job) +{ + job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize); + sendReadRequest(job, job->jobId); + for (int i = 1; i < job->inFlightCount; ++i) { + const quint32 requestId = ++m_nextJobId; + m_jobs.insert(requestId, job); + sendReadRequest(job, requestId); + } +} + +void SftpChannelPrivate::createDelayedInitFailedSignal(const QString &reason) +{ + new SftpInitializationFailedSignal(this, QWeakPointer<SftpChannel>(m_sftp), + reason); +} + +void SftpChannelPrivate::emitInitializationFailedSignal(const QString &reason) +{ + emit m_sftp->initializationFailed(reason); +} + +void SftpChannelPrivate::createDelayedInitializedSignal() +{ + new SftpInitializedSignal(this, QWeakPointer<SftpChannel>(m_sftp)); +} + +void SftpChannelPrivate::emitInitialized() +{ + emit m_sftp->initialized(); +} + +void SftpChannelPrivate::createDelayedJobFinishedSignal(SftpJobId jobId, + const QString &error) +{ + new SftpJobFinishedSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, error); +} + +void SftpChannelPrivate::emitJobFinished(SftpJobId jobId, const QString &error) +{ + emit m_sftp->finished(jobId, error); +} + +void SftpChannelPrivate::createDelayedDataAvailableSignal(SftpJobId jobId, + const QString &data) +{ + new SftpDataAvailableSignal(this, QWeakPointer<SftpChannel>(m_sftp), jobId, data); +} + +void SftpChannelPrivate::emitDataAvailable(SftpJobId jobId, const QString &data) +{ + emit m_sftp->dataAvailable(jobId, data); +} + +void SftpChannelPrivate::createClosedSignal() +{ + new SftpClosedSignal(this, QWeakPointer<SftpChannel>(m_sftp)); +} + +void SftpChannelPrivate::emitClosed() +{ + emit m_sftp->closed(); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftpchannel.h b/src/plugins/coreplugin/ssh/sftpchannel.h new file mode 100644 index 0000000000..6b4ffea7e5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpchannel.h @@ -0,0 +1,120 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTCHANNEL_H +#define SFTCHANNEL_H + +#include "sftpdefs.h" +#include "sftpincomingpacket_p.h" + +#include <coreplugin/core_global.h> + +#include <QtCore/QByteArray> +#include <QtCore/QObject> +#include <QtCore/QSharedPointer> +#include <QtCore/QString> + +namespace Core { + +namespace Internal { +class SftpChannelPrivate; +class SshChannelManager; +class SshSendFacility; +} // namespace Internal + +/* + * This class provides SFTP operations. + * Objects are created via SshConnection::createSftpChannel(). + * The channel needs to be initialized with + * a call to initialize() and is closed via closeChannel(). After closing + * a channel, no more operations are possible. It cannot be re-opened + * using initialize(); use SshConnection::createSftpChannel() if you need + * a new one. + * After the initialized() signal has been emitted, operations can be started. + * All SFTP operations are asynchronous (non-blocking) and can be in-flight + * simultaneously (though callers must ensure that concurrently running jobs + * are independent of each other, e.g. they must not write to the same file). + * Operations are identified by their job id, which is returned by + * the respective member function. If the function can right away detect that + * the operation cannot succeed, it returns SftpInvalidJob. If an error occurs + * later, the finishedWithError() signal is emitted for the respective job. + * Note that directory names must not have a trailing slash. + */ +class CORE_EXPORT SftpChannel : public QObject +{ + Q_OBJECT + + friend class Internal::SftpChannelPrivate; + friend class Internal::SshChannelManager; +public: + typedef QSharedPointer<SftpChannel> Ptr; + + enum State { Uninitialized, Initializing, Initialized, Closing, Closed }; + State state() const; + + void initialize(); + void closeChannel(); + + SftpJobId listDirectory(const QString &dirPath); + SftpJobId createDirectory(const QString &dirPath); + SftpJobId removeDirectory(const QString &dirPath); + SftpJobId removeFile(const QString &filePath); + SftpJobId renameFileOrDirectory(const QString &oldPath, + const QString &newPath); + SftpJobId createFile(const QString &filePath, SftpOverwriteMode mode); + SftpJobId uploadFile(const QString &localFilePath, + const QString &remoteFilePath, SftpOverwriteMode mode); + SftpJobId downloadFile(const QString &remoteFilePath, + const QString &localFilePath, SftpOverwriteMode mode); + + ~SftpChannel(); + +signals: + void initialized(); + void initializationFailed(const QString &reason); + void closed(); + + // error.isEmpty <=> finished successfully + void finished(Core::SftpJobId job, const QString &error = QString()); + + /* + * This signal is only emitted by the "List Directory" operation, + * one file at a time. + */ + void dataAvailable(SftpJobId job, const QString &data); + +private: + SftpChannel(quint32 channelId, Internal::SshSendFacility &sendFacility); + + Internal::SftpChannelPrivate *d; +}; + +} // namespace Core + +#endif // SFTPCHANNEL_H diff --git a/src/plugins/coreplugin/ssh/sftpchannel_p.h b/src/plugins/coreplugin/ssh/sftpchannel_p.h new file mode 100644 index 0000000000..8d27a2c01d --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpchannel_p.h @@ -0,0 +1,130 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTCHANNEL_P_H +#define SFTCHANNEL_P_H + +#include "sftpdefs.h" +#include "sftpincomingpacket_p.h" +#include "sftpoperation_p.h" +#include "sftpoutgoingpacket_p.h" +#include "sshchannel_p.h" + +#include <QtCore/QByteArray> +#include <QtCore/QMap> + +namespace Core { +class SftpChannel; +namespace Internal { + +class SftpChannelPrivate : public AbstractSshChannel +{ + friend class Core::SftpChannel; +public: + + enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized }; + + virtual void handleChannelSuccess(); + virtual void handleChannelFailure(); + + virtual void closeHook(); + + void emitInitializationFailedSignal(const QString &reason); + void emitInitialized(); + void emitJobFinished(SftpJobId jobId, const QString &error); + void emitDataAvailable(SftpJobId jobId, const QString &data); + void emitClosed(); + +private: + typedef QMap<SftpJobId, AbstractSftpOperation::Ptr> JobMap; + + SftpChannelPrivate(quint32 channelId, SshSendFacility &sendFacility, + SftpChannel *sftp); + SftpJobId createJob(const AbstractSftpOperation::Ptr &job); + + virtual void handleOpenSuccessInternal(); + virtual void handleOpenFailureInternal(); + virtual void handleChannelDataInternal(const QByteArray &data); + virtual void handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data); + + void handleCurrentPacket(); + void handleServerVersion(); + void handleHandle(); + void handleStatus(); + void handleName(); + void handleReadData(); + void handleAttrs(); + + void handleStatusGeneric(const JobMap::Iterator &it, + const SftpStatusResponse &response); + void handleLsStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response); + void handleGetStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response); + void handlePutStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response); + + void handleLsHandle(const JobMap::Iterator &it); + void handleCreateFileHandle(const JobMap::Iterator &it); + void handleGetHandle(const JobMap::Iterator &it); + void handlePutHandle(const JobMap::Iterator &it); + + void spawnReadRequests(const SftpDownload::Ptr &job); + void spawnWriteRequests(const JobMap::Iterator &it); + void sendReadRequest(const SftpDownload::Ptr &job, quint32 requestId); + void sendWriteRequest(const JobMap::Iterator &it); + void finishTransferRequest(const JobMap::Iterator &it); + void removeTransferRequest(const JobMap::Iterator &it); + void reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job, + const QString &error); + void sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job, + quint32 requestId); + + void createDelayedInitFailedSignal(const QString &reason); + void createDelayedInitializedSignal(); + void createDelayedJobFinishedSignal(SftpJobId jobId, + const QString &error = QString()); + void createDelayedDataAvailableSignal(SftpJobId jobId, const QString &data); + void createClosedSignal(); + + JobMap::Iterator lookupJob(SftpJobId id); + JobMap m_jobs; + SftpOutgoingPacket m_outgoingPacket; + SftpIncomingPacket m_incomingPacket; + QByteArray m_incomingData; + SftpJobId m_nextJobId; + SftpState m_sftpState; + SftpChannel *m_sftp; +}; + +} // namespace Internal +} // namespace Core + +#endif // SFTPCHANNEL_P_H diff --git a/src/plugins/coreplugin/ssh/sftpdefs.cpp b/src/plugins/coreplugin/ssh/sftpdefs.cpp new file mode 100644 index 0000000000..6a2f6de45e --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpdefs.cpp @@ -0,0 +1,32 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpdefs.h" + +namespace Core { const SftpJobId SftpInvalidJob = 0; } diff --git a/src/plugins/coreplugin/ssh/sftpdefs.h b/src/plugins/coreplugin/ssh/sftpdefs.h new file mode 100644 index 0000000000..5f59582a05 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpdefs.h @@ -0,0 +1,48 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTPDEFS_H +#define SFTPDEFS_H + +#include <coreplugin/core_global.h> + +#include <QtCore/QtGlobal> + +namespace Core { + +typedef quint32 SftpJobId; +CORE_EXPORT extern const SftpJobId SftpInvalidJob; + +enum SftpOverwriteMode { + SftpOverwriteExisting, SftpAppendToExisting, SftpSkipExisting +}; + +} // namespace Core + +#endif // SFTPDEFS_H diff --git a/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp b/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp new file mode 100644 index 0000000000..804bdb21d5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpincomingpacket.cpp @@ -0,0 +1,230 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpincomingpacket_p.h" + +#include "sshexception_p.h" +#include "sshpacketparser_p.h" + +namespace Core { +namespace Internal { + +namespace { + const int SSH_FILEXFER_ATTR_SIZE = 0x00000001; + const int SSH_FILEXFER_ATTR_UIDGID = 0x00000002; + const int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004; + const int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; + const int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000; +} // anonymous namespace + +SftpIncomingPacket::SftpIncomingPacket() : m_length(0) +{ +} + +void SftpIncomingPacket::consumeData(QByteArray &newData) +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("%s: current data size = %d, new data size = %d", Q_FUNC_INFO, + m_data.size(), newData.size()); +#endif + + if (isComplete() || dataSize() + newData.size() < sizeof m_length) + return; + + if (dataSize() < sizeof m_length) { + moveFirstBytes(m_data, newData, sizeof m_length - m_data.size()); + m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0)); + if (m_length < static_cast<quint32>(TypeOffset + 1) + || m_length > MaxPacketSize) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid length field in SFTP packet."); + } + } + + moveFirstBytes(m_data, newData, + qMin<quint32>(m_length - dataSize() + 4, newData.size())); +} + +void SftpIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source, + int n) +{ + target.append(source.left(n)); + source.remove(0, n); +} + +bool SftpIncomingPacket::isComplete() const +{ + return m_length == dataSize() - 4; +} + +void SftpIncomingPacket::clear() +{ + m_data.clear(); + m_length = 0; +} + +quint32 SftpIncomingPacket::extractServerVersion() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_VERSION); + try { + return SshPacketParser::asUint32(m_data, TypeOffset + 1); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_VERSION packet."); + } +} + +SftpHandleResponse SftpIncomingPacket::asHandleResponse() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_HANDLE); + try { + SftpHandleResponse response; + quint32 offset = RequestIdOffset; + response.requestId = SshPacketParser::asUint32(m_data, &offset); + response.handle = SshPacketParser::asString(m_data, &offset); + return response; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_HANDLE packet"); + } +} + +SftpStatusResponse SftpIncomingPacket::asStatusResponse() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_STATUS); + try { + SftpStatusResponse response; + quint32 offset = RequestIdOffset; + response.requestId = SshPacketParser::asUint32(m_data, &offset); + response.status = static_cast<SftpStatusCode>(SshPacketParser::asUint32(m_data, &offset)); + response.errorString = SshPacketParser::asUserString(m_data, &offset); + response.language = SshPacketParser::asString(m_data, &offset); + return response; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_STATUS packet."); + } +} + +SftpNameResponse SftpIncomingPacket::asNameResponse() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_NAME); + try { + SftpNameResponse response; + quint32 offset = RequestIdOffset; + response.requestId = SshPacketParser::asUint32(m_data, &offset); + const quint32 count = SshPacketParser::asUint32(m_data, &offset); + for (quint32 i = 0; i < count; ++i) + response.files << asFile(offset); + return response; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_NAME packet."); + } +} + +SftpDataResponse SftpIncomingPacket::asDataResponse() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_DATA); + try { + SftpDataResponse response; + quint32 offset = RequestIdOffset; + response.requestId = SshPacketParser::asUint32(m_data, &offset); + response.data = SshPacketParser::asString(m_data, &offset); + return response; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_DATA packet."); + } +} + +SftpAttrsResponse SftpIncomingPacket::asAttrsResponse() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_FXP_ATTRS); + try { + SftpAttrsResponse response; + quint32 offset = RequestIdOffset; + response.requestId = SshPacketParser::asUint32(m_data, &offset); + response.attrs = asFileAttributes(offset); + return response; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_FXP_ATTRS packet."); + } +} + +SftpFile SftpIncomingPacket::asFile(quint32 &offset) const +{ + SftpFile file; + file.fileName + = QString::fromUtf8(SshPacketParser::asString(m_data, &offset)); + file.longName + = QString::fromUtf8(SshPacketParser::asString(m_data, &offset)); + file.attributes = asFileAttributes(offset); + return file; +} + +SftpFileAttributes SftpIncomingPacket::asFileAttributes(quint32 &offset) const +{ + SftpFileAttributes attributes; + const quint32 flags = SshPacketParser::asUint32(m_data, &offset); + attributes.sizePresent = flags & SSH_FILEXFER_ATTR_SIZE; + attributes.timesPresent = flags & SSH_FILEXFER_ATTR_ACMODTIME; + attributes.uidAndGidPresent = flags & SSH_FILEXFER_ATTR_UIDGID; + attributes.permissionsPresent = flags & SSH_FILEXFER_ATTR_PERMISSIONS; + if (attributes.sizePresent) + attributes.size = SshPacketParser::asUint64(m_data, &offset); + if (attributes.uidAndGidPresent) { + attributes.uid = SshPacketParser::asUint32(m_data, &offset); + attributes.gid = SshPacketParser::asUint32(m_data, &offset); + } + if (attributes.permissionsPresent) + attributes.permissions = SshPacketParser::asUint32(m_data, &offset); + if (attributes.timesPresent) { + attributes.atime = SshPacketParser::asUint32(m_data, &offset); + attributes.mtime = SshPacketParser::asUint32(m_data, &offset); + } + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + const quint32 count = SshPacketParser::asUint32(m_data, &offset); + for (quint32 i = 0; i < count; ++i) { + SshPacketParser::asString(m_data, &offset); + SshPacketParser::asString(m_data, &offset); + } + } + return attributes; +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h b/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h new file mode 100644 index 0000000000..5a5b8d42fe --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpincomingpacket_p.h @@ -0,0 +1,111 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTPINCOMINGPACKET_P_H +#define SFTPINCOMINGPACKET_P_H + +#include "sftppacket_p.h" + +namespace Core { +namespace Internal { + +struct SftpHandleResponse { + quint32 requestId; + QByteArray handle; +}; + +struct SftpStatusResponse { + quint32 requestId; + SftpStatusCode status; + QString errorString; + QByteArray language; +}; + +struct SftpFileAttributes { + bool sizePresent; + bool timesPresent; + bool uidAndGidPresent; + bool permissionsPresent; + quint64 size; + quint32 uid; + quint32 gid; + quint32 permissions; + quint32 atime; + quint32 mtime; +}; + +struct SftpFile { + QString fileName; + QString longName; // Not present in later RFCs, so we don't expose this to the user. + SftpFileAttributes attributes; +}; + +struct SftpNameResponse { + quint32 requestId; + QList<SftpFile> files; +}; + +struct SftpDataResponse { + quint32 requestId; + QByteArray data; +}; + +struct SftpAttrsResponse { + quint32 requestId; + SftpFileAttributes attrs; +}; + +class SftpIncomingPacket : public AbstractSftpPacket +{ +public: + SftpIncomingPacket(); + + void consumeData(QByteArray &data); + void clear(); + bool isComplete() const; + quint32 extractServerVersion() const; + SftpHandleResponse asHandleResponse() const; + SftpStatusResponse asStatusResponse() const; + SftpNameResponse asNameResponse() const; + SftpDataResponse asDataResponse() const; + SftpAttrsResponse asAttrsResponse() const; + +private: + void moveFirstBytes(QByteArray &target, QByteArray &source, int n); + + SftpFileAttributes asFileAttributes(quint32 &offset) const; + SftpFile asFile(quint32 &offset) const; + + quint32 m_length; +}; + +} // namespace Internal +} // namespace Core + +#endif // SFTPINCOMINGPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sftpoperation.cpp b/src/plugins/coreplugin/ssh/sftpoperation.cpp new file mode 100644 index 0000000000..8acb126db1 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpoperation.cpp @@ -0,0 +1,176 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpoperation_p.h" + +#include "sftpoutgoingpacket_p.h" + +#include <QtCore/QTime> +#include <QtCore/QFile> + +namespace Core { +namespace Internal { + +AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId) +{ +} + +AbstractSftpOperation::~AbstractSftpOperation() { } + + +SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path) + : AbstractSftpOperation(jobId), remoteDir(path) +{ +} + +SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet) +{ + return packet.generateMkDir(remoteDir, jobId); +} + + +SftpRmDir::SftpRmDir(SftpJobId, const QString &path) + : AbstractSftpOperation(jobId), remoteDir(path) +{ +} + +SftpOutgoingPacket &SftpRmDir::initialPacket(SftpOutgoingPacket &packet) +{ + return packet.generateRmDir(remoteDir, jobId); +} + + +SftpRm::SftpRm(SftpJobId jobId, const QString &path) + : AbstractSftpOperation(jobId), remoteFile(path) {} + +SftpOutgoingPacket &SftpRm::initialPacket(SftpOutgoingPacket &packet) +{ + return packet.generateRm(remoteFile, jobId); +} + + +SftpRename::SftpRename(SftpJobId jobId, const QString &oldPath, + const QString &newPath) + : AbstractSftpOperation(jobId), oldPath(oldPath), newPath(newPath) +{ +} + +SftpOutgoingPacket &SftpRename::initialPacket(SftpOutgoingPacket &packet) +{ + return packet.generateRename(oldPath, newPath, jobId); +} + + +AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId, + const QString &remotePath) + : AbstractSftpOperation(jobId), + remotePath(remotePath), state(Inactive), hasError(false) +{ +} + +AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { } + + +SftpListDir::SftpListDir(SftpJobId jobId, const QString &path) + : AbstractSftpOperationWithHandle(jobId, path) +{ +} + +SftpOutgoingPacket &SftpListDir::initialPacket(SftpOutgoingPacket &packet) +{ + state = OpenRequested; + return packet.generateOpenDir(remotePath, jobId); +} + + +SftpCreateFile::SftpCreateFile(SftpJobId jobId, const QString &path, + SftpOverwriteMode mode) + : AbstractSftpOperationWithHandle(jobId, path), mode(mode) +{ +} + +SftpOutgoingPacket & SftpCreateFile::initialPacket(SftpOutgoingPacket &packet) +{ + state = OpenRequested; + return packet.generateOpenFileForWriting(remotePath, mode, jobId); +} + + +const int AbstractSftpTransfer::MaxInFlightCount = 10; // Experimentally found to be enough. + +AbstractSftpTransfer::AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile) + : AbstractSftpOperationWithHandle(jobId, remotePath), + localFile(localFile), fileSize(0), offset(0), inFlightCount(0), + statRequested(false) +{ +} + +void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize) +{ + if (fileSize == 0) { + inFlightCount = 1; + } else { + inFlightCount = fileSize / chunkSize; + if (fileSize % chunkSize) + ++inFlightCount; + if (inFlightCount > MaxInFlightCount) + inFlightCount = MaxInFlightCount; + } +} + + +SftpDownload::SftpDownload(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile) + : AbstractSftpTransfer(jobId, remotePath, localFile), eofId(SftpInvalidJob) +{ +} + +SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet) +{ + state = OpenRequested; + return packet.generateOpenFileForReading(remotePath, jobId); +} + + +SftpUpload::SftpUpload(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode) + : AbstractSftpTransfer(jobId, remotePath, localFile), mode(mode) +{ + fileSize = localFile->size(); +} + +SftpOutgoingPacket &SftpUpload::initialPacket(SftpOutgoingPacket &packet) +{ + state = OpenRequested; + return packet.generateOpenFileForWriting(remotePath, mode, jobId); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftpoperation_p.h b/src/plugins/coreplugin/ssh/sftpoperation_p.h new file mode 100644 index 0000000000..6598781332 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpoperation_p.h @@ -0,0 +1,193 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTPOPERATION_P_H +#define SFTPOPERATION_P_H + +#include "sftpdefs.h" + +#include <QtCore/QByteArray> +#include <QtCore/QMap> +#include <QtCore/QSharedPointer> + +QT_BEGIN_NAMESPACE +class QFile; +QT_END_NAMESPACE + +namespace Core { +namespace Internal { + +class SftpOutgoingPacket; + +struct AbstractSftpOperation +{ + typedef QSharedPointer<AbstractSftpOperation> Ptr; + enum Type { + ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, Upload + }; + + AbstractSftpOperation(SftpJobId jobId); + virtual ~AbstractSftpOperation(); + virtual Type type() const=0; + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet)=0; + + const SftpJobId jobId; + +private: + AbstractSftpOperation(const AbstractSftpOperation &); + AbstractSftpOperation &operator=(const AbstractSftpOperation &); +}; + +struct SftpMakeDir : public AbstractSftpOperation +{ + typedef QSharedPointer<SftpMakeDir> Ptr; + + SftpMakeDir(SftpJobId jobId, const QString &path); + virtual Type type() const { return MakeDir; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + const QString remoteDir; +}; + +struct SftpRmDir : public AbstractSftpOperation +{ + typedef QSharedPointer<SftpRmDir> Ptr; + + SftpRmDir(SftpJobId jobId, const QString &path); + virtual Type type() const { return RmDir; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + const QString remoteDir; +}; + +struct SftpRm : public AbstractSftpOperation +{ + typedef QSharedPointer<SftpRm> Ptr; + + SftpRm(SftpJobId jobId, const QString &path); + virtual Type type() const { return Rm; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + const QString remoteFile; +}; + +struct SftpRename : public AbstractSftpOperation +{ + typedef QSharedPointer<SftpRename> Ptr; + + SftpRename(SftpJobId jobId, const QString &oldPath, const QString &newPath); + virtual Type type() const { return Rename; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + const QString oldPath; + const QString newPath; +}; + + +struct AbstractSftpOperationWithHandle : public AbstractSftpOperation +{ + typedef QSharedPointer<AbstractSftpOperationWithHandle> Ptr; + enum State { Inactive, OpenRequested, Open, CloseRequested }; + + AbstractSftpOperationWithHandle(SftpJobId jobId, const QString &remotePath); + ~AbstractSftpOperationWithHandle(); + + const QString remotePath; + QByteArray remoteHandle; + State state; + bool hasError; +}; + + +struct SftpListDir : public AbstractSftpOperationWithHandle +{ + typedef QSharedPointer<SftpListDir> Ptr; + + SftpListDir(SftpJobId jobId, const QString &path); + virtual Type type() const { return ListDir; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); +}; + + +struct SftpCreateFile : public AbstractSftpOperationWithHandle +{ + typedef QSharedPointer<SftpCreateFile> Ptr; + + SftpCreateFile(SftpJobId jobId, const QString &path, SftpOverwriteMode mode); + virtual Type type() const { return CreateFile; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + const SftpOverwriteMode mode; +}; + +struct AbstractSftpTransfer : public AbstractSftpOperationWithHandle +{ + typedef QSharedPointer<AbstractSftpTransfer> Ptr; + + AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile); + void calculateInFlightCount(quint32 chunkSize); + + static const int MaxInFlightCount; + + const QSharedPointer<QFile> localFile; + quint64 fileSize; + quint64 offset; + int inFlightCount; + bool statRequested; +}; + +struct SftpDownload : public AbstractSftpTransfer +{ + typedef QSharedPointer<SftpDownload> Ptr; + SftpDownload(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile); + virtual Type type() const { return Download; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + QMap<quint32, quint32> offsets; + SftpJobId eofId; +}; + +struct SftpUpload : public AbstractSftpTransfer +{ + typedef QSharedPointer<SftpUpload> Ptr; + + SftpUpload(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode); + virtual Type type() const { return Upload; } + virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + + SftpOverwriteMode mode; +}; + +} // namespace Internal +} // namespace Core + +#endif // SFTPOPERATION_P_H diff --git a/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp b/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp new file mode 100644 index 0000000000..57fd85467e --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpoutgoingpacket.cpp @@ -0,0 +1,202 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftpoutgoingpacket_p.h" + +#include "sshpacket_p.h" + +#include <QtCore/QtEndian> + +namespace Core { +namespace Internal { + +namespace { + const quint32 DefaultAttributes = 0; + const quint32 SSH_FXF_READ = 0x00000001; + const quint32 SSH_FXF_WRITE = 0x00000002; + const quint32 SSH_FXF_APPEND = 0x00000004; + const quint32 SSH_FXF_CREAT = 0x00000008; + const quint32 SSH_FXF_TRUNC = 0x00000010; + const quint32 SSH_FXF_EXCL = 0x00000020; +} + +SftpOutgoingPacket::SftpOutgoingPacket() +{ +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateInit(quint32 version) +{ + return init(SSH_FXP_INIT, 0).appendInt(version).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateOpenDir(const QString &path, + quint32 requestId) +{ + return init(SSH_FXP_OPENDIR, requestId).appendString(path).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateReadDir(const QByteArray &handle, + quint32 requestId) +{ + return init(SSH_FXP_READDIR, requestId).appendString(handle).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateCloseHandle(const QByteArray &handle, + quint32 requestId) +{ + return init(SSH_FXP_CLOSE, requestId).appendString(handle).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateMkDir(const QString &path, + quint32 requestId) +{ + return init(SSH_FXP_MKDIR, requestId).appendString(path) + .appendInt(DefaultAttributes).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateRmDir(const QString &path, + quint32 requestId) +{ + return init(SSH_FXP_RMDIR, requestId).appendString(path).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateRm(const QString &path, + quint32 requestId) +{ + return init(SSH_FXP_REMOVE, requestId).appendString(path).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateRename(const QString &oldPath, + const QString &newPath, quint32 requestId) +{ + return init(SSH_FXP_RENAME, requestId).appendString(oldPath) + .appendString(newPath).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForWriting(const QString &path, + SftpOverwriteMode mode, quint32 requestId) +{ + return generateOpenFile(path, Write, mode, requestId); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path, + quint32 requestId) +{ + // Note: Overwrite mode is irrelevant and will be ignored. + return generateOpenFile(path, Read, SftpSkipExisting, requestId); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateReadFile(const QByteArray &handle, + quint64 offset, quint32 length, quint32 requestId) +{ + return init(SSH_FXP_READ, requestId).appendString(handle).appendInt64(offset) + .appendInt(length).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateFstat(const QByteArray &handle, + quint32 requestId) +{ + return init(SSH_FXP_FSTAT, requestId).appendString(handle).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateWriteFile(const QByteArray &handle, + quint64 offset, const QByteArray &data, quint32 requestId) +{ + return init(SSH_FXP_WRITE, requestId).appendString(handle) + .appendInt64(offset).appendString(data).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFile(const QString &path, + OpenType openType, SftpOverwriteMode mode, quint32 requestId) +{ + quint32 pFlags; + switch (openType) { + case Read: + pFlags = SSH_FXF_READ; + break; + case Write: + pFlags = SSH_FXF_WRITE | SSH_FXF_CREAT; + switch (mode) { + case SftpOverwriteExisting: pFlags |= SSH_FXF_TRUNC; break; + case SftpAppendToExisting: pFlags |= SSH_FXF_APPEND; break; + case SftpSkipExisting: pFlags |= SSH_FXF_EXCL; break; + } + break; + } + return init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags) + .appendInt(DefaultAttributes).finalize(); +} + +SftpOutgoingPacket &SftpOutgoingPacket::init(SftpPacketType type, + quint32 requestId) +{ + m_data.resize(TypeOffset + 1); + m_data[TypeOffset] = type; + if (type != SSH_FXP_INIT) { + appendInt(requestId); +#ifdef CREATOR_SSH_DEBUG + qDebug("Generating SFTP packet of type %d with request id %u", type, + requestId); +#endif + } + return *this; +} + +SftpOutgoingPacket &SftpOutgoingPacket::appendInt(quint32 val) +{ + m_data.append(AbstractSshPacket::encodeInt(val)); + return *this; +} + +SftpOutgoingPacket &SftpOutgoingPacket::appendInt64(quint64 value) +{ + m_data.append(AbstractSshPacket::encodeInt(value)); + return *this; +} + +SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QString &string) +{ + m_data.append(AbstractSshPacket::encodeString(string.toUtf8())); + return *this; +} + +SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QByteArray &string) +{ + m_data += AbstractSshPacket::encodeString(string); + return *this; +} + +SftpOutgoingPacket &SftpOutgoingPacket::finalize() +{ + AbstractSshPacket::setLengthField(m_data); + return *this; +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h b/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h new file mode 100644 index 0000000000..4f456e8754 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftpoutgoingpacket_p.h @@ -0,0 +1,83 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTPOUTGOINGPACKET_P_H +#define SFTPOUTGOINGPACKET_P_H + +#include "sftppacket_p.h" +#include "sftpdefs.h" + +namespace Core { +namespace Internal { + +class SftpOutgoingPacket : public AbstractSftpPacket +{ +public: + SftpOutgoingPacket(); + SftpOutgoingPacket &generateInit(quint32 version); + SftpOutgoingPacket &generateOpenDir(const QString &path, quint32 requestId); + SftpOutgoingPacket &generateReadDir(const QByteArray &handle, + quint32 requestId); + SftpOutgoingPacket &generateCloseHandle(const QByteArray &handle, + quint32 requestId); + SftpOutgoingPacket &generateMkDir(const QString &path, quint32 requestId); + SftpOutgoingPacket &generateRmDir(const QString &path, quint32 requestId); + SftpOutgoingPacket &generateRm(const QString &path, quint32 requestId); + SftpOutgoingPacket &generateRename(const QString &oldPath, + const QString &newPath, quint32 requestId); + SftpOutgoingPacket &generateOpenFileForWriting(const QString &path, + SftpOverwriteMode mode, quint32 requestId); + SftpOutgoingPacket &generateOpenFileForReading(const QString &path, + quint32 requestId); + SftpOutgoingPacket &generateReadFile(const QByteArray &handle, + quint64 offset, quint32 length, quint32 requestId); + SftpOutgoingPacket &generateFstat(const QByteArray &handle, + quint32 requestId); + SftpOutgoingPacket &generateWriteFile(const QByteArray &handle, + quint64 offset, const QByteArray &data, quint32 requestId); + +private: + static QByteArray encodeString(const QString &string); + + enum OpenType { Read, Write }; + SftpOutgoingPacket &generateOpenFile(const QString &path, OpenType openType, + SftpOverwriteMode mode, quint32 requestId); + + SftpOutgoingPacket &init(SftpPacketType type, quint32 requestId); + SftpOutgoingPacket &appendInt(quint32 value); + SftpOutgoingPacket &appendInt64(quint64 value); + SftpOutgoingPacket &appendString(const QString &string); + SftpOutgoingPacket &appendString(const QByteArray &string); + SftpOutgoingPacket &finalize(); +}; + +} // namespace Internal +} // namespace Core + +#endif // SFTPOUTGOINGPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sftppacket.cpp b/src/plugins/coreplugin/ssh/sftppacket.cpp new file mode 100644 index 0000000000..0064bf39fa --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftppacket.cpp @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sftppacket_p.h" + +#include "sshpacketparser_p.h" + +namespace Core { +namespace Internal { + +const quint32 AbstractSftpPacket::MaxDataSize = 32768; +const quint32 AbstractSftpPacket::MaxPacketSize = 34000; +const int AbstractSftpPacket::TypeOffset = 4; +const int AbstractSftpPacket::RequestIdOffset = TypeOffset + 1; +const int AbstractSftpPacket::PayloadOffset = RequestIdOffset + 4; + + +AbstractSftpPacket::AbstractSftpPacket() +{ +} + +quint32 AbstractSftpPacket::requestId() const +{ + return SshPacketParser::asUint32(m_data, RequestIdOffset); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftppacket_p.h b/src/plugins/coreplugin/ssh/sftppacket_p.h new file mode 100644 index 0000000000..aae1a15a97 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sftppacket_p.h @@ -0,0 +1,108 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SFTPPACKET_P_H +#define SFTPPACKET_P_H + +#include <QtCore/QByteArray> +#include <QtCore/QList> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +enum SftpPacketType { + SSH_FXP_INIT = 1, + SSH_FXP_VERSION = 2, + SSH_FXP_OPEN = 3, + SSH_FXP_CLOSE = 4, + SSH_FXP_READ = 5, + SSH_FXP_WRITE = 6, + SSH_FXP_LSTAT = 7, + SSH_FXP_FSTAT = 8, + SSH_FXP_SETSTAT = 9, + SSH_FXP_FSETSTAT = 10, + SSH_FXP_OPENDIR = 11, + SSH_FXP_READDIR = 12, + SSH_FXP_REMOVE = 13, + SSH_FXP_MKDIR = 14, + SSH_FXP_RMDIR = 15, + SSH_FXP_REALPATH = 16, + SSH_FXP_STAT = 17, + SSH_FXP_RENAME = 18, + SSH_FXP_READLINK = 19, + SSH_FXP_SYMLINK = 20, // Removed from later protocol versions. Try not to use. + + SSH_FXP_STATUS = 101, + SSH_FXP_HANDLE = 102, + SSH_FXP_DATA = 103, + SSH_FXP_NAME = 104, + SSH_FXP_ATTRS = 105, + + SSH_FXP_EXTENDED = 200, + SSH_FXP_EXTENDED_REPLY = 201 +}; + +enum SftpStatusCode { + SSH_FX_OK = 0, + SSH_FX_EOF = 1, + SSH_FX_NO_SUCH_FILE = 2, + SSH_FX_PERMISSION_DENIED = 3, + SSH_FX_FAILURE = 4, + SSH_FX_BAD_MESSAGE = 5, + SSH_FX_NO_CONNECTION = 6, + SSH_FX_CONNECTION_LOST = 7, + SSH_FX_OP_UNSUPPORTED = 8 +}; + +class AbstractSftpPacket +{ +public: + AbstractSftpPacket(); + quint32 requestId() const; + const QByteArray &rawData() const { return m_data; } + SftpPacketType type() const { return static_cast<SftpPacketType>(m_data.at(TypeOffset)); } + + static const quint32 MaxDataSize; // "Pure" data size per read/writepacket. + static const quint32 MaxPacketSize; + +protected: + quint32 dataSize() const { return static_cast<quint32>(m_data.size()); } + + static const int TypeOffset; + static const int RequestIdOffset; + static const int PayloadOffset; + + QByteArray m_data; +}; + +} // namespace Internal +} // namespace Core + +#endif // SFTPPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sshbotanconversions_p.h b/src/plugins/coreplugin/ssh/sshbotanconversions_p.h new file mode 100644 index 0000000000..0582977942 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshbotanconversions_p.h @@ -0,0 +1,97 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef BYTEARRAYCONVERSIONS_P_H +#define BYTEARRAYCONVERSIONS_P_H + +#include "sshcapabilities_p.h" + +#include <botan/rng.h> +#include <botan/secmem.h> + +namespace Core { +namespace Internal { + +inline const Botan::byte *convertByteArray(const QByteArray &a) +{ + return reinterpret_cast<const Botan::byte *>(a.constData()); +} + +inline Botan::byte *convertByteArray(QByteArray &a) +{ + return reinterpret_cast<Botan::byte *>(a.data()); +} + +inline QByteArray convertByteArray(const Botan::SecureVector<Botan::byte> &v) +{ + return QByteArray(reinterpret_cast<const char *>(v.begin()), v.size()); +} + +inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName) +{ + Q_ASSERT(rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1 + || rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1); + return rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1 + ? "modp/ietf/1024" : "modp/ietf/2048"; +} + +inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName) +{ + Q_ASSERT(rfcAlgoName == SshCapabilities::CryptAlgo3Des + || rfcAlgoName == SshCapabilities::CryptAlgoAes128); + return rfcAlgoName == SshCapabilities::CryptAlgo3Des + ? "TripleDES" : "AES-128"; +} + +inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName) +{ + Q_ASSERT(rfcAlgoName == SshCapabilities::PubKeyDss + || rfcAlgoName == SshCapabilities::PubKeyRsa); + return rfcAlgoName == SshCapabilities::PubKeyDss + ? "EMSA1(SHA-1)" : "EMSA3(SHA-1)"; +} + +inline const char *botanSha1Name() { return "SHA-1"; } + +inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName) +{ + Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1); + return botanSha1Name(); +} + +inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName) +{ + Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1); + return 20; +} + +} // namespace Internal +} // namespace Core + +#endif // BYTEARRAYCONVERSIONS_P_H diff --git a/src/plugins/coreplugin/ssh/sshcapabilities.cpp b/src/plugins/coreplugin/ssh/sshcapabilities.cpp new file mode 100644 index 0000000000..56db394206 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshcapabilities.cpp @@ -0,0 +1,103 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshcapabilities_p.h" + +#include "sshexception_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +namespace { + QByteArray listAsByteArray(const QList<QByteArray> &list) + { + QByteArray array; + foreach(const QByteArray &elem, list) + array += elem + ','; + if (!array.isEmpty()) + array.remove(array.count() - 1, 1); + return array; + } +} // anonymous namspace + +const QByteArray SshCapabilities::DiffieHellmanGroup1Sha1("diffie-hellman-group1-sha1"); +const QByteArray SshCapabilities::DiffieHellmanGroup14Sha1("diffie-hellman-group14-sha1"); +const QList<QByteArray> SshCapabilities::KeyExchangeMethods + = QList<QByteArray>() << SshCapabilities::DiffieHellmanGroup1Sha1 + << SshCapabilities::DiffieHellmanGroup14Sha1; + +const QByteArray SshCapabilities::PubKeyDss("ssh-dss"); +const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa"); +const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms + = QList<QByteArray>() << SshCapabilities::PubKeyRsa + << SshCapabilities::PubKeyDss; + +const QByteArray SshCapabilities::CryptAlgo3Des("3des-cbc"); +const QByteArray SshCapabilities::CryptAlgoAes128("aes128-cbc"); +const QList<QByteArray> SshCapabilities::EncryptionAlgorithms + = QList<QByteArray>() << SshCapabilities::CryptAlgoAes128 + << SshCapabilities::CryptAlgo3Des; + +const QByteArray SshCapabilities::HMacSha1("hmac-sha1"); +const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96"); +const QList<QByteArray> SshCapabilities::MacAlgorithms + = QList<QByteArray>() /* << SshCapabilities::HMacSha196 */ + << SshCapabilities::HMacSha1; + +const QList<QByteArray> SshCapabilities::CompressionAlgorithms + = QList<QByteArray>() << "none"; + +const QByteArray SshCapabilities::SshConnectionService("ssh-connection"); + +const QByteArray SshCapabilities::PublicKeyAuthMethod("publickey"); +const QByteArray SshCapabilities::PasswordAuthMethod("password"); + + +QByteArray SshCapabilities::findBestMatch(const QList<QByteArray> &myCapabilities, + const QList<QByteArray> &serverCapabilities) +{ + foreach (const QByteArray &myCapability, myCapabilities) { + if (serverCapabilities.contains(myCapability)) + return myCapability; + } + + throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Server and client capabilities don't match.", + QCoreApplication::translate("SshConnection", + "Server and client capabilities don't match. " + "Client list was: %1.\nServer list was %2.") + .arg(listAsByteArray(myCapabilities).data()) + .arg(listAsByteArray(serverCapabilities).data())); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshcapabilities_p.h b/src/plugins/coreplugin/ssh/sshcapabilities_p.h new file mode 100644 index 0000000000..7c58c830f5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshcapabilities_p.h @@ -0,0 +1,72 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CAPABILITIES_P_H +#define CAPABILITIES_P_H + +#include <QtCore/QByteArray> +#include <QtCore/QList> + +namespace Core { +namespace Internal { + +class SshCapabilities +{ +public: + static const QByteArray DiffieHellmanGroup1Sha1; + static const QByteArray DiffieHellmanGroup14Sha1; + static const QList<QByteArray> KeyExchangeMethods; + + static const QByteArray PubKeyDss; + static const QByteArray PubKeyRsa; + static const QList<QByteArray> PublicKeyAlgorithms; + + static const QByteArray CryptAlgo3Des; + static const QByteArray CryptAlgoAes128; + static const QList<QByteArray> EncryptionAlgorithms; + + static const QByteArray HMacSha1; + static const QByteArray HMacSha196; + static const QList<QByteArray> MacAlgorithms; + + static const QList<QByteArray> CompressionAlgorithms; + + static const QByteArray SshConnectionService; + + static const QByteArray PublicKeyAuthMethod; + static const QByteArray PasswordAuthMethod; + + static QByteArray findBestMatch(const QList<QByteArray> &myCapabilities, + const QList<QByteArray> &serverCapabilities); +}; + +} // namespace Internal +} // namespace Core + +#endif // CAPABILITIES_P_H diff --git a/src/plugins/coreplugin/ssh/sshchannel.cpp b/src/plugins/coreplugin/ssh/sshchannel.cpp new file mode 100644 index 0000000000..6e1b9c43f5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshchannel.cpp @@ -0,0 +1,244 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshchannel_p.h" + +#include "sshincomingpacket_p.h" +#include "sshsendfacility_p.h" + +#include <botan/exceptn.h> + +namespace Core { +namespace Internal { + +namespace { + const quint32 MinMaxPacketSize = 32768; + const quint32 MaxPacketSize = 16 * 1024 * 1024; + const quint32 InitialWindowSize = MaxPacketSize; + const quint32 NoChannel = 0xffffffffu; +} // anonymous namespace + +AbstractSshChannel::AbstractSshChannel(quint32 channelId, + SshSendFacility &sendFacility) + : m_sendFacility(sendFacility), m_localChannel(channelId), + m_remoteChannel(NoChannel), m_localWindowSize(InitialWindowSize), + m_remoteWindowSize(0), m_state(Inactive) +{ +} + +AbstractSshChannel::~AbstractSshChannel() +{ + +} + +void AbstractSshChannel::setChannelState(ChannelState state) +{ + m_state = state; + if (state == Closed) + closeHook(); +} + +void AbstractSshChannel::requestSessionStart() +{ + // Note: We are just being paranoid here about the Botan exceptions, + // which are extremely unlikely to happen, because if there was a problem + // with our cryptography stuff, it would have hit us before, on + // establishing the connection. + try { + m_sendFacility.sendSessionPacket(m_localChannel, InitialWindowSize, + MaxPacketSize); + setChannelState(SessionRequested); + } catch (Botan::Exception &e) { + m_errorString = QString::fromAscii(e.what()); + closeChannel(); + } +} + +void AbstractSshChannel::sendData(const QByteArray &data) +{ + try { + m_sendBuffer += data; + flushSendBuffer(); + } catch (Botan::Exception &e) { + m_errorString = QString::fromAscii(e.what()); + closeChannel(); + } +} + +void AbstractSshChannel::handleWindowAdjust(quint32 bytesToAdd) +{ + checkChannelActive(); + + const quint64 newValue = m_remoteWindowSize + bytesToAdd; + if (newValue > 0xffffffffu) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Illegal window size requested."); + } + + m_remoteWindowSize = newValue; + flushSendBuffer(); +} + +void AbstractSshChannel::flushSendBuffer() +{ + const quint32 bytesToSend + = qMin<quint32>(m_remoteWindowSize, m_sendBuffer.size()); + if (bytesToSend > 0) { + const QByteArray &data = m_sendBuffer.left(bytesToSend); + m_sendFacility.sendChannelDataPacket(m_remoteChannel, data); + m_sendBuffer.remove(0, bytesToSend); + m_remoteWindowSize -= bytesToSend; + } +} + +void AbstractSshChannel::handleOpenSuccess(quint32 remoteChannelId, + quint32 remoteWindowSize, quint32 remoteMaxPacketSize) +{ + if (m_state != SessionRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet."); + } + + if (remoteMaxPacketSize < MinMaxPacketSize) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Maximum packet size too low."); + } + +#ifdef CREATOR_SSH_DEBUG + qDebug("Channel opened. remote channel id: %u, remote window size: %u, " + "remote max packet size: %u", + remoteChannelId, remoteWindowSize, remoteMaxPacketSize); +#endif + m_remoteChannel = remoteChannelId; + m_remoteWindowSize = remoteWindowSize; + m_remoteMaxPacketSize = remoteMaxPacketSize; + setChannelState(SessionEstablished); + handleOpenSuccessInternal(); +} + +void AbstractSshChannel::handleOpenFailure(const QString &reason) +{ + if (m_state != SessionRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_OPEN_FAILURE packet."); + } + +#ifdef CREATOR_SSH_DEBUG + qDebug("Channel open request failed for channel %u", m_localChannel); +#endif + m_errorString = reason; + handleOpenFailureInternal(); +} + +void AbstractSshChannel::handleChannelEof() +{ + if (m_state == Inactive || m_state == Closed) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_EOF message."); + } + m_localWindowSize = 0; +} + +void AbstractSshChannel::handleChannelClose() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("Receiving CLOSE for channel %u", m_localChannel); +#endif + if (channelState() == Inactive || channelState() == Closed) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_CLOSE message."); + } + closeChannel(); + setChannelState(Closed); +} + +void AbstractSshChannel::handleChannelData(const QByteArray &data) +{ + const int bytesToDeliver = handleChannelOrExtendedChannelData(data); + handleChannelDataInternal(bytesToDeliver == data.size() + ? data : data.left(bytesToDeliver)); +} + +void AbstractSshChannel::handleChannelExtendedData(quint32 type, const QByteArray &data) +{ + const int bytesToDeliver = handleChannelOrExtendedChannelData(data); + handleChannelExtendedDataInternal(type, bytesToDeliver == data.size() + ? data : data.left(bytesToDeliver)); +} + +void AbstractSshChannel::handleChannelRequest(const SshIncomingPacket &packet) +{ + qWarning("Ignoring unknown request type '%s'", + packet.extractChannelRequestType().data()); +} + +int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data) +{ + checkChannelActive(); + + const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize()); + if (bytesToDeliver != data.size()) + qWarning("Misbehaving server does not respect local window, clipping."); + + m_localWindowSize -= bytesToDeliver; + if (m_localWindowSize < MaxPacketSize) { + m_localWindowSize += MaxPacketSize; + m_sendFacility.sendWindowAdjustPacket(m_remoteChannel, + MaxPacketSize); + } + return bytesToDeliver; +} + +void AbstractSshChannel::closeChannel() +{ + if (m_state != CloseRequested && m_state != Closed) { + if (m_state == Inactive) { + setChannelState(Closed); + } else { + setChannelState(CloseRequested); + m_sendFacility.sendChannelEofPacket(m_remoteChannel); + m_sendFacility.sendChannelClosePacket(m_remoteChannel); + } + } +} + +void AbstractSshChannel::checkChannelActive() +{ + if (channelState() == Inactive || channelState() == Closed) + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Channel not open."); +} + +quint32 AbstractSshChannel::maxDataSize() const +{ + return qMin(m_localWindowSize, MaxPacketSize); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshchannel_p.h b/src/plugins/coreplugin/ssh/sshchannel_p.h new file mode 100644 index 0000000000..993357d871 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshchannel_p.h @@ -0,0 +1,111 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHCHANNEL_P_H +#define SSHCHANNEL_P_H + +#include <QtCore/QByteArray> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +class SshIncomingPacket; +class SshSendFacility; + +class AbstractSshChannel +{ +public: + enum ChannelState { + Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed + }; + + ChannelState channelState() const { return m_state; } + void setChannelState(ChannelState state); + + void setError(const QString &error) { m_errorString = error; } + QString errorString() const { return m_errorString; } + + quint32 localChannelId() const { return m_localChannel; } + quint32 remoteChannel() const { return m_remoteChannel; } + + virtual void handleChannelSuccess()=0; + virtual void handleChannelFailure()=0; + virtual void handleChannelRequest(const SshIncomingPacket &packet); + + virtual void closeHook()=0; + + void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize, + quint32 remoteMaxPacketSize); + void handleOpenFailure(const QString &reason); + void handleWindowAdjust(quint32 bytesToAdd); + void handleChannelEof(); + void handleChannelClose(); + void handleChannelData(const QByteArray &data); + void handleChannelExtendedData(quint32 type, const QByteArray &data); + + void requestSessionStart(); + void sendData(const QByteArray &data); + void closeChannel(); + + virtual ~AbstractSshChannel(); + +protected: + AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility); + + quint32 maxDataSize() const; + void checkChannelActive(); + + SshSendFacility &m_sendFacility; + +private: + virtual void handleOpenSuccessInternal()=0; + virtual void handleOpenFailureInternal()=0; + virtual void handleChannelDataInternal(const QByteArray &data)=0; + virtual void handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data)=0; + + void setState(ChannelState newState); + void flushSendBuffer(); + int handleChannelOrExtendedChannelData(const QByteArray &data); + + const quint32 m_localChannel; + quint32 m_remoteChannel; + quint32 m_localWindowSize; + quint32 m_remoteWindowSize; + quint32 m_remoteMaxPacketSize; + ChannelState m_state; + QByteArray m_sendBuffer; + QString m_errorString; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHCHANNEL_P_H diff --git a/src/plugins/coreplugin/ssh/sshchannelmanager.cpp b/src/plugins/coreplugin/ssh/sshchannelmanager.cpp new file mode 100644 index 0000000000..c7d3113697 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshchannelmanager.cpp @@ -0,0 +1,188 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshchannelmanager_p.h" + +#include "sftpchannel.h" +#include "sftpchannel_p.h" +#include "sshincomingpacket_p.h" +#include "sshremoteprocess.h" +#include "sshremoteprocess_p.h" +#include "sshsendfacility_p.h" + +#include <QtCore/QList> + +namespace Core { +namespace Internal { + +SshChannelManager::SshChannelManager(SshSendFacility &sendFacility) + : m_sendFacility(sendFacility), m_nextLocalChannelId(0) +{ +} + +SshChannelManager::~SshChannelManager() {} + +void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet) +{ + lookupChannel(packet.extractRecipientChannel()) + ->handleChannelRequest(packet); +} + +void SshChannelManager::handleChannelOpen(const SshIncomingPacket &) +{ + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Server tried to open channel on client."); +} + +void SshChannelManager::handleChannelOpenFailure(const SshIncomingPacket &packet) +{ + const SshChannelOpenFailure &failure = packet.extractChannelOpenFailure(); + ChannelIterator it = lookupChannelAsIterator(failure.localChannel); + try { + it.value()->handleOpenFailure(failure.reasonString); + } catch (SshServerException &e) { + removeChannel(it); + throw e; + } + removeChannel(it); +} + +void SshChannelManager::handleChannelOpenConfirmation(const SshIncomingPacket &packet) +{ + const SshChannelOpenConfirmation &confirmation + = packet.extractChannelOpenConfirmation(); + lookupChannel(confirmation.localChannel)->handleOpenSuccess(confirmation.remoteChannel, + confirmation.remoteWindowSize, confirmation.remoteMaxPacketSize); +} + +void SshChannelManager::handleChannelSuccess(const SshIncomingPacket &packet) +{ + lookupChannel(packet.extractRecipientChannel())->handleChannelSuccess(); +} + +void SshChannelManager::handleChannelFailure(const SshIncomingPacket &packet) +{ + lookupChannel(packet.extractRecipientChannel())->handleChannelFailure(); +} + +void SshChannelManager::handleChannelWindowAdjust(const SshIncomingPacket &packet) +{ + const SshChannelWindowAdjust adjust = packet.extractWindowAdjust(); + lookupChannel(adjust.localChannel)->handleWindowAdjust(adjust.bytesToAdd); +} + +void SshChannelManager::handleChannelData(const SshIncomingPacket &packet) +{ + const SshChannelData &data = packet.extractChannelData(); + lookupChannel(data.localChannel)->handleChannelData(data.data); +} + +void SshChannelManager::handleChannelExtendedData(const SshIncomingPacket &packet) +{ + const SshChannelExtendedData &data = packet.extractChannelExtendedData(); + lookupChannel(data.localChannel)->handleChannelExtendedData(data.type, data.data); +} + +void SshChannelManager::handleChannelEof(const SshIncomingPacket &packet) +{ + AbstractSshChannel * const channel + = lookupChannel(packet.extractRecipientChannel(), true); + if (channel) + channel->handleChannelEof(); +} + +void SshChannelManager::handleChannelClose(const SshIncomingPacket &packet) +{ + const quint32 channelId = packet.extractRecipientChannel(); + + ChannelIterator it = lookupChannelAsIterator(channelId, true); + if (it != m_channels.end()) { + it.value()->handleChannelClose(); + removeChannel(it); + } +} + +SshChannelManager::ChannelIterator SshChannelManager::lookupChannelAsIterator(quint32 channelId, + bool allowNotFound) +{ + ChannelIterator it = m_channels.find(channelId); + if (it == m_channels.end() && !allowNotFound) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid channel id.", + SSH_TR("Invalid channel id %1").arg(channelId)); + } + return it; +} + +AbstractSshChannel *SshChannelManager::lookupChannel(quint32 channelId, + bool allowNotFound) +{ + ChannelIterator it = lookupChannelAsIterator(channelId, allowNotFound); + return it == m_channels.end() ? 0 : it.value(); +} + +Core::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteArray &command) +{ + SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility)); + insertChannel(proc->d, proc); + return proc; +} + +Core::SftpChannel::Ptr SshChannelManager::createSftpChannel() +{ + SftpChannel::Ptr sftp(new SftpChannel(m_nextLocalChannelId++, m_sendFacility)); + insertChannel(sftp->d, sftp); + return sftp; +} + +void SshChannelManager::insertChannel(AbstractSshChannel *priv, + const QSharedPointer<QObject> &pub) +{ + m_channels.insert(priv->localChannelId(), priv); + m_sessions.insert(priv, pub); +} + +void SshChannelManager::closeAllChannels() +{ + for (ChannelIterator it = m_channels.begin(); it != m_channels.end(); ++it) + it.value()->closeChannel(); + m_channels.clear(); + m_sessions.clear(); +} + +void SshChannelManager::removeChannel(ChannelIterator it) +{ + Q_ASSERT(it != m_channels.end() && "Unexpected channel lookup failure."); + const int removeCount = m_sessions.remove(it.value()); + Q_ASSERT(removeCount == 1 && "Session for channel not found."); + m_channels.erase(it); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshchannelmanager_p.h b/src/plugins/coreplugin/ssh/sshchannelmanager_p.h new file mode 100644 index 0000000000..fe62c00924 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshchannelmanager_p.h @@ -0,0 +1,89 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHCHANNELLAYER_P_H +#define SSHCHANNELLAYER_P_H + +#include <QtCore/QHash> +#include <QtCore/QSharedPointer> + +namespace Core { + +class SftpChannel; +class SshRemoteProcess; + +namespace Internal { + +class AbstractSshChannel; +class SshIncomingPacket; +class SshSendFacility; + +class SshChannelManager +{ +public: + SshChannelManager(SshSendFacility &sendFacility); + ~SshChannelManager(); + + QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command); + QSharedPointer<SftpChannel> createSftpChannel(); + void closeAllChannels(); + + void handleChannelRequest(const SshIncomingPacket &packet); + void handleChannelOpen(const SshIncomingPacket &packet); + void handleChannelOpenFailure(const SshIncomingPacket &packet); + void handleChannelOpenConfirmation(const SshIncomingPacket &packet); + void handleChannelSuccess(const SshIncomingPacket &packet); + void handleChannelFailure(const SshIncomingPacket &packet); + void handleChannelWindowAdjust(const SshIncomingPacket &packet); + void handleChannelData(const SshIncomingPacket &packet); + void handleChannelExtendedData(const SshIncomingPacket &packet); + void handleChannelEof(const SshIncomingPacket &packet); + void handleChannelClose(const SshIncomingPacket &packet); + +private: + typedef QHash<quint32, AbstractSshChannel *>::Iterator ChannelIterator; + + ChannelIterator lookupChannelAsIterator(quint32 channelId, + bool allowNotFound = false); + AbstractSshChannel *lookupChannel(quint32 channelId, + bool allowNotFound = false); + void removeChannel(ChannelIterator it); + void insertChannel(AbstractSshChannel *priv, + const QSharedPointer<QObject> &pub); + + SshSendFacility &m_sendFacility; + QHash<quint32, AbstractSshChannel *> m_channels; + QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions; + quint32 m_nextLocalChannelId; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHCHANNELLAYER_P_H diff --git a/src/plugins/coreplugin/ssh/sshconnection.cpp b/src/plugins/coreplugin/ssh/sshconnection.cpp index 7433bbebd8..fbf63d7670 100644 --- a/src/plugins/coreplugin/ssh/sshconnection.cpp +++ b/src/plugins/coreplugin/ssh/sshconnection.cpp @@ -1,19 +1,20 @@ -/**************************************************************************** +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** -** This file is part of Qt Creator. +** Commercial Usage ** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. +** 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 @@ -21,471 +22,540 @@ ** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. ** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +**************************************************************************/ #include "sshconnection.h" +#include "sshconnection_p.h" -#include "ne7sshobject.h" +#include "sftpchannel.h" +#include "sshcapabilities_p.h" +#include "sshchannelmanager_p.h" +#include "sshcryptofacility_p.h" +#include "sshexception_p.h" +#include "sshkeyexchange_p.h" -#include <QtCore/QCoreApplication> -#include <QtCore/QDir> -#include <QtCore/QFileInfo> -#include <QtCore/QMutex> -#include <QtCore/QThread> -#include <QtCore/QWaitCondition> - -#include <ne7ssh.h> +#include <botan/exceptn.h> +#include <botan/init.h> -#include <exception> +#include <QtCore/QFile> +#include <QtCore/QMutex> +#include <QtNetwork/QTcpSocket> namespace Core { namespace { + const QByteArray ClientId("SSH-2.0-QtCreator\r\n"); -class GenericSshConnection -{ - Q_DECLARE_TR_FUNCTIONS(GenericSshConnection) -public: - GenericSshConnection(const SshServerInfo &server) - : ssh(Internal::Ne7SshObject::instance()->get()), - m_server(server), - m_channel(-1) - { } - - ~GenericSshConnection() - { - quit(); - } + bool staticInitializationsDone = false; + QMutex staticInitMutex; - bool start(bool shell, void (*callbackFunc)(void *), void *callbackArg) + void doStaticInitializationsIfNecessary() { - Q_ASSERT(m_channel == -1); - - try { - const QString *authString; - int (ne7ssh::*connFunc)(const char *, int, const char *, - const char *, bool, int, void (*)(void *), void *); - if (m_server.authType == SshServerInfo::AuthByPwd) { - authString = &m_server.pwd; - connFunc = &ne7ssh::connectWithPassword; - } else { - authString = &m_server.privateKeyFile; - connFunc = &ne7ssh::connectWithKey; + if (!staticInitializationsDone) { + staticInitMutex.lock(); + if (!staticInitializationsDone) { + Botan::LibraryInitializer::initialize("thread_safe=true"); + qRegisterMetaType<SshError>("SshError"); + staticInitializationsDone = true; } - m_channel = (ssh.data()->*connFunc)(m_server.host.toLatin1(), - m_server.port, m_server.uname.toAscii(), authString->toLatin1(), - shell, m_server.timeout, callbackFunc, callbackArg); - if (m_channel == -1) { - setError(tr("Could not connect to host."), false); - return false; - } - } catch (const std::exception &e) { - // Should in theory not be necessary, but Net7 leaks Botan exceptions. - setError(tr("Error in cryptography backend: %1") - .arg(QLatin1String(e.what())), false); - return false; - } - - return true; - } - - void quit() - { - const int channel = m_channel; - if (channel != -1) { - m_channel = -1; - if (!ssh->close(channel)) - qWarning("%s: close() failed.", Q_FUNC_INFO); + staticInitMutex.unlock(); } } +} - bool isConnected() const { return channel() != -1; } - bool hasError() const { return !m_error.isEmpty(); } - QString error() const { return m_error; } - int channel() const { return m_channel; } - QString lastNe7Error() { return ssh->errors()->pop(channel()); } - const SshServerInfo &server() { return m_server; } +// TODO: Mechanism for checking the host key. First connection to host: save, later: compare - void setError(const QString error, bool appendNe7ErrMsg) - { - m_error = error; - if (appendNe7ErrMsg) - m_error += QLatin1String(": ") + lastNe7Error(); - } - - QSharedPointer<ne7ssh> ssh; -private: - const SshServerInfo m_server; - QString m_error; - int m_channel; -}; +SshConnection::Ptr SshConnection::create() +{ + doStaticInitializationsIfNecessary(); + return Ptr(new SshConnection); +} -char *alloc(size_t n) +SshConnection::SshConnection() : d(new Internal::SshConnectionPrivate(this)) { - return new char[n]; + connect(d, SIGNAL(connected()), this, SIGNAL(connected()), + Qt::QueuedConnection); + connect(d, SIGNAL(dataAvailable(QString)), this, + SIGNAL(dataAvailable(QString)), Qt::QueuedConnection); + connect(d, SIGNAL(disconnected()), this, SIGNAL(disconnected()), + Qt::QueuedConnection); + connect(d, SIGNAL(error(SshError)), this, SIGNAL(error(SshError)), + Qt::QueuedConnection); } -} // anonymous namespace +void SshConnection::connectToHost(const SshConnectionParameters &serverInfo) +{ + d->connectToHost(serverInfo); +} -namespace Internal { +void SshConnection::disconnectFromHost() +{ + d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "", + QString()); +} -struct InteractiveSshConnectionPrivate +SshConnection::State SshConnection::state() const { - InteractiveSshConnectionPrivate(const SshServerInfo &server) - : conn(server), outputReader(0) {} + switch (d->state()) { + case Internal::SocketUnconnected: + return Unconnected; + case Internal::ConnectionEstablished: + return Connected; + default: + return Connecting; + } +} - GenericSshConnection conn; - ConnectionOutputReader *outputReader; - QByteArray remoteOutput; - QMutex mutex; - QWaitCondition waitCond; -}; +SshError SshConnection::errorState() const +{ + return d->error(); +} -struct NonInteractiveSshConnectionPrivate +QString SshConnection::errorString() const { - NonInteractiveSshConnectionPrivate(const SshServerInfo &server) - : conn(server) {} + return d->errorString(); +} - GenericSshConnection conn; - Ne7SftpSubsystem sftp; -}; +SshConnectionParameters SshConnection::connectionParameters() const +{ + return d->m_connParams; +} -class ConnectionOutputReader : public QThread +SshConnection::~SshConnection() { -public: - ConnectionOutputReader(InteractiveSshConnection *parent) - : QThread(parent), m_conn(parent), m_stopRequested(false), - m_dataAvailable(false) - {} + disconnect(); + disconnectFromHost(); + delete d; +} - ~ConnectionOutputReader() - { - stop(); - wait(); - } +QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command) +{ + return state() == Connected + ? d->createRemoteProcess(command) : QSharedPointer<SshRemoteProcess>(); +} - void stop() - { - m_mutex.lock(); - m_stopRequested = true; - m_waitCond.wakeOne(); - m_mutex.unlock(); - } +QSharedPointer<SftpChannel> SshConnection::createSftpChannel() +{ + return state() == Connected + ? d->createSftpChannel() : QSharedPointer<SftpChannel>(); +} - void dataAvailable() - { - m_mutex.lock(); - m_dataAvailable = true; - m_waitCond.wakeOne(); - m_mutex.unlock(); - } -private: - virtual void run() - { - while (true) { - m_mutex.lock(); - if (m_stopRequested) { - m_mutex.unlock(); - return; - } - const int channel = m_conn->d->conn.channel(); - if (!m_dataAvailable || channel == -1) - m_waitCond.wait(&m_mutex); - m_dataAvailable = false; - m_mutex.unlock(); - QScopedPointer<char, QScopedPointerArrayDeleter<char> > - output(m_conn->d->conn.ssh->readAndReset(channel, alloc)); - if (output) { - m_conn->d->mutex.lock(); - m_conn->d->remoteOutput += output.data(); - emit m_conn->remoteOutputAvailable(); - m_conn->d->mutex.unlock(); - } - } - } +namespace Internal { - InteractiveSshConnection *m_conn; - bool m_stopRequested; - bool m_dataAvailable; - QMutex m_mutex; - QWaitCondition m_waitCond; -}; +SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn) + : m_socket(new QTcpSocket(this)), m_state(SocketUnconnected), + m_sendFacility(m_socket), + m_channelManager(new SshChannelManager(m_sendFacility)), + m_error(SshNoError), m_ignoreNextPacket(false), m_conn(conn) +{ + setupPacketHandlers(); + connect(&m_timeoutTimer, SIGNAL(timeout()), this, SLOT(handleTimeout())); +} -} // namespace Internal +SshConnectionPrivate::~SshConnectionPrivate() +{ + disconnect(); +} +void SshConnectionPrivate::setupPacketHandlers() +{ + typedef SshConnectionPrivate This; + + setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected, + &This::handleKeyExchangeInitPacket); + setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << KeyExchangeStarted, + &This::handleKeyExchangeReplyPacket); + + setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << KeyExchangeSuccess, + &This::handleNewKeysPacket); + setupPacketHandler(SSH_MSG_SERVICE_ACCEPT, + StateList() << UserAuthServiceRequested, + &This::handleServiceAcceptPacket); + setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, + StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket); + setupPacketHandler(SSH_MSG_GLOBAL_REQUEST, + StateList() << ConnectionEstablished, &This::handleGlobalRequest); + + const StateList authReqList = StateList() << UserAuthRequested; + setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList, + &This::handleUserAuthBannerPacket); + setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList, + &This::handleUserAuthSuccessPacket); + setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList, + &This::handleUserAuthFailurePacket); + + const StateList connectedList + = StateList() << ConnectionEstablished; + setupPacketHandler(SSH_MSG_CHANNEL_REQUEST, connectedList, + &This::handleChannelRequest); + setupPacketHandler(SSH_MSG_CHANNEL_OPEN, connectedList, + &This::handleChannelOpen); + setupPacketHandler(SSH_MSG_CHANNEL_OPEN_FAILURE, connectedList, + &This::handleChannelOpenFailure); + setupPacketHandler(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, connectedList, + &This::handleChannelOpenConfirmation); + setupPacketHandler(SSH_MSG_CHANNEL_SUCCESS, connectedList, + &This::handleChannelSuccess); + setupPacketHandler(SSH_MSG_CHANNEL_FAILURE, connectedList, + &This::handleChannelFailure); + setupPacketHandler(SSH_MSG_CHANNEL_WINDOW_ADJUST, connectedList, + &This::handleChannelWindowAdjust); + setupPacketHandler(SSH_MSG_CHANNEL_DATA, connectedList, + &This::handleChannelData); + setupPacketHandler(SSH_MSG_CHANNEL_EXTENDED_DATA, connectedList, + &This::handleChannelExtendedData); + + const StateList connectedOrClosedList + = StateList() << SocketUnconnected << ConnectionEstablished; + setupPacketHandler(SSH_MSG_CHANNEL_EOF, connectedOrClosedList, + &This::handleChannelEof); + setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList, + &This::handleChannelClose); + + setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected + << KeyExchangeStarted << KeyExchangeSuccess + << UserAuthServiceRequested << UserAuthRequested + << ConnectionEstablished, &This::handleDisconnect); +} -namespace { +void SshConnectionPrivate::setupPacketHandler(SshPacketType type, + const SshConnectionPrivate::StateList &states, + SshConnectionPrivate::PacketHandler handler) +{ + m_packetHandlers.insert(type, HandlerInStates(states, handler)); +} -void wakeupReader(void *opaqueReader) +void SshConnectionPrivate::handleSocketConnected() { - static_cast<Internal::ConnectionOutputReader*>(opaqueReader)->dataAvailable(); + m_state = SocketConnected; + sendData(ClientId); } -} // Anonymous namespace +void SshConnectionPrivate::handleIncomingData() +{ + if (m_state == SocketUnconnected) + return; // For stuff queued in the event loop after we've called closeConnection(); + + try { + m_incomingData += m_socket->readAll(); +#ifdef CREATOR_SSH_DEBUG + qDebug("state = %d, remote data size = %d", m_state, + m_incomingData.count()); +#endif + if (m_state == SocketConnected) + handleServerId(); + handlePackets(); + } catch (SshServerException &e) { + closeConnection(e.error, SshProtocolError, e.errorStringServer, + tr("SSH Protocol error: %1").arg(e.errorStringUser)); + } catch (SshClientException &e) { + closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "", + e.errorString); + } catch (Botan::Exception &e) { + closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "", + tr("Botan exception: %1").arg(e.what())); + } +} +void SshConnectionPrivate::handleServerId() +{ + const int idOffset = m_incomingData.indexOf("SSH-"); + if (idOffset == -1) + return; + m_incomingData.remove(0, idOffset); + if (m_incomingData.size() < 7) + return; + const QByteArray &version = m_incomingData.mid(4, 3); + if (version != "2.0") { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, + "Invalid protocol version.", + tr("Invalid protocol version: Expected '2.0', got '%1'.") + .arg(SshPacketParser::asUserString(version))); + } + const int endOffset = m_incomingData.indexOf("\r\n"); + if (endOffset == -1) + return; + if (m_incomingData.at(7) != '-') { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid server id.", tr("Invalid server id '%1'.") + .arg(SshPacketParser::asUserString(m_incomingData))); + } -InteractiveSshConnection::InteractiveSshConnection(const SshServerInfo &server) - : d(new Internal::InteractiveSshConnectionPrivate(server)) -{ - d->outputReader = new Internal::ConnectionOutputReader(this); + m_keyExchange.reset(new SshKeyExchange(m_sendFacility)); + m_keyExchange->sendKexInitPacket(m_incomingData.left(endOffset)); + m_incomingData.remove(0, endOffset + 2); } -InteractiveSshConnection::~InteractiveSshConnection() +void SshConnectionPrivate::handlePackets() { - d->conn.ssh->send("exit\n", d->conn.channel()); - quit(); - delete d; + m_incomingPacket.consumeData(m_incomingData); + while (m_incomingPacket.isComplete()) { + handleCurrentPacket(); + m_incomingPacket.clear(); + m_incomingPacket.consumeData(m_incomingData); + } } -bool InteractiveSshConnection::start() +void SshConnectionPrivate::handleCurrentPacket() { - if (isConnected()) - return true; + Q_ASSERT(m_incomingPacket.isComplete()); + Q_ASSERT(m_state == KeyExchangeStarted || !m_ignoreNextPacket); - if (!d->conn.start(true, wakeupReader, d->outputReader)) - return false; + if (m_ignoreNextPacket) { + m_ignoreNextPacket = false; + return; + } - d->outputReader->start(); - return true; + QHash<SshPacketType, HandlerInStates>::ConstIterator it + = m_packetHandlers.find(m_incomingPacket.type()); + if (it == m_packetHandlers.end()) { + m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr()); + return; + } + if (!it.value().first.contains(m_state)) { + throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected packet.", tr("Unexpected packet of type %1.") + .arg(m_incomingPacket.type())); + } + (this->*it.value().second)(); } -bool InteractiveSshConnection::sendInput(const QByteArray &input) +void SshConnectionPrivate::handleKeyExchangeInitPacket() { - if (!d->conn.ssh->send(input.data(), d->conn.channel())) { - d->conn.setError(tr("Error sending input"), true); - return false; - } - return true; + // If the server sends a guessed packet, the guess must be wrong, + // because the algorithms we support requires us to initiate the + // key exchange. + if (m_keyExchange->sendDhInitPacket(m_incomingPacket)) + m_ignoreNextPacket = true; + m_state = KeyExchangeStarted; } -void InteractiveSshConnection::quit() +void SshConnectionPrivate::handleKeyExchangeReplyPacket() { - d->mutex.lock(); - d->waitCond.wakeOne(); - d->mutex.unlock(); - d->outputReader->stop(); - d->conn.quit(); + m_keyExchange->sendNewKeysPacket(m_incomingPacket, + ClientId.left(ClientId.size() - 2)); + m_sendFacility.recreateKeys(*m_keyExchange); + m_state = KeyExchangeSuccess; } -QByteArray InteractiveSshConnection::waitForRemoteOutput(int msecs) +void SshConnectionPrivate::handleNewKeysPacket() { - d->mutex.lock(); - if (d->remoteOutput.isEmpty()) - d->waitCond.wait(&d->mutex, msecs == -1 ? ULONG_MAX : msecs); - const QByteArray remoteOutput = d->remoteOutput; - d->remoteOutput.clear(); - d->mutex.unlock(); - return remoteOutput; + m_incomingPacket.recreateKeys(*m_keyExchange); + m_keyExchange.reset(); + m_sendFacility.sendUserAuthServiceRequestPacket(); + m_state = UserAuthServiceRequested; } +void SshConnectionPrivate::handleServiceAcceptPacket() +{ + if (m_connParams.authType == SshConnectionParameters::AuthByPwd) { + m_sendFacility.sendUserAuthByPwdRequestPacket(m_connParams.uname.toUtf8(), + SshCapabilities::SshConnectionService, m_connParams.pwd.toUtf8()); + } else { + QFile privKeyFile(m_connParams.privateKeyFile); + bool couldOpen = privKeyFile.open(QIODevice::ReadOnly); + QByteArray contents; + if (couldOpen) + contents = privKeyFile.readAll(); + if (!couldOpen || privKeyFile.error() != QFile::NoError) { + throw SshClientException(SshKeyFileError, + tr("Could not read private key file: %1") + .arg(privKeyFile.errorString())); + } -InteractiveSshConnection::Ptr InteractiveSshConnection::create(const SshServerInfo &server) -{ - return Ptr(new InteractiveSshConnection(server)); + m_sendFacility.createAuthenticationKey(contents); + m_sendFacility.sendUserAuthByKeyRequestPacket(m_connParams.uname.toUtf8(), + SshCapabilities::SshConnectionService); + } + m_state = UserAuthRequested; } -bool InteractiveSshConnection::isConnected() const +void SshConnectionPrivate::handlePasswordExpiredPacket() { - return d->conn.isConnected(); + if (m_connParams.authType == SshConnectionParameters::AuthByKey) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Got SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, but did not use password."); + } + + throw SshClientException(SshAuthenticationError, tr("Password expired.")); } -bool InteractiveSshConnection::hasError() const +void SshConnectionPrivate::handleUserAuthBannerPacket() { - return d->conn.hasError(); + emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message); } -QString InteractiveSshConnection::error() const +void SshConnectionPrivate::handleGlobalRequest() { - return d->conn.error(); + m_sendFacility.sendRequestFailurePacket(); } - -namespace { - -class FileMgr +void SshConnectionPrivate::handleUserAuthSuccessPacket() { -public: - FileMgr(const QString &filePath, const char *mode) - : m_file(fopen(filePath.toLatin1().data(), mode)) {} - ~FileMgr() { if (m_file) fclose(m_file); } - FILE *file() const { return m_file; } -private: - FILE * const m_file; -}; - -} // Anonymous namespace + m_state = ConnectionEstablished; + m_timeoutTimer.stop(); + emit connected(); +} -SftpConnection::SftpConnection(const SshServerInfo &server) - : d(new Internal::NonInteractiveSshConnectionPrivate(server)) -{ } +void SshConnectionPrivate::handleUserAuthFailurePacket() +{ + const QString errorMsg = m_connParams.authType == SshConnectionParameters::AuthByPwd + ? tr("Server rejected password.") : tr("Server rejected key."); + throw SshClientException(SshAuthenticationError, errorMsg); +} +void SshConnectionPrivate::handleDebugPacket() +{ + const SshDebug &msg = m_incomingPacket.extractDebug(); + if (msg.display) + emit dataAvailable(msg.message); +} -SftpConnection::~SftpConnection() +void SshConnectionPrivate::handleChannelRequest() { - quit(); - delete d; + m_channelManager->handleChannelRequest(m_incomingPacket); } -bool SftpConnection::start() +void SshConnectionPrivate::handleChannelOpen() { - if (isConnected()) - return true; - if (!d->conn.start(false, 0, 0)) - return false; - if (!d->conn.ssh->initSftp(d->sftp, d->conn.channel()) - || !d->sftp.setTimeout(d->conn.server().timeout)) { - d->conn.setError(tr("Error setting up SFTP subsystem"), true); - quit(); - return false; - } - return true; + m_channelManager->handleChannelOpen(m_incomingPacket); } -bool SftpConnection::transferFiles(const QList<SftpTransferInfo> &transferList) +void SshConnectionPrivate::handleChannelOpenFailure() { - for (int i = 0; i < transferList.count(); ++i) { - const SftpTransferInfo &transfer = transferList.at(i); - bool success; - if (transfer.type == SftpTransferInfo::Upload) { - success = upload(transfer.localFilePath, transfer.remoteFilePath); - } else { - success = download(transfer.remoteFilePath, transfer.localFilePath); - } - if (!success) - return false; - } + m_channelManager->handleChannelOpenFailure(m_incomingPacket); +} - return true; +void SshConnectionPrivate::handleChannelOpenConfirmation() +{ + m_channelManager->handleChannelOpenConfirmation(m_incomingPacket); } -bool SftpConnection::upload(const QString &localFilePath, - const QByteArray &remoteFilePath) +void SshConnectionPrivate::handleChannelSuccess() { - FileMgr fileMgr(localFilePath, "rb"); - if (!fileMgr.file()) { - d->conn.setError(tr("Could not open file '%1'").arg(localFilePath), - false); - return false; - } + m_channelManager->handleChannelSuccess(m_incomingPacket); +} - if (!d->sftp.put(fileMgr.file(), remoteFilePath.data())) { - d->conn.setError(tr("Could not uplodad file '%1'") - .arg(localFilePath), true); - return false; - } +void SshConnectionPrivate::handleChannelFailure() +{ + m_channelManager->handleChannelFailure(m_incomingPacket); +} - emit fileCopied(localFilePath); - return true; +void SshConnectionPrivate::handleChannelWindowAdjust() +{ + m_channelManager->handleChannelWindowAdjust(m_incomingPacket); } -bool SftpConnection::download(const QByteArray &remoteFilePath, - const QString &localFilePath) +void SshConnectionPrivate::handleChannelData() { - FileMgr fileMgr(localFilePath, "wb"); - if (!fileMgr.file()) { - d->conn.setError(tr("Could not open file '%1'").arg(localFilePath), - false); - return false; - } + m_channelManager->handleChannelData(m_incomingPacket); +} - if (!d->sftp.get(remoteFilePath.data(), fileMgr.file())) { - d->conn.setError(tr("Could not copy remote file '%1' to local file '%2'") - .arg(remoteFilePath, localFilePath), false); - return false; - } +void SshConnectionPrivate::handleChannelExtendedData() +{ + m_channelManager->handleChannelExtendedData(m_incomingPacket); +} - emit fileCopied(remoteFilePath); - return true; +void SshConnectionPrivate::handleChannelEof() +{ + m_channelManager->handleChannelEof(m_incomingPacket); } -bool SftpConnection::createRemoteDir(const QByteArray &remoteDir) +void SshConnectionPrivate::handleChannelClose() { - if (!d->sftp.mkdir(remoteDir.data())) { - d->conn.setError(tr("Could not create remote directory"), true); - return false; - } - return true; + m_channelManager->handleChannelClose(m_incomingPacket); } -bool SftpConnection::removeRemoteDir(const QByteArray &remoteDir) +void SshConnectionPrivate::handleDisconnect() { - if (!d->sftp.rmdir(remoteDir.data())) { - d->conn.setError(tr("Could not remove remote directory"), true); - return false; - } - return true; + const SshDisconnect msg = m_incomingPacket.extractDisconnect(); + throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST, + "", tr("Server closed connection: %1").arg(msg.description)); } -QByteArray SftpConnection::listRemoteDirContents(const QByteArray &remoteDir, - bool withAttributes, bool &ok) +void SshConnectionPrivate::sendData(const QByteArray &data) { - const char * const buffer = d->sftp.ls(remoteDir.data(), withAttributes); - if (!buffer) { - d->conn.setError(tr("Could not get remote directory contents"), true); - ok = false; - return QByteArray(); - } - ok = true; - return QByteArray(buffer); + m_socket->write(data); } -bool SftpConnection::removeRemoteFile(const QByteArray &remoteFile) +void SshConnectionPrivate::handleSocketDisconnected() { - if (!d->sftp.rm(remoteFile.data())) { - d->conn.setError(tr("Could not remove remote file"), true); - return false; - } - return true; + closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError, + "Connection closed unexpectedly.", + tr("Connection closed unexpectedly.")); } -bool SftpConnection::changeRemoteWorkingDir(const QByteArray &newRemoteDir) +void SshConnectionPrivate::handleSocketError() { - if (!d->sftp.cd(newRemoteDir.data())) { - d->conn.setError(tr("Could not change remote working directory"), true); - return false; + if (m_error == SshNoError) { + closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError, + "Network error", m_socket->errorString()); } - return true; } -void SftpConnection::quit() +void SshConnectionPrivate::handleTimeout() { - d->conn.quit(); + if (m_state != ConnectionEstablished) + closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "", + tr("Connection timed out.")); } -bool SftpConnection::isConnected() const -{ - return d->conn.isConnected(); +void SshConnectionPrivate::connectToHost(const SshConnectionParameters &serverInfo) +{ + m_incomingData.clear(); + m_incomingPacket.reset(); + m_sendFacility.reset(); + m_error = SshNoError; + m_ignoreNextPacket = false; + m_errorString.clear(); + connect(m_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected())); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(handleIncomingData())); + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, + SLOT(handleSocketError())); + connect(m_socket, SIGNAL(disconnected()), this, + SLOT(handleSocketDisconnected())); + this->m_connParams = serverInfo; + m_state = SocketConnecting; + m_timeoutTimer.start(m_connParams.timeout * 1000); + m_socket->connectToHost(serverInfo.host, serverInfo.port); } -bool SftpConnection::hasError() const -{ - return d->conn.hasError(); +void SshConnectionPrivate::closeConnection(SshErrorCode sshError, + SshError userError, const QByteArray &serverErrorString, + const QString &userErrorString) +{ + // Prevent endless loops by recursive exceptions. + if (m_state == SocketUnconnected || m_error != SshNoError) + return; + + m_error = userError; + m_errorString = userErrorString; + m_timeoutTimer.stop(); + disconnect(m_socket, 0, this, 0); + try { + m_channelManager->closeAllChannels(); + m_sendFacility.sendDisconnectPacket(sshError, serverErrorString); + } catch (Botan::Exception &) {} // Nothing sensible to be done here. + if (m_error != SshNoError) + emit error(userError); + if (m_state == ConnectionEstablished) + emit disconnected(); + m_socket->disconnectFromHost(); + m_state = SocketUnconnected; } -QString SftpConnection::error() const +QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command) { - return d->conn.error(); + return m_channelManager->createRemoteProcess(command); } -SftpConnection::Ptr SftpConnection::create(const SshServerInfo &server) +QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel() { - return Ptr(new SftpConnection(server)); + return m_channelManager->createSftpChannel(); } +} // namespace Internal } // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshconnection.h b/src/plugins/coreplugin/ssh/sshconnection.h index 8c7b59f594..e7f73995a4 100644 --- a/src/plugins/coreplugin/ssh/sshconnection.h +++ b/src/plugins/coreplugin/ssh/sshconnection.h @@ -1,19 +1,20 @@ -/**************************************************************************** +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** -** This file is part of Qt Creator. +** Commercial Usage ** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. +** 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 @@ -21,27 +22,16 @@ ** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. ** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +**************************************************************************/ #ifndef SSHCONNECTION_H #define SSHCONNECTION_H +#include "ssherrors.h" + #include <coreplugin/core_global.h> #include <QtCore/QByteArray> @@ -50,14 +40,14 @@ #include <QtCore/QString> namespace Core { +class SftpChannel; +class SshRemoteProcess; namespace Internal { - struct InteractiveSshConnectionPrivate; - struct NonInteractiveSshConnectionPrivate; - class ConnectionOutputReader; -} +class SshConnectionPrivate; +} // namespace Internal -struct CORE_EXPORT SshServerInfo +struct CORE_EXPORT SshConnectionParameters { QString host; QString uname; @@ -69,85 +59,44 @@ struct CORE_EXPORT SshServerInfo }; -class CORE_EXPORT InteractiveSshConnection : public QObject +/* + * This class provides an SSH connection, implementing protocol version 2.0 + * It can spawn channels for remote execution and SFTP operations (version 3). + * It operates asynchronously (non-blocking) and is not thread-safe. + */ +class CORE_EXPORT SshConnection : public QObject { Q_OBJECT - Q_DISABLE_COPY(InteractiveSshConnection) - friend class Internal::ConnectionOutputReader; + Q_DISABLE_COPY(SshConnection) public: - typedef QSharedPointer<InteractiveSshConnection> Ptr; + enum State { Unconnected, Connecting, Connected }; + typedef QSharedPointer<SshConnection> Ptr; - static Ptr create(const SshServerInfo &server); + static Ptr create(); - bool start(); - void quit(); - bool isConnected() const; - bool sendInput(const QByteArray &input); // Should normally end in newline. - QByteArray waitForRemoteOutput(int msecs = -1); - bool hasError() const; - QString error() const; - ~InteractiveSshConnection(); + void connectToHost(const SshConnectionParameters &serverInfo); + void disconnectFromHost(); + State state() const; + SshError errorState() const; + QString errorString() const; + SshConnectionParameters connectionParameters() const; + ~SshConnection(); -signals: - void remoteOutputAvailable(); - -private: - InteractiveSshConnection(const SshServerInfo &server); - - struct Internal::InteractiveSshConnectionPrivate *d; -}; - - -struct CORE_EXPORT SftpTransferInfo -{ - enum Type { Upload, Download }; - - SftpTransferInfo(const QString &localFilePath, - const QByteArray &remoteFilePath, Type type) - : localFilePath(localFilePath), - remoteFilePath(remoteFilePath), - type(type) - { - } - - QString localFilePath; - QByteArray remoteFilePath; - Type type; -}; - -class CORE_EXPORT SftpConnection : public QObject -{ - Q_OBJECT - Q_DISABLE_COPY(SftpConnection) -public: - typedef QSharedPointer<SftpConnection> Ptr; - - static Ptr create(const SshServerInfo &server); - bool start(); - void quit(); - bool isConnected() const; - bool hasError() const; - QString error() const; - bool upload(const QString &localFilePath, const QByteArray &remoteFilePath); - bool download(const QByteArray &remoteFilePath, const QString &localFilePath); - bool transferFiles(const QList<SftpTransferInfo> &transferList); - bool createRemoteDir(const QByteArray &remoteDir); - bool removeRemoteDir(const QByteArray &remoteDir); - bool removeRemoteFile(const QByteArray &remoteFile); - bool changeRemoteWorkingDir(const QByteArray &newRemoteDir); - QByteArray listRemoteDirContents(const QByteArray &remoteDir, - bool withAttributes, bool &ok); - ~SftpConnection(); + QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command); + QSharedPointer<SftpChannel> createSftpChannel(); signals: - void fileCopied(const QString &filePath); + void connected(); + void disconnected(); + void dataAvailable(const QString &message); + void error(SshError); private: - SftpConnection(const SshServerInfo &server); + SshConnection(); - Internal::NonInteractiveSshConnectionPrivate *d; + Internal::SshConnectionPrivate *d; }; -} // namespace Core +} // namespace Internal #endif // SSHCONNECTION_H diff --git a/src/plugins/coreplugin/ssh/sshconnection_p.h b/src/plugins/coreplugin/ssh/sshconnection_p.h new file mode 100644 index 0000000000..c20ccf78b5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshconnection_p.h @@ -0,0 +1,157 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHCONNECTION_P_H +#define SSHCONNECTION_P_H + +#include "sshconnection.h" +#include "sshexception_p.h" +#include "sshincomingpacket_p.h" +#include "sshremoteprocess.h" +#include "sshsendfacility_p.h" + +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtCore/QObject> +#include <QtCore/QPair> +#include <QtCore/QScopedPointer> +#include <QtCore/QTimer> + +QT_BEGIN_NAMESPACE +class QTcpSocket; +QT_END_NAMESPACE + +namespace Botan { class Exception; } + +namespace Core { +class SftpChannel; + +namespace Internal { +class SshChannelManager; + +// NOTE: When you add stuff here, don't forget to update m_packetHandlers. +enum SshStateInternal { + SocketUnconnected, // initial and after disconnect + SocketConnecting, // After connectToHost() + SocketConnected, // After socket's connected() signal + KeyExchangeStarted, // After server's KEXINIT message + KeyExchangeSuccess, // After server's DH_REPLY message + UserAuthServiceRequested, + UserAuthRequested, + + ConnectionEstablished // After service has been started + // ... +}; + +class SshConnectionPrivate : public QObject +{ + Q_OBJECT + friend class Core::SshConnection; +public: + SshConnectionPrivate(SshConnection *conn); + ~SshConnectionPrivate(); + + void connectToHost(const SshConnectionParameters &serverInfo); + void closeConnection(SshErrorCode sshError, SshError userError, + const QByteArray &serverErrorString, const QString &userErrorString); + QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command); + QSharedPointer<SftpChannel> createSftpChannel(); + SshStateInternal state() const { return m_state; } + SshError error() const { return m_error; } + QString errorString() const { return m_errorString; } + +signals: + void connected(); + void disconnected(); + void dataAvailable(const QString &message); + void error(SshError); + +private: + Q_SLOT void handleSocketConnected(); + Q_SLOT void handleIncomingData(); + Q_SLOT void handleSocketError(); + Q_SLOT void handleSocketDisconnected(); + Q_SLOT void handleTimeout(); + + void handleServerId(); + void handlePackets(); + void handleCurrentPacket(); + void handleKeyExchangeInitPacket(); + void handleKeyExchangeReplyPacket(); + void handleNewKeysPacket(); + void handleServiceAcceptPacket(); + void handlePasswordExpiredPacket(); + void handleUserAuthSuccessPacket(); + void handleUserAuthFailurePacket(); + void handleUserAuthBannerPacket(); + void handleGlobalRequest(); + void handleDebugPacket(); + void handleChannelRequest(); + void handleChannelOpen(); + void handleChannelOpenFailure(); + void handleChannelOpenConfirmation(); + void handleChannelSuccess(); + void handleChannelFailure(); + void handleChannelWindowAdjust(); + void handleChannelData(); + void handleChannelExtendedData(); + void handleChannelEof(); + void handleChannelClose(); + void handleDisconnect(); + + void sendData(const QByteArray &data); + + typedef void (SshConnectionPrivate::*PacketHandler)(); + typedef QList<SshStateInternal> StateList; + void setupPacketHandlers(); + void setupPacketHandler(SshPacketType type, const StateList &states, + PacketHandler handler); + + typedef QPair<StateList, PacketHandler> HandlerInStates; + QHash<SshPacketType, HandlerInStates> m_packetHandlers; + + QTcpSocket *m_socket; + SshStateInternal m_state; + SshIncomingPacket m_incomingPacket; + SshSendFacility m_sendFacility; + QScopedPointer<SshChannelManager> m_channelManager; + SshConnectionParameters m_connParams; + QByteArray m_incomingData; + SshError m_error; + QString m_errorString; + QScopedPointer<SshKeyExchange> m_keyExchange; + QTimer m_timeoutTimer; + bool m_ignoreNextPacket; + SshConnection *m_conn; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHCONNECTION_P_H diff --git a/src/plugins/coreplugin/ssh/sshcryptofacility.cpp b/src/plugins/coreplugin/ssh/sshcryptofacility.cpp new file mode 100644 index 0000000000..fd2fe32044 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshcryptofacility.cpp @@ -0,0 +1,369 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshcryptofacility_p.h" + +#include "sshbotanconversions_p.h" +#include "sshcapabilities_p.h" +#include "sshexception_p.h" +#include "sshkeyexchange_p.h" +#include "sshpacket_p.h" + +#include <botan/ber_dec.h> +#include <botan/botan.h> +#include <botan/cbc.h> +#include <botan/dsa.h> +#include <botan/hash.h> +#include <botan/hmac.h> +#include <botan/look_pk.h> +#include <botan/pipe.h> +#include <botan/pkcs8.h> +#include <botan/pubkey.h> +#include <botan/rsa.h> + +#include <QtCore/QDebug> +#include <QtCore/QList> + +#include <string> + +using namespace Botan; + +namespace Core { +namespace Internal { + +SshAbstractCryptoFacility::SshAbstractCryptoFacility() + : m_cipherBlockSize(0), m_macLength(0) +{ +} + +SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {} + +void SshAbstractCryptoFacility::clearKeys() +{ + m_cipherBlockSize = 0; + m_macLength = 0; + m_sessionId.clear(); + m_pipe.reset(0); + m_hMac.reset(0); +} + +void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex) +{ + checkInvariant(); + + if (m_sessionId.isEmpty()) + m_sessionId = kex.h(); + Algorithm_Factory &af = global_state().algorithm_factory(); + const std::string &cryptAlgo = botanCryptAlgoName(cryptAlgoName(kex)); + BlockCipher * const cipher = af.prototype_block_cipher(cryptAlgo)->clone(); + + m_cipherBlockSize = cipher->BLOCK_SIZE; + const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize); + const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize); + + const quint32 keySize = max_keylength_of(cryptAlgo); + const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize); + SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize); + + BlockCipherMode * const cipherMode + = makeCipherMode(cipher, new Null_Padding, iv, cryptKey); + m_pipe.reset(new Pipe(cipherMode)); + + m_macLength = botanHMacKeyLen(hMacAlgoName(kex)); + const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength()); + SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength()); + const HashFunction * const hMacProto + = af.prototype_hash_function(botanHMacAlgoName(hMacAlgoName(kex))); + m_hMac.reset(new HMAC(hMacProto->clone())); + m_hMac->set_key(hMacKey); +} + +void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset, + quint32 dataSize) const +{ + Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size())); + checkInvariant(); + + // Session id empty => No key exchange has happened yet. + if (dataSize == 0 || m_sessionId.isEmpty()) + return; + + if (dataSize % cipherBlockSize() != 0) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid packet size"); + } + m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset, + dataSize); + quint32 bytesRead = m_pipe->read(reinterpret_cast<byte *>(data.data()) + offset, + dataSize, m_pipe->message_count() - 1); // Can't use Pipe::LAST_MESSAGE because of a VC bug. + Q_ASSERT(bytesRead == dataSize); +} + +QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data, + quint32 dataSize) const +{ + return m_sessionId.isEmpty() + ? QByteArray() + : convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()), + dataSize)); +} + +QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex, + char c, quint32 length) +{ + const QByteArray &k = kex.k(); + const QByteArray &h = kex.h(); + QByteArray data(k); + data.append(h).append(c).append(m_sessionId); + SecureVector<byte> key + = kex.hash()->process(convertByteArray(data), data.size()); + while (key.size() < length) { + SecureVector<byte> tmpKey; + tmpKey.append(convertByteArray(k), k.size()); + tmpKey.append(convertByteArray(h), h.size()); + tmpKey.append(key); + key.append(kex.hash()->process(tmpKey)); + } + return QByteArray(reinterpret_cast<const char *>(key.begin()), length); +} + +void SshAbstractCryptoFacility::checkInvariant() const +{ + Q_ASSERT(m_sessionId.isEmpty() == !m_pipe); +} + + +const QByteArray SshEncryptionFacility::PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----"); +const QByteArray SshEncryptionFacility::PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----"); +const QByteArray SshEncryptionFacility::PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----"); +const QByteArray SshEncryptionFacility::PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----"); + +QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const +{ + return kex.encryptionAlgo(); +} + +QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const +{ + return kex.hMacAlgoClientToServer(); +} + +BlockCipherMode *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher, + BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv, + const SymmetricKey &key) +{ + return new CBC_Encryption(cipher, paddingMethod, key, iv); +} + +void SshEncryptionFacility::encrypt(QByteArray &data) const +{ + convert(data, 0, data.size()); +} + +void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents) +{ + if (privKeyFileContents == m_cachedPrivKeyContents) + return; + +#ifdef CREATOR_SSH_DEBUG + qDebug("%s: Key not cached, reading", Q_FUNC_INFO); +#endif + QList<BigInt> pubKeyParams; + QList<BigInt> allKeyParams; + try { + createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, + allKeyParams); + } catch (Botan::Exception &) { + createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, + allKeyParams); + } + + foreach (const BigInt &b, allKeyParams) { + if (b.is_zero()) { + throw SshClientException(SshKeyFileError, + SSH_TR("Decoding of private key file failed.")); + } + } + + m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName); + foreach (const BigInt &b, pubKeyParams) + m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b); + m_cachedPrivKeyContents = privKeyFileContents; +} + +void SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents, + QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams) +{ + Pipe pipe; + pipe.process_msg(convertByteArray(privKeyFileContents), + privKeyFileContents.size()); + Private_Key * const key = PKCS8::load_key(pipe, m_rng); + if (DSA_PrivateKey * const dsaKey = dynamic_cast<DSA_PrivateKey *>(key)) { + m_authKey.reset(dsaKey); + pubKeyParams << dsaKey->group_p() << dsaKey->group_q() + << dsaKey->group_g() << dsaKey->get_y(); + allKeyParams << pubKeyParams << dsaKey->get_x(); + } else if (RSA_PrivateKey * const rsaKey = dynamic_cast<RSA_PrivateKey *>(key)) { + m_authKey.reset(rsaKey); + pubKeyParams << rsaKey->get_e() << rsaKey->get_n(); + allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q() + << rsaKey->get_d(); + } else { + throw Botan::Exception(); + } +} + +void SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents, + QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams) +{ + bool syntaxOk = true; + QList<QByteArray> lines = privKeyFileContents.split('\n'); + while (lines.last().isEmpty()) + lines.removeLast(); + if (lines.count() < 3) { + syntaxOk = false; + } else if (lines.first() == PrivKeyFileStartLineRsa) { + if (lines.last() != PrivKeyFileEndLineRsa) + syntaxOk =false; + else + m_authKeyAlgoName = SshCapabilities::PubKeyRsa; + } else if (lines.first() == PrivKeyFileStartLineDsa) { + if (lines.last() != PrivKeyFileEndLineDsa) + syntaxOk = false; + else + m_authKeyAlgoName = SshCapabilities::PubKeyDss; + } else { + syntaxOk = false; + } + if (!syntaxOk) { + throw SshClientException(SshKeyFileError, + SSH_TR("Private key file has unexpected format.")); + } + + QByteArray privateKeyBlob; + for (int i = 1; i < lines.size() - 1; ++i) + privateKeyBlob += lines.at(i); + privateKeyBlob = QByteArray::fromBase64(privateKeyBlob); + + BER_Decoder decoder(convertByteArray(privateKeyBlob), + privateKeyBlob.size()); + BER_Decoder sequence = decoder.start_cons(SEQUENCE); + quint32 version; + sequence.decode (version); + if (version != 0) { + throw SshClientException(SshKeyFileError, + SSH_TR("Private key encoding has version %1, expected 0.") + .arg(version)); + } + + if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) { + BigInt p, q, g, y, x; + sequence.decode (p).decode (q).decode (g).decode (y).decode (x); + DSA_PrivateKey * const dsaKey + = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x); + m_authKey.reset(dsaKey); + pubKeyParams << p << q << g << y; + allKeyParams << pubKeyParams << x; + } else { + BigInt p, q, e, d, n; + sequence.decode (n).decode (e).decode (d).decode (p).decode (q); + RSA_PrivateKey * const rsaKey + = new RSA_PrivateKey (m_rng, p, q, e, d, n); + m_authKey.reset(rsaKey); + pubKeyParams << e << n; + allKeyParams << pubKeyParams << p << q << d; + } + + sequence.discard_remaining(); + sequence.verify_end(); +} + +QByteArray SshEncryptionFacility::authenticationAlgorithmName() const +{ + Q_ASSERT(m_authKey); + return m_authKeyAlgoName; +} + +QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const +{ + Q_ASSERT(m_authKey); + + QScopedPointer<PK_Signer> signer(get_pk_signer (*m_authKey, + botanEmsaAlgoName(m_authKeyAlgoName))); + QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data; + QByteArray signature + = convertByteArray(signer->sign_message(convertByteArray(dataToSign), + dataToSign.size(), m_rng)); + return AbstractSshPacket::encodeString(m_authKeyAlgoName) + + AbstractSshPacket::encodeString(signature); +} + +QByteArray SshEncryptionFacility::getRandomNumbers(int count) const +{ + QByteArray data; + data.resize(count); + m_rng.randomize(convertByteArray(data), count); + return data; +} + +SshEncryptionFacility::~SshEncryptionFacility() {} + + +QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const +{ + return kex.decryptionAlgo(); +} + +QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const +{ + return kex.hMacAlgoServerToClient(); +} + +BlockCipherMode *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher, + BlockCipherModePaddingMethod *paddingMethod, const InitializationVector &iv, + const SymmetricKey &key) +{ + return new CBC_Decryption(cipher, paddingMethod, key, iv); +} + +void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset, + quint32 dataSize) const +{ + convert(data, offset, dataSize); +#ifdef CREATOR_SSH_DEBUG + qDebug("Decrypted data:"); + const char * const start = data.constData() + offset; + const char * const end = start + dataSize; + for (const char *c = start; c < end; ++c) + qDebug() << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")"; +#endif +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshcryptofacility_p.h b/src/plugins/coreplugin/ssh/sshcryptofacility_p.h new file mode 100644 index 0000000000..f60e6d4b8d --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshcryptofacility_p.h @@ -0,0 +1,154 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHABSTRACTCRYPTOFACILITY_P_H +#define SSHABSTRACTCRYPTOFACILITY_P_H + +#include <botan/auto_rng.h> +#include <botan/symkey.h> + +#include <QtCore/QByteArray> +#include <QtCore/QScopedPointer> + +namespace Botan { + class BigInt; + class BlockCipher; + class BlockCipherMode; + class BlockCipherModePaddingMethod; + class HashFunction; + class HMAC; + class Pipe; + class PK_Signing_Key; +} + +namespace Core { +namespace Internal { + +class SshKeyExchange; + +class SshAbstractCryptoFacility +{ +public: + virtual ~SshAbstractCryptoFacility(); + + void clearKeys(); + void recreateKeys(const SshKeyExchange &kex); + QByteArray generateMac(const QByteArray &data, quint32 dataSize) const; + quint32 cipherBlockSize() const { return m_cipherBlockSize; } + quint32 macLength() const { return m_macLength; } + +protected: + SshAbstractCryptoFacility(); + void convert(QByteArray &data, quint32 offset, quint32 dataSize) const; + QByteArray sessionId() const { return m_sessionId; } + +private: + SshAbstractCryptoFacility(const SshAbstractCryptoFacility &); + SshAbstractCryptoFacility &operator=(const SshAbstractCryptoFacility &); + + virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const=0; + virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const=0; + virtual Botan::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher, + Botan::BlockCipherModePaddingMethod *paddingMethod, + const Botan::InitializationVector &iv, + const Botan::SymmetricKey &key)=0; + virtual char ivChar() const=0; + virtual char keyChar() const=0; + virtual char macChar() const=0; + + QByteArray generateHash(const SshKeyExchange &kex, char c, quint32 length); + void checkInvariant() const; + + QByteArray m_sessionId; + QScopedPointer<Botan::Pipe> m_pipe; + QScopedPointer<Botan::HMAC> m_hMac; + quint32 m_cipherBlockSize; + quint32 m_macLength; +}; + +class SshEncryptionFacility : public SshAbstractCryptoFacility +{ +public: + void encrypt(QByteArray &data) const; + + void createAuthenticationKey(const QByteArray &privKeyFileContents); + QByteArray authenticationAlgorithmName() const; + QByteArray authenticationPublicKey() const { return m_authPubKeyBlob; } + QByteArray authenticationKeySignature(const QByteArray &data) const; + QByteArray getRandomNumbers(int count) const; + + ~SshEncryptionFacility(); + +private: + virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const; + virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const; + virtual Botan::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher, + Botan::BlockCipherModePaddingMethod *paddingMethod, + const Botan::InitializationVector &iv, const Botan::SymmetricKey &key); + virtual char ivChar() const { return 'A'; } + virtual char keyChar() const { return 'C'; } + virtual char macChar() const { return 'E'; } + + void createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents, + QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams); + void createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents, + QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams); + + static const QByteArray PrivKeyFileStartLineRsa; + static const QByteArray PrivKeyFileStartLineDsa; + static const QByteArray PrivKeyFileEndLineRsa; + static const QByteArray PrivKeyFileEndLineDsa; + + QByteArray m_authKeyAlgoName; + QByteArray m_authPubKeyBlob; + QByteArray m_cachedPrivKeyContents; + QScopedPointer<Botan::PK_Signing_Key> m_authKey; + mutable Botan::AutoSeeded_RNG m_rng; +}; + +class SshDecryptionFacility : public SshAbstractCryptoFacility +{ +public: + void decrypt(QByteArray &data, quint32 offset, quint32 dataSize) const; + +private: + virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const; + virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const; + virtual Botan::BlockCipherMode *makeCipherMode(Botan::BlockCipher *cipher, + Botan::BlockCipherModePaddingMethod *paddingMethod, + const Botan::InitializationVector &iv, const Botan::SymmetricKey &key); + virtual char ivChar() const { return 'B'; } + virtual char keyChar() const { return 'D'; } + virtual char macChar() const { return 'F'; } +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHABSTRACTCRYPTOFACILITY_P_H diff --git a/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp b/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp new file mode 100644 index 0000000000..d35075bf77 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshdelayedsignal.cpp @@ -0,0 +1,165 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshdelayedsignal_p.h" + +#include "sftpchannel_p.h" +#include "sshremoteprocess_p.h" + +#include <QtCore/QTimer> + +namespace Core { +namespace Internal { + +SshDelayedSignal::SshDelayedSignal(const QWeakPointer<QObject> &checkObject) + : m_checkObject(checkObject) +{ + QTimer::singleShot(0, this, SLOT(handleTimeout())); +} + +void SshDelayedSignal::handleTimeout() +{ + if (!m_checkObject.isNull()) + emitSignal(); + deleteLater(); +} + + +SftpDelayedSignal::SftpDelayedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject) + : SshDelayedSignal(checkObject), m_privChannel(privChannel) {} + + +SftpInitializationFailedSignal::SftpInitializationFailedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QString &reason) + : SftpDelayedSignal(privChannel, checkObject), m_reason(reason) {} + +void SftpInitializationFailedSignal::emitSignal() +{ + m_privChannel->emitInitializationFailedSignal(m_reason); +} + + +SftpInitializedSignal::SftpInitializedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject) + : SftpDelayedSignal(privChannel, checkObject) {} + +void SftpInitializedSignal::emitSignal() +{ + m_privChannel->emitInitialized(); +} + + +SftpJobFinishedSignal::SftpJobFinishedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, SftpJobId jobId, + const QString &error) + : SftpDelayedSignal(privChannel, checkObject), m_jobId(jobId), m_error(error) +{ +} + +void SftpJobFinishedSignal::emitSignal() +{ + m_privChannel->emitJobFinished(m_jobId, m_error); +} + + +SftpDataAvailableSignal::SftpDataAvailableSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, SftpJobId jobId, + const QString &data) + : SftpDelayedSignal(privChannel, checkObject), m_jobId(jobId), m_data(data) {} + +void SftpDataAvailableSignal::emitSignal() +{ + m_privChannel->emitDataAvailable(m_jobId, m_data); +} + + +SftpClosedSignal::SftpClosedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject) + : SftpDelayedSignal(privChannel, checkObject) {} + +void SftpClosedSignal::emitSignal() +{ + m_privChannel->emitClosed(); +} + + +SshRemoteProcessDelayedSignal::SshRemoteProcessDelayedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject) + : SshDelayedSignal(checkObject), m_privChannel(privChannel) {} + + +SshRemoteProcessStartedSignal::SshRemoteProcessStartedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject) + : SshRemoteProcessDelayedSignal(privChannel, checkObject) {} + +void SshRemoteProcessStartedSignal::emitSignal() +{ + m_privChannel->emitStartedSignal(); +} + + +SshRemoteProcessOutputAvailableSignal::SshRemoteProcessOutputAvailableSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QByteArray &output) + : SshRemoteProcessDelayedSignal(privChannel, checkObject), m_output(output) +{ +} + +void SshRemoteProcessOutputAvailableSignal::emitSignal() +{ + m_privChannel->emitOutputAvailableSignal(m_output); +} + + +SshRemoteProcessErrorOutputAvailableSignal::SshRemoteProcessErrorOutputAvailableSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QByteArray &output) + : SshRemoteProcessDelayedSignal(privChannel, checkObject), m_output(output) +{ +} + +void SshRemoteProcessErrorOutputAvailableSignal::emitSignal() +{ + m_privChannel->emitErrorOutputAvailableSignal(m_output); +} + + +SshRemoteProcessClosedSignal::SshRemoteProcessClosedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, int exitStatus) + : SshRemoteProcessDelayedSignal(privChannel, checkObject), + m_exitStatus(exitStatus) +{ +} + +void SshRemoteProcessClosedSignal::emitSignal() +{ + m_privChannel->emitClosedSignal(m_exitStatus); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h b/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h new file mode 100644 index 0000000000..09163fb686 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshdelayedsignal_p.h @@ -0,0 +1,190 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHDELAYEDSIGNAL_P_H +#define SSHDELAYEDSIGNAL_P_H + +#include "sftpdefs.h" + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QWeakPointer> + +namespace Core { +namespace Internal { +class SftpChannelPrivate; +class SshRemoteProcessPrivate; + +class SshDelayedSignal : public QObject +{ + Q_OBJECT +public: + SshDelayedSignal(const QWeakPointer<QObject> &checkObject); + +private: + Q_SLOT void handleTimeout(); + virtual void emitSignal()=0; + + const QWeakPointer<QObject> m_checkObject; +}; + + +class SftpDelayedSignal : public SshDelayedSignal +{ +public: + SftpDelayedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject); + +protected: + SftpChannelPrivate * const m_privChannel; +}; + +class SftpInitializationFailedSignal : public SftpDelayedSignal +{ +public: + SftpInitializationFailedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QString &reason); + +private: + virtual void emitSignal(); + + const QString m_reason; +}; + +class SftpInitializedSignal : public SftpDelayedSignal +{ +public: + SftpInitializedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject); + +private: + virtual void emitSignal(); +}; + +class SftpJobFinishedSignal : public SftpDelayedSignal +{ +public: + SftpJobFinishedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, SftpJobId jobId, + const QString &error); + +private: + virtual void emitSignal(); + + const SftpJobId m_jobId; + const QString m_error; +}; + +class SftpDataAvailableSignal : public SftpDelayedSignal +{ +public: + SftpDataAvailableSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, SftpJobId jobId, + const QString &data); + +private: + virtual void emitSignal(); + + const SftpJobId m_jobId; + const QString m_data; +}; + +class SftpClosedSignal : public SftpDelayedSignal +{ +public: + SftpClosedSignal(SftpChannelPrivate *privChannel, + const QWeakPointer<QObject> &checkObject); + +private: + virtual void emitSignal(); +}; + + +class SshRemoteProcessDelayedSignal : public SshDelayedSignal +{ +public: + SshRemoteProcessDelayedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject); + +protected: + SshRemoteProcessPrivate * const m_privChannel; +}; + +class SshRemoteProcessStartedSignal : public SshRemoteProcessDelayedSignal +{ +public: + SshRemoteProcessStartedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject); + +private: + virtual void emitSignal(); +}; + +class SshRemoteProcessOutputAvailableSignal + : public SshRemoteProcessDelayedSignal +{ +public: + SshRemoteProcessOutputAvailableSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QByteArray &output); + +private: + virtual void emitSignal(); + + const QByteArray m_output; +}; + +class SshRemoteProcessErrorOutputAvailableSignal + : public SshRemoteProcessDelayedSignal +{ +public: + SshRemoteProcessErrorOutputAvailableSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, const QByteArray &output); + +private: + virtual void emitSignal(); + + const QByteArray m_output; +}; + +class SshRemoteProcessClosedSignal : public SshRemoteProcessDelayedSignal +{ +public: + SshRemoteProcessClosedSignal(SshRemoteProcessPrivate *privChannel, + const QWeakPointer<QObject> &checkObject, int exitStatus); + +private: + virtual void emitSignal(); + + const int m_exitStatus; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHDELAYEDSIGNAL_P_H diff --git a/src/plugins/coreplugin/ssh/ssherrors.h b/src/plugins/coreplugin/ssh/ssherrors.h new file mode 100644 index 0000000000..01587edfc5 --- /dev/null +++ b/src/plugins/coreplugin/ssh/ssherrors.h @@ -0,0 +1,43 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHERRORS_P_H +#define SSHERRORS_P_H + +namespace Core { + +enum SshError { + SshNoError, SshSocketError, SshTimeoutError, SshProtocolError, + SshHostKeyError, SshKeyFileError, SshAuthenticationError, + SshClosedByServerError, SshInternalError +}; + +} // namespace Core + +#endif // SSHERRORS_P_H diff --git a/src/plugins/coreplugin/ssh/sshexception_p.h b/src/plugins/coreplugin/ssh/sshexception_p.h new file mode 100644 index 0000000000..6812fabc49 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshexception_p.h @@ -0,0 +1,89 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHEXCEPTION_P_H +#define SSHEXCEPTION_P_H + +#include "ssherrors.h" + +#include <QtCore/QByteArray> +#include <QtCore/QCoreApplication> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +enum SshErrorCode { + SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1, + SSH_DISCONNECT_PROTOCOL_ERROR = 2, + SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3, + SSH_DISCONNECT_RESERVED = 4, + SSH_DISCONNECT_MAC_ERROR = 5, + SSH_DISCONNECT_COMPRESSION_ERROR = 6, + SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7, + SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8, + SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9, + SSH_DISCONNECT_CONNECTION_LOST = 10, + SSH_DISCONNECT_BY_APPLICATION = 11, + SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12, + SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13, + SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14, + SSH_DISCONNECT_ILLEGAL_USER_NAME = 15 +}; + +#define SSH_TR(string) QCoreApplication::translate("SshConnection", string) + +#define SSH_SERVER_EXCEPTION(error, errorString) \ + SshServerException((error), (errorString), SSH_TR(errorString)) + +struct SshServerException +{ + SshServerException(SshErrorCode error, const QByteArray &errorStringServer, + const QString &errorStringUser) + : error(error), errorStringServer(errorStringServer), + errorStringUser(errorStringUser) {} + + const SshErrorCode error; + const QByteArray errorStringServer; + const QString errorStringUser; +}; + +struct SshClientException +{ + SshClientException(SshError error, const QString &errorString) + : error(error), errorString(errorString) {} + + const SshError error; + const QString errorString; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHEXCEPTION_P_H diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket.cpp b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp new file mode 100644 index 0000000000..fdc274bbbd --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshincomingpacket.cpp @@ -0,0 +1,442 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshincomingpacket_p.h" + +#include "sshcapabilities_p.h" + +namespace Core { +namespace Internal { + +const QByteArray SshIncomingPacket::ExitStatusType("exit-status"); +const QByteArray SshIncomingPacket::ExitSignalType("exit-signal"); + +SshIncomingPacket::SshIncomingPacket() : m_serverSeqNr(0) { } + +quint32 SshIncomingPacket::cipherBlockSize() const +{ + return qMax(m_decrypter.cipherBlockSize(), 8U); +} + +quint32 SshIncomingPacket::macLength() const +{ + return m_decrypter.macLength(); +} + +void SshIncomingPacket::recreateKeys(const SshKeyExchange &keyExchange) +{ + m_decrypter.recreateKeys(keyExchange); +} + +void SshIncomingPacket::reset() +{ + clear(); + m_serverSeqNr = 0; + m_decrypter.clearKeys(); +} + +void SshIncomingPacket::consumeData(QByteArray &newData) +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("%s: current data size = %d, new data size = %d", + Q_FUNC_INFO, m_data.size(), newData.size()); +#endif + + if (isComplete() || newData.isEmpty()) + return; + + /* + * Until we have reached the minimum packet size, we cannot decrypt the + * length field. + */ + const quint32 minSize = minPacketSize(); + if (currentDataSize() < minSize) { + const int bytesToTake + = qMin<quint32>(minSize - currentDataSize(), newData.size()); + moveFirstBytes(m_data, newData, bytesToTake); +#ifdef CREATOR_SSH_DEBUG + qDebug("Took %d bytes from new data", bytesToTake); +#endif + if (currentDataSize() < minSize) + return; + } + + const int bytesToTake + = qMin<quint32>(length() + 4 + macLength() - currentDataSize(), + newData.size()); + moveFirstBytes(m_data, newData, bytesToTake); +#ifdef CREATOR_SSH_DEBUG + qDebug("Took %d bytes from new data", bytesToTake); +#endif + if (isComplete()) { +#ifdef CREATOR_SSH_DEBUG + qDebug("Message complete. Overall size: %u, payload size: %u", + m_data.size(), m_length - paddingLength() - 1); +#endif + decrypt(); + ++m_serverSeqNr; + } +} + +void SshIncomingPacket::decrypt() +{ + Q_ASSERT(isComplete()); + const quint32 netDataLength = length() + 4; + m_decrypter.decrypt(m_data, cipherBlockSize(), + netDataLength - cipherBlockSize()); + const QByteArray &mac = m_data.mid(netDataLength, macLength()); + if (mac != generateMac(m_decrypter, m_serverSeqNr)) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_MAC_ERROR, + "Message authentication failed."); + } +} + +void SshIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source, + int n) +{ + target.append(source.left(n)); + source.remove(0, n); +} + +SshKeyExchangeInit SshIncomingPacket::extractKeyExchangeInitData() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_KEXINIT); + + SshKeyExchangeInit exchangeData; + try { + quint32 offset = TypeOffset + 1; + std::memcpy(exchangeData.cookie, &m_data.constData()[offset], + sizeof exchangeData.cookie); + offset += sizeof exchangeData.cookie; + exchangeData.keyAlgorithms + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.serverHostKeyAlgorithms + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.encryptionAlgorithmsClientToServer + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.encryptionAlgorithmsServerToClient + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.macAlgorithmsClientToServer + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.macAlgorithmsServerToClient + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.compressionAlgorithmsClientToServer + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.compressionAlgorithmsServerToClient + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.languagesClientToServer + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.languagesServerToClient + = SshPacketParser::asNameList(m_data, &offset); + exchangeData.firstKexPacketFollows + = SshPacketParser::asBool(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Key exchange failed: Server sent invalid SSH_MSG_KEXINIT packet."); + } + return exchangeData; +} + +SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY); + + try { + SshKeyExchangeReply replyData; + quint32 offset = TypeOffset + 1; + const quint32 k_sLength + = SshPacketParser::asUint32(m_data, &offset); + if (offset + k_sLength > currentDataSize()) + throw SshPacketParseException(); + replyData.k_s = m_data.mid(offset - 4, k_sLength + 4); + if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo) + throw SshPacketParseException(); + + // DSS: p and q, RSA: e and n + replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); + replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); + + // g and y + if (pubKeyAlgo == SshCapabilities::PubKeyDss) { + replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); + replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); + } + + replyData.f = SshPacketParser::asBigInt(m_data, &offset); + offset += 4; + if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo) + throw SshPacketParseException(); + replyData.signatureBlob = SshPacketParser::asString(m_data, &offset); + return replyData; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Key exchange failed: " + "Server sent invalid SSH_MSG_KEXDH_REPLY packet."); + } +} + +SshDisconnect SshIncomingPacket::extractDisconnect() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_DISCONNECT); + + SshDisconnect msg; + try { + quint32 offset = TypeOffset + 1; + msg.reasonCode = SshPacketParser::asUint32(m_data, &offset); + msg.description = SshPacketParser::asUserString(m_data, &offset); + msg.language = SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_DISCONNECT."); + } + + return msg; +} + +SshUserAuthBanner SshIncomingPacket::extractUserAuthBanner() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_USERAUTH_BANNER); + + try { + SshUserAuthBanner msg; + quint32 offset = TypeOffset + 1; + msg.message = SshPacketParser::asUserString(m_data, &offset); + msg.language = SshPacketParser::asString(m_data, &offset); + return msg; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_USERAUTH_BANNER."); + } +} + +SshDebug SshIncomingPacket::extractDebug() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_DEBUG); + + try { + SshDebug msg; + quint32 offset = TypeOffset + 1; + msg.display = SshPacketParser::asBool(m_data, &offset); + msg.message = SshPacketParser::asUserString(m_data, &offset); + msg.language = SshPacketParser::asString(m_data, &offset); + return msg; + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_USERAUTH_BANNER."); + } +} + +SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_FAILURE); + + SshChannelOpenFailure openFailure; + try { + quint32 offset = TypeOffset + 1; + openFailure.localChannel = SshPacketParser::asUint32(m_data, &offset); + openFailure.reasonCode = SshPacketParser::asUint32(m_data, &offset); + openFailure.reasonString = SshPacketParser::asString(m_data, &offset); + openFailure.language = SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Server sent invalid SSH_MSG_CHANNEL_OPEN_FAILURE packet."); + } + return openFailure; +} + +SshChannelOpenConfirmation SshIncomingPacket::extractChannelOpenConfirmation() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_CONFIRMATION); + + SshChannelOpenConfirmation confirmation; + try { + quint32 offset = TypeOffset + 1; + confirmation.localChannel = SshPacketParser::asUint32(m_data, &offset); + confirmation.remoteChannel = SshPacketParser::asUint32(m_data, &offset); + confirmation.remoteWindowSize = SshPacketParser::asUint32(m_data, &offset); + confirmation.remoteMaxPacketSize = SshPacketParser::asUint32(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Server sent invalid SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet."); + } + return confirmation; +} + +SshChannelWindowAdjust SshIncomingPacket::extractWindowAdjust() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_WINDOW_ADJUST); + + SshChannelWindowAdjust adjust; + try { + quint32 offset = TypeOffset + 1; + adjust.localChannel = SshPacketParser::asUint32(m_data, &offset); + adjust.bytesToAdd = SshPacketParser::asUint32(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_WINDOW_ADJUST packet."); + } + return adjust; +} + +SshChannelData SshIncomingPacket::extractChannelData() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_DATA); + + SshChannelData data; + try { + quint32 offset = TypeOffset + 1; + data.localChannel = SshPacketParser::asUint32(m_data, &offset); + data.data = SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_DATA packet."); + } + return data; +} + +SshChannelExtendedData SshIncomingPacket::extractChannelExtendedData() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_EXTENDED_DATA); + + SshChannelExtendedData data; + try { + quint32 offset = TypeOffset + 1; + data.localChannel = SshPacketParser::asUint32(m_data, &offset); + data.type = SshPacketParser::asUint32(m_data, &offset); + data.data = SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_EXTENDED_DATA packet."); + } + return data; +} + +SshChannelExitStatus SshIncomingPacket::extractChannelExitStatus() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST); + + SshChannelExitStatus exitStatus; + try { + quint32 offset = TypeOffset + 1; + exitStatus.localChannel = SshPacketParser::asUint32(m_data, &offset); + const QByteArray &type = SshPacketParser::asString(m_data, &offset); + Q_ASSERT(type == ExitStatusType); + if (SshPacketParser::asBool(m_data, &offset)) + throw SshPacketParseException(); + exitStatus.exitStatus = SshPacketParser::asUint32(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid exit-status packet."); + } + return exitStatus; +} + +SshChannelExitSignal SshIncomingPacket::extractChannelExitSignal() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST); + + SshChannelExitSignal exitSignal; + try { + quint32 offset = TypeOffset + 1; + exitSignal.localChannel = SshPacketParser::asUint32(m_data, &offset); + const QByteArray &type = SshPacketParser::asString(m_data, &offset); + Q_ASSERT(type == ExitSignalType); + if (SshPacketParser::asBool(m_data, &offset)) + throw SshPacketParseException(); + exitSignal.signal = SshPacketParser::asString(m_data, &offset); + exitSignal.coreDumped = SshPacketParser::asBool(m_data, &offset); + exitSignal.error = SshPacketParser::asUserString(m_data, &offset); + exitSignal.language = SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid exit-signal packet."); + } + return exitSignal; +} + +quint32 SshIncomingPacket::extractRecipientChannel() const +{ + Q_ASSERT(isComplete()); + + try { + quint32 offset = TypeOffset + 1; + return SshPacketParser::asUint32(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Server sent invalid packet."); + } +} + +QByteArray SshIncomingPacket::extractChannelRequestType() const +{ + Q_ASSERT(isComplete()); + Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST); + + try { + quint32 offset = TypeOffset + 1; + SshPacketParser::asUint32(m_data, &offset); + return SshPacketParser::asString(m_data, &offset); + } catch (SshPacketParseException &) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Invalid SSH_MSG_CHANNEL_REQUEST packet."); + } +} + +void SshIncomingPacket::calculateLength() const +{ + Q_ASSERT(currentDataSize() >= minPacketSize()); +#ifdef CREATOR_SSH_DEBUG + qDebug("Length field before decryption: %d-%d-%d-%d", m_data.at(0) & 0xff, + m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff); +#endif + m_decrypter.decrypt(m_data, 0, cipherBlockSize()); +#ifdef CREATOR_SSH_DEBUG + qDebug("Length field after decryption: %d-%d-%d-%d", m_data.at(0) & 0xff, m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff); + qDebug("message type = %d", m_data.at(TypeOffset)); +#endif + m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0)); +#ifdef CREATOR_SSH_DEBUG + qDebug("decrypted length is %u", m_length); +#endif +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshincomingpacket_p.h b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h new file mode 100644 index 0000000000..9b10c8f799 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshincomingpacket_p.h @@ -0,0 +1,186 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHINCOMINGPACKET_P_H +#define SSHINCOMINGPACKET_P_H + +#include "sshpacket_p.h" + +#include "sshcryptofacility_p.h" +#include "sshpacketparser_p.h" + +#include <QtCore/QList> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +class SshKeyExchange; + +struct SshKeyExchangeInit +{ + char cookie[16]; + SshNameList keyAlgorithms; + SshNameList serverHostKeyAlgorithms; + SshNameList encryptionAlgorithmsClientToServer; + SshNameList encryptionAlgorithmsServerToClient; + SshNameList macAlgorithmsClientToServer; + SshNameList macAlgorithmsServerToClient; + SshNameList compressionAlgorithmsClientToServer; + SshNameList compressionAlgorithmsServerToClient; + SshNameList languagesClientToServer; + SshNameList languagesServerToClient; + bool firstKexPacketFollows; +}; + +struct SshKeyExchangeReply +{ + QByteArray k_s; + QList<Botan::BigInt> parameters; // DSS: p, q, g, y. RSA: e, n. + Botan::BigInt f; + QByteArray signatureBlob; +}; + +struct SshDisconnect +{ + quint32 reasonCode; + QString description; + QByteArray language; +}; + +struct SshUserAuthBanner +{ + QString message; + QByteArray language; +}; + +struct SshDebug +{ + bool display; + QString message; + QByteArray language; +}; + +struct SshChannelOpenFailure +{ + quint32 localChannel; + quint32 reasonCode; + QString reasonString; + QByteArray language; +}; + +struct SshChannelOpenConfirmation +{ + quint32 localChannel; + quint32 remoteChannel; + quint32 remoteWindowSize; + quint32 remoteMaxPacketSize; +}; + +struct SshChannelWindowAdjust +{ + quint32 localChannel; + quint32 bytesToAdd; +}; + +struct SshChannelData +{ + quint32 localChannel; + QByteArray data; +}; + +struct SshChannelExtendedData +{ + quint32 localChannel; + quint32 type; + QByteArray data; +}; + +struct SshChannelExitStatus +{ + quint32 localChannel; + quint32 exitStatus; +}; + +struct SshChannelExitSignal +{ + quint32 localChannel; + QByteArray signal; + bool coreDumped; + QString error; + QByteArray language; +}; + + +class SshIncomingPacket : public AbstractSshPacket +{ +public: + SshIncomingPacket(); + + void consumeData(QByteArray &data); + void recreateKeys(const SshKeyExchange &keyExchange); + void reset(); + + SshKeyExchangeInit extractKeyExchangeInitData() const; + SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const; + SshDisconnect extractDisconnect() const; + SshUserAuthBanner extractUserAuthBanner() const; + SshDebug extractDebug() const; + + SshChannelOpenFailure extractChannelOpenFailure() const; + SshChannelOpenConfirmation extractChannelOpenConfirmation() const; + SshChannelWindowAdjust extractWindowAdjust() const; + SshChannelData extractChannelData() const; + SshChannelExtendedData extractChannelExtendedData() const; + SshChannelExitStatus extractChannelExitStatus() const; + SshChannelExitSignal extractChannelExitSignal() const; + quint32 extractRecipientChannel() const; + QByteArray extractChannelRequestType() const; + + quint32 serverSeqNr() const { return m_serverSeqNr; } + + static const QByteArray ExitStatusType; + static const QByteArray ExitSignalType; + +private: + virtual quint32 cipherBlockSize() const; + virtual quint32 macLength() const; + virtual void calculateLength() const; + + void decrypt(); + void moveFirstBytes(QByteArray &target, QByteArray &source, int n); + + quint32 m_serverSeqNr; + SshDecryptionFacility m_decrypter; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHINCOMINGPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sshkeyexchange.cpp b/src/plugins/coreplugin/ssh/sshkeyexchange.cpp new file mode 100644 index 0000000000..7875d2ecd0 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshkeyexchange.cpp @@ -0,0 +1,197 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshkeyexchange_p.h" + +#include "sshbotanconversions_p.h" +#include "sshcapabilities_p.h" +#include "sshsendfacility_p.h" +#include "sshexception_p.h" +#include "sshincomingpacket_p.h" + +#include <botan/botan.h> +#include <botan/dsa.h> +#include <botan/look_pk.h> +#include <botan/pubkey.h> +#include <botan/rsa.h> + +#include <string> + +using namespace Botan; + +namespace Core { +namespace Internal { + +namespace { + + // For debugging + void printNameList(const char *listName, const SshNameList &list) + { +#ifdef CREATOR_SSH_DEBUG + qDebug("%s:", listName); + foreach (const QByteArray &name, list.names) + qDebug("%s", name.constData()); +#else + Q_UNUSED(listName); + Q_UNUSED(list); +#endif + } +} // anonymous namespace + +SshKeyExchange::SshKeyExchange(SshSendFacility &sendFacility) + : m_sendFacility(sendFacility) +{ +} + +SshKeyExchange::~SshKeyExchange() {} + +void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId) +{ + m_serverId = serverId; + const AbstractSshPacket::Payload &payload + = m_sendFacility.sendKeyExchangeInitPacket(); + m_clientKexInitPayload = QByteArray(payload.data, payload.size); +} + +bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit) +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("server requests key exchange"); +#endif + serverKexInit.printRawBytes(); + SshKeyExchangeInit kexInitParams + = serverKexInit.extractKeyExchangeInitData(); + + printNameList("Key Algorithms", kexInitParams.keyAlgorithms); + printNameList("Server Host Key Algorithms", kexInitParams.serverHostKeyAlgorithms); + printNameList("Encryption algorithms client to server", kexInitParams.encryptionAlgorithmsClientToServer); + printNameList("Encryption algorithms server to client", kexInitParams.encryptionAlgorithmsServerToClient); + printNameList("MAC algorithms client to server", kexInitParams.macAlgorithmsClientToServer); + printNameList("MAC algorithms server to client", kexInitParams.macAlgorithmsServerToClient); + printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer); + printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer); + printNameList("Languages client to server", kexInitParams.languagesClientToServer); + printNameList("Languages server to client", kexInitParams.languagesServerToClient); +#ifdef CREATOR_SSH_DEBUG + qDebug("First packet follows: %d", kexInitParams.firstKexPacketFollows); +#endif + + const QByteArray &keyAlgo + = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods, + kexInitParams.keyAlgorithms.names); + m_serverHostKeyAlgo + = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms, + kexInitParams.serverHostKeyAlgorithms.names); + m_encryptionAlgo + = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, + kexInitParams.encryptionAlgorithmsClientToServer.names); + m_decryptionAlgo + = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, + kexInitParams.encryptionAlgorithmsServerToClient.names); + m_c2sHMacAlgo + = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, + kexInitParams.macAlgorithmsClientToServer.names); + m_s2cHMacAlgo + = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, + kexInitParams.macAlgorithmsServerToClient.names); + SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, + kexInitParams.compressionAlgorithmsClientToServer.names); + SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, + kexInitParams.compressionAlgorithmsServerToClient.names); + + AutoSeeded_RNG rng; + m_dhKey.reset(new DH_PrivateKey(rng, + DL_Group(botanKeyExchangeAlgoName(keyAlgo)))); + + const AbstractSshPacket::Payload &payload = serverKexInit.payLoad(); + m_serverKexInitPayload = QByteArray(payload.data, payload.size); + m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y()); + return kexInitParams.firstKexPacketFollows; +} + +void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, + const QByteArray &clientId) +{ + const SshKeyExchangeReply &reply + = dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo); + if (reply.f <= 0 || reply.f >= m_dhKey->group_p()) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Server sent invalid f."); + } + + QByteArray concatenatedData = AbstractSshPacket::encodeString(clientId); + concatenatedData += AbstractSshPacket::encodeString(m_serverId); + concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload); + concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload); + concatenatedData += reply.k_s; + concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y()); + concatenatedData += AbstractSshPacket::encodeMpInt(reply.f); + SymmetricKey k = m_dhKey->derive_key(reply.f); + m_k = AbstractSshPacket::encodeMpInt(BigInt(k.begin(), k.length())); + concatenatedData += m_k; + + m_hash.reset(get_hash(botanSha1Name())); + const SecureVector<byte> &hashResult + = m_hash->process(convertByteArray(concatenatedData), + concatenatedData.size()); + m_h = convertByteArray(hashResult); + + QScopedPointer<Public_Key> sigKey; + QScopedPointer<PK_Verifier> verifier; + if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) { + const DL_Group group(reply.parameters.at(0), reply.parameters.at(1), + reply.parameters.at(2)); + DSA_PublicKey * const dsaKey + = new DSA_PublicKey(group, reply.parameters.at(3)); + sigKey.reset(dsaKey); + verifier.reset(get_pk_verifier(*dsaKey, + botanEmsaAlgoName(SshCapabilities::PubKeyDss))); + } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) { + RSA_PublicKey * const rsaKey + = new RSA_PublicKey(reply.parameters.at(1), reply.parameters.at(0)); + sigKey.reset(rsaKey); + verifier.reset(get_pk_verifier(*rsaKey, + botanEmsaAlgoName(SshCapabilities::PubKeyRsa))); + } else { + Q_ASSERT(!"Impossible: Neither DSS nor RSA!"); + } + const byte * const botanH = convertByteArray(m_h); + const Botan::byte * const botanSig + = convertByteArray(reply.signatureBlob); + if (!verifier->verify_message(botanH, m_h.size(), botanSig, + reply.signatureBlob.size())) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, + "Invalid signature in SSH_MSG_KEXDH_REPLY packet."); + } + + m_sendFacility.sendNewKeysPacket(); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshkeyexchange_p.h b/src/plugins/coreplugin/ssh/sshkeyexchange_p.h new file mode 100644 index 0000000000..076f5bedd6 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshkeyexchange_p.h @@ -0,0 +1,87 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHKEYEXCHANGE_P_H +#define SSHKEYEXCHANGE_P_H + +#include <botan/dh.h> + +#include <QtCore/QByteArray> +#include <QtCore/QScopedPointer> + +namespace Botan { class HashFunction; } + +namespace Core { +namespace Internal { + +class SshSendFacility; +class SshIncomingPacket; + +class SshKeyExchange +{ +public: + SshKeyExchange(SshSendFacility &sendFacility); + ~SshKeyExchange(); + + void sendKexInitPacket(const QByteArray &serverId); + + // Returns true <=> the server sends a guessed package. + bool sendDhInitPacket(const SshIncomingPacket &serverKexInit); + + void sendNewKeysPacket(const SshIncomingPacket &dhReply, + const QByteArray &clientId); + + QByteArray k() const { return m_k; } + QByteArray h() const { return m_h; } + Botan::HashFunction *hash() const { return m_hash.data(); } + QByteArray encryptionAlgo() const { return m_encryptionAlgo; } + QByteArray decryptionAlgo() const { return m_decryptionAlgo; } + QByteArray hMacAlgoClientToServer() const { return m_c2sHMacAlgo; } + QByteArray hMacAlgoServerToClient() const { return m_s2cHMacAlgo; } + +private: + QByteArray m_serverId; + QByteArray m_clientKexInitPayload; + QByteArray m_serverKexInitPayload; + QScopedPointer<Botan::DH_PrivateKey> m_dhKey; + QByteArray m_k; + QByteArray m_h; + QByteArray m_serverHostKeyAlgo; + QByteArray m_encryptionAlgo; + QByteArray m_decryptionAlgo; + QByteArray m_c2sHMacAlgo; + QByteArray m_s2cHMacAlgo; + QScopedPointer<Botan::HashFunction> m_hash; + SshSendFacility &m_sendFacility; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHKEYEXCHANGE_P_H diff --git a/src/plugins/coreplugin/ssh/sshkeygenerator.cpp b/src/plugins/coreplugin/ssh/sshkeygenerator.cpp index 17a63886e3..976d0094c7 100644 --- a/src/plugins/coreplugin/ssh/sshkeygenerator.cpp +++ b/src/plugins/coreplugin/ssh/sshkeygenerator.cpp @@ -1,55 +1,138 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + #include "sshkeygenerator.h" -#include "ne7sshobject.h" +#include "sshbotanconversions_p.h" +#include "sshcapabilities_p.h" +#include "sshpacket_p.h" -#include <QtCore/QFile> -#include <QtCore/QTemporaryFile> +#include <botan/auto_rng.h> +#include <botan/bigint.h> +#include <botan/der_enc.h> +#include <botan/dsa.h> +#include <botan/pem.h> +#include <botan/pkcs8.h> +#include <botan/rsa.h> +#include <botan/x509_key.h> -#include <ne7ssh.h> +#include <QtCore/QDateTime> namespace Core { -SshKeyGenerator::SshKeyGenerator() -{ -} +using namespace Botan; +using namespace Internal; -bool SshKeyGenerator::generateKeys(KeyType type, const QString &id, int keySize) +SshKeyGenerator::SshKeyGenerator() { } + +bool SshKeyGenerator::generateKeys(KeyType type, PrivateKeyFormat format, + int keySize) { - QTemporaryFile tmpPubKeyFile; - QTemporaryFile tmpPrivKeyFile; - if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) { - m_error = tr("Error creating temporary files."); - return false; - } - tmpPubKeyFile.setAutoRemove(false); - tmpPubKeyFile.close(); - tmpPrivKeyFile.close(); - const char * const typeStr = type == Rsa ? "rsa" : "dsa"; - Internal::Ne7SshObject::Ptr ne7Object - = Internal::Ne7SshObject::instance()->get(); - if (!ne7Object->generateKeyPair(typeStr, id.toUtf8(), - tmpPrivKeyFile.fileName().toUtf8(), - tmpPubKeyFile.fileName().toUtf8(), keySize)) { - // TODO: Race condition on pop() call. Perhaps not use Net7 errors? Or hack API - m_error = tr("Error generating keys: %1") - .arg(ne7Object->errors()->pop()); + try { + AutoSeeded_RNG rng; + KeyPtr key; + if (type == Rsa) + key = KeyPtr(new RSA_PrivateKey(rng, keySize)); + else + key = KeyPtr(new DSA_PrivateKey(rng, DL_Group(rng, DL_Group::Strong, + keySize))); + return format == Pkcs8 + ? generatePkcs8Keys(key) : generateOpenSslKeys(key, type); + } catch (Botan::Exception &e) { + m_error = tr("Error generating key: %1").arg(e.what()); return false; } +} - if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) { - m_error = tr("Error reading temporary files."); - return false; +bool SshKeyGenerator::generatePkcs8Keys(const KeyPtr &key) +{ + generatePkcs8Key(key, false); + generatePkcs8Key(key, true); + return true; +} + +void SshKeyGenerator::generatePkcs8Key(const KeyPtr &key, bool privateKey) +{ + Pipe pipe; + pipe.start_msg(); + QByteArray *keyData; + if (privateKey) { + PKCS8::encode(*key, pipe); + keyData = &m_privateKey; + } else { + X509::encode(*key, pipe); + keyData = &m_publicKey; } + pipe.end_msg(); + keyData->resize(pipe.remaining(pipe.message_count() - 1)); + pipe.read(convertByteArray(*keyData), keyData->size(), + pipe.message_count() - 1); +} - m_publicKey = tmpPubKeyFile.readAll(); - m_privateKey = tmpPrivKeyFile.readAll(); - if (tmpPubKeyFile.error() != QFile::NoError - || tmpPrivKeyFile.error() != QFile::NoError) { - m_error = tr("Error reading temporary files."); - return false; +bool SshKeyGenerator::generateOpenSslKeys(const KeyPtr &key, KeyType type) +{ + QList<BigInt> publicParams; + QList<BigInt> allParams; + QByteArray keyId; + if (type == Rsa) { + const QSharedPointer<RSA_PrivateKey> rsaKey + = key.dynamicCast<RSA_PrivateKey>(); + publicParams << rsaKey->get_e() << rsaKey->get_n(); + allParams << rsaKey->get_n() << rsaKey->get_e() << rsaKey->get_d() + << rsaKey->get_p() << rsaKey->get_q(); + keyId = SshCapabilities::PubKeyRsa; + } else { + const QSharedPointer<DSA_PrivateKey> dsaKey + = key.dynamicCast<DSA_PrivateKey>(); + publicParams << dsaKey->group_p() << dsaKey->group_q() + << dsaKey->group_g() << dsaKey->get_y(); + allParams << publicParams << dsaKey->get_x(); + keyId = SshCapabilities::PubKeyDss; } - m_type = type; + QByteArray publicKeyBlob = AbstractSshPacket::encodeString(keyId); + foreach (const BigInt &b, publicParams) + publicKeyBlob += AbstractSshPacket::encodeMpInt(b); + publicKeyBlob = publicKeyBlob.toBase64(); + const QByteArray id = "QtCreator/" + + QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8(); + m_publicKey = keyId + ' ' + publicKeyBlob + ' ' + id; + + DER_Encoder encoder; + encoder.start_cons(SEQUENCE).encode (0U); + foreach (const BigInt &b, allParams) + encoder.encode(b); + encoder.end_cons(); + const char * const label + = type == Rsa ? "RSA PRIVATE KEY" : "DSA PRIVATE KEY"; + m_privateKey + = QByteArray(PEM_Code::encode (encoder.get_contents(), label).c_str()); return true; } diff --git a/src/plugins/coreplugin/ssh/sshkeygenerator.h b/src/plugins/coreplugin/ssh/sshkeygenerator.h index a68237e576..ada06150a0 100644 --- a/src/plugins/coreplugin/ssh/sshkeygenerator.h +++ b/src/plugins/coreplugin/ssh/sshkeygenerator.h @@ -1,10 +1,43 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + #ifndef SSHKEYGENERATOR_H #define SSHKEYGENERATOR_H #include <coreplugin/core_global.h> #include <QtCore/QCoreApplication> -#include <QtCore/QPair> +#include <QtCore/QSharedPointer> + +namespace Botan { + class Private_Key; +} namespace Core { @@ -13,19 +46,28 @@ class CORE_EXPORT SshKeyGenerator Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator) public: enum KeyType { Rsa, Dsa }; + enum PrivateKeyFormat { Pkcs8, OpenSsl }; SshKeyGenerator(); - bool generateKeys(KeyType type, const QString &id, int keySize); + bool generateKeys(KeyType type, PrivateKeyFormat format, int keySize); QString error() const { return m_error; } - QString privateKey() const { return m_privateKey; } - QString publicKey() const { return m_publicKey; } + QByteArray privateKey() const { return m_privateKey; } + QByteArray publicKey() const { return m_publicKey; } KeyType type() const { return m_type; } + PrivateKeyFormat format() const { return m_format; } private: + typedef QSharedPointer<Botan::Private_Key> KeyPtr; + + bool generatePkcs8Keys(const KeyPtr &key); + void generatePkcs8Key(const KeyPtr &key, bool privateKey); + bool generateOpenSslKeys(const KeyPtr &key, KeyType type); + QString m_error; - QString m_publicKey; - QString m_privateKey; + QByteArray m_publicKey; + QByteArray m_privateKey; KeyType m_type; + PrivateKeyFormat m_format; }; } // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp new file mode 100644 index 0000000000..c6cf99443d --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket.cpp @@ -0,0 +1,284 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshoutgoingpacket_p.h" + +#include "sshcapabilities_p.h" +#include "sshcryptofacility_p.h" + +#include <QtCore/QtEndian> + +namespace Core { +namespace Internal { + +SshOutgoingPacket::SshOutgoingPacket(const SshEncryptionFacility &encrypter, + const quint32 &seqNr) : m_encrypter(encrypter), m_seqNr(seqNr) +{ +} + +quint32 SshOutgoingPacket::cipherBlockSize() const +{ + return qMax(m_encrypter.cipherBlockSize(), 4U); +} + +quint32 SshOutgoingPacket::macLength() const +{ + return m_encrypter.macLength(); +} + +void SshOutgoingPacket::generateKeyExchangeInitPacket() +{ + const QByteArray &supportedkeyExchangeMethods + = encodeNameList(SshCapabilities::KeyExchangeMethods); + const QByteArray &supportedPublicKeyAlgorithms + = encodeNameList(SshCapabilities::PublicKeyAlgorithms); + const QByteArray &supportedEncryptionAlgorithms + = encodeNameList(SshCapabilities::EncryptionAlgorithms); + const QByteArray &supportedMacAlgorithms + = encodeNameList(SshCapabilities::MacAlgorithms); + const QByteArray &supportedCompressionAlgorithms + = encodeNameList(SshCapabilities::CompressionAlgorithms); + const QByteArray &supportedLanguages = encodeNameList(QList<QByteArray>()); + + init(SSH_MSG_KEXINIT); + m_data += m_encrypter.getRandomNumbers(16); + m_data.append(supportedkeyExchangeMethods); + m_data.append(supportedPublicKeyAlgorithms); + m_data.append(supportedEncryptionAlgorithms) + .append(supportedEncryptionAlgorithms); + m_data.append(supportedMacAlgorithms).append(supportedMacAlgorithms); + m_data.append(supportedCompressionAlgorithms) + .append(supportedCompressionAlgorithms); + m_data.append(supportedLanguages).append(supportedLanguages); + appendBool(false); // No guessed packet. + m_data.append(QByteArray(4, 0)); // Reserved. + finalize(); +} + +void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e) +{ + init(SSH_MSG_KEXDH_INIT).appendMpInt(e).finalize(); +} + +void SshOutgoingPacket::generateNewKeysPacket() +{ + init(SSH_MSG_NEWKEYS).finalize(); +} + +void SshOutgoingPacket::generateUserAuthServiceRequestPacket() +{ + generateServiceRequest("ssh-userauth"); +} + +void SshOutgoingPacket::generateServiceRequest(const QByteArray &service) +{ + init(SSH_MSG_SERVICE_REQUEST).appendString(service).finalize(); +} + +void SshOutgoingPacket::generateUserAuthByPwdRequestPacket(const QByteArray &user, + const QByteArray &service, const QByteArray &pwd) +{ + init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service) + .appendString("password").appendBool(false).appendString(pwd) + .finalize(); +} + +void SshOutgoingPacket::generateUserAuthByKeyRequestPacket(const QByteArray &user, + const QByteArray &service) +{ + init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service) + .appendString("publickey").appendBool(true) + .appendString(m_encrypter.authenticationAlgorithmName()) + .appendString(m_encrypter.authenticationPublicKey()); + const QByteArray &dataToSign = m_data.mid(PayloadOffset); + appendString(m_encrypter.authenticationKeySignature(dataToSign)); + finalize(); +} + +void SshOutgoingPacket::generateRequestFailurePacket() +{ + init(SSH_MSG_REQUEST_FAILURE).finalize(); +} + +void SshOutgoingPacket::generateSessionPacket(quint32 channelId, + quint32 windowSize, quint32 maxPacketSize) +{ + init(SSH_MSG_CHANNEL_OPEN).appendString("session").appendInt(channelId) + .appendInt(windowSize).appendInt(maxPacketSize).finalize(); +} + +void SshOutgoingPacket::generateEnvPacket(quint32 remoteChannel, + const QByteArray &var, const QByteArray &value) +{ + init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("env") + .appendBool(false).appendString(var).appendString(value); +} + +void SshOutgoingPacket::generateExecPacket(quint32 remoteChannel, + const QByteArray &command) +{ + init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel).appendString("exec") + .appendBool(true).appendString(command).finalize(); +} + +void SshOutgoingPacket::generateSftpPacket(quint32 remoteChannel) +{ + init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel) + .appendString("subsystem").appendBool(true).appendString("sftp") + .finalize(); +} + +void SshOutgoingPacket::generateWindowAdjustPacket(quint32 remoteChannel, + quint32 bytesToAdd) +{ + init(SSH_MSG_CHANNEL_WINDOW_ADJUST).appendInt(remoteChannel) + .appendInt(bytesToAdd).finalize(); +} + +void SshOutgoingPacket::generateChannelDataPacket(quint32 remoteChannel, + const QByteArray &data) +{ + init(SSH_MSG_CHANNEL_DATA).appendInt(remoteChannel).appendString(data) + .finalize(); +} + +void SshOutgoingPacket::generateChannelSignalPacket(quint32 remoteChannel, + const QByteArray &signalName) +{ + init(SSH_MSG_CHANNEL_REQUEST).appendInt(remoteChannel) + .appendString("signal").appendBool(false).appendString(signalName) + .finalize(); +} + +void SshOutgoingPacket::generateChannelEofPacket(quint32 remoteChannel) +{ + init(SSH_MSG_CHANNEL_EOF).appendInt(remoteChannel).finalize(); +} + +void SshOutgoingPacket::generateChannelClosePacket(quint32 remoteChannel) +{ + init(SSH_MSG_CHANNEL_CLOSE).appendInt(remoteChannel).finalize(); +} + +void SshOutgoingPacket::generateDisconnectPacket(SshErrorCode reason, + const QByteArray &reasonString) +{ + init(SSH_MSG_DISCONNECT).appendInt(reason).appendString(reasonString) + .appendString(QByteArray()).finalize(); +} + +void SshOutgoingPacket::generateMsgUnimplementedPacket(quint32 serverSeqNr) +{ + init(SSH_MSG_UNIMPLEMENTED).appendInt(serverSeqNr).finalize(); +} + +SshOutgoingPacket &SshOutgoingPacket::appendInt(quint32 val) +{ + m_data.append(encodeInt(val)); + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::appendMpInt(const Botan::BigInt &number) +{ + m_data.append(encodeMpInt(number)); + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::appendBool(bool b) +{ + m_data += static_cast<char>(b); + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::appendString(const QByteArray &string) +{ + m_data.append(encodeString(string)); + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::init(SshPacketType type) +{ + m_data.resize(TypeOffset + 1); + m_data[TypeOffset] = type; + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::setPadding() +{ + m_data += m_encrypter.getRandomNumbers(MinPaddingLength); + int padLength = MinPaddingLength; + const int divisor = sizeDivisor(); + const int mod = m_data.size() % divisor; + padLength += divisor - mod; + m_data += m_encrypter.getRandomNumbers(padLength - MinPaddingLength); + m_data[PaddingLengthOffset] = padLength; + return *this; +} + +SshOutgoingPacket &SshOutgoingPacket::encrypt() +{ + const QByteArray &mac + = generateMac(m_encrypter, m_seqNr); + m_encrypter.encrypt(m_data); + m_data += mac; + return *this; +} + +void SshOutgoingPacket::finalize() +{ + setPadding(); + setLengthField(m_data); + m_length = m_data.size() - 4; +#ifdef CREATOR_SSH_DEBUG + qDebug("Encrypting packet of type %u", m_data.at(TypeOffset)); +#endif + encrypt(); + Q_ASSERT(isComplete()); +} + +int SshOutgoingPacket::sizeDivisor() const +{ + return qMax(cipherBlockSize(), 8U); +} + +QByteArray SshOutgoingPacket::encodeNameList(const QList<QByteArray> &list) +{ + QByteArray data; + data.resize(4); + for (int i = 0; i < list.count(); ++i) { + if (i > 0) + data.append(','); + data.append(list.at(i)); + } + AbstractSshPacket::setLengthField(data); + return data; +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h new file mode 100644 index 0000000000..eb9c2f520d --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshoutgoingpacket_p.h @@ -0,0 +1,98 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHOUTGOINGPACKET_P_H +#define SSHOUTGOINGPACKET_P_H + +#include "sshpacket_p.h" + +namespace Core { +namespace Internal { + +class SshEncryptionFacility; + +class SshOutgoingPacket : public AbstractSshPacket +{ +public: + SshOutgoingPacket(const SshEncryptionFacility &encrypter, + const quint32 &seqNr); + + void generateKeyExchangeInitPacket(); + void generateKeyDhInitPacket(const Botan::BigInt &e); + void generateNewKeysPacket(); + void generateDisconnectPacket(SshErrorCode reason, + const QByteArray &reasonString); + void generateMsgUnimplementedPacket(quint32 serverSeqNr); + void generateUserAuthServiceRequestPacket(); + void generateUserAuthByPwdRequestPacket(const QByteArray &user, + const QByteArray &service, const QByteArray &pwd); + void generateUserAuthByKeyRequestPacket(const QByteArray &user, + const QByteArray &service); + void generateRequestFailurePacket(); + void generateSessionPacket(quint32 channelId, quint32 windowSize, + quint32 maxPacketSize); + void generateEnvPacket(quint32 remoteChannel, const QByteArray &var, + const QByteArray &value); + void generateExecPacket(quint32 remoteChannel, const QByteArray &command); + void generateSftpPacket(quint32 remoteChannel); + void generateWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd); + void generateChannelDataPacket(quint32 remoteChannel, + const QByteArray &data); + void generateChannelSignalPacket(quint32 remoteChannel, + const QByteArray &signalName); + void generateChannelEofPacket(quint32 remoteChannel); + void generateChannelClosePacket(quint32 remoteChannel); + +private: + virtual quint32 cipherBlockSize() const; + virtual quint32 macLength() const; + + static QByteArray encodeNameList(const QList<QByteArray> &list); + + void generateServiceRequest(const QByteArray &service); + + SshOutgoingPacket &init(SshPacketType type); + SshOutgoingPacket &setPadding(); + SshOutgoingPacket &encrypt(); + void finalize(); + + SshOutgoingPacket &appendInt(quint32 val); + SshOutgoingPacket &appendString(const QByteArray &string); + SshOutgoingPacket &appendMpInt(const Botan::BigInt &number); + SshOutgoingPacket &appendBool(bool b); + int sizeDivisor() const; + + const SshEncryptionFacility &m_encrypter; + const quint32 &m_seqNr; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHOUTGOINGPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sshpacket.cpp b/src/plugins/coreplugin/ssh/sshpacket.cpp new file mode 100644 index 0000000000..ff70509355 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshpacket.cpp @@ -0,0 +1,167 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshpacket_p.h" + +#include "sshcapabilities_p.h" +#include "sshcryptofacility_p.h" +#include "sshexception_p.h" +#include "sshpacketparser_p.h" + +#include <QtCore/QDebug> + +#include <cctype> + +namespace Core { +namespace Internal { + +const quint32 AbstractSshPacket::PaddingLengthOffset = 4; +const quint32 AbstractSshPacket::PayloadOffset = PaddingLengthOffset + 1; +const quint32 AbstractSshPacket::TypeOffset = PayloadOffset; +const quint32 AbstractSshPacket::MinPaddingLength = 4; + +namespace { + + void printByteArray(const QByteArray &data) + { +#ifdef CREATOR_SSH_DEBUG + for (int i = 0; i < data.count(); ++i) + qDebug() << std::hex << (static_cast<unsigned int>(data[i]) & 0xff) << " "; +#else + Q_UNUSED(data); +#endif + } +} // anonymous namespace + + +AbstractSshPacket::AbstractSshPacket() : m_length(0) { } +AbstractSshPacket::~AbstractSshPacket() {} + +bool AbstractSshPacket::isComplete() const +{ + if (currentDataSize() < minPacketSize()) + return false; + Q_ASSERT(4 + length() + macLength() >= currentDataSize()); + return 4 + length() + macLength() == currentDataSize(); +} + +void AbstractSshPacket::clear() +{ + m_data.clear(); + m_length = 0; +} + +SshPacketType AbstractSshPacket::type() const +{ + Q_ASSERT(isComplete()); + return static_cast<SshPacketType>(m_data.at(TypeOffset)); +} + +AbstractSshPacket::Payload AbstractSshPacket::payLoad() const +{ + Payload p; + p.data = m_data.constData() + PayloadOffset; + p.size = length() - paddingLength() - 1; + return p; +} + +void AbstractSshPacket::printRawBytes() const +{ + printByteArray(m_data); +} + +QByteArray AbstractSshPacket::encodeString(const QByteArray &string) +{ + QByteArray data; + data.resize(4); + data += string; + setLengthField(data); + return data; +} + +QByteArray AbstractSshPacket::encodeMpInt(const Botan::BigInt &number) +{ + if (number.is_zero()) + return QByteArray(4, 0); + + int stringLength = number.bytes(); + const bool positiveAndMsbSet = number.sign() == Botan::BigInt::Positive + && (number.byte_at(stringLength - 1) & 0x80); + if (positiveAndMsbSet) + ++stringLength; + QByteArray data; + data.resize(4 + stringLength); + int pos = 4; + if (positiveAndMsbSet) + data[pos++] = '\0'; + number.binary_encode(reinterpret_cast<Botan::byte *>(data.data()) + pos); + setLengthField(data); + return data; +} + +int AbstractSshPacket::paddingLength() const +{ + return m_data[PaddingLengthOffset]; +} + +quint32 AbstractSshPacket::length() const +{ + //Q_ASSERT(currentDataSize() >= minPacketSize()); + if (m_length == 0) + calculateLength(); + return m_length; +} + +void AbstractSshPacket::calculateLength() const +{ + m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0)); +} + +QByteArray AbstractSshPacket::generateMac(const SshAbstractCryptoFacility &crypt, + quint32 seqNr) const +{ + const quint32 seqNrBe = qToBigEndian(seqNr); + QByteArray data(reinterpret_cast<const char *>(&seqNrBe), sizeof seqNrBe); + data += QByteArray(m_data.constData(), length() + 4); + return crypt.generateMac(data, data.size()); +} + +quint32 AbstractSshPacket::minPacketSize() const +{ + return qMax<quint32>(cipherBlockSize(), 16) + macLength(); +} + +void AbstractSshPacket::setLengthField(QByteArray &data) +{ + const quint32 length = qToBigEndian(data.size() - 4); + data.replace(0, 4, reinterpret_cast<const char *>(&length), 4); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshpacket_p.h b/src/plugins/coreplugin/ssh/sshpacket_p.h new file mode 100644 index 0000000000..7120f001fd --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshpacket_p.h @@ -0,0 +1,137 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHPACKET_P_H +#define SSHPACKET_P_H + +#include "sshexception_p.h" + +#include <QtCore/QtEndian> +#include <QtCore/QByteArray> +#include <QtCore/QList> + +#include <botan/bigint.h> + +namespace Core { +namespace Internal { + +enum SshPacketType { + SSH_MSG_DISCONNECT = 1, + SSH_MSG_IGNORE = 2, + SSH_MSG_UNIMPLEMENTED = 3, + SSH_MSG_DEBUG = 4, + SSH_MSG_SERVICE_REQUEST = 5, + SSH_MSG_SERVICE_ACCEPT = 6, + + SSH_MSG_KEXINIT = 20, + SSH_MSG_NEWKEYS = 21, + SSH_MSG_KEXDH_INIT = 30, + SSH_MSG_KEXDH_REPLY = 31, + + SSH_MSG_USERAUTH_REQUEST = 50, + SSH_MSG_USERAUTH_FAILURE = 51, + SSH_MSG_USERAUTH_SUCCESS = 52, + SSH_MSG_USERAUTH_BANNER = 53, + SSH_MSG_USERAUTH_PK_OK = 60, + SSH_MSG_USERAUTH_PASSWD_CHANGEREQ = 60, + + SSH_MSG_GLOBAL_REQUEST = 80, + SSH_MSG_REQUEST_SUCCESS = 81, + SSH_MSG_REQUEST_FAILURE = 82, + + SSH_MSG_CHANNEL_OPEN = 90, + SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91, + SSH_MSG_CHANNEL_OPEN_FAILURE = 92, + SSH_MSG_CHANNEL_WINDOW_ADJUST = 93, + SSH_MSG_CHANNEL_DATA = 94, + SSH_MSG_CHANNEL_EXTENDED_DATA = 95, + SSH_MSG_CHANNEL_EOF = 96, + SSH_MSG_CHANNEL_CLOSE = 97, + SSH_MSG_CHANNEL_REQUEST = 98, + SSH_MSG_CHANNEL_SUCCESS = 99, + SSH_MSG_CHANNEL_FAILURE = 100, +}; + +enum SshExtendedDataType { SSH_EXTENDED_DATA_STDERR = 1 }; + +class SshAbstractCryptoFacility; + +class AbstractSshPacket +{ +public: + virtual ~AbstractSshPacket(); + + void clear(); + bool isComplete() const; + SshPacketType type() const; + + static QByteArray encodeString(const QByteArray &string); + static QByteArray encodeMpInt(const Botan::BigInt &number); + template<typename T> static QByteArray encodeInt(T value) + { + const T valMsb = qToBigEndian(value); + return QByteArray(reinterpret_cast<const char *>(&valMsb), sizeof valMsb); + } + + static void setLengthField(QByteArray &data); + + void printRawBytes() const; // For Debugging. + + const QByteArray &rawData() const { return m_data; } + + struct Payload { const char *data; quint32 size; }; + Payload payLoad() const; + +protected: + AbstractSshPacket(); + + virtual quint32 cipherBlockSize() const=0; + virtual quint32 macLength() const=0; + virtual void calculateLength() const; + + quint32 length() const; + int paddingLength() const; + quint32 minPacketSize() const; + quint32 currentDataSize() const { return m_data.size(); } + QByteArray generateMac(const SshAbstractCryptoFacility &crypt, + quint32 seqNr) const; + + static const quint32 PaddingLengthOffset; + static const quint32 PayloadOffset; + static const quint32 TypeOffset; + static const quint32 MinPaddingLength; + + mutable QByteArray m_data; + mutable quint32 m_length; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHPACKET_P_H diff --git a/src/plugins/coreplugin/ssh/sshpacketparser.cpp b/src/plugins/coreplugin/ssh/sshpacketparser.cpp new file mode 100644 index 0000000000..2f339c03d9 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshpacketparser.cpp @@ -0,0 +1,153 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshpacketparser_p.h" + +#include <cctype> + +namespace Core { +namespace Internal { + +namespace { quint32 size(const QByteArray &data) { return data.size(); } } + +QString SshPacketParser::asUserString(const QByteArray &rawString) +{ + QByteArray filteredString; + filteredString.resize(rawString.size()); + for (int i = 0; i < rawString.size(); ++i) { + const char c = rawString.at(i); + filteredString[i] + = std::isprint(c) || c == '\n' || c == '\r' || c == '\t' ? c : '?'; + } + return QString::fromUtf8(filteredString); +} + +bool SshPacketParser::asBool(const QByteArray &data, quint32 offset) +{ + if (size(data) <= offset) + throw SshPacketParseException(); + return data.at(offset); +} + +bool SshPacketParser::asBool(const QByteArray &data, quint32 *offset) +{ + bool b = asBool(data, *offset); + ++(*offset); + return b; +} + + +quint32 SshPacketParser::asUint32(const QByteArray &data, quint32 offset) +{ + if (size(data) < offset + 4) + throw SshPacketParseException(); + const quint32 value = ((data.at(offset) & 0xff) << 24) + + ((data.at(offset + 1) & 0xff) << 16) + + ((data.at(offset + 2) & 0xff) << 8) + (data.at(offset + 3) & 0xff); + return value; +} + +quint32 SshPacketParser::asUint32(const QByteArray &data, quint32 *offset) +{ + const quint32 v = asUint32(data, *offset); + *offset += 4; + return v; +} + +quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 offset) +{ + if (size(data) < offset + 8) + throw SshPacketParseException(); + const quint64 value = (static_cast<quint64>(data.at(offset) & 0xff) << 56) + + (static_cast<quint64>(data.at(offset + 1) & 0xff) << 48) + + (static_cast<quint64>(data.at(offset + 2) & 0xff) << 40) + + (static_cast<quint64>(data.at(offset + 3) & 0xff) << 32) + + ((data.at(offset + 4) & 0xff) << 24) + + ((data.at(offset + 5) & 0xff) << 16) + + ((data.at(offset + 6) & 0xff) << 8) + + (data.at(offset + 7) & 0xff); + return value; +} + +quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 *offset) +{ + const quint64 val = asUint64(data, *offset); + *offset += 8; + return val; +} + +QByteArray SshPacketParser::asString(const QByteArray &data, quint32 *offset) +{ + const quint32 length = asUint32(data, offset); + if (size(data) < *offset + length) + throw SshPacketParseException(); + const QByteArray &string = data.mid(*offset, length); + *offset += length; + return string; +} + +QString SshPacketParser::asUserString(const QByteArray &data, quint32 *offset) +{ + return asUserString(asString(data, offset)); +} + +SshNameList SshPacketParser::asNameList(const QByteArray &data, quint32 *offset) +{ + const quint32 length = asUint32(data, offset); + const int listEndPos = *offset + length; + if (data.size() < listEndPos) + throw SshPacketParseException(); + SshNameList names(length + 4); + int nextNameOffset = *offset; + int nextCommaOffset = data.indexOf(',', nextNameOffset); + while (nextNameOffset > 0 && nextNameOffset < listEndPos) { + const int stringEndPos = nextCommaOffset == -1 + || nextCommaOffset > listEndPos ? listEndPos : nextCommaOffset; + names.names << QByteArray(data.constData() + nextNameOffset, + stringEndPos - nextNameOffset); + nextNameOffset = nextCommaOffset + 1; + nextCommaOffset = data.indexOf(',', nextNameOffset); + } + *offset += length; + return names; +} + +Botan::BigInt SshPacketParser::asBigInt(const QByteArray &data, quint32 *offset) +{ + const quint32 length = asUint32(data, offset); + if (length == 0) + return Botan::BigInt(); + const Botan::byte *numberStart + = reinterpret_cast<const Botan::byte *>(data.constData() + *offset); + *offset += length; + return Botan::BigInt::decode(numberStart, length); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshpacketparser_p.h b/src/plugins/coreplugin/ssh/sshpacketparser_p.h new file mode 100644 index 0000000000..253f256b7b --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshpacketparser_p.h @@ -0,0 +1,81 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHPACKETPARSER_P_H +#define SSHPACKETPARSER_P_H + +#include <botan/bigint.h> + +#include <QtCore/QByteArray> +#include <QtCore/QList> +#include <QtCore/QString> + +namespace Core { +namespace Internal { + +struct SshNameList +{ + SshNameList() : originalLength(0) {} + SshNameList(quint32 originalLength) : originalLength(originalLength) {} + quint32 originalLength; + QList<QByteArray> names; +}; + +class SshPacketParseException { }; + +// This class's functions try to read a byte array at a certain offset +// as the respective chunk of data as specified in the SSH RFCs. +// If they succeed, they update the offset, so they can easily +// be called in succession by client code. +// For convenience, some have also versions that don't update the offset, +// so they can be called with rvalues if the new value is not needed. +// If they fail, they throw an SshPacketParseException. +class SshPacketParser +{ +public: + static bool asBool(const QByteArray &data, quint32 offset); + static bool asBool(const QByteArray &data, quint32 *offset); + static quint16 asUint16(const QByteArray &data, quint32 offset); + static quint16 asUint16(const QByteArray &data, quint32 *offset); + static quint64 asUint64(const QByteArray &data, quint32 offset); + static quint64 asUint64(const QByteArray &data, quint32 *offset); + static quint32 asUint32(const QByteArray &data, quint32 offset); + static quint32 asUint32(const QByteArray &data, quint32 *offset); + static QByteArray asString(const QByteArray &data, quint32 *offset); + static QString asUserString(const QByteArray &data, quint32 *offset); + static SshNameList asNameList(const QByteArray &data, quint32 *offset); + static Botan::BigInt asBigInt(const QByteArray &data, quint32 *offset); + + static QString asUserString(const QByteArray &rawString); +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHPACKETPARSER_P_H diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess.cpp b/src/plugins/coreplugin/ssh/sshremoteprocess.cpp new file mode 100644 index 0000000000..c9566e9aca --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshremoteprocess.cpp @@ -0,0 +1,270 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshremoteprocess.h" +#include "sshremoteprocess_p.h" + +#include "sshdelayedsignal_p.h" +#include "sshincomingpacket_p.h" +#include "sshsendfacility_p.h" + +#include <botan/exceptn.h> + +namespace Core { + +const QByteArray SshRemoteProcess::AbrtSignal("ABRT"); +const QByteArray SshRemoteProcess::AlrmSignal("ALRM"); +const QByteArray SshRemoteProcess::FpeSignal("FPE"); +const QByteArray SshRemoteProcess::HupSignal("HUP"); +const QByteArray SshRemoteProcess::IllSignal("ILL"); +const QByteArray SshRemoteProcess::IntSignal("INT"); +const QByteArray SshRemoteProcess::KillSignal("KILL"); +const QByteArray SshRemoteProcess::PipeSignal("PIPE"); +const QByteArray SshRemoteProcess::QuitSignal("QUIT"); +const QByteArray SshRemoteProcess::SegvSignal("SEGV"); +const QByteArray SshRemoteProcess::TermSignal("TERM"); +const QByteArray SshRemoteProcess::Usr1Signal("USR1"); +const QByteArray SshRemoteProcess::Usr2Signal("USR2"); + +SshRemoteProcess::SshRemoteProcess(const QByteArray &command, quint32 channelId, + Internal::SshSendFacility &sendFacility) + : d(new Internal::SshRemoteProcessPrivate(command, channelId, sendFacility, this)) +{ +} + +SshRemoteProcess::~SshRemoteProcess() +{ + Q_ASSERT(d->channelState() == Internal::SshRemoteProcessPrivate::Inactive + || d->channelState() == Internal::SshRemoteProcessPrivate::CloseRequested + || d->channelState() == Internal::SshRemoteProcessPrivate::Closed); + delete d; +} + +void SshRemoteProcess::addToEnvironment(const QByteArray &var, const QByteArray &value) +{ + if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) + d->m_env << qMakePair(var, value); // Cached locally and sent on start() +} + +void SshRemoteProcess::start() +{ + if (d->channelState() == Internal::SshRemoteProcessPrivate::Inactive) { +#ifdef CREATOR_SSH_DEBUG + qDebug("process start requested, channel id = %u", d->localChannelId()); +#endif + d->requestSessionStart(); + } +} + +void SshRemoteProcess::sendSignal(const QByteArray &signal) +{ + try { + if (isRunning()) + d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(), + signal); + } catch (Botan::Exception &e) { + d->setError(QString::fromAscii(e.what())); + d->closeChannel(); + } +} + +void SshRemoteProcess::closeChannel() +{ + d->closeChannel(); +} + +void SshRemoteProcess::sendInput(const QByteArray &data) +{ + if (isRunning()) + d->sendData(data); +} + +bool SshRemoteProcess::isRunning() const +{ + return d->m_procState == Internal::SshRemoteProcessPrivate::Running; +} + +QString SshRemoteProcess::errorString() const { return d->errorString(); } + +int SshRemoteProcess::exitCode() const { return d->m_exitCode; } + +QByteArray SshRemoteProcess::exitSignal() const { return d->m_signal; } + +namespace Internal { + +SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command, + quint32 channelId, SshSendFacility &sendFacility, SshRemoteProcess *proc) + : AbstractSshChannel(channelId, sendFacility), m_procState(NotYetStarted), + m_wasRunning(false), m_exitCode(0), m_command(command), m_proc(proc) +{ +} + +void SshRemoteProcessPrivate::setProcState(ProcessState newState) +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("channel: old state = %d,new state = %d", m_procState, newState); +#endif + m_procState = newState; + if (newState == StartFailed) { + createClosedSignal(SshRemoteProcess::FailedToStart); + } else if (newState == Running) { + m_wasRunning = true; + createStartedSignal(); + } +} + +void SshRemoteProcessPrivate::closeHook() +{ + if (m_wasRunning) { + if (!m_signal.isEmpty()) + createClosedSignal(SshRemoteProcess::KilledBySignal); + else + createClosedSignal(SshRemoteProcess::ExitedNormally); + } +} + +void SshRemoteProcessPrivate::handleOpenSuccessInternal() +{ + foreach (const EnvVar &envVar, m_env) { + m_sendFacility.sendEnvPacket(remoteChannel(), envVar.first, + envVar.second); + } + + m_sendFacility.sendExecPacket(remoteChannel(), m_command); + setProcState(ExecRequested); +} + +void SshRemoteProcessPrivate::handleOpenFailureInternal() +{ + setProcState(StartFailed); +} + +void SshRemoteProcessPrivate::handleChannelSuccess() +{ + if (m_procState != ExecRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_SUCCESS message."); + } + setProcState(Running); +} + +void SshRemoteProcessPrivate::handleChannelFailure() +{ + if (m_procState != ExecRequested) { + throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, + "Unexpected SSH_MSG_CHANNEL_FAILURE message."); + } + + setProcState(StartFailed); + closeChannel(); +} + +void SshRemoteProcessPrivate::handleChannelDataInternal(const QByteArray &data) +{ + createOutputAvailableSignal(data); +} + +void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data) +{ + if (type != SSH_EXTENDED_DATA_STDERR) + qWarning("Unknown extended data type %u", type); + else + createErrorOutputAvailableSignal(data); +} + +void SshRemoteProcessPrivate::handleChannelRequest(const SshIncomingPacket &packet) +{ + checkChannelActive(); + const QByteArray &requestType = packet.extractChannelRequestType(); + if (requestType == SshIncomingPacket::ExitStatusType) { + const SshChannelExitStatus status = packet.extractChannelExitStatus(); +#ifdef CREATOR_SSH_DEBUG + qDebug("Process exiting with exit code %d", status.exitStatus); +#endif + m_exitCode = status.exitStatus; + m_procState = Exited; + } else if (requestType == SshIncomingPacket::ExitSignalType) { + const SshChannelExitSignal &signal = packet.extractChannelExitSignal(); +#ifdef CREATOR_SSH_DEBUG + qDebug("Exit due to signal %s", signal.signal.data()); +#endif + setError(signal.error); + m_signal = signal.signal; + m_procState = Exited; + } else { + qWarning("Ignoring unknown request type '%s'", requestType.data()); + } +} + +void SshRemoteProcessPrivate::createStartedSignal() +{ + new SshRemoteProcessStartedSignal(this, QWeakPointer<SshRemoteProcess>(m_proc)); +} + +void SshRemoteProcessPrivate::emitStartedSignal() +{ + emit m_proc->started(); +} + +void SshRemoteProcessPrivate::createOutputAvailableSignal(const QByteArray &output) +{ + new SshRemoteProcessOutputAvailableSignal(this, + QWeakPointer<SshRemoteProcess>(m_proc), output); +} + +void SshRemoteProcessPrivate::emitOutputAvailableSignal(const QByteArray &output) +{ + emit m_proc->outputAvailable(output); +} + +void SshRemoteProcessPrivate::createErrorOutputAvailableSignal(const QByteArray &output) +{ + new SshRemoteProcessErrorOutputAvailableSignal(this, + QWeakPointer<SshRemoteProcess>(m_proc), output); +} + +void SshRemoteProcessPrivate::emitErrorOutputAvailableSignal(const QByteArray &output) +{ + emit m_proc->errorOutputAvailable(output); +} + +void SshRemoteProcessPrivate::createClosedSignal(int exitStatus) +{ + new SshRemoteProcessClosedSignal(this, + QWeakPointer<SshRemoteProcess>(m_proc), exitStatus); +} + +void SshRemoteProcessPrivate::emitClosedSignal(int exitStatus) +{ + emit m_proc->closed(exitStatus); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess.h b/src/plugins/coreplugin/ssh/sshremoteprocess.h new file mode 100644 index 0000000000..941894990f --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshremoteprocess.h @@ -0,0 +1,130 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHREMOTECOMMAND_H +#define SSHREMOTECOMMAND_H + +#include <coreplugin/core_global.h> + +#include <QtCore/QObject> +#include <QtCore/QSharedPointer> + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace Core { +namespace Internal { +class SshChannelManager; +class SshRemoteProcessPrivate; +class SshSendFacility; +} // namespace Internal + + +/* + * This class implements an SSH channel for running a remote process. + * Objects are created via SshConnection::createRemoteProcess. + * The process is started via the start() member function. + * A closeChannel() function is provided, but rarely useful, because + * a) when the process ends, the channel is closed automatically, and + * b) closing a channel will not necessarily kill the remote process. + * Therefore, the only sensible use case for calling closeChannel() is to + * get rid of an SshRemoteProces object before the process is actually started. + * Note that the process does not have a terminal, so you can't use it + * for applications that require one. + */ +class CORE_EXPORT SshRemoteProcess : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(SshRemoteProcess) + + friend class Internal::SshChannelManager; + friend class Internal::SshRemoteProcessPrivate; + +public: + typedef QSharedPointer<SshRemoteProcess> Ptr; + enum ExitStatus { FailedToStart, KilledBySignal, ExitedNormally }; + + static const QByteArray AbrtSignal; + static const QByteArray AlrmSignal; + static const QByteArray FpeSignal; + static const QByteArray HupSignal; + static const QByteArray IllSignal; + static const QByteArray IntSignal; + static const QByteArray KillSignal; + static const QByteArray PipeSignal; + static const QByteArray QuitSignal; + static const QByteArray SegvSignal; + static const QByteArray TermSignal; + static const QByteArray Usr1Signal; + static const QByteArray Usr2Signal; + + ~SshRemoteProcess(); + + /* + * Note that this is of limited value in practice, because servers are + * usually configured to ignore such requests for security reasons. + */ + void addToEnvironment(const QByteArray &var, const QByteArray &value); + + void start(); + void closeChannel(); + + bool isRunning() const; + QString errorString() const; + int exitCode() const; + QByteArray exitSignal() const; + + // Note: This is ignored by the OpenSSH server. + void sendSignal(const QByteArray &signal); + void kill() { sendSignal(KillSignal); } + + void sendInput(const QByteArray &data); // Should usually have a trailing newline. + +signals: + void started(); + void outputAvailable(const QByteArray &output); + void errorOutputAvailable(const QByteArray &output); + + /* + * Parameter is of type ExitStatus, but we use int because of + * signal/slot awkwardness (full namespace required). + */ + void closed(int exitStatus); + +private: + SshRemoteProcess(const QByteArray &command, quint32 channelId, + Internal::SshSendFacility &sendFacility); + + Internal::SshRemoteProcessPrivate *d; +}; + +} // namespace Core + +#endif // SSHREMOTECOMMAND_H diff --git a/src/plugins/coreplugin/ssh/sshremoteprocess_p.h b/src/plugins/coreplugin/ssh/sshremoteprocess_p.h new file mode 100644 index 0000000000..951ca24731 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshremoteprocess_p.h @@ -0,0 +1,96 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHREMOTEPROCESS_P_H +#define SSHREMOTEPROCESS_P_H + +#include "sshchannel_p.h" + +#include <QtCore/QList> +#include <QtCore/QPair> + +namespace Core { +class SshRemoteProcess; + +namespace Internal { +class SshSendFacility; + +class SshRemoteProcessPrivate : public AbstractSshChannel +{ + friend class Core::SshRemoteProcess; +public: + enum ProcessState { + NotYetStarted, ExecRequested, StartFailed,Running, Exited + }; + + virtual void handleChannelSuccess(); + virtual void handleChannelFailure(); + + virtual void closeHook(); + + void emitStartedSignal(); + void emitOutputAvailableSignal(const QByteArray &output); + void emitErrorOutputAvailableSignal(const QByteArray &output); + void emitClosedSignal(int exitStatus); + +private: + SshRemoteProcessPrivate(const QByteArray &command, quint32 channelId, + SshSendFacility &sendFacility, SshRemoteProcess *proc); + + virtual void handleOpenSuccessInternal(); + virtual void handleOpenFailureInternal(); + virtual void handleChannelDataInternal(const QByteArray &data); + virtual void handleChannelExtendedDataInternal(quint32 type, + const QByteArray &data); + virtual void handleChannelRequest(const SshIncomingPacket &packet); + + void setProcState(ProcessState newState); + + void createStartedSignal(); + void createOutputAvailableSignal(const QByteArray &output); + void createErrorOutputAvailableSignal(const QByteArray &output); + void createClosedSignal(int exitStatus); + + ProcessState m_procState; + bool m_wasRunning; + QByteArray m_signal; + int m_exitCode; + + const QByteArray m_command; + + typedef QPair<QByteArray, QByteArray> EnvVar; + QList<EnvVar> m_env; + + SshRemoteProcess *m_proc; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHREMOTEPROCESS_P_H diff --git a/src/plugins/coreplugin/ssh/sshsendfacility.cpp b/src/plugins/coreplugin/ssh/sshsendfacility.cpp new file mode 100644 index 0000000000..3c793af24d --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshsendfacility.cpp @@ -0,0 +1,191 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "sshsendfacility_p.h" + +#include "sshkeyexchange_p.h" +#include "sshoutgoingpacket_p.h" + +#include <QtNetwork/QTcpSocket> + +namespace Core { +namespace Internal { + +SshSendFacility::SshSendFacility(QTcpSocket *socket) + : m_clientSeqNr(0), m_socket(socket), + m_outgoingPacket(m_encrypter, m_clientSeqNr) +{ +} + +void SshSendFacility::sendPacket() +{ +#ifdef CREATOR_SSH_DEBUG + qDebug("Sending packet, client seq nr is %u", m_clientSeqNr); +#endif + m_socket->write(m_outgoingPacket.rawData()); + ++m_clientSeqNr; +} + +void SshSendFacility::reset() +{ + m_clientSeqNr = 0; + m_encrypter.clearKeys(); +} + +void SshSendFacility::recreateKeys(const SshKeyExchange &keyExchange) +{ + m_encrypter.recreateKeys(keyExchange); +} + +void SshSendFacility::createAuthenticationKey(const QByteArray &privKeyFileContents) +{ + m_encrypter.createAuthenticationKey(privKeyFileContents); +} + +SshOutgoingPacket::Payload SshSendFacility::sendKeyExchangeInitPacket() +{ + m_outgoingPacket.generateKeyExchangeInitPacket(); + sendPacket(); + return m_outgoingPacket.payLoad(); +} + +void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e) +{ + m_outgoingPacket.generateKeyDhInitPacket(e); + sendPacket(); +} + +void SshSendFacility::sendNewKeysPacket() +{ + m_outgoingPacket.generateNewKeysPacket(); + sendPacket(); +} + +void SshSendFacility::sendDisconnectPacket(SshErrorCode reason, + const QByteArray &reasonString) +{ + m_outgoingPacket.generateDisconnectPacket(reason, reasonString); + sendPacket(); + } + +void SshSendFacility::sendMsgUnimplementedPacket(quint32 serverSeqNr) +{ + m_outgoingPacket.generateMsgUnimplementedPacket(serverSeqNr); + sendPacket(); +} + +void SshSendFacility::sendUserAuthServiceRequestPacket() +{ + m_outgoingPacket.generateUserAuthServiceRequestPacket(); + sendPacket(); +} + +void SshSendFacility::sendUserAuthByPwdRequestPacket(const QByteArray &user, + const QByteArray &service, const QByteArray &pwd) +{ + m_outgoingPacket.generateUserAuthByPwdRequestPacket(user, service, pwd); + sendPacket(); + } + +void SshSendFacility::sendUserAuthByKeyRequestPacket(const QByteArray &user, + const QByteArray &service) +{ + m_outgoingPacket.generateUserAuthByKeyRequestPacket(user, service); + sendPacket(); +} + +void SshSendFacility::sendRequestFailurePacket() +{ + m_outgoingPacket.generateRequestFailurePacket(); + sendPacket(); +} + +void SshSendFacility::sendSessionPacket(quint32 channelId, quint32 windowSize, + quint32 maxPacketSize) +{ + m_outgoingPacket.generateSessionPacket(channelId, windowSize, + maxPacketSize); + sendPacket(); +} + +void SshSendFacility::sendEnvPacket(quint32 remoteChannel, + const QByteArray &var, const QByteArray &value) +{ + m_outgoingPacket.generateEnvPacket(remoteChannel, var, value); + sendPacket(); +} + +void SshSendFacility::sendExecPacket(quint32 remoteChannel, + const QByteArray &command) +{ + m_outgoingPacket.generateExecPacket(remoteChannel, command); + sendPacket(); +} + +void SshSendFacility::sendSftpPacket(quint32 remoteChannel) +{ + m_outgoingPacket.generateSftpPacket(remoteChannel); + sendPacket(); +} + +void SshSendFacility::sendWindowAdjustPacket(quint32 remoteChannel, + quint32 bytesToAdd) +{ + m_outgoingPacket.generateWindowAdjustPacket(remoteChannel, bytesToAdd); + sendPacket(); +} + +void SshSendFacility::sendChannelDataPacket(quint32 remoteChannel, + const QByteArray &data) +{ + m_outgoingPacket.generateChannelDataPacket(remoteChannel, data); + sendPacket(); +} + +void SshSendFacility::sendChannelSignalPacket(quint32 remoteChannel, + const QByteArray &signalName) +{ + m_outgoingPacket.generateChannelSignalPacket(remoteChannel, signalName); + sendPacket(); +} + +void SshSendFacility::sendChannelEofPacket(quint32 remoteChannel) +{ + m_outgoingPacket.generateChannelEofPacket(remoteChannel); + sendPacket(); +} + +void SshSendFacility::sendChannelClosePacket(quint32 remoteChannel) +{ + m_outgoingPacket.generateChannelClosePacket(remoteChannel); + sendPacket(); +} + +} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/ssh/sshsendfacility_p.h b/src/plugins/coreplugin/ssh/sshsendfacility_p.h new file mode 100644 index 0000000000..6f1cdf76f3 --- /dev/null +++ b/src/plugins/coreplugin/ssh/sshsendfacility_p.h @@ -0,0 +1,90 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SSHCONNECTIONOUTSTATE_P_H +#define SSHCONNECTIONOUTSTATE_P_H + +#include "sshcryptofacility_p.h" +#include "sshoutgoingpacket_p.h" + +QT_BEGIN_NAMESPACE +class QTcpSocket; +QT_END_NAMESPACE + + +namespace Core { +namespace Internal { +class SshKeyExchange; + +class SshSendFacility +{ +public: + SshSendFacility(QTcpSocket *socket); + void reset(); + void recreateKeys(const SshKeyExchange &keyExchange); + void createAuthenticationKey(const QByteArray &privKeyFileContents); + + SshOutgoingPacket::Payload sendKeyExchangeInitPacket(); + void sendKeyDhInitPacket(const Botan::BigInt &e); + void sendNewKeysPacket(); + void sendDisconnectPacket(SshErrorCode reason, + const QByteArray &reasonString); + void sendMsgUnimplementedPacket(quint32 serverSeqNr); + void sendUserAuthServiceRequestPacket(); + void sendUserAuthByPwdRequestPacket(const QByteArray &user, + const QByteArray &service, const QByteArray &pwd); + void sendUserAuthByKeyRequestPacket(const QByteArray &user, + const QByteArray &service); + void sendRequestFailurePacket(); + void sendSessionPacket(quint32 channelId, quint32 windowSize, + quint32 maxPacketSize); + void sendEnvPacket(quint32 remoteChannel, const QByteArray &var, + const QByteArray &value); + void sendExecPacket(quint32 remoteChannel, const QByteArray &command); + void sendSftpPacket(quint32 remoteChannel); + void sendWindowAdjustPacket(quint32 remoteChannel, quint32 bytesToAdd); + void sendChannelDataPacket(quint32 remoteChannel, const QByteArray &data); + void sendChannelSignalPacket(quint32 remoteChannel, + const QByteArray &signalName); + void sendChannelEofPacket(quint32 remoteChannel); + void sendChannelClosePacket(quint32 remoteChannel); + +private: + void sendPacket(); + + quint32 m_clientSeqNr; + SshEncryptionFacility m_encrypter; + QTcpSocket *m_socket; + SshOutgoingPacket m_outgoingPacket; +}; + +} // namespace Internal +} // namespace Core + +#endif // SSHCONNECTIONOUTSTATE_P_H diff --git a/src/plugins/cpaster/cpasterplugin.cpp b/src/plugins/cpaster/cpasterplugin.cpp index 626f1f1b3a..0756b1b77d 100644 --- a/src/plugins/cpaster/cpasterplugin.cpp +++ b/src/plugins/cpaster/cpasterplugin.cpp @@ -144,7 +144,7 @@ void CodepasterPlugin::extensionsInitialized() { } -void CodepasterPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag CodepasterPlugin::aboutToShutdown() { // Delete temporary, fetched files foreach(const QString &fetchedSnippet, m_fetchedSnippets) { @@ -152,6 +152,7 @@ void CodepasterPlugin::aboutToShutdown() if (file.exists()) file.remove(); } + return SynchronousShutdown; } void CodepasterPlugin::postEditor() diff --git a/src/plugins/cpaster/cpasterplugin.h b/src/plugins/cpaster/cpasterplugin.h index b75158b0c9..6dcc8a902b 100644 --- a/src/plugins/cpaster/cpasterplugin.h +++ b/src/plugins/cpaster/cpasterplugin.h @@ -55,7 +55,7 @@ public: virtual bool initialize(const QStringList &arguments, QString *error_message); virtual void extensionsInitialized(); - virtual void aboutToShutdown(); + virtual ShutdownFlag aboutToShutdown(); public slots: void postEditor(); diff --git a/src/plugins/cppeditor/cppcheckundefinedsymbols.cpp b/src/plugins/cppeditor/cppchecksymbols.cpp index 0e16c51bb8..0e9af30b39 100644 --- a/src/plugins/cppeditor/cppcheckundefinedsymbols.cpp +++ b/src/plugins/cppeditor/cppchecksymbols.cpp @@ -27,7 +27,7 @@ ** **************************************************************************/ -#include "cppcheckundefinedsymbols.h" +#include "cppchecksymbols.h" #include <cplusplus/Overview.h> #include <Names.h> @@ -117,8 +117,8 @@ protected: return; } else if (const QualifiedNameId *q = name->asQualifiedNameId()) { - for (unsigned i = 0; i < q->nameCount(); ++i) - addType(q->nameAt(i)); + addType(q->base()); + addType(q->name()); } else if (name->isNameId() || name->isTemplateNameId()) { addType(name->identifier()); @@ -255,13 +255,13 @@ protected: } // end of anonymous namespace -CheckUndefinedSymbols::Future CheckUndefinedSymbols::go(Document::Ptr doc, const LookupContext &context) +CheckSymbols::Future CheckSymbols::go(Document::Ptr doc, const LookupContext &context) { Q_ASSERT(doc); - return (new CheckUndefinedSymbols(doc, context))->start(); + return (new CheckSymbols(doc, context))->start(); } -CheckUndefinedSymbols::CheckUndefinedSymbols(Document::Ptr doc, const LookupContext &context) +CheckSymbols::CheckSymbols(Document::Ptr doc, const LookupContext &context) : ASTVisitor(doc->translationUnit()), _doc(doc), _context(context) { _fileName = doc->fileName(); @@ -270,10 +270,10 @@ CheckUndefinedSymbols::CheckUndefinedSymbols(Document::Ptr doc, const LookupCont _scopes = collectTypes.scopes(); } -CheckUndefinedSymbols::~CheckUndefinedSymbols() +CheckSymbols::~CheckSymbols() { } -void CheckUndefinedSymbols::run() +void CheckSymbols::run() { if (! isCanceled()) runFunctor(); @@ -281,7 +281,7 @@ void CheckUndefinedSymbols::run() reportFinished(); } -void CheckUndefinedSymbols::runFunctor() +void CheckSymbols::runFunctor() { _diagnosticMessages.clear(); @@ -291,14 +291,14 @@ void CheckUndefinedSymbols::runFunctor() } } -bool CheckUndefinedSymbols::warning(unsigned line, unsigned column, const QString &text, unsigned length) +bool CheckSymbols::warning(unsigned line, unsigned column, const QString &text, unsigned length) { Document::DiagnosticMessage m(Document::DiagnosticMessage::Warning, _fileName, line, column, text, length); _diagnosticMessages.append(m); return false; } -bool CheckUndefinedSymbols::warning(AST *ast, const QString &text) +bool CheckSymbols::warning(AST *ast, const QString &text) { const Token &firstToken = tokenAt(ast->firstToken()); const Token &lastToken = tokenAt(ast->lastToken() - 1); @@ -311,7 +311,7 @@ bool CheckUndefinedSymbols::warning(AST *ast, const QString &text) return false; } -bool CheckUndefinedSymbols::preVisit(AST *) +bool CheckSymbols::preVisit(AST *) { if (isCanceled()) return false; @@ -319,7 +319,7 @@ bool CheckUndefinedSymbols::preVisit(AST *) return true; } -bool CheckUndefinedSymbols::visit(NamespaceAST *ast) +bool CheckSymbols::visit(NamespaceAST *ast) { if (ast->identifier_token) { const Token &tok = tokenAt(ast->identifier_token); @@ -334,22 +334,22 @@ bool CheckUndefinedSymbols::visit(NamespaceAST *ast) return true; } -bool CheckUndefinedSymbols::visit(UsingDirectiveAST *) +bool CheckSymbols::visit(UsingDirectiveAST *) { return true; } -bool CheckUndefinedSymbols::visit(SimpleDeclarationAST *) +bool CheckSymbols::visit(SimpleDeclarationAST *) { return true; } -bool CheckUndefinedSymbols::visit(NamedTypeSpecifierAST *) +bool CheckSymbols::visit(NamedTypeSpecifierAST *) { return true; } -void CheckUndefinedSymbols::checkNamespace(NameAST *name) +void CheckSymbols::checkNamespace(NameAST *name) { if (! name) return; @@ -369,9 +369,9 @@ void CheckUndefinedSymbols::checkNamespace(NameAST *name) warning(line, column, QCoreApplication::translate("CheckUndefinedSymbols", "Expected a namespace-name"), length); } -void CheckUndefinedSymbols::checkName(NameAST *ast) +void CheckSymbols::checkName(NameAST *ast) { - if (ast->name) { + if (ast && ast->name) { if (const Identifier *ident = ast->name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialTypes.contains(id)) { @@ -383,25 +383,25 @@ void CheckUndefinedSymbols::checkName(NameAST *ast) } } -bool CheckUndefinedSymbols::visit(SimpleNameAST *ast) +bool CheckSymbols::visit(SimpleNameAST *ast) { checkName(ast); return true; } -bool CheckUndefinedSymbols::visit(TemplateIdAST *ast) +bool CheckSymbols::visit(TemplateIdAST *ast) { checkName(ast); return true; } -bool CheckUndefinedSymbols::visit(DestructorNameAST *ast) +bool CheckSymbols::visit(DestructorNameAST *ast) { checkName(ast); return true; } -bool CheckUndefinedSymbols::visit(QualifiedNameAST *ast) +bool CheckSymbols::visit(QualifiedNameAST *ast) { if (ast->name) { Scope *scope = findScope(ast); @@ -439,7 +439,7 @@ bool CheckUndefinedSymbols::visit(QualifiedNameAST *ast) return false; } -bool CheckUndefinedSymbols::visit(TypenameTypeParameterAST *ast) +bool CheckSymbols::visit(TypenameTypeParameterAST *ast) { if (ast->name && ast->name->name) { if (const Identifier *templId = ast->name->name->identifier()) { @@ -454,24 +454,24 @@ bool CheckUndefinedSymbols::visit(TypenameTypeParameterAST *ast) return true; } -bool CheckUndefinedSymbols::visit(TemplateTypeParameterAST *ast) +bool CheckSymbols::visit(TemplateTypeParameterAST *ast) { checkName(ast->name); return true; } -bool CheckUndefinedSymbols::visit(TemplateDeclarationAST *ast) +bool CheckSymbols::visit(TemplateDeclarationAST *ast) { _templateDeclarationStack.append(ast); return true; } -void CheckUndefinedSymbols::endVisit(TemplateDeclarationAST *) +void CheckSymbols::endVisit(TemplateDeclarationAST *) { _templateDeclarationStack.takeFirst(); } -void CheckUndefinedSymbols::addTypeUsage(const Use &use) +void CheckSymbols::addTypeUsage(const Use &use) { _typeUsages.append(use); @@ -479,7 +479,7 @@ void CheckUndefinedSymbols::addTypeUsage(const Use &use) flush(); } -void CheckUndefinedSymbols::addTypeUsage(ClassOrNamespace *b, NameAST *ast) +void CheckSymbols::addTypeUsage(ClassOrNamespace *b, NameAST *ast) { if (! b) return; @@ -500,7 +500,7 @@ void CheckUndefinedSymbols::addTypeUsage(ClassOrNamespace *b, NameAST *ast) //qDebug() << "added use" << oo(ast->name) << line << column << length; } -void CheckUndefinedSymbols::addTypeUsage(const QList<Symbol *> &candidates, NameAST *ast) +void CheckSymbols::addTypeUsage(const QList<Symbol *> &candidates, NameAST *ast) { unsigned startToken = ast->firstToken(); if (DestructorNameAST *dtor = ast->asDestructorName()) @@ -530,7 +530,7 @@ void CheckUndefinedSymbols::addTypeUsage(const QList<Symbol *> &candidates, Name } } -unsigned CheckUndefinedSymbols::startOfTemplateDeclaration(TemplateDeclarationAST *ast) const +unsigned CheckSymbols::startOfTemplateDeclaration(TemplateDeclarationAST *ast) const { if (ast->declaration) { if (TemplateDeclarationAST *templ = ast->declaration->asTemplateDeclaration()) @@ -542,7 +542,7 @@ unsigned CheckUndefinedSymbols::startOfTemplateDeclaration(TemplateDeclarationAS return ast->firstToken(); } -Scope *CheckUndefinedSymbols::findScope(AST *ast) const +Scope *CheckSymbols::findScope(AST *ast) const { Scope *scope = 0; @@ -561,7 +561,7 @@ Scope *CheckUndefinedSymbols::findScope(AST *ast) const return scope; } -void CheckUndefinedSymbols::flush() +void CheckSymbols::flush() { if (_typeUsages.isEmpty()) return; diff --git a/src/plugins/cppeditor/cppcheckundefinedsymbols.h b/src/plugins/cppeditor/cppchecksymbols.h index 4dbc0b52f3..1e5d74c1b3 100644 --- a/src/plugins/cppeditor/cppcheckundefinedsymbols.h +++ b/src/plugins/cppeditor/cppchecksymbols.h @@ -27,8 +27,8 @@ ** **************************************************************************/ -#ifndef CPLUSPLUS_CHECKUNDEFINEDSYMBOLS_H -#define CPLUSPLUS_CHECKUNDEFINEDSYMBOLS_H +#ifndef CPLUSPLUS_CHECKSYMBOLS_H +#define CPLUSPLUS_CHECKSYMBOLS_H #include "cppsemanticinfo.h" @@ -41,12 +41,12 @@ namespace CPlusPlus { -class CheckUndefinedSymbols: +class CheckSymbols: protected ASTVisitor, public QtConcurrent::RunFunctionTaskBase<CppEditor::Internal::SemanticInfo::Use> { public: - virtual ~CheckUndefinedSymbols(); + virtual ~CheckSymbols(); typedef CppEditor::Internal::SemanticInfo::Use Use; @@ -56,11 +56,27 @@ public: typedef QFuture<Use> Future; static Future go(Document::Ptr doc, const LookupContext &context); + static QMap<int, QVector<Use> > chunks(const QFuture<Use> &future, int from, int to) + { + QMap<int, QVector<Use> > chunks; + + for (int i = from; i < to; ++i) { + const Use use = future.resultAt(i); + if (! use.line) + continue; // skip it, it's an invalid use. + + const int blockNumber = use.line - 1; + chunks[blockNumber].append(use); + } + + return chunks; + } + protected: using ASTVisitor::visit; using ASTVisitor::endVisit; - CheckUndefinedSymbols(Document::Ptr doc, const LookupContext &context); + CheckSymbols(Document::Ptr doc, const LookupContext &context); bool warning(unsigned line, unsigned column, const QString &text, unsigned length = 0); bool warning(AST *ast, const QString &text); @@ -107,4 +123,4 @@ private: } // end of namespace CPlusPlus -#endif // CPLUSPLUS_CHECKUNDEFINEDSYMBOLS_H +#endif // CPLUSPLUS_CHECKSYMBOLS_H diff --git a/src/plugins/cppeditor/cppdeclfromdef.cpp b/src/plugins/cppeditor/cppdeclfromdef.cpp new file mode 100644 index 0000000000..23a11d7a50 --- /dev/null +++ b/src/plugins/cppeditor/cppdeclfromdef.cpp @@ -0,0 +1,250 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cppdeclfromdef.h" + +#include <Literals.h> //### remove +#include <QDebug> //###remove + +#include <AST.h> +#include <ASTVisitor.h> +#include <CoreTypes.h> +#include <Names.h> +#include <Symbols.h> +#include <TranslationUnit.h> +#include <cplusplus/ASTPath.h> +#include <cplusplus/LookupContext.h> +#include <cplusplus/Overview.h> + +#include <QtCore/QCoreApplication> + +using namespace CPlusPlus; +using namespace CppEditor::Internal; +using namespace CppTools; + +using CppEditor::CppRefactoringChanges; + +namespace { + +class InsertionPointFinder: public ASTVisitor +{ +public: + InsertionPointFinder(Document::Ptr doc, const QString &className) + : ASTVisitor(doc->translationUnit()) + , _doc(doc) + , _className(className) + {} + + void operator()(int *line, int *column) + { + if (!line && !column) + return; + _line = 0; + _column = 0; + + AST *ast = translationUnit()->ast(); + accept(ast); + + if (line) + *line = _line - 1; + if (column) + *column = _column - 1; + } + +protected: + using ASTVisitor::visit; + + bool visit(ClassSpecifierAST *ast) + { + if (!ast->symbol || _className != QLatin1String(ast->symbol->identifier()->chars())) + return true; + + unsigned currentVisibility = (tokenKind(ast->classkey_token) == T_CLASS) ? T_PUBLIC : T_PRIVATE; + unsigned insertBefore = 0; + + for (DeclarationListAST *iter = ast->member_specifier_list; iter; iter = iter->next) { + DeclarationAST *decl = iter->value; + if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) { + const unsigned token = xsDecl->access_specifier_token; + const int kind = tokenKind(token); + if (kind == T_PUBLIC) { + currentVisibility = T_PUBLIC; + } else if (kind == T_PROTECTED) { + if (currentVisibility == T_PUBLIC) { + insertBefore = token; + break; + } else { + currentVisibility = T_PROTECTED; + } + } else if (kind == T_PRIVATE) { + if (currentVisibility == T_PUBLIC + || currentVisibility == T_PROTECTED) { + insertBefore = token; + break; + } else { + currentVisibility = T_PRIVATE; + } + } + } + } + + if (!insertBefore) + insertBefore = ast->rbrace_token; + + getTokenStartPosition(insertBefore, &_line, &_column); + + return false; + } + +private: + Document::Ptr _doc; + QString _className; + + unsigned _line; + unsigned _column; +}; + +QString prettyMinimalType(const FullySpecifiedType &ty, + const LookupContext &context, + Scope *source, + ClassOrNamespace *target) +{ + Overview oo; + + if (const NamedType *namedTy = ty->asNamedType()) + return oo.prettyTypeWithName(ty, context.minimalName(namedTy->name(), + source, + target), + context.control().data()); + else + return oo(ty); +} + +} // anonymous namespace + +DeclFromDef::DeclFromDef(TextEditor::BaseTextEditor *editor) + : CppQuickFixOperation(editor) +{} + +QString DeclFromDef::description() const +{ + return QCoreApplication::tr("Create Declaration from Definition", "DeclFromDef"); +} + +int DeclFromDef::match(const QList<CPlusPlus::AST *> &path) +{ + m_targetFileName.clear(); + m_targetSymbolName.clear(); + m_decl.clear(); + + FunctionDefinitionAST *funDef = 0; + int idx = 0; + for (; idx < path.size(); ++idx) { + AST *node = path.at(idx); + if (FunctionDefinitionAST *candidate = node->asFunctionDefinition()) { + if (!funDef) + funDef = candidate; + } else if (node->asClassSpecifier()) { + return -1; + } + } + + if (!funDef || !funDef->symbol) + return -1; + + Function *method = funDef->symbol; + LookupContext context(document(), snapshot()); + + if (ClassOrNamespace *targetBinding = context.lookupParent(method)) { + foreach (Symbol *s, targetBinding->symbols()) { + if (Class *clazz = s->asClass()) { + m_targetFileName = QLatin1String(clazz->fileName()); + m_targetSymbolName = QLatin1String(clazz->identifier()->chars()); + + m_decl = generateDeclaration(method, targetBinding); + + return idx; + } // ### TODO: support insertion into namespaces + } + } + + return -1; +} + +void DeclFromDef::createChanges() +{ + CppRefactoringChanges *changes = refactoringChanges(); + + Document::Ptr targetDoc = changes->document(m_targetFileName); + InsertionPointFinder findInsertionPoint(targetDoc, m_targetSymbolName); + int line = 0, column = 0; + findInsertionPoint(&line, &column); + + int targetPosition1 = changes->positionInFile(m_targetFileName, line, column); + int targetPosition2 = changes->positionInFile(m_targetFileName, line + 1, 0) - 1; + + Utils::ChangeSet target; + target.insert(targetPosition1, m_decl); + changes->changeFile(m_targetFileName, target); + + changes->reindent(m_targetFileName, + Utils::ChangeSet::Range(targetPosition1, targetPosition2)); + + changes->openEditor(m_targetFileName, line, column); +} + +QString DeclFromDef::generateDeclaration(Function *method, ClassOrNamespace *targetBinding) +{ + LookupContext context(document(), snapshot()); + Overview oo; + QString decl; + + decl.append(prettyMinimalType(method->returnType(), + context, + method->scope(), + targetBinding)); + + decl.append(QLatin1Char(' ')); + decl.append(QLatin1String(method->name()->identifier()->chars())); + decl.append(QLatin1Char('(')); + for (unsigned argIdx = 0; argIdx < method->argumentCount(); ++argIdx) { + if (argIdx > 0) + decl.append(QLatin1String(", ")); + Argument *arg = method->argumentAt(argIdx)->asArgument(); + decl.append(prettyMinimalType(arg->type(), + context, + method->members(), + targetBinding)); + decl.append(QLatin1Char(' ')); + decl.append(oo(arg->name())); + } + decl.append(QLatin1String(");\n\n")); + + return decl; +} diff --git a/src/plugins/cppeditor/cppdeclfromdef.h b/src/plugins/cppeditor/cppdeclfromdef.h new file mode 100644 index 0000000000..8cbebc9271 --- /dev/null +++ b/src/plugins/cppeditor/cppdeclfromdef.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CPPDECLFROMDEF_H +#define CPPDECLFROMDEF_H + +#include "cppquickfix.h" + +#include <CPlusPlusForwardDeclarations.h> + +namespace CPlusPlus { +class ClassOrNamespace; +} // namespace CPlusPlus + +namespace CppEditor { +namespace Internal { + +class DeclFromDef: public CppQuickFixOperation +{ +public: + DeclFromDef(TextEditor::BaseTextEditor *editor); + + virtual int match(const QList<CPlusPlus::AST *> &path); + virtual QString description() const; + virtual void createChanges(); + +protected: + virtual QString generateDeclaration(CPlusPlus::Function *method, + CPlusPlus::ClassOrNamespace *targetBinding); + +private: + QString m_targetFileName; + QString m_targetSymbolName; + QString m_decl; +}; + + +} // namespace Internal +} // namespace CppEditor + +#endif // CPPDECLFROMDEF_H diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index cc4238013a..adef080f35 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -31,10 +31,8 @@ #include "cppeditorconstants.h" #include "cppplugin.h" #include "cpphighlighter.h" -#include "cppcheckundefinedsymbols.h" - +#include "cppchecksymbols.h" #include "cppquickfix.h" -#include <cpptools/cpptoolsplugin.h> #include <AST.h> #include <Control.h> @@ -57,6 +55,7 @@ #include <cplusplus/BackwardsScanner.h> #include <cplusplus/FastPreprocessor.h> +#include <cpptools/cpptoolsplugin.h> #include <cpptools/cppmodelmanagerinterface.h> #include <cpptools/cpptoolsconstants.h> #include <cpptools/cppcodeformatter.h> @@ -97,7 +96,7 @@ #include <sstream> enum { - UPDATE_METHOD_BOX_INTERVAL = 150, + UPDATE_OUTLINE_INTERVAL = 150, UPDATE_USES_INTERVAL = 300 }; @@ -129,47 +128,6 @@ static QList<QTextEdit::ExtraSelection> createSelections(QTextDocument *document return selections; } -static QList<QTextEdit::ExtraSelection> createSelections(QTextDocument *document, - const QList<SemanticInfo::Use> &msgs, - const QTextCharFormat &format) -{ - QList<QTextEdit::ExtraSelection> selections; - - QTextBlock currentBlock = document->firstBlock(); - unsigned currentLine = 1; - - foreach (const SemanticInfo::Use &use, msgs) { - QTextCursor cursor(document); - - if (currentLine != use.line) { - int delta = use.line - currentLine; - - if (delta >= 0) { - while (delta--) - currentBlock = currentBlock.next(); - } else { - currentBlock = document->findBlockByNumber(use.line - 1); - } - - currentLine = use.line; - } - - const int pos = currentBlock.position() + use.column - 1; - if (pos < 0) - continue; - - cursor.setPosition(pos); - cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, use.length); - - QTextEdit::ExtraSelection sel; - sel.cursor = cursor; - sel.format = format; - selections.append(sel); - } - - return selections; -} - namespace { class OverviewTreeView : public QTreeView @@ -189,6 +147,32 @@ public: } }; +class OverviewProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + OverviewProxyModel(CPlusPlus::OverviewModel *sourceModel, QObject *parent) : + QSortFilterProxyModel(parent), + m_sourceModel(sourceModel) + { + setSourceModel(m_sourceModel); + } + + bool filterAcceptsRow(int sourceRow,const QModelIndex &sourceParent) const + { + // ignore generated symbols, e.g. by macro expansion (Q_OBJECT) + const QModelIndex sourceIndex = m_sourceModel->index(sourceRow, 0, sourceParent); + CPlusPlus::Symbol *symbol = m_sourceModel->symbolFromIndex(sourceIndex); + if (symbol && symbol->isGenerated()) + return false; + + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } +private: + CPlusPlus::OverviewModel *m_sourceModel; +}; + + class FindLocalUses: protected ASTVisitor { Scope *_functionScope; @@ -509,7 +493,7 @@ protected: { const Name *name = function->name(); if (const QualifiedNameId *q = name->asQualifiedNameId()) - name = q->unqualifiedNameId(); + name = q->name(); if (_declarationName->isEqualTo(name)) _functions->append(function); @@ -617,8 +601,10 @@ CPPEditor::CPPEditor(QWidget *parent) this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr))); } - m_highlighteRevision = 0; - connect(&m_highlightWatcher, SIGNAL(finished()), SLOT(highlightTypeUsages())); + m_highlightRevision = 0; + m_nextHighlightBlockNumber = 0; + connect(&m_highlightWatcher, SIGNAL(resultsReadyAt(int,int)), SLOT(highlightTypeUsages(int,int))); + connect(&m_highlightWatcher, SIGNAL(finished()), SLOT(finishTypeUsages())); } CPPEditor::~CPPEditor() @@ -638,51 +624,55 @@ TextEditor::BaseTextEditorEditable *CPPEditor::createEditableInterface() void CPPEditor::createToolBar(CPPEditorEditable *editable) { - m_methodCombo = new QComboBox; - m_methodCombo->setMinimumContentsLength(22); + m_outlineCombo = new QComboBox; + m_outlineCombo->setMinimumContentsLength(22); // Make the combo box prefer to expand - QSizePolicy policy = m_methodCombo->sizePolicy(); + QSizePolicy policy = m_outlineCombo->sizePolicy(); policy.setHorizontalPolicy(QSizePolicy::Expanding); - m_methodCombo->setSizePolicy(policy); - - QTreeView *methodView = new OverviewTreeView; - methodView->header()->hide(); - methodView->setItemsExpandable(false); - m_methodCombo->setView(methodView); - m_methodCombo->setMaxVisibleItems(20); - - m_overviewModel = new OverviewModel(this); - m_proxyModel = new QSortFilterProxyModel(this); - m_proxyModel->setSourceModel(m_overviewModel); - if (CppPlugin::instance()->sortedMethodOverview()) + m_outlineCombo->setSizePolicy(policy); + + QTreeView *outlineView = new OverviewTreeView; + outlineView->header()->hide(); + outlineView->setItemsExpandable(false); + m_outlineCombo->setView(outlineView); + m_outlineCombo->setMaxVisibleItems(20); + + m_outlineModel = new OverviewModel(this); + m_proxyModel = new OverviewProxyModel(m_outlineModel, this); + if (CppPlugin::instance()->sortedOutline()) m_proxyModel->sort(0, Qt::AscendingOrder); else - m_proxyModel->sort(-1, Qt::AscendingOrder); // don't sort yet, but set column for sortedMethodOverview() + m_proxyModel->sort(-1, Qt::AscendingOrder); // don't sort yet, but set column for sortedOutline() m_proxyModel->setDynamicSortFilter(true); m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_methodCombo->setModel(m_proxyModel); + m_outlineCombo->setModel(m_proxyModel); - m_methodCombo->setContextMenuPolicy(Qt::ActionsContextMenu); - m_sortAction = new QAction(tr("Sort alphabetically"), m_methodCombo); + m_outlineCombo->setContextMenuPolicy(Qt::ActionsContextMenu); + m_sortAction = new QAction(tr("Sort Alphabetically"), m_outlineCombo); m_sortAction->setCheckable(true); - m_sortAction->setChecked(sortedMethodOverview()); - connect(m_sortAction, SIGNAL(toggled(bool)), CppPlugin::instance(), SLOT(setSortedMethodOverview(bool))); - m_methodCombo->addAction(m_sortAction); + m_sortAction->setChecked(sortedOutline()); + connect(m_sortAction, SIGNAL(toggled(bool)), CppPlugin::instance(), SLOT(setSortedOutline(bool))); + m_outlineCombo->addAction(m_sortAction); + + m_updateOutlineTimer = new QTimer(this); + m_updateOutlineTimer->setSingleShot(true); + m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL); + connect(m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow())); - m_updateMethodBoxTimer = new QTimer(this); - m_updateMethodBoxTimer->setSingleShot(true); - m_updateMethodBoxTimer->setInterval(UPDATE_METHOD_BOX_INTERVAL); - connect(m_updateMethodBoxTimer, SIGNAL(timeout()), this, SLOT(updateMethodBoxIndexNow())); + m_updateOutlineIndexTimer = new QTimer(this); + m_updateOutlineIndexTimer->setSingleShot(true); + m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL); + connect(m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow())); m_updateUsesTimer = new QTimer(this); m_updateUsesTimer->setSingleShot(true); m_updateUsesTimer->setInterval(UPDATE_USES_INTERVAL); connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow())); - connect(m_methodCombo, SIGNAL(activated(int)), this, SLOT(jumpToMethod(int))); - connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateMethodBoxIndex())); - connect(m_methodCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateMethodBoxToolTip())); + connect(m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement(int))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateOutlineIndex())); + connect(m_outlineCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOutlineToolTip())); connect(document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onContentsChanged(int,int,int))); connect(file(), SIGNAL(changed()), this, SLOT(updateFileName())); @@ -698,7 +688,7 @@ void CPPEditor::createToolBar(CPPEditorEditable *editable) QToolBar *toolBar = static_cast<QToolBar*>(editable->toolBar()); QList<QAction*> actions = toolBar->actions(); QWidget *w = toolBar->widgetForAction(actions.first()); - static_cast<QHBoxLayout*>(w->layout())->insertWidget(0, m_methodCombo, 1); + static_cast<QHBoxLayout*>(w->layout())->insertWidget(0, m_outlineCombo, 1); } void CPPEditor::paste() @@ -806,10 +796,7 @@ void CPPEditor::onDocumentUpdated(Document::Ptr doc) m_semanticHighlighter->rehighlight(source); } - m_overviewModel->rebuild(doc); - OverviewTreeView *treeView = static_cast<OverviewTreeView *>(m_methodCombo->view()); - treeView->sync(); - updateMethodBoxIndexNow(); + m_updateOutlineTimer->start(); } const Macro *CPPEditor::findCanonicalMacro(const QTextCursor &cursor, Document::Ptr doc) const @@ -985,19 +972,19 @@ void CPPEditor::onContentsChanged(int position, int charsRemoved, int charsAdded void CPPEditor::updateFileName() { } -void CPPEditor::jumpToMethod(int) +void CPPEditor::jumpToOutlineElement(int) { - QModelIndex index = m_proxyModel->mapToSource(m_methodCombo->view()->currentIndex()); - Symbol *symbol = m_overviewModel->symbolFromIndex(index); + QModelIndex index = m_proxyModel->mapToSource(m_outlineCombo->view()->currentIndex()); + Symbol *symbol = m_outlineModel->symbolFromIndex(index); if (! symbol) return; openCppEditorAt(linkToSymbol(symbol)); } -void CPPEditor::setSortedMethodOverview(bool sort) +void CPPEditor::setSortedOutline(bool sort) { - if (sort != sortedMethodOverview()) { + if (sort != sortedOutline()) { if (sort) m_proxyModel->sort(0, Qt::AscendingOrder); else @@ -1005,18 +992,38 @@ void CPPEditor::setSortedMethodOverview(bool sort) bool block = m_sortAction->blockSignals(true); m_sortAction->setChecked(m_proxyModel->sortColumn() == 0); m_sortAction->blockSignals(block); - updateMethodBoxIndexNow(); + updateOutlineIndexNow(); } } -bool CPPEditor::sortedMethodOverview() const +bool CPPEditor::sortedOutline() const { return (m_proxyModel->sortColumn() == 0); } -void CPPEditor::updateMethodBoxIndex() +void CPPEditor::updateOutlineNow() +{ + const Snapshot snapshot = m_modelManager->snapshot(); + Document::Ptr document = snapshot.document(file()->fileName()); + + if (!document) + return; + + if (document->editorRevision() != editorRevision()) { + m_updateOutlineTimer->start(); + return; + } + + m_outlineModel->rebuild(document); + + OverviewTreeView *treeView = static_cast<OverviewTreeView *>(m_outlineCombo->view()); + treeView->sync(); + updateOutlineIndexNow(); +} + +void CPPEditor::updateOutlineIndex() { - m_updateMethodBoxTimer->start(); + m_updateOutlineIndexTimer->start(); } void CPPEditor::highlightUses(const QList<SemanticInfo::Use> &uses, @@ -1055,49 +1062,46 @@ void CPPEditor::highlightUses(const QList<SemanticInfo::Use> &uses, } } -void CPPEditor::updateMethodBoxIndexNow() +void CPPEditor::updateOutlineIndexNow() { - if (! m_overviewModel->document()) + if (!m_outlineModel->document()) return; - if (m_overviewModel->document()->editorRevision() != editorRevision()) { - m_updateMethodBoxTimer->start(); + if (m_outlineModel->document()->editorRevision() != editorRevision()) { + m_updateOutlineIndexTimer->start(); return; } - m_updateMethodBoxTimer->stop(); + m_updateOutlineIndexTimer->stop(); - int line = 0, column = 0; - convertPosition(position(), &line, &column); + m_outlineModelIndex = QModelIndex(); //invalidate + QModelIndex comboIndex = outlineModelIndex(); - QModelIndex lastIndex; - const int rc = m_overviewModel->rowCount(); - for (int row = 0; row < rc; ++row) { - const QModelIndex index = m_overviewModel->index(row, 0, QModelIndex()); - Symbol *symbol = m_overviewModel->symbolFromIndex(index); - if (symbol && symbol->line() > unsigned(line)) - break; - lastIndex = index; - } + if (comboIndex.isValid()) { + bool blocked = m_outlineCombo->blockSignals(true); + + // There is no direct way to select a non-root item + m_outlineCombo->setRootModelIndex(m_proxyModel->mapFromSource(comboIndex.parent())); + m_outlineCombo->setCurrentIndex(m_proxyModel->mapFromSource(comboIndex).row()); + m_outlineCombo->setRootModelIndex(QModelIndex()); - if (lastIndex.isValid()) { - bool blocked = m_methodCombo->blockSignals(true); - m_methodCombo->setCurrentIndex(m_proxyModel->mapFromSource(lastIndex).row()); - updateMethodBoxToolTip(); - (void) m_methodCombo->blockSignals(blocked); + updateOutlineToolTip(); + + m_outlineCombo->blockSignals(blocked); } } -void CPPEditor::updateMethodBoxToolTip() +void CPPEditor::updateOutlineToolTip() { - m_methodCombo->setToolTip(m_methodCombo->currentText()); + m_outlineCombo->setToolTip(m_outlineCombo->currentText()); } void CPPEditor::updateUses() { + if (editorRevision() != m_highlightRevision) + m_highlighter.cancel(); m_updateUsesTimer->start(); - m_highlighter.cancel(); } void CPPEditor::updateUsesNow() @@ -1108,18 +1112,76 @@ void CPPEditor::updateUsesNow() semanticRehighlight(); } -void CPPEditor::highlightTypeUsages() +void CPPEditor::highlightTypeUsages(int from, int to) { - if (editorRevision() != m_highlighteRevision) + if (editorRevision() != m_highlightRevision) return; // outdated else if (m_highlighter.isCanceled()) return; // aborted - const QList<SemanticInfo::Use> typeUsages = m_highlighter.results(); - setExtraSelections(TypeSelection, createSelections(document(), typeUsages, m_typeFormat)); + CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter()); + Q_ASSERT(highlighter); + QTextDocument *doc = document(); + + if (m_nextHighlightBlockNumber >= doc->blockCount()) + return; + + QMap<int, QVector<SemanticInfo::Use> > chunks = CheckSymbols::chunks(m_highlighter, from, to); + Q_ASSERT(!chunks.isEmpty()); + QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber); + + QMapIterator<int, QVector<SemanticInfo::Use> > it(chunks); + while (b.isValid() && it.hasNext()) { + it.next(); + const int blockNumber = it.key(); + Q_ASSERT(blockNumber < doc->blockCount()); + + while (m_nextHighlightBlockNumber < blockNumber) { + highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>()); + b = b.next(); + ++m_nextHighlightBlockNumber; + } + + QList<QTextLayout::FormatRange> formats; + foreach (const SemanticInfo::Use &use, it.value()) { + QTextLayout::FormatRange formatRange; + formatRange.format = m_typeFormat; + formatRange.start = use.column - 1; + formatRange.length = use.length; + formats.append(formatRange); + } + highlighter->setExtraAdditionalFormats(b, formats); + b = b.next(); + ++m_nextHighlightBlockNumber; + } } +void CPPEditor::finishTypeUsages() +{ + if (editorRevision() != m_highlightRevision) + return; // outdated + + else if (m_highlighter.isCanceled()) + return; // aborted + + CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter()); + Q_ASSERT(highlighter); + QTextDocument *doc = document(); + + if (m_nextHighlightBlockNumber >= doc->blockCount()) + return; + + QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber); + + while (b.isValid()) { + highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>()); + b = b.next(); + ++m_nextHighlightBlockNumber; + } +} + + void CPPEditor::switchDeclarationDefinition() { if (! m_modelManager) @@ -1417,6 +1479,23 @@ SemanticInfo CPPEditor::semanticInfo() const return m_lastSemanticInfo; } +CPlusPlus::OverviewModel *CPPEditor::outlineModel() const +{ + return m_outlineModel; +} + +QModelIndex CPPEditor::outlineModelIndex() +{ + if (!m_outlineModelIndex.isValid()) { + int line = 0, column = 0; + convertPosition(position(), &line, &column); + m_outlineModelIndex = indexForPosition(line, column); + emit outlineModelIndexChanged(m_outlineModelIndex); + } + + return m_outlineModelIndex; +} + bool CPPEditor::isElectricCharacter(QChar ch) const { if (ch == QLatin1Char('{') || @@ -1897,9 +1976,10 @@ void CPPEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) if (semanticInfo.doc) { LookupContext context(semanticInfo.doc, semanticInfo.snapshot); - CheckUndefinedSymbols::Future f = CheckUndefinedSymbols::go(semanticInfo.doc, context); + CheckSymbols::Future f = CheckSymbols::go(semanticInfo.doc, context); m_highlighter = f; - m_highlighteRevision = semanticInfo.revision; + m_highlightRevision = semanticInfo.revision; + m_nextHighlightBlockNumber = 0; m_highlightWatcher.setFuture(m_highlighter); } @@ -1910,6 +1990,7 @@ void CPPEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) #endif } + setExtraSelections(UnusedSymbolSelection, unusedSelections); if (! m_renameSelections.isEmpty()) @@ -2161,3 +2242,26 @@ SemanticInfo SemanticHighlighter::semanticInfo(const Source &source) return semanticInfo; } + +QModelIndex CPPEditor::indexForPosition(int line, int column, const QModelIndex &rootIndex) const +{ + QModelIndex lastIndex = rootIndex; + + const int rowCount = m_outlineModel->rowCount(rootIndex); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex index = m_outlineModel->index(row, 0, rootIndex); + Symbol *symbol = m_outlineModel->symbolFromIndex(index); + if (symbol && symbol->line() > unsigned(line)) + break; + lastIndex = index; + } + + if (lastIndex != rootIndex) { + // recurse + lastIndex = indexForPosition(line, column, lastIndex); + } + + return lastIndex; +} + +#include "cppeditor.moc" diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 488bcfd9e2..738a977586 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -42,6 +42,7 @@ #include <QtCore/QMutex> #include <QtCore/QWaitCondition> #include <QtCore/QFutureWatcher> +#include <QtCore/QModelIndex> QT_BEGIN_NAMESPACE class QComboBox; @@ -166,6 +167,9 @@ public: bool isOutdated() const; SemanticInfo semanticInfo() const; + CPlusPlus::OverviewModel *outlineModel() const; + QModelIndex outlineModelIndex(); + virtual void paste(); // reimplemented from BaseTextEditor virtual void cut(); // reimplemented from BaseTextEditor @@ -176,10 +180,13 @@ public: void setObjCEnabled(bool onoff); bool isObjCEnabled() const; +Q_SIGNALS: + void outlineModelIndexChanged(const QModelIndex &index); + public Q_SLOTS: virtual void setFontSettings(const TextEditor::FontSettings &); virtual void setTabSettings(const TextEditor::TabSettings &); - void setSortedMethodOverview(bool sort); + void setSortedOutline(bool sort); void switchDeclarationDefinition(); void jumpToDefinition(); void renameSymbolUnderCursor(); @@ -214,10 +221,11 @@ protected: private Q_SLOTS: void updateFileName(); - void jumpToMethod(int index); - void updateMethodBoxIndex(); - void updateMethodBoxIndexNow(); - void updateMethodBoxToolTip(); + void jumpToOutlineElement(int index); + void updateOutlineNow(); + void updateOutlineIndex(); + void updateOutlineIndexNow(); + void updateOutlineToolTip(); void updateUses(); void updateUsesNow(); void onDocumentUpdated(CPlusPlus::Document::Ptr doc); @@ -225,7 +233,8 @@ private Q_SLOTS: void semanticRehighlight(); void updateSemanticInfo(const CppEditor::Internal::SemanticInfo &semanticInfo); - void highlightTypeUsages(); + void highlightTypeUsages(int from, int to); + void finishTypeUsages(); void performQuickFix(int index); @@ -234,7 +243,7 @@ private: void setShowWarningMessage(bool showWarningMessage); void markSymbols(CPlusPlus::Symbol *canonicalSymbol, const SemanticInfo &info); - bool sortedMethodOverview() const; + bool sortedOutline() const; CPlusPlus::Symbol *findDefinition(CPlusPlus::Symbol *symbol, const CPlusPlus::Snapshot &snapshot); virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar); virtual void indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar); @@ -258,15 +267,19 @@ private: bool openLink(const Link &link) { return openCppEditorAt(link); } bool openCppEditorAt(const Link &); + QModelIndex indexForPosition(int line, int column, const QModelIndex &rootIndex = QModelIndex()) const; + static Link linkToSymbol(CPlusPlus::Symbol *symbol); CppTools::CppModelManagerInterface *m_modelManager; - QComboBox *m_methodCombo; - CPlusPlus::OverviewModel *m_overviewModel; + QComboBox *m_outlineCombo; + CPlusPlus::OverviewModel *m_outlineModel; + QModelIndex m_outlineModelIndex; QSortFilterProxyModel *m_proxyModel; QAction *m_sortAction; - QTimer *m_updateMethodBoxTimer; + QTimer *m_updateOutlineTimer; + QTimer *m_updateOutlineIndexTimer; QTimer *m_updateUsesTimer; QTextCharFormat m_occurrencesFormat; QTextCharFormat m_occurrencesUnusedFormat; @@ -288,7 +301,8 @@ private: QFuture<SemanticInfo::Use> m_highlighter; QFutureWatcher<SemanticInfo::Use> m_highlightWatcher; - unsigned m_highlighteRevision; // the editor revision that requested the highlight + unsigned m_highlightRevision; // the editor revision that requested the highlight + int m_nextHighlightBlockNumber; }; diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 49306644a2..4b017f64fe 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -17,9 +17,10 @@ HEADERS += cppplugin.h \ cppclasswizard.h \ cppquickfix.h \ cpprefactoringchanges.h \ - cppcheckundefinedsymbols.h \ + cppchecksymbols.h \ cppsemanticinfo.h \ - cppoutline.h + cppoutline.h \ + cppdeclfromdef.h SOURCES += cppplugin.cpp \ cppeditor.cpp \ @@ -29,9 +30,10 @@ SOURCES += cppplugin.cpp \ cppclasswizard.cpp \ cppquickfix.cpp \ cpprefactoringchanges.cpp \ - cppcheckundefinedsymbols.cpp \ + cppchecksymbols.cpp \ cppsemanticinfo.cpp \ - cppoutline.cpp + cppoutline.cpp \ + cppdeclfromdef.cpp RESOURCES += cppeditor.qrc diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp index 321025058e..c98c35dcdc 100644 --- a/src/plugins/cppeditor/cpphighlighter.cpp +++ b/src/plugins/cppeditor/cpphighlighter.cpp @@ -42,7 +42,7 @@ using namespace TextEditor; using namespace CPlusPlus; CppHighlighter::CppHighlighter(QTextDocument *document) : - QSyntaxHighlighter(document) + TextEditor::SyntaxHighlighter(document) { } diff --git a/src/plugins/cppeditor/cpphighlighter.h b/src/plugins/cppeditor/cpphighlighter.h index 9d7c0496fc..d8fe769371 100644 --- a/src/plugins/cppeditor/cpphighlighter.h +++ b/src/plugins/cppeditor/cpphighlighter.h @@ -31,16 +31,17 @@ #define CPPHIGHLIGHTER_H #include "cppeditorenums.h" -#include <QtGui/QSyntaxHighlighter> +#include <texteditor/syntaxhighlighter.h> #include <QtGui/QTextCharFormat> #include <QtCore/QtAlgorithms> namespace CppEditor { + namespace Internal { class CPPEditor; -class CppHighlighter : public QSyntaxHighlighter +class CppHighlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT diff --git a/src/plugins/cppeditor/cpphoverhandler.cpp b/src/plugins/cppeditor/cpphoverhandler.cpp index 8115032b1e..5c84e86841 100644 --- a/src/plugins/cppeditor/cpphoverhandler.cpp +++ b/src/plugins/cppeditor/cpphoverhandler.cpp @@ -29,7 +29,6 @@ #include "cpphoverhandler.h" #include "cppeditor.h" -#include "cppplugin.h" #include <coreplugin/icore.h> #include <coreplugin/helpmanager.h> @@ -39,48 +38,69 @@ #include <extensionsystem/pluginmanager.h> #include <texteditor/itexteditor.h> #include <texteditor/basetexteditor.h> +#include <texteditor/displaysettings.h> #include <debugger/debuggerconstants.h> +#include <utils/htmldocextractor.h> -#include <CoreTypes.h> #include <FullySpecifiedType.h> -#include <Literals.h> -#include <Control.h> -#include <Names.h> #include <Scope.h> #include <Symbol.h> #include <Symbols.h> #include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/Overview.h> #include <cplusplus/TypeOfExpression.h> -#include <cplusplus/SimpleLexer.h> +#include <cplusplus/LookupContext.h> +#include <cplusplus/LookupItem.h> -#include <QtCore/QDebug> #include <QtCore/QDir> #include <QtCore/QFileInfo> -#include <QtCore/QSettings> #include <QtGui/QToolTip> #include <QtGui/QTextCursor> -#include <QtGui/QTextBlock> using namespace CppEditor::Internal; using namespace CPlusPlus; using namespace Core; +namespace { + QString removeQualificationIfAny(const QString &name) { + int index = name.lastIndexOf(QLatin1Char(':')); + if (index == -1) + return name; + else + return name.right(name.length() - index - 1); + } + + void moveCursorToEndOfQualifiedName(QTextCursor *tc) { + QTextDocument *doc = tc->document(); + if (!doc) + return; + + while (true) { + const QChar &ch = doc->characterAt(tc->position()); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) + tc->movePosition(QTextCursor::NextCharacter); + else if (ch == QLatin1Char(':') && + doc->characterAt(tc->position() + 1) == QLatin1Char(':')) + tc->movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 2); + else + break; + } + } +} + CppHoverHandler::CppHoverHandler(QObject *parent) - : QObject(parent) + : QObject(parent), m_modelManager(0), m_matchingHelpCandidate(-1) { - m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>(); + m_modelManager = + ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>(); + + m_htmlDocExtractor.setLengthReference(1000, true); // Listen for editor opened events in order to connect to tooltip/helpid requests connect(ICore::instance()->editorManager(), SIGNAL(editorOpened(Core::IEditor *)), this, SLOT(editorOpened(Core::IEditor *))); } -void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos) -{ - updateHelpIdAndTooltip(editor, pos); -} - void CppHoverHandler::editorOpened(IEditor *editor) { CPPEditorEditable *cppEditor = qobject_cast<CPPEditorEditable *>(editor); @@ -94,22 +114,59 @@ void CppHoverHandler::editorOpened(IEditor *editor) this, SLOT(updateContextHelpId(TextEditor::ITextEditor*, int))); } +void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos) +{ + if (!editor) + return; + + // If the tooltip is visible and there is a help match, this match is used to update the help + // id. Otherwise, the identification process happens. + if (!QToolTip::isVisible() || m_matchingHelpCandidate == -1) + identifyMatch(editor, pos); + + if (m_matchingHelpCandidate != -1) + editor->setContextHelpId(m_helpCandidates.at(m_matchingHelpCandidate).m_helpId); + else + editor->setContextHelpId(QString()); +} + void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint &point, int pos) { if (!editor) return; - ICore *core = ICore::instance(); - const int dbgcontext = core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_DEBUGMODE); + editor->setContextHelpId(QString()); - if (core->hasContext(dbgcontext)) + ICore *core = ICore::instance(); + const int dbgContext = + core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_DEBUGMODE); + if (core->hasContext(dbgContext)) return; - updateHelpIdAndTooltip(editor, pos); + identifyMatch(editor, pos); - if (m_toolTip.isEmpty()) + if (m_toolTip.isEmpty()) { QToolTip::hideText(); - else { + } else { + if (m_matchingHelpCandidate != -1) { + QString contents; + TextEditor::BaseTextEditor *baseEditor = baseTextEditor(editor); + if (baseEditor && baseEditor->displaySettings().m_integrateDocsIntoTooltips) + contents = getDocContents(); + if (!contents.isEmpty()) { + m_toolTip = contents; + } else { + m_toolTip = Qt::escape(m_toolTip); + m_toolTip.prepend(QLatin1String("<nobr>")); + m_toolTip.append(QLatin1String("</nobr>")); + } + + m_toolTip = QString(QLatin1String("<table><tr>" + "<td valign=middle>%1</td>" + "<td><img src=\":/cppeditor/images/f1.png\"></td>" + "</tr></table>")).arg(m_toolTip); + } + const QPoint pnt = point - QPoint(0, #ifdef Q_WS_WIN 24 @@ -122,210 +179,241 @@ void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint } } -static QString buildHelpId(Symbol *symbol, const Name *declarationName) +void CppHoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos) { - Scope *scope = 0; + resetMatchings(); - if (symbol) { - scope = symbol->scope(); - declarationName = symbol->name(); - } + if (!m_modelManager) + return; - if (! declarationName) - return QString(); + const Snapshot &snapshot = m_modelManager->snapshot(); + Document::Ptr doc = snapshot.document(editor->file()->fileName()); + if (!doc) + return; - Overview overview; - overview.setShowArgumentNames(false); - overview.setShowReturnTypes(false); + int line = 0; + int column = 0; + editor->convertPosition(pos, &line, &column); - QStringList qualifiedNames; - qualifiedNames.prepend(overview.prettyName(declarationName)); + if (!matchDiagnosticMessage(doc, line) && + !matchIncludeFile(doc, line) && + !matchMacroInUse(doc, pos)) { - for (; scope; scope = scope->enclosingScope()) { - Symbol *owner = scope->owner(); + TextEditor::BaseTextEditor *baseEditor = baseTextEditor(editor); + if (!baseEditor) + return; - if (owner && owner->name() && ! scope->isEnumScope()) { - const Name *name = owner->name(); - const Identifier *id = 0; + bool extraSelectionTooltip = false; + if (!baseEditor->extraSelectionTooltip(pos).isEmpty()) { + m_toolTip = baseEditor->extraSelectionTooltip(pos); + extraSelectionTooltip = true; + } - if (const NameId *nameId = name->asNameId()) - id = nameId->identifier(); + QTextCursor tc(baseEditor->document()); + tc.setPosition(pos); + moveCursorToEndOfQualifiedName(&tc); - else if (const TemplateNameId *nameId = name->asTemplateNameId()) - id = nameId->identifier(); + // Fetch the expression's code + ExpressionUnderCursor expressionUnderCursor; + const QString &expression = expressionUnderCursor(tc); + Scope *scope = doc->scopeAt(line, column); - if (id) - qualifiedNames.prepend(QString::fromLatin1(id->chars(), id->size())); - } + TypeOfExpression typeOfExpression; + typeOfExpression.init(doc, snapshot); + const QList<LookupItem> &lookupItems = typeOfExpression(expression, scope); + if (lookupItems.isEmpty()) + return; + + const LookupItem &lookupItem = lookupItems.first(); // ### TODO: select the best candidate. + handleLookupItemMatch(lookupItem, !extraSelectionTooltip); } - return qualifiedNames.join(QLatin1String("::")); + evaluateHelpCandidates(); } -void CppHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos) +bool CppHoverHandler::matchDiagnosticMessage(const CPlusPlus::Document::Ptr &document, + unsigned line) { - m_helpId.clear(); - m_toolTip.clear(); - - if (!m_modelManager) - return; - - TextEditor::BaseTextEditor *edit = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget()); - if (!edit) - return; - - const Snapshot snapshot = m_modelManager->snapshot(); - const QString fileName = editor->file()->fileName(); - Document::Ptr doc = snapshot.document(fileName); - if (!doc) - return; // nothing to do - - QString formatTooltip = edit->extraSelectionTooltip(pos); - QTextCursor tc(edit->document()); - tc.setPosition(pos); - - const unsigned lineNumber = tc.block().blockNumber() + 1; - - // Find the last symbol up to the cursor position - int line = 0, column = 0; - editor->convertPosition(tc.position(), &line, &column); - Scope *scope = doc->scopeAt(line, column); - - TypeOfExpression typeOfExpression; - typeOfExpression.init(doc, snapshot); - - // We only want to show F1 if the tooltip matches the help id - bool showF1 = true; - - foreach (const Document::DiagnosticMessage &m, doc->diagnosticMessages()) { - if (m.line() == lineNumber) { + foreach (const Document::DiagnosticMessage &m, document->diagnosticMessages()) { + if (m.line() == line) { m_toolTip = m.text(); - showF1 = false; - break; + return true; } } + return false; +} - QMap<QString, QUrl> helpLinks; - if (m_toolTip.isEmpty()) { - foreach (const Document::Include &incl, doc->includes()) { - if (incl.line() == lineNumber) { - m_toolTip = QDir::toNativeSeparators(incl.fileName()); - m_helpId = QFileInfo(incl.fileName()).fileName(); - helpLinks = Core::HelpManager::instance()->linksForIdentifier(m_helpId); - break; - } +bool CppHoverHandler::matchIncludeFile(const CPlusPlus::Document::Ptr &document, unsigned line) +{ + foreach (const Document::Include &includeFile, document->includes()) { + if (includeFile.line() == line) { + m_toolTip = QDir::toNativeSeparators(includeFile.fileName()); + const QString &fileName = QFileInfo(includeFile.fileName()).fileName(); + m_helpCandidates.append(HelpCandidate(fileName, fileName, HelpCandidate::Include)); + return true; } } + return false; +} - if (m_helpId.isEmpty()) { - // Move to the end of a qualified name - bool stop = false; - while (!stop) { - const QChar ch = editor->characterAt(tc.position()); - if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) - tc.setPosition(tc.position() + 1); - else if (ch == QLatin1Char(':') && editor->characterAt(tc.position() + 1) == QLatin1Char(':')) { - tc.setPosition(tc.position() + 2); - } else { - stop = true; +bool CppHoverHandler::matchMacroInUse(const CPlusPlus::Document::Ptr &document, unsigned pos) +{ + foreach (const Document::MacroUse &use, document->macroUses()) { + if (use.contains(pos)) { + const unsigned begin = use.begin(); + const QString &name = use.macro().name(); + if (pos < begin + name.length()) { + m_toolTip = use.macro().toString(); + m_helpCandidates.append(HelpCandidate(name, name, HelpCandidate::Macro)); + return true; } } + } + return false; +} - // Fetch the expression's code - ExpressionUnderCursor expressionUnderCursor; - const QString expression = expressionUnderCursor(tc); - - const QList<LookupItem> types = typeOfExpression(expression, scope); - - - if (!types.isEmpty()) { - Overview overview; - overview.setShowArgumentNames(true); - overview.setShowReturnTypes(true); - overview.setShowFullyQualifiedNamed(true); - - const LookupItem result = types.first(); // ### TODO: select the best candidate. - FullySpecifiedType symbolTy = result.type(); // result of `type of expression'. - Symbol *declaration = result.declaration(); // lookup symbol - const Name *declarationName = declaration ? declaration->name() : 0; +void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, const bool assignTooltip) +{ + Symbol *matchingDeclaration = lookupItem.declaration(); + FullySpecifiedType matchingType = lookupItem.type(); - if (declaration && declaration->scope() - && declaration->scope()->isClassScope()) { - Class *enclosingClass = declaration->scope()->owner()->asClass(); - if (const Identifier *id = enclosingClass->identifier()) { - if (id->isEqualTo(declaration->identifier())) - declaration = enclosingClass; - } + Overview overview; + overview.setShowArgumentNames(true); + overview.setShowReturnTypes(true); + overview.setShowFullyQualifiedNamed(true); + + if (!matchingDeclaration && assignTooltip) { + m_toolTip = overview.prettyType(matchingType, QString()); + } else { + QString qualifiedName; + if (matchingDeclaration->enclosingSymbol()->isClass() || + matchingDeclaration->enclosingSymbol()->isNamespace() || + matchingDeclaration->enclosingSymbol()->isEnum()) { + const QList<const Name *> &names = + LookupContext::fullyQualifiedName(matchingDeclaration); + const int size = names.size(); + for (int i = 0; i < size; ++i) { + qualifiedName.append(overview.prettyName(names.at(i))); + if (i < size - 1) + qualifiedName.append(QLatin1String("::")); } + } else { + qualifiedName.append(overview.prettyName(matchingDeclaration->name())); + } - m_helpId = buildHelpId(declaration, declarationName); - - if (m_toolTip.isEmpty()) { - Symbol *symbol = declaration; - - if (declaration) - symbol = declaration; - - if (symbol && symbol == declaration && symbol->isClass()) { - m_toolTip = m_helpId; - - } else if (declaration && (declaration->isDeclaration() || declaration->isArgument())) { - m_toolTip = overview.prettyType(symbolTy, buildHelpId(declaration, declaration->name())); - - } else if (symbolTy->isClassType() || symbolTy->isEnumType() || - symbolTy->isForwardClassDeclarationType()) { - m_toolTip = m_helpId; - - } else { - m_toolTip = overview.prettyType(symbolTy, m_helpId); - - } + if (assignTooltip) { + if (matchingDeclaration->isClass() || + matchingDeclaration->isNamespace() || + matchingDeclaration->isForwardClassDeclaration() || + matchingDeclaration->isEnum()) { + m_toolTip = qualifiedName; + } else { + m_toolTip = overview.prettyType(matchingType, qualifiedName); } + } - // Some docs don't contain the namespace in the documentation pages, for instance - // there is QtMobility::QContactManager but the help page is for QContactManager. - // To show their help anyway, try stripping scopes until we find something. - const QString startHelpId = m_helpId; - while (!m_helpId.isEmpty()) { - helpLinks = Core::HelpManager::instance()->linksForIdentifier(m_helpId); - if (!helpLinks.isEmpty()) - break; - - int coloncolonIndex = m_helpId.indexOf(QLatin1String("::")); - if (coloncolonIndex == -1) { - m_helpId = startHelpId; - break; - } - - m_helpId.remove(0, coloncolonIndex + 2); - } + HelpCandidate::Category helpCategory; + if (matchingDeclaration->isNamespace() || + matchingDeclaration->isClass() || + matchingDeclaration->isForwardClassDeclaration()) { + helpCategory = HelpCandidate::ClassOrNamespace; + } else if (matchingDeclaration->isEnum()) { + helpCategory = HelpCandidate::Enum; + } else if (matchingDeclaration->isTypedef()) { + helpCategory = HelpCandidate::Typedef; + } else if (matchingDeclaration->isStatic() && !matchingDeclaration->isFunction()) { + helpCategory = HelpCandidate::Var; + } else { + helpCategory = HelpCandidate::Function; } + + // Help identifiers are simply the name with no signature, arguments or return type. + // They might or might not include a qualification. This is why two candidates are + // created. + overview.setShowArgumentNames(false); + overview.setShowReturnTypes(false); + overview.setShowFunctionSignatures(false); + overview.setShowFullyQualifiedNamed(false); + const QString &simpleName = overview.prettyName(matchingDeclaration->name()); + overview.setShowFunctionSignatures(true); + const QString &specifierId = overview.prettyType(matchingType, simpleName); + + m_helpCandidates.append(HelpCandidate(simpleName, specifierId, helpCategory)); + m_helpCandidates.append(HelpCandidate(qualifiedName, specifierId, helpCategory)); } +} - if (m_toolTip.isEmpty()) { - foreach (const Document::MacroUse &use, doc->macroUses()) { - if (use.contains(pos)) { - const Macro m = use.macro(); - m_toolTip = m.toString(); - m_helpId = m.name(); - break; - } +void CppHoverHandler::evaluateHelpCandidates() +{ + for (int i = 0; i < m_helpCandidates.size(); ++i) { + if (helpIdExists(m_helpCandidates.at(i).m_helpId)) { + m_matchingHelpCandidate = i; + return; } } +} - if (!formatTooltip.isEmpty()) - m_toolTip = formatTooltip; +bool CppHoverHandler::helpIdExists(const QString &helpId) const +{ + QMap<QString, QUrl> helpLinks = Core::HelpManager::instance()->linksForIdentifier(helpId); + if (!helpLinks.isEmpty()) + return true; + return false; +} - if (!m_helpId.isEmpty() && !helpLinks.isEmpty()) { - if (showF1) { - // we need the original width without escape sequences - const int width = QFontMetrics(QToolTip::font()).width(m_toolTip); - m_toolTip = QString(QLatin1String("<table><tr><td valign=middle width=%2>%1</td>" - "<td><img src=\":/cppeditor/images/f1.png\"></td></tr></table>")) - .arg(Qt::escape(m_toolTip)).arg(width); +QString CppHoverHandler::getDocContents() +{ + Q_ASSERT(m_matchingHelpCandidate >= 0); + + QString contents; + const HelpCandidate &help = m_helpCandidates.at(m_matchingHelpCandidate); + QMap<QString, QUrl> helpLinks = + Core::HelpManager::instance()->linksForIdentifier(help.m_helpId); + foreach (const QUrl &url, helpLinks) { + // The help id might or might not be qualified. But anchors and marks are not qualified. + const QString &name = removeQualificationIfAny(help.m_helpId); + const QByteArray &html = Core::HelpManager::instance()->fileData(url); + switch (help.m_category) { + case HelpCandidate::Include: + contents = m_htmlDocExtractor.getClassOrNamespaceBrief(html, name); + break; + case HelpCandidate::ClassOrNamespace: + contents = m_htmlDocExtractor.getClassOrNamespaceDescription(html, name); + break; + case HelpCandidate::Function: + contents = + m_htmlDocExtractor.getFunctionDescription(html, help.m_markId, name); + break; + case HelpCandidate::Enum: + contents = m_htmlDocExtractor.getEnumDescription(html, name); + break; + case HelpCandidate::Typedef: + contents = m_htmlDocExtractor.getTypedefDescription(html, name); + break; + case HelpCandidate::Var: + contents = m_htmlDocExtractor.getVarDescription(html, name); + break; + case HelpCandidate::Macro: + contents = m_htmlDocExtractor.getMacroDescription(html, help.m_markId, name); + break; + default: + break; } - editor->setContextHelpId(m_helpId); - } else if (!m_toolTip.isEmpty() && Qt::mightBeRichText(m_toolTip)) { - m_toolTip = QString(QLatin1String("<nobr>%1</nobr>")).arg(Qt::escape(m_toolTip)); + + if (!contents.isEmpty()) + break; } + return contents; +} + +void CppHoverHandler::resetMatchings() +{ + m_matchingHelpCandidate = -1; + m_helpCandidates.clear(); + m_toolTip.clear(); +} + +TextEditor::BaseTextEditor *CppHoverHandler::baseTextEditor(TextEditor::ITextEditor *editor) +{ + return qobject_cast<TextEditor::BaseTextEditor *>(editor->widget()); } diff --git a/src/plugins/cppeditor/cpphoverhandler.h b/src/plugins/cppeditor/cpphoverhandler.h index 311b829223..84c58733d4 100644 --- a/src/plugins/cppeditor/cpphoverhandler.h +++ b/src/plugins/cppeditor/cpphoverhandler.h @@ -30,12 +30,21 @@ #ifndef CPPHOVERHANDLER_H #define CPPHOVERHANDLER_H +#include <utils/htmldocextractor.h> + #include <QtCore/QObject> +#include <QtCore/QList> + +#include <cplusplus/CppDocument.h> QT_BEGIN_NAMESPACE class QPoint; QT_END_NAMESPACE +namespace CPlusPlus { +class LookupItem; +} + namespace Core { class IEditor; } @@ -46,6 +55,7 @@ class CppModelManagerInterface; namespace TextEditor { class ITextEditor; +class BaseTextEditor; } namespace CppEditor { @@ -54,7 +64,6 @@ namespace Internal { class CppHoverHandler : public QObject { Q_OBJECT - public: CppHoverHandler(QObject *parent = 0); @@ -66,11 +75,45 @@ private slots: void editorOpened(Core::IEditor *editor); private: - void updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos); + struct HelpCandidate + { + enum Category { + ClassOrNamespace, + Enum, + Typedef, + Var, + Macro, + Include, + Function + }; + + HelpCandidate(const QString &helpId, const QString &markId, Category category) : + m_helpId(helpId), m_markId(markId), m_category(category) + {} + QString m_helpId; + QString m_markId; + Category m_category; + }; + + void resetMatchings(); + void identifyMatch(TextEditor::ITextEditor *editor, int pos); + bool matchDiagnosticMessage(const CPlusPlus::Document::Ptr &document, unsigned line); + bool matchIncludeFile(const CPlusPlus::Document::Ptr &document, unsigned line); + bool matchMacroInUse(const CPlusPlus::Document::Ptr &document, unsigned pos); + void handleLookupItemMatch(const CPlusPlus::LookupItem &lookupItem, + const bool assignTooltip); + + void evaluateHelpCandidates(); + bool helpIdExists(const QString &helpId) const; + QString getDocContents(); + + static TextEditor::BaseTextEditor *baseTextEditor(TextEditor::ITextEditor *editor); CppTools::CppModelManagerInterface *m_modelManager; - QString m_helpId; + int m_matchingHelpCandidate; + QList<HelpCandidate> m_helpCandidates; QString m_toolTip; + Utils::HtmlDocExtractor m_htmlDocExtractor; }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppoutline.cpp b/src/plugins/cppeditor/cppoutline.cpp index b6ee497c51..146b060ae6 100644 --- a/src/plugins/cppeditor/cppoutline.cpp +++ b/src/plugins/cppeditor/cppoutline.cpp @@ -4,10 +4,12 @@ #include <Symbol.h> #include <coreplugin/ifile.h> +#include <coreplugin/editormanager/editormanager.h> #include <cplusplus/OverviewModel.h> -#include <QtGui/QVBoxLayout> #include <QtCore/QDebug> +#include <QtGui/QVBoxLayout> +#include <QtCore/QTimer> using namespace CppEditor::Internal; @@ -29,9 +31,11 @@ CppOutlineTreeView::CppOutlineTreeView(QWidget *parent) : setExpandsOnDoubleClick(false); } -CppOutlineFilterModel::CppOutlineFilterModel(QObject *parent) : - QSortFilterProxyModel(parent) +CppOutlineFilterModel::CppOutlineFilterModel(CPlusPlus::OverviewModel *sourceModel, QObject *parent) : + QSortFilterProxyModel(parent), + m_sourceModel(sourceModel) { + setSourceModel(m_sourceModel); } bool CppOutlineFilterModel::filterAcceptsRow(int sourceRow, @@ -41,6 +45,12 @@ bool CppOutlineFilterModel::filterAcceptsRow(int sourceRow, if (!sourceParent.isValid() && sourceRow == 0) { return false; } + // ignore generated symbols, e.g. by macro expansion (Q_OBJECT) + const QModelIndex sourceIndex = m_sourceModel->index(sourceRow, 0, sourceParent); + CPlusPlus::Symbol *symbol = m_sourceModel->symbolFromIndex(sourceIndex); + if (symbol && symbol->isGenerated()) + return false; + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } @@ -49,8 +59,8 @@ CppOutlineWidget::CppOutlineWidget(CPPEditor *editor) : TextEditor::IOutlineWidget(), m_editor(editor), m_treeView(new CppOutlineTreeView(this)), - m_model(new CPlusPlus::OverviewModel(this)), - m_proxyModel(new CppOutlineFilterModel(this)), + m_model(m_editor->outlineModel()), + m_proxyModel(new CppOutlineFilterModel(m_model, this)), m_enableCursorSync(true), m_blockCursorSync(false) { @@ -60,20 +70,13 @@ CppOutlineWidget::CppOutlineWidget(CPPEditor *editor) : layout->addWidget(m_treeView); setLayout(layout); - m_proxyModel->setSourceModel(m_model); m_treeView->setModel(m_proxyModel); - CppTools::CppModelManagerInterface *modelManager = CppTools::CppModelManagerInterface::instance(); + connect(m_model, SIGNAL(modelReset()), this, SLOT(modelUpdated())); + modelUpdated(); - connect(modelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), - this, SLOT(updateOutline(CPlusPlus::Document::Ptr))); - - if (modelManager->snapshot().contains(editor->file()->fileName())) { - updateOutline(modelManager->snapshot().document(editor->file()->fileName())); - } - - connect(m_editor, SIGNAL(cursorPositionChanged()), - this, SLOT(updateSelectionInTree())); + connect(m_editor, SIGNAL(outlineModelIndexChanged(QModelIndex)), + this, SLOT(updateSelectionInTree(QModelIndex))); connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateSelectionInText(QItemSelection))); } @@ -82,32 +85,19 @@ void CppOutlineWidget::setCursorSynchronization(bool syncWithCursor) { m_enableCursorSync = syncWithCursor; if (m_enableCursorSync) - updateSelectionInTree(); + updateSelectionInTree(m_editor->outlineModelIndex()); } -void CppOutlineWidget::updateOutline(CPlusPlus::Document::Ptr document) +void CppOutlineWidget::modelUpdated() { - m_document = document; - if (document && m_editor - && (document->fileName() == m_editor->file()->fileName()) - && (document->editorRevision() == m_editor->editorRevision())) { - if (debug) - qDebug() << "CppOutline - rebuilding model"; - m_model->rebuild(document); - m_treeView->expandAll(); - updateSelectionInTree(); - } + m_treeView->expandAll(); } -void CppOutlineWidget::updateSelectionInTree() +void CppOutlineWidget::updateSelectionInTree(const QModelIndex &index) { if (!syncCursor()) return; - int line = m_editor->textCursor().blockNumber(); - int column = m_editor->textCursor().columnNumber(); - - QModelIndex index = indexForPosition(QModelIndex(), line, column); QModelIndex proxyIndex = m_proxyModel->mapFromSource(index); m_blockCursorSync = true; @@ -115,6 +105,7 @@ void CppOutlineWidget::updateSelectionInTree() qDebug() << "CppOutline - updating selection due to cursor move"; m_treeView->selectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect); + m_treeView->scrollTo(proxyIndex); m_blockCursorSync = false; } @@ -129,69 +120,21 @@ void CppOutlineWidget::updateSelectionInText(const QItemSelection &selection) CPlusPlus::Symbol *symbol = m_model->symbolFromIndex(index); if (symbol) { m_blockCursorSync = true; - unsigned line, column; - m_document->translationUnit()->getPosition(symbol->startOffset(), &line, &column); if (debug) - qDebug() << "CppOutline - moving cursor to" << line << column - 1; + qDebug() << "CppOutline - moving cursor to" << symbol->line() << symbol->column() - 1; + + Core::EditorManager *editorManager = Core::EditorManager::instance(); + editorManager->cutForwardNavigationHistory(); + editorManager->addCurrentPositionToNavigationHistory(); // line has to be 1 based, column 0 based! - m_editor->gotoLine(line, column - 1); + m_editor->gotoLine(symbol->line(), symbol->column() - 1); m_blockCursorSync = false; } } } -QModelIndex CppOutlineWidget::indexForPosition(const QModelIndex &rootIndex, int line, int column) -{ - QModelIndex result = rootIndex; - - const int rowCount = m_model->rowCount(rootIndex); - for (int row = 0; row < rowCount; ++row) { - QModelIndex index = m_model->index(row, 0, rootIndex); - CPlusPlus::Symbol *symbol = m_model->symbolFromIndex(index); - if (symbol && positionInsideSymbol(line, column, symbol)) { - // recurse to children - result = indexForPosition(index, line, column); - } - } - - return result; -} - -bool CppOutlineWidget::positionInsideSymbol(unsigned cursorLine, unsigned cursorColumn, CPlusPlus::Symbol *symbol) const -{ - if (!m_document) - return false; - CPlusPlus::TranslationUnit *translationUnit = m_document->translationUnit(); - - unsigned symbolStartLine = -1; - unsigned symbolStartColumn = -1; - - translationUnit->getPosition(symbol->startOffset(), &symbolStartLine, &symbolStartColumn); - - // normalize to 0 based - --symbolStartLine; - --symbolStartColumn; - - if (symbolStartLine < cursorLine - || (symbolStartLine == cursorLine && symbolStartColumn <= cursorColumn)) { - unsigned symbolEndLine = -1; - unsigned symbolEndColumn = -1; - translationUnit->getPosition(symbol->endOffset(), &symbolEndLine, &symbolEndColumn); - - // normalize to 0 based - --symbolEndLine; - --symbolEndColumn; - - if (symbolEndLine > cursorLine - || (symbolEndLine == cursorLine && symbolEndColumn >= cursorColumn)) { - return true; - } - } - return false; -} - bool CppOutlineWidget::syncCursor() { return m_enableCursorSync && !m_blockCursorSync; diff --git a/src/plugins/cppeditor/cppoutline.h b/src/plugins/cppeditor/cppoutline.h index 96443811cc..3eea8b3c65 100644 --- a/src/plugins/cppeditor/cppoutline.h +++ b/src/plugins/cppeditor/cppoutline.h @@ -22,10 +22,12 @@ class CppOutlineFilterModel : public QSortFilterProxyModel { Q_OBJECT public: - CppOutlineFilterModel(QObject *parent); + CppOutlineFilterModel(CPlusPlus::OverviewModel *sourceModel, QObject *parent); // QSortFilterProxyModel bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; +private: + CPlusPlus::OverviewModel *m_sourceModel; }; class CppOutlineWidget : public TextEditor::IOutlineWidget @@ -38,13 +40,11 @@ public: virtual void setCursorSynchronization(bool syncWithCursor); private slots: - void updateOutline(CPlusPlus::Document::Ptr document); - void updateSelectionInTree(); + void modelUpdated(); + void updateSelectionInTree(const QModelIndex &index); void updateSelectionInText(const QItemSelection &selection); private: - QModelIndex indexForPosition(const QModelIndex &rootIndex, int line, int column); - bool positionInsideSymbol(unsigned cursorLine, unsigned cursorColumn, CPlusPlus::Symbol *symbol) const; bool syncCursor(); private: @@ -52,7 +52,6 @@ private: CppOutlineTreeView *m_treeView; CPlusPlus::OverviewModel *m_model; CppOutlineFilterModel *m_proxyModel; - CPlusPlus::Document::Ptr m_document; bool m_enableCursorSync; bool m_blockCursorSync; diff --git a/src/plugins/cppeditor/cppplugin.cpp b/src/plugins/cppeditor/cppplugin.cpp index c1dfeff08c..2b9e980f82 100644 --- a/src/plugins/cppeditor/cppplugin.cpp +++ b/src/plugins/cppeditor/cppplugin.cpp @@ -135,7 +135,7 @@ CppPlugin *CppPlugin::m_instance = 0; CppPlugin::CppPlugin() : m_actionHandler(0), - m_sortedMethodOverview(false), + m_sortedOutline(false), m_renameSymbolUnderCursorAction(0), m_findUsagesAction(0), m_updateCodeModelAction(0) @@ -176,19 +176,19 @@ void CppPlugin::initializeEditor(CPPEditor *editor) this, SLOT(quickFix(TextEditor::ITextEditable*))); // method combo box sorting - connect(this, SIGNAL(methodOverviewSortingChanged(bool)), - editor, SLOT(setSortedMethodOverview(bool))); + connect(this, SIGNAL(outlineSortingChanged(bool)), + editor, SLOT(setSortedOutline(bool))); } -void CppPlugin::setSortedMethodOverview(bool sorted) +void CppPlugin::setSortedOutline(bool sorted) { - m_sortedMethodOverview = sorted; - emit methodOverviewSortingChanged(sorted); + m_sortedOutline = sorted; + emit outlineSortingChanged(sorted); } -bool CppPlugin::sortedMethodOverview() const +bool CppPlugin::sortedOutline() const { - return m_sortedMethodOverview; + return m_sortedOutline; } CppQuickFixCollector *CppPlugin::quickFixCollector() const @@ -308,21 +308,22 @@ bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMess void CppPlugin::readSettings() { - m_sortedMethodOverview = Core::ICore::instance()->settings()->value("CppTools/SortedMethodOverview", false).toBool(); + m_sortedOutline = Core::ICore::instance()->settings()->value("CppTools/SortedMethodOverview", false).toBool(); } void CppPlugin::writeSettings() { - Core::ICore::instance()->settings()->setValue("CppTools/SortedMethodOverview", m_sortedMethodOverview); + Core::ICore::instance()->settings()->setValue("CppTools/SortedMethodOverview", m_sortedOutline); } void CppPlugin::extensionsInitialized() { } -void CppPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag CppPlugin::aboutToShutdown() { writeSettings(); + return SynchronousShutdown; } void CppPlugin::switchDeclarationDefinition() diff --git a/src/plugins/cppeditor/cppplugin.h b/src/plugins/cppeditor/cppplugin.h index c7d4b82c3c..517da5c2f9 100644 --- a/src/plugins/cppeditor/cppplugin.h +++ b/src/plugins/cppeditor/cppplugin.h @@ -60,20 +60,20 @@ public: bool initialize(const QStringList &arguments, QString *error_message = 0); void extensionsInitialized(); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); // Connect editor to settings changed signals. void initializeEditor(CPPEditor *editor); - bool sortedMethodOverview() const; + bool sortedOutline() const; CppQuickFixCollector *quickFixCollector() const; signals: - void methodOverviewSortingChanged(bool sort); + void outlineSortingChanged(bool sort); public slots: - void setSortedMethodOverview(bool sorted); + void setSortedOutline(bool sorted); private slots: void switchDeclarationDefinition(); @@ -93,7 +93,7 @@ private: static CppPlugin *m_instance; TextEditor::TextEditorActionHandler *m_actionHandler; - bool m_sortedMethodOverview; + bool m_sortedOutline; QAction *m_renameSymbolUnderCursorAction; QAction *m_findUsagesAction; QAction *m_updateCodeModelAction; diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp index 29dc9ed1b6..b717d63254 100644 --- a/src/plugins/cppeditor/cppquickfix.cpp +++ b/src/plugins/cppeditor/cppquickfix.cpp @@ -29,11 +29,13 @@ #include "cppquickfix.h" #include "cppeditor.h" +#include "cppdeclfromdef.h" #include <cplusplus/ASTPath.h> #include <cplusplus/CppDocument.h> #include <cplusplus/ResolveExpression.h> #include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> #include <TranslationUnit.h> #include <ASTVisitor.h> @@ -929,7 +931,7 @@ public: // We need to do a QCA::translate, so we need a context. // Use fully qualified class name: Overview oo; - foreach (const Name *n, LookupContext::fullyQualifiedName(function)) { + foreach (const Name *n, LookupContext::path(function)) { if (!m_context.isEmpty()) m_context.append(QLatin1String("::")); m_context.append(oo.prettyName(n)); @@ -1217,6 +1219,135 @@ public: }; +/* + Adds missing case statements for "switch (enumVariable)" +*/ +class CompleteSwitchCaseStatement: public CppQuickFixOperation +{ +public: + CompleteSwitchCaseStatement(TextEditor::BaseTextEditor *editor) + : CppQuickFixOperation(editor) + {} + + virtual QString description() const + { + return QApplication::translate("CppTools::QuickFix", "Complete Switch Statement"); + } + + virtual int match(const QList<AST *> &path) + { + if (path.isEmpty()) + return -1; // nothing to do + + // look for switch statement + for (int depth = path.size()-1; depth >= 0; --depth) { + AST *ast = path.at(depth); + SwitchStatementAST *switchStatement = ast->asSwitchStatement(); + if (switchStatement) { + if (!isCursorOn(switchStatement->switch_token) || !switchStatement->statement) + return -1; + compoundStatement = switchStatement->statement->asCompoundStatement(); + if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" + return -1; + // look if the condition's type is an enum + if (Enum *e = conditionEnum(switchStatement)) { + // check the possible enum values + values.clear(); + Overview prettyPrint; + for (unsigned i = 0; i < e->memberCount(); ++i) { + if (Declaration *decl = e->memberAt(i)->asDeclaration()) { + values << prettyPrint(decl->name()); + } + } + // Get the used values + CaseStatementCollector caseValues(document()->translationUnit()); + QStringList usedValues = caseValues(switchStatement); + // save the values that would be added + foreach (const QString &usedValue, usedValues) + values.removeAll(usedValue); + if (values.isEmpty()) + return -1; + return depth; + } + return -1; + } + } + + return -1; + } + + virtual void createChanges() + { + ChangeSet changes; + int start = endOf(compoundStatement->lbrace_token); + changes.insert(start, QLatin1String("\ncase ") + + values.join(QLatin1String(":\nbreak;\ncase ")) + + QLatin1String(":\nbreak;")); + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(compoundStatement)); + } + +protected: + Enum *conditionEnum(SwitchStatementAST *statement) + { + Block *block = statement->symbol; + Scope *scope = document()->scopeAt(block->line(), block->column()); + TypeOfExpression typeOfExpression; + typeOfExpression.init(document(), snapshot()); + const QList<LookupItem> results = typeOfExpression(statement->condition, + document(), + scope); + foreach (LookupItem result, results) { + FullySpecifiedType fst = result.type(); + if (Enum *e = result.declaration()->type()->asEnumType()) + return e; + if (NamedType *namedType = fst->asNamedType()) { + QList<Symbol *> candidates = + typeOfExpression.context().lookup(namedType->name(), scope); + foreach (Symbol *candidate, candidates) { + if (Enum *e = candidate->asEnum()) { + return e; + } + } + } + } + return 0; + } + class CaseStatementCollector : public ASTVisitor + { + public: + CaseStatementCollector(TranslationUnit *unit) : ASTVisitor(unit) {} + QStringList operator ()(AST *ast) + { + values.clear(); + foundCaseStatementLevel = false; + accept(ast); + return values; + } + + bool preVisit(AST *ast) { + if (CaseStatementAST *cs = ast->asCaseStatement()) { + foundCaseStatementLevel = true; + if (SimpleNameAST *sm = cs->expression->asSimpleName()) { + Overview prettyPrint; + values << prettyPrint(sm->name); + } + return true; + } else if (foundCaseStatementLevel) { + return false; + } + return true; + } + + bool foundCaseStatementLevel; + QStringList values; + }; + +protected: + CompoundStatementAST *compoundStatement; + QStringList values; +}; + } // end of anonymous namespace @@ -1415,6 +1546,8 @@ QList<TextEditor::QuickFixOperation::Ptr> CppQuickFixFactory::quickFixOperations QSharedPointer<ConvertNumericToHex> convertNumericToHex(new ConvertNumericToHex(editor)); QSharedPointer<ConvertNumericToOctal> convertNumericToOctal(new ConvertNumericToOctal(editor)); QSharedPointer<ConvertNumericToDecimal> convertNumericToDecimal(new ConvertNumericToDecimal(editor)); + QSharedPointer<CompleteSwitchCaseStatement> completeSwitchCaseStatement(new CompleteSwitchCaseStatement(editor)); + QSharedPointer<DeclFromDef> declFromDef(new DeclFromDef(editor)); quickFixOperations.append(rewriteLogicalAndOp); quickFixOperations.append(splitIfStatementOp); @@ -1429,6 +1562,11 @@ QList<TextEditor::QuickFixOperation::Ptr> CppQuickFixFactory::quickFixOperations quickFixOperations.append(convertNumericToHex); quickFixOperations.append(convertNumericToOctal); quickFixOperations.append(convertNumericToDecimal); + quickFixOperations.append(completeSwitchCaseStatement); + +#if 0 + quickFixOperations.append(declFromDef); +#endif if (editor->mimeType() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE) quickFixOperations.append(wrapCString); diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcodecompletion.cpp index 733671fa51..3f90f644c0 100644 --- a/src/plugins/cpptools/cppcodecompletion.cpp +++ b/src/plugins/cpptools/cppcodecompletion.cpp @@ -200,7 +200,7 @@ protected: { _item = newCompletionItem(name); } virtual void visit(const QualifiedNameId *name) - { _item = newCompletionItem(name->unqualifiedNameId()); } + { _item = newCompletionItem(name->name()); } }; struct CompleteFunctionDeclaration @@ -1723,7 +1723,33 @@ QList<TextEditor::CompletionItem> CppCodeCompletion::getCompletions() return completionItems; } -void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) +bool CppCodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) +{ + if (item.data.canConvert<QString>()) // snippet + return false; + + if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) + return typedChar == QLatin1Char('(') + || typedChar == QLatin1Char(','); + + if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) + return typedChar == QLatin1Char('/') + && item.text.endsWith(QLatin1Char('/')); + + if (item.data.value<Symbol *>()) + return typedChar == QLatin1Char(':') + || typedChar == QLatin1Char(';') + || typedChar == QLatin1Char('.') + || typedChar == QLatin1Char(',') + || typedChar == QLatin1Char('('); + + if (item.data.canConvert<CompleteFunctionDeclaration>()) + return typedChar == QLatin1Char('('); + + return false; +} + +void CppCodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) { Symbol *symbol = 0; @@ -1749,10 +1775,15 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { toInsert = item.text; extraChars += QLatin1Char(')'); + + if (typedChar == QLatin1Char('(')) // Eat the opening parenthesis + typedChar = QChar(); } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) { toInsert = item.text; if (!toInsert.endsWith(QLatin1Char('/'))) extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"'); + else if (typedChar == QLatin1Char('/')) // Eat the slash + typedChar = QChar(); } else { toInsert = item.text; @@ -1768,7 +1799,7 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (! function->hasReturnType() && (function->identity() && !function->identity()->isDestructorNameId())) { // Don't insert any magic, since the user might have just wanted to select the class - } else if (function->templateParameterCount() != 0) { + } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) { // If there are no arguments, then we need the template specification if (function->argumentCount() == 0) { extraChars += QLatin1Char('<'); @@ -1777,32 +1808,50 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (completionSettings().m_spaceAfterFunctionName) extraChars += QLatin1Char(' '); extraChars += QLatin1Char('('); + if (typedChar == QLatin1Char('(')) + typedChar = QChar(); // If the function doesn't return anything, automatically place the semicolon, // unless we're doing a scope completion (then it might be function definition). - bool endWithSemicolon = function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON; + const QChar characterAtCursor = m_editor->characterAt(m_editor->position()); + bool endWithSemicolon = typedChar == QLatin1Char(';') + || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON); + const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar; + + if (endWithSemicolon && characterAtCursor == semicolon) { + endWithSemicolon = false; + typedChar = QChar(); + } // If the function takes no arguments, automatically place the closing parenthesis if (item.duplicateCount == 0 && ! function->hasArguments()) { extraChars += QLatin1Char(')'); - if (endWithSemicolon) - extraChars += QLatin1Char(';'); + if (endWithSemicolon) { + extraChars += semicolon; + typedChar = QChar(); + } } else if (autoParenthesesEnabled) { const QChar lookAhead = m_editor->characterAt(m_editor->position() + 1); if (MatchingText::shouldInsertMatchingText(lookAhead)) { extraChars += QLatin1Char(')'); --cursorOffset; if (endWithSemicolon) { - extraChars += QLatin1Char(';'); + extraChars += semicolon; --cursorOffset; + typedChar = QChar(); } } + // TODO: When an opening parenthesis exists, the "semicolon" should really be + // inserted after the matching closing parenthesis. } } } } if (autoInsertBrackets && item.data.canConvert<CompleteFunctionDeclaration>()) { + if (typedChar == QLatin1Char('(')) + typedChar = QChar(); + // everything from the closing parenthesis on are extra chars, to // make sure an auto-inserted ")" gets replaced by ") const" if necessary int closingParen = toInsert.lastIndexOf(QLatin1Char(')')); @@ -1811,6 +1860,13 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) } } + // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before + if (!typedChar.isNull()) { + extraChars += typedChar; + if (cursorOffset != 0) + --cursorOffset; + } + // Avoid inserting characters that are already there for (int i = 0; i < extraChars.length(); ++i) { const QChar a = extraChars.at(i); @@ -1836,7 +1892,7 @@ bool CppCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { return false; } else if (completionItems.count() == 1) { - complete(completionItems.first()); + complete(completionItems.first(), QChar()); return true; } else if (m_completionOperator != T_LPAREN) { return TextEditor::ICompletionCollector::partiallyComplete(completionItems); diff --git a/src/plugins/cpptools/cppcodecompletion.h b/src/plugins/cpptools/cppcodecompletion.h index 513b038407..406d4a2f87 100644 --- a/src/plugins/cpptools/cppcodecompletion.h +++ b/src/plugins/cpptools/cppcodecompletion.h @@ -78,7 +78,8 @@ public: int startCompletion(TextEditor::ITextEditable *editor); void completions(QList<TextEditor::CompletionItem> *completions); - void complete(const TextEditor::CompletionItem &item); + bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); + void complete(const TextEditor::CompletionItem &item, QChar typedChar); bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); void cleanup(); diff --git a/src/plugins/cpptools/cppcodeformatter.cpp b/src/plugins/cpptools/cppcodeformatter.cpp index 45c79e783e..75fe53fda8 100644 --- a/src/plugins/cpptools/cppcodeformatter.cpp +++ b/src/plugins/cpptools/cppcodeformatter.cpp @@ -1,3 +1,32 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + #include "cppcodeformatter.h" #include <Token.h> @@ -828,7 +857,7 @@ int CodeFormatter::tokenizeBlock(const QTextBlock &block, bool *endedJoined) *endedJoined = tokenize.endedJoined(); const int lexerState = tokenize.state(); - TextBlockUserData::setLexerState(block, lexerState); + BaseTextDocumentLayout::setLexerState(block, lexerState); return lexerState; } @@ -913,12 +942,12 @@ bool QtStyleCodeFormatter::loadBlockData(const QTextBlock &block, BlockData *dat void QtStyleCodeFormatter::saveLexerState(QTextBlock *block, int state) const { - TextBlockUserData::setLexerState(*block, state); + BaseTextDocumentLayout::setLexerState(*block, state); } int QtStyleCodeFormatter::loadLexerState(const QTextBlock &block) const { - return TextBlockUserData::lexerState(block); + return BaseTextDocumentLayout::lexerState(block); } void QtStyleCodeFormatter::onEnter(int newState, int *indentDepth, int *savedIndentDepth) const diff --git a/src/plugins/cpptools/cppcodeformatter.h b/src/plugins/cpptools/cppcodeformatter.h index bc615586fe..fb3db4997f 100644 --- a/src/plugins/cpptools/cppcodeformatter.h +++ b/src/plugins/cpptools/cppcodeformatter.h @@ -1,3 +1,32 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + #ifndef CPPCODEFORMATTER_H #define CPPCODEFORMATTER_H diff --git a/src/plugins/cpptools/cppfindreferences.cpp b/src/plugins/cpptools/cppfindreferences.cpp index 194b4ed790..d785bf1097 100644 --- a/src/plugins/cpptools/cppfindreferences.cpp +++ b/src/plugins/cpptools/cppfindreferences.cpp @@ -148,7 +148,7 @@ public: CppFindReferences::CppFindReferences(CppTools::CppModelManagerInterface *modelManager) : QObject(modelManager), _modelManager(modelManager), - _resultWindow(ExtensionSystem::PluginManager::instance()->getObject<Find::SearchResultWindow>()) + _resultWindow(Find::SearchResultWindow::instance()) { m_watcher.setPendingResultsLimit(1); connect(&m_watcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(displayResults(int,int))); diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index dcff150363..9b84e8012b 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -160,8 +160,9 @@ void CppToolsPlugin::extensionsInitialized() m_modelManager->setHeaderSuffixes(mimeType.suffixes()); } -void CppToolsPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag CppToolsPlugin::aboutToShutdown() { + return SynchronousShutdown; } void CppToolsPlugin::switchHeaderSource() diff --git a/src/plugins/cpptools/cpptoolsplugin.h b/src/plugins/cpptools/cpptoolsplugin.h index 97e95b017f..532662b2ae 100644 --- a/src/plugins/cpptools/cpptoolsplugin.h +++ b/src/plugins/cpptools/cpptoolsplugin.h @@ -65,7 +65,7 @@ public: bool initialize(const QStringList &arguments, QString *error_message); void extensionsInitialized(); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); CppModelManager *cppModelManager() { return m_modelManager; } QString correspondingHeaderOrSource(const QString &fileName) const; diff --git a/src/plugins/cpptools/searchsymbols.cpp b/src/plugins/cpptools/searchsymbols.cpp index f66700bf30..99c2616489 100644 --- a/src/plugins/cpptools/searchsymbols.cpp +++ b/src/plugins/cpptools/searchsymbols.cpp @@ -96,10 +96,9 @@ bool SearchSymbols::visit(Function *symbol) QString extraScope; if (const Name *name = symbol->name()) { - if (const QualifiedNameId *nameId = name->asQualifiedNameId()) { - if (nameId->nameCount() > 1) { - extraScope = overview.prettyName(nameId->nameAt(nameId->nameCount() - 2)); - } + if (const QualifiedNameId *q = name->asQualifiedNameId()) { + if (q->base()) + extraScope = overview.prettyName(q->base()); } } QString fullScope = _scope; diff --git a/src/plugins/debugger/cdb/breakpoint.cpp b/src/plugins/debugger/cdb/breakpoint.cpp index 49a6550990..3f2525920c 100644 --- a/src/plugins/debugger/cdb/breakpoint.cpp +++ b/src/plugins/debugger/cdb/breakpoint.cpp @@ -257,9 +257,9 @@ static bool mapDeviceToDriveLetter(QString *s) for (const TCHAR *driveLetter = driveLetters; *driveLetter; driveLetter++) { szDrive[0] = *driveLetter; // Look up each device name if (QueryDosDevice(szDrive, driveName, MAX_PATH)) { - const QString deviceName = QString::fromUtf16(driveName); + const QString deviceName = QString::fromWCharArray(driveName); if (s->startsWith(deviceName)) { - s->replace(0, deviceName.size(), QString::fromUtf16(szDrive)); + s->replace(0, deviceName.size(), QString::fromWCharArray(szDrive)); return true; } } @@ -275,7 +275,7 @@ static bool mapDeviceToDriveLetter(QString *s) static inline QString normalizeFileNameCaseHelper(const QString &f) { - HANDLE hFile = CreateFile(f.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + HANDLE hFile = CreateFile((const wchar_t*)f.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) return f; // Get the file size. We need a non-empty file to map it. @@ -305,7 +305,7 @@ static inline QString normalizeFileNameCaseHelper(const QString &f) pszFilename[0] = 0; // Get a file name of the form "/Device/HarddiskVolume1/file.cpp" if (GetMappedFileName (GetCurrentProcess(), pMem, pszFilename, MAX_PATH)) { - rc = QString::fromUtf16(pszFilename); + rc = QString::fromWCharArray(pszFilename); if (!mapDeviceToDriveLetter(&rc)) rc.clear(); } diff --git a/src/plugins/debugger/cdb/coreengine.cpp b/src/plugins/debugger/cdb/coreengine.cpp index 324d470831..c84b7b9264 100644 --- a/src/plugins/debugger/cdb/coreengine.cpp +++ b/src/plugins/debugger/cdb/coreengine.cpp @@ -296,7 +296,7 @@ bool CoreEngine::init(const QString &dllEnginePath, QString *errorMessage) *errorMessage = msgComFailed("GetImagePathWide", hr); return false; } - m_baseImagePath = QString::fromUtf16(buf); + m_baseImagePath = QString::fromWCharArray(buf); hr = lib.debugCreate( __uuidof(IDebugRegisters2), reinterpret_cast<void**>(&m_cif.debugRegisters)); if (FAILED(hr)) { @@ -447,7 +447,7 @@ bool CoreEngine::startDebuggerWithExecutable(const QString &workingDirectory, PCWSTR workingDirC = 0; const QString workingDirN = workingDirectory.isEmpty() ? QString() : QDir::toNativeSeparators(workingDirectory); if (!workingDirN.isEmpty()) - workingDirC = workingDirN.utf16(); + workingDirC = (PCWSTR)workingDirN.utf16(); hr = m_cif.debugClient->CreateProcess2Wide(NULL, reinterpret_cast<PWSTR>(const_cast<ushort *>(cmd.utf16())), &dbgopts, sizeof(dbgopts), @@ -820,7 +820,7 @@ quint64 CoreEngine::getSourceLineAddress(const QString &file, { ULONG64 rc = 0; const HRESULT hr = m_cif.debugSymbols->GetOffsetByLineWide(line, - const_cast<ushort*>(file.utf16()), + (wchar_t*)(file.utf16()), &rc); if (FAILED(hr)) { *errorMessage = QString::fromLatin1("Unable to determine address of %1:%2 : %3"). @@ -830,6 +830,11 @@ quint64 CoreEngine::getSourceLineAddress(const QString &file, return rc; } +void CoreEngine::outputVersion() +{ + m_cif.debugControl->OutputVersionInformation(DEBUG_OUTCTL_ALL_CLIENTS); +} + bool CoreEngine::autoDetectPath(QString *outPath, QStringList *checkedDirectories /* = 0 */) { @@ -978,10 +983,10 @@ static void formatEventFilter(CIDebugControl *ctl, unsigned long start, unsigned HRESULT hr = ctl->GetEventFilterTextWide(i, buffer, bufSize, 0); if (SUCCEEDED(hr)) { ULONG size; - str << "- #" << i << " \"" << QString::fromUtf16(buffer) << '"'; + str << "- #" << i << " \"" << QString::fromWCharArray(buffer) << '"'; hr = ctl->GetEventFilterCommandWide(i, buffer, bufSize, &size); if (SUCCEEDED(hr) && size > 1) - str << " command: '" << QString::fromUtf16(buffer) << '\''; + str << " command: '" << QString::fromWCharArray(buffer) << '\''; if (isException) { DEBUG_EXCEPTION_FILTER_PARAMETERS exceptionParameters; hr = ctl->GetExceptionFilterParameters(1, 0, i, &exceptionParameters); @@ -996,7 +1001,7 @@ static void formatEventFilter(CIDebugControl *ctl, unsigned long start, unsigned if (exceptionParameters.SecondCommandSize) { hr = ctl->GetExceptionFilterSecondCommandWide(i, buffer, bufSize, 0); if (SUCCEEDED(hr)) - str << " 2nd-command '" << QString::fromUtf16(buffer) << '\''; + str << " 2nd-command '" << QString::fromWCharArray(buffer) << '\''; } } } // isException diff --git a/src/plugins/debugger/cdb/coreengine.h b/src/plugins/debugger/cdb/coreengine.h index c73fc7e72d..c6e25e9ced 100644 --- a/src/plugins/debugger/cdb/coreengine.h +++ b/src/plugins/debugger/cdb/coreengine.h @@ -168,6 +168,9 @@ signals: // feature of the engine. void modulesLoaded(); +public slots: + void outputVersion(); + protected: virtual void timerEvent(QTimerEvent* te); diff --git a/src/plugins/debugger/debuggeragents.cpp b/src/plugins/debugger/debuggeragents.cpp index 7ce93ba1c9..2e6ded7373 100644 --- a/src/plugins/debugger/debuggeragents.cpp +++ b/src/plugins/debugger/debuggeragents.cpp @@ -50,6 +50,7 @@ #include <utils/qtcassert.h> #include <QtCore/QDebug> +#include <QtCore/QMetaObject> #include <QtGui/QMessageBox> #include <QtGui/QPlainTextEdit> @@ -74,6 +75,8 @@ namespace Internal { it handles communication between the engine and the bineditor. */ +namespace { const int DataRange = 1024 * 1024; } + MemoryViewAgent::MemoryViewAgent(DebuggerEngine *engine, quint64 addr) : QObject(engine), m_engine(engine) { @@ -113,11 +116,15 @@ void MemoryViewAgent::createBinEditor(quint64 addr) connect(editor->widget(), SIGNAL(newRangeRequested(Core::IEditor *, quint64)), this, SLOT(provideNewRange(Core::IEditor*,quint64))); + connect(editor->widget(), SIGNAL(startOfFileRequested(Core::IEditor *)), + this, SLOT(handleStartOfFileRequested(Core::IEditor*))); + connect(editor->widget(), SIGNAL(endOfFileRequested(Core::IEditor *)), + this, SLOT(handleEndOfFileRequested(Core::IEditor*))); m_editors << editor; editorManager->activateEditor(editor); QMetaObject::invokeMethod(editor->widget(), "setNewWindowRequestAllowed"); QMetaObject::invokeMethod(editor->widget(), "setLazyData", - Q_ARG(quint64, addr), Q_ARG(int, 1024 * 1024), Q_ARG(int, BinBlockSize)); + Q_ARG(quint64, addr), Q_ARG(int, DataRange), Q_ARG(int, BinBlockSize)); } else { DebuggerPlugin::instance()->showMessageBox(QMessageBox::Warning, tr("No memory viewer available"), @@ -147,10 +154,26 @@ void MemoryViewAgent::addLazyData(QObject *editorToken, quint64 addr, void MemoryViewAgent::provideNewRange(Core::IEditor *editor, quint64 address) { QMetaObject::invokeMethod(editor->widget(), "setLazyData", - Q_ARG(quint64, address), Q_ARG(int, 1024 * 1024), + Q_ARG(quint64, address), Q_ARG(int, DataRange), Q_ARG(int, BinBlockSize)); } +// Since we are not dealing with files, we take these signals to mean +// "move to start/end of range". This seems to make more sense than +// jumping to the start or end of the address space, respectively. +void MemoryViewAgent::handleStartOfFileRequested(Core::IEditor *editor) +{ + QMetaObject::invokeMethod(editor->widget(), + "setCursorPosition", Q_ARG(int, 0)); +} + +void MemoryViewAgent::handleEndOfFileRequested(Core::IEditor *editor) +{ + QMetaObject::invokeMethod(editor->widget(), + "setCursorPosition", Q_ARG(int, DataRange - 1)); +} + + /////////////////////////////////////////////////////////////////////// // diff --git a/src/plugins/debugger/debuggeragents.h b/src/plugins/debugger/debuggeragents.h index 85eab2f353..396b06fad5 100644 --- a/src/plugins/debugger/debuggeragents.h +++ b/src/plugins/debugger/debuggeragents.h @@ -63,6 +63,8 @@ private: Q_SLOT void createBinEditor(quint64 startAddr); Q_SLOT void fetchLazyData(Core::IEditor *, quint64 block, bool sync); Q_SLOT void provideNewRange(Core::IEditor *editor, quint64 address); + Q_SLOT void handleStartOfFileRequested(Core::IEditor *editor); + Q_SLOT void handleEndOfFileRequested(Core::IEditor *editor); QList<QPointer<Core::IEditor> > m_editors; QPointer<DebuggerEngine> m_engine; diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp index 3795f73688..b0ced81a84 100644 --- a/src/plugins/debugger/debuggerdialogs.cpp +++ b/src/plugins/debugger/debuggerdialogs.cpp @@ -207,6 +207,8 @@ void AttachCoreDialog::setCoreFile(const QString &fileName) // /////////////////////////////////////////////////////////////////////// +#ifndef Q_OS_WIN + static bool isUnixProcessId(const QString &procname) { for (int i = 0; i != procname.size(); ++i) @@ -288,6 +290,7 @@ static QList<ProcData> unixProcessList() } return rc; } +#endif // Q_OS_WIN static QList<ProcData> processList() { diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index 0f7ba09523..c542087c25 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -87,7 +87,7 @@ public: QString dumperLibrary; QStringList dumperLibraryLocations; - Core::SshServerInfo sshserver; + Core::SshConnectionParameters connParams; DebuggerStartMode startMode; }; diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 7e847eab48..c1b2a7fbcb 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -2480,13 +2480,14 @@ void DebuggerPlugin::clearCppCodeModelSnapshot() d->m_codeModelSnapshot = CPlusPlus::Snapshot(); } -void DebuggerPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag DebuggerPlugin::aboutToShutdown() { writeSettings(); if (d->m_uiSwitcher) d->m_uiSwitcher->aboutToShutdown(); //if (d->m_engine) // d->m_engine->shutdown(); + return SynchronousShutdown; } void DebuggerPlugin::showMessage(const QString &msg, int channel, int timeout) diff --git a/src/plugins/debugger/debuggerplugin.h b/src/plugins/debugger/debuggerplugin.h index d3fadff4c6..b183c3c855 100644 --- a/src/plugins/debugger/debuggerplugin.h +++ b/src/plugins/debugger/debuggerplugin.h @@ -121,7 +121,7 @@ private: friend class Internal::DebuggerListener ; bool initialize(const QStringList &arguments, QString *errorMessage); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); void extensionsInitialized(); void remoteCommand(const QStringList &options, const QStringList &arguments); diff --git a/src/plugins/debugger/gdb/remotegdbprocess.cpp b/src/plugins/debugger/gdb/remotegdbprocess.cpp index fb7e2d0281..7288f44c7b 100644 --- a/src/plugins/debugger/gdb/remotegdbprocess.cpp +++ b/src/plugins/debugger/gdb/remotegdbprocess.cpp @@ -33,14 +33,15 @@ #include <ctype.h> +using namespace Core; + namespace Debugger { namespace Internal { -RemoteGdbProcess::RemoteGdbProcess(const Core::SshServerInfo &server, +RemoteGdbProcess::RemoteGdbProcess(const Core::SshConnectionParameters &connParams, RemotePlainGdbAdapter *adapter, QObject *parent) - : AbstractGdbProcess(parent), m_serverInfo(server), m_adapter(adapter) + : AbstractGdbProcess(parent), m_connParams(connParams), m_adapter(adapter) { - } QByteArray RemoteGdbProcess::readAllStandardOutput() @@ -59,68 +60,122 @@ QByteArray RemoteGdbProcess::readAllStandardError() void RemoteGdbProcess::start(const QString &cmd, const QStringList &args) { - m_gdbConn = Core::InteractiveSshConnection::create(m_serverInfo); - m_appOutputConn = Core::InteractiveSshConnection::create(m_serverInfo); - m_errOutputConn = Core::InteractiveSshConnection::create(m_serverInfo); m_command = cmd; m_cmdArgs = args; - m_errOutputConn->start(); - m_appOutputConn->start(); - m_gdbConn->start(); - } + m_gdbStarted = false; + m_error.clear(); + m_conn = SshConnection::create(); + connect(m_conn.data(), SIGNAL(connected()), this, SLOT(handleConnected())); + connect(m_conn.data(), SIGNAL(error(SshError)), this, + SLOT(handleConnectionError())); + m_conn->connectToHost(m_connParams); +} -bool RemoteGdbProcess::waitForStarted() +void RemoteGdbProcess::handleConnected() { - if (!waitForInputReady(m_appOutputConn)) - return false; - if (!sendAndWaitForEcho(m_appOutputConn, readerCmdLine(AppOutputFile))) - return false; - if (!waitForInputReady(m_errOutputConn)) - return false; - if (!sendAndWaitForEcho(m_errOutputConn, readerCmdLine(ErrOutputFile))) - return false; - if (!waitForInputReady(m_gdbConn)) - return false; - connect(m_appOutputConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleAppOutput())); - connect(m_errOutputConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleErrOutput())); - connect(m_gdbConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleGdbOutput())); - m_gdbStarted = false; - m_gdbCmdLine = "stty -echo && DISPLAY=:0.0 " + m_command.toUtf8() + ' ' + m_fifoCreator = m_conn->createRemoteProcess( "rm -f " + + AppOutputFile + " && mkfifo " + AppOutputFile); + connect(m_fifoCreator.data(), SIGNAL(closed(int)), this, + SLOT(handleFifoCreationFinished(int))); + m_fifoCreator->start(); +} + +void RemoteGdbProcess::handleConnectionError() +{ + emitErrorExit(tr("Connection could not be established.")); +} + +void RemoteGdbProcess::handleFifoCreationFinished(int exitStatus) +{ + if (exitStatus != SshRemoteProcess::ExitedNormally) { + emitErrorExit(tr("Could not create FIFO.")); + } else { + m_appOutputReader = m_conn->createRemoteProcess("cat " + AppOutputFile); + connect(m_appOutputReader.data(), SIGNAL(started()), this, + SLOT(handleAppOutputReaderStarted())); + connect(m_appOutputReader.data(), SIGNAL(closed(int)), this, + SLOT(handleAppOutputReaderFinished(int))); + m_appOutputReader->start(); + } +} + +void RemoteGdbProcess::handleAppOutputReaderStarted() +{ + connect(m_appOutputReader.data(), SIGNAL(outputAvailable(QByteArray)), + this, SLOT(handleAppOutput(QByteArray))); + QByteArray cmdLine = "DISPLAY=:0.0 " + m_command.toUtf8() + ' ' + m_cmdArgs.join(QLatin1String(" ")).toUtf8() - + " -tty=" + AppOutputFile + " 2>" + ErrOutputFile + '\n'; + + " -tty=" + AppOutputFile; if (!m_wd.isEmpty()) - m_gdbCmdLine.prepend("cd " + m_wd.toUtf8() + " && "); - if (sendInput(m_gdbCmdLine) != m_gdbCmdLine.count()) - return false; + cmdLine.prepend("cd " + m_wd.toUtf8() + " && "); + m_gdbProc = m_conn->createRemoteProcess(cmdLine); + connect(m_gdbProc.data(), SIGNAL(started()), this, + SLOT(handleGdbStarted())); + connect(m_gdbProc.data(), SIGNAL(closed(int)), this, + SLOT(handleGdbFinished(int))); + connect(m_gdbProc.data(), SIGNAL(outputAvailable(QByteArray)), this, + SLOT(handleGdbOutput(QByteArray))); + connect(m_gdbProc.data(), SIGNAL(errorOutputAvailable(QByteArray)), this, + SLOT(handleErrOutput(QByteArray))); + m_gdbProc->start(); +} + +void RemoteGdbProcess::handleAppOutputReaderFinished(int exitStatus) +{ + if (exitStatus != SshRemoteProcess::ExitedNormally) + emitErrorExit(tr("Application output reader unexpectedly finished.")); +} - return true; +void RemoteGdbProcess::handleGdbStarted() +{ + m_gdbStarted = true; +} + +void RemoteGdbProcess::handleGdbFinished(int exitStatus) +{ + switch (exitStatus) { + case SshRemoteProcess::FailedToStart: + emitErrorExit(tr("Remote gdb failed to start.")); + break; + case SshRemoteProcess::KilledBySignal: + emitErrorExit(tr("Remote gdb crashed.")); + break; + case SshRemoteProcess::ExitedNormally: + emit finished(m_gdbProc->exitCode(), QProcess::NormalExit); + break; + } + disconnect(m_conn.data(), 0, this, 0); + m_gdbProc = SshRemoteProcess::Ptr(); + m_appOutputReader = SshRemoteProcess::Ptr(); + m_conn->disconnectFromHost(); +} + +bool RemoteGdbProcess::waitForStarted() +{ + return m_error.isEmpty(); } qint64 RemoteGdbProcess::write(const QByteArray &data) { - if (!m_gdbStarted || !m_inputToSend.isEmpty() || !m_lastSeqNr.isEmpty()) { + if (!m_gdbStarted || !m_inputToSend.isEmpty() || !m_lastSeqNr.isEmpty()) m_inputToSend.enqueue(data); - return data.size(); - } else { - return sendInput(data); - } + else + sendInput(data); + return data.size(); } void RemoteGdbProcess::kill() { - stopReaders(); - Core::InteractiveSshConnection::Ptr controlConn - = Core::InteractiveSshConnection::create(m_serverInfo); - if (!controlConn->hasError()) { - if (controlConn->start()) - controlConn->sendInput("pkill -x gdb\r\n"); - } + SshRemoteProcess::Ptr killProc + = m_conn->createRemoteProcess("pkill -SIGKILL -x gdb"); + killProc->start(); +} - m_gdbConn->quit(); - emit finished(0, QProcess::CrashExit); +void RemoteGdbProcess::interruptInferior() +{ + SshRemoteProcess::Ptr intProc + = m_conn->createRemoteProcess("pkill -x -SIGINT gdb"); + intProc->start(); } QProcess::ProcessState RemoteGdbProcess::state() const @@ -130,35 +185,19 @@ QProcess::ProcessState RemoteGdbProcess::state() const QString RemoteGdbProcess::errorString() const { - return m_gdbConn ? m_gdbConn->error() : QString(); + return m_error; } -void RemoteGdbProcess::handleGdbOutput() +void RemoteGdbProcess::handleGdbOutput(const QByteArray &output) { - m_currentGdbOutput - += removeCarriageReturn(m_gdbConn->waitForRemoteOutput(0)); + // TODO: Carriage return removal still necessary? + m_currentGdbOutput += removeCarriageReturn(output); #if 0 qDebug("%s: complete unread output is '%s'", Q_FUNC_INFO, m_currentGdbOutput.data()); #endif - if (checkForGdbExit(m_currentGdbOutput)) { - m_currentGdbOutput.clear(); - return; - } - if (!m_currentGdbOutput.endsWith('\n')) return; - if (!m_gdbStarted) { - const int index = m_currentGdbOutput.indexOf(m_gdbCmdLine); - if (index != -1) - m_currentGdbOutput.remove(index, m_gdbCmdLine.size()); - // Note: We can't guarantee that we will match the command line, - // because the remote terminal sometimes inserts control characters. - // Otherwise we could set m_gdbStarted here. - } - - m_gdbStarted = true; - if (m_currentGdbOutput.contains(m_lastSeqNr + '^')) m_lastSeqNr.clear(); @@ -187,7 +226,7 @@ QProcessEnvironment RemoteGdbProcess::processEnvironment() const void RemoteGdbProcess::setProcessEnvironment(const QProcessEnvironment & /* env */) { - // TODO: Do something. + // TODO: Do something. (if remote process exists: set, otherwise queue) } void RemoteGdbProcess::setEnvironment(const QStringList & /* env */) @@ -211,48 +250,27 @@ int RemoteGdbProcess::findAnchor(const QByteArray &data) const return -1; } -qint64 RemoteGdbProcess::sendInput(const QByteArray &data) +void RemoteGdbProcess::sendInput(const QByteArray &data) { int pos; for (pos = 0; pos < data.size(); ++pos) if (!isdigit(data.at(pos))) break; m_lastSeqNr = data.left(pos); - return m_gdbConn->sendInput(data) ? data.size() : 0; + m_gdbProc->sendInput(data); } -void RemoteGdbProcess::handleAppOutput() +void RemoteGdbProcess::handleAppOutput(const QByteArray &output) { - m_adapter->handleApplicationOutput(m_appOutputConn->waitForRemoteOutput(0)); + m_adapter->handleApplicationOutput(output); } -void RemoteGdbProcess::handleErrOutput() +void RemoteGdbProcess::handleErrOutput(const QByteArray &output) { - m_errorOutput += m_errOutputConn->waitForRemoteOutput(0); + m_errorOutput += output; emit readyReadStandardError(); } -void RemoteGdbProcess::stopReaders() -{ - if (m_appOutputConn) { - disconnect(m_appOutputConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleAppOutput())); - m_appOutputConn->sendInput(CtrlC); - m_appOutputConn->quit(); - } - if (m_errOutputConn) { - disconnect(m_errOutputConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleErrOutput())); - m_errOutputConn->sendInput(CtrlC); - m_errOutputConn->quit(); - } -} - -QByteArray RemoteGdbProcess::readerCmdLine(const QByteArray &file) -{ - return "rm -f " + file + " && mkfifo " + file + " && cat " + file + "\r\n"; -} - QByteArray RemoteGdbProcess::removeCarriageReturn(const QByteArray &data) { QByteArray output; @@ -264,48 +282,16 @@ QByteArray RemoteGdbProcess::removeCarriageReturn(const QByteArray &data) return output; } -bool RemoteGdbProcess::checkForGdbExit(QByteArray &output) +void RemoteGdbProcess::emitErrorExit(const QString &error) { - const QByteArray exitString("^exit"); - const int exitPos = output.indexOf(exitString); - if (exitPos == -1) - return false; - - emit finished(0, QProcess::NormalExit); - disconnect(m_gdbConn.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleGdbOutput())); - output.remove(exitPos + exitString.size(), output.size()); - stopReaders(); - return true; -} - -bool RemoteGdbProcess::waitForInputReady(Core::InteractiveSshConnection::Ptr &conn) -{ - if (conn->waitForRemoteOutput(m_serverInfo.timeout).isEmpty()) - return false; - while (!conn->waitForRemoteOutput(100).isEmpty()) - ; - return true; -} - -bool RemoteGdbProcess::sendAndWaitForEcho(Core::InteractiveSshConnection::Ptr &conn, - const QByteArray &cmdLine) -{ - conn->sendInput(cmdLine); - QByteArray allOutput; - while (!allOutput.endsWith(cmdLine)) { - const QByteArray curOutput = conn->waitForRemoteOutput(100); - if (curOutput.isEmpty()) - return false; - allOutput += curOutput; + if (m_error.isEmpty()) { + m_error = error; + emit finished(-1, QProcess::CrashExit); } - return true; } - const QByteArray RemoteGdbProcess::CtrlC = QByteArray(1, 0x3); const QByteArray RemoteGdbProcess::AppOutputFile("app_output"); -const QByteArray RemoteGdbProcess::ErrOutputFile("err_output"); } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/gdb/remotegdbprocess.h b/src/plugins/debugger/gdb/remotegdbprocess.h index 1008b334da..9a9679b695 100644 --- a/src/plugins/debugger/gdb/remotegdbprocess.h +++ b/src/plugins/debugger/gdb/remotegdbprocess.h @@ -33,6 +33,7 @@ #include "abstractgdbprocess.h" #include <coreplugin/ssh/sshconnection.h> +#include <coreplugin/ssh/sshremoteprocess.h> #include <QtCore/QByteArray> #include <QtCore/QQueue> @@ -46,7 +47,7 @@ class RemoteGdbProcess : public AbstractGdbProcess { Q_OBJECT public: - RemoteGdbProcess(const Core::SshServerInfo &server, + RemoteGdbProcess(const Core::SshConnectionParameters &server, RemotePlainGdbAdapter *adapter, QObject *parent = 0); virtual QByteArray readAllStandardOutput(); @@ -65,32 +66,37 @@ public: virtual void setEnvironment(const QStringList &env); virtual void setWorkingDirectory(const QString &dir); + void interruptInferior(); + static const QByteArray CtrlC; private slots: - void handleGdbOutput(); - void handleAppOutput(); - void handleErrOutput(); + void handleConnected(); + void handleConnectionError(); + void handleFifoCreationFinished(int exitStatus); + void handleAppOutputReaderStarted(); + void handleAppOutputReaderFinished(int exitStatus); + void handleGdbStarted(); + void handleGdbFinished(int exitStatus); + void handleGdbOutput(const QByteArray &output); + void handleAppOutput(const QByteArray &output); + void handleErrOutput(const QByteArray &output); private: static QByteArray readerCmdLine(const QByteArray &file); int findAnchor(const QByteArray &data) const; - qint64 sendInput(const QByteArray &data); - void stopReaders(); + void sendInput(const QByteArray &data); QByteArray removeCarriageReturn(const QByteArray &data); - bool checkForGdbExit(QByteArray &output); - bool sendAndWaitForEcho(Core::InteractiveSshConnection::Ptr &conn, - const QByteArray &cmdLine); - bool waitForInputReady(Core::InteractiveSshConnection::Ptr &conn); + void emitErrorExit(const QString &error); static const QByteArray AppOutputFile; - static const QByteArray ErrOutputFile; - Core::SshServerInfo m_serverInfo; - Core::InteractiveSshConnection::Ptr m_gdbConn; - Core::InteractiveSshConnection::Ptr m_appOutputConn; - Core::InteractiveSshConnection::Ptr m_errOutputConn; + Core::SshConnectionParameters m_connParams; + Core::SshConnection::Ptr m_conn; + Core::SshRemoteProcess::Ptr m_gdbProc; + Core::SshRemoteProcess::Ptr m_appOutputReader; + Core::SshRemoteProcess::Ptr m_fifoCreator; QByteArray m_gdbOutput; QByteArray m_errorOutput; QString m_command; @@ -99,7 +105,7 @@ private: QQueue<QByteArray> m_inputToSend; QByteArray m_currentGdbOutput; QByteArray m_lastSeqNr; - QByteArray m_gdbCmdLine; + QString m_error; bool m_gdbStarted; RemotePlainGdbAdapter *m_adapter; diff --git a/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp b/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp index 2f51918883..88946c1136 100644 --- a/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp +++ b/src/plugins/debugger/gdb/remoteplaingdbadapter.cpp @@ -40,7 +40,7 @@ namespace Internal { RemotePlainGdbAdapter::RemotePlainGdbAdapter(GdbEngine *engine, QObject *parent) : AbstractPlainGdbAdapter(engine, parent), - m_gdbProc(engine->startParameters().sshserver, this) + m_gdbProc(engine->startParameters().connParams, this) { } @@ -60,7 +60,7 @@ void RemotePlainGdbAdapter::startAdapter() void RemotePlainGdbAdapter::interruptInferior() { - m_gdbProc.write(RemoteGdbProcess::CtrlC); + m_gdbProc.interruptInferior(); } QByteArray RemotePlainGdbAdapter::execFilePath() const diff --git a/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp b/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp index a3b9f7624d..f9bf949cbc 100644 --- a/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp +++ b/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp @@ -270,6 +270,7 @@ void TcfTrkGdbAdapter::tcftrkEvent(const tcftrk::TcfTrkEvent &e) switch (e.type()) { case tcftrk::TcfTrkEvent::LocatorHello: + m_trkDevice->sendLoggingAddListenerCommand(TcfTrkCallback()); startGdb(); // Commands are only accepted after hello break; case tcftrk::TcfTrkEvent::RunControlModuleLoadSuspended: // A module was loaded @@ -311,6 +312,9 @@ void TcfTrkGdbAdapter::tcftrkEvent(const tcftrk::TcfTrkEvent &e) Symbian::RegisterCount); } break; + case tcftrk::TcfTrkEvent::LoggingWriteEvent: // TODO: Not tested yet. + showMessage(e.toString(), AppOutput); + break; default: break; } diff --git a/src/plugins/debugger/shared/sharedlibraryinjector.cpp b/src/plugins/debugger/shared/sharedlibraryinjector.cpp index c83c2a402e..04c39071f5 100644 --- a/src/plugins/debugger/shared/sharedlibraryinjector.cpp +++ b/src/plugins/debugger/shared/sharedlibraryinjector.cpp @@ -50,7 +50,7 @@ template <class SymbolType> inline bool resolveSymbol(const char *libraryName, HMODULE libraryHandle, const char *symbolName, SymbolType *s, QString *errorMessage) { *s = 0; - FARPROC WINAPI vs = ::GetProcAddress(libraryHandle, symbolName); + FARPROC vs = ::GetProcAddress(libraryHandle, symbolName); if (vs == 0) { *errorMessage = QString::fromLatin1("Unable to resolve '%2' in '%1'.").arg(QString::fromAscii(symbolName), QString::fromAscii(libraryName)); return false; diff --git a/src/plugins/debugger/watchutils.cpp b/src/plugins/debugger/watchutils.cpp index be26c36f2c..4f4c2f542d 100644 --- a/src/plugins/debugger/watchutils.cpp +++ b/src/plugins/debugger/watchutils.cpp @@ -693,8 +693,8 @@ QString decodeData(const QByteArray &ba, int encoding) decodedBa[i + 1] = c; } //qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n"; - return doubleQuote + QString::fromUcs4(reinterpret_cast<const uint *> - (decodedBa.data()), decodedBa.size() / 4) + doubleQuote; + return doubleQuote + QString::fromUtf16(reinterpret_cast<const ushort *> + (decodedBa.data()), decodedBa.size() / 2) + doubleQuote; } } qDebug() << "ENCODING ERROR: " << encoding; diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index f5a44b0602..d4bde7f222 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -149,19 +149,21 @@ enum Mode enum SubMode { NoSubMode, - ChangeSubMode, // Used for c - DeleteSubMode, // Used for d - FilterSubMode, // Used for ! - IndentSubMode, // Used for = - RegisterSubMode, // Used for " - ShiftLeftSubMode, // Used for < - ShiftRightSubMode, // Used for > - TransformSubMode, // Used for ~/gu/gU - WindowSubMode, // Used for Ctrl-w - YankSubMode, // Used for y - ZSubMode, // Used for z - CapitalZSubMode, // Used for Z - ReplaceSubMode, // Used for r + ChangeSubMode, // Used for c + DeleteSubMode, // Used for d + FilterSubMode, // Used for ! + IndentSubMode, // Used for = + RegisterSubMode, // Used for " + ShiftLeftSubMode, // Used for < + ShiftRightSubMode, // Used for > + TransformSubMode, // Used for ~/gu/gU + WindowSubMode, // Used for Ctrl-w + YankSubMode, // Used for y + ZSubMode, // Used for z + CapitalZSubMode, // Used for Z + ReplaceSubMode, // Used for r + OpenSquareSubMode, // Used for [ + CloseSquareSubMode, // Used for ] }; /*! A \e SubSubMode is used for things that require one more data item @@ -232,6 +234,11 @@ struct Column int logical; // Column on screen. }; +QDebug operator<<(QDebug ts, const Column &col) +{ + return ts << "(p: " << col.physical << ", l: " << col.logical << ")"; +} + struct CursorPosition { // for jump history @@ -292,7 +299,7 @@ QDebug operator<<(QDebug ts, const Range &range) ExCommand::ExCommand(const QString &c, const QString &a, const Range &r) - : cmd(c), hasBang(false), args(a), range(r) + : cmd(c), hasBang(false), args(a), range(r), count(1) {} QDebug operator<<(QDebug ts, const ExCommand &cmd) @@ -600,12 +607,15 @@ public: EventResult handleCommandMode(const Input &); EventResult handleRegisterMode(const Input &); EventResult handleExMode(const Input &); + EventResult handleOpenSquareSubMode(const Input &); + EventResult handleCloseSquareSubMode(const Input &); EventResult handleSearchSubSubMode(const Input &); EventResult handleCommandSubSubMode(const Input &); void finishMovement(const QString &dotCommand = QString()); void finishMovement(const QString &dotCommand, int count); void resetCommandMode(); void search(const SearchData &sd); + void searchBalanced(bool forward, QChar needle, QChar other); void highlightMatches(const QString &needle); void stopIncrementalFind(); @@ -1680,6 +1690,34 @@ EventResult FakeVimHandler::Private::handleCommandSubSubMode(const Input &input) return handled; } +EventResult FakeVimHandler::Private::handleOpenSquareSubMode(const Input &input) +{ + EventResult handled = EventHandled; + m_submode = NoSubMode; + if (input.is('{')) { + searchBalanced(false, '{', '}'); + } else if (input.is('(')) { + searchBalanced(false, '(', ')'); + } else { + handled = EventUnhandled; + } + return handled; +} + +EventResult FakeVimHandler::Private::handleCloseSquareSubMode(const Input &input) +{ + EventResult handled = EventHandled; + m_submode = NoSubMode; + if (input.is('}')) { + searchBalanced(true, '}', '{'); + } else if (input.is(')')) { + searchBalanced(true, ')', '('); + } else { + handled = EventUnhandled; + } + return handled; +} + EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) { EventResult handled = EventHandled; @@ -1698,6 +1736,10 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } } else if (m_subsubmode != NoSubSubMode) { handleCommandSubSubMode(input); + } else if (m_submode == OpenSquareSubMode) { + handled = handleOpenSquareSubMode(input); + } else if (m_submode == CloseSquareSubMode) { + handled = handleCloseSquareSubMode(input); } else if (m_submode == WindowSubMode) { emit q->windowCommandRequested(input.key()); m_submode = NoSubMode; @@ -2035,22 +2077,25 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) m_opcount = m_mvcount; m_mvcount.clear(); m_submode = DeleteSubMode; - } else if ((input.is('d') || input.is('x')) && isVisualCharMode()) { - leaveVisualMode(); - m_submode = DeleteSubMode; - finishMovement(); - } else if ((input.is('d') || input.is('x')) && isVisualLineMode()) { - leaveVisualMode(); - m_rangemode = RangeLineMode; - yankText(currentRange(), m_register); - removeText(currentRange()); - handleStartOfLine(); - } else if ((input.is('d') || input.is('x')) && isVisualBlockMode()) { - leaveVisualMode(); - m_rangemode = RangeBlockMode; - yankText(currentRange(), m_register); - removeText(currentRange()); - setPosition(qMin(position(), anchor())); + } else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete)) + && isVisualMode()) { + if (isVisualCharMode()) { + leaveVisualMode(); + m_submode = DeleteSubMode; + finishMovement(); + } else if (isVisualLineMode()) { + leaveVisualMode(); + m_rangemode = RangeLineMode; + yankText(currentRange(), m_register); + removeText(currentRange()); + handleStartOfLine(); + } else if (isVisualBlockMode()) { + leaveVisualMode(); + m_rangemode = RangeBlockMode; + yankText(currentRange(), m_register); + removeText(currentRange()); + setPosition(qMin(position(), anchor())); + } } else if (input.is('D') && isNoVisualMode()) { if (atEndOfLine()) moveLeft(); @@ -2170,7 +2215,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) setCursorPosition(m_jumpListRedo.last()); m_jumpListRedo.pop_back(); } - } else if (input.is('j') || input.isKey(Key_Down)) { + } else if (input.is('j') || input.isKey(Key_Down) + || input.isControl('j') || input.isControl('n')) { m_movetype = MoveLineWise; setAnchor(); moveDown(count()); @@ -2198,7 +2244,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } endEditBlock(); finishMovement(); - } else if (input.is('k') || input.isKey(Key_Up)) { + } else if (input.is('k') || input.isKey(Key_Up) || input.isControl('p')) { m_movetype = MoveLineWise; setAnchor(); moveUp(count()); @@ -2472,6 +2518,10 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) else if (input.is('U')) m_subsubmode = UpCaseSubSubMode; finishMovement(); + } else if (input.is('[')) { + m_submode = OpenSquareSubMode; + } else if (input.is(']')) { + m_submode = CloseSquareSubMode; } else if (input.isKey(Key_PageDown) || input.isControl('f')) { moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen()); scrollToLine(cursorLine()); @@ -2486,6 +2536,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) setAnchor(); moveRight(qMin(1, rightDist())); removeText(currentRange()); + if (atEndOfLine()) + moveLeft(); } else if (input.isKey(Key_BracketLeft) || input.isKey(Key_BracketRight)) { } else if (input.isControl(Key_BracketRight)) { @@ -2624,11 +2676,11 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) if (col.logical <= ind.logical && col.logical && startsWithWhitespace(data, col.physical)) { const int ts = config(ConfigTabStop).toInt(); - const int newcol = col.logical - 1 - (col.logical - 1) % ts; - data.remove(0, col.physical); - setLineContents(line, tabExpand(newcol).append(data)); + const int newl = col.logical - 1 - (col.logical - 1) % ts; + const QString prefix = tabExpand(newl); + setLineContents(line, prefix + data.mid(col.physical)); moveToStartOfLine(); - moveRight(newcol); + moveRight(prefix.size()); m_lastInsertion.clear(); // FIXME } else { m_tc.deletePreviousChar(); @@ -2649,14 +2701,18 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) removeAutomaticIndentation(); moveUp(count() * (linesOnScreen() - 2)); m_lastInsertion.clear(); - } else if (input.isKey(Key_Tab) && hasConfig(ConfigExpandTab)) { + } else if (input.isKey(Key_Tab)) { m_justAutoIndented = 0; - const int ts = config(ConfigTabStop).toInt(); - const int col = logicalCursorColumn(); - QString str = QString(ts - col % ts, ' '); - m_lastInsertion.append(str); - insertText(str); - setTargetColumn(); + if (hasConfig(ConfigExpandTab)) { + const int ts = config(ConfigTabStop).toInt(); + const int col = logicalCursorColumn(); + QString str = QString(ts - col % ts, ' '); + m_lastInsertion.append(str); + insertText(str); + setTargetColumn(); + } else { + insertInInsertMode(input.raw()); + } } else if (input.isControl('d')) { // remove one level of indentation from the current line int shift = config(ConfigShiftWidth).toInt(); @@ -3393,7 +3449,7 @@ void FakeVimHandler::Private::handleExCommand(const QString &line0) if (line.startsWith(QLatin1Char('%'))) line = "1,$" + line.mid(1); - int beginLine = readLineCode(line); + const int beginLine = readLineCode(line); int endLine = -1; if (line.startsWith(',')) { line = line.mid(1); @@ -3411,6 +3467,8 @@ void FakeVimHandler::Private::handleExCommand(const QString &line0) cmd.hasBang = arg0.endsWith('!'); if (cmd.hasBang) cmd.cmd.chop(1); + if (beginLine != -1) + cmd.count = beginLine; //qDebug() << "CMD: " << cmd; enterCommandMode(); @@ -3459,6 +3517,40 @@ static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *fla //qDebug() << "NEEDLE " << needle0 << needle; } +void FakeVimHandler::Private::searchBalanced(bool forward, QChar needle, QChar other) +{ + int level = 1; + int pos = m_tc.position(); + const int npos = forward ? lastPositionInDocument() : 0; + QTextDocument *doc = m_tc.document(); + while (true) { + if (forward) + ++pos; + else + --pos; + if (pos == npos) + return; + QChar c = doc->characterAt(pos); + if (c == other) + ++level; + else if (c == needle) + --level; + if (level == 0) { + const int oldLine = cursorLine() - cursorLineOnScreen(); + m_tc.setPosition(pos, MoveAnchor); + m_tc.clearSelection(); + EDITOR(setTextCursor(m_tc)); + // Making this unconditional feels better, but is not "vim like". + if (oldLine != cursorLine() - cursorLineOnScreen()) + scrollToLine(cursorLine() - linesOnScreen() / 2); + setTargetColumn(); + updateSelection(); + recordJump(); + return; + } + } +} + void FakeVimHandler::Private::search(const SearchData &sd) { if (sd.needle.isEmpty()) @@ -3613,13 +3705,13 @@ void FakeVimHandler::Private::shiftRegionRight(int repeat) if (hasConfig(ConfigStartOfLine)) targetPos = firstPositionInLine(beginLine); - int len = config(ConfigShiftWidth).toInt() * repeat; - QString indent(len, ' '); - + const int sw = config(ConfigShiftWidth).toInt(); beginEditBlock(targetPos); for (int line = beginLine; line <= endLine; ++line) { - setPosition(firstPositionInLine(line)); - m_tc.insertText(indent); + QString data = lineContents(line); + const Column col = indentation(data); + data = tabExpand(col.logical + sw * repeat) + data.mid(col.physical); + setLineContents(line, data); } endEditBlock(); @@ -4301,10 +4393,12 @@ void FakeVimHandler::Private::setLineContents(int line, const QString &contents) { QTextBlock block = m_tc.document()->findBlockByNumber(line - 1); QTextCursor tc = m_tc; - tc.setPosition(block.position()); - tc.setPosition(block.position() + block.length() - 1, KeepAnchor); + const int begin = block.position(); + const int len = block.length(); + tc.setPosition(begin); + tc.setPosition(begin + len - 1, KeepAnchor); tc.removeSelectedText(); - fixMarks(block.position(), block.length() - contents.size()); + fixMarks(begin, contents.size() + 1 - len); tc.insertText(contents); } diff --git a/src/plugins/fakevim/fakevimhandler.h b/src/plugins/fakevim/fakevimhandler.h index f5b0db1c04..6db23a2d46 100644 --- a/src/plugins/fakevim/fakevimhandler.h +++ b/src/plugins/fakevim/fakevimhandler.h @@ -60,7 +60,7 @@ struct Range struct ExCommand { - ExCommand() : hasBang(false) {} + ExCommand() : hasBang(false), count(1) {} ExCommand(const QString &cmd, const QString &args = QString(), const Range &range = Range()); @@ -68,6 +68,7 @@ struct ExCommand bool hasBang; QString args; Range range; + int count; }; class FakeVimHandler : public QObject diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index 0ecdcdc4fa..fc313bac38 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -102,8 +102,6 @@ const char * const SETTINGS_CATEGORY = "D.FakeVim"; const char * const SETTINGS_CATEGORY_FAKEVIM_ICON = ":/core/images/category_fakevim.png"; const char * const SETTINGS_ID = "A.General"; const char * const SETTINGS_EX_CMDS_ID = "B.ExCommands"; -const char * const CMD_FILE_NEXT = "FakeVim.SwitchFileNext"; -const char * const CMD_FILE_PREV = "FakeVim.SwitchFilePrev"; } // namespace Constants } // namespace FakeVim @@ -119,6 +117,7 @@ namespace FakeVim { namespace Internal { typedef QMap<QString, QRegExp> CommandMap; +typedef QLatin1String _; class FakeVimOptionPage : public Core::IOptionsPage { @@ -128,11 +127,12 @@ public: FakeVimOptionPage() {} // IOptionsPage - QString id() const { return QLatin1String(Constants::SETTINGS_ID); } + QString id() const { return _(Constants::SETTINGS_ID); } QString displayName() const { return tr("General"); } - QString category() const { return QLatin1String(Constants::SETTINGS_CATEGORY); } + QString category() const { return _(Constants::SETTINGS_CATEGORY); } QString displayCategory() const { return tr("FakeVim"); } - QIcon categoryIcon() const { return QIcon(QLatin1String(Constants::SETTINGS_CATEGORY_FAKEVIM_ICON)); } + QIcon categoryIcon() const + { return QIcon(_(Constants::SETTINGS_CATEGORY_FAKEVIM_ICON)); } QWidget *createPage(QWidget *parent); void apply() { m_group.apply(ICore::instance()->settings()); } @@ -238,7 +238,7 @@ void FakeVimOptionPage::setQtStyle() m_ui.checkBoxAutoIndent->setChecked(true); m_ui.checkBoxSmartIndent->setChecked(true); m_ui.checkBoxIncSearch->setChecked(true); - m_ui.lineEditBackspace->setText(QLatin1String("indent,eol,start")); + m_ui.lineEditBackspace->setText(_("indent,eol,start")); } void FakeVimOptionPage::setPlainStyle() @@ -275,7 +275,7 @@ struct CommandItem QTreeWidgetItem *m_item; }; -Q_DECLARE_METATYPE(CommandItem*); +Q_DECLARE_METATYPE(CommandItem *); namespace FakeVim { namespace Internal { @@ -288,9 +288,9 @@ public: FakeVimExCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {} // IOptionsPage - QString id() const { return QLatin1String(Constants::SETTINGS_EX_CMDS_ID); } + QString id() const { return _(Constants::SETTINGS_EX_CMDS_ID); } QString displayName() const { return tr("Ex Command Mapping"); } - QString category() const { return QLatin1String(Constants::SETTINGS_CATEGORY); } + QString category() const { return _(Constants::SETTINGS_CATEGORY); } QString displayCategory() const { return tr("FakeVim"); } QIcon categoryIcon() const { return QIcon(); } // TODO: Icon for FakeVim @@ -512,9 +512,8 @@ private slots: void handleDelayedQuitAll(bool forced); void handleDelayedQuit(bool forced, Core::IEditor *editor); - void switchFile(bool previous); - void switchFileNext(); - void switchFilePrev(); + void switchToFile(int n); + int currentFile() const; signals: void delayedQuitRequested(bool forced, Core::IEditor *editor); @@ -525,6 +524,8 @@ private: FakeVimOptionPage *m_fakeVimOptionsPage; FakeVimExCommandsPage *m_fakeVimExCommandsPage; QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler; + QPointer<EditorManager> m_editorManager; + EditorManager *editorManager() const { return m_editorManager; } void triggerAction(const QString &code); void setActionChecked(const QString &code, bool check); @@ -546,10 +547,6 @@ FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin) q = plugin; m_fakeVimOptionsPage = 0; m_fakeVimExCommandsPage = 0; - defaultExCommandMap()[Constants::CMD_FILE_NEXT] = - QRegExp("^n(ext)?!?( (.*))?$"); - defaultExCommandMap()[Constants::CMD_FILE_PREV] = - QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$"); defaultExCommandMap()[CppTools::Constants::SWITCH_HEADER_SOURCE] = QRegExp("^A$"); defaultExCommandMap()["Coreplugin.OutputPane.previtem"] = @@ -560,7 +557,7 @@ FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin) QRegExp("^tag?$"); defaultExCommandMap()[Core::Constants::GO_BACK] = QRegExp("^pop?$"); - defaultExCommandMap()[QLatin1String("QtCreator.Locate")] = + defaultExCommandMap()[_("QtCreator.Locate")] = QRegExp("^e$"); } @@ -584,6 +581,7 @@ void FakeVimPluginPrivate::aboutToShutdown() bool FakeVimPluginPrivate::initialize() { + m_editorManager = Core::ICore::instance()->editorManager(); Core::ActionManager *actionManager = Core::ICore::instance()->actionManager(); QTC_ASSERT(actionManager, return false); @@ -607,10 +605,9 @@ bool FakeVimPluginPrivate::initialize() advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR); // EditorManager - QObject *editorManager = Core::ICore::instance()->editorManager(); - connect(editorManager, SIGNAL(editorAboutToClose(Core::IEditor*)), + connect(editorManager(), SIGNAL(editorAboutToClose(Core::IEditor*)), this, SLOT(editorAboutToClose(Core::IEditor*))); - connect(editorManager, SIGNAL(editorOpened(Core::IEditor*)), + connect(editorManager(), SIGNAL(editorOpened(Core::IEditor*)), this, SLOT(editorOpened(Core::IEditor*))); connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)), @@ -618,16 +615,6 @@ bool FakeVimPluginPrivate::initialize() connect(theFakeVimSetting(ConfigReadVimRc), SIGNAL(valueChanged(QVariant)), this, SLOT(maybeReadVimRc())); - QAction *switchFileNextAction = new QAction(tr("Switch to next file"), this); - cmd = actionManager->registerAction(switchFileNextAction, Constants::CMD_FILE_NEXT, globalcontext); - cmd->setAttribute(Command::CA_Hide); - connect(switchFileNextAction, SIGNAL(triggered()), this, SLOT(switchFileNext())); - - QAction *switchFilePrevAction = new QAction(tr("Switch to previous file"), this); - cmd = actionManager->registerAction(switchFilePrevAction, Constants::CMD_FILE_PREV, globalcontext); - cmd->setAttribute(Command::CA_Hide); - connect(switchFilePrevAction, SIGNAL(triggered()), this, SLOT(switchFilePrev())); - // Delayed operations. connect(this, SIGNAL(delayedQuitRequested(bool,Core::IEditor*)), this, SLOT(handleDelayedQuit(bool,Core::IEditor*)), Qt::QueuedConnection); @@ -645,7 +632,7 @@ static const char *idKey = "Command"; void FakeVimPluginPrivate::writeSettings(QSettings *settings) { - settings->beginWriteArray(QLatin1String(exCommandMapGroup)); + settings->beginWriteArray(_(exCommandMapGroup)); int count = 0; typedef CommandMap::const_iterator Iterator; @@ -657,8 +644,8 @@ void FakeVimPluginPrivate::writeSettings(QSettings *settings) if ((defaultExCommandMap().contains(id) && defaultExCommandMap()[id] != re) || (!defaultExCommandMap().contains(id) && !re.pattern().isEmpty())) { settings->setArrayIndex(count); - settings->setValue(QLatin1String(idKey), id); - settings->setValue(QLatin1String(reKey), re.pattern()); + settings->setValue(_(idKey), id); + settings->setValue(_(reKey), re.pattern()); ++count; } } @@ -670,11 +657,11 @@ void FakeVimPluginPrivate::readSettings(QSettings *settings) { exCommandMap() = defaultExCommandMap(); - int size = settings->beginReadArray(QLatin1String(exCommandMapGroup)); + int size = settings->beginReadArray(_(exCommandMapGroup)); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); - const QString id = settings->value(QLatin1String(idKey)).toString(); - const QString re = settings->value(QLatin1String(reKey)).toString(); + const QString id = settings->value(_(idKey)).toString(); + const QString re = settings->value(_(reKey)).toString(); exCommandMap()[id] = QRegExp(re); } settings->endArray(); @@ -702,8 +689,8 @@ void FakeVimPluginPrivate::maybeReadVimRc() void FakeVimPluginPrivate::showSettingsDialog() { Core::ICore::instance()->showOptionsDialog( - QLatin1String(Constants::SETTINGS_CATEGORY), - QLatin1String(Constants::SETTINGS_ID)); + _(Constants::SETTINGS_CATEGORY), + _(Constants::SETTINGS_ID)); } void FakeVimPluginPrivate::triggerAction(const QString &code) @@ -873,15 +860,15 @@ void FakeVimPluginPrivate::setUseFakeVim(const QVariant &value) if (Find::FindPlugin::instance()) Find::FindPlugin::instance()->setUseFakeVim(on); if (on) { - Core::EditorManager::instance()->showEditorStatusBar( - QLatin1String(Constants::MINI_BUFFER), + editorManager()->showEditorStatusBar( + _(Constants::MINI_BUFFER), "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.", tr("Quit FakeVim"), this, SLOT(quitFakeVim())); foreach (Core::IEditor *editor, m_editorToHandler.keys()) m_editorToHandler[editor]->setupWidget(); } else { - Core::EditorManager::instance()->hideEditorStatusBar( - QLatin1String(Constants::MINI_BUFFER)); + editorManager()->hideEditorStatusBar( + _(Constants::MINI_BUFFER)); TextEditor::TabSettings ts = TextEditor::TextEditorSettings::instance()->tabSettings(); foreach (Core::IEditor *editor, m_editorToHandler.keys()) @@ -912,7 +899,7 @@ void FakeVimPluginPrivate::checkForElectricCharacter(bool *result, QChar c) void FakeVimPluginPrivate::handleExCommand(bool *handled, const ExCommand &cmd) { using namespace Core; - //qDebug() << "PLUGIN HANDLE: " << cmd.cmd; + //qDebug() << "PLUGIN HANDLE: " << cmd.cmd << cmd.count; *handled = false; @@ -920,8 +907,7 @@ void FakeVimPluginPrivate::handleExCommand(bool *handled, const ExCommand &cmd) if (!handler) return; - EditorManager *editorManager = EditorManager::instance(); - QTC_ASSERT(editorManager, return); + QTC_ASSERT(editorManager(), return); *handled = true; if (cmd.cmd == "w" || cmd.cmd == "write") { @@ -980,6 +966,20 @@ void FakeVimPluginPrivate::handleExCommand(bool *handled, const ExCommand &cmd) setActionChecked(Find::Constants::CASE_SENSITIVE, true); *handled = false; // Let the handler see it as well. } + } else if (cmd.cmd == "n" || cmd.cmd == "next") { + // :n[ext] + switchToFile(currentFile() + cmd.count); + } else if (cmd.cmd == "prev" || cmd.cmd == "previous" + || cmd.cmd == "N" || cmd.cmd == "Next") { + // :prev[ious] + switchToFile(currentFile() - cmd.count); + } else if (cmd.cmd == "bn" || cmd.cmd == "bnext") { + // :bn[ext] + switchToFile(currentFile() + cmd.count); + } else if (cmd.cmd == "bp" || cmd.cmd == "bprevious" + || cmd.cmd == "bN" || cmd.cmd == "bNext") { + // :bp[revious] + switchToFile(currentFile() - cmd.count); } else { // Check whether one of the configure commands matches. typedef CommandMap::const_iterator Iterator; @@ -1027,7 +1027,7 @@ void FakeVimPluginPrivate::moveToMatchingParenthesis(bool *moved, bool *forward, if (undoFakeEOL) cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1); if (match == TextEditor::TextBlockUserData::NoMatch) { - // backward matching is according to the character before the cursor + // Backward matching is according to the character before the cursor. bool undoMove = false; if (!cursor->atBlockEnd()) { cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1); @@ -1102,8 +1102,8 @@ void FakeVimPluginPrivate::quitFakeVim() void FakeVimPluginPrivate::showCommandBuffer(const QString &contents) { //qDebug() << "SHOW COMMAND BUFFER" << contents; - Core::EditorManager::instance()->showEditorStatusBar( - QLatin1String(Constants::MINI_BUFFER), contents, + editorManager()->showEditorStatusBar( + _(Constants::MINI_BUFFER), contents, tr("Quit FakeVim"), this, SLOT(quitFakeVim())); } @@ -1122,24 +1122,22 @@ void FakeVimPluginPrivate::changeSelection bt->setExtraSelections(BaseTextEditor::FakeVimSelection, selection); } -void FakeVimPluginPrivate::switchFile(bool previous) +int FakeVimPluginPrivate::currentFile() const { - Core::EditorManager *em = Core::EditorManager::instance(); - Core::OpenEditorsModel *model = em->openedEditorsModel(); + Core::OpenEditorsModel *model = editorManager()->openedEditorsModel(); IEditor *cur = Core::EditorManager::instance()->currentEditor(); - int curIdx = model->indexOf(cur).row(); - int nIdx = (curIdx + model->rowCount() + (previous ? -1 : 1)) % model->rowCount(); - em->activateEditor(model->index(nIdx, 0), 0); -} - -void FakeVimPluginPrivate::switchFileNext() -{ - switchFile(false); + return model->indexOf(cur).row(); } -void FakeVimPluginPrivate::switchFilePrev() +void FakeVimPluginPrivate::switchToFile(int n) { - switchFile(true); + Core::OpenEditorsModel *model = editorManager()->openedEditorsModel(); + int size = model->rowCount(); + QTC_ASSERT(size, return); + n = n % size; + if (n < 0) + n += size; + editorManager()->activateEditor(model->index(n, 0), 0); } CommandMap &FakeVimExCommandsPage::exCommandMap() @@ -1175,9 +1173,10 @@ bool FakeVimPlugin::initialize(const QStringList &arguments, QString *errorMessa return d->initialize(); } -void FakeVimPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag FakeVimPlugin::aboutToShutdown() { d->aboutToShutdown(); + return SynchronousShutdown; } void FakeVimPlugin::extensionsInitialized() diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h index a40bb9a9f2..b9e38375e6 100644 --- a/src/plugins/fakevim/fakevimplugin.h +++ b/src/plugins/fakevim/fakevimplugin.h @@ -50,7 +50,7 @@ public: private: // implementation of ExtensionSystem::IPlugin bool initialize(const QStringList &arguments, QString *error_message); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); void extensionsInitialized(); private: diff --git a/src/plugins/find/basetextfind.cpp b/src/plugins/find/basetextfind.cpp index 86dd69e33c..80e30044ce 100644 --- a/src/plugins/find/basetextfind.cpp +++ b/src/plugins/find/basetextfind.cpp @@ -153,8 +153,15 @@ IFindSupport::Result BaseTextFind::findStep(const QString &txt, IFindSupport::Fi return found ? Found : NotFound; } -bool BaseTextFind::replaceStep(const QString &before, const QString &after, - IFindSupport::FindFlags findFlags) +void BaseTextFind::replace(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags) +{ + QTextCursor cursor = replaceInternal(before, after, findFlags); + setTextCursor(cursor); +} + +QTextCursor BaseTextFind::replaceInternal(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags) { QTextCursor cursor = textCursor(); bool usesRegExp = (findFlags & IFindSupport::FindRegularExpression); @@ -169,6 +176,13 @@ bool BaseTextFind::replaceStep(const QString &before, const QString &after, if ((findFlags&IFindSupport::FindBackward) != 0) cursor.setPosition(start); } + return cursor; +} + +bool BaseTextFind::replaceStep(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags) +{ + QTextCursor cursor = replaceInternal(before, after, findFlags); return find(before, findFlags, cursor); } diff --git a/src/plugins/find/basetextfind.h b/src/plugins/find/basetextfind.h index c62f5a7282..e2bca51127 100644 --- a/src/plugins/find/basetextfind.h +++ b/src/plugins/find/basetextfind.h @@ -60,6 +60,8 @@ public: Result findIncremental(const QString &txt, IFindSupport::FindFlags findFlags); Result findStep(const QString &txt, IFindSupport::FindFlags findFlags); + void replace(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags); bool replaceStep(const QString &before, const QString &after, IFindSupport::FindFlags findFlags); int replaceAll(const QString &before, const QString &after, @@ -76,6 +78,8 @@ private: bool find(const QString &txt, IFindSupport::FindFlags findFlags, QTextCursor start); + QTextCursor replaceInternal(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags); QTextCursor textCursor() const; void setTextCursor(const QTextCursor&); diff --git a/src/plugins/find/currentdocumentfind.cpp b/src/plugins/find/currentdocumentfind.cpp index 18c625b547..d378823bcb 100644 --- a/src/plugins/find/currentdocumentfind.cpp +++ b/src/plugins/find/currentdocumentfind.cpp @@ -118,6 +118,13 @@ IFindSupport::Result CurrentDocumentFind::findStep(const QString &txt, IFindSupp return m_currentFind->findStep(txt, findFlags); } +void CurrentDocumentFind::replace(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags) +{ + QTC_ASSERT(m_currentFind, return); + m_currentFind->replace(before, after, findFlags); +} + bool CurrentDocumentFind::replaceStep(const QString &before, const QString &after, IFindSupport::FindFlags findFlags) { diff --git a/src/plugins/find/currentdocumentfind.h b/src/plugins/find/currentdocumentfind.h index f104ef5f4b..f199854c53 100644 --- a/src/plugins/find/currentdocumentfind.h +++ b/src/plugins/find/currentdocumentfind.h @@ -56,6 +56,8 @@ public: void highlightAll(const QString &txt, IFindSupport::FindFlags findFlags); IFindSupport::Result findIncremental(const QString &txt, IFindSupport::FindFlags findFlags); IFindSupport::Result findStep(const QString &txt, IFindSupport::FindFlags findFlags); + void replace(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags); bool replaceStep(const QString &before, const QString &after, IFindSupport::FindFlags findFlags); int replaceAll(const QString &before, const QString &after, diff --git a/src/plugins/find/find.pro b/src/plugins/find/find.pro index bfdb979332..d0f67a405c 100644 --- a/src/plugins/find/find.pro +++ b/src/plugins/find/find.pro @@ -27,7 +27,8 @@ SOURCES += findtoolwindow.cpp \ searchresulttreeitems.cpp \ searchresulttreemodel.cpp \ searchresulttreeview.cpp \ - searchresultwindow.cpp + searchresultwindow.cpp \ + ifindfilter.cpp FORMS += findwidget.ui \ finddialog.ui RESOURCES += find.qrc diff --git a/src/plugins/find/findplugin.cpp b/src/plugins/find/findplugin.cpp index f5d22a9128..900020c765 100644 --- a/src/plugins/find/findplugin.cpp +++ b/src/plugins/find/findplugin.cpp @@ -143,12 +143,13 @@ void FindPlugin::extensionsInitialized() readSettings(); } -void FindPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag FindPlugin::aboutToShutdown() { d->m_findToolBar->setVisible(false); d->m_findToolBar->setParent(0); d->m_currentDocumentFind->removeConnections(); writeSettings(); + return SynchronousShutdown; } void FindPlugin::filterChanged() @@ -208,7 +209,8 @@ void FindPlugin::setupMenu() mfindadvanced->menu()->setTitle(tr("Advanced Find")); mfind->addMenu(mfindadvanced, Constants::G_FIND_FILTERS); d->m_openFindDialog = new QAction(tr("Open Advanced Find..."), this); - cmd = am->registerAction(d->m_openFindDialog, QLatin1String("Find.Dialog"), globalcontext); + d->m_openFindDialog->setIconText(tr("Advanced...")); + cmd = am->registerAction(d->m_openFindDialog, Constants::ADVANCED_FIND, globalcontext); cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+F"))); mfindadvanced->addAction(cmd); connect(d->m_openFindDialog, SIGNAL(triggered()), this, SLOT(openFindFilter())); @@ -226,7 +228,7 @@ void FindPlugin::setupFilterMenuItems() d->m_filterActions.clear(); bool haveEnabledFilters = false; foreach (IFindFilter *filter, findInterfaces) { - QAction *action = new QAction(QLatin1String(" ") + filter->name(), this); + QAction *action = new QAction(QLatin1String(" ") + filter->displayName(), this); bool isEnabled = filter->isEnabled(); if (isEnabled) haveEnabledFilters = true; diff --git a/src/plugins/find/findplugin.h b/src/plugins/find/findplugin.h index ef0e0d7356..d643be94fc 100644 --- a/src/plugins/find/findplugin.h +++ b/src/plugins/find/findplugin.h @@ -67,7 +67,7 @@ public: // IPlugin bool initialize(const QStringList &arguments, QString *error_message); void extensionsInitialized(); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); QTextDocument::FindFlags findFlags() const; void updateFindCompletion(const QString &text); diff --git a/src/plugins/find/findtoolbar.cpp b/src/plugins/find/findtoolbar.cpp index 2dc9323683..6b0118759e 100644 --- a/src/plugins/find/findtoolbar.cpp +++ b/src/plugins/find/findtoolbar.cpp @@ -71,7 +71,9 @@ FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumen m_enterFindStringAction(0), m_findNextAction(0), m_findPreviousAction(0), + m_replaceAction(0), m_replaceNextAction(0), + m_replacePreviousAction(0), m_casesensitiveIcon(":/find/images/casesensitively.png"), m_regexpIcon(":/find/images/regexp.png"), m_wholewordsIcon(":/find/images/wholewords.png"), @@ -138,6 +140,8 @@ FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumen Core::ActionContainer *mfind = am->actionContainer(Constants::M_FIND); Core::Command *cmd; + m_ui.advancedButton->setDefaultAction(am->command(Constants::ADVANCED_FIND)->action()); + QIcon icon = QIcon::fromTheme(QLatin1String("edit-find-replace")); m_findInDocumentAction = new QAction(icon, tr("Find/Replace"), this); cmd = am->registerAction(m_findInDocumentAction, Constants::FIND_IN_DOCUMENT, globalcontext); @@ -168,7 +172,15 @@ FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumen connect(m_findPreviousAction, SIGNAL(triggered()), this, SLOT(invokeFindPrevious())); m_ui.findPreviousButton->setDefaultAction(cmd->action()); - m_replaceNextAction = new QAction(tr("Replace && Find Next"), this); + m_replaceAction = new QAction(tr("Replace"), this); + cmd = am->registerAction(m_replaceAction, Constants::REPLACE, globalcontext); + cmd->setDefaultKeySequence(QKeySequence()); + mfind->addAction(cmd, Constants::G_FIND_ACTIONS); + connect(m_replaceAction, SIGNAL(triggered()), this, SLOT(invokeReplace())); + m_ui.replaceButton->setDefaultAction(cmd->action()); + + m_replaceNextAction = new QAction(tr("Replace && Find"), this); + m_replaceNextAction->setIconText(tr("Replace && Find")); // work around bug in Qt that kills ampersands in tool button cmd = am->registerAction(m_replaceNextAction, Constants::REPLACE_NEXT, globalcontext); cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+="))); mfind->addAction(cmd, Constants::G_FIND_ACTIONS); @@ -181,7 +193,6 @@ FindToolBar::FindToolBar(FindPlugin *plugin, CurrentDocumentFind *currentDocumen //cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+="))); mfind->addAction(cmd, Constants::G_FIND_ACTIONS); connect(m_replacePreviousAction, SIGNAL(triggered()), this, SLOT(invokeReplacePrevious())); - m_ui.replacePreviousButton->setDefaultAction(cmd->action()); m_replaceAllAction = new QAction(tr("Replace All"), this); cmd = am->registerAction(m_replaceAllAction, Constants::REPLACE_ALL, globalcontext); @@ -315,6 +326,7 @@ void FindToolBar::updateToolBar() m_findNextAction->setEnabled(enabled); m_findPreviousAction->setEnabled(enabled); + m_replaceAction->setEnabled(replaceEnabled); m_replaceNextAction->setEnabled(replaceEnabled); m_replacePreviousAction->setEnabled(replaceEnabled); m_replaceAllAction->setEnabled(replaceEnabled); @@ -332,9 +344,10 @@ void FindToolBar::updateToolBar() m_ui.replaceLabel->setEnabled(replaceEnabled); m_ui.replaceEdit->setVisible(replaceEnabled); m_ui.replaceLabel->setVisible(replaceEnabled); - m_ui.replacePreviousButton->setVisible(replaceEnabled); + m_ui.replaceButton->setVisible(replaceEnabled); m_ui.replaceNextButton->setVisible(replaceEnabled); m_ui.replaceAllButton->setVisible(replaceEnabled); + m_ui.advancedButton->setVisible(replaceEnabled); layout()->invalidate(); if (!replaceEnabled && enabled && replaceFocus) @@ -433,6 +446,16 @@ void FindToolBar::invokeFindIncremental() } } +void FindToolBar::invokeReplace() +{ + setFindFlag(IFindSupport::FindBackward, false); + if (m_currentDocumentFind->isEnabled() && m_currentDocumentFind->supportsReplace()) { + m_plugin->updateFindCompletion(getFindText()); + m_plugin->updateReplaceCompletion(getReplaceText()); + m_currentDocumentFind->replace(getFindText(), getReplaceText(), effectiveFindFlags()); + } +} + void FindToolBar::invokeReplaceNext() { setFindFlag(IFindSupport::FindBackward, false); diff --git a/src/plugins/find/findtoolbar.h b/src/plugins/find/findtoolbar.h index ab8d8a4132..92abe7e475 100644 --- a/src/plugins/find/findtoolbar.h +++ b/src/plugins/find/findtoolbar.h @@ -67,6 +67,7 @@ private slots: void invokeFindNext(); void invokeFindPrevious(); void invokeFindStep(); + void invokeReplace(); void invokeReplaceNext(); void invokeReplacePrevious(); void invokeReplaceStep(); @@ -120,6 +121,7 @@ private: QAction *m_enterFindStringAction; QAction *m_findNextAction; QAction *m_findPreviousAction; + QAction *m_replaceAction; QAction *m_replaceNextAction; QAction *m_replacePreviousAction; QAction *m_replaceAllAction; diff --git a/src/plugins/find/findtoolwindow.cpp b/src/plugins/find/findtoolwindow.cpp index 5b4bf7d35e..63993c13cd 100644 --- a/src/plugins/find/findtoolwindow.cpp +++ b/src/plugins/find/findtoolwindow.cpp @@ -98,7 +98,7 @@ void FindToolWindow::setFindFilters(const QList<IFindFilter *> &filters) m_ui.filterList->clear(); QStringList names; foreach (IFindFilter *filter, filters) { - names << filter->name(); + names << filter->displayName(); m_configWidgets.append(filter->createConfigWidget()); } m_ui.filterList->addItems(names); diff --git a/src/plugins/find/findwidget.ui b/src/plugins/find/findwidget.ui index 0e9b1cafe7..2457dbc698 100644 --- a/src/plugins/find/findwidget.ui +++ b/src/plugins/find/findwidget.ui @@ -100,7 +100,7 @@ </widget> </item> <item row="1" column="1"> - <widget class="Utils::FilterLineEdit" name="replaceEdit"/> + <widget class="Utils::FilterLineEdit" name="replaceEdit"/> </item> <item row="1" column="2"> <layout class="QHBoxLayout" name="horizontalLayout"> @@ -108,10 +108,16 @@ <number>3</number> </property> <item> - <widget class="QToolButton" name="replacePreviousButton"> + <widget class="QToolButton" name="replaceButton"> <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> + <property name="text"> + <string>Replace</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> <property name="arrowType"> <enum>Qt::LeftArrow</enum> </property> @@ -125,6 +131,12 @@ <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> + <property name="text"> + <string>Replace && Find</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> <property name="arrowType"> <enum>Qt::RightArrow</enum> </property> @@ -136,7 +148,7 @@ <font/> </property> <property name="text"> - <string>All</string> + <string>Replace All</string> </property> <property name="toolButtonStyle"> <enum>Qt::ToolButtonTextOnly</enum> @@ -156,6 +168,16 @@ </property> </spacer> </item> + <item> + <widget class="QToolButton" name="advancedButton"> + <property name="text"> + <string>Advanced...</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + </widget> + </item> </layout> </item> </layout> diff --git a/src/plugins/find/ifindfilter.cpp b/src/plugins/find/ifindfilter.cpp new file mode 100644 index 0000000000..d2eef03065 --- /dev/null +++ b/src/plugins/find/ifindfilter.cpp @@ -0,0 +1,174 @@ +#include "ifindfilter.h" + +/*! + \class Find::IFindFilter + \brief The IFindFilter class is the base class for find implementations + that are invoked via the \gui{Edit -> Find/Replace -> Advanced Find} + dialog. + + Implementations of this class add an additional "Scope" to the Advanced + Find dialog. That can be any search that requires the user to provide + a text based search term (potentially with find flags like + searching case sensitively or using regular expressions). Existing + scopes are e.g. "All Projects" (i.e. search in all files in all projects) + and "Files on File System" where the user provides a directory and file + patterns to search. + + To make your find scope available to the user, you need to implement this + class, and register an instance of your subclass in the plugin manager. + + A common way to present the search results to the user, is to use the + shared \gui{Search Results} panel. + + If you want to implement a find filter that is doing a file based text + search, you should use Find::BaseFileFind, which already implements all + the details for this kind of search, only requiring you to provide an + iterator over the file names of the files that should be searched. + + If you want to implement a more specialized find filter, you'll need + to + \list + \o Start your search in a separate thread + \o Make this known to the Core::ProgressManager, for a progress bar + and the ability to cancel the search + \o Interface with the shared \gui{Search Results} panel, to show + the search results, handle the event that the user click on one + of the search result items, and possible handle a global replace + of all or some of the search result items. + \endlist + + Luckily QtConcurrent and the search result panel provide the frameworks + that make it relatively easy to implement, + while ensuring a common way for the user. + + The common pattern is roughly this: + + Implement the actual search within a QtConcurrent based method, i.e. + a method that takes a \c{QFutureInterface<MySearchResult> &future} + as the first parameter and the other information needed for the search + as additional parameters. It should set useful progress information + on the QFutureInterface, regularly check for \c{future.isPaused()} + and \c{future.isCanceled()}, and report the search results + (possibly in chunks) via \c{future.reportResult}. + + In the find filter's find/replaceAll method, get the shared + \gui{Search Results} window, initiate a new search and connect the + signals for handling selection of results and the replace action + (see the Find::SearchResultWindow class for details). + Start your search implementation via the corresponding QtConcurrent + methods. Add the returned QFuture object to the Core::ProgressManager. + Use a QFutureWatcher on the returned QFuture object to receive a signal + when your search implementation reports search results, and add these + to the shared \gui{Search Results} window. +*/ + +/*! + \fn IFindFilter::~IFindFilter() + \internal +*/ + +/*! + \fn QString IFindFilter::id() const + \brief Unique string identifier for this find filter. + + Usually should be something like "MyPlugin.MyFindFilter". +*/ + +/*! + \fn QString IFindFilter::displayName() const + \brief The name of the find filter/scope as presented to the user. + + This is the name that e.g. appears in the scope selection combo box. + Always return a translatable string (i.e. use tr() for the return value). +*/ + +/*! + \fn bool IFindFilter::isEnabled() const + \brief Returns if the user should be able to select this find filter + at the moment. + + This is used e.g. for the "Current Projects" scope - if the user hasn't + opened a project, the scope is disabled. + + \sa changed() +*/ + +/*! + \fn QKeySequence IFindFilter::defaultShortcut() const + \brief Returns the shortcut that can be used to open the advanced find + dialog with this filter/scope preselected. + + Usually return an empty shortcut here, the user can still choose and + assign a specific shortcut to this find scope via the preferences. +*/ + +/*! + \fn bool IFindFilter::isReplaceSupported() const + \brief Returns if the find filter supports search and replace. + + The default value is false, override this method to return true, if + your find filter supports global search and replace. +*/ + +/*! + \fn void IFindFilter::findAll(const QString &txt, QTextDocument::FindFlags findFlags) + \brief This method is called when the user selected this find scope and + initiated a search. + + You should start a thread which actually performs the search for \a txt + using the given \a findFlags + (add it to Core::ProgressManager for a progress bar!) and presents the + search results to the user (using the \gui{Search Results} output pane). + For more information see the descriptions of this class, + Core::ProgressManager, and Find::SearchResultWindow. + + \sa replaceAll() + \sa Core::ProgressManager + \sa Find::SearchResultWindow +*/ + +/*! + \fn void IFindFilter::replaceAll(const QString &txt, QTextDocument::FindFlags findFlags) + \brief Override this method if you want to support search and replace. + + This method is called when the user selected this find scope and + initiated a search and replace. + The default implementation does nothing. + + You should start a thread which actually performs the search for \a txt + using the given \a findFlags + (add it to Core::ProgressManager for a progress bar!) and presents the + search results to the user (using the \gui{Search Results} output pane). + For more information see the descriptions of this class, + Core::ProgressManager, and Find::SearchResultWindow. + + \sa findAll() + \sa Core::ProgressManager + \sa Find::SearchResultWindow +*/ + +/*! + \fn QWidget *IFindFilter::createConfigWidget() + \brief Return a widget that contains additional controls for options + for this find filter. + + The widget will be shown below the common options in the Advanced Find + dialog. It will be reparented and deleted by the find plugin. +*/ + +/*! + \fn void IFindFilter::writeSettings(QSettings *settings) + \brief Called at shutdown to write the state of the additional options + for this find filter to the \a settings. +*/ + +/*! + \fn void IFindFilter::readSettings(QSettings *settings) + \brief Called at startup to read the state of the additional options + for this find filter from the \a settings. +*/ + +/*! + \fn void IFindFilter::changed() + \brief Signals that the enabled state of this find filter has changed. +*/ diff --git a/src/plugins/find/ifindfilter.h b/src/plugins/find/ifindfilter.h index 8f7967e933..c063b55329 100644 --- a/src/plugins/find/ifindfilter.h +++ b/src/plugins/find/ifindfilter.h @@ -50,7 +50,7 @@ public: virtual ~IFindFilter() {} virtual QString id() const = 0; - virtual QString name() const = 0; + virtual QString displayName() const = 0; virtual bool isEnabled() const = 0; virtual QKeySequence defaultShortcut() const = 0; virtual bool isReplaceSupported() const { return false; } diff --git a/src/plugins/find/ifindsupport.h b/src/plugins/find/ifindsupport.h index 3d7900f051..e0293474bb 100644 --- a/src/plugins/find/ifindsupport.h +++ b/src/plugins/find/ifindsupport.h @@ -65,6 +65,8 @@ public: virtual void highlightAll(const QString &txt, FindFlags findFlags); virtual Result findIncremental(const QString &txt, FindFlags findFlags) = 0; virtual Result findStep(const QString &txt, FindFlags findFlags) = 0; + virtual void replace(const QString &before, const QString &after, + FindFlags findFlags) = 0; virtual bool replaceStep(const QString &before, const QString &after, FindFlags findFlags) = 0; virtual int replaceAll(const QString &before, const QString &after, diff --git a/src/plugins/find/searchresultwindow.cpp b/src/plugins/find/searchresultwindow.cpp index 45b69f4d75..f6566b5a77 100644 --- a/src/plugins/find/searchresultwindow.cpp +++ b/src/plugins/find/searchresultwindow.cpp @@ -160,6 +160,14 @@ namespace Internal { return IFindSupport::NotFound; } + void replace(const QString &before, const QString &after, + IFindSupport::FindFlags findFlags) + { + Q_UNUSED(before) + Q_UNUSED(after) + Q_UNUSED(findFlags) + } + bool replaceStep(const QString &before, const QString &after, IFindSupport::FindFlags findFlags) { @@ -211,8 +219,88 @@ namespace Internal { using namespace Find::Internal; +/*! + \enum Find::SearchResultWindow::SearchMode + Specifies if a search should show the replace UI or not. + + \value SearchOnly + The search doesn't support replace. + \value SearchAndReplace + The search supports replace, so show the UI for it. +*/ + +/*! + \class Find::SearchResult + \brief Reports user interaction like activation of a search result item. + + Whenever a new search is initiated via startNewSearch, an instance of this + class is returned to provide the initiator with the hooks for handling user + interaction. +*/ + +/*! + \fn void SearchResult::activated(const Find::SearchResultItem &item) + \brief Sent if the user activated (e.g. double-clicked) a search result + \a item. +*/ + +/*! + \fn void SearchResult::replaceButtonClicked(const QString &replaceText, const QList<Find::SearchResultItem> &checkedItems) + \brief Sent when the user initiated a replace, e.g. by pressing the replace + all button. + + The signal reports the text to use for replacement in \a replaceText, + and the list of search result items that were selected by the user + in \a checkedItems. + The handler of this signal should apply the replace only on the selected + items. +*/ + +/*! + \class Find::SearchResultWindow + \brief The SearchResultWindow class is the implementation of a commonly + shared \gui{Search Results} output pane. Use it to show search results + to a user. + + Whenever you want to show the user a list of search results, or want + to present UI for a global search and replace, use the single instance + of this class. + + Except for being an implementation of a output pane, the + SearchResultWindow has a few methods and one enum that allows other + plugins to show their search results and hook into the user actions for + selecting an entry and performing a global replace. + + Whenever you start a search, call startNewSearch(SearchMode) to initialize + the search result window. The parameter determines if the GUI for + replacing should be shown. + The method returns a SearchResult object that is your + hook into the signals from user interaction for this search. + When you produce search results, call addResults or addResult to add them + to the search result window. + After the search has finished call finishSearch to inform the search + result window about it. + + After that you get activated signals via your SearchResult instance when + the user selects a search result item, and, if you started the search + with the SearchAndReplace option, the replaceButtonClicked signal + when the user requests a replace. +*/ + +/*! + \fn QString SearchResultWindow::displayName() const + \internal +*/ + +SearchResultWindow *SearchResultWindow::m_instance = 0; + +/*! + \fn SearchResultWindow::SearchResultWindow() + \internal +*/ SearchResultWindow::SearchResultWindow() : d(new SearchResultWindowPrivate) { + m_instance = this; d->m_widget = new QStackedWidget; d->m_widget->setWindowTitle(displayName()); @@ -260,6 +348,10 @@ SearchResultWindow::SearchResultWindow() : d(new SearchResultWindowPrivate) setShowReplaceUI(false); } +/*! + \fn SearchResultWindow::~SearchResultWindow() + \internal +*/ SearchResultWindow::~SearchResultWindow() { writeSettings(); @@ -271,16 +363,38 @@ SearchResultWindow::~SearchResultWindow() delete d; } +/*! + \fn SearchResultWindow *SearchResultWindow::instance() + \brief Returns the single shared instance of the Search Results window. +*/ +SearchResultWindow *SearchResultWindow::instance() +{ + return m_instance; +} + +/*! + \fn void SearchResultWindow::setTextToReplace(const QString &textToReplace) + \brief Sets the value in the UI element that allows the user to type + the text that should replace text in search results to \a textToReplace. +*/ void SearchResultWindow::setTextToReplace(const QString &textToReplace) { d->m_replaceTextEdit->setText(textToReplace); } +/*! + \fn QString SearchResultWindow::textToReplace() const + \brief Returns the text that should replace the text in search results. +*/ QString SearchResultWindow::textToReplace() const { return d->m_replaceTextEdit->text(); } +/*! + \fn void SearchResultWindow::setShowReplaceUI(bool show) + \internal +*/ void SearchResultWindow::setShowReplaceUI(bool show) { d->m_searchResultTreeView->model()->setShowReplaceUI(show); @@ -290,6 +404,10 @@ void SearchResultWindow::setShowReplaceUI(bool show) d->m_isShowingReplaceUI = show; } +/*! + \fn void SearchResultWindow::handleReplaceButton() + \internal +*/ void SearchResultWindow::handleReplaceButton() { QTC_ASSERT(d->m_currentSearch, return); @@ -299,6 +417,10 @@ void SearchResultWindow::handleReplaceButton() d->m_currentSearch->replaceButtonClicked(d->m_replaceTextEdit->text(), checkedItems()); } +/*! + \fn QList<SearchResultItem> SearchResultWindow::checkedItems() const + \internal +*/ QList<SearchResultItem> SearchResultWindow::checkedItems() const { QList<SearchResultItem> result; @@ -318,20 +440,42 @@ QList<SearchResultItem> SearchResultWindow::checkedItems() const return result; } +/*! + \fn void SearchResultWindow::visibilityChanged(bool) + \internal +*/ void SearchResultWindow::visibilityChanged(bool /*visible*/) { } +/*! + \fn QWidget *SearchResultWindow::outputWidget(QWidget *) + \internal +*/ QWidget *SearchResultWindow::outputWidget(QWidget *) { return d->m_widget; } +/*! + \fn QList<QWidget*> SearchResultWindow::toolBarWidgets() const + \internal +*/ QList<QWidget*> SearchResultWindow::toolBarWidgets() const { return QList<QWidget*>() << d->m_expandCollapseButton << d->m_replaceLabel << d->m_replaceTextEdit << d->m_replaceButton; } +/*! + \fn SearchResult *SearchResultWindow::startNewSearch(SearchMode searchOrSearchAndReplace) + \brief Tells the search results window to start a new search. + + This will clear the contents of the previous search and initialize the UI + with regard to showing the replace UI or not (depending on the search mode + in \a searchOrSearchAndReplace). + Returns a SearchResult object that is used for signaling user interaction + with the results of this search. +*/ SearchResult *SearchResultWindow::startNewSearch(SearchMode searchOrSearchAndReplace) { clearContents(); @@ -341,6 +485,11 @@ SearchResult *SearchResultWindow::startNewSearch(SearchMode searchOrSearchAndRep return d->m_currentSearch; } +/*! + \fn void SearchResultWindow::finishSearch() + \brief Notifies the search result window that the current search + has finished, and the UI should reflect that. +*/ void SearchResultWindow::finishSearch() { if (d->m_items.count()) { @@ -350,6 +499,10 @@ void SearchResultWindow::finishSearch() } } +/*! + \fn void SearchResultWindow::clearContents() + \brief Clears the current contents in the search result window. +*/ void SearchResultWindow::clearContents() { d->m_replaceTextEdit->setEnabled(false); @@ -361,6 +514,10 @@ void SearchResultWindow::clearContents() navigateStateChanged(); } +/*! + \fn void SearchResultWindow::showNoMatchesFound() + \internal +*/ void SearchResultWindow::showNoMatchesFound() { d->m_replaceTextEdit->setEnabled(false); @@ -368,26 +525,47 @@ void SearchResultWindow::showNoMatchesFound() d->m_widget->setCurrentWidget(d->m_noMatchesFoundDisplay); } +/*! + \fn bool SearchResultWindow::isEmpty() const + Returns if the search result window currently doesn't show any results. +*/ bool SearchResultWindow::isEmpty() const { return (d->m_searchResultTreeView->model()->rowCount() < 1); } +/*! + \fn int SearchResultWindow::numberOfResults() const + Returns the number of search results currently shown in the search + results window. +*/ int SearchResultWindow::numberOfResults() const { return d->m_items.count(); } +/*! + \fn bool SearchResultWindow::hasFocus() + \internal +*/ bool SearchResultWindow::hasFocus() { return d->m_searchResultTreeView->hasFocus() || (d->m_isShowingReplaceUI && d->m_replaceTextEdit->hasFocus()); } +/*! + \fn bool SearchResultWindow::canFocus() + \internal +*/ bool SearchResultWindow::canFocus() { return !d->m_items.isEmpty(); } +/*! + \fn void SearchResultWindow::setFocus() + \internal +*/ void SearchResultWindow::setFocus() { if (!d->m_items.isEmpty()) { @@ -406,17 +584,37 @@ void SearchResultWindow::setFocus() } } +/*! + \fn void SearchResultWindow::setTextEditorFont(const QFont &font) + \internal +*/ void SearchResultWindow::setTextEditorFont(const QFont &font) { d->m_searchResultTreeView->setTextEditorFont(font); } +/*! + \fn void SearchResultWindow::handleJumpToSearchResult(int index, bool) + \internal +*/ void SearchResultWindow::handleJumpToSearchResult(int index, bool /* checked */) { QTC_ASSERT(d->m_currentSearch, return); d->m_currentSearch->activated(d->m_items.at(index)); } +/*! + \fn void SearchResultWindow::addResult(const QString &fileName, int lineNumber, const QString &rowText, int searchTermStart, int searchTermLength, const QVariant &userData) + \brief Adds a single result line to the search results. + + The \a fileName, \a lineNumber and \a rowText are shown in the result line. + \a searchTermStart and \a searchTermLength specify the region that + should be visually marked (string position and length in \a rowText). + You can attach arbitrary \a userData to the search result, which can + be used e.g. when reacting to the signals of the SearchResult for your search. + + \sa addResults() +*/ void SearchResultWindow::addResult(const QString &fileName, int lineNumber, const QString &rowText, int searchTermStart, int searchTermLength, const QVariant &userData) { @@ -430,6 +628,13 @@ void SearchResultWindow::addResult(const QString &fileName, int lineNumber, cons addResults(QList<SearchResultItem>() << item); } +/*! + \fn void SearchResultWindow::addResults(QList<SearchResultItem> &items) + \brief Adds all of the given search result \a items to the search + results window. + + \sa addResult() +*/ void SearchResultWindow::addResults(QList<SearchResultItem> &items) { int index = d->m_items.size(); @@ -452,6 +657,10 @@ void SearchResultWindow::addResults(QList<SearchResultItem> &items) } } +/*! + \fn void SearchResultWindow::handleExpandCollapseToolButton(bool checked) + \internal +*/ void SearchResultWindow::handleExpandCollapseToolButton(bool checked) { d->m_searchResultTreeView->setAutoExpandResults(checked); @@ -461,6 +670,10 @@ void SearchResultWindow::handleExpandCollapseToolButton(bool checked) d->m_searchResultTreeView->collapseAll(); } +/*! + \fn void SearchResultWindow::readSettings() + \internal +*/ void SearchResultWindow::readSettings() { QSettings *s = Core::ICore::instance()->settings(); @@ -471,6 +684,10 @@ void SearchResultWindow::readSettings() } } +/*! + \fn void SearchResultWindow::writeSettings() + \internal +*/ void SearchResultWindow::writeSettings() { QSettings *s = Core::ICore::instance()->settings(); @@ -481,21 +698,37 @@ void SearchResultWindow::writeSettings() } } +/*! + \fn int SearchResultWindow::priorityInStatusBar() const + \internal +*/ int SearchResultWindow::priorityInStatusBar() const { return 80; } +/*! + \fn bool SearchResultWindow::canNext() + \internal +*/ bool SearchResultWindow::canNext() { return d->m_items.count() > 0; } +/*! + \fn bool SearchResultWindow::canPrevious() + \internal +*/ bool SearchResultWindow::canPrevious() { return d->m_items.count() > 0; } +/*! + \fn void SearchResultWindow::goToNext() + \internal +*/ void SearchResultWindow::goToNext() { if (d->m_items.count() == 0) @@ -506,6 +739,11 @@ void SearchResultWindow::goToNext() d->m_searchResultTreeView->emitJumpToSearchResult(idx); } } + +/*! + \fn void SearchResultWindow::goToPrev() + \internal +*/ void SearchResultWindow::goToPrev() { if (!d->m_searchResultTreeView->model()->rowCount()) @@ -517,6 +755,10 @@ void SearchResultWindow::goToPrev() } } +/*! + \fn bool SearchResultWindow::canNavigate() + \internal +*/ bool SearchResultWindow::canNavigate() { return true; diff --git a/src/plugins/find/searchresultwindow.h b/src/plugins/find/searchresultwindow.h index 47a80c2468..92e58cebbb 100644 --- a/src/plugins/find/searchresultwindow.h +++ b/src/plugins/find/searchresultwindow.h @@ -82,6 +82,7 @@ public: SearchResultWindow(); virtual ~SearchResultWindow(); + static SearchResultWindow *instance(); QWidget *outputWidget(QWidget *); QList<QWidget*> toolBarWidgets() const; @@ -129,6 +130,7 @@ private: QList<SearchResultItem> checkedItems() const; Internal::SearchResultWindowPrivate *d; + static SearchResultWindow *m_instance; }; } // namespace Find diff --git a/src/plugins/find/textfindconstants.h b/src/plugins/find/textfindconstants.h index a5e56e2297..69f6a2428e 100644 --- a/src/plugins/find/textfindconstants.h +++ b/src/plugins/find/textfindconstants.h @@ -40,11 +40,13 @@ const char * const G_FIND_FILTERS = "Find.FindMenu.Filters"; const char * const G_FIND_FLAGS = "Find.FindMenu.Flags"; const char * const G_FIND_ACTIONS = "Find.FindMenu.Actions"; +const char * const ADVANCED_FIND = "Find.Dialog"; const char * const FIND = "Find.FindReplace"; const char * const FIND_IN_DOCUMENT = "Find.FindInCurrentDocument"; const char * const FIND_NEXT = "Find.FindNext"; const char * const FIND_PREVIOUS = "Find.FindPrevious"; const char * const FIND_ALL = "Find.FindAll"; +const char * const REPLACE = "Find.Replace"; const char * const REPLACE_NEXT = "Find.ReplaceNext"; const char * const REPLACE_PREVIOUS = "Find.ReplacePrevious"; const char * const REPLACE_ALL = "Find.ReplaceAll"; diff --git a/src/plugins/help/helpfindsupport.h b/src/plugins/help/helpfindsupport.h index 4292381b17..f317f5aa9f 100644 --- a/src/plugins/help/helpfindsupport.h +++ b/src/plugins/help/helpfindsupport.h @@ -58,6 +58,8 @@ public: Result findIncremental(const QString &txt, Find::IFindSupport::FindFlags findFlags); Result findStep(const QString &txt, Find::IFindSupport::FindFlags findFlags); + void replace(const QString &, const QString &, + Find::IFindSupport::FindFlags ) { } bool replaceStep(const QString &, const QString &, Find::IFindSupport::FindFlags ) { return false; } int replaceAll(const QString &, const QString &, @@ -85,6 +87,8 @@ public: Result findIncremental(const QString &txt, Find::IFindSupport::FindFlags findFlags); Result findStep(const QString &txt, Find::IFindSupport::FindFlags findFlags); + void replace(const QString &, const QString &, + Find::IFindSupport::FindFlags ) { } bool replaceStep(const QString &, const QString &, Find::IFindSupport::FindFlags ) { return false; } int replaceAll(const QString &, const QString &, diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp index f5259658e6..718436afbf 100644 --- a/src/plugins/help/helpplugin.cpp +++ b/src/plugins/help/helpplugin.cpp @@ -79,6 +79,12 @@ #include <QtHelp/QHelpEngine> +#if !defined(QT_NO_WEBKIT) +#include <QtWebKit/QWebElement> +#include <QtWebKit/QWebElementCollection> +#include <QtWebKit/QWebFrame> +#endif + using namespace Core::Constants; using namespace Help::Internal; @@ -332,10 +338,11 @@ void HelpPlugin::extensionsInitialized() helpManager->registerDocumentation(filesToRegister); } -void HelpPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag HelpPlugin::aboutToShutdown() { if (m_sideBar) m_sideBar->saveSettings(m_core->settings(), QLatin1String("HelpSideBar")); + return SynchronousShutdown; } void HelpPlugin::setupUi() @@ -688,13 +695,11 @@ void HelpPlugin::activateContext() } else if (m_core->modeManager()->currentMode() == m_mode) return; - QString id; - QMap<QString, QUrl> links; - // Find out what to show + QMap<QString, QUrl> links; if (IContext *context = m_core->currentContextObject()) { - id = context->contextHelpId(); - links = Core::HelpManager::instance()->linksForIdentifier(id); + m_idFromContext = context->contextHelpId(); + links = Core::HelpManager::instance()->linksForIdentifier(m_idFromContext); } if (HelpViewer* viewer = viewerForContextMode()) { @@ -702,16 +707,26 @@ void HelpPlugin::activateContext() // No link found or no context object viewer->setHtml(tr("<html><head><title>No Documentation</title>" "</head><body><br/><center><b>%1</b><br/>No documentation " - "available.</center></body></html>").arg(id)); + "available.</center></body></html>").arg(m_idFromContext)); viewer->setSource(QUrl()); } else { const QUrl &source = *links.begin(); - if (viewer->source() != source) + const QUrl &oldSource = viewer->source(); + if (source != oldSource) { +#if !defined(QT_NO_WEBKIT) + viewer->stop(); +#endif viewer->setSource(source); + } viewer->setFocus(); + connect(viewer, SIGNAL(loadFinished(bool)), this, + SLOT(highlightSearchTerms())); + + if (source.toString().remove(source.fragment()) + == oldSource.toString().remove(oldSource.fragment())) { + highlightSearchTerms(); + } } - if (viewer != m_helpViewerForSideBar) - activateHelpMode(); } } @@ -819,6 +834,45 @@ void HelpPlugin::addBookmark() manager->showBookmarkDialog(m_centralWidget, viewer->title(), url); } +void HelpPlugin::highlightSearchTerms() +{ + if (HelpViewer* viewer = viewerForContextMode()) { + disconnect(viewer, SIGNAL(loadFinished(bool)), this, + SLOT(highlightSearchTerms())); + +#if !defined(QT_NO_WEBKIT) + const QString &attrValue = m_idFromContext.mid(m_idFromContext + .lastIndexOf(QChar(':')) + 1); + if (attrValue.isEmpty()) + return; + + const QWebElement &document = viewer->page()->mainFrame()->documentElement(); + const QWebElementCollection &collection = document.findAll(QLatin1String("h3.fn a")); + + const QLatin1String property("background-color"); + foreach (const QWebElement &element, collection) { + const QString &name = element.attribute(QLatin1String("name")); + if (name.isEmpty()) + continue; + + if (m_oldAttrValue == name) { + QWebElement parent = element.parent(); + parent.setStyleProperty(property, m_styleProperty); + } + + if (attrValue == name) { + QWebElement parent = element.parent(); + m_styleProperty = parent.styleProperty(property, + QWebElement::InlineStyle); + parent.setStyleProperty(property, QLatin1String("yellow")); + } + } + m_oldAttrValue = attrValue; +#endif + viewer->findText(m_idFromContext, 0, false, true); + } +} + void HelpPlugin::handleHelpRequest(const QUrl &url) { if (HelpViewer::launchWithExternalApp(url)) diff --git a/src/plugins/help/helpplugin.h b/src/plugins/help/helpplugin.h index a5099db7f7..b97b61c2b6 100644 --- a/src/plugins/help/helpplugin.h +++ b/src/plugins/help/helpplugin.h @@ -70,7 +70,7 @@ public: bool initialize(const QStringList &arguments, QString *error_message); void extensionsInitialized(); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); private slots: void modeChanged(Core::IMode *mode); @@ -98,6 +98,7 @@ private slots: void updateCloseButton(); void setupHelpEngineIfNeeded(); + void highlightSearchTerms(); void handleHelpRequest(const QUrl &url); private: @@ -134,6 +135,10 @@ private: Core::MiniSplitter *m_splitter; QToolButton *m_closeButton; + + QString m_oldAttrValue; + QString m_styleProperty; + QString m_idFromContext; }; } // namespace Internal diff --git a/src/plugins/perforce/perforcesettings.cpp b/src/plugins/perforce/perforcesettings.cpp index 745f6ab1a6..7cf1f8712d 100644 --- a/src/plugins/perforce/perforcesettings.cpp +++ b/src/plugins/perforce/perforcesettings.cpp @@ -46,6 +46,7 @@ static const char *portKeyC = "Port"; static const char *clientKeyC = "Client"; static const char *userKeyC = "User"; static const char *promptToSubmitKeyC = "PromptForSubmit"; +static const char *autoOpenKeyC = "PromptToOpen"; static const char *timeOutKeyC = "TimeOut"; static const char *logCountKeyC = "LogCount"; @@ -68,7 +69,8 @@ Settings::Settings() : logCount(defaultLogCount), defaultEnv(true), timeOutS(defaultTimeOutS), - promptToSubmit(true) + promptToSubmit(true), + autoOpen(true) { } @@ -78,7 +80,8 @@ bool Settings::equals(const Settings &rhs) const && logCount == rhs.logCount && p4Command == rhs.p4Command && p4Port == rhs.p4Port && p4Client == rhs.p4Client && p4User == rhs.p4User - && timeOutS == rhs.timeOutS && promptToSubmit == rhs.promptToSubmit; + && timeOutS == rhs.timeOutS && promptToSubmit == rhs.promptToSubmit + && autoOpen == rhs.autoOpen; }; QStringList Settings::commonP4Arguments() const @@ -115,6 +118,7 @@ void PerforceSettings::fromSettings(QSettings *settings) m_settings.timeOutS = settings->value(QLatin1String(timeOutKeyC), defaultTimeOutS).toInt(); m_settings.promptToSubmit = settings->value(QLatin1String(promptToSubmitKeyC), true).toBool(); m_settings.logCount = settings->value(QLatin1String(logCountKeyC), int(defaultLogCount)).toInt(); + m_settings.autoOpen = settings->value(QLatin1String(autoOpenKeyC), true).toBool(); settings->endGroup(); } @@ -129,6 +133,7 @@ void PerforceSettings::toSettings(QSettings *settings) const settings->setValue(QLatin1String(timeOutKeyC), m_settings.timeOutS); settings->setValue(QLatin1String(promptToSubmitKeyC), m_settings.promptToSubmit); settings->setValue(QLatin1String(logCountKeyC), m_settings.logCount); + settings->setValue(QLatin1String(autoOpenKeyC), m_settings.autoOpen); settings->endGroup(); } @@ -180,6 +185,16 @@ void PerforceSettings::setPromptToSubmit(bool p) m_settings.promptToSubmit = p; } +bool PerforceSettings::autoOpen() const +{ + return m_settings.autoOpen; +} + +void PerforceSettings::setAutoOpen(bool b) +{ + m_settings.autoOpen = b; +} + QString PerforceSettings::topLevel() const { return m_topLevel; diff --git a/src/plugins/perforce/perforcesettings.h b/src/plugins/perforce/perforcesettings.h index 803645237f..cdb1ee0675 100644 --- a/src/plugins/perforce/perforcesettings.h +++ b/src/plugins/perforce/perforcesettings.h @@ -61,6 +61,7 @@ struct Settings { bool defaultEnv; int timeOutS; bool promptToSubmit; + bool autoOpen; }; inline bool operator==(const Settings &s1, const Settings &s2) { return s1.equals(s2); } @@ -122,6 +123,8 @@ public: bool defaultEnv() const; bool promptToSubmit() const; void setPromptToSubmit(bool p); + bool autoOpen() const; + void setAutoOpen(bool p); // Return basic arguments, including -d and server connection parameters. QStringList commonP4Arguments(const QString &workingDir) const; diff --git a/src/plugins/perforce/perforceversioncontrol.cpp b/src/plugins/perforce/perforceversioncontrol.cpp index 2dc414f59c..0f0a5e8190 100644 --- a/src/plugins/perforce/perforceversioncontrol.cpp +++ b/src/plugins/perforce/perforceversioncontrol.cpp @@ -70,6 +70,14 @@ bool PerforceVersionControl::vcsOpen(const QString &fileName) return m_plugin->vcsOpen(fi.absolutePath(), fi.fileName()); } +Core::IVersionControl::SettingsFlags PerforceVersionControl::settingsFlags() const +{ + SettingsFlags rc; + if (m_plugin->settings().autoOpen()) + rc|= AutoOpen; + return rc; +}; + bool PerforceVersionControl::vcsAdd(const QString &fileName) { const QFileInfo fi(fileName); diff --git a/src/plugins/perforce/perforceversioncontrol.h b/src/plugins/perforce/perforceversioncontrol.h index 1e14cd2810..b3a01f7a6c 100644 --- a/src/plugins/perforce/perforceversioncontrol.h +++ b/src/plugins/perforce/perforceversioncontrol.h @@ -50,6 +50,7 @@ public: virtual bool supportsOperation(Operation operation) const; virtual bool vcsOpen(const QString &fileName); + virtual SettingsFlags settingsFlags() const; virtual bool vcsAdd(const QString &fileName); virtual bool vcsDelete(const QString &filename); virtual bool vcsMove(const QString &from, const QString &to); diff --git a/src/plugins/perforce/settingspage.cpp b/src/plugins/perforce/settingspage.cpp index 5bdfe0f547..47fa6c9d24 100644 --- a/src/plugins/perforce/settingspage.cpp +++ b/src/plugins/perforce/settingspage.cpp @@ -85,6 +85,7 @@ Settings SettingsPageWidget::settings() const settings.timeOutS = m_ui.timeOutSpinBox->value(); settings.logCount = m_ui.logCountSpinBox->value(); settings.promptToSubmit = m_ui.promptToSubmitCheckBox->isChecked(); + settings.autoOpen = m_ui.autoOpenCheckBox->isChecked(); return settings; } @@ -98,6 +99,7 @@ void SettingsPageWidget::setSettings(const PerforceSettings &s) m_ui.logCountSpinBox->setValue(s.logCount()); m_ui.timeOutSpinBox->setValue(s.timeOutS()); m_ui.promptToSubmitCheckBox->setChecked(s.promptToSubmit()); + m_ui.autoOpenCheckBox->setChecked(s.autoOpen()); } void SettingsPageWidget::setStatusText(const QString &t) diff --git a/src/plugins/perforce/settingspage.ui b/src/plugins/perforce/settingspage.ui index 6f27041508..e62622038b 100644 --- a/src/plugins/perforce/settingspage.ui +++ b/src/plugins/perforce/settingspage.ui @@ -2,14 +2,6 @@ <ui version="4.0"> <class>Perforce::Internal::SettingsPage</class> <widget class="QWidget" name="Perforce::Internal::SettingsPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>437</width> - <height>407</height> - </rect> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QGroupBox" name="configGroupBox"> @@ -25,7 +17,7 @@ </widget> </item> <item row="0" column="1"> - <widget class="Utils::PathChooser" name="pathChooser"/> + <widget class="Utils::PathChooser" name="pathChooser" native="true"/> </item> </layout> </widget> @@ -134,6 +126,13 @@ </property> </widget> </item> + <item row="3" column="0" colspan="2"> + <widget class="QCheckBox" name="autoOpenCheckBox"> + <property name="text"> + <string>Automatically open files when editing</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/plugins/projectexplorer/allprojectsfind.cpp b/src/plugins/projectexplorer/allprojectsfind.cpp index abb1b90b23..78823bda17 100644 --- a/src/plugins/projectexplorer/allprojectsfind.cpp +++ b/src/plugins/projectexplorer/allprojectsfind.cpp @@ -60,7 +60,7 @@ QString AllProjectsFind::id() const return QLatin1String("All Projects"); } -QString AllProjectsFind::name() const +QString AllProjectsFind::displayName() const { return tr("All Projects"); } diff --git a/src/plugins/projectexplorer/allprojectsfind.h b/src/plugins/projectexplorer/allprojectsfind.h index 2f78757c15..d4710a7381 100644 --- a/src/plugins/projectexplorer/allprojectsfind.h +++ b/src/plugins/projectexplorer/allprojectsfind.h @@ -51,7 +51,7 @@ public: AllProjectsFind(ProjectExplorerPlugin *plugin, Find::SearchResultWindow *resultWindow); QString id() const; - QString name() const; + QString displayName() const; bool isEnabled() const; QKeySequence defaultShortcut() const; diff --git a/src/plugins/projectexplorer/buildconfigdialog.cpp b/src/plugins/projectexplorer/buildconfigdialog.cpp index 5e3a58fbe1..32e018ec45 100644 --- a/src/plugins/projectexplorer/buildconfigdialog.cpp +++ b/src/plugins/projectexplorer/buildconfigdialog.cpp @@ -31,6 +31,7 @@ #include "project.h" #include "runconfiguration.h" #include "buildconfiguration.h" +#include "target.h" #include <QtGui/QVBoxLayout> #include <QtGui/QPushButton> diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index dc58861cab..c94f5452cf 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -411,27 +411,43 @@ bool BuildManager::buildQueueAppend(QList<BuildStep *> steps) return true; } -void BuildManager::buildProjects(const QList<BuildConfiguration *> &configurations) +bool BuildManager::buildProjects(const QList<BuildConfiguration *> &configurations) { QList<BuildStep *> steps; - foreach(BuildConfiguration *bc, configurations) { + foreach(BuildConfiguration *bc, configurations) steps.append(bc->steps(BuildStep::Build)); - // TODO: Verify that this is indeed what we want. - steps.append(bc->steps(BuildStep::Deploy)); + + bool success = buildQueueAppend(steps); + if (!success) { + m_outputWindow->popup(false); + return false; } + if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput) + m_outputWindow->popup(false); + startBuildQueue(); + return true; +} + +bool BuildManager::deployProjects(const QList<BuildConfiguration *> &configurations) +{ + QList<BuildStep *> steps; + foreach(BuildConfiguration *bc, configurations) + steps.append(bc->steps(BuildStep::Deploy)); + bool success = buildQueueAppend(steps); if (!success) { m_outputWindow->popup(false); - return; + return false; } if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput) m_outputWindow->popup(false); startBuildQueue(); + return true; } -void BuildManager::cleanProjects(const QList<BuildConfiguration *> &configurations) +bool BuildManager::cleanProjects(const QList<BuildConfiguration *> &configurations) { QList<BuildStep *> steps; foreach(BuildConfiguration *bc, configurations) @@ -440,22 +456,28 @@ void BuildManager::cleanProjects(const QList<BuildConfiguration *> &configuratio bool success = buildQueueAppend(steps); if (!success) { m_outputWindow->popup(false); - return; + return false; } if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput) m_outputWindow->popup(false); startBuildQueue(); + return true; +} + +bool BuildManager::buildProject(BuildConfiguration *configuration) +{ + return buildProjects(QList<BuildConfiguration *>() << configuration); } -void BuildManager::buildProject(BuildConfiguration *configuration) +bool BuildManager::deployProject(BuildConfiguration *configuration) { - buildProjects(QList<BuildConfiguration *>() << configuration); + return deployProjects(QList<BuildConfiguration *>() << configuration); } -void BuildManager::cleanProject(BuildConfiguration *configuration) +bool BuildManager::cleanProject(BuildConfiguration *configuration) { - cleanProjects(QList<BuildConfiguration *>() << configuration); + return cleanProjects(QList<BuildConfiguration *>() << configuration); } void BuildManager::appendStep(BuildStep *step) diff --git a/src/plugins/projectexplorer/buildmanager.h b/src/plugins/projectexplorer/buildmanager.h index 45b2218d4c..0423d9e807 100644 --- a/src/plugins/projectexplorer/buildmanager.h +++ b/src/plugins/projectexplorer/buildmanager.h @@ -72,10 +72,12 @@ public: bool tasksAvailable() const; - void buildProject(BuildConfiguration *bc); - void buildProjects(const QList<BuildConfiguration *> &configurations); - void cleanProject(BuildConfiguration *configuration); - void cleanProjects(const QList<BuildConfiguration *> &configurations); + bool buildProject(BuildConfiguration *bc); + bool buildProjects(const QList<BuildConfiguration *> &configurations); + bool deployProject(BuildConfiguration *bc); + bool deployProjects(const QList<BuildConfiguration *> &configurations); + bool cleanProject(BuildConfiguration *configuration); + bool cleanProjects(const QList<BuildConfiguration *> &configurations); bool isBuilding(Project *p); bool isBuilding(BuildStep *step); diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index 95e12558ad..6424438b7f 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -38,6 +38,7 @@ #include <QtGui/QKeyEvent> #include <QtGui/QIcon> +#include <QtGui/QTextCharFormat> #include <QtGui/QTextBlock> #include <QtGui/QTextCursor> #include <QtGui/QTextEdit> @@ -53,14 +54,14 @@ const int MAX_LINECOUNT = 10000; CompileOutputWindow::CompileOutputWindow(BuildManager * /*bm*/) { - m_textEdit = new QPlainTextEdit(); - m_textEdit->setWindowTitle(tr("Compile Output")); - m_textEdit->setWindowIcon(QIcon(":/qt4projectmanager/images/window.png")); - m_textEdit->setReadOnly(true); - m_textEdit->setFrameStyle(QFrame::NoFrame); + m_outputWindow = new OutputWindow(); + m_outputWindow->setWindowTitle(tr("Compile Output")); + m_outputWindow->setWindowIcon(QIcon(":/qt4projectmanager/images/window.png")); + m_outputWindow->setReadOnly(true); + Aggregation::Aggregate *agg = new Aggregation::Aggregate; - agg->add(m_textEdit); - agg->add(new Find::BaseTextFind(m_textEdit)); + agg->add(m_outputWindow); + agg->add(new Find::BaseTextFind(m_outputWindow)); qRegisterMetaType<QTextCharFormat>("QTextCharFormat"); @@ -76,7 +77,7 @@ CompileOutputWindow::~CompileOutputWindow() bool CompileOutputWindow::hasFocus() { - return m_textEdit->hasFocus(); + return m_outputWindow->hasFocus(); } bool CompileOutputWindow::canFocus() @@ -86,50 +87,28 @@ bool CompileOutputWindow::canFocus() void CompileOutputWindow::setFocus() { - m_textEdit->setFocus(); + m_outputWindow->setFocus(); } QWidget *CompileOutputWindow::outputWidget(QWidget *) { - return m_textEdit; + return m_outputWindow; } void CompileOutputWindow::appendText(const QString &text, const QTextCharFormat &textCharFormat) { - if (m_textEdit->document()->blockCount() > MAX_LINECOUNT) - return; - bool shouldScroll = (m_textEdit->verticalScrollBar()->value() == - m_textEdit->verticalScrollBar()->maximum()); - QString textWithNewline = text; - if (!textWithNewline.endsWith("\n")) - textWithNewline.append("\n"); - QTextCursor cursor = QTextCursor(m_textEdit->document()); - cursor.movePosition(QTextCursor::End); - cursor.beginEditBlock(); - cursor.insertText(textWithNewline, textCharFormat); - - if (m_textEdit->document()->blockCount() > MAX_LINECOUNT) { - QTextCharFormat tmp; - tmp.setFontWeight(QFont::Bold); - cursor.insertText(tr("Additional output omitted\n"), tmp); - } - - cursor.endEditBlock(); - - if (shouldScroll) - m_textEdit->setTextCursor(cursor); + m_outputWindow->appendText(text, textCharFormat, MAX_LINECOUNT); } void CompileOutputWindow::clearContents() { - m_textEdit->clear(); + m_outputWindow->clear(); m_taskPositions.clear(); } -void CompileOutputWindow::visibilityChanged(bool b) +void CompileOutputWindow::visibilityChanged(bool) { - if (b) - m_textEdit->verticalScrollBar()->setValue(m_textEdit->verticalScrollBar()->maximum()); + } int CompileOutputWindow::priorityInStatusBar() const @@ -164,7 +143,7 @@ bool CompileOutputWindow::canNavigate() void CompileOutputWindow::registerPositionOf(const Task &task) { - int blocknumber = m_textEdit->blockCount(); + int blocknumber = m_outputWindow->blockCount(); if (blocknumber > MAX_LINECOUNT) return; m_taskPositions.insert(task.taskId, blocknumber - 1); @@ -178,7 +157,7 @@ bool CompileOutputWindow::knowsPositionOf(const Task &task) void CompileOutputWindow::showPositionOf(const Task &task) { int position = m_taskPositions.value(task.taskId); - QTextCursor newCursor(m_textEdit->document()->findBlockByNumber(position)); + QTextCursor newCursor(m_outputWindow->document()->findBlockByNumber(position)); newCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); - m_textEdit->setTextCursor(newCursor); + m_outputWindow->setTextCursor(newCursor); } diff --git a/src/plugins/projectexplorer/compileoutputwindow.h b/src/plugins/projectexplorer/compileoutputwindow.h index 2065af56a8..5b4e7b8adb 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.h +++ b/src/plugins/projectexplorer/compileoutputwindow.h @@ -30,15 +30,14 @@ #ifndef COMPILEOUTPUTWINDOW_H #define COMPILEOUTPUTWINDOW_H +#include "outputwindow.h" #include <coreplugin/ioutputpane.h> #include <QtCore/QHash> -#include <QtGui/QColor> -#include <QtGui/QTextCharFormat> - QT_BEGIN_NAMESPACE class QPlainTextEdit; +class QTextCharFormat; QT_END_NAMESPACE namespace ProjectExplorer { @@ -80,7 +79,7 @@ public: void showPositionOf(const Task &task); private: - QPlainTextEdit *m_textEdit; + OutputWindow *m_outputWindow; QHash<unsigned int, int> m_taskPositions; ShowOutputTaskHandler * m_handler; }; diff --git a/src/plugins/projectexplorer/currentprojectfind.cpp b/src/plugins/projectexplorer/currentprojectfind.cpp index 7256a8603b..ef9ec44a9b 100644 --- a/src/plugins/projectexplorer/currentprojectfind.cpp +++ b/src/plugins/projectexplorer/currentprojectfind.cpp @@ -60,7 +60,7 @@ QString CurrentProjectFind::id() const return QLatin1String("Current Project"); } -QString CurrentProjectFind::name() const +QString CurrentProjectFind::displayName() const { return tr("Current Project"); } diff --git a/src/plugins/projectexplorer/currentprojectfind.h b/src/plugins/projectexplorer/currentprojectfind.h index 2e98e49e1b..5ee35a83c8 100644 --- a/src/plugins/projectexplorer/currentprojectfind.h +++ b/src/plugins/projectexplorer/currentprojectfind.h @@ -54,7 +54,7 @@ public: CurrentProjectFind(ProjectExplorerPlugin *plugin, Find::SearchResultWindow *resultWindow); QString id() const; - QString name() const; + QString displayName() const; bool isEnabled() const; QKeySequence defaultShortcut() const; diff --git a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp index de2894ac8c..43a3af6a73 100644 --- a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp +++ b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp @@ -38,6 +38,7 @@ #include <utils/detailswidget.h> #include <utils/pathchooser.h> +#include <QtCore/QDir> #include <QtGui/QCheckBox> #include <QtGui/QComboBox> #include <QtGui/QDialog> diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp index b9c8e7e18a..9ec1483b21 100644 --- a/src/plugins/projectexplorer/customwizard/customwizard.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp @@ -155,11 +155,10 @@ static inline bool createFile(Internal::CustomWizardFile cwFile, return false; } // Field replacement on contents - QString contents = QString::fromLocal8Bit(file.readAll()); - if (!contents.isEmpty() && !fm.isEmpty()) - Internal::CustomWizardContext::replaceFields(fm, &contents); + const QString contentsIn = QString::fromLocal8Bit(file.readAll()); + Core::GeneratedFile generatedFile; - generatedFile.setContents(contents); + generatedFile.setContents(Internal::CustomWizardContext::processFile(fm, contentsIn)); generatedFile.setPath(targetPath); Core::GeneratedFile::Attributes attributes = 0; if (cwFile.openEditor) diff --git a/src/plugins/projectexplorer/customwizard/customwizard.pri b/src/plugins/projectexplorer/customwizard/customwizard.pri index 61475f3ae4..ac8bf2ea26 100644 --- a/src/plugins/projectexplorer/customwizard/customwizard.pri +++ b/src/plugins/projectexplorer/customwizard/customwizard.pri @@ -1,7 +1,9 @@ INCLUDEPATH *= $$PWD HEADERS += $$PWD/customwizard.h \ $$PWD/customwizardparameters.h \ - $$PWD/customwizardpage.h + $$PWD/customwizardpage.h \ + customwizard/customwizardpreprocessor.h SOURCES += $$PWD/customwizard.cpp \ $$PWD/customwizardparameters.cpp \ - $$PWD/customwizardpage.cpp + $$PWD/customwizardpage.cpp \ + customwizard/customwizardpreprocessor.cpp diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp index 487c3aef37..d821ed5203 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp @@ -28,6 +28,7 @@ **************************************************************************/ #include "customwizardparameters.h" +#include "customwizardpreprocessor.h" #include <coreplugin/mimedatabase.h> #include <coreplugin/icore.h> @@ -644,5 +645,24 @@ void CustomWizardContext::reset() mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE))); } +QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in) +{ + + if (in.isEmpty()) + return in; + + if (!fm.isEmpty()) + replaceFields(fm, &in); + + QString out; + QString errorMessage; + if (!customWizardPreprocess(in, &out, &errorMessage)) { + qWarning("Error preprocessing custom widget file: %s\nFile:\n%s", + qPrintable(errorMessage), qPrintable(in)); + return QString(); + } + return out; +} + } // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h index e5c26abf3f..5d92843f04 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardparameters.h +++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h @@ -105,6 +105,7 @@ struct CustomWizardContext { // %Field:l% -> lower case replacement, 'u' upper case, // 'c' capitalize first letter. static void replaceFields(const FieldReplacementMap &fm, QString *s); + static QString processFile(const FieldReplacementMap &fm, QString in); FieldReplacementMap baseReplacements; }; diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp new file mode 100644 index 0000000000..2d0a279041 --- /dev/null +++ b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp @@ -0,0 +1,268 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "customwizardpreprocessor.h" + +#include <utils/qtcassert.h> + +#include <QtCore/QStringList> +#include <QtCore/QStack> +#include <QtCore/QRegExp> +#include <QtCore/QDebug> + +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptValue> + +namespace ProjectExplorer { +namespace Internal { + +enum { debug = 0 }; + +// Preprocessor: Conditional section type. +enum PreprocessorSection { + IfSection, + ElsifSection, + ElseSection, + EndifSection, + OtherSection +}; + +// Preprocessor: Section stack entry containing nested '@if' section +// state. +struct PreprocessStackEntry { + PreprocessStackEntry(PreprocessorSection section = OtherSection, + bool parentEnabled = true, + bool condition = false, + bool anyIfClauseMatched = false); + + PreprocessorSection section; + bool parentEnabled; + bool condition; // Current 'if/elsif' section is enabled. + bool anyIfClauseMatched; // Determines if 'else' triggers +}; + +PreprocessStackEntry::PreprocessStackEntry(PreprocessorSection s, + bool p, bool c, bool a) : + section(s), parentEnabled(p), condition(c), anyIfClauseMatched(a) +{ +} + +// Context for preprocessing. +class PreprocessContext { +public: + PreprocessContext(); + bool process(const QString &in, QString *out, QString *errorMessage); + +private: + void reset(); + bool evaluateExpression(const QString &expression, bool *result, QString *errorMessage); + PreprocessorSection preprocessorLine(const QString & in, QString *ifExpression) const; + + mutable QRegExp m_ifPattern; + mutable QRegExp m_elsifPattern; + const QRegExp m_elsePattern; + const QRegExp m_endifPattern; + + QStack<PreprocessStackEntry> m_sectionStack; + QScriptEngine m_scriptEngine; +}; + +PreprocessContext::PreprocessContext() : + // Cut out expression for 'if/elsif ' + m_ifPattern(QLatin1String("^([\\s]*@[\\s]*if[\\s]*)(.*)$")), + m_elsifPattern(QLatin1String("^([\\s]*@[\\s]*elsif[\\s]*)(.*)$")), + m_elsePattern(QLatin1String("^[\\s]*@[\\s]*else.*$")), + m_endifPattern(QLatin1String("^[\\s]*@[\\s]*endif.*$")) +{ + QTC_ASSERT(m_ifPattern.isValid() && m_elsifPattern.isValid() + && m_elsePattern.isValid() &&m_endifPattern.isValid(), return); +} + +void PreprocessContext::reset() +{ + m_sectionStack.clear(); // Add a default, enabled section. + m_sectionStack.push(PreprocessStackEntry(OtherSection, true, true)); +} + +// Determine type of line and return enumeration, cut out +// expression for '@if/@elsif'. +PreprocessorSection PreprocessContext::preprocessorLine(const QString &in, + QString *ifExpression) const +{ + if (m_ifPattern.exactMatch(in)) { + *ifExpression = m_ifPattern.cap(2).trimmed(); + return IfSection; + } + if (m_elsifPattern.exactMatch(in)) { + *ifExpression = m_elsifPattern.cap(2).trimmed(); + return ElsifSection; + } + + ifExpression->clear(); + + if (m_elsePattern.exactMatch(in)) + return ElseSection; + if (m_endifPattern.exactMatch(in)) + return EndifSection; + return OtherSection; +} + +// Evaluate an expression within an 'if'/'elsif' to a bool via QScript +bool PreprocessContext::evaluateExpression(const QString &expression, + bool *result, + QString *errorMessage) +{ + errorMessage->clear(); + *result = false; + m_scriptEngine.clearExceptions(); + const QScriptValue value = m_scriptEngine.evaluate(expression); + if (m_scriptEngine.hasUncaughtException()) { + *errorMessage = QString::fromLatin1("Error in '%1': %2"). + arg(expression, m_scriptEngine.uncaughtException().toString()); + return false; + } + // Try to convert to bool, be that an int or whatever. + if (value.isBool()) { + *result = value.toBool(); + return true; + } + if (value.isNumber()) { + *result = !qFuzzyCompare(value.toNumber(), 0); + return true; + } + if (value.isString()) { + *result = !value.toString().isEmpty(); + return true; + } + *errorMessage = QString::fromLatin1("Cannot convert result of '%1' ('%2'to bool."). + arg(expression, value.toString()); + return false; +} + +static inline QString msgEmptyStack(int line) +{ + return QString::fromLatin1("Unmatched '@endif' at line %1.").arg(line); +} + +bool PreprocessContext::process(const QString &in, QString *out, QString *errorMessage) +{ + out->clear(); + if (in.isEmpty()) + return true; + + out->reserve(in.size()); + reset(); + + const QChar newLine = QLatin1Char('\n'); + const QStringList lines = in.split(newLine, QString::KeepEmptyParts); + const int lineCount = lines.size(); + for (int l = 0; l < lineCount; l++) { + // Check for element of the stack (be it dummy, else something is wrong). + if (m_sectionStack.isEmpty()) { + *errorMessage = msgEmptyStack(l); + return false; + } + QString expression; + bool expressionValue = false; + PreprocessStackEntry &top = m_sectionStack.back(); + + switch (preprocessorLine(lines.at(l), &expression)) { + case IfSection: + // '@If': Push new section + if (top.parentEnabled) { + if (!evaluateExpression(expression, &expressionValue, errorMessage)) { + *errorMessage = QString::fromLatin1("Error in @if at %1: %2"). + arg(l + 1).arg(*errorMessage); + return false; + } + } + if (debug) + qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue); + m_sectionStack.push(PreprocessStackEntry(IfSection, + top.condition, expressionValue, expressionValue)); + break; + case ElsifSection: // '@elsif': Check condition. + if (top.section != IfSection && top.section != ElsifSection) { + *errorMessage = QString::fromLatin1("No preceding @if found for @elsif at %1"). + arg(l + 1); + return false; + } + if (top.parentEnabled) { + if (!evaluateExpression(expression, &expressionValue, errorMessage)) { + *errorMessage = QString::fromLatin1("Error in @elsif at %1: %2"). + arg(l + 1).arg(*errorMessage); + return false; + } + } + if (debug) + qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue); + top.section = ElsifSection; + // ignore consecutive '@elsifs' once something matched + if (top.anyIfClauseMatched) { + top.condition = false; + } else { + if ( (top.condition = expressionValue) ) + top.anyIfClauseMatched = true; + } + break; + case ElseSection: // '@else': Check condition. + if (top.section != IfSection && top.section != ElsifSection) { + *errorMessage = QString::fromLatin1("No preceding @if/@elsif found for @else at %1"). + arg(l + 1); + return false; + } + expressionValue = top.parentEnabled && !top.anyIfClauseMatched; + if (debug) + qDebug("%s -> %d", qPrintable(lines.at(l)), expressionValue); + top.section = ElseSection; + top.condition = expressionValue; + break; + case EndifSection: // '@endif': Discard section. + m_sectionStack.pop(); + break; + case OtherSection: // Rest: Append according to current condition. + if (top.condition) { + out->append(lines.at(l)); + out->append(newLine); + } + break; + } // switch section + + } // for lines + return true; +} + +bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage) +{ + PreprocessContext context; + return context.process(in, out, errorMessage); +} + +} // namespace Internal +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h new file mode 100644 index 0000000000..be84a04f72 --- /dev/null +++ b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h @@ -0,0 +1,58 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CUSTOMWIZARDPREPROCESSOR_H +#define CUSTOMWIZARDPREPROCESSOR_H + +#include <QtCore/QString> + +namespace ProjectExplorer { +namespace Internal { + +/* Preprocess a string using simple syntax: \code + +Text +@if <JavaScript-expression> +Bla... +@elsif <JavaScript-expression2> +Blup +@endif +\endcode + +* JavaScript-expressions must evaluate to integers or boolean, like +* '2 == 1 + 1', '"a" == "a"'. The variables of the custom wizard will be +* expanded beforem, so , "%VAR%" should be used for strings and %VAR% for integers. +*/ + +bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage); + +} // namespace Internal +} // namespace ProjectExplorer + +#endif // CUSTOMWIZARDPREPROCESSOR_H diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp index 3bbc97bf28..315ed6f1da 100644 --- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp +++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp @@ -30,6 +30,7 @@ #include "miniprojecttargetselector.h" #include "buildconfigurationmodel.h" #include "runconfigurationmodel.h" +#include "target.h" #include <utils/qtcassert.h> #include <utils/styledbar.h> diff --git a/src/plugins/projectexplorer/outputformatter.cpp b/src/plugins/projectexplorer/outputformatter.cpp index 69bb04b72e..9cf37e3103 100644 --- a/src/plugins/projectexplorer/outputformatter.cpp +++ b/src/plugins/projectexplorer/outputformatter.cpp @@ -37,8 +37,8 @@ using namespace ProjectExplorer; using namespace TextEditor; -OutputFormatter::OutputFormatter(QObject *parent) - : QObject(parent) +OutputFormatter::OutputFormatter() + : QObject() , m_formats(0) { initFormats(); @@ -58,7 +58,6 @@ QPlainTextEdit *OutputFormatter::plainTextEdit() const void OutputFormatter::setPlainTextEdit(QPlainTextEdit *plainText) { m_plainTextEdit = plainText; - setParent(m_plainTextEdit); } void OutputFormatter::appendApplicationOutput(const QString &text, bool onStdErr) @@ -83,15 +82,6 @@ void OutputFormatter::append(const QString &text, const QTextCharFormat &format) cursor.insertText(text, format); } -void OutputFormatter::mousePressEvent(QMouseEvent * /*e*/) -{} - -void OutputFormatter::mouseReleaseEvent(QMouseEvent * /*e*/) -{} - -void OutputFormatter::mouseMoveEvent(QMouseEvent * /*e*/) -{} - void OutputFormatter::initFormats() { FontSettings fs = TextEditorSettings::instance()->fontSettings(); @@ -117,3 +107,8 @@ void OutputFormatter::initFormats() m_formats[StdErrFormat].setFont(font); m_formats[StdErrFormat].setForeground(QColor(200, 0, 0)); } + +void OutputFormatter::handleLink(const QString &href) +{ + Q_UNUSED(href); +} diff --git a/src/plugins/projectexplorer/outputformatter.h b/src/plugins/projectexplorer/outputformatter.h index effff7725a..71737a9e7c 100644 --- a/src/plugins/projectexplorer/outputformatter.h +++ b/src/plugins/projectexplorer/outputformatter.h @@ -46,7 +46,7 @@ class PROJECTEXPLORER_EXPORT OutputFormatter: public QObject Q_OBJECT public: - OutputFormatter(QObject *parent = 0); + OutputFormatter(); virtual ~OutputFormatter(); QPlainTextEdit *plainTextEdit() const; @@ -55,9 +55,7 @@ public: virtual void appendApplicationOutput(const QString &text, bool onStdErr); virtual void appendMessage(const QString &text, bool isError); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); + virtual void handleLink(const QString &href); protected: enum Format { diff --git a/src/plugins/projectexplorer/outputwindow.cpp b/src/plugins/projectexplorer/outputwindow.cpp index 91d0cab951..99a8d34d83 100644 --- a/src/plugins/projectexplorer/outputwindow.cpp +++ b/src/plugins/projectexplorer/outputwindow.cpp @@ -33,6 +33,7 @@ #include "projectexplorersettings.h" #include "runconfiguration.h" #include "session.h" +#include "outputformatter.h" #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actioncontainer.h> @@ -43,6 +44,8 @@ #include <coreplugin/icontext.h> #include <find/basetextfind.h> #include <aggregation/aggregate.h> +#include <texteditor/basetexteditor.h> +#include <projectexplorer/project.h> #include <QtGui/QIcon> #include <QtGui/QScrollBar> @@ -56,6 +59,7 @@ #include <QtGui/QVBoxLayout> #include <QtGui/QTabWidget> #include <QtGui/QToolButton> +#include <QtGui/QShowEvent> using namespace ProjectExplorer::Internal; using namespace ProjectExplorer; @@ -209,7 +213,7 @@ void OutputPane::createNewOutputWindow(RunControl *rc) OutputWindow *ow = static_cast<OutputWindow *>(m_tabWidget->widget(i)); ow->grayOutOldContent(); ow->verticalScrollBar()->setValue(ow->verticalScrollBar()->maximum()); - ow->setFormatter(rc->createOutputFormatter(ow)); + ow->setFormatter(rc->outputFormatter()); m_outputWindows.insert(rc, ow); found = true; break; @@ -217,7 +221,9 @@ void OutputPane::createNewOutputWindow(RunControl *rc) } if (!found) { OutputWindow *ow = new OutputWindow(m_tabWidget); - ow->setFormatter(rc->createOutputFormatter(ow)); + ow->setWindowTitle(tr("Application Output Window")); + ow->setWindowIcon(QIcon(":/qt4projectmanager/images/window.png")); + ow->setFormatter(rc->outputFormatter()); Aggregation::Aggregate *agg = new Aggregation::Aggregate; agg->add(ow); agg->add(new Find::BaseTextFind(ow)); @@ -372,13 +378,14 @@ bool OutputPane::canNavigate() OutputWindow::OutputWindow(QWidget *parent) : QPlainTextEdit(parent) + , m_formatter(0) , m_enforceNewline(false) , m_scrollToBottom(false) + , m_linksActive(true) + , m_mousePressed(false) { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //setCenterOnScroll(false); - setWindowTitle(tr("Application Output Window")); - setWindowIcon(QIcon(":/qt4projectmanager/images/window.png")); setFrameShape(QFrame::NoFrame); setMouseTracking(true); @@ -427,6 +434,42 @@ OutputWindow::~OutputWindow() delete m_outputWindowContext; } +void OutputWindow::mousePressEvent(QMouseEvent * e) +{ + m_mousePressed = true; + QPlainTextEdit::mousePressEvent(e); +} + + +void OutputWindow::mouseReleaseEvent(QMouseEvent *e) +{ + m_mousePressed = false; + + if (!m_linksActive) { + // Mouse was released, activate links again + m_linksActive = true; + return; + } + + const QString href = anchorAt(e->pos()); + if (m_formatter) + m_formatter->handleLink(href); + QPlainTextEdit::mousePressEvent(e); +} + +void OutputWindow::mouseMoveEvent(QMouseEvent *e) +{ + // Cursor was dragged to make a selection, deactivate links + if (m_mousePressed && textCursor().hasSelection()) + m_linksActive = false; + + if (!m_linksActive || anchorAt(e->pos()).isEmpty()) + viewport()->setCursor(Qt::IBeamCursor); + else + viewport()->setCursor(Qt::PointingHandCursor); + QPlainTextEdit::mouseMoveEvent(e); +} + OutputFormatter *OutputWindow::formatter() const { return m_formatter; @@ -514,6 +557,28 @@ void OutputWindow::appendMessage(const QString &out, bool isError) enableUndoRedo(); } +// TODO rename +void OutputWindow::appendText(const QString &text, const QTextCharFormat &format, int maxLineCount) +{ + if (document()->blockCount() > maxLineCount) + return; + const bool atBottom = isScrollbarAtBottom(); + QTextCursor cursor = QTextCursor(document()); + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); + cursor.insertText(doNewlineEnfocement(text), format); + + if (document()->blockCount() > maxLineCount) { + QTextCharFormat tmp; + tmp.setFontWeight(QFont::Bold); + cursor.insertText(tr("Additional output omitted\n"), tmp); + } + + cursor.endEditBlock(); + if (atBottom) + scrollToBottom(); +} + bool OutputWindow::isScrollbarAtBottom() const { return verticalScrollBar()->value() == verticalScrollBar()->maximum(); @@ -552,21 +617,3 @@ void OutputWindow::enableUndoRedo() setMaximumBlockCount(0); setUndoRedoEnabled(true); } - -void OutputWindow::mousePressEvent(QMouseEvent *e) -{ - QPlainTextEdit::mousePressEvent(e); - m_formatter->mousePressEvent(e); -} - -void OutputWindow::mouseReleaseEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseReleaseEvent(e); - m_formatter->mouseReleaseEvent(e); -} - -void OutputWindow::mouseMoveEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseMoveEvent(e); - m_formatter->mouseMoveEvent(e); -} diff --git a/src/plugins/projectexplorer/outputwindow.h b/src/plugins/projectexplorer/outputwindow.h index 0646c16f7e..4cc3eecb03 100644 --- a/src/plugins/projectexplorer/outputwindow.h +++ b/src/plugins/projectexplorer/outputwindow.h @@ -31,12 +31,9 @@ #define OUTPUTWINDOW_H #include <coreplugin/ioutputpane.h> -#include <projectexplorer/outputformatter.h> -#include <QtCore/QObject> #include <QtCore/QHash> #include <QtGui/QIcon> -#include <QtGui/QShowEvent> #include <QtGui/QPlainTextEdit> QT_BEGIN_NAMESPACE @@ -50,8 +47,9 @@ namespace Core { } namespace ProjectExplorer { - +class OutputFormatter; class RunControl; +class Project; namespace Constants { const char * const C_APP_OUTPUT = "Application Output"; @@ -137,29 +135,38 @@ public: void appendApplicationOutput(const QString &out, bool onStdErr); void appendApplicationOutputInline(const QString &out, bool onStdErr); void appendMessage(const QString &out, bool isError); + /// appends a \p text using \p format without using formater + void appendText(const QString &text, const QTextCharFormat &format, int maxLineCount); void grayOutOldContent(); void showEvent(QShowEvent *); -protected: - void mousePressEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); + void clear() + { + m_enforceNewline = false; + QPlainTextEdit::clear(); + } +protected: bool isScrollbarAtBottom() const; void scrollToBottom(); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + private: void enableUndoRedo(); QString doNewlineEnfocement(const QString &out); -private: Core::BaseContext *m_outputWindowContext; OutputFormatter *m_formatter; bool m_enforceNewline; bool m_scrollToBottom; + bool m_linksActive; + bool m_mousePressed; }; } // namespace Internal diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index 01b4569cb3..f88dcabd76 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -31,7 +31,6 @@ #define PROJECT_H #include "projectexplorer_export.h" -#include "target.h" #include <QtCore/QObject> #include <QtCore/QSet> diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index ad0cebcde4..aa6aa70e2b 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -97,6 +97,7 @@ #include <utils/consoleprocess.h> #include <utils/qtcassert.h> #include <utils/parameteraction.h> +#include <utils/stringutils.h> #include <QtCore/QtPlugin> #include <QtCore/QDateTime> @@ -146,6 +147,10 @@ struct ProjectExplorerPluginPrivate { Utils::ParameterAction *m_rebuildActionContextMenu; QAction *m_rebuildSessionAction; QAction *m_cleanProjectOnlyAction; + QAction *m_deployProjectOnlyAction; + Utils::ParameterAction *m_deployAction; + Utils::ParameterAction *m_deployActionContextMenu; + QAction *m_deploySessionAction; Utils::ParameterAction *m_cleanAction; Utils::ParameterAction *m_cleanActionContextMenu; QAction *m_cleanSessionAction; @@ -312,13 +317,12 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er ProcessStepFactory *processStepFactory = new ProcessStepFactory; addAutoReleasedObject(processStepFactory); - ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); AllProjectsFind *allProjectsFind = new AllProjectsFind(this, - pm->getObject<Find::SearchResultWindow>()); + Find::SearchResultWindow::instance()); addAutoReleasedObject(allProjectsFind); CurrentProjectFind *currentProjectFind = new CurrentProjectFind(this, - pm->getObject<Find::SearchResultWindow>()); + Find::SearchResultWindow::instance()); addAutoReleasedObject(currentProjectFind); addAutoReleasedObject(new LocalApplicationRunControlFactory); @@ -569,6 +573,12 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er mbuild->addAction(cmd, Constants::G_BUILD_SESSION); msessionContextMenu->addAction(cmd, Constants::G_SESSION_BUILD); + // deploy session + d->m_deploySessionAction = new QAction(tr("Deploy All"), this); + cmd = am->registerAction(d->m_deploySessionAction, Constants::DEPLOYSESSION, globalcontext); + mbuild->addAction(cmd, Constants::G_BUILD_SESSION); + msessionContextMenu->addAction(cmd, Constants::G_SESSION_BUILD); + // clean session QIcon cleanIcon(Constants::ICON_CLEAN); cleanIcon.addFile(Constants::ICON_CLEAN_SMALL); @@ -594,6 +604,14 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er cmd->setDefaultText(d->m_rebuildAction->text()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT); + // deploy action + d->m_deployAction = new Utils::ParameterAction(tr("Deploy Project"), tr("Deploy Project \"%1\""), + Utils::ParameterAction::AlwaysEnabled, this); + cmd = am->registerAction(d->m_deployAction, Constants::DEPLOY, globalcontext); + cmd->setAttribute(Core::Command::CA_UpdateText); + cmd->setDefaultText(d->m_deployAction->text()); + mbuild->addAction(cmd, Constants::G_BUILD_PROJECT); + // clean action d->m_cleanAction = new Utils::ParameterAction(tr("Clean Project"), tr("Clean Project \"%1\""), Utils::ParameterAction::AlwaysEnabled, this); @@ -618,6 +636,14 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er cmd->setDefaultText(d->m_rebuildActionContextMenu->text()); mproject->addAction(cmd, Constants::G_PROJECT_BUILD); + // deploy action (context menu) + d->m_deployActionContextMenu = new Utils::ParameterAction(tr("Deploy Project"), tr("Deploy Project \"%1\""), + Utils::ParameterAction::AlwaysEnabled, this); + cmd = am->registerAction(d->m_rebuildActionContextMenu, Constants::DEPLOYCM, globalcontext); + cmd->setAttribute(Core::Command::CA_UpdateText); + cmd->setDefaultText(d->m_deployActionContextMenu->text()); + mproject->addAction(cmd, Constants::G_PROJECT_BUILD); + // clean action (context menu) d->m_cleanActionContextMenu = new Utils::ParameterAction(tr("Clean Project"), tr("Clean Project \"%1\""), Utils::ParameterAction::AlwaysEnabled, this); @@ -634,6 +660,10 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er d->m_rebuildProjectOnlyAction = new QAction(tr("Rebuild Without Dependencies"), this); cmd = am->registerAction(d->m_rebuildProjectOnlyAction, Constants::REBUILDPROJECTONLY, globalcontext); + // deploy without dependencies action + d->m_deployProjectOnlyAction = new QAction(tr("Deploy Without Dependencies"), this); + cmd = am->registerAction(d->m_deployProjectOnlyAction, Constants::DEPLOYPROJECTONLY, globalcontext); + // clean without dependencies action d->m_cleanProjectOnlyAction = new QAction(tr("Clean Without Dependencies"), this); cmd = am->registerAction(d->m_cleanProjectOnlyAction, Constants::CLEANPROJECTONLY, globalcontext); @@ -748,7 +778,8 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er } if (QSettings *s = core->settings()) { - d->m_projectExplorerSettings.buildBeforeRun = s->value("ProjectExplorer/Settings/BuildBeforeRun", true).toBool(); + d->m_projectExplorerSettings.buildBeforeDeploy = s->value("ProjectExplorer/Settings/BuildBeforeDeploy", true).toBool(); + d->m_projectExplorerSettings.deployBeforeRun = s->value("ProjectExplorer/Settings/DeployBeforeRun", true).toBool(); d->m_projectExplorerSettings.saveBeforeBuild = s->value("ProjectExplorer/Settings/SaveBeforeBuild", false).toBool(); d->m_projectExplorerSettings.showCompilerOutput = s->value("ProjectExplorer/Settings/ShowCompilerOutput", false).toBool(); d->m_projectExplorerSettings.cleanOldAppOutput = s->value("ProjectExplorer/Settings/CleanOldAppOutput", false).toBool(); @@ -771,6 +802,10 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er connect(d->m_rebuildAction, SIGNAL(triggered()), this, SLOT(rebuildProject())); connect(d->m_rebuildActionContextMenu, SIGNAL(triggered()), this, SLOT(rebuildProjectContextMenu())); connect(d->m_rebuildSessionAction, SIGNAL(triggered()), this, SLOT(rebuildSession())); + connect(d->m_deployProjectOnlyAction, SIGNAL(triggered()), this, SLOT(deployProjectOnly())); + connect(d->m_deployAction, SIGNAL(triggered()), this, SLOT(deployProject())); + connect(d->m_deployActionContextMenu, SIGNAL(triggered()), this, SLOT(deployProjectContextMenu())); + connect(d->m_deploySessionAction, SIGNAL(triggered()), this, SLOT(deploySession())); connect(d->m_cleanProjectOnlyAction, SIGNAL(triggered()), this, SLOT(cleanProjectOnly())); connect(d->m_cleanAction, SIGNAL(triggered()), this, SLOT(cleanProject())); connect(d->m_cleanActionContextMenu, SIGNAL(triggered()), this, SLOT(cleanProjectContextMenu())); @@ -909,12 +944,13 @@ void ProjectExplorerPlugin::extensionsInitialized() d->m_buildManager->extensionsInitialized(); } -void ProjectExplorerPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown() { d->m_proWindow->aboutToShutdown(); // disconnect from session d->m_session->clear(); d->m_projectsMode = 0; // d->m_proWindow->saveConfigChanges(); + return SynchronousShutdown; } void ProjectExplorerPlugin::newProject() @@ -998,7 +1034,8 @@ void ProjectExplorerPlugin::savePersistentSettings() s->setValue("ProjectExplorer/RecentProjects/FileNames", fileNames); s->setValue("ProjectExplorer/RecentProjects/DisplayNames", displayNames); - s->setValue("ProjectExplorer/Settings/BuildBeforeRun", d->m_projectExplorerSettings.buildBeforeRun); + s->setValue("ProjectExplorer/Settings/BuildBeforeDeploy", d->m_projectExplorerSettings.buildBeforeDeploy); + s->setValue("ProjectExplorer/Settings/DeployBeforeRun", d->m_projectExplorerSettings.deployBeforeRun); s->setValue("ProjectExplorer/Settings/SaveBeforeBuild", d->m_projectExplorerSettings.saveBeforeBuild); s->setValue("ProjectExplorer/Settings/ShowCompilerOutput", d->m_projectExplorerSettings.showCompilerOutput); s->setValue("ProjectExplorer/Settings/CleanOldAppOutput", d->m_projectExplorerSettings.cleanOldAppOutput); @@ -1261,7 +1298,6 @@ void ProjectExplorerPlugin::executeRunConfiguration(RunConfiguration *runConfigu RunControl *control = runControlFactory->create(runConfiguration, runMode); startRunControl(control, runMode); } - } void ProjectExplorerPlugin::startRunControl(RunControl *runControl, const QString &runMode) @@ -1362,19 +1398,18 @@ void ProjectExplorerPlugin::updateActions() if (debug) qDebug() << "ProjectExplorerPlugin::updateActions"; - Project *startupProject = session()->startupProject(); - bool enableBuildActions = startupProject - && ! (d->m_buildManager->isBuilding(startupProject)) - && hasBuildSettings(startupProject); + Project *project = startupProject(); + bool enableBuildActions = project + && ! (d->m_buildManager->isBuilding(project)) + && hasBuildSettings(project); bool enableBuildActionsContextMenu = d->m_currentProject && ! (d->m_buildManager->isBuilding(d->m_currentProject)) && hasBuildSettings(d->m_currentProject); - bool hasProjects = !d->m_session->projects().isEmpty(); bool building = d->m_buildManager->isBuilding(); - QString projectName = startupProject ? startupProject->displayName() : QString(); + QString projectName = project ? project->displayName() : QString(); QString projectNameContextMenu = d->m_currentProject ? d->m_currentProject->displayName() : QString(); if (debug) @@ -1411,7 +1446,7 @@ void ProjectExplorerPlugin::updateActions() d->m_projectSelectorAction->setEnabled(!session()->projects().isEmpty()); d->m_projectSelectorActionMenu->setEnabled(!session()->projects().isEmpty()); - updateRunActions(); + updateDeployActions(); } // NBS TODO check projectOrder() @@ -1575,6 +1610,54 @@ void ProjectExplorerPlugin::rebuildSession() } } +void ProjectExplorerPlugin::deployProjectOnly() +{ + if (!saveModifiedFiles()) + return; + d->m_buildManager->deployProject(session()->startupProject()->activeTarget()->activeBuildConfiguration()); +} + +void ProjectExplorerPlugin::deployProject() +{ + if (!saveModifiedFiles()) + return; + + const QList<Project *> &projects = d->m_session->projectOrder(session()->startupProject()); + QList<BuildConfiguration *> configurations; + foreach (Project *pro, projects) + if (pro->activeTarget()->activeBuildConfiguration()) + configurations << pro->activeTarget()->activeBuildConfiguration(); + + d->m_buildManager->deployProjects(configurations); +} + +void ProjectExplorerPlugin::deployProjectContextMenu() +{ + if (!saveModifiedFiles()) + return; + + QList<BuildConfiguration *> configurations; + foreach (Project *pro, d->m_session->projectOrder(d->m_currentProject)) + if (pro->activeTarget()->activeBuildConfiguration()) + configurations << pro->activeTarget()->activeBuildConfiguration(); + + d->m_buildManager->deployProjects(configurations); +} + +void ProjectExplorerPlugin::deploySession() +{ + if (!saveModifiedFiles()) + return; + + const QList<Project *> & projects = d->m_session->projectOrder(); + QList<BuildConfiguration *> configurations; + foreach (Project *pro, projects) + if (pro->activeTarget()->activeBuildConfiguration()) + configurations << pro->activeTarget()->activeBuildConfiguration(); + + d->m_buildManager->deployProjects(configurations); +} + void ProjectExplorerPlugin::cleanProjectOnly() { if (debug) @@ -1648,34 +1731,58 @@ bool ProjectExplorerPlugin::hasBuildSettings(Project *pro) return false; } +bool ProjectExplorerPlugin::hasDeploySettings(Project *pro) +{ + const QList<Project *> & projects = d->m_session->projectOrder(pro); + foreach(Project *project, projects) + if (project->activeTarget()->activeBuildConfiguration() && + !project->activeTarget()->activeBuildConfiguration()->steps(BuildStep::Deploy).isEmpty()) + return true; + return false; +} + void ProjectExplorerPlugin::runProjectImpl(Project *pro, QString mode) { if (!pro) return; - if (d->m_projectExplorerSettings.buildBeforeRun && hasBuildSettings(pro)) { - if (!pro->activeTarget()->activeRunConfiguration()->isEnabled()) { - if (!showBuildConfigDialog()) + if (!pro->activeTarget()->activeRunConfiguration()->isEnabled()) { + if (!showBuildConfigDialog()) + return; + } + + if (!saveModifiedFiles()) + return; + + bool delayRun = false; + // Deploy/build first? + if (d->m_projectExplorerSettings.deployBeforeRun) { + const QList<Project *> & projects = d->m_session->projectOrder(pro); + QList<BuildConfiguration *> configurations; + foreach(Project *project, projects) + if (project->activeTarget()->activeBuildConfiguration()) + configurations << project->activeTarget()->activeBuildConfiguration(); + + if (d->m_projectExplorerSettings.buildBeforeDeploy && hasBuildSettings(pro)) { + if (!d->m_buildManager->buildProjects(configurations)) return; + delayRun = true; } - if (saveModifiedFiles()) { - d->m_runMode = mode; - d->m_delayedRunConfiguration = pro->activeTarget()->activeRunConfiguration(); - - const QList<Project *> & projects = d->m_session->projectOrder(pro); - QList<BuildConfiguration *> configurations; - foreach(Project *project, projects) - if (project->activeTarget()->activeBuildConfiguration()) - configurations << project->activeTarget()->activeBuildConfiguration(); - d->m_buildManager->buildProjects(configurations); - - updateRunActions(); + if (hasDeploySettings(pro)) { + if (!d->m_buildManager->deployProjects(configurations)) + return; + delayRun = true; } + } + + // Actually run (delayed) + if (delayRun) { + d->m_runMode = mode; + d->m_delayedRunConfiguration = pro->activeTarget()->activeRunConfiguration(); } else { - // TODO this ignores RunConfiguration::isEnabled() - if (saveModifiedFiles()) - executeRunConfiguration(pro->activeTarget()->activeRunConfiguration(), mode); + executeRunConfiguration(pro->activeTarget()->activeRunConfiguration(), mode); } + updateRunActions(); } void ProjectExplorerPlugin::debugProject() @@ -1735,7 +1842,7 @@ void ProjectExplorerPlugin::startupProjectChanged() this, SLOT(updateRunActions())); disconnect(previousStartupProject, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)), - this, SLOT(updateRunActions())); + this, SLOT(updateDeployActions())); disconnect(previousStartupProject->activeTarget()->activeRunConfiguration(), SIGNAL(isEnabledChanged(bool)), this, SLOT(updateRunActions())); @@ -1748,7 +1855,7 @@ void ProjectExplorerPlugin::startupProjectChanged() if (project) { connect(project, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)), - this, SLOT(updateRunActions())); + this, SLOT(updateDeployActions())); connect(project->activeTarget(), SIGNAL(activeRunConfigurationChanged(ProjectExplorer::RunConfiguration*)), this, SLOT(updateRunActions())); @@ -1776,6 +1883,35 @@ IRunControlFactory *ProjectExplorerPlugin::findRunControlFactory(RunConfiguratio return 0; } +void ProjectExplorerPlugin::updateDeployActions() +{ + Project *project = startupProject(); + + bool enableDeployActions = project + && ! (d->m_buildManager->isBuilding(project)) + && hasDeploySettings(project); + bool enableDeployActionsContextMenu = d->m_currentProject + && ! (d->m_buildManager->isBuilding(d->m_currentProject)) + && hasDeploySettings(d->m_currentProject); + + const QString projectName = project ? project->displayName() : QString(); + const QString projectNameContextMenu = d->m_currentProject ? d->m_currentProject->displayName() : QString(); + bool hasProjects = !d->m_session->projects().isEmpty(); + bool building = d->m_buildManager->isBuilding(); + + d->m_deployAction->setParameter(projectName); + d->m_deployAction->setEnabled(enableDeployActions); + + d->m_deployActionContextMenu->setParameter(projectNameContextMenu); + d->m_deployActionContextMenu->setEnabled(enableDeployActionsContextMenu); + + d->m_deployProjectOnlyAction->setEnabled(enableDeployActions); + + d->m_deploySessionAction->setEnabled(hasProjects && !building); + + updateRunActions(); +} + void ProjectExplorerPlugin::updateRunActions() { const Project *project = startupProject(); @@ -1863,7 +1999,7 @@ void ProjectExplorerPlugin::updateRecentProjectMenu() const QPair<QString, QString> &s = *it; if (s.first.endsWith(QLatin1String(".qws"))) continue; - QAction *action = menu->addAction(s.first); + QAction *action = menu->addAction(Utils::withTildeHomePath(s.first)); action->setData(s.first); connect(action, SIGNAL(triggered()), this, SLOT(openRecentProject())); } diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index 13491c3364..973dc53535 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -100,7 +100,7 @@ public: //PluginInterface bool initialize(const QStringList &arguments, QString *error_message); void extensionsInitialized(); - void aboutToShutdown(); + ShutdownFlag aboutToShutdown(); void setProjectExplorerSettings(const Internal::ProjectExplorerSettings &pes); Internal::ProjectExplorerSettings projectExplorerSettings() const; @@ -140,6 +140,10 @@ private slots: void rebuildProject(); void rebuildProjectContextMenu(); void rebuildSession(); + void deployProjectOnly(); + void deployProject(); + void deployProjectContextMenu(); + void deploySession(); void cleanProjectOnly(); void cleanProject(); void cleanProjectContextMenu(); @@ -182,6 +186,7 @@ private slots: void runControlFinished(); void startupProjectChanged(); // Calls updateRunAction + void updateDeployActions(); void updateRunActions(); void loadProject(const QString &project) { openProject(project); } @@ -207,6 +212,8 @@ private: void runProjectImpl(Project *pro, QString mode); void executeRunConfiguration(RunConfiguration *, const QString &mode); bool hasBuildSettings(Project *pro); + bool hasDeploySettings(Project *pro); + bool showBuildConfigDialog(); void setCurrent(Project *project, QString filePath, Node *node); diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h index 5a09c20c61..e25693ca97 100644 --- a/src/plugins/projectexplorer/projectexplorerconstants.h +++ b/src/plugins/projectexplorer/projectexplorerconstants.h @@ -53,6 +53,10 @@ const char * const REBUILDPROJECTONLY = "ProjectExplorer.RebuildProjectOnly"; const char * const REBUILD = "ProjectExplorer.Rebuild"; const char * const REBUILDCM = "ProjectExplorer.RebuildCM"; const char * const REBUILDSESSION = "ProjectExplorer.RebuildSession"; +const char * const DEPLOYPROJECTONLY = "ProjectExplorer.DeployProjectOnly"; +const char * const DEPLOY = "ProjectExplorer.Deploy"; +const char * const DEPLOYCM = "ProjectExplorer.DeployCM"; +const char * const DEPLOYSESSION = "ProjectExplorer.DeploySession"; const char * const CLEANPROJECTONLY = "ProjectExplorer.CleanProjectOnly"; const char * const CLEAN = "ProjectExplorer.Clean"; const char * const CLEANCM = "ProjectExplorer.CleanCM"; diff --git a/src/plugins/projectexplorer/projectexplorersettings.h b/src/plugins/projectexplorer/projectexplorersettings.h index 74b6f1c363..256f4ef263 100644 --- a/src/plugins/projectexplorer/projectexplorersettings.h +++ b/src/plugins/projectexplorer/projectexplorersettings.h @@ -37,11 +37,11 @@ namespace Internal { struct ProjectExplorerSettings { - ProjectExplorerSettings() : buildBeforeRun(true), saveBeforeBuild(false), - showCompilerOutput(false), - cleanOldAppOutput(false), useJom(true) {} + ProjectExplorerSettings() : buildBeforeDeploy(true), deployBeforeRun(true), saveBeforeBuild(false), + showCompilerOutput(false), cleanOldAppOutput(false), useJom(true) {} - bool buildBeforeRun; + bool buildBeforeDeploy; + bool deployBeforeRun; bool saveBeforeBuild; bool showCompilerOutput; bool cleanOldAppOutput; @@ -54,7 +54,8 @@ struct ProjectExplorerSettings inline bool operator==(const ProjectExplorerSettings &p1, const ProjectExplorerSettings &p2) { - return p1.buildBeforeRun == p2.buildBeforeRun + return p1.buildBeforeDeploy == p2.buildBeforeDeploy + && p1.deployBeforeRun == p2.deployBeforeRun && p1.saveBeforeBuild == p2.saveBeforeBuild && p1.showCompilerOutput == p2.showCompilerOutput && p1.cleanOldAppOutput == p2.cleanOldAppOutput diff --git a/src/plugins/projectexplorer/projectexplorersettingspage.cpp b/src/plugins/projectexplorer/projectexplorersettingspage.cpp index 806b9b4570..71f6ed60d9 100644 --- a/src/plugins/projectexplorer/projectexplorersettingspage.cpp +++ b/src/plugins/projectexplorer/projectexplorersettingspage.cpp @@ -65,7 +65,8 @@ void ProjectExplorerSettingsWidget::setJomVisible(bool v) ProjectExplorerSettings ProjectExplorerSettingsWidget::settings() const { ProjectExplorerSettings pes; - pes.buildBeforeRun = m_ui.buildProjectBeforeRunCheckBox->isChecked(); + pes.buildBeforeDeploy = m_ui.buildProjectBeforeDeployCheckBox->isChecked(); + pes.deployBeforeRun = m_ui.deployProjectBeforeRunCheckBox->isChecked(); pes.saveBeforeBuild = m_ui.saveAllFilesCheckBox->isChecked(); pes.showCompilerOutput = m_ui.showCompileOutputCheckBox->isChecked(); pes.cleanOldAppOutput = m_ui.cleanOldAppOutputCheckBox->isChecked(); @@ -75,7 +76,8 @@ ProjectExplorerSettings ProjectExplorerSettingsWidget::settings() const void ProjectExplorerSettingsWidget::setSettings(const ProjectExplorerSettings &pes) const { - m_ui.buildProjectBeforeRunCheckBox->setChecked(pes.buildBeforeRun); + m_ui.buildProjectBeforeDeployCheckBox->setChecked(pes.buildBeforeDeploy); + m_ui.deployProjectBeforeRunCheckBox->setChecked(pes.deployBeforeRun); m_ui.saveAllFilesCheckBox->setChecked(pes.saveBeforeBuild); m_ui.showCompileOutputCheckBox->setChecked(pes.showCompilerOutput); m_ui.cleanOldAppOutputCheckBox->setChecked(pes.cleanOldAppOutput); diff --git a/src/plugins/projectexplorer/projectexplorersettingspage.ui b/src/plugins/projectexplorer/projectexplorersettingspage.ui index a08005b5f3..dc0c30526d 100644 --- a/src/plugins/projectexplorer/projectexplorersettingspage.ui +++ b/src/plugins/projectexplorer/projectexplorersettingspage.ui @@ -63,9 +63,16 @@ </widget> </item> <item> - <widget class="QCheckBox" name="buildProjectBeforeRunCheckBox"> + <widget class="QCheckBox" name="buildProjectBeforeDeployCheckBox"> <property name="text"> - <string>Always build project before running</string> + <string>Always build project before deploying it</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="deployProjectBeforeRunCheckBox"> + <property name="text"> + <string>Always deploy project before running it</string> </property> </widget> </item> diff --git a/src/plugins/projectexplorer/projectnodes.cpp b/src/plugins/projectexplorer/projectnodes.cpp index 04ad845036..f34e85ab25 100644 --- a/src/plugins/projectexplorer/projectnodes.cpp +++ b/src/plugins/projectexplorer/projectnodes.cpp @@ -154,8 +154,7 @@ bool FileNode::isGenerated() const */ FolderNode::FolderNode(const QString &folderPath) : Node(FolderNodeType, folderPath), - m_displayName(QDir::toNativeSeparators(folderPath)), - m_icon(Core::FileIconProvider::instance()->icon(QFileIconProvider::Folder)) + m_displayName(QDir::toNativeSeparators(folderPath)) { } @@ -182,6 +181,9 @@ QString FolderNode::displayName() const */ QIcon FolderNode::icon() const { + // Instantiating the Icon provider is expensive. + if (m_icon.isNull()) + m_icon = Core::FileIconProvider::instance()->icon(QFileIconProvider::Folder); return m_icon; } diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index 3a4511e8b5..6679b6184a 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -147,7 +147,7 @@ private: // managed by ProjectNode friend class ProjectNode; QString m_displayName; - QIcon m_icon; + mutable QIcon m_icon; }; class PROJECTEXPLORER_EXPORT ProjectNode : public FolderNode diff --git a/src/plugins/projectexplorer/projectwelcomepagewidget.cpp b/src/plugins/projectexplorer/projectwelcomepagewidget.cpp index e75df35e9b..9c30289eb4 100644 --- a/src/plugins/projectexplorer/projectwelcomepagewidget.cpp +++ b/src/plugins/projectexplorer/projectwelcomepagewidget.cpp @@ -39,6 +39,8 @@ #include <coreplugin/mainwindow.h> #include <coreplugin/filemanager.h> +#include <utils/stringutils.h> + #include <QtCore/QFileInfo> #include <QtCore/QDir> #include <QtCore/QPair> @@ -146,8 +148,9 @@ void ProjectWelcomePageWidget::updateWelcomePage(const WelcomePageData &welcomeP break; const QFileInfo fi(it.first); QString label = "<b>" + it.second + - "</b><br><font color=gray>" + - fm.elidedText(it.first, Qt::ElideMiddle, 250); + "</b><br><font color=gray>" + + fm.elidedText(QDir::toNativeSeparators(Utils::withTildeHomePath(it.first)), + Qt::ElideMiddle, 250); ui->projTreeWidget->addItem(label, it.first, QDir::toNativeSeparators(fi.absolutePath())); } diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp index d6b4e505cb..cd1a210ca7 100644 --- a/src/plugins/projectexplorer/projectwindow.cpp +++ b/src/plugins/projectexplorer/projectwindow.cpp @@ -59,6 +59,7 @@ #include <QtGui/QScrollArea> #include <QtGui/QLabel> #include <QtGui/QPainter> +#include <QtGui/QStackedWidget> #include <QtGui/QPaintEvent> #include <QtGui/QMenu> diff --git a/src/plugins/projectexplorer/projectwindow.h b/src/plugins/projectexplorer/projectwindow.h index b8f9e0e01a..b4bf4fc948 100644 --- a/src/plugins/projectexplorer/projectwindow.h +++ b/src/plugins/projectexplorer/projectwindow.h @@ -30,22 +30,13 @@ #ifndef PROJECTWINDOW_H #define PROJECTWINDOW_H -#include "iprojectproperties.h" - -#include <QtCore/QPair> -#include <QtCore/QMap> -#include <QtGui/QApplication> -#include <QtGui/QComboBox> -#include <QtGui/QLabel> -#include <QtGui/QPushButton> #include <QtGui/QScrollArea> -#include <QtGui/QStackedWidget> -#include <QtGui/QWidget> QT_BEGIN_NAMESPACE class QLabel; class QGridLayout; class QMenu; +class QStackedWidget; QT_END_NAMESPACE namespace ProjectExplorer { @@ -105,7 +96,6 @@ private: QList<ProjectExplorer::Project *> m_tabIndexToProject; }; - } // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index 33b59d6984..d089ef8ecf 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -179,6 +179,11 @@ Target *RunConfiguration::target() const return m_target; } +ProjectExplorer::OutputFormatter *RunConfiguration::createOutputFormatter() const +{ + return new OutputFormatter(); +} + IRunConfigurationFactory::IRunConfigurationFactory(QObject *parent) : QObject(parent) { @@ -218,13 +223,23 @@ IRunControlFactory::~IRunControlFactory() RunControl::RunControl(RunConfiguration *runConfiguration, QString mode) : m_runMode(mode), m_runConfiguration(runConfiguration) { - if (runConfiguration) + if (runConfiguration) { m_displayName = runConfiguration->displayName(); + m_outputFormatter = runConfiguration->createOutputFormatter(); + } + // We need to ensure that there's always a OutputFormatter + if (!m_outputFormatter) + m_outputFormatter = new OutputFormatter(); } RunControl::~RunControl() { + delete m_outputFormatter; +} +OutputFormatter *RunControl::outputFormatter() +{ + return m_outputFormatter; } QString RunControl::runMode() const @@ -242,11 +257,6 @@ bool RunControl::sameRunConfiguration(RunControl *other) return other->m_runConfiguration.data() == m_runConfiguration.data(); } -OutputFormatter *RunControl::createOutputFormatter(QObject *parent) -{ - return new OutputFormatter(parent); -} - void RunControl::bringApplicationToForeground(qint64 pid) { #ifdef Q_OS_MAC diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index f9e897e509..c1532590f6 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -85,6 +85,8 @@ public: Target *target() const; + virtual ProjectExplorer::OutputFormatter *createOutputFormatter() const; + signals: void isEnabledChanged(bool value); @@ -169,7 +171,7 @@ public: bool sameRunConfiguration(RunControl *other); - virtual OutputFormatter *createOutputFormatter(QObject *parent = 0); + OutputFormatter *outputFormatter(); QString runMode() const; signals: @@ -189,6 +191,7 @@ private: QString m_displayName; QString m_runMode; const QWeakPointer<RunConfiguration> m_runConfiguration; + OutputFormatter *m_outputFormatter; #ifdef Q_OS_MAC //these two are used to bring apps in the foreground on Mac diff --git a/src/plugins/projectexplorer/runsettingspropertiespage.h b/src/plugins/projectexplorer/runsettingspropertiespage.h index 29eeb04b2e..5491a2afbd 100644 --- a/src/plugins/projectexplorer/runsettingspropertiespage.h +++ b/src/plugins/projectexplorer/runsettingspropertiespage.h @@ -33,7 +33,6 @@ #include "iprojectproperties.h" #include <QtGui/QWidget> -#include <QtCore/QAbstractListModel> QT_BEGIN_NAMESPACE class QMenu; diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h index d92128a824..f68e311766 100644 --- a/src/plugins/projectexplorer/target.h +++ b/src/plugins/projectexplorer/target.h @@ -33,8 +33,6 @@ #include "projectconfiguration.h" #include "projectexplorer_export.h" -#include <QtCore/QObject> -#include <QtGui/QFileSystemModel> #include <QtGui/QIcon> namespace ProjectExplorer { diff --git a/src/plugins/projectexplorer/targetsettingspanel.cpp b/src/plugins/projectexplorer/targetsettingspanel.cpp index 69a77ae983..8ef5beb3d9 100644 --- a/src/plugins/projectexplorer/targetsettingspanel.cpp +++ b/src/plugins/projectexplorer/targetsettingspanel.cpp @@ -43,6 +43,7 @@ #include <QtGui/QMenu> #include <QtGui/QMessageBox> #include <QtGui/QVBoxLayout> +#include <QtGui/QStackedWidget> using namespace ProjectExplorer; using namespace ProjectExplorer::Internal; diff --git a/src/plugins/projectexplorer/targetsettingspanel.h b/src/plugins/projectexplorer/targetsettingspanel.h index 6d449ec1c7..874263b936 100644 --- a/src/plugins/projectexplorer/targetsettingspanel.h +++ b/src/plugins/projectexplorer/targetsettingspanel.h @@ -30,19 +30,18 @@ #ifndef TARGETSETTINGSPANEL_H #define TARGETSETTINGSPANEL_H -#include "iprojectproperties.h" - -#include <QtGui/QStackedWidget> #include <QtGui/QWidget> QT_BEGIN_NAMESPACE class QAction; class QMenu; +class QStackedWidget; QT_END_NAMESPACE namespace ProjectExplorer { class Target; +class Project; namespace Internal { diff --git a/src/plugins/projectexplorer/taskhub.h b/src/plugins/projectexplorer/taskhub.h index ec27b0a3c9..1fd5a10f92 100644 --- a/src/plugins/projectexplorer/taskhub.h +++ b/src/plugins/projectexplorer/taskhub.h @@ -31,7 +31,7 @@ #define TASKHUB_H #include "task.h" -#include "projectexplorer_export.h" + #include <QtCore/QObject> #include <QtGui/QIcon> diff --git a/src/plugins/projectexplorer/taskwindow.h b/src/plugins/projectexplorer/taskwindow.h index b6f1f4c8fb..d5a0f410c0 100644 --- a/src/plugins/projectexplorer/taskwindow.h +++ b/src/plugins/projectexplorer/taskwindow.h @@ -30,17 +30,17 @@ #ifndef TASKWINDOW_H #define TASKWINDOW_H -#include "task.h" #include <coreplugin/ioutputpane.h> -#include <QtGui/QIcon> QT_BEGIN_NAMESPACE class QAction; class QModelIndex; +class QPoint; QT_END_NAMESPACE namespace ProjectExplorer { class TaskHub; +class Task; namespace Internal { class TaskWindowPrivate; diff --git a/src/plugins/projectexplorer/vcsannotatetaskhandler.h b/src/plugins/projectexplorer/vcsannotatetaskhandler.h index 27fa40a804..18bfcaf6b8 100644 --- a/src/plugins/projectexplorer/vcsannotatetaskhandler.h +++ b/src/plugins/projectexplorer/vcsannotatetaskhandler.h @@ -34,9 +34,6 @@ #include "itaskhandler.h" -#include <QtCore/QHash> -#include <QtCore/QString> - namespace Core { class IVersionControl; } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.cpp index f49c6077e9..eb8b87fde5 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.cpp @@ -32,6 +32,7 @@ #include <utils/filterlineedit.h> #include "itemlibrarywidgets.h" #include "itemlibrarymodel.h" +#include "itemlibraryimageprovider.h" #include "customdraganddrop.h" #include <QFileInfo> @@ -164,6 +165,9 @@ ItemLibrary::ItemLibrary(QWidget *parent) : m_d->m_resourcesView->setModel(m_d->m_resourcesDirModel); m_d->m_resourcesView->setIconSize(m_d->m_resIconSize); + /* create image provider for loading item icons */ + m_d->m_itemsView->engine()->addImageProvider(QLatin1String("qmldesigner_itemlibrary"), new Internal::ItemLibraryImageProvider); + /* other widgets */ QTabBar *tabBar = new QTabBar(this); tabBar->addTab(tr("Items", "Title of library items view")); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri index d4e3b0f71b..4cd2f619cd 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri @@ -6,8 +6,8 @@ VPATH += $$PWD INCLUDEPATH += $$PWD # Input -HEADERS += itemlibrary.h customdraganddrop.h itemlibrarymodel.h itemlibrarywidgets.h -SOURCES += itemlibrary.cpp customdraganddrop.cpp itemlibrarymodel.cpp itemlibrarywidgets.cpp +HEADERS += itemlibrary.h customdraganddrop.h itemlibrarymodel.h itemlibrarywidgets.h itemlibraryimageprovider.h +SOURCES += itemlibrary.cpp customdraganddrop.cpp itemlibrarymodel.cpp itemlibrarywidgets.cpp itemlibraryimageprovider.cpp RESOURCES += itemlibrary.qrc OTHER_FILES += \ diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.cpp new file mode 100644 index 0000000000..b3bb37b146 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.cpp @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ +#include "itemlibraryimageprovider.h" + +namespace QmlDesigner { + +namespace Internal { + +ItemLibraryImageProvider::ItemLibraryImageProvider() : + QDeclarativeImageProvider(QDeclarativeImageProvider::Pixmap) +{ +} + +QPixmap ItemLibraryImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + QPixmap pixmap(id); + if (size) { + size->setWidth(pixmap.width()); + size->setHeight(pixmap.height()); + } + if (requestedSize.isValid()) + return pixmap.scaled(requestedSize); + return pixmap; +} + +} + +} + diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.h new file mode 100644 index 0000000000..861e4cfd47 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryimageprovider.h @@ -0,0 +1,51 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef ITEMLIBRARYIMAGEPROVIDER_H +#define ITEMLIBRARYIMAGEPROVIDER_H + +#include <QDeclarativeImageProvider> + +namespace QmlDesigner { + +namespace Internal { + +class ItemLibraryImageProvider : public QDeclarativeImageProvider +{ +public: + ItemLibraryImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); +}; + +} + +} + +#endif // ITEMLIBRARYIMAGEPROVIDER_H diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index 28764e0ad9..42d8cc6889 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -176,7 +176,6 @@ ItemLibraryItemModel::ItemLibraryItemModel(QScriptEngine *scriptEngine, int item m_scriptEngine(scriptEngine), m_libId(itemLibId), m_name(itemName), - m_icon(), m_iconSize(64, 64) { QScriptValue pixmapScriptValue(m_scriptEngine->newVariant(QPixmap())); @@ -204,21 +203,19 @@ QString ItemLibraryItemModel::itemName() const return m_name; } - -void ItemLibraryItemModel::setItemIcon(const QIcon &itemIcon) +void ItemLibraryItemModel::setItemIconPath(const QString &iconPath) { - m_icon = itemIcon; + m_iconPath = iconPath; - QScriptValue pixmapScriptValue(m_scriptEngine->newVariant(m_icon.pixmap(m_iconSize))); - setProperty(QLatin1String("itemPixmap"), pixmapScriptValue); + setProperty(QLatin1String("itemLibraryIconPath"), + QString(QLatin1String("image://qmldesigner_itemlibrary/") + iconPath)); } - void ItemLibraryItemModel::setItemIconSize(const QSize &itemIconSize) { m_iconSize = itemIconSize; // qDebug() << "set icon size" << itemIconSize; - setItemIcon(m_icon); + setItemIconPath(m_iconPath); } @@ -414,12 +411,12 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo) itemModel = new ItemLibraryItemModel(m_scriptEngine.data(), itemId, entry.name()); // delayed creation of (default) icons - if (entry.icon().isNull()) - entry.setIcon(QIcon(QLatin1String(":/ItemLibrary/images/item-default-icon.png"))); + if (entry.iconPath().isEmpty()) + entry.setIconPath(QLatin1String(":/ItemLibrary/images/item-default-icon.png")); if (entry.dragIcon().isNull()) entry.setDragIcon(createDragPixmap(getWidth(entry), getHeight(entry))); - itemModel->setItemIcon(entry.icon()); + itemModel->setItemIconPath(entry.iconPath()); itemModel->setItemIconSize(m_itemIconSize); sectionModel->addSectionEntry(itemModel); m_sections.insert(itemId, sectionId); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h index 896710f411..5859ae61ba 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h @@ -84,7 +84,7 @@ public: int itemLibId() const; QString itemName() const; - void setItemIcon(const QIcon &itemIcon); + void setItemIconPath(const QString &iconPath); void setItemIconSize(const QSize &itemIconSize); bool operator<(const ItemLibraryItemModel &other) const; @@ -93,7 +93,7 @@ private: QWeakPointer<QScriptEngine> m_scriptEngine; int m_libId; QString m_name; - QIcon m_icon; + QString m_iconPath; QSize m_iconSize; }; diff --git a/src/plugins/qmldesigner/components/itemlibrary/qml/ItemView.qml b/src/plugins/qmldesigner/components/itemlibrary/qml/ItemView.qml index ca3babe624..9380c90ad9 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/qml/ItemView.qml +++ b/src/plugins/qmldesigner/components/itemlibrary/qml/ItemView.qml @@ -96,7 +96,7 @@ Item { width: itemLibraryIconWidth // to be set in Qml context height: itemLibraryIconHeight // to be set in Qml context - pixmap: itemPixmap // to be set by model + source: itemLibraryIconPath // to be set by model } Text { diff --git a/src/plugins/qmldesigner/components/propertyeditor/contextpanetextwidget.cpp b/src/plugins/qmldesigner/components/propertyeditor/contextpanetextwidget.cpp index 7d1bf03308..09a893062e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/contextpanetextwidget.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/contextpanetextwidget.cpp @@ -272,14 +272,14 @@ void ContextPaneTextWidget::onHorizontalAlignmentChanged() { QString alignment; if (ui->centerHAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignHCenter"); + alignment = QLatin1String("AlignHCenter"); else if (ui->leftAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignLeft"); + alignment = QLatin1String("AlignLeft"); else if (ui->rightAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignRight"); + alignment = QLatin1String("AlignRight"); if (m_horizontalAlignment != alignment) { m_horizontalAlignment = alignment; - if (alignment == QLatin1String("Text.AlignLeft")) + if (alignment == QLatin1String("AlignLeft")) emit removeProperty(QLatin1String("horizontalAlignment")); else emit propertyChanged(QLatin1String("horizontalAlignment"), alignment); @@ -298,14 +298,14 @@ void ContextPaneTextWidget::onVerticalAlignmentChanged() { QString alignment; if (ui->centerVAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignVCenter"); + alignment = QLatin1String("AlignVCenter"); else if (ui->topAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignTop"); + alignment = QLatin1String("AlignTop"); else if (ui->bottomAlignmentButton->isChecked()) - alignment = QLatin1String("Text.AlignBottom"); + alignment = QLatin1String("AlignBottom"); if (m_verticalAlignment != alignment) { m_verticalAlignment = alignment; - if (alignment == QLatin1String("Text.AlignTop")) + if (alignment == QLatin1String("AlignTop")) emit removeProperty(QLatin1String("verticalAlignment")); else emit propertyChanged(QLatin1String("verticalAlignment"), alignment); diff --git a/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.cpp b/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.cpp index addd0dcb74..1b198005ae 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.cpp @@ -65,7 +65,7 @@ ContextPaneWidget::~ContextPaneWidget() m_bauhausColorDialog.clear(); } -void ContextPaneWidget::activate(const QPoint &pos, const QPoint &alternative) +void ContextPaneWidget::activate(const QPoint &pos, const QPoint &alternative, const QPoint &alternative2) { //uncheck all color buttons foreach (ColorButton *colorButton, findChildren<ColorButton*>()) { @@ -73,20 +73,19 @@ void ContextPaneWidget::activate(const QPoint &pos, const QPoint &alternative) } resize(sizeHint()); show(); - rePosition(pos, alternative); + rePosition(pos, alternative, alternative2); raise(); } -void ContextPaneWidget::rePosition(const QPoint &position, const QPoint &alternative) +void ContextPaneWidget::rePosition(const QPoint &position, const QPoint &alternative, const QPoint &alternative2) { - if (position.y() > 0) + if ((position.x() + width()) < parentWidget()->width()) move(position); else move(alternative); - m_originalPos = pos(); - if (m_xPos > 0) - move(m_xPos, pos().y()); + if (pos().y() < 0) + move(alternative2); } void ContextPaneWidget::deactivate() @@ -178,13 +177,17 @@ void ContextPaneWidget::mouseReleaseEvent(QMouseEvent *event) void ContextPaneWidget::mouseMoveEvent(QMouseEvent * event) { if (event->buttons() && Qt::LeftButton) { - + if (pos().x() < 10 && event->pos().x() < -20) + return; if (m_oldPos != QPoint(-1, -1)) { QPoint diff = event->globalPos() - m_oldPos; - move(pos() + diff); - if (m_bauhausColorDialog) - m_bauhausColorDialog->move(m_bauhausColorDialog->pos() + diff); - m_xPos = pos().x(); + if (m_bauhausColorDialog) { + QPoint newPos = pos() + diff; + if (newPos.x() > 0 && newPos.y() > 0 && (newPos.x() + width()) < parentWidget()->width() && (newPos.y() + height()) < parentWidget()->height()) { + m_bauhausColorDialog->move(m_bauhausColorDialog->pos() + diff); + move(newPos); + } + } } else { m_opacityEffect = new QGraphicsOpacityEffect; setGraphicsEffect(m_opacityEffect); diff --git a/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.h b/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.h index ce600a5399..7097d6a5e1 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.h +++ b/src/plugins/qmldesigner/components/propertyeditor/contextpanewidget.h @@ -22,8 +22,8 @@ class ContextPaneWidget : public QFrame public: explicit ContextPaneWidget(QWidget *parent = 0); ~ContextPaneWidget(); - void activate(const QPoint &pos, const QPoint &alternative); - void rePosition(const QPoint &pos, const QPoint &alternative); + void activate(const QPoint &pos, const QPoint &alternative, const QPoint &alternative2); + void rePosition(const QPoint &pos, const QPoint &alternative , const QPoint &alternative3); void deactivate(); BauhausColorDialog *colorDialog(); void setProperties(QmlJS::PropertyReader *propertyReader); diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditor.pri b/src/plugins/qmldesigner/components/stateseditor/stateseditor.pri index bb98fc009e..b2e922c513 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditor.pri +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditor.pri @@ -2,10 +2,12 @@ VPATH += $$PWD INCLUDEPATH += $$PWD SOURCES += stateseditorwidget.cpp \ stateseditormodel.cpp \ - stateseditorview.cpp + stateseditorview.cpp \ + stateseditorimageprovider.cpp HEADERS += stateseditorwidget.h \ stateseditormodel.h \ - stateseditorview.h + stateseditorview.h \ + stateseditorimageprovider.cpp RESOURCES += $$PWD/stateseditor.qrc OTHER_FILES +=$$PWD/stateslist.qml \ $$PWD/HorizontalScrollBar.qml diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.cpp new file mode 100644 index 0000000000..2d08b622b7 --- /dev/null +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.cpp @@ -0,0 +1,65 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ +#include "stateseditorimageprovider.h" +#include "stateseditorview.h" + +namespace QmlDesigner { + +namespace Internal { + +StatesEditorImageProvider::StatesEditorImageProvider(StatesEditorView *view) : + QDeclarativeImageProvider(QDeclarativeImageProvider::Pixmap), + m_view(view) +{ +} + +QPixmap StatesEditorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + if (!m_view.isNull()) { + // discard the count number (see StatesEditorModel m_updateCounter) + QString s = id.mid(0, id.lastIndexOf(QLatin1Char('-'))); + + bool ok = false; + int state = s.toInt(&ok); + if (ok) { + QPixmap pm = m_view->renderState(state); + if (size) + *size = pm.size(); + if (requestedSize.isValid()) + return pm.scaled(requestedSize); + return pm; + } + } + return QPixmap(); +} + +} + +} + diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.h b/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.h new file mode 100644 index 0000000000..ae61446236 --- /dev/null +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorimageprovider.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef STATESEDITORIMAGEPROVIDER_H +#define STATESEDITORIMAGEPROVIDER_H + +#include <QDeclarativeImageProvider> + +#include <QWeakPointer> + +namespace QmlDesigner { + +namespace Internal { + +class StatesEditorView; + +class StatesEditorImageProvider : public QDeclarativeImageProvider +{ +public: + StatesEditorImageProvider(StatesEditorView *view); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize); + +private: + QWeakPointer<StatesEditorView> m_view; +}; + +} + +} + +#endif // STATESEDITORIMAGEPROVIDER_H + diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp index 8c5b5ac476..71e51b6d8a 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.cpp @@ -42,12 +42,12 @@ namespace QmlDesigner { namespace Internal { StatesEditorModel::StatesEditorModel(QObject *parent) : - QAbstractListModel(parent) + QAbstractListModel(parent), + m_updateCounter(0) { - QHash<int, QByteArray> roleNames; roleNames.insert(StateNameRole, "stateName"); - roleNames.insert(StatesPixmapRole, "statePixmap"); + roleNames.insert(StateImageSourceRole, "stateImageSource"); setRoleNames(roleNames); } @@ -78,11 +78,9 @@ QVariant StatesEditorModel::data(const QModelIndex &index, int role) const result = m_stateNames.at(index.row()); break; } - case StatesPixmapRole: { - // TODO: Maybe cache this? - if (!m_statesView.isNull()) { - result = m_statesView->renderState(index.row()); - } + case StateImageSourceRole: { + if (!m_statesView.isNull()) + return QString("image://qmldesigner_stateseditor/%1-%2").arg(index.row()).arg(m_updateCounter); break; } } @@ -120,7 +118,10 @@ void StatesEditorModel::renameState(int i, const QString &newName) if (m_stateNames[i] != newName) { if (m_stateNames.contains(newName) || newName.isEmpty()) { - QMessageBox::warning(0, tr("Invalid state name"), newName.isEmpty()?tr("The empty string as a name is reserved for the base state."):tr("Name already used in another state")); + QMessageBox::warning(0, tr("Invalid state name"), + newName.isEmpty() ? + tr("The empty string as a name is reserved for the base state.") : + tr("Name already used in another state")); } else { m_stateNames.replace(i, newName); m_statesView->renameState(i,newName); @@ -134,6 +135,14 @@ void StatesEditorModel::updateState(int i) { Q_ASSERT(i >= 0 && i < m_stateNames.count()); + // QML images with the same URL are always cached, so this changes the URL each + // time to ensure the image is loaded from the StatesImageProvider and not the + // cache. + // TODO: only increase imageId when the scene has changed so that we can load + // from the cache instead where possible. + if (++m_updateCounter == INT_MAX) + m_updateCounter = 0; + emit dataChanged(createIndex(i, 0), createIndex(i, 0)); } diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h index 89b748ed92..36f92105e8 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditormodel.h @@ -47,7 +47,7 @@ class StatesEditorModel : public QAbstractListModel enum { StateNameRole = Qt::DisplayRole, - StatesPixmapRole = Qt::UserRole + StateImageSourceRole = Qt::UserRole, }; public: @@ -71,6 +71,7 @@ signals: private: QList<QString> m_stateNames; QWeakPointer<StatesEditorView> m_statesView; + int m_updateCounter; }; } // namespace Itnernal diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp index 937af6e900..689f5e7a17 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorwidget.cpp @@ -30,6 +30,7 @@ #include "stateseditorwidget.h" #include "stateseditormodel.h" #include "stateseditorview.h" +#include "stateseditorimageprovider.h" #include <qmlitemnode.h> #include <invalidargumentexception.h> @@ -203,6 +204,10 @@ void StatesEditorWidget::setup(Model *model) m_d->model = model; if (m_d->statesEditorView.isNull()) m_d->statesEditorView = new Internal::StatesEditorView(m_d->statesEditorModel.data(), this); + + m_d->listView->engine()->addImageProvider( + QLatin1String("qmldesigner_stateseditor"), new Internal::StatesEditorImageProvider(m_d->statesEditorView.data())); + m_d->statesEditorModel->setStatesEditorView(m_d->statesEditorView.data()); m_d->model->attachView(m_d->statesEditorView.data()); diff --git a/src/plugins/qmldesigner/components/stateseditor/stateslist.qml b/src/plugins/qmldesigner/components/stateseditor/stateslist.qml index dc2b649e35..2a552c32bb 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateslist.qml +++ b/src/plugins/qmldesigner/components/stateseditor/stateslist.qml @@ -145,7 +145,7 @@ Rectangle { anchors.bottomMargin: 9 Image { anchors.centerIn:parent - pixmap: statePixmap + source: stateImageSource Rectangle { anchors.fill:parent color:"transparent" diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index 35286f2f0b..5a0baeba59 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -62,6 +62,7 @@ public: QString name() const; QString typeName() const; QIcon icon() const; + QString iconPath() const; int majorVersion() const; int minorVersion() const; QString category() const; @@ -77,7 +78,7 @@ public: void setType(const QString &typeName, int majorVersion, int minorVersion); void setName(const QString &name); - void setIcon(const QIcon &icon); + void setIconPath(const QString &iconPath); void addProperty(const Property &p); void addProperty(QString &name, QString &type, QString &value); void setDragIcon(const QIcon &icon); diff --git a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp index c640c77b76..18757e8b9c 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/itemlibraryinfo.cpp @@ -46,6 +46,7 @@ public: QString category; int majorVersion; int minorVersion; + QString iconPath; QIcon icon; QIcon dragIcon; QList<PropertyContainer> properties; @@ -148,6 +149,11 @@ QIcon ItemLibraryEntry::icon() const return m_data->icon; } +QString ItemLibraryEntry::iconPath() const +{ + return m_data->iconPath; +} + void ItemLibraryEntry::setName(const QString &name) { m_data->name = name; @@ -160,9 +166,9 @@ void ItemLibraryEntry::setType(const QString &typeName, int majorVersion, int mi m_data->minorVersion = minorVersion; } -void ItemLibraryEntry::setIcon(const QIcon &icon) +void ItemLibraryEntry::setIconPath(const QString &iconPath) { - m_data->icon = icon; + m_data->iconPath = iconPath; } void ItemLibraryEntry::setQml(const QString &qml) @@ -184,6 +190,7 @@ QDataStream& operator<<(QDataStream& stream, const ItemLibraryEntry &itemLibrary stream << itemLibraryEntry.majorVersion(); stream << itemLibraryEntry.minorVersion(); stream << itemLibraryEntry.icon(); + stream << itemLibraryEntry.iconPath(); stream << itemLibraryEntry.category(); stream << itemLibraryEntry.dragIcon(); stream << itemLibraryEntry.m_data->properties; @@ -198,6 +205,7 @@ QDataStream& operator>>(QDataStream& stream, ItemLibraryEntry &itemLibraryEntry) stream >> itemLibraryEntry.m_data->majorVersion; stream >> itemLibraryEntry.m_data->minorVersion; stream >> itemLibraryEntry.m_data->icon; + stream >> itemLibraryEntry.m_data->iconPath; stream >> itemLibraryEntry.m_data->category; stream >> itemLibraryEntry.m_data->dragIcon; stream >> itemLibraryEntry.m_data->properties; diff --git a/src/plugins/qmldesigner/designercore/metainfo/metainfoparser.cpp b/src/plugins/qmldesigner/designercore/metainfo/metainfoparser.cpp index 216ba28dd3..9edf26e538 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/metainfoparser.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/metainfoparser.cpp @@ -205,8 +205,8 @@ void MetaInfoParser::handleNodeItemLibraryEntryElement(QXmlStreamReader &reader, entry.setName(name); QString iconPath = reader.attributes().value("icon").toString(); - if (!iconPath.isEmpty()) - entry.setIcon(QIcon(iconPath)); + if (!iconPath.isEmpty()) + entry.setIconPath(iconPath); QString category = reader.attributes().value("category").toString(); if (!category.isEmpty()) diff --git a/src/plugins/qmldesigner/designersettings.cpp b/src/plugins/qmldesigner/designersettings.cpp index fece615974..c1f7de9342 100644 --- a/src/plugins/qmldesigner/designersettings.cpp +++ b/src/plugins/qmldesigner/designersettings.cpp @@ -52,7 +52,7 @@ void DesignerSettings::fromSettings(QSettings *settings) snapMargin = settings->value( QLatin1String(QmlDesigner::Constants::QML_SNAPMARGIN_KEY), QVariant(0)).toInt(); enableContextPane = settings->value( - QLatin1String(QmlDesigner::Constants::QML_CONTEXTPANE_KEY), QVariant(0)).toBool(); + QLatin1String(QmlDesigner::Constants::QML_CONTEXTPANE_KEY), QVariant(1)).toBool(); settings->endGroup(); settings->endGroup(); diff --git a/src/plugins/qmldesigner/qmlcontextpane.cpp b/src/plugins/qmldesigner/qmlcontextpane.cpp index 88c65ab9a1..ca07a3adf9 100644 --- a/src/plugins/qmldesigner/qmlcontextpane.cpp +++ b/src/plugins/qmldesigner/qmlcontextpane.cpp @@ -8,6 +8,9 @@ #include <qmljs/qmljspropertyreader.h> #include <qmljs/qmljsrewriter.h> #include <qmljs/qmljsindenter.h> +#include <qmljs/qmljslookupcontext.h> +#include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljsbind.h> #include <texteditor/basetexteditor.h> #include <texteditor/tabsettings.h> #include <colorwidget.h> @@ -62,7 +65,7 @@ QmlContextPane::~QmlContextPane() m_widget.clear(); } -void QmlContextPane::apply(TextEditor::BaseTextEditorEditable *editor, Document::Ptr doc, Node *node, bool update) +void QmlContextPane::apply(TextEditor::BaseTextEditorEditable *editor, Document::Ptr doc, const QmlJS::Snapshot &snapshot, AST::Node *node, bool update) { if (!Internal::BauhausPlugin::pluginInstance()->settings().enableContextPane) return; @@ -73,6 +76,15 @@ void QmlContextPane::apply(TextEditor::BaseTextEditorEditable *editor, Document: if (update && editor != m_editor) return; //do not update for different editor + LookupContext::Ptr lookupContext = LookupContext::create(doc, snapshot, QList<Node*>()); + const Interpreter::ObjectValue *scopeObject = doc->bind()->findQmlObject(node); + + QStringList prototypes; + while (scopeObject) { + prototypes.append(scopeObject->className()); + scopeObject = scopeObject->prototype(lookupContext->context()); + } + setEnabled(doc->isParsedCorrectly()); m_editor = editor; contextWidget()->setParent(editor->widget()->parentWidget()); @@ -97,6 +109,24 @@ void QmlContextPane::apply(TextEditor::BaseTextEditorEditable *editor, Document: offset = objectBinding->firstSourceLocation().offset; end = objectBinding->lastSourceLocation().end(); } + + int line1; + int column1; + int line2; + int column2; + m_editor->convertPosition(offset, &line1, &column1); //get line + m_editor->convertPosition(end, &line2, &column2); //get line + + QRegion reg; + if (line1 > -1 && line2 > -1) + reg = m_editor->editor()->translatedLineRegion(line1 - 1, line2); + + QRect rect; + rect.setHeight(m_widget->height() + 10); + rect.setWidth(reg.boundingRect().width() - reg.boundingRect().left()); + rect.moveTo(reg.boundingRect().topLeft()); + reg = reg.intersect(rect); + if (name.contains("Text")) { m_node = 0; PropertyReader propertyReader(doc.data(), initializer); @@ -105,11 +135,15 @@ void QmlContextPane::apply(TextEditor::BaseTextEditorEditable *editor, Document: QPoint p1 = editor->editor()->mapToParent(editor->editor()->viewport()->mapToParent(editor->editor()->cursorRect(tc).topLeft()) - QPoint(0, contextWidget()->height() + 10)); tc.setPosition(end); QPoint p2 = editor->editor()->mapToParent(editor->editor()->viewport()->mapToParent(editor->editor()->cursorRect(tc).bottomLeft()) + QPoint(0, 10)); + QPoint offset = QPoint(10, 0); + if (reg.boundingRect().width() < 400) + offset = QPoint(400 - reg.boundingRect().width() + 10 ,0); + QPoint p3 = editor->editor()->mapToParent(editor->editor()->viewport()->mapToParent(reg.boundingRect().topRight()) + offset); p2.setX(p1.x()); if (!update) - contextWidget()->activate(p1 , p2); + contextWidget()->activate(p3 , p1, p2); else - contextWidget()->rePosition(p1 , p2); + contextWidget()->rePosition(p3 , p1, p2); m_blockWriting = true; contextWidget()->setType(name); contextWidget()->setProperties(&propertyReader); @@ -163,7 +197,6 @@ void QmlContextPane::setProperty(const QString &propertyName, const QVariant &va QTextCursor tc(m_editor->editor()->document()); tc.beginEditBlock(); int cursorPostion = tc.position(); - tc.beginEditBlock(); changeSet.apply(&tc); if (line > 0) { @@ -171,17 +204,16 @@ void QmlContextPane::setProperty(const QString &propertyName, const QVariant &va QmlJSIndenter indenter; indenter.setTabSize(ts.m_tabSize); indenter.setIndentSize(ts.m_indentSize); - QTextBlock start = m_editor->editor()->document()->findBlockByLineNumber(line); - QTextBlock end = m_editor->editor()->document()->findBlockByLineNumber(line); + QTextBlock start = m_editor->editor()->document()->findBlockByNumber(line); + QTextBlock end = m_editor->editor()->document()->findBlockByNumber(line); - const int indent = indenter.indentForBottomLine(m_editor->editor()->document()->begin(), end.next(), QChar::Null); - ts.indentLine(start, indent); + if (end.isValid()) { + const int indent = indenter.indentForBottomLine(m_editor->editor()->document()->begin(), end.next(), QChar::Null); + ts.indentLine(start, indent); + } } tc.endEditBlock(); tc.setPosition(cursorPostion); - - tc.endEditBlock(); - tc.setPosition(cursorPostion); } } @@ -234,8 +266,14 @@ void QmlContextPane::onPropertyRemovedAndChange(const QString &remove, const QSt if (!m_doc) return; - setProperty(change, value); + QTextCursor tc(m_editor->editor()->document()); + tc.beginEditBlock(); + removeProperty(remove); + setProperty(change, value); + + tc.endEditBlock(); + m_doc.clear(); //the document is outdated } diff --git a/src/plugins/qmldesigner/qmlcontextpane.h b/src/plugins/qmldesigner/qmlcontextpane.h index d13d7125e0..a2b799042b 100644 --- a/src/plugins/qmldesigner/qmlcontextpane.h +++ b/src/plugins/qmldesigner/qmlcontextpane.h @@ -28,7 +28,7 @@ class QmlContextPane : public QmlJS::IContextPane public: QmlContextPane(QObject *parent = 0); ~QmlContextPane(); - void apply(TextEditor::BaseTextEditorEditable *editor, QmlJS::Document::Ptr doc, QmlJS::AST::Node *node, bool update); + void apply(TextEditor::BaseTextEditorEditable *editor, QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot, QmlJS::AST::Node *node, bool update); void setProperty(const QString &propertyName, const QVariant &value); void removeProperty(const QString &propertyName); void setEnabled(bool); diff --git a/src/plugins/qmldesigner/settingspage.ui b/src/plugins/qmldesigner/settingspage.ui index 48b3f50b83..66f276ca89 100644 --- a/src/plugins/qmldesigner/settingspage.ui +++ b/src/plugins/qmldesigner/settingspage.ui @@ -101,13 +101,13 @@ <item row="1" column="0"> <widget class="QGroupBox" name="groupBox"> <property name="title"> - <string>Text Editor Helper</string> + <string>Quick Toolbars</string> </property> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QCheckBox" name="textEditHelperCheckBox"> <property name="text"> - <string>enable</string> + <string>Text Quick Toolbar</string> </property> </widget> </item> diff --git a/src/plugins/qmljseditor/qmljscodecompletion.cpp b/src/plugins/qmljseditor/qmljscodecompletion.cpp index 1bd3c77a52..5f28d8acac 100644 --- a/src/plugins/qmljseditor/qmljscodecompletion.cpp +++ b/src/plugins/qmljseditor/qmljscodecompletion.cpp @@ -878,8 +878,19 @@ void CodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) } } -void CodeCompletion::complete(const TextEditor::CompletionItem &item) +bool CodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) { + if (item.data.canConvert<QString>()) // snippet + return false; + + return (item.text.endsWith(QLatin1String(": ")) && typedChar == QLatin1Char(':')) + || (item.text.endsWith(QLatin1Char('.')) && typedChar == QLatin1Char('.')); +} + +void CodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) +{ + Q_UNUSED(typedChar) // Currently always included in the completion item when used + QString toInsert = item.text; if (QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget())) { @@ -895,7 +906,7 @@ void CodeCompletion::complete(const TextEditor::CompletionItem &item) QString replacableChars; if (toInsert.endsWith(QLatin1String(": "))) replacableChars = QLatin1String(": "); - else if (toInsert.endsWith(QLatin1String("."))) + else if (toInsert.endsWith(QLatin1Char('.'))) replacableChars = QLatin1String("."); int replacedLength = 0; @@ -924,7 +935,7 @@ bool CodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> & const TextEditor::CompletionItem item = completionItems.first(); if (!item.data.canConvert<QString>()) { - complete(item); + complete(item, QChar()); return true; } } diff --git a/src/plugins/qmljseditor/qmljscodecompletion.h b/src/plugins/qmljseditor/qmljscodecompletion.h index 0cae652ad2..f435badf24 100644 --- a/src/plugins/qmljseditor/qmljscodecompletion.h +++ b/src/plugins/qmljseditor/qmljscodecompletion.h @@ -68,7 +68,8 @@ public: virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual void complete(const TextEditor::CompletionItem &item); + virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); + virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); virtual QList<TextEditor::CompletionItem> getCompletions(); diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 0f5ec78049..ea6c3741e5 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -32,6 +32,7 @@ #include "qmljshighlighter.h" #include "qmljseditorplugin.h" #include "qmljsmodelmanager.h" +#include "qmloutlinemodel.h" #include <qmljs/qmljsindenter.h> #include <qmljs/qmljsinterpreter.h> @@ -49,6 +50,7 @@ #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/command.h> +#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> #include <coreplugin/modemanager.h> @@ -60,6 +62,7 @@ #include <texteditor/textblockiterator.h> #include <texteditor/texteditorconstants.h> #include <texteditor/texteditorsettings.h> +#include <texteditor/syntaxhighlighter.h> #include <qmldesigner/qmldesignerconstants.h> #include <utils/changeset.h> #include <utils/uncommentselection.h> @@ -69,13 +72,16 @@ #include <QtGui/QMenu> #include <QtGui/QComboBox> +#include <QtGui/QHeaderView> #include <QtGui/QInputDialog> #include <QtGui/QMainWindow> #include <QtGui/QToolBar> +#include <QtGui/QTreeView> enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 50, - UPDATE_USES_DEFAULT_INTERVAL = 150 + UPDATE_USES_DEFAULT_INTERVAL = 150, + UPDATE_OUTLINE_INTERVAL = 150 // msecs after new semantic info has been arrived / cursor has moved }; using namespace QmlJS; @@ -610,7 +616,8 @@ QString QmlJSEditorEditable::preferredMode() const QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) : TextEditor::BaseTextEditor(parent), - m_methodCombo(0), + m_outlineCombo(0), + m_outlineModel(new QmlOutlineModel(this)), m_modelManager(0), m_contextPane(0) { @@ -642,13 +649,23 @@ QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) : connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument())); connect(this, SIGNAL(textChanged()), this, SLOT(updateUses())); + m_updateOutlineTimer = new QTimer(this); + m_updateOutlineTimer->setInterval(UPDATE_OUTLINE_INTERVAL); + m_updateOutlineTimer->setSingleShot(true); + connect(m_updateOutlineTimer, SIGNAL(timeout()), this, SLOT(updateOutlineNow())); + + m_updateOutlineIndexTimer = new QTimer(this); + m_updateOutlineIndexTimer->setInterval(UPDATE_OUTLINE_INTERVAL); + m_updateOutlineIndexTimer->setSingleShot(true); + connect(m_updateOutlineIndexTimer, SIGNAL(timeout()), this, SLOT(updateOutlineIndexNow())); + baseTextDocument()->setSyntaxHighlighter(new Highlighter(document())); m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<ModelManagerInterface>(); m_contextPane = ExtensionSystem::PluginManager::instance()->getObject<QmlJS::IContextPane>(); if (m_contextPane) connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(onCursorPositionChanged())); - m_oldCurserPosition = -1; + m_oldCursorPosition = -1; if (m_modelManager) { m_semanticHighlighter->setModelManager(m_modelManager); @@ -674,19 +691,33 @@ SemanticInfo QmlJSTextEditor::semanticInfo() const return m_semanticInfo; } -int QmlJSTextEditor::documentRevision() const +int QmlJSTextEditor::editorRevision() const { return document()->revision(); } bool QmlJSTextEditor::isOutdated() const { - if (m_semanticInfo.revision() != documentRevision()) + if (m_semanticInfo.revision() != editorRevision()) return true; return false; } +QmlOutlineModel *QmlJSTextEditor::outlineModel() const +{ + return m_outlineModel; +} + +QModelIndex QmlJSTextEditor::outlineModelIndex() +{ + if (!m_outlineModelIndex.isValid()) { + m_outlineModelIndex = indexForPosition(position()); + emit outlineModelIndexChanged(m_outlineModelIndex); + } + return m_outlineModelIndex; +} + Core::IEditor *QmlJSEditorEditable::duplicate(QWidget *parent) { QmlJSTextEditor *newEditor = new QmlJSTextEditor(parent); @@ -779,6 +810,8 @@ void QmlJSTextEditor::onDocumentUpdated(QmlJS::Document::Ptr doc) const SemanticHighlighter::Source source = currentSource(/*force = */ true); m_semanticHighlighter->rehighlight(source); + + m_updateOutlineTimer->start(); } else { // show parsing errors QList<QTextEdit::ExtraSelection> selections; @@ -793,33 +826,70 @@ void QmlJSTextEditor::modificationChanged(bool changed) m_modelManager->fileChangedOnDisk(file()->fileName()); } -void QmlJSTextEditor::jumpToMethod(int index) +void QmlJSTextEditor::jumpToOutlineElement(int /*index*/) +{ + QModelIndex index = m_outlineCombo->view()->currentIndex(); + AST::SourceLocation location = index.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>(); + + Core::EditorManager *editorManager = Core::EditorManager::instance(); + editorManager->cutForwardNavigationHistory(); + editorManager->addCurrentPositionToNavigationHistory(); + + QTextCursor cursor = textCursor(); + cursor.setPosition(location.offset); + setTextCursor(cursor); + + setFocus(); +} + +void QmlJSTextEditor::updateOutlineNow() { - if (index > 0 && index <= m_semanticInfo.declarations.size()) { // indexes are 1-based - Declaration d = m_semanticInfo.declarations.at(index - 1); - gotoLine(d.startLine, d.startColumn - 1); - setFocus(); + const Snapshot snapshot = m_modelManager->snapshot(); + Document::Ptr document = snapshot.document(file()->fileName()); + + if (!document) + return; + + if (document->editorRevision() != editorRevision()) { + m_updateOutlineTimer->start(); + return; } + + m_outlineModel->update(document, snapshot); + + QTreeView *treeView = static_cast<QTreeView*>(m_outlineCombo->view()); + treeView->expandAll(); + + updateOutlineIndexNow(); } -void QmlJSTextEditor::updateMethodBoxIndex() +void QmlJSTextEditor::updateOutlineIndexNow() { - int line = 0, column = 0; - convertPosition(position(), &line, &column); + if (m_updateOutlineTimer->isActive()) + return; // updateOutlineNow will call this function soon anyway + + if (!m_outlineModel->document()) + return; - int currentSymbolIndex = 0; + if (m_outlineModel->document()->editorRevision() != editorRevision()) { + m_updateOutlineIndexTimer->start(); + return; + } - int index = 0; - while (index < m_semanticInfo.declarations.size()) { - const Declaration &d = m_semanticInfo.declarations.at(index++); + m_outlineModelIndex = QModelIndex(); // invalidate + QModelIndex comboIndex = outlineModelIndex(); - if (line < d.startLine) - break; - else - currentSymbolIndex = index; + if (comboIndex.isValid()) { + bool blocked = m_outlineCombo->blockSignals(true); + + // There is no direct way to select a non-root item + m_outlineCombo->setRootModelIndex(comboIndex.parent()); + m_outlineCombo->setCurrentIndex(comboIndex.row()); + m_outlineCombo->setRootModelIndex(QModelIndex()); + + m_outlineCombo->blockSignals(blocked); } - m_methodCombo->setCurrentIndex(currentSymbolIndex); updateUses(); } @@ -995,10 +1065,6 @@ void QmlJSTextEditor::setSelectedElement() emit selectedElementsChanged(offsets, wordAtCursor); } -void QmlJSTextEditor::updateMethodBoxToolTip() -{ -} - void QmlJSTextEditor::updateFileName() { } @@ -1122,25 +1188,33 @@ TextEditor::BaseTextEditorEditable *QmlJSTextEditor::createEditableInterface() void QmlJSTextEditor::createToolBar(QmlJSEditorEditable *editable) { - m_methodCombo = new QComboBox; - m_methodCombo->setMinimumContentsLength(22); - //m_methodCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_outlineCombo = new QComboBox; + m_outlineCombo->setMinimumContentsLength(22); + m_outlineCombo->setModel(m_outlineModel); + + QTreeView *treeView = new QTreeView; + treeView->header()->hide(); + treeView->setItemsExpandable(false); + treeView->setRootIsDecorated(false); + m_outlineCombo->setView(treeView); + treeView->expandAll(); + + //m_outlineCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); // Make the combo box prefer to expand - QSizePolicy policy = m_methodCombo->sizePolicy(); + QSizePolicy policy = m_outlineCombo->sizePolicy(); policy.setHorizontalPolicy(QSizePolicy::Expanding); - m_methodCombo->setSizePolicy(policy); + m_outlineCombo->setSizePolicy(policy); - connect(m_methodCombo, SIGNAL(activated(int)), this, SLOT(jumpToMethod(int))); - connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateMethodBoxIndex())); - connect(m_methodCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateMethodBoxToolTip())); + connect(m_outlineCombo, SIGNAL(activated(int)), this, SLOT(jumpToOutlineElement(int))); + connect(this, SIGNAL(cursorPositionChanged()), m_updateOutlineIndexTimer, SLOT(start())); connect(file(), SIGNAL(changed()), this, SLOT(updateFileName())); QToolBar *toolBar = static_cast<QToolBar*>(editable->toolBar()); QList<QAction*> actions = toolBar->actions(); - toolBar->insertWidget(actions.first(), m_methodCombo); + toolBar->insertWidget(actions.first(), m_outlineCombo); } TextEditor::BaseTextEditor::Link QmlJSTextEditor::findLinkAt(const QTextCursor &cursor, bool /*resolveTarget*/) @@ -1226,7 +1300,7 @@ bool QmlJSTextEditor::event(QEvent *e) switch (e->type()) { case QEvent::ShortcutOverride: if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_contextPane) { - m_contextPane->apply(editableInterface(), m_semanticInfo.document, 0, false); + m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, 0, false); } break; default: @@ -1241,7 +1315,7 @@ void QmlJSTextEditor::wheelEvent(QWheelEvent *event) { BaseTextEditor::wheelEvent(event); if (m_contextPane) - m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.declaringMember(position()), true); + m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, m_semanticInfo.declaringMember(position()), true); } void QmlJSTextEditor::unCommentSelection() @@ -1473,20 +1547,11 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) FindDeclarations findDeclarations; m_semanticInfo.declarations = findDeclarations(doc->ast()); - QStringList items; - items.append(tr("<Select Symbol>")); - - foreach (Declaration decl, m_semanticInfo.declarations) - items.append(decl.text); - - m_methodCombo->clear(); - m_methodCombo->addItems(items); - updateMethodBoxIndex(); if (m_contextPane) { Node *newNode = m_semanticInfo.declaringMember(position()); if (newNode) { - m_contextPane->apply(editableInterface(), doc, newNode, true); - m_oldCurserPosition = position(); + m_contextPane->apply(editableInterface(), doc, m_semanticInfo.snapshot, newNode, true); + m_oldCursorPosition = position(); } } } @@ -1496,19 +1561,43 @@ void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document()); appendExtraSelectionsForMessages(&selections, m_semanticInfo.semanticMessages, document()); setExtraSelections(CodeWarningsSelection, selections); - - emit semanticInfoUpdated(semanticInfo); } void QmlJSTextEditor::onCursorPositionChanged() { + + if (m_contextPane) { Node *newNode = m_semanticInfo.declaringMember(position()); - Node *oldNode = m_semanticInfo.declaringMember(m_oldCurserPosition); + Node *oldNode = m_semanticInfo.declaringMember(m_oldCursorPosition); if (oldNode != newNode) - m_contextPane->apply(editableInterface(), m_semanticInfo.document, newNode, false); - m_oldCurserPosition = position(); + m_contextPane->apply(editableInterface(), m_semanticInfo.document, m_semanticInfo.snapshot, newNode, false); + m_oldCursorPosition = position(); + } +} + +QModelIndex QmlJSTextEditor::indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex) const +{ + QModelIndex lastIndex = rootIndex; + + + const int rowCount = m_outlineModel->rowCount(rootIndex); + for (int i = 0; i < rowCount; ++i) { + QModelIndex childIndex = m_outlineModel->index(i, 0, rootIndex); + AST::SourceLocation location = childIndex.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>(); + + if ((cursorPosition >= location.offset) + && (cursorPosition <= location.offset + location.length)) { + lastIndex = childIndex; + break; + } } + + if (lastIndex != rootIndex) { + // recurse + lastIndex = indexForPosition(cursorPosition, lastIndex); + } + return lastIndex; } SemanticHighlighter::Source QmlJSTextEditor::currentSource(bool force) @@ -1633,3 +1722,4 @@ void SemanticHighlighter::setModelManager(QmlJS::ModelManagerInterface *modelMan { m_modelManager = modelManager; } + diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index e2916f4733..17b7da2a74 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -37,6 +37,7 @@ #include <texteditor/basetexteditor.h> #include <QtCore/QWaitCondition> +#include <QtCore/QModelIndex> #include <QtCore/QMutex> #include <QtCore/QThread> @@ -60,6 +61,7 @@ class Highlighter; namespace Internal { class QmlJSTextEditor; +class QmlOutlineModel; class QmlJSEditorEditable : public TextEditor::BaseTextEditorEditable { @@ -213,18 +215,19 @@ public: virtual void unCommentSelection(); SemanticInfo semanticInfo() const; - int documentRevision() const; + int editorRevision() const; bool isOutdated() const; -signals: - void selectedElementsChanged(QList<int> offsets, const QString &wordAtCursor); + QmlOutlineModel *outlineModel() const; + QModelIndex outlineModelIndex(); public slots: void followSymbolUnderCursor(); virtual void setFontSettings(const TextEditor::FontSettings &); signals: - void semanticInfoUpdated(const QmlJSEditor::Internal::SemanticInfo &semanticInfo); + void outlineModelIndexChanged(const QModelIndex &index); + void selectedElementsChanged(QList<int> offsets, const QString &wordAtCursor); private slots: void onDocumentUpdated(QmlJS::Document::Ptr doc); @@ -232,9 +235,9 @@ private slots: void updateDocument(); void updateDocumentNow(); - void jumpToMethod(int index); - void updateMethodBoxIndex(); - void updateMethodBoxToolTip(); + void jumpToOutlineElement(int index); + void updateOutlineNow(); + void updateOutlineIndexNow(); void updateFileName(); void updateUses(); @@ -272,13 +275,18 @@ private: QString wordUnderCursor() const; SemanticHighlighter::Source currentSource(bool force = false); + QModelIndex indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex = QModelIndex()) const; const Core::Context m_context; QTimer *m_updateDocumentTimer; QTimer *m_updateUsesTimer; QTimer *m_semanticRehighlightTimer; - QComboBox *m_methodCombo; + QTimer *m_updateOutlineTimer; + QTimer *m_updateOutlineIndexTimer; + QComboBox *m_outlineCombo; + QmlOutlineModel *m_outlineModel; + QModelIndex m_outlineModelIndex; QmlJS::ModelManagerInterface *m_modelManager; QTextCharFormat m_occurrencesFormat; QTextCharFormat m_occurrencesUnusedFormat; @@ -288,7 +296,7 @@ private: SemanticInfo m_semanticInfo; QmlJS::IContextPane *m_contextPane; - int m_oldCurserPosition; + int m_oldCursorPosition; }; } // namespace Internal diff --git a/src/plugins/qmljseditor/qmljseditor.pro b/src/plugins/qmljseditor/qmljseditor.pro index f92ba23c6c..e2de54e54c 100644 --- a/src/plugins/qmljseditor/qmljseditor.pro +++ b/src/plugins/qmljseditor/qmljseditor.pro @@ -24,7 +24,9 @@ HEADERS += \ qmljsquickfix.h \ qmljsrefactoringchanges.h \ qmljscomponentfromobjectdef.h \ - qmljsoutline.h + qmljsoutline.h \ + qmloutlinemodel.h \ + qmltaskmanager.h SOURCES += \ qmljscodecompletion.cpp \ @@ -41,7 +43,9 @@ SOURCES += \ qmljsquickfix.cpp \ qmljsrefactoringchanges.cpp \ qmljscomponentfromobjectdef.cpp \ - qmljsoutline.cpp + qmljsoutline.cpp \ + qmloutlinemodel.cpp \ + qmltaskmanager.cpp RESOURCES += qmljseditor.qrc OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml diff --git a/src/plugins/qmljseditor/qmljseditorconstants.h b/src/plugins/qmljseditor/qmljseditorconstants.h index 0db49ad417..f3df94ff8c 100644 --- a/src/plugins/qmljseditor/qmljseditorconstants.h +++ b/src/plugins/qmljseditor/qmljseditorconstants.h @@ -49,7 +49,7 @@ const char * const FOLLOW_SYMBOL_UNDER_CURSOR = "QmlJSEditor.FollowSymbolUnderCu const char * const QML_MIMETYPE = "application/x-qml"; const char * const JS_MIMETYPE = "application/javascript"; - +const char *const TASK_CATEGORY_QML = "Task.Category.Qml"; } // namespace Constants } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index c611c36aa4..f67dd204fe 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -39,6 +39,8 @@ #include "qmljsoutline.h" #include "qmljspreviewrunner.h" #include "qmljsquickfix.h" +#include "qmljs/qmljsicons.h" +#include "qmltaskmanager.h" #include <qmldesigner/qmldesignerconstants.h> @@ -51,6 +53,7 @@ #include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/command.h> #include <coreplugin/editormanager/editormanager.h> +#include <projectexplorer/taskhub.h> #include <extensionsystem/pluginmanager.h> #include <texteditor/fontsettings.h> #include <texteditor/storagesettings.h> @@ -179,11 +182,29 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e addAutoReleasedObject(new QmlJSOutlineWidgetFactory); + m_qmlTaskManager = new QmlTaskManager; + addAutoReleasedObject(m_qmlTaskManager); + + connect(m_modelManager, SIGNAL(documentChangedOnDisk(QmlJS::Document::Ptr)), + m_qmlTaskManager, SLOT(documentChangedOnDisk(QmlJS::Document::Ptr))); + connect(m_modelManager, SIGNAL(aboutToRemoveFiles(QStringList)), + m_qmlTaskManager, SLOT(documentsRemoved(QStringList))); + return true; } void QmlJSEditorPlugin::extensionsInitialized() { + ProjectExplorer::TaskHub *taskHub = + ExtensionSystem::PluginManager::instance()->getObject<ProjectExplorer::TaskHub>(); + taskHub->addCategory(Constants::TASK_CATEGORY_QML, tr("QML")); +} + +ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown() +{ + delete QmlJS::Icons::instance(); // delete object held by singleton + + return IPlugin::aboutToShutdown(); } void QmlJSEditorPlugin::openPreview() @@ -251,7 +272,7 @@ void QmlJSEditorPlugin::quickFixNow() if (QmlJSTextEditor *editor = qobject_cast<QmlJSTextEditor*>(m_currentTextEditable->widget())) { if (currentEditor == editor) { if (editor->isOutdated()) { - // qDebug() << "TODO: outdated document" << editor->documentRevision() << editor->semanticInfo().revision(); + // qDebug() << "TODO: outdated document" << editor->editorRevision() << editor->semanticInfo().revision(); // ### FIXME: m_quickFixTimer->start(QUICKFIX_INTERVAL); m_quickFixTimer->stop(); } else { diff --git a/src/plugins/qmljseditor/qmljseditorplugin.h b/src/plugins/qmljseditor/qmljseditorplugin.h index af6e5f9280..fb19f78afd 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.h +++ b/src/plugins/qmljseditor/qmljseditorplugin.h @@ -65,6 +65,7 @@ class QmlJSEditorFactory; class QmlJSTextEditor; class QmlJSPreviewRunner; class QmlJSQuickFixCollector; +class QmlTaskManager; class QmlJSEditorPlugin : public ExtensionSystem::IPlugin { @@ -77,6 +78,7 @@ public: // IPlugin bool initialize(const QStringList &arguments, QString *errorMessage = 0); void extensionsInitialized(); + ShutdownFlag aboutToShutdown(); static QmlJSEditorPlugin *instance() { return m_instance; } @@ -111,6 +113,7 @@ private: QTimer *m_quickFixTimer; QPointer<TextEditor::ITextEditable> m_currentTextEditable; + QmlTaskManager *m_qmlTaskManager; }; } // namespace Internal diff --git a/src/plugins/qmljseditor/qmljshighlighter.cpp b/src/plugins/qmljseditor/qmljshighlighter.cpp index e7965286d4..423598a4e6 100644 --- a/src/plugins/qmljseditor/qmljshighlighter.cpp +++ b/src/plugins/qmljseditor/qmljshighlighter.cpp @@ -39,7 +39,7 @@ using namespace QmlJSEditor; using namespace QmlJS; Highlighter::Highlighter(QTextDocument *parent) - : QSyntaxHighlighter(parent), + : TextEditor::SyntaxHighlighter(parent), m_qmlEnabled(true), m_inMultilineComment(false) { diff --git a/src/plugins/qmljseditor/qmljshighlighter.h b/src/plugins/qmljseditor/qmljshighlighter.h index 3c33ebe721..0be4f9bbf4 100644 --- a/src/plugins/qmljseditor/qmljshighlighter.h +++ b/src/plugins/qmljseditor/qmljshighlighter.h @@ -39,10 +39,11 @@ #include <QtGui/QSyntaxHighlighter> #include <texteditor/basetextdocumentlayout.h> +#include <texteditor/syntaxhighlighter.h> namespace QmlJSEditor { -class QMLJSEDITOR_EXPORT Highlighter : public QSyntaxHighlighter +class QMLJSEDITOR_EXPORT Highlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index 88d1326a59..519eec2190 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -126,7 +126,7 @@ void HoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int p const SemanticInfo semanticInfo = edit->semanticInfo(); - if (semanticInfo.revision() != edit->documentRevision()) + if (semanticInfo.revision() != edit->editorRevision()) return; const Snapshot snapshot = semanticInfo.snapshot; diff --git a/src/plugins/qmljseditor/qmljsoutline.cpp b/src/plugins/qmljseditor/qmljsoutline.cpp index d808a2e2d1..4c018a5b6e 100644 --- a/src/plugins/qmljseditor/qmljsoutline.cpp +++ b/src/plugins/qmljseditor/qmljsoutline.cpp @@ -1,15 +1,11 @@ #include "qmljsoutline.h" +#include "qmloutlinemodel.h" -#include <coreplugin/icore.h> -#include <extensionsystem/pluginmanager.h> -#include <qmljs/parser/qmljsast_p.h> -#include <qmljs/qmljsmodelmanagerinterface.h> - -#include <QtCore/QDebug> +#include <coreplugin/ifile.h> +#include <coreplugin/editormanager/editormanager.h> #include <QtGui/QVBoxLayout> -#include <typeinfo> - +#include <QDebug> using namespace QmlJS; enum { @@ -19,11 +15,6 @@ enum { namespace QmlJSEditor { namespace Internal { -QmlOutlineModel::QmlOutlineModel(QObject *parent) : - QStandardItemModel(parent) -{ -} - QmlJSOutlineTreeView::QmlJSOutlineTreeView(QWidget *parent) : QTreeView(parent) { @@ -38,207 +29,9 @@ QmlJSOutlineTreeView::QmlJSOutlineTreeView(QWidget *parent) : setExpandsOnDoubleClick(false); } -void QmlOutlineModel::startSync() -{ - m_treePos.clear(); - m_treePos.append(0); - m_currentItem = invisibleRootItem(); -} - -QModelIndex QmlOutlineModel::enterElement(const QString &type, const AST::SourceLocation &sourceLocation) -{ - QStandardItem *item = enterNode(sourceLocation); - item->setText(type); - item->setIcon(m_icons.objectDefinitionIcon()); - return item->index(); -} - -void QmlOutlineModel::leaveElement() -{ - leaveNode(); -} - -QModelIndex QmlOutlineModel::enterProperty(const QString &name, const AST::SourceLocation &sourceLocation) -{ - QStandardItem *item = enterNode(sourceLocation); - item->setText(name); - item->setIcon(m_icons.scriptBindingIcon()); - return item->index(); -} - -void QmlOutlineModel::leaveProperty() -{ - leaveNode(); -} - -QStandardItem *QmlOutlineModel::enterNode(const QmlJS::AST::SourceLocation &location) -{ - int siblingIndex = m_treePos.last(); - if (siblingIndex == 0) { - // first child - if (!m_currentItem->hasChildren()) { - QStandardItem *parentItem = m_currentItem; - m_currentItem = new QStandardItem; - m_currentItem->setEditable(false); - parentItem->appendRow(m_currentItem); - if (debug) - qDebug() << "QmlOutlineModel - Adding" << "element to" << parentItem->text(); - } else { - m_currentItem = m_currentItem->child(0); - } - } else { - // sibling - if (m_currentItem->rowCount() <= siblingIndex) { - // attach - QStandardItem *oldItem = m_currentItem; - m_currentItem = new QStandardItem; - m_currentItem->setEditable(false); - oldItem->appendRow(m_currentItem); - if (debug) - qDebug() << "QmlOutlineModel - Adding" << "element to" << oldItem->text(); - } else { - m_currentItem = m_currentItem->child(siblingIndex); - } - } - - m_treePos.append(0); - m_currentItem->setData(QVariant::fromValue(location), SourceLocationRole); - - return m_currentItem; -} - -void QmlOutlineModel::leaveNode() -{ - int lastIndex = m_treePos.takeLast(); - - - if (lastIndex > 0) { - // element has children - if (lastIndex < m_currentItem->rowCount()) { - if (debug) - qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << lastIndex << m_currentItem->rowCount() - lastIndex; - m_currentItem->removeRows(lastIndex, m_currentItem->rowCount() - lastIndex); - } - m_currentItem = parentItem(); - } else { - if (m_currentItem->hasChildren()) { - if (debug) - qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << 0 << m_currentItem->rowCount(); - m_currentItem->removeRows(0, m_currentItem->rowCount()); - } - m_currentItem = parentItem(); - } - - - m_treePos.last()++; -} - -QStandardItem *QmlOutlineModel::parentItem() -{ - QStandardItem *parent = m_currentItem->parent(); - if (!parent) - parent = invisibleRootItem(); - return parent; -} - -class QmlOutlineModelSync : protected AST::Visitor -{ -public: - QmlOutlineModelSync(QmlOutlineModel *model) : - m_model(model), - indent(0) - { - } - - void operator()(Document::Ptr doc) - { - m_nodeToIndex.clear(); - - if (debug) - qDebug() << "QmlOutlineModel ------"; - m_model->startSync(); - if (doc && doc->ast()) - doc->ast()->accept(this); - } - -private: - bool preVisit(AST::Node *node) - { - if (!node) - return false; - if (debug) - qDebug() << "QmlOutlineModel -" << QByteArray(indent++, '-').constData() << node << typeid(*node).name(); - return true; - } - - void postVisit(AST::Node *) - { - indent--; - } - - QString asString(AST::UiQualifiedId *id) - { - QString text; - for (; id; id = id->next) { - if (id->name) - text += id->name->asString(); - else - text += QLatin1Char('?'); - - if (id->next) - text += QLatin1Char('.'); - } - - return text; - } - - bool visit(AST::UiObjectDefinition *objDef) - { - AST::SourceLocation location; - location.offset = objDef->firstSourceLocation().offset; - location.length = objDef->lastSourceLocation().offset - - objDef->firstSourceLocation().offset - + objDef->lastSourceLocation().length; - - QModelIndex index = m_model->enterElement(asString(objDef->qualifiedTypeNameId), location); - m_nodeToIndex.insert(objDef, index); - return true; - } - - void endVisit(AST::UiObjectDefinition * /*objDefinition*/) - { - m_model->leaveElement(); - } - - bool visit(AST::UiScriptBinding *scriptBinding) - { - AST::SourceLocation location; - location.offset = scriptBinding->firstSourceLocation().offset; - location.length = scriptBinding->lastSourceLocation().offset - - scriptBinding->firstSourceLocation().offset - + scriptBinding->lastSourceLocation().length; - - QModelIndex index = m_model->enterProperty(asString(scriptBinding->qualifiedId), location); - m_nodeToIndex.insert(scriptBinding, index); - - return true; - } - - void endVisit(AST::UiScriptBinding * /*scriptBinding*/) - { - m_model->leaveProperty(); - } - - QmlOutlineModel *m_model; - QHash<AST::Node*, QModelIndex> m_nodeToIndex; - int indent; -}; - - QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent) : TextEditor::IOutlineWidget(parent), - m_treeView(new QmlJSOutlineTreeView()), - m_model(new QmlOutlineModel), + m_treeView(new QmlJSOutlineTreeView(this)), m_enableCursorSync(true), m_blockCursorSync(false) { @@ -249,94 +42,44 @@ QmlJSOutlineWidget::QmlJSOutlineWidget(QWidget *parent) : layout->addWidget(m_treeView); setLayout(layout); - - m_treeView->setModel(m_model); - - connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), - this, SLOT(updateSelectionInText(QItemSelection))); } void QmlJSOutlineWidget::setEditor(QmlJSTextEditor *editor) { m_editor = editor; - connect(m_editor.data(), SIGNAL(semanticInfoUpdated(QmlJSEditor::Internal::SemanticInfo)), - this, SLOT(updateOutline(QmlJSEditor::Internal::SemanticInfo))); - connect(m_editor.data(), SIGNAL(cursorPositionChanged()), - this, SLOT(updateSelectionInTree())); + m_treeView->setModel(m_editor.data()->outlineModel()); + modelUpdated(); + + connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(updateSelectionInText(QItemSelection))); - updateOutline(m_editor.data()->semanticInfo()); + connect(m_editor.data(), SIGNAL(outlineModelIndexChanged(QModelIndex)), + this, SLOT(updateSelectionInTree(QModelIndex))); + connect(m_editor.data()->outlineModel(), SIGNAL(updated()), + this, SLOT(modelUpdated())); } void QmlJSOutlineWidget::setCursorSynchronization(bool syncWithCursor) { m_enableCursorSync = syncWithCursor; if (m_enableCursorSync) - updateSelectionInTree(); + updateSelectionInTree(m_editor.data()->outlineModelIndex()); } -void QmlJSOutlineWidget::updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo) +void QmlJSOutlineWidget::modelUpdated() { - Document::Ptr doc = semanticInfo.document; - - if (!doc) { - return; - } - - if (!m_editor - || m_editor.data()->file()->fileName() != doc->fileName() - || m_editor.data()->documentRevision() != doc->editorRevision()) { - return; - } - - if (doc->ast() - && m_model) { - - // got a correctly parsed (or recovered) file. - - if (QmlOutlineModel *qmlModel = qobject_cast<QmlOutlineModel*>(m_model)) { - QmlOutlineModelSync syncModel(qmlModel); - syncModel(doc); - } - } else { - // TODO: Maybe disable view? - } - m_treeView->expandAll(); } -QModelIndex QmlJSOutlineWidget::indexForPosition(const QModelIndex &rootIndex, int cursorPosition) -{ - if (!rootIndex.isValid()) - return QModelIndex(); - - AST::SourceLocation location = rootIndex.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>(); - - if (!offsetInsideLocation(cursorPosition, location)) { - return QModelIndex(); - } - - const int rowCount = rootIndex.model()->rowCount(rootIndex); - for (int i = 0; i < rowCount; ++i) { - QModelIndex childIndex = rootIndex.child(i, 0); - QModelIndex resultIndex = indexForPosition(childIndex, cursorPosition); - if (resultIndex.isValid()) - return resultIndex; - } - - return rootIndex; -} - -void QmlJSOutlineWidget::updateSelectionInTree() +void QmlJSOutlineWidget::updateSelectionInTree(const QModelIndex &index) { if (!syncCursor()) return; - int absoluteCursorPos = m_editor.data()->textCursor().position(); - QModelIndex index = indexForPosition(m_model->index(0, 0), absoluteCursorPos); - m_blockCursorSync = true; m_treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); + m_treeView->scrollTo(index); m_blockCursorSync = false; } @@ -350,6 +93,10 @@ void QmlJSOutlineWidget::updateSelectionInText(const QItemSelection &selection) QModelIndex index = selection.indexes().first(); AST::SourceLocation location = index.data(QmlOutlineModel::SourceLocationRole).value<AST::SourceLocation>(); + Core::EditorManager *editorManager = Core::EditorManager::instance(); + editorManager->cutForwardNavigationHistory(); + editorManager->addCurrentPositionToNavigationHistory(); + QTextCursor textCursor = m_editor.data()->textCursor(); m_blockCursorSync = true; textCursor.setPosition(location.offset); @@ -358,12 +105,6 @@ void QmlJSOutlineWidget::updateSelectionInText(const QItemSelection &selection) } } -bool QmlJSOutlineWidget::offsetInsideLocation(quint32 offset, const QmlJS::AST::SourceLocation &location) -{ - return ((offset >= location.offset) - && (offset <= location.offset + location.length)); -} - bool QmlJSOutlineWidget::syncCursor() { return m_enableCursorSync && !m_blockCursorSync; diff --git a/src/plugins/qmljseditor/qmljsoutline.h b/src/plugins/qmljseditor/qmljsoutline.h index 1571069afb..c42685dd8d 100644 --- a/src/plugins/qmljseditor/qmljsoutline.h +++ b/src/plugins/qmljseditor/qmljsoutline.h @@ -3,16 +3,9 @@ #include "qmljseditor.h" -#include <coreplugin/editormanager/editormanager.h> -#include <coreplugin/inavigationwidgetfactory.h> #include <texteditor/ioutlinewidget.h> -#include <qmljs/parser/qmljsastvisitor_p.h> -#include <qmljs/qmljsdocument.h> -#include <qmljs/qmljsicons.h> -#include <QtGui/QStandardItemModel> #include <QtGui/QTreeView> -#include <QtGui/QWidget> namespace Core { class IEditor; @@ -25,35 +18,6 @@ class Editor; namespace QmlJSEditor { namespace Internal { -class QmlOutlineModel : public QStandardItemModel -{ - Q_OBJECT -public: - enum CustomRoles { - SourceLocationRole = Qt::UserRole + 1 - }; - - QmlOutlineModel(QObject *parent = 0); - - void startSync(); - - QModelIndex enterElement(const QString &typeName, const QmlJS::AST::SourceLocation &location); - void leaveElement(); - - QModelIndex enterProperty(const QString &name, const QmlJS::AST::SourceLocation &location); - void leaveProperty(); - -private: - QStandardItem *enterNode(const QmlJS::AST::SourceLocation &location); - void leaveNode(); - - QStandardItem *parentItem(); - - QList<int> m_treePos; - QStandardItem *m_currentItem; - QmlJS::Icons m_icons; -}; - class QmlJSOutlineTreeView : public QTreeView { Q_OBJECT @@ -61,7 +25,6 @@ public: QmlJSOutlineTreeView(QWidget *parent = 0); }; - class QmlJSOutlineWidget : public TextEditor::IOutlineWidget { Q_OBJECT @@ -74,18 +37,15 @@ public: virtual void setCursorSynchronization(bool syncWithCursor); private slots: - void updateOutline(const QmlJSEditor::Internal::SemanticInfo &semanticInfo); - void updateSelectionInTree(); + void modelUpdated(); + void updateSelectionInTree(const QModelIndex &index); void updateSelectionInText(const QItemSelection &selection); private: - QModelIndex indexForPosition(const QModelIndex &rootIndex, int cursorPosition); - bool offsetInsideLocation(quint32 offset, const QmlJS::AST::SourceLocation &location); bool syncCursor(); private: QmlJSOutlineTreeView *m_treeView; - QAbstractItemModel *m_model; QWeakPointer<QmlJSTextEditor> m_editor; bool m_enableCursorSync; @@ -103,6 +63,4 @@ public: } // namespace Internal } // namespace QmlJSEditor -Q_DECLARE_METATYPE(QmlJS::AST::SourceLocation); - #endif // QMLJSOUTLINE_H diff --git a/src/plugins/qmljseditor/qmloutlinemodel.cpp b/src/plugins/qmljseditor/qmloutlinemodel.cpp new file mode 100644 index 0000000000..ef2c838013 --- /dev/null +++ b/src/plugins/qmljseditor/qmloutlinemodel.cpp @@ -0,0 +1,337 @@ +#include "qmloutlinemodel.h" +#include <qmljs/parser/qmljsastvisitor_p.h> +#include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljslookupcontext.h> + +#include <coreplugin/icore.h> +#include <QtCore/QDebug> +#include <QtCore/QTime> +#include <typeinfo> + +using namespace QmlJS; +using namespace QmlJSEditor::Internal; + +enum { + debug = false +}; + +namespace QmlJSEditor { +namespace Internal { + +class QmlOutlineModelSync : protected AST::Visitor +{ +public: + QmlOutlineModelSync(QmlOutlineModel *model) : + m_model(model), + indent(0) + { + } + + void operator()(Document::Ptr doc, const Snapshot &snapshot) + { + m_nodeToIndex.clear(); + + // Set up lookup context once to do the element type lookup + // + // We're simplifying here by using the root context everywhere + // (empty node list). However, creating the LookupContext is quite expensive (about 3ms), + // and there is AFAIK no way to introduce new type names in a sub-context. + m_context = LookupContext::create(doc, snapshot, QList<AST::Node*>()); + + if (debug) + qDebug() << "QmlOutlineModel ------"; + if (doc && doc->ast()) + doc->ast()->accept(this); + + m_context.clear(); + } + +private: + bool preVisit(AST::Node *node) + { + if (!node) + return false; + if (debug) + qDebug() << "QmlOutlineModel -" << QByteArray(indent++, '-').constData() << node << typeid(*node).name(); + return true; + } + + void postVisit(AST::Node *) + { + indent--; + } + + QString asString(AST::UiQualifiedId *id) + { + QString text; + for (; id; id = id->next) { + if (id->name) + text += id->name->asString(); + else + text += QLatin1Char('?'); + + if (id->next) + text += QLatin1Char('.'); + } + + return text; + } + + + typedef QPair<QString,QString> ElementType; + bool visit(AST::UiObjectDefinition *objDef) + { + if (!validElement(objDef)) { + return true; + } + + AST::SourceLocation location = getLocation(objDef); + + const QString typeName = asString(objDef->qualifiedTypeNameId); + + if (!m_typeToIcon.contains(typeName)) { + m_typeToIcon.insert(typeName, getIcon(objDef)); + } + const QIcon icon = m_typeToIcon.value(typeName); + QString id = getId(objDef); + + QModelIndex index = m_model->enterElement(typeName, id, icon, location); + m_nodeToIndex.insert(objDef, index); + return true; + } + + void endVisit(AST::UiObjectDefinition *objDef) + { + if (!validElement(objDef)) { + return; + } + m_model->leaveElement(); + } + + bool visit(AST::UiScriptBinding *scriptBinding) + { + AST::SourceLocation location = getLocation(scriptBinding); + + QModelIndex index = m_model->enterProperty(asString(scriptBinding->qualifiedId), false, location); + m_nodeToIndex.insert(scriptBinding, index); + + return true; + } + + void endVisit(AST::UiScriptBinding * /*scriptBinding*/) + { + m_model->leaveProperty(); + } + + bool visit(AST::UiPublicMember *publicMember) + { + AST::SourceLocation location = getLocation(publicMember); + QModelIndex index = m_model->enterProperty(publicMember->name->asString(), true, location); + m_nodeToIndex.insert(publicMember, index); + + return true; + } + + void endVisit(AST::UiPublicMember * /*publicMember*/) + { + m_model->leaveProperty(); + } + + bool validElement(AST::UiObjectDefinition *objDef) { + // For 'Rectangle { id }', id is parsed as UiObjectDefinition ... Filter this out. + return objDef->qualifiedTypeNameId->name->asString().at(0).isUpper(); + } + + QIcon getIcon(AST::UiObjectDefinition *objDef) { + const QmlJS::Interpreter::Value *value = m_context->evaluate(objDef->qualifiedTypeNameId); + + if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { + do { + QString module; + QString typeName; + if (const Interpreter::QmlObjectValue *qmlObjectValue = + dynamic_cast<const Interpreter::QmlObjectValue*>(objectValue)) { + module = qmlObjectValue->packageName(); + } + typeName = objectValue->className(); + + QIcon icon = m_model->m_icons->icon(module, typeName); + if (! icon.isNull()) + return icon; + + objectValue = objectValue->prototype(m_context->context()); + } while (objectValue); + } + return QIcon(); + } + + QString getId(AST::UiObjectDefinition *objDef) { + QString id; + for (AST::UiObjectMemberList *it = objDef->initializer->members; it; it = it->next) { + if (AST::UiScriptBinding *binding = dynamic_cast<AST::UiScriptBinding*>(it->member)) { + if (binding->qualifiedId->name->asString() == "id") { + AST::ExpressionStatement *expr = dynamic_cast<AST::ExpressionStatement*>(binding->statement); + if (!expr) + continue; + AST::IdentifierExpression *idExpr = dynamic_cast<AST::IdentifierExpression*>(expr->expression); + if (!idExpr) + continue; + id = idExpr->name->asString(); + break; + } + } + } + return id; + } + + AST::SourceLocation getLocation(AST::UiObjectMember *objMember) { + AST::SourceLocation location; + location.offset = objMember->firstSourceLocation().offset; + location.length = objMember->lastSourceLocation().offset + - objMember->firstSourceLocation().offset + + objMember->lastSourceLocation().length; + return location; + } + + QmlOutlineModel *m_model; + LookupContext::Ptr m_context; + + QHash<AST::Node*, QModelIndex> m_nodeToIndex; + QHash<QString, QIcon> m_typeToIcon; + int indent; +}; + +QmlOutlineModel::QmlOutlineModel(QObject *parent) : + QStandardItemModel(parent) +{ + m_icons = Icons::instance(); + const QString resourcePath = Core::ICore::instance()->resourcePath(); + QmlJS::Icons::instance()->setIconFilesPath(resourcePath + "/qmlicons"); +} + +QmlJS::Document::Ptr QmlOutlineModel::document() const +{ + return m_document; +} + +void QmlOutlineModel::update(QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot) +{ + m_document = doc; + + m_treePos.clear(); + m_treePos.append(0); + m_currentItem = invisibleRootItem(); + + QmlOutlineModelSync syncModel(this); + syncModel(doc, snapshot); + + emit updated(); +} + +QModelIndex QmlOutlineModel::enterElement(const QString &type, const QString &id, const QIcon &icon, const AST::SourceLocation &sourceLocation) +{ + QStandardItem *item = enterNode(sourceLocation); + if (!id.isEmpty()) { + item->setText(id); + } else { + item->setText(type); + } + item->setIcon(icon); + item->setToolTip(type); + return item->index(); +} + +void QmlOutlineModel::leaveElement() +{ + leaveNode(); +} + +QModelIndex QmlOutlineModel::enterProperty(const QString &name, bool isCustomProperty, const AST::SourceLocation &sourceLocation) +{ + QStandardItem *item = enterNode(sourceLocation); + item->setText(name); + if (isCustomProperty) { + item->setIcon(m_icons->publicMemberIcon()); + } else { + item->setIcon(m_icons->scriptBindingIcon()); + } + return item->index(); +} + +void QmlOutlineModel::leaveProperty() +{ + leaveNode(); +} + +QStandardItem *QmlOutlineModel::enterNode(const QmlJS::AST::SourceLocation &location) +{ + int siblingIndex = m_treePos.last(); + if (siblingIndex == 0) { + // first child + if (!m_currentItem->hasChildren()) { + QStandardItem *parentItem = m_currentItem; + m_currentItem = new QStandardItem; + m_currentItem->setEditable(false); + parentItem->appendRow(m_currentItem); + if (debug) + qDebug() << "QmlOutlineModel - Adding" << "element to" << parentItem->text(); + } else { + m_currentItem = m_currentItem->child(0); + } + } else { + // sibling + if (m_currentItem->rowCount() <= siblingIndex) { + // attach + QStandardItem *oldItem = m_currentItem; + m_currentItem = new QStandardItem; + m_currentItem->setEditable(false); + oldItem->appendRow(m_currentItem); + if (debug) + qDebug() << "QmlOutlineModel - Adding" << "element to" << oldItem->text(); + } else { + m_currentItem = m_currentItem->child(siblingIndex); + } + } + + m_treePos.append(0); + m_currentItem->setData(QVariant::fromValue(location), SourceLocationRole); + + return m_currentItem; +} + +void QmlOutlineModel::leaveNode() +{ + int lastIndex = m_treePos.takeLast(); + + + if (lastIndex > 0) { + // element has children + if (lastIndex < m_currentItem->rowCount()) { + if (debug) + qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << lastIndex << m_currentItem->rowCount() - lastIndex; + m_currentItem->removeRows(lastIndex, m_currentItem->rowCount() - lastIndex); + } + m_currentItem = parentItem(); + } else { + if (m_currentItem->hasChildren()) { + if (debug) + qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << 0 << m_currentItem->rowCount(); + m_currentItem->removeRows(0, m_currentItem->rowCount()); + } + m_currentItem = parentItem(); + } + + + m_treePos.last()++; +} + +QStandardItem *QmlOutlineModel::parentItem() +{ + QStandardItem *parent = m_currentItem->parent(); + if (!parent) + parent = invisibleRootItem(); + return parent; +} + +} // namespace Internal +} // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmloutlinemodel.h b/src/plugins/qmljseditor/qmloutlinemodel.h new file mode 100644 index 0000000000..6b22538ed8 --- /dev/null +++ b/src/plugins/qmljseditor/qmloutlinemodel.h @@ -0,0 +1,55 @@ +#ifndef QMLOUTLINEMODEL_H +#define QMLOUTLINEMODEL_H + +#include <qmljs/qmljsdocument.h> +#include <qmljs/qmljsicons.h> + +#include <QStandardItemModel> + +namespace QmlJSEditor { +namespace Internal { + +class QmlOutlineModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum CustomRoles { + SourceLocationRole = Qt::UserRole + 1 + }; + + QmlOutlineModel(QObject *parent = 0); + + QmlJS::Document::Ptr document() const; + void update(QmlJS::Document::Ptr doc, const QmlJS::Snapshot &snapshot); + + QModelIndex enterElement(const QString &typeName, const QString &id, const QIcon &icon, + const QmlJS::AST::SourceLocation &location); + void leaveElement(); + + QModelIndex enterProperty(const QString &name, bool isCustomProperty, + const QmlJS::AST::SourceLocation &location); + void leaveProperty(); + +signals: + void updated(); + +private: + QStandardItem *enterNode(const QmlJS::AST::SourceLocation &location); + void leaveNode(); + + QStandardItem *parentItem(); + + QmlJS::Document::Ptr m_document; + QList<int> m_treePos; + QStandardItem *m_currentItem; + QmlJS::Icons *m_icons; + + friend class QmlOutlineModelSync; +}; + +} // namespace Internal +} // namespace QmlJSEditor + +Q_DECLARE_METATYPE(QmlJS::AST::SourceLocation); + +#endif // QMLOUTLINEMODEL_H diff --git a/src/plugins/qmlprojectmanager/qmltaskmanager.cpp b/src/plugins/qmljseditor/qmltaskmanager.cpp index fc8905f2b8..30cd0a9dbe 100644 --- a/src/plugins/qmlprojectmanager/qmltaskmanager.cpp +++ b/src/plugins/qmljseditor/qmltaskmanager.cpp @@ -28,14 +28,15 @@ **************************************************************************/ #include "qmltaskmanager.h" -#include "qmlprojectconstants.h" +#include "qmljseditorconstants.h" #include <extensionsystem/pluginmanager.h> #include <projectexplorer/taskhub.h> +#include <qmljs/qmljsmodelmanagerinterface.h> #include <QDebug> -namespace QmlProjectManager { +namespace QmlJSEditor { namespace Internal { QmlTaskManager::QmlTaskManager(QObject *parent) : @@ -45,12 +46,6 @@ QmlTaskManager::QmlTaskManager(QObject *parent) : m_taskHub = ExtensionSystem::PluginManager::instance()->getObject<ProjectExplorer::TaskHub>(); } -QmlTaskManager *QmlTaskManager::instance() -{ - ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance(); - return pluginManager->getObject<QmlTaskManager>(); -} - void QmlTaskManager::documentChangedOnDisk(QmlJS::Document::Ptr doc) { const QString fileName = doc->fileName(); diff --git a/src/plugins/qmlprojectmanager/qmltaskmanager.h b/src/plugins/qmljseditor/qmltaskmanager.h index 74e9d0a313..18ccc828c6 100644 --- a/src/plugins/qmlprojectmanager/qmltaskmanager.h +++ b/src/plugins/qmljseditor/qmltaskmanager.h @@ -42,7 +42,7 @@ namespace ProjectExplorer { class TaskHub; } // namespace ProjectExplorer -namespace QmlProjectManager { +namespace QmlJSEditor { namespace Internal { class QmlTaskManager : public QObject @@ -51,7 +51,7 @@ class QmlTaskManager : public QObject public: QmlTaskManager(QObject *parent = 0); - static QmlTaskManager *instance(); + void extensionsInitialized(); public slots: void documentChangedOnDisk(QmlJS::Document::Ptr doc); @@ -67,6 +67,6 @@ private: }; } // Internal -} // QmlProjectManager +} // QmlJSEditor #endif // QMLTASKMANAGER_H diff --git a/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp b/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp index 5a21144c28..87e17bb232 100644 --- a/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp +++ b/src/plugins/qmljsinspector/qmljsinspectorplugin.cpp @@ -114,8 +114,9 @@ Inspector *InspectorPlugin::inspector() const return _inspector; } -void InspectorPlugin::aboutToShutdown() +ExtensionSystem::IPlugin::ShutdownFlag InspectorPlugin::aboutToShutdown() { + return SynchronousShutdown; } bool InspectorPlugin::initialize(const QStringList &arguments, QString *errorString) diff --git a/src/plugins/qmljsinspector/qmljsinspectorplugin.h b/src/plugins/qmljsinspector/qmljsinspectorplugin.h index d1696b0668..237b8e566c 100644 --- a/src/plugins/qmljsinspector/qmljsinspectorplugin.h +++ b/src/plugins/qmljsinspector/qmljsinspectorplugin.h @@ -70,7 +70,7 @@ public: // ExtensionSystem::IPlugin interface virtual bool initialize(const QStringList &arguments, QString *errorString); virtual void extensionsInitialized(); - virtual void aboutToShutdown(); + virtual ExtensionSystem::IPlugin::ShutdownFlag aboutToShutdown(); public slots: void activateDebuggerForProject(ProjectExplorer::Project *project, const QString &runMode); diff --git a/src/plugins/qmlprojectmanager/images/qml_wizard.png b/src/plugins/qmlprojectmanager/images/qml_wizard.png Binary files differindex 5355c80bf8..23c0236871 100644 --- a/src/plugins/qmlprojectmanager/images/qml_wizard.png +++ b/src/plugins/qmlprojectmanager/images/qml_wizard.png diff --git a/src/plugins/qmlprojectmanager/qmlprojectapplicationwizard.cpp b/src/plugins/qmlprojectmanager/qmlprojectapplicationwizard.cpp index eafbc0385b..f913cc7d82 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectapplicationwizard.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectapplicationwizard.cpp @@ -61,14 +61,7 @@ QmlProjectApplicationWizard::~QmlProjectApplicationWizard() Core::BaseFileWizardParameters QmlProjectApplicationWizard::parameters() { Core::BaseFileWizardParameters parameters(ProjectWizard); - // TODO: provide icons in correct size - { - QPixmap icon(22, 22); - icon.fill(Qt::transparent); - QPainter p(&icon); - p.drawPixmap(3, 3, 16, 16, QPixmap(QLatin1String(Constants::QML_WIZARD_ICON))); - parameters.setIcon(icon); - } + parameters.setIcon(QIcon(QLatin1String(Constants::QML_WIZARD_ICON))); parameters.setDisplayName(tr("Qt QML Application")); parameters.setId(QLatin1String("QA.QML Application")); parameters.setDescription(tr("Creates a Qt QML application project with a single QML file containing the main view.\n\n" diff --git a/src/plugins/qmlprojectmanager/qmlprojectconstants.h b/src/plugins/qmlprojectmanager/qmlprojectconstants.h index 1fd04264a0..3cd4d9c7e0 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectconstants.h +++ b/src/plugins/qmlprojectmanager/qmlprojectconstants.h @@ -47,8 +47,6 @@ const char *const FILES_EDITOR_ID = "Qt4.QmlProjectEditor"; const char *const FILES_EDITOR_DISPLAY_NAME = QT_TRANSLATE_NOOP("OpenWith::Editors", ".qmlproject Editor"); const char *const FILES_MIMETYPE = QMLMIMETYPE; -const char *const TASK_CATEGORY_QML = "Task.Category.Qml"; - // Wizard category const char * const QML_WIZARD_CATEGORY = "F.Projects"; // (after Qt) const char * const QML_WIZARD_TR_SCOPE = "QmlProjectManager"; diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanager.cpp b/src/plugins/qmlprojectmanager/qmlprojectmanager.cpp index 683bc64f89..8a810319a6 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectmanager.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectmanager.cpp @@ -30,7 +30,6 @@ #include "qmlprojectmanager.h" #include "qmlprojectconstants.h" #include "qmlproject.h" -#include "qmltaskmanager.h" #include <coreplugin/icore.h> #include <coreplugin/ifile.h> diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanager.pro b/src/plugins/qmlprojectmanager/qmlprojectmanager.pro index 72e0d2cadd..d97c600458 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectmanager.pro +++ b/src/plugins/qmlprojectmanager/qmlprojectmanager.pro @@ -19,11 +19,9 @@ HEADERS += qmlproject.h \ qmlprojectrunconfiguration.h \ qmlprojectrunconfigurationfactory.h \ qmlprojectapplicationwizard.h \ - qmltaskmanager.h \ qmlprojectmanager_global.h \ qmlprojectmanagerconstants.h \ - qmlprojecttarget.h \ - qmloutputformatter.h + qmlprojecttarget.h SOURCES += qmlproject.cpp \ qmlprojectplugin.cpp \ qmlprojectmanager.cpp \ @@ -34,9 +32,7 @@ SOURCES += qmlproject.cpp \ qmlprojectrunconfiguration.cpp \ qmlprojectrunconfigurationfactory.cpp \ qmlprojectapplicationwizard.cpp \ - qmltaskmanager.cpp \ - qmlprojecttarget.cpp \ - qmloutputformatter.cpp + qmlprojecttarget.cpp RESOURCES += qmlproject.qrc OTHER_FILES += QmlProjectManager.pluginspec \ diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp index 44229263ce..622f594079 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp @@ -35,7 +35,6 @@ #include "qmlproject.h" #include "qmlprojectrunconfigurationfactory.h" #include "qmlprojectruncontrol.h" -#include "qmltaskmanager.h" #include "fileformat/qmlprojectfileformat.h" #include <extensionsystem/pluginmanager.h> @@ -47,15 +46,13 @@ #include <texteditor/texteditoractionhandler.h> #include <projectexplorer/taskhub.h> -#include <qmljs/qmljsmodelmanagerinterface.h> #include <QtCore/QtPlugin> namespace QmlProjectManager { namespace Internal { -QmlProjectPlugin::QmlProjectPlugin() : - m_qmlTaskManager(0) +QmlProjectPlugin::QmlProjectPlugin() { } QmlProjectPlugin::~QmlProjectPlugin() @@ -76,9 +73,6 @@ bool QmlProjectPlugin::initialize(const QStringList &, QString *errorMessage) Manager *manager = new Manager; - m_qmlTaskManager = new QmlTaskManager(this); - addAutoReleasedObject(m_qmlTaskManager); - addAutoReleasedObject(manager); addAutoReleasedObject(new Internal::QmlProjectRunConfigurationFactory); addAutoReleasedObject(new Internal::QmlRunControlFactory); @@ -94,16 +88,6 @@ bool QmlProjectPlugin::initialize(const QStringList &, QString *errorMessage) void QmlProjectPlugin::extensionsInitialized() { - ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance(); - ProjectExplorer::TaskHub *taskHub = pluginManager->getObject<ProjectExplorer::TaskHub>(); - taskHub->addCategory(Constants::TASK_CATEGORY_QML, tr("QML")); - - QmlJS::ModelManagerInterface *modelManager = pluginManager->getObject<QmlJS::ModelManagerInterface>(); - Q_ASSERT(modelManager); - connect(modelManager, SIGNAL(documentChangedOnDisk(QmlJS::Document::Ptr)), - m_qmlTaskManager, SLOT(documentChangedOnDisk(QmlJS::Document::Ptr))); - connect(modelManager, SIGNAL(aboutToRemoveFiles(QStringList)), - m_qmlTaskManager, SLOT(documentsRemoved(QStringList))); } } // namespace Internal diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.h b/src/plugins/qmlprojectmanager/qmlprojectplugin.h index 140a16f2c5..7fb905d820 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectplugin.h +++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.h @@ -38,7 +38,6 @@ namespace QmlProjectManager { namespace Internal { class ProjectFilesFactory; -class QmlTaskManager; class QmlProjectPlugin: public ExtensionSystem::IPlugin { @@ -50,9 +49,6 @@ public: virtual bool initialize(const QStringList &arguments, QString *errorString); virtual void extensionsInitialized(); - -private: - QmlTaskManager *m_qmlTaskManager; }; } // namespace Internal diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index fbd9076b10..a9eee44e6f 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -27,9 +27,9 @@ ** **************************************************************************/ +#include "qmlprojectrunconfiguration.h" #include "qmlproject.h" #include "qmlprojectmanagerconstants.h" -#include "qmlprojectrunconfiguration.h" #include "qmlprojecttarget.h" #include "projectexplorer/projectexplorer.h" diff --git a/src/plugins/qmlprojectmanager/qmlprojectruncontrol.cpp b/src/plugins/qmlprojectmanager/qmlprojectruncontrol.cpp index 7461ef73f4..59663372e8 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectruncontrol.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectruncontrol.cpp @@ -27,7 +27,6 @@ ** **************************************************************************/ -#include "qmloutputformatter.h" #include "qmlprojectruncontrol.h" #include "qmlprojectrunconfiguration.h" #include "qmlprojectconstants.h" @@ -103,11 +102,6 @@ bool QmlRunControl::isRunning() const return m_applicationLauncher.isRunning(); } -ProjectExplorer::OutputFormatter *QmlRunControl::createOutputFormatter(QObject *parent) -{ - return new QmlOutputFormatter(parent); -} - void QmlRunControl::slotBringApplicationToForeground(qint64 pid) { bringApplicationToForeground(pid); diff --git a/src/plugins/qmlprojectmanager/qmlprojectruncontrol.h b/src/plugins/qmlprojectmanager/qmlprojectruncontrol.h index 89d0d7dc54..7e85370a6e 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectruncontrol.h +++ b/src/plugins/qmlprojectmanager/qmlprojectruncontrol.h @@ -51,8 +51,6 @@ public: virtual void stop(); virtual bool isRunning() const; - virtual ProjectExplorer::OutputFormatter *createOutputFormatter(QObject *parent = 0); - private slots: void processExited(int exitCode); void slotBringApplicationToForeground(qint64 pid); diff --git a/src/plugins/qt4projectmanager/gettingstartedwelcomepagewidget.h b/src/plugins/qt4projectmanager/gettingstartedwelcomepagewidget.h index db2664dee9..e0903d3eed 100644 --- a/src/plugins/qt4projectmanager/gettingstartedwelcomepagewidget.h +++ b/src/plugins/qt4projectmanager/gettingstartedwelcomepagewidget.h @@ -30,7 +30,7 @@ #ifndef GETTINGSTARTEDWELCOMEPAGEWIDGET_H #define GETTINGSTARTEDWELCOMEPAGEWIDGET_H -#include <QWidget> +#include <QtGui/QWidget> namespace Qt4ProjectManager { namespace Internal { diff --git a/src/plugins/qt4projectmanager/profilehighlighter.cpp b/src/plugins/qt4projectmanager/profilehighlighter.cpp index 69a7ca2bf4..cf9e72d2ca 100644 --- a/src/plugins/qt4projectmanager/profilehighlighter.cpp +++ b/src/plugins/qt4projectmanager/profilehighlighter.cpp @@ -157,7 +157,7 @@ static bool isFunction(const QString &word) } ProFileHighlighter::ProFileHighlighter(QTextDocument *document) : - QSyntaxHighlighter(document) + TextEditor::SyntaxHighlighter(document) { } diff --git a/src/plugins/qt4projectmanager/profilehighlighter.h b/src/plugins/qt4projectmanager/profilehighlighter.h index 14f3401451..87dadce27e 100644 --- a/src/plugins/qt4projectmanager/profilehighlighter.h +++ b/src/plugins/qt4projectmanager/profilehighlighter.h @@ -30,6 +30,7 @@ #ifndef PROFILEHIGHLIGHTER_H #define PROFILEHIGHLIGHTER_H +#include <texteditor/syntaxhighlighter.h> #include <QtCore/QtAlgorithms> #include <QtGui/QSyntaxHighlighter> #include <QtGui/QTextCharFormat> @@ -37,7 +38,7 @@ namespace Qt4ProjectManager { namespace Internal { -class ProFileHighlighter : public QSyntaxHighlighter +class ProFileHighlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT public: diff --git a/src/plugins/qt4projectmanager/projectloadwizard.cpp b/src/plugins/qt4projectmanager/projectloadwizard.cpp index 3a5e3986e8..277b3b8ae0 100644 --- a/src/plugins/qt4projectmanager/projectloadwizard.cpp +++ b/src/plugins/qt4projectmanager/projectloadwizard.cpp @@ -35,6 +35,7 @@ #include "makestep.h" #include "qt4buildconfiguration.h" #include "qt4projectmanagerconstants.h" +#include "qtversionmanager.h" #include "wizards/targetsetuppage.h" diff --git a/src/plugins/qt4projectmanager/projectloadwizard.h b/src/plugins/qt4projectmanager/projectloadwizard.h index c05345c981..b6e56d6ce1 100644 --- a/src/plugins/qt4projectmanager/projectloadwizard.h +++ b/src/plugins/qt4projectmanager/projectloadwizard.h @@ -30,17 +30,13 @@ #ifndef PROJECTLOADWIZARD_H #define PROJECTLOADWIZARD_H -#include "qtversionmanager.h" -#include <wizards/targetsetuppage.h> - #include <QtGui/QWizard> namespace Qt4ProjectManager { class Qt4Project; namespace Internal { - -class TargetsPage; +class TargetSetupPage; class ProjectLoadWizard : public QWizard { diff --git a/src/plugins/qt4projectmanager/qmakeparser.h b/src/plugins/qt4projectmanager/qmakeparser.h index 9f6999b14e..0bbd17e30e 100644 --- a/src/plugins/qt4projectmanager/qmakeparser.h +++ b/src/plugins/qt4projectmanager/qmakeparser.h @@ -32,8 +32,6 @@ #include <projectexplorer/ioutputparser.h> -#include <QtCore/QRegExp> - namespace Qt4ProjectManager { namespace Internal { diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp index eddb077acd..575da23144 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.cpp @@ -35,38 +35,14 @@ #include "maemoconfigtestdialog.h" #include "ui_maemoconfigtestdialog.h" -#include "maemosshthread.h" +#include "maemodeviceconfigurations.h" -#include <QtGui/QPushButton> - -namespace { - -/** - * Class that waits until a thread is finished and then deletes it, and then - * schedules itself to be deleted. - */ -class SafeThreadDeleter : public QThread -{ -public: - SafeThreadDeleter(QThread *thread) : m_thread(thread) {} - ~SafeThreadDeleter() { wait(); } - -protected: - void run() - { - // Wait for m_thread to finish and then delete it - m_thread->wait(); - delete m_thread; - - // Schedule this thread for deletion - deleteLater(); - } +#include <coreplugin/ssh/sshconnection.h> +#include <coreplugin/ssh/sshremoteprocess.h> -private: - QThread *m_thread; -}; +#include <QtGui/QPushButton> -} // anonymous namespace +using namespace Core; namespace Qt4ProjectManager { namespace Internal { @@ -75,7 +51,6 @@ MaemoConfigTestDialog::MaemoConfigTestDialog(const MaemoDeviceConfig &config, QW : QDialog(parent) , m_ui(new Ui_MaemoConfigTestDialog) , m_config(config) - , m_deviceTester(0) { setAttribute(Qt::WA_DeleteOnClose); @@ -94,65 +69,86 @@ MaemoConfigTestDialog::~MaemoConfigTestDialog() void MaemoConfigTestDialog::startConfigTest() { - if (m_deviceTester) + if (m_testProcess) return; m_ui->testResultEdit->setPlainText(tr("Testing configuration...")); m_closeButton->setText(tr("Stop Test")); + m_connection = SshConnection::create(); + connect(m_connection.data(), SIGNAL(connected()), this, + SLOT(handleConnected())); + connect(m_connection.data(), SIGNAL(error(SshError)), this, + SLOT(handleConnectionError())); + m_connection->connectToHost(m_config.server); +} +void MaemoConfigTestDialog::handleConnected() +{ + if (!m_connection) + return; QLatin1String sysInfoCmd("uname -rsm"); QLatin1String qtInfoCmd("dpkg -l |grep libqt " "|sed 's/[[:space:]][[:space:]]*/ /g' " "|cut -d ' ' -f 2,3 |sed 's/~.*//g'"); QString command(sysInfoCmd + " && " + qtInfoCmd); - m_deviceTester = new MaemoSshRunner(m_config.server, command); - connect(m_deviceTester, SIGNAL(remoteOutput(QString)), - this, SLOT(processSshOutput(QString))); - connect(m_deviceTester, SIGNAL(finished()), - this, SLOT(handleTestThreadFinished())); + m_testProcess = m_connection->createRemoteProcess(command.toUtf8()); + connect(m_testProcess.data(), SIGNAL(closed(int)), this, + SLOT(handleProcessFinished(int))); + connect(m_testProcess.data(), SIGNAL(outputAvailable(QByteArray)), this, + SLOT(processSshOutput(QByteArray))); + m_testProcess->start(); +} - m_deviceTester->start(); +void MaemoConfigTestDialog::handleConnectionError() +{ + if (!m_connection) + return; + QString output = tr("Could not connect to host: %1") + .arg(m_connection->errorString()); + if (m_config.type == MaemoDeviceConfig::Simulator) + output += tr("\nDid you start Qemu?"); + m_ui->testResultEdit->setPlainText(output); + stopConfigTest(); } -void MaemoConfigTestDialog::handleTestThreadFinished() +void MaemoConfigTestDialog::handleProcessFinished(int exitStatus) { - if (!m_deviceTester) + Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart + || exitStatus == SshRemoteProcess::KilledBySignal + || exitStatus == SshRemoteProcess::ExitedNormally); + + if (!m_testProcess) return; - QString output; - if (m_deviceTester->hasError()) { - output = tr("Device configuration test failed:\n%1").arg(m_deviceTester->error()); - if (m_config.type == MaemoDeviceConfig::Simulator) - output.append(tr("\nDid you start Qemu?")); + if (exitStatus != SshRemoteProcess::ExitedNormally + || m_testProcess->exitCode() != 0) { + m_ui->testResultEdit->setPlainText(tr("Remote process failed: %1") + .arg(m_testProcess->errorString())); } else { - output = parseTestOutput(); + const QString &output = parseTestOutput(); if (!m_qtVersionOk) { m_ui->errorLabel->setText(tr("Qt version mismatch! " " Expected Qt on device: 4.6.2 or later.")); } + m_ui->testResultEdit->setPlainText(output); } - m_ui->testResultEdit->setPlainText(output); stopConfigTest(); } void MaemoConfigTestDialog::stopConfigTest() { - if (m_deviceTester) { - m_deviceTester->disconnect(); // Disconnect signals - m_deviceTester->stop(); + if (m_testProcess) + disconnect(m_testProcess.data(), 0, this, 0); + if (m_connection) + disconnect(m_connection.data(), 0, this, 0); - SafeThreadDeleter *deleter = new SafeThreadDeleter(m_deviceTester); - deleter->start(); - - m_deviceTester = 0; - m_deviceTestOutput.clear(); - m_closeButton->setText(tr("Close")); - } + m_deviceTestOutput.clear(); + m_closeButton->setText(tr("Close")); } -void MaemoConfigTestDialog::processSshOutput(const QString &data) +void MaemoConfigTestDialog::processSshOutput(const QByteArray &output) { - m_deviceTestOutput.append(data); + m_deviceTestOutput.append(QString::fromUtf8(output)); } QString MaemoConfigTestDialog::parseTestOutput() diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h index 8bc2d63930..a6ec321ca2 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoconfigtestdialog.h @@ -35,18 +35,23 @@ #ifndef MAEMOCONFIGTESTDIALOG_H #define MAEMOCONFIGTESTDIALOG_H -#include <QDialog> +#include <QtCore/QSharedPointer> +#include <QtGui/QDialog> QT_BEGIN_NAMESPACE class QPushButton; class Ui_MaemoConfigTestDialog; QT_END_NAMESPACE +namespace Core { + class SshConnection; + class SshRemoteProcess; +} // namespace Core + namespace Qt4ProjectManager { namespace Internal { class MaemoDeviceConfig; -class MaemoSshRunner; /** * A dialog that runs a test of a device configuration. @@ -60,8 +65,10 @@ public: private slots: void stopConfigTest(); - void processSshOutput(const QString &data); - void handleTestThreadFinished(); + void processSshOutput(const QByteArray &output); + void handleConnected(); + void handleConnectionError(); + void handleProcessFinished(int exitStatus); private: void startConfigTest(); @@ -71,7 +78,8 @@ private: QPushButton *m_closeButton; const MaemoDeviceConfig &m_config; - MaemoSshRunner *m_deviceTester; + QSharedPointer<Core::SshConnection> m_connection; + QSharedPointer<Core::SshRemoteProcess> m_testProcess; QString m_deviceTestOutput; bool m_qtVersionOk; }; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.cpp index 203b0333a0..1c4b8fbd5b 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.cpp @@ -42,9 +42,9 @@ #include "maemodeployables.h" #include "maemodeployablelistmodel.h" -#include "maemopackagecreationstep.h" #include "maemotoolchain.h" +#include <projectexplorer/buildstep.h> #include <qt4projectmanager/qt4buildconfiguration.h> #include <qt4projectmanager/qt4nodes.h> #include <qt4projectmanager/qt4project.h> @@ -55,8 +55,8 @@ namespace Qt4ProjectManager { namespace Internal { -MaemoDeployables::MaemoDeployables(MaemoPackageCreationStep *packagingStep) - : m_packagingStep(packagingStep), m_proFilesWatcher(0) +MaemoDeployables::MaemoDeployables(const ProjectExplorer::BuildStep *buildStep) + : m_buildStep(buildStep), m_proFilesWatcher(0) { QTimer::singleShot(0, this, SLOT(createModels())); } @@ -64,8 +64,8 @@ MaemoDeployables::MaemoDeployables(MaemoPackageCreationStep *packagingStep) void MaemoDeployables::createModels() { m_listModels.clear(); - Qt4ProFileNode *rootNode = m_packagingStep->qt4BuildConfiguration() - ->qt4Target()->qt4Project()->rootProjectNode(); + Qt4ProFileNode *rootNode + = qt4BuildConfiguration()->qt4Target()->qt4Project()->rootProjectNode(); createModels(rootNode); if (!m_proFilesWatcher) { m_proFilesWatcher = new Qt4NodesWatcher(this); @@ -84,7 +84,10 @@ void MaemoDeployables::createModels(const Qt4ProFileNode *proFileNode) case ApplicationTemplate: case LibraryTemplate: case ScriptTemplate: { - const QString qConfigFile = m_packagingStep->maemoToolChain()->sysrootRoot() + const MaemoToolChain * const tc + = dynamic_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain()); + Q_ASSERT(tc); + const QString qConfigFile = tc->sysrootRoot() + QLatin1String("/usr/share/qt/mkspecs/qconfig.pri"); m_listModels << new MaemoDeployableListModel(proFileNode, qConfigFile, this); break; @@ -151,5 +154,13 @@ QString MaemoDeployables::remoteExecutableFilePath(const QString &localExecutabl return QString(); } +const Qt4BuildConfiguration *MaemoDeployables::qt4BuildConfiguration() const +{ + const Qt4BuildConfiguration * const bc + = qobject_cast<Qt4BuildConfiguration *>(m_buildStep->buildConfiguration()); + Q_ASSERT(bc); + return bc; +} + } // namespace Qt4ProjectManager } // namespace Internal diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.h index d732882f69..1e46b91d82 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeployables.h @@ -47,10 +47,13 @@ #include <QtCore/QList> #include <QtCore/QObject> +namespace ProjectExplorer { class BuildStep; } + namespace Qt4ProjectManager { namespace Internal { + class MaemoDeployableListModel; -class MaemoPackageCreationStep; +class Qt4BuildConfiguration; class Qt4NodesWatcher; class Qt4ProFileNode; @@ -58,7 +61,7 @@ class MaemoDeployables : public QObject { Q_OBJECT public: - MaemoDeployables(MaemoPackageCreationStep *packagingStep); + MaemoDeployables(const ProjectExplorer::BuildStep *buildStep); void setUnmodified(); bool isModified() const; int deployableCount() const; @@ -73,9 +76,10 @@ signals: private: Q_SLOT void createModels(); void createModels(const Qt4ProFileNode *proFileNode); + const Qt4BuildConfiguration *qt4BuildConfiguration() const; QList<MaemoDeployableListModel *> m_listModels; - MaemoPackageCreationStep * const m_packagingStep; + const ProjectExplorer::BuildStep * const m_buildStep; Qt4NodesWatcher *m_proFilesWatcher; }; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp new file mode 100644 index 0000000000..200923f9ff --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.cpp @@ -0,0 +1,473 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "maemodeploystep.h" + +#include "maemoconstants.h" +#include "maemodeployables.h" +#include "maemodeploystepwidget.h" +#include "maemoglobal.h" +#include "maemopackagecreationstep.h" +#include "maemorunconfiguration.h" + +#include <coreplugin/ssh/sftpchannel.h> +#include <coreplugin/ssh/sshconnection.h> +#include <coreplugin/ssh/sshremoteprocess.h> + +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/target.h> + +#include <QtCore/QCryptographicHash> +#include <QtCore/QEventLoop> +#include <QtCore/QFileInfo> +#include <QtCore/QTimer> + +using namespace Core; +using namespace ProjectExplorer; + +namespace Qt4ProjectManager { +namespace Internal { + +const QLatin1String MaemoDeployStep::Id("Qt4ProjectManager.MaemoDeployStep"); + + +MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc) + : BuildStep(bc, Id), m_deployables(new MaemoDeployables(this)) +{ + ctor(); +} + +MaemoDeployStep::MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc, + MaemoDeployStep *other) + : BuildStep(bc, other), m_deployables(new MaemoDeployables(this)), + m_lastDeployed(other->m_lastDeployed) +{ + ctor(); +} + +MaemoDeployStep::~MaemoDeployStep() +{ + delete m_deployables; +} + +void MaemoDeployStep::ctor() +{ +} + +bool MaemoDeployStep::init() +{ + return true; +} + +void MaemoDeployStep::run(QFutureInterface<bool> &fi) +{ + // Move to GUI thread for connection sharing with run control. + QTimer::singleShot(0, this, SLOT(start())); + + MaemoDeployEventHandler eventHandler(this, fi); + connect (&eventHandler, SIGNAL(destroyed()), this, SLOT(stop())); +} + +BuildStepConfigWidget *MaemoDeployStep::createConfigWidget() +{ + return new MaemoDeployStepWidget(this); +} + +QVariantMap MaemoDeployStep::toMap() const +{ + QVariantMap map(BuildStep::toMap()); + addDeployTimesToMap(map); + return map; +} + +void MaemoDeployStep::addDeployTimesToMap(QVariantMap &map) const +{ + QVariantList hostList; + QVariantList fileList; + QVariantList remotePathList; + QVariantList timeList; + typedef QHash<DeployablePerHost, QDateTime>::ConstIterator DepIt; + for (DepIt it = m_lastDeployed.begin(); it != m_lastDeployed.end(); ++it) { + fileList << it.key().first.localFilePath; + remotePathList << it.key().first.remoteDir; + hostList << it.key().second; + timeList << it.value(); + } + map.insert(LastDeployedHostsKey, hostList); + map.insert(LastDeployedFilesKey, fileList); + map.insert(LastDeployedRemotePathsKey, remotePathList); + map.insert(LastDeployedTimesKey, timeList); +} + +bool MaemoDeployStep::fromMap(const QVariantMap &map) +{ + if (!BuildStep::fromMap(map)) + return false; + getDeployTimesFromMap(map); + return true; +} + +void MaemoDeployStep::getDeployTimesFromMap(const QVariantMap &map) +{ + const QVariantList &hostList = map.value(LastDeployedHostsKey).toList(); + const QVariantList &fileList = map.value(LastDeployedFilesKey).toList(); + const QVariantList &remotePathList + = map.value(LastDeployedRemotePathsKey).toList(); + const QVariantList &timeList = map.value(LastDeployedTimesKey).toList(); + const int elemCount + = qMin(qMin(hostList.size(), fileList.size()), + qMin(remotePathList.size(), timeList.size())); + for (int i = 0; i < elemCount; ++i) { + const MaemoDeployable d(fileList.at(i).toString(), + remotePathList.at(i).toString()); + m_lastDeployed.insert(DeployablePerHost(d, hostList.at(i).toString()), + timeList.at(i).toDateTime()); + } +} + +const MaemoPackageCreationStep *MaemoDeployStep::packagingStep() const +{ + const MaemoPackageCreationStep * const step + = MaemoGlobal::buildStep<MaemoPackageCreationStep>(buildConfiguration()); + Q_ASSERT_X(step, Q_FUNC_INFO, + "Impossible: Maemo build configuration without packaging step."); + return step; +} + +void MaemoDeployStep::raiseError(const QString &errorString) +{ + emit addTask(Task(Task::Error, errorString, QString(), -1, + Constants::TASK_CATEGORY_BUILDSYSTEM)); + stop(); + emit error(); +} + +void MaemoDeployStep::writeOutput(const QString &text, + const QTextCharFormat &format) +{ + emit addOutput(text, format); +} + +void MaemoDeployStep::stop() +{ + if (m_installer && m_installer->isRunning()) { + disconnect(m_installer.data(), 0, this, 0); + } else if (!m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty()) { + m_uploadsInProgress.clear(); + m_linksInProgress.clear(); + disconnect(m_uploader.data(), 0, this, 0); + m_uploader->closeChannel(); + } + if (m_connection) + disconnect(m_connection.data(), 0, this, 0); + m_stopped = true; +} + +QString MaemoDeployStep::uploadDir() const +{ + return MaemoGlobal::homeDirOnDevice(m_connection->connectionParameters().uname); +} + +bool MaemoDeployStep::currentlyNeedsDeployment(const QString &host, + const MaemoDeployable &deployable) const +{ + const QDateTime &lastDeployed + = m_lastDeployed.value(DeployablePerHost(deployable, host)); + return !lastDeployed.isValid() + || QFileInfo(deployable.localFilePath).lastModified() > lastDeployed; +} + +void MaemoDeployStep::setDeployed(const QString &host, + const MaemoDeployable &deployable) +{ + m_lastDeployed.insert(DeployablePerHost(deployable, host), + QDateTime::currentDateTime()); +} + +MaemoDeviceConfig MaemoDeployStep::deviceConfig() const +{ + // TODO: For lib template, get info from config widget + const RunConfiguration * const rc = + buildConfiguration()->target()->activeRunConfiguration(); + return rc ? qobject_cast<const MaemoRunConfiguration *>(rc)->deviceConfig() + : MaemoDeviceConfig(); +} + +void MaemoDeployStep::start() +{ + m_stopped = false; + if (m_stopped) + return; + + // TODO: Re-use if possible (disconnect + reconnect). + if (m_connection) + disconnect(m_connection.data(), 0, this, 0); + m_connection = SshConnection::create(); + + const MaemoDeviceConfig &devConfig = deviceConfig(); + if (!devConfig.isValid()) { + raiseError(tr("Deployment failed: No valid device set.")); + return; + } + connect(m_connection.data(), SIGNAL(connected()), this, + SLOT(handleConnected())); + connect(m_connection.data(), SIGNAL(error(SshError)), this, + SLOT(handleConnectionFailure())); + m_connection->connectToHost(devConfig.server); +} + +void MaemoDeployStep::handleConnected() +{ + if (m_stopped) + return; + + // TODO: If nothing to deploy, skip this step. + m_uploader = m_connection->createSftpChannel(); + connect(m_uploader.data(), SIGNAL(initialized()), this, + SLOT(handleSftpChannelInitialized())); + connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this, + SLOT(handleSftpChannelInitializationFailed(QString))); + connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)), + this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString))); + m_uploader->initialize(); +} + +void MaemoDeployStep::handleConnectionFailure() +{ + if (m_stopped) + return; + + raiseError(tr("Could not connect to host: %1") + .arg(m_connection->errorString())); +} + +void MaemoDeployStep::handleSftpChannelInitialized() +{ + if (m_stopped) + return; + + m_uploadsInProgress.clear(); + m_linksInProgress.clear(); + m_needsInstall = false; + const MaemoPackageCreationStep * const pStep = packagingStep(); + const QString hostName = m_connection->connectionParameters().host; + if (pStep->isPackagingEnabled()) { + const MaemoDeployable d(pStep->packageFilePath(), uploadDir()); + if (currentlyNeedsDeployment(hostName, d)) { + if (!deploy(MaemoDeployable(d))) + return; + m_needsInstall = true; + } else { + m_needsInstall = false; + } + } else { + const int deployableCount = m_deployables->deployableCount(); + for (int i = 0; i < deployableCount; ++i) { + const MaemoDeployable &d = m_deployables->deployableAt(i); + if (currentlyNeedsDeployment(hostName, d) + && !deploy(MaemoDeployable(d))) + return; + } + m_needsInstall = false; + } + if (m_uploadsInProgress.isEmpty()) + emit done(); +} + +bool MaemoDeployStep::deploy(const MaemoDeployable &deployable) +{ + const QString fileName = QFileInfo(deployable.localFilePath).fileName(); + const QString remoteFilePath = deployable.remoteDir + '/' + fileName; + const QString uploadFilePath = uploadDir() + '/' + fileName + '.' + + QCryptographicHash::hash(remoteFilePath.toUtf8(), + QCryptographicHash::Md5).toHex(); + const SftpJobId job = m_uploader->uploadFile(deployable.localFilePath, + uploadFilePath, SftpOverwriteExisting); + if (job == SftpInvalidJob) { + raiseError(tr("Upload failed: Could not open file '%1'") + .arg(deployable.localFilePath)); + return false; + } + writeOutput(tr("Started uploading file '%1'.").arg(deployable.localFilePath)); + m_uploadsInProgress.insert(job, DeployInfo(deployable, uploadFilePath)); + return true; +} + +void MaemoDeployStep::handleSftpChannelInitializationFailed(const QString &error) +{ + if (m_stopped) + return; + raiseError(tr("Could not set up SFTP connection: %1").arg(error)); +} + +void MaemoDeployStep::handleSftpJobFinished(Core::SftpJobId job, + const QString &error) +{ + if (m_stopped) + return; + + QMap<SftpJobId, DeployInfo>::Iterator it = m_uploadsInProgress.find(job); + if (it == m_uploadsInProgress.end()) { + qWarning("%s: Job %u not found in map.", Q_FUNC_INFO, job); + return; + } + + const DeployInfo &deployInfo = it.value(); + if (!error.isEmpty()) { + raiseError(tr("Failed to upload file %1: %2") + .arg(deployInfo.first.localFilePath, error)); + return; + } + + writeOutput(tr("Successfully uploaded file '%1'.") + .arg(deployInfo.first.localFilePath)); + const QString remoteFilePath = deployInfo.first.remoteDir + '/' + + QFileInfo(deployInfo.first.localFilePath).fileName(); + QByteArray linkCommand = MaemoGlobal::remoteSudo().toUtf8() + " ln -sf " + + deployInfo.second.toUtf8() + ' ' + remoteFilePath.toUtf8(); + SshRemoteProcess::Ptr linkProcess + = m_connection->createRemoteProcess(linkCommand); + connect(linkProcess.data(), SIGNAL(closed(int)), this, + SLOT(handleLinkProcessFinished(int))); + m_linksInProgress.insert(linkProcess, deployInfo.first); + linkProcess->start(); + m_uploadsInProgress.erase(it); +} + +void MaemoDeployStep::handleLinkProcessFinished(int exitStatus) +{ + if (m_stopped) + return; + + SshRemoteProcess * const proc = static_cast<SshRemoteProcess *>(sender()); + + // TODO: List instead of map? We can't use it for lookup anyway. + QMap<SshRemoteProcess::Ptr, MaemoDeployable>::Iterator it; + for (it = m_linksInProgress.begin(); it != m_linksInProgress.end(); ++it) { + if (it.key().data() == proc) + break; + } + if (it == m_linksInProgress.end()) { + qWarning("%s: Remote process %p not found in process list.", + Q_FUNC_INFO, proc); + return; + } + + const MaemoDeployable &deployable = it.value(); + if (exitStatus != SshRemoteProcess::ExitedNormally + || proc->exitCode() != 0) { + raiseError(tr("Deployment failed for file '%1': " + "Could not create link '%2' on remote system.") + .arg(deployable.localFilePath, deployable.remoteDir + '/' + + QFileInfo(deployable.localFilePath).fileName())); + return; + } + + setDeployed(m_connection->connectionParameters().host, it.value()); + m_linksInProgress.erase(it); + if (m_linksInProgress.isEmpty() && m_uploadsInProgress.isEmpty()) { + if (m_needsInstall) { + writeOutput(tr("Installing package ...")); + const QString packageFileName + = QFileInfo(packagingStep()->packageFilePath()).fileName(); + const QByteArray cmd = MaemoGlobal::remoteSudo().toUtf8() + + " dpkg -i " + packageFileName.toUtf8(); + m_installer = m_connection->createRemoteProcess(cmd); + connect(m_installer.data(), SIGNAL(closed(int)), this, + SLOT(handleInstallationFinished(int))); + connect(m_installer.data(), SIGNAL(outputAvailable(QByteArray)), + this, SLOT(handleInstallerOutput(QByteArray))); + connect(m_installer.data(), + SIGNAL(errorOutputAvailable(QByteArray)), this, + SLOT(handleInstallerErrorOutput(QByteArray))); + m_installer->start(); + } else { + emit done(); + } + } +} + +void MaemoDeployStep::handleInstallationFinished(int exitStatus) +{ + if (m_stopped) + return; + + if (exitStatus != SshRemoteProcess::ExitedNormally + || m_installer->exitCode() != 0) { + raiseError(tr("Installing package failed.")); + } else { + writeOutput(tr("Package installation finished.")); + emit done(); + } +} + +void MaemoDeployStep::handleInstallerOutput(const QByteArray &output) +{ + writeOutput(QString::fromUtf8(output)); +} + +void MaemoDeployStep::handleInstallerErrorOutput(const QByteArray &output) +{ + QTextCharFormat format; + format.setForeground(QBrush(QColor("red"))); + writeOutput(output, format); +} + + +MaemoDeployEventHandler::MaemoDeployEventHandler(MaemoDeployStep *deployStep, + QFutureInterface<bool> &future) + : m_deployStep(deployStep), m_future(future), m_eventLoop(new QEventLoop) +{ + connect(m_deployStep, SIGNAL(done()), this, SLOT(handleDeployingDone())); + connect(m_deployStep, SIGNAL(error()), this, SLOT(handleDeployingFailed())); + QTimer cancelChecker; + connect(&cancelChecker, SIGNAL(timeout()), this, SLOT(checkForCanceled())); + cancelChecker.start(500); + future.reportResult(m_eventLoop->exec() == 0); +} + +void MaemoDeployEventHandler::handleDeployingDone() +{ + m_eventLoop->exit(0); +} + +void MaemoDeployEventHandler::handleDeployingFailed() +{ + m_eventLoop->exit(1); +} + +void MaemoDeployEventHandler::checkForCanceled() +{ + if (m_future.isCanceled()) + handleDeployingFailed(); +} + +} // namespace Internal +} // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h new file mode 100644 index 0000000000..2363e1ea85 --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystep.h @@ -0,0 +1,146 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef MAEMODEPLOYSTEP_H +#define MAEMODEPLOYSTEP_H + +#include "maemodeployable.h" +#include "maemodeviceconfigurations.h" + +#include <coreplugin/ssh/sftpdefs.h> +#include <projectexplorer/buildstep.h> + +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QPair> +#include <QtCore/QSharedPointer> + +QT_BEGIN_NAMESPACE +class QEventLoop; +QT_END_NAMESPACE + +namespace Core { +class SftpChannel; +class SshConnection; +class SshRemoteProcess; +} + +namespace Qt4ProjectManager { +namespace Internal { +class MaemoDeployables; +class MaemoPackageCreationStep; + +class MaemoDeployStep : public ProjectExplorer::BuildStep +{ + Q_OBJECT + friend class MaemoDeployStepFactory; +public: + MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc); + virtual ~MaemoDeployStep(); + MaemoDeviceConfig deviceConfig() const; + bool currentlyNeedsDeployment(const QString &host, + const MaemoDeployable &deployable) const; + void setDeployed(const QString &host, const MaemoDeployable &deployable); + MaemoDeployables *deployables() const { return m_deployables; } + +signals: + void done(); + void error(); + +private slots: + void start(); + void stop(); + void handleConnected(); + void handleConnectionFailure(); + void handleSftpChannelInitialized(); + void handleSftpChannelInitializationFailed(const QString &error); + void handleSftpJobFinished(Core::SftpJobId job, const QString &error); + void handleLinkProcessFinished(int exitStatus); + void handleInstallationFinished(int exitStatus); + void handleInstallerOutput(const QByteArray &output); + void handleInstallerErrorOutput(const QByteArray &output); + +private: + MaemoDeployStep(ProjectExplorer::BuildConfiguration *bc, + MaemoDeployStep *other); + virtual bool init(); + virtual void run(QFutureInterface<bool> &fi); + virtual ProjectExplorer::BuildStepConfigWidget *createConfigWidget(); + virtual bool immutable() const { return true; } + virtual QVariantMap toMap() const; + virtual bool fromMap(const QVariantMap &map); + + void ctor(); + void raiseError(const QString &error); + void writeOutput(const QString &text, + const QTextCharFormat &format = QTextCharFormat()); + void addDeployTimesToMap(QVariantMap &map) const; + void getDeployTimesFromMap(const QVariantMap &map); + const MaemoPackageCreationStep *packagingStep() const; + bool deploy(const MaemoDeployable &deployable); + QString uploadDir() const; + + static const QLatin1String Id; + + MaemoDeployables * const m_deployables; + QSharedPointer<Core::SshConnection> m_connection; + QSharedPointer<Core::SftpChannel> m_uploader; + QSharedPointer<Core::SshRemoteProcess> m_installer; + typedef QPair<MaemoDeployable, QString> DeployInfo; + QMap<Core::SftpJobId, DeployInfo> m_uploadsInProgress; + QMap<QSharedPointer<Core::SshRemoteProcess>, MaemoDeployable> m_linksInProgress; + bool m_needsInstall; + bool m_stopped; + typedef QPair<MaemoDeployable, QString> DeployablePerHost; + QHash<DeployablePerHost, QDateTime> m_lastDeployed; +}; + +class MaemoDeployEventHandler : public QObject +{ + Q_OBJECT +public: + MaemoDeployEventHandler(MaemoDeployStep *deployStep, + QFutureInterface<bool> &future); + +private slots: + void handleDeployingDone(); + void handleDeployingFailed(); + void checkForCanceled(); + +private: + const MaemoDeployStep * const m_deployStep; + const QFutureInterface<bool> m_future; + QEventLoop * const m_eventLoop; +}; + +} // namespace Internal +} // namespace Qt4ProjectManager + +#endif // MAEMODEPLOYSTEP_H diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp new file mode 100644 index 0000000000..2d1e712e1a --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp @@ -0,0 +1,82 @@ +#include "maemodeploystepfactory.h" + +#include "maemodeploystep.h" + +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/target.h> +#include <qt4projectmanager/qt4projectmanagerconstants.h> + +using namespace ProjectExplorer; + +namespace Qt4ProjectManager { +namespace Internal { + +MaemoDeployStepFactory::MaemoDeployStepFactory(QObject *parent) + : IBuildStepFactory(parent) +{ +} + +QStringList MaemoDeployStepFactory::availableCreationIds(BuildConfiguration *, + BuildStep::Type) const +{ + return QStringList(); +} + +QString MaemoDeployStepFactory::displayNameForId(const QString &) const +{ + return QString(); +} + +bool MaemoDeployStepFactory::canCreate(BuildConfiguration *, + BuildStep::Type, const QString &) const +{ + return false; +} + +BuildStep *MaemoDeployStepFactory::create(BuildConfiguration *, + BuildStep::Type, const QString &) +{ + Q_ASSERT(false); + return 0; +} + +bool MaemoDeployStepFactory::canRestore(BuildConfiguration *parent, + BuildStep::Type type, const QVariantMap &map) const +{ + return canCreateInternally(parent, type, idFromMap(map)); +} + +BuildStep *MaemoDeployStepFactory::restore(BuildConfiguration *parent, + BuildStep::Type type, const QVariantMap &map) +{ + Q_ASSERT(canRestore(parent, type, map)); + MaemoDeployStep * const step = new MaemoDeployStep(parent); + if (!step->fromMap(map)) { + delete step; + return 0; + } + return step; +} + +bool MaemoDeployStepFactory::canClone(BuildConfiguration *parent, + BuildStep::Type type, BuildStep *product) const +{ + return canCreateInternally(parent, type, product->id()); +} + +BuildStep *MaemoDeployStepFactory::clone(BuildConfiguration *parent, + BuildStep::Type type, BuildStep *product) +{ + Q_ASSERT(canClone(parent, type, product)); + return new MaemoDeployStep(parent, static_cast<MaemoDeployStep*>(product)); +} + +bool MaemoDeployStepFactory::canCreateInternally(BuildConfiguration *parent, + BuildStep::Type type, const QString &id) const +{ + return type == BuildStep::Deploy && id == MaemoDeployStep::Id + && parent->target()->id() == Constants::MAEMO_DEVICE_TARGET_ID; +} + +} // namespace Internal +} // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.h new file mode 100644 index 0000000000..0516578673 --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.h @@ -0,0 +1,49 @@ +#ifndef MAEMODEPLOYSTEPFACTORY_H +#define MAEMODEPLOYSTEPFACTORY_H + +#include <projectexplorer/buildstep.h> + +namespace Qt4ProjectManager { +namespace Internal { + +class MaemoDeployStepFactory : public ProjectExplorer::IBuildStepFactory +{ +public: + MaemoDeployStepFactory(QObject *parent); + + virtual QStringList availableCreationIds(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type) const; + virtual QString displayNameForId(const QString &id) const; + + virtual bool canCreate(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, + const QString &id) const; + virtual ProjectExplorer::BuildStep * + create(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, const QString &id); + + virtual bool canRestore(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, + const QVariantMap &map) const; + virtual ProjectExplorer::BuildStep * + restore(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, const QVariantMap &map); + + virtual bool canClone(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, + ProjectExplorer::BuildStep *product) const; + virtual ProjectExplorer::BuildStep * + clone(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, + ProjectExplorer::BuildStep *product); + +private: + bool canCreateInternally(ProjectExplorer::BuildConfiguration *parent, + ProjectExplorer::BuildStep::Type type, + const QString &id) const; +}; + +} // namespace Internal +} // namespace Qt4ProjectManager + +#endif // MAEMODEPLOYSTEPFACTORY_H diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp new file mode 100644 index 0000000000..33c61dc9de --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.cpp @@ -0,0 +1,72 @@ +#include "maemodeploystepwidget.h" +#include "ui_maemodeploystepwidget.h" + +#include "maemodeploystep.h" +#include "maemodeployablelistmodel.h" +#include "maemodeployablelistwidget.h" +#include "maemodeployables.h" +#include "maemorunconfiguration.h" + +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/target.h> + +namespace Qt4ProjectManager { +namespace Internal { + +MaemoDeployStepWidget::MaemoDeployStepWidget(MaemoDeployStep *step) : + ProjectExplorer::BuildStepConfigWidget(), + ui(new Ui::MaemoDeployStepWidget), + m_step(step) +{ + ui->setupUi(this); + + connect(m_step->deployables(), SIGNAL(modelsCreated()), this, + SLOT(handleModelsCreated())); + handleModelsCreated(); +} + +MaemoDeployStepWidget::~MaemoDeployStepWidget() +{ + delete ui; +} + +void MaemoDeployStepWidget::init() +{ + const ProjectExplorer::RunConfiguration * const rc = + m_step->buildConfiguration()->target()->activeRunConfiguration(); + if (rc) { + connect(qobject_cast<const MaemoRunConfiguration *>(rc), + SIGNAL(deviceConfigurationChanged(ProjectExplorer::Target *)), + this, SLOT(handleDeviceUpdate())); + } +} + +void MaemoDeployStepWidget::handleDeviceUpdate() +{ + emit updateSummary(); +} + +QString MaemoDeployStepWidget::summaryText() const +{ + return tr("<b>Deploy to device</b>: ") + m_step->deviceConfig().name; +} + +QString MaemoDeployStepWidget::displayName() const +{ + return QString(); +} + + +void MaemoDeployStepWidget::handleModelsCreated() +{ + ui->tabWidget->clear(); + for (int i = 0; i < m_step->deployables()->modelCount(); ++i) { + MaemoDeployableListModel * const model + = m_step->deployables()->modelAt(i); + ui->tabWidget->addTab(new MaemoDeployableListWidget(this, model), + model->projectName()); + } +} + +} // namespace Internal +} // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h new file mode 100644 index 0000000000..bfa631fda5 --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.h @@ -0,0 +1,39 @@ +#ifndef MAEMODEPLOYSTEPWIDGET_H +#define MAEMODEPLOYSTEPWIDGET_H + +#include <projectexplorer/buildstep.h> + +QT_BEGIN_NAMESPACE +namespace Ui { + class MaemoDeployStepWidget; +} +QT_END_NAMESPACE + +namespace Qt4ProjectManager { +namespace Internal { +class MaemoDeployStep; + +class MaemoDeployStepWidget : public ProjectExplorer::BuildStepConfigWidget +{ + Q_OBJECT + +public: + MaemoDeployStepWidget(MaemoDeployStep *step); + ~MaemoDeployStepWidget(); + +private: + Q_SLOT void handleModelsCreated(); + virtual void init(); + virtual QString summaryText() const; + virtual QString displayName() const; + + Q_SLOT void handleDeviceUpdate(); + + Ui::MaemoDeployStepWidget *ui; + MaemoDeployStep * const m_step; +}; + +} // namespace Internal +} // namespace Qt4ProjectManager + +#endif // MAEMODEPLOYSTEPWIDGET_H diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.ui b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.ui new file mode 100644 index 0000000000..b0cc2d6408 --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepwidget.ui @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MaemoDeployStepWidget</class> + <widget class="QWidget" name="MaemoDeployStepWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="toolTip"> + <string>These show the INSTALLS settings from the project file(s).</string> + </property> + <property name="text"> + <string><b>Files to install:</b></string> + </property> + </widget> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"/> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp index 7ff2747ddf..0749e00e1c 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.cpp @@ -36,24 +36,18 @@ #include <coreplugin/icore.h> +#include <QtCore/QCoreApplication> #include <QtCore/QSettings> #include <QtCore/QStringBuilder> #include <QtGui/QDesktopServices> #include <algorithm> -typedef Core::SshServerInfo::AuthType AuthType; +typedef Core::SshConnectionParameters::AuthType AuthType; namespace Qt4ProjectManager { namespace Internal { -QString homeDirOnDevice(const QString &uname) -{ - return uname == QLatin1String("root") - ? QString::fromLatin1("/root") - : QLatin1String("/home/") + uname; -} - namespace { const QLatin1String SettingsGroup("MaemoDeviceConfigs"); const QLatin1String IdCounterKey("IdCounter"); @@ -80,7 +74,7 @@ namespace { const QString DefaultHostNameHW(QLatin1String("192.168.2.15")); const QString DefaultHostNameSim(QLatin1String("localhost")); const QString DefaultUserName(QLatin1String("developer")); - const AuthType DefaultAuthType(Core::SshServerInfo::AuthByKey); + const AuthType DefaultAuthType(Core::SshConnectionParameters::AuthByKey); const int DefaultTimeout(30); const MaemoDeviceConfig::DeviceType DefaultDeviceType(MaemoDeviceConfig::Physical); }; @@ -132,7 +126,8 @@ MaemoDeviceConfig::MaemoDeviceConfig(const QSettings &settings, } MaemoDeviceConfig::MaemoDeviceConfig() - : internalId(InvalidId) + : name(QCoreApplication::translate("MaemoDeviceConfig", "(Invalid device)")), + internalId(InvalidId) { } diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h index 7ffa28251e..463911f817 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeviceconfigurations.h @@ -48,8 +48,6 @@ QT_END_NAMESPACE namespace Qt4ProjectManager { namespace Internal { -QString homeDirOnDevice(const QString &uname); - class MaemoDeviceConfig { public: @@ -60,7 +58,7 @@ public: void save(QSettings &settings) const; bool isValid() const; - Core::SshServerInfo server; + Core::SshConnectionParameters server; QString name; DeviceType type; int gdbServerPort; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.cpp new file mode 100644 index 0000000000..9a797fd7ad --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.cpp @@ -0,0 +1,50 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "maemoglobal.h" + +#include <QtCore/QString> + +namespace Qt4ProjectManager { +namespace Internal { + +QString MaemoGlobal::homeDirOnDevice(const QString &uname) +{ + return uname == QLatin1String("root") + ? QString::fromLatin1("/root") + : QLatin1String("/home/") + uname; +} + +QString MaemoGlobal::remoteSudo() +{ + return QLatin1String("/usr/lib/mad-developer/devrootsh"); +} + +} // namespace Internal +} // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.h b/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.h new file mode 100644 index 0000000000..7a8b2ff8df --- /dev/null +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoglobal.h @@ -0,0 +1,65 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef MAEMOGLOBAL_H +#define MAEMOGLOBAL_H + +#include <projectexplorer/buildconfiguration.h> + +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace Qt4ProjectManager { +namespace Internal { + +class MaemoGlobal +{ +public: + static QString homeDirOnDevice(const QString &uname); + static QString remoteSudo(); + + template<class T> static T *buildStep(const ProjectExplorer::BuildConfiguration *bc) + { + const QList<ProjectExplorer::BuildStep *> &buildSteps + = bc->steps(ProjectExplorer::BuildStep::Deploy); + for (int i = buildSteps.count() - 1; i >= 0; --i) { + if (T * const step = qobject_cast<T *>(buildSteps.at(i))) + return step; + } + return 0; + } +}; + +} // namespace Internal +} // namespace Qt4ProjectManager + +#endif // MAEMOGLOBAL_H diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemomanager.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemomanager.cpp index 60f6a80fb5..eb36e1f79f 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemomanager.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemomanager.cpp @@ -30,6 +30,7 @@ #include "maemomanager.h" #include "maemoconstants.h" +#include "maemodeploystepfactory.h" #include "maemodeviceconfigurations.h" #include "maemopackagecreationfactory.h" #include "maemorunfactories.h" @@ -57,6 +58,7 @@ MaemoManager::MaemoManager() , m_runControlFactory(new MaemoRunControlFactory(this)) , m_runConfigurationFactory(new MaemoRunConfigurationFactory(this)) , m_packageCreationFactory(new MaemoPackageCreationFactory(this)) + , m_deployStepFactory(new MaemoDeployStepFactory(this)) , m_settingsPage(new MaemoSettingsPage(this)) { Q_ASSERT(!m_instance); @@ -69,6 +71,7 @@ MaemoManager::MaemoManager() pluginManager->addObject(m_runControlFactory); pluginManager->addObject(m_runConfigurationFactory); pluginManager->addObject(m_packageCreationFactory); + pluginManager->addObject(m_deployStepFactory); pluginManager->addObject(m_settingsPage); } @@ -77,6 +80,7 @@ MaemoManager::~MaemoManager() PluginManager *pluginManager = PluginManager::instance(); pluginManager->removeObject(m_runControlFactory); pluginManager->removeObject(m_runConfigurationFactory); + pluginManager->removeObject(m_deployStepFactory); pluginManager->removeObject(m_packageCreationFactory); pluginManager->removeObject(m_settingsPage); diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemomanager.h b/src/plugins/qt4projectmanager/qt-maemo/maemomanager.h index 689fbfa27f..865098d0f3 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemomanager.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemomanager.h @@ -41,6 +41,7 @@ namespace Qt4ProjectManager { class QtVersion; namespace Internal { +class MaemoDeployStepFactory; class MaemoPackageCreationFactory; class MaemoRunControlFactory; class MaemoRunConfigurationFactory; @@ -67,6 +68,7 @@ private: MaemoRunControlFactory *m_runControlFactory; MaemoRunConfigurationFactory *m_runConfigurationFactory; MaemoPackageCreationFactory *m_packageCreationFactory; + MaemoDeployStepFactory *m_deployStepFactory; MaemoSettingsPage *m_settingsPage; QemuRuntimeManager *m_qemuRuntimeManager; }; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.cpp index 76ac7327a3..3d8b061557 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.cpp @@ -42,8 +42,10 @@ #include "maemopackagecreationstep.h" #include "maemoconstants.h" +#include "maemoglobal.h" #include "maemopackagecreationwidget.h" #include "maemodeployables.h" +#include "maemodeploystep.h" #include "maemotoolchain.h" #include "profilewrapper.h" @@ -73,7 +75,6 @@ namespace Internal { MaemoPackageCreationStep::MaemoPackageCreationStep(BuildConfiguration *buildConfig) : ProjectExplorer::BuildStep(buildConfig, CreatePackageId), - m_deployables(new MaemoDeployables(this)), m_packagingEnabled(true), m_versionString(DefaultVersionNumber) { @@ -82,7 +83,6 @@ MaemoPackageCreationStep::MaemoPackageCreationStep(BuildConfiguration *buildConf MaemoPackageCreationStep::MaemoPackageCreationStep(BuildConfiguration *buildConfig, MaemoPackageCreationStep *other) : BuildStep(buildConfig, other), - m_deployables(new MaemoDeployables(this)), m_packagingEnabled(other->m_packagingEnabled), m_versionString(other->m_versionString) { @@ -90,7 +90,6 @@ MaemoPackageCreationStep::MaemoPackageCreationStep(BuildConfiguration *buildConf MaemoPackageCreationStep::~MaemoPackageCreationStep() { - delete m_deployables; } bool MaemoPackageCreationStep::init() @@ -245,7 +244,7 @@ bool MaemoPackageCreationStep::createPackage() } emit addOutput(tr("Package created."), textCharFormat); - m_deployables->setUnmodified(); + deployStep()->deployables()->setUnmodified(); return true; } @@ -313,6 +312,15 @@ const MaemoToolChain *MaemoPackageCreationStep::maemoToolChain() const return static_cast<MaemoToolChain *>(qt4BuildConfiguration()->toolChain()); } +MaemoDeployStep *MaemoPackageCreationStep::deployStep() const +{ + MaemoDeployStep * const deployStep + = MaemoGlobal::buildStep<MaemoDeployStep>(buildConfiguration()); + Q_ASSERT(deployStep && + "Fatal error: Maemo build configuration without deploy step."); + return deployStep; +} + QString MaemoPackageCreationStep::maddeRoot() const { return maemoToolChain()->maddeRoot(); @@ -325,14 +333,15 @@ QString MaemoPackageCreationStep::targetRoot() const bool MaemoPackageCreationStep::packagingNeeded() const { + const MaemoDeployables * const deployables = deployStep()->deployables(); QFileInfo packageInfo(packageFilePath()); - if (!packageInfo.exists() || m_deployables->isModified()) + if (!packageInfo.exists() || deployables->isModified()) return true; - const int deployableCount = m_deployables->deployableCount(); + const int deployableCount = deployables->deployableCount(); for (int i = 0; i < deployableCount; ++i) { if (packageInfo.lastModified() - <= QFileInfo(m_deployables->deployableAt(i).localFilePath) + <= QFileInfo(deployables->deployableAt(i).localFilePath) .lastModified()) return true; } diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.h b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.h index 6b6cbae768..9128b45947 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationstep.h @@ -45,7 +45,6 @@ #include <projectexplorer/buildstep.h> #include <QtCore/QScopedPointer> -#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QFile; @@ -55,7 +54,7 @@ QT_END_NAMESPACE namespace Qt4ProjectManager { namespace Internal { -class MaemoDeployables; +class MaemoDeployStep; class MaemoToolChain; class ProFileWrapper; class Qt4BuildConfiguration; @@ -69,9 +68,6 @@ public: ~MaemoPackageCreationStep(); QString packageFilePath() const; - MaemoDeployables *deployables() const { return m_deployables; } - const Qt4BuildConfiguration *qt4BuildConfiguration() const; - const MaemoToolChain *maemoToolChain() const; bool isPackagingEnabled() const { return m_packagingEnabled; } void setPackagingEnabled(bool enabled) { m_packagingEnabled = enabled; } @@ -103,10 +99,12 @@ private: const QString &detailedMsg = QString()); QString buildDirectory() const; QString projectName() const; + const Qt4BuildConfiguration *qt4BuildConfiguration() const; + const MaemoToolChain *maemoToolChain() const; + MaemoDeployStep * deployStep() const; static const QLatin1String CreatePackageId; - MaemoDeployables * const m_deployables; bool m_packagingEnabled; QString m_versionString; QScopedPointer<QProcess> m_buildProc; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.cpp index 6c0c5cf13f..fae213969f 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.cpp @@ -42,11 +42,7 @@ #include "maemopackagecreationwidget.h" #include "ui_maemopackagecreationwidget.h" -#include "maemodeployablelistmodel.h" -#include "maemodeployablelistwidget.h" -#include "maemodeployables.h" #include "maemopackagecreationstep.h" -#include "maemotoolchain.h" #include <utils/qtcassert.h> #include <projectexplorer/project.h> @@ -70,10 +66,6 @@ MaemoPackageCreationWidget::MaemoPackageCreationWidget(MaemoPackageCreationStep m_ui->minor->setValue(list.value(1, QLatin1String("0")).toInt()); m_ui->patch->setValue(list.value(2, QLatin1String("0")).toInt()); versionInfoChanged(); - - connect(m_step->deployables(), SIGNAL(modelsCreated()), this, - SLOT(handleModelsCreated())); - handleModelsCreated(); } void MaemoPackageCreationWidget::init() @@ -92,6 +84,9 @@ QString MaemoPackageCreationWidget::displayName() const void MaemoPackageCreationWidget::handleSkipButtonToggled(bool checked) { + m_ui->major->setEnabled(!checked); + m_ui->minor->setEnabled(!checked); + m_ui->patch->setEnabled(!checked); m_step->setPackagingEnabled(!checked); } @@ -102,16 +97,5 @@ void MaemoPackageCreationWidget::versionInfoChanged() emit updateSummary(); } -void MaemoPackageCreationWidget::handleModelsCreated() -{ - m_ui->tabWidget->clear(); - for (int i = 0; i < m_step->deployables()->modelCount(); ++i) { - MaemoDeployableListModel * const model - = m_step->deployables()->modelAt(i); - m_ui->tabWidget->addTab(new MaemoDeployableListWidget(this, model), - model->projectName()); - } -} - } // namespace Internal } // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.h b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.h index 560a9ac1ee..6f01bdeaa7 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.h @@ -66,7 +66,6 @@ public: private slots: void handleSkipButtonToggled(bool checked); void versionInfoChanged(); - void handleModelsCreated(); private: MaemoPackageCreationStep * const m_step; diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.ui b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.ui index 8e3b923136..f415839e03 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.ui +++ b/src/plugins/qt4projectmanager/qt-maemo/maemopackagecreationwidget.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>478</width> - <height>335</height> + <width>453</width> + <height>116</height> </rect> </property> <property name="sizePolicy"> @@ -178,24 +178,17 @@ </layout> </item> <item> - <widget class="QLabel" name="contentsLabel"> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string>Files to deploy:</string> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> - </widget> - </item> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>-1</number> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> </property> - </widget> + </spacer> </item> </layout> </widget> diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp index 1aaf817a1f..6b09682b13 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.cpp @@ -29,10 +29,13 @@ #include "maemorunconfiguration.h" +#include "maemodeploystep.h" +#include "maemoglobal.h" #include "maemopackagecreationstep.h" #include "maemorunconfigurationwidget.h" #include "maemotoolchain.h" #include "qemuruntimemanager.h" +#include "qtoutputformatter.h" #include <coreplugin/icore.h> #include <coreplugin/messagemanager.h> @@ -48,7 +51,7 @@ #include <QtCore/QStringBuilder> namespace Qt4ProjectManager { - namespace Internal { +namespace Internal { using namespace ProjectExplorer; @@ -67,7 +70,6 @@ MaemoRunConfiguration::MaemoRunConfiguration(Qt4Target *parent, , m_gdbPath(source->m_gdbPath) , m_devConfig(source->m_devConfig) , m_arguments(source->m_arguments) - , m_lastDeployed(source->m_lastDeployed) { init(); } @@ -112,6 +114,11 @@ QWidget *MaemoRunConfiguration::createConfigurationWidget() return new MaemoRunConfigurationWidget(this); } +ProjectExplorer::OutputFormatter *MaemoRunConfiguration::createConfigurationWidget() const +{ + return new QtOutputFormatter(qt4Target()->qt4Project()); +} + void MaemoRunConfiguration::proFileUpdate(Qt4ProjectManager::Internal::Qt4ProFileNode *pro) { if (m_proFilePath == pro->path()) @@ -123,32 +130,11 @@ QVariantMap MaemoRunConfiguration::toMap() const QVariantMap map(RunConfiguration::toMap()); map.insert(DeviceIdKey, m_devConfig.internalId); map.insert(ArgumentsKey, m_arguments); - addDeployTimesToMap(map); const QDir dir = QDir(target()->project()->projectDirectory()); map.insert(ProFileKey, dir.relativeFilePath(m_proFilePath)); - return map; } -void MaemoRunConfiguration::addDeployTimesToMap(QVariantMap &map) const -{ - QVariantList hostList; - QVariantList fileList; - QVariantList remotePathList; - QVariantList timeList; - typedef QHash<DeployablePerHost, QDateTime>::ConstIterator DepIt; - for (DepIt it = m_lastDeployed.begin(); it != m_lastDeployed.end(); ++it) { - hostList << it.key().first.localFilePath; - remotePathList << it.key().first.remoteDir; - fileList << it.key().second; - timeList << it.value(); - } - map.insert(LastDeployedHostsKey, hostList); - map.insert(LastDeployedFilesKey, fileList); - map.insert(LastDeployedRemotePathsKey, remotePathList); - map.insert(LastDeployedTimesKey, timeList); -} - bool MaemoRunConfiguration::fromMap(const QVariantMap &map) { if (!RunConfiguration::fromMap(map)) @@ -157,47 +143,12 @@ bool MaemoRunConfiguration::fromMap(const QVariantMap &map) setDeviceConfig(MaemoDeviceConfigurations::instance(). find(map.value(DeviceIdKey, 0).toInt())); m_arguments = map.value(ArgumentsKey).toStringList(); - getDeployTimesFromMap(map); const QDir dir = QDir(target()->project()->projectDirectory()); m_proFilePath = dir.filePath(map.value(ProFileKey).toString()); return true; } -void MaemoRunConfiguration::getDeployTimesFromMap(const QVariantMap &map) -{ - const QVariantList &hostList = map.value(LastDeployedHostsKey).toList(); - const QVariantList &fileList = map.value(LastDeployedFilesKey).toList(); - const QVariantList &remotePathList - = map.value(LastDeployedRemotePathsKey).toList(); - const QVariantList &timeList = map.value(LastDeployedTimesKey).toList(); - const int elemCount - = qMin(qMin(hostList.size(), fileList.size()), - qMin(remotePathList.size(), timeList.size())); - for (int i = 0; i < elemCount; ++i) { - const MaemoDeployable d(fileList.at(i).toString(), - remotePathList.at(i).toString()); - m_lastDeployed.insert(DeployablePerHost(d, hostList.at(i).toString()), - timeList.at(i).toDateTime()); - } -} - -bool MaemoRunConfiguration::currentlyNeedsDeployment(const QString &host, - const MaemoDeployable &deployable) const -{ - const QDateTime &lastDeployed - = m_lastDeployed.value(DeployablePerHost(deployable, host)); - return !lastDeployed.isValid() - || QFileInfo(deployable.localFilePath).lastModified() > lastDeployed; -} - -void MaemoRunConfiguration::setDeployed(const QString &host, - const MaemoDeployable &deployable) -{ - m_lastDeployed.insert(DeployablePerHost(deployable, host), - QDateTime::currentDateTime()); -} - void MaemoRunConfiguration::setDeviceConfig(const MaemoDeviceConfig &devConf) { m_devConfig = devConf; @@ -225,18 +176,13 @@ const QString MaemoRunConfiguration::gdbCmd() const return QString(); } -const MaemoPackageCreationStep *MaemoRunConfiguration::packageStep() const +MaemoDeployStep *MaemoRunConfiguration::deployStep() const { - const QList<ProjectExplorer::BuildStep *> &buildSteps - = activeQt4BuildConfiguration()->steps(ProjectExplorer::BuildStep::Deploy); - for (int i = buildSteps.count() - 1; i >= 0; --i) { - const MaemoPackageCreationStep * const pStep - = qobject_cast<MaemoPackageCreationStep *>(buildSteps.at(i)); - if (pStep) - return pStep; - } - Q_ASSERT(!"Impossible: Maemo run configuration without packaging step."); - return 0; + MaemoDeployStep * const step + = MaemoGlobal::buildStep<MaemoDeployStep>(activeQt4BuildConfiguration()); + Q_ASSERT_X(step, Q_FUNC_INFO, + "Impossible: Maemo build configuration without deploy step."); + return step; } QString MaemoRunConfiguration::maddeRoot() const @@ -278,8 +224,7 @@ QString MaemoRunConfiguration::executable() const if (!ti.valid) return QString(); - return QDir::toNativeSeparators(QDir::cleanPath(ti.workingDir - + QLatin1Char('/') + ti.target)); + return QDir::cleanPath(ti.workingDir + QLatin1Char('/') + ti.target); } QString MaemoRunConfiguration::runtimeGdbServerPort() const @@ -312,5 +257,5 @@ void MaemoRunConfiguration::updateDeviceConfigurations() emit deviceConfigurationsUpdated(target()); } - } // namespace Internal +} // namespace Internal } // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h index 04b9f1cc3c..81a2897d53 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunconfiguration.h @@ -53,6 +53,7 @@ class Qt4BuildConfiguration; class Qt4ProFileNode; class Qt4Target; +class MaemoDeployStep; class MaemoPackageCreationStep; class MaemoRunConfigurationFactory; @@ -67,14 +68,11 @@ public: bool isEnabled(ProjectExplorer::BuildConfiguration *config) const; QWidget *createConfigurationWidget(); + ProjectExplorer::OutputFormatter *createConfigurationWidget() const; Qt4Target *qt4Target() const; Qt4BuildConfiguration *activeQt4BuildConfiguration() const; - bool currentlyNeedsDeployment(const QString &host, - const MaemoDeployable &deployable) const; - void setDeployed(const QString &host, const MaemoDeployable &deployable); - - const MaemoPackageCreationStep *packageStep() const; + MaemoDeployStep *deployStep() const; QString maddeRoot() const; QString executable() const; @@ -107,17 +105,12 @@ private slots: private: void init(); const MaemoToolChain *toolchain() const; - void addDeployTimesToMap(QVariantMap &map) const; - void getDeployTimesFromMap(const QVariantMap &map); QString m_proFilePath; mutable QString m_gdbPath; MaemoDeviceConfig m_devConfig; QStringList m_arguments; - - typedef QPair<MaemoDeployable, QString> DeployablePerHost; - QHash<DeployablePerHost, QDateTime> m_lastDeployed; }; } // namespace Internal diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp index c173ddb607..007018c513 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.cpp @@ -35,28 +35,30 @@ #include "maemoruncontrol.h" #include "maemodeployables.h" -#include "maemopackagecreationstep.h" +#include "maemodeploystep.h" +#include "maemoglobal.h" #include "maemorunconfiguration.h" #include <coreplugin/icore.h> -#include <coreplugin/progressmanager/progressmanager.h> +#include <coreplugin/ssh/sftpchannel.h> +#include <coreplugin/ssh/sshconnection.h> +#include <coreplugin/ssh/sshremoteprocess.h> #include <debugger/debuggerengine.h> #include <debugger/debuggerplugin.h> #include <debugger/debuggerrunner.h> #include <extensionsystem/pluginmanager.h> +#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/toolchain.h> +#include <qt4projectmanager/qt4buildconfiguration.h> #include <utils/qtcassert.h> -#include <projectexplorer/projectexplorerconstants.h> -#include <QtCore/QCryptographicHash> -#include <QtCore/QDir> #include <QtCore/QFileInfo> -#include <QtCore/QFuture> -#include <QtCore/QProcess> #include <QtCore/QStringBuilder> #include <QtGui/QMessageBox> +using namespace Core; + namespace Qt4ProjectManager { namespace Internal { @@ -78,215 +80,146 @@ AbstractMaemoRunControl::~AbstractMaemoRunControl() { } + // TODO: We can cache the connection. We'd have to check if the connection + // is active and its parameters are the same as m_devConfig. If yes, + // skip the connection step and jump right to handleConnected() void AbstractMaemoRunControl::start() { - m_stoppedByUser = false; if (!m_devConfig.isValid()) { handleError(tr("No device configuration set for run configuration.")); } else { + m_stopped = false; emit started(); - startInitialCleanup(); + m_connection = SshConnection::create(); + connect(m_connection.data(), SIGNAL(connected()), this, + SLOT(handleConnected())); + connect(m_connection.data(), SIGNAL(error(SshError)), this, + SLOT(handleConnectionFailure())); + m_connection->connectToHost(m_devConfig.server); } } -void AbstractMaemoRunControl::startInitialCleanup() -{ +void AbstractMaemoRunControl::handleConnected() +{ + if (m_stopped) + return; + emit appendMessage(this, tr("Cleaning up remote leftovers first ..."), false); - const QStringList appsToKill - = QStringList() << executableFileName() << QLatin1String("gdbserver"); + const QStringList appsToKill = QStringList() << executableFileName() +#ifdef USE_GDBSERVER + << QLatin1String("gdbserver"); +#else + << QLatin1String("gdb"); +#endif killRemoteProcesses(appsToKill, true); } -void AbstractMaemoRunControl::stop() +void AbstractMaemoRunControl::handleConnectionFailure() { - m_stoppedByUser = true; - if (isCleaning()) - m_initialCleaner->stop(); - else if (isDeploying()) - m_sshDeployer->stop(); - else - stopInternal(); + if (m_stopped) + return; + + handleError(tr("Could not connect to host: %1") + .arg(m_connection->errorString())); + emit finished(); } -void AbstractMaemoRunControl::handleInitialCleanupFinished() +void AbstractMaemoRunControl::stop() { - if (m_stoppedByUser) { - emit appendMessage(this, tr("Initial cleanup canceled by user."), false); + m_stopped = true; + if (m_connection && m_connection->state() == SshConnection::Connecting) { + disconnect(m_connection.data(), 0, this, 0); + m_connection->disconnectFromHost(); emit finished(); - } else if (m_initialCleaner->hasError()) { - handleError(tr("Error running initial cleanup: %1") - .arg(m_initialCleaner->error())); + } else if (m_initialCleaner && m_initialCleaner->isRunning()) { + disconnect(m_initialCleaner.data(), 0, this, 0); emit finished(); } else { - emit appendMessage(this, tr("Initial cleanup done."), false); - startInternal(); + stopInternal(); } } -void AbstractMaemoRunControl::startDeployment(bool forDebugging) +QString AbstractMaemoRunControl::executableFilePathOnTarget() const { - QTC_ASSERT(m_runConfig, return); - - if (m_stoppedByUser) { - emit finished(); - } else { - m_needsInstall = false; - m_deployables.clear(); - m_remoteLinks.clear(); - const MaemoPackageCreationStep * const packageStep - = m_runConfig->packageStep(); - if (packageStep->isPackagingEnabled()) { - const MaemoDeployable d(packageFilePath(), uploadDir()); - m_needsInstall = addDeployableIfNeeded(d); - } else { - const MaemoDeployables * const deployables - = packageStep->deployables(); - const int deployableCount = deployables->deployableCount(); - for (int i = 0; i < deployableCount; ++i) { - const MaemoDeployable &d = deployables->deployableAt(i); - if (addDeployableIfNeeded(d)) - m_needsInstall = true; - } - } - - if (forDebugging) { - QFileInfo dumperInfo(m_runConfig->dumperLib()); - if (dumperInfo.exists()) { - const MaemoDeployable d(m_runConfig->dumperLib(), uploadDir()); - m_needsInstall = addDeployableIfNeeded(d); - } - } - deploy(); - } -} - -bool AbstractMaemoRunControl::addDeployableIfNeeded(const MaemoDeployable &deployable) -{ - if (m_runConfig->currentlyNeedsDeployment(m_devConfig.server.host, - deployable)) { - const QString fileName - = QFileInfo(deployable.localFilePath).fileName(); - const QString remoteFilePath = deployable.remoteDir + '/' + fileName; - const QString uploadFilePath =uploadDir() + '/' + fileName + '.' - + QCryptographicHash::hash(remoteFilePath.toUtf8(), - QCryptographicHash::Md5).toHex(); - m_deployables.append(Core::SftpTransferInfo(deployable.localFilePath, - uploadFilePath.toUtf8(), Core::SftpTransferInfo::Upload)); - m_remoteLinks.insert(uploadFilePath, remoteFilePath); - return true; - } else { - return false; - } + const MaemoDeployables * const deployables + = m_runConfig->deployStep()->deployables(); + return deployables->remoteExecutableFilePath(m_runConfig->executable()); } -void AbstractMaemoRunControl::deploy() +void AbstractMaemoRunControl::startExecutionIfPossible() { - Core::ICore::instance()->progressManager() - ->addTask(m_progress.future(), tr("Deploying"), - QLatin1String("Maemo.Deploy")); - if (!m_deployables.isEmpty()) { - QList<Core::SftpTransferInfo> deploySpecs; - QStringList files; - foreach (const Core::SftpTransferInfo &deployable, m_deployables) - files << deployable.localFilePath; - emit appendMessage(this, - tr("Files to deploy: %1.").arg(files.join(" ")), false); - m_sshDeployer.reset(new MaemoSshDeployer(m_devConfig.server, m_deployables)); - connect(m_sshDeployer.data(), SIGNAL(finished()), - this, SLOT(handleDeployThreadFinished())); - connect(m_sshDeployer.data(), SIGNAL(fileCopied(QString)), - this, SLOT(handleFileCopied())); - m_progress.setProgressRange(0, m_deployables.count()); - m_progress.setProgressValue(0); - m_progress.reportStarted(); - m_sshDeployer->start(); + if (executableFilePathOnTarget().isEmpty()) { + handleError(tr("Cannot run: No remote executable set.")); + emit finished(); } else { - m_progress.reportFinished(); - startExecutionIfPossible(); + startExecution(); } } -void AbstractMaemoRunControl::handleFileCopied() -{ - const Core::SftpTransferInfo &deployable = m_deployables.takeFirst(); - const QString remoteDir - = QFileInfo(m_remoteLinks.value(QString::fromUtf8(deployable.remoteFilePath))) - .dir().path(); - - // TODO: This should be done after the linking step, in case the - // operation is cancelled directly after the upload. - m_runConfig->setDeployed(m_devConfig.server.host, - MaemoDeployable(deployable.localFilePath, remoteDir)); - - m_progress.setProgressValue(m_progress.progressValue() + 1); -} - -bool AbstractMaemoRunControl::isDeploying() const -{ - return m_sshDeployer && m_sshDeployer->isRunning(); -} - -QString AbstractMaemoRunControl::packageFileName() const -{ - return QFileInfo(packageFilePath()).fileName(); -} - -QString AbstractMaemoRunControl::packageFilePath() const +void AbstractMaemoRunControl::startExecution() { - return m_runConfig->packageStep()->packageFilePath(); + m_runner = m_connection->createRemoteProcess(remoteCall().toUtf8()); + connect(m_runner.data(), SIGNAL(started()), this, + SLOT(handleRemoteProcessStarted())); + connect(m_runner.data(), SIGNAL(closed(int)), this, + SLOT(handleRemoteProcessFinished(int))); + connect(m_runner.data(), SIGNAL(outputAvailable(QByteArray)), this, + SLOT(handleRemoteOutput(QByteArray))); + connect(m_runner.data(), SIGNAL(errorOutputAvailable(QByteArray)), this, + SLOT(handleRemoteErrorOutput(QByteArray))); + emit appendMessage(this, tr("Starting remote application."), false); + m_runner->start(); } -QString AbstractMaemoRunControl::executableFilePathOnTarget() const +void AbstractMaemoRunControl::handleRemoteProcessFinished(int exitStatus) { - const MaemoDeployables * const deployables - = m_runConfig->packageStep()->deployables(); - return deployables->remoteExecutableFilePath(m_runConfig->executable()); -} + Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart + || exitStatus == SshRemoteProcess::KilledBySignal + || exitStatus == SshRemoteProcess::ExitedNormally); -bool AbstractMaemoRunControl::isCleaning() const -{ - return m_initialCleaner && m_initialCleaner->isRunning(); -} + if (m_stopped) + return; -void AbstractMaemoRunControl::startExecutionIfPossible() -{ - if (executableFilePathOnTarget().isEmpty()) { - handleError(tr("Cannot run: No remote executable set.")); + if (exitStatus == SshRemoteProcess::ExitedNormally) { + emit appendMessage(this, + tr("Finished running remote process. Exit code was %1.") + .arg(m_runner->exitCode()), false); emit finished(); } else { - startExecution(); + handleError(tr("Error running remote process: %1") + .arg(m_runner->errorString())); } } -void AbstractMaemoRunControl::startExecution() +void AbstractMaemoRunControl::handleRemoteOutput(const QByteArray &output) { - m_sshRunner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall())); - connect(m_sshRunner.data(), SIGNAL(finished()), - this, SLOT(handleRunThreadFinished())); - connect(m_sshRunner.data(), SIGNAL(remoteOutput(QString)), - this, SLOT(handleRemoteOutput(QString))); - emit appendMessage(this, tr("Starting remote application."), false); - m_sshRunner->start(); + emit addToOutputWindowInline(this, QString::fromUtf8(output), false); +} + +void AbstractMaemoRunControl::handleRemoteErrorOutput(const QByteArray &output) +{ + emit addToOutputWindowInline(this, QString::fromUtf8(output), true); } bool AbstractMaemoRunControl::isRunning() const { - return isDeploying() || (m_sshRunner && m_sshRunner->isRunning()); + return m_runner && m_runner->isRunning(); } void AbstractMaemoRunControl::stopRunning(bool forDebugging) { - if (m_sshRunner && m_sshRunner->isRunning()) { - m_sshRunner->stop(); + if (m_runner && m_runner->isRunning()) { + disconnect(m_runner.data(), 0, this, 0); QStringList apps(executableFileName()); if (forDebugging) apps << QLatin1String("gdbserver"); killRemoteProcesses(apps, false); + emit finished(); } } void AbstractMaemoRunControl::killRemoteProcesses(const QStringList &apps, - bool initialCleanup) + bool initialCleanup) { QString niceKill; QString brutalKill; @@ -296,54 +229,34 @@ void AbstractMaemoRunControl::killRemoteProcesses(const QStringList &apps, } QString remoteCall = niceKill + QLatin1String("sleep 1; ") + brutalKill; remoteCall.remove(remoteCall.count() - 1, 1); // Get rid of trailing semicolon. - QScopedPointer<MaemoSshRunner> &runner - = initialCleanup ? m_initialCleaner : m_sshStopper; - runner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall)); - if (initialCleanup) - connect(runner.data(), SIGNAL(finished()), - this, SLOT(handleInitialCleanupFinished())); - runner->start(); -} - -void AbstractMaemoRunControl::handleDeployThreadFinished() -{ - bool cancel; - if (m_stoppedByUser) { - emit appendMessage(this, tr("Deployment canceled by user."), false); - cancel = true; - } else if (m_sshDeployer->hasError()) { - handleError(tr("Deployment failed: %1").arg(m_sshDeployer->error())); - cancel = true; - } else { - emit appendMessage(this, tr("Deployment finished."), false); - cancel = false; - } - - if (cancel) { - m_progress.reportCanceled(); - m_progress.reportFinished(); - emit finished(); + SshRemoteProcess::Ptr proc + = m_connection->createRemoteProcess(remoteCall.toUtf8()); + if (initialCleanup) { + m_initialCleaner = proc; + connect(m_initialCleaner.data(), SIGNAL(closed(int)), this, + SLOT(handleInitialCleanupFinished(int))); } else { - m_progress.reportFinished(); - startExecutionIfPossible(); + m_stopper = proc; } + proc->start(); } -void AbstractMaemoRunControl::handleRunThreadFinished() +void AbstractMaemoRunControl::handleInitialCleanupFinished(int exitStatus) { - if (m_stoppedByUser) { - emit appendMessage(this, - tr("Remote execution canceled due to user request."), - false); - } else if (m_sshRunner && m_sshRunner->hasError()) { - emit appendMessage(this, tr("Error running remote process: %1") - .arg(m_sshRunner->error()), - true); + Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart + || exitStatus == SshRemoteProcess::KilledBySignal + || exitStatus == SshRemoteProcess::ExitedNormally); + + if (m_stopped) + return; + + if (exitStatus != SshRemoteProcess::ExitedNormally) { + handleError(tr("Initial cleanup failed: %1") + .arg(m_initialCleaner->errorString())); } else { - emit appendMessage(this, tr("Finished running remote process."), - false); + emit appendMessage(this, tr("Initial cleanup done."), false); + startExecutionIfPossible(); } - emit finished(); } const QString AbstractMaemoRunControl::executableOnHost() const @@ -358,40 +271,13 @@ const QString AbstractMaemoRunControl::executableFileName() const const QString AbstractMaemoRunControl::uploadDir() const { - return homeDirOnDevice(m_devConfig.server.uname); -} - -QString AbstractMaemoRunControl::remoteSudo() const -{ - return QLatin1String("/usr/lib/mad-developer/devrootsh"); -} - -QString AbstractMaemoRunControl::remoteInstallCommand() const -{ - Q_ASSERT(m_needsInstall); - QString cmd; - for (QMap<QString, QString>::ConstIterator it = m_remoteLinks.begin(); - it != m_remoteLinks.end(); ++it) { - cmd += QString::fromLocal8Bit("%1 ln -sf %2 %3 && ") - .arg(remoteSudo(), it.key(), it.value()); - } - if (m_runConfig->packageStep()->isPackagingEnabled()) { - cmd += QString::fromLocal8Bit("%1 dpkg -i %2").arg(remoteSudo()) - .arg(packageFileName()); - } else if (!m_remoteLinks.isEmpty()) { - return cmd.remove(cmd.length() - 4, 4); // Trailing " && " - } - - return cmd; + return MaemoGlobal::homeDirOnDevice(m_devConfig.server.uname); } const QString AbstractMaemoRunControl::targetCmdLinePrefix() const { - const QString &installPrefix = m_needsInstall - ? remoteInstallCommand() + QLatin1String(" && ") - : QString(); - return QString::fromLocal8Bit("%1%2 chmod a+x %3 && source /etc/profile && DISPLAY=:0.0 ") - .arg(installPrefix).arg(remoteSudo()).arg(executableFilePathOnTarget()); + return QString::fromLocal8Bit("%1 chmod a+x %2 && source /etc/profile && DISPLAY=:0.0 ") + .arg(MaemoGlobal::remoteSudo()).arg(executableFilePathOnTarget()); } QString AbstractMaemoRunControl::targetCmdLineSuffix() const @@ -403,6 +289,15 @@ void AbstractMaemoRunControl::handleError(const QString &errString) { QMessageBox::critical(0, tr("Remote Execution Failure"), errString); emit appendMessage(this, errString, true); + stop(); +} + +template<typename SshChannel> void AbstractMaemoRunControl::closeSshChannel(SshChannel &channel) +{ + if (channel) { + disconnect(channel.data(), 0, this, 0); + // channel->closeChannel(); + } } @@ -416,11 +311,6 @@ MaemoRunControl::~MaemoRunControl() stop(); } -void MaemoRunControl::startInternal() -{ - startDeployment(false); -} - QString MaemoRunControl::remoteCall() const { return QString::fromLocal8Bit("%1 %2 %3").arg(targetCmdLinePrefix()) @@ -432,16 +322,12 @@ void MaemoRunControl::stopInternal() AbstractMaemoRunControl::stopRunning(false); } -void MaemoRunControl::handleRemoteOutput(const QString &output) -{ - emit addToOutputWindowInline(this, output, false); -} - MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration) : AbstractMaemoRunControl(runConfiguration, ProjectExplorer::Constants::DEBUGMODE) , m_debuggerRunControl(0) , m_startParams(new DebuggerStartParameters) + , m_uploadJob(SftpInvalidJob) { #ifdef USE_GDBSERVER m_startParams->startMode = AttachToRemote; @@ -453,8 +339,9 @@ MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration) #else m_startParams->startMode = StartRemoteGdb; m_startParams->executable = executableFilePathOnTarget(); - m_startParams->debuggerCommand = QLatin1String("/usr/bin/gdb"); - m_startParams->sshserver = m_devConfig.server; + m_startParams->debuggerCommand = targetCmdLinePrefix() + + QLatin1String(" /usr/bin/gdb"); + m_startParams->connParams = m_devConfig.server; #endif m_startParams->processArgs = m_runConfig->arguments(); m_startParams->sysRoot = m_runConfig->sysRoot(); @@ -475,12 +362,6 @@ MaemoDebugRunControl::~MaemoDebugRunControl() stop(); } -void MaemoDebugRunControl::startInternal() -{ - m_debuggingStarted = false; - startDeployment(true); -} - QString MaemoDebugRunControl::remoteCall() const { return QString::fromLocal8Bit("%1 gdbserver :%2 %3 %4") @@ -490,37 +371,101 @@ QString MaemoDebugRunControl::remoteCall() const void MaemoDebugRunControl::startExecution() { -#ifdef USE_GDBSERVER - AbstractMaemoRunControl::startExecution(); -#else - startDebugging(); -#endif + const QString &dumperLib = m_runConfig->dumperLib(); + if (!dumperLib.isEmpty() + && m_runConfig->deployStep()->currentlyNeedsDeployment(m_devConfig.server.host, + MaemoDeployable(dumperLib, uploadDir()))) { + m_uploader = m_connection->createSftpChannel(); + connect(m_uploader.data(), SIGNAL(initialized()), this, + SLOT(handleSftpChannelInitialized())); + connect(m_uploader.data(), SIGNAL(initializationFailed(QString)), this, + SLOT(handleSftpChannelInitializationFailed(QString))); + connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)), + this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString))); + m_uploader->initialize(); + } else { + startDebugging(); + } } -void MaemoDebugRunControl::handleRemoteOutput(const QString &output) +void MaemoDebugRunControl::handleSftpChannelInitialized() { -#ifdef USE_GDBSERVER - if (!m_debuggingStarted) { - m_debuggingStarted = true; + if (m_stopped) + return; + + const QString dumperLib = m_runConfig->dumperLib(); + const QString fileName = QFileInfo(dumperLib).fileName(); + const QString remoteFilePath = uploadDir() + '/' + fileName; + m_uploadJob = m_uploader->uploadFile(dumperLib, remoteFilePath, + SftpOverwriteExisting); + if (m_uploadJob == SftpInvalidJob) { + handleError(tr("Upload failed: Could not open file '%1'") + .arg(dumperLib)); + } else { + emit appendMessage(this, + tr("Started uploading debugging helpers ('%1').").arg(dumperLib), + false); + } +} + +void MaemoDebugRunControl::handleSftpChannelInitializationFailed(const QString &error) +{ + handleError(error); +} + +void MaemoDebugRunControl::handleSftpJobFinished(Core::SftpJobId job, + const QString &error) +{ + if (m_stopped) + return; + + if (job != m_uploadJob) { + qWarning("Warning: Unknown debugging helpers upload job %d finished.", job); + return; + } + + if (!error.isEmpty()) { + handleError(tr("Error: Could not upload debugging helpers.")); + } else { + m_runConfig->deployStep()->setDeployed(m_devConfig.server.host, + MaemoDeployable(m_runConfig->dumperLib(), uploadDir())); + emit appendMessage(this, + tr("Finished uploading debugging helpers."), false); startDebugging(); } -#endif - emit addToOutputWindowInline(m_debuggerRunControl, output, false); } void MaemoDebugRunControl::startDebugging() { +#ifdef USE_GDBSERVER + AbstractMaemoRunControl::startExecution(); +#else DebuggerPlugin::startDebugger(m_debuggerRunControl); +#endif +} + +bool MaemoDebugRunControl::isDeploying() const +{ + return m_uploader && m_uploadJob != SftpInvalidJob; } void MaemoDebugRunControl::stopInternal() { - m_debuggerRunControl->engine()->quitDebugger(); + if (isDeploying()) { + disconnect(m_uploader.data(), 0, this, 0); + m_uploader->closeChannel(); + m_uploadJob = SftpInvalidJob; + emit finished(); + } else if (m_debuggerRunControl && m_debuggerRunControl->engine()) { + m_debuggerRunControl->engine()->quitDebugger(); + } else { + emit finished(); + } } bool MaemoDebugRunControl::isRunning() const { - return AbstractMaemoRunControl::isRunning() + return isDeploying() || AbstractMaemoRunControl::isRunning() || m_debuggerRunControl->state() != DebuggerNotReady; } @@ -529,10 +474,20 @@ void MaemoDebugRunControl::debuggingFinished() #ifdef USE_GDBSERVER AbstractMaemoRunControl::stopRunning(true); #else - AbstractMaemoRunControl::handleRunThreadFinished(); + emit finished(); #endif } +void MaemoDebugRunControl::handleRemoteProcessStarted() +{ + DebuggerPlugin::startDebugger(m_debuggerRunControl); +} + +void MaemoDebugRunControl::debuggerOutput(const QString &output) +{ + emit appendMessage(this, QLatin1String("[gdb says:] ") + output, true); +} + QString MaemoDebugRunControl::gdbServerPort() const { return m_devConfig.type == MaemoDeviceConfig::Physical diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h index ec6849b325..a9af1e6071 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemoruncontrol.h @@ -36,19 +36,22 @@ #define MAEMORUNCONTROL_H #include "maemodeviceconfigurations.h" -#include "maemodeployable.h" -#include "maemosshthread.h" +#include <coreplugin/ssh/sftpdefs.h> #include <projectexplorer/runconfiguration.h> -#include <QtCore/QFutureInterface> -#include <QtCore/QScopedPointer> #include <QtCore/QString> QT_BEGIN_NAMESPACE class QProcess; QT_END_NAMESPACE +namespace Core { + class SftpChannel; + class SshConnection; + class SshRemoteProcess; +} + namespace Debugger { class DebuggerRunControl; class DebuggerStartParameters; @@ -56,8 +59,6 @@ namespace Debugger { namespace Qt4ProjectManager { namespace Internal { -class MaemoSshDeployer; -class MaemoSshRunner; class MaemoRunConfiguration; class AbstractMaemoRunControl : public ProjectExplorer::RunControl @@ -73,8 +74,6 @@ protected: virtual void start(); virtual void stop(); - void startDeployment(bool forDebugging); - void deploy(); void stopRunning(bool forDebugging); virtual void startExecution(); void handleError(const QString &errString); @@ -83,49 +82,35 @@ protected: const QString targetCmdLinePrefix() const; QString targetCmdLineSuffix() const; const QString uploadDir() const; - QString packageFileName() const; - QString packageFilePath() const; QString executableFilePathOnTarget() const; -protected slots: - void handleRunThreadFinished(); - private slots: - virtual void handleRemoteOutput(const QString &output)=0; - void handleInitialCleanupFinished(); - void handleDeployThreadFinished(); - void handleFileCopied(); + void handleConnected(); + void handleConnectionFailure(); + void handleInitialCleanupFinished(int exitStatus); + virtual void handleRemoteProcessStarted() {} + void handleRemoteProcessFinished(int exitStatus); + void handleRemoteOutput(const QByteArray &output); + void handleRemoteErrorOutput(const QByteArray &output); protected: MaemoRunConfiguration *m_runConfig; // TODO this pointer can be invalid const MaemoDeviceConfig m_devConfig; + QSharedPointer<Core::SshConnection> m_connection; + bool m_stopped; private: - bool addDeployableIfNeeded(const MaemoDeployable &deployable); - - virtual void startInternal()=0; virtual void stopInternal()=0; virtual QString remoteCall() const=0; - void startInitialCleanup(); void killRemoteProcesses(const QStringList &apps, bool initialCleanup); + void cancelActions(); + template<class SshChannel> void closeSshChannel(SshChannel &channel); void startExecutionIfPossible(); - bool isCleaning() const; - bool isDeploying() const; - QString remoteSudo() const; - QString remoteInstallCommand() const; - QString uploadFilePath(const MaemoDeployable &deployable) const; - - QFutureInterface<void> m_progress; - QScopedPointer<MaemoSshDeployer> m_sshDeployer; - QScopedPointer<MaemoSshRunner> m_sshRunner; - QScopedPointer<MaemoSshRunner> m_sshStopper; - QScopedPointer<MaemoSshRunner> m_initialCleaner; - bool m_stoppedByUser; - - QList<Core::SftpTransferInfo> m_deployables; - QMap<QString, QString> m_remoteLinks; - bool m_needsInstall; + + QSharedPointer<Core::SshRemoteProcess> m_runner; + QSharedPointer<Core::SshRemoteProcess> m_stopper; + QSharedPointer<Core::SshRemoteProcess> m_initialCleaner; }; class MaemoRunControl : public AbstractMaemoRunControl @@ -135,11 +120,7 @@ public: explicit MaemoRunControl(ProjectExplorer::RunConfiguration *runConfiguration); ~MaemoRunControl(); -private slots: - virtual void handleRemoteOutput(const QString &output); - private: - virtual void startInternal(); virtual void stopInternal(); virtual QString remoteCall() const; }; @@ -153,22 +134,26 @@ public: bool isRunning() const; private slots: - virtual void handleRemoteOutput(const QString &output); + virtual void handleRemoteProcessStarted(); + void debuggerOutput(const QString &output); void debuggingFinished(); + void handleSftpChannelInitialized(); + void handleSftpChannelInitializationFailed(const QString &error); + void handleSftpJobFinished(Core::SftpJobId job, const QString &error); private: - virtual void startInternal(); virtual void stopInternal(); virtual void startExecution(); virtual QString remoteCall() const; QString gdbServerPort() const; void startDebugging(); + bool isDeploying() const; Debugger::DebuggerRunControl *m_debuggerRunControl; QSharedPointer<Debugger::DebuggerStartParameters> m_startParams; - - bool m_debuggingStarted; + QSharedPointer<Core::SftpChannel> m_uploader; + Core::SftpJobId m_uploadJob; }; } // namespace Internal diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemorunfactories.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemorunfactories.cpp index 420b31c3f3..b2ad7b1ad0 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemorunfactories.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemorunfactories.cpp @@ -81,7 +81,10 @@ bool MaemoRunConfigurationFactory::canCreate(Target *parent, bool MaemoRunConfigurationFactory::canRestore(Target *parent, const QVariantMap &map) const { - return canCreate(parent, ProjectExplorer::idFromMap(map)); + if (!qobject_cast<Qt4Target *>(parent)) + return false; + return ProjectExplorer::idFromMap(map) + .startsWith(QLatin1String(MAEMO_RC_ID)); } bool MaemoRunConfigurationFactory::canClone(Target *parent, diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp index 6ef22cf3d4..0d2e3a7fbe 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.cpp @@ -39,7 +39,9 @@ #include "maemoconfigtestdialog.h" #include "maemodeviceconfigurations.h" #include "maemosshconfigdialog.h" -#include "maemosshthread.h" + +#include <coreplugin/ssh/sshconnection.h> +#include <coreplugin/ssh/sshremoteprocess.h> #include <QtCore/QFile> #include <QtCore/QFileInfo> @@ -52,6 +54,8 @@ #include <algorithm> +using namespace Core; + namespace Qt4ProjectManager { namespace Internal { @@ -97,8 +101,7 @@ MaemoSettingsWidget::MaemoSettingsWidget(QWidget *parent) : QWidget(parent), m_ui(new Ui_MaemoSettingsWidget), m_devConfs(MaemoDeviceConfigurations::instance().devConfigs()), - m_nameValidator(new NameValidator(m_devConfs)), - m_keyDeployer(0) + m_nameValidator(new NameValidator(m_devConfs)) { initGui(); } @@ -195,7 +198,7 @@ void MaemoSettingsWidget::display(const MaemoDeviceConfig &devConfig) otherConfig->server.pwd = devConfig.server.pwd; otherConfig->server.privateKeyFile = devConfig.server.privateKeyFile; - if (devConfig.server.authType == Core::SshServerInfo::AuthByPwd) + if (devConfig.server.authType == Core::SshConnectionParameters::AuthByPwd) m_ui->passwordButton->setChecked(true); else m_ui->keyButton->setChecked(true); @@ -272,7 +275,7 @@ void MaemoSettingsWidget::authenticationTypeChanged() { const bool usePassword = m_ui->passwordButton->isChecked(); currentConfig().server.authType - = usePassword ? Core::SshServerInfo::AuthByPwd : Core::SshServerInfo::AuthByKey; + = usePassword ? Core::SshConnectionParameters::AuthByPwd : Core::SshConnectionParameters::AuthByKey; m_ui->pwdLineEdit->setEnabled(usePassword); m_ui->passwordLabel->setEnabled(usePassword); m_ui->keyFileLineEdit->setEnabled(!usePassword); @@ -337,13 +340,32 @@ void MaemoSettingsWidget::deployKey() if (m_keyDeployer) return; + disconnect(m_ui->deployKeyButton, 0, this, 0); + m_ui->deployKeyButton->setText(tr("Stop Deploying")); + connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, + SLOT(stopDeploying())); + m_connection = SshConnection::create(); + connect(m_connection.data(), SIGNAL(connected()), this, + SLOT(handleConnected())); + connect(m_connection.data(), SIGNAL(error(SshError)), this, + SLOT(handleConnectionFailure())); + m_connection->connectToHost(currentConfig().server); +} + +void MaemoSettingsWidget::handleConnected() +{ + if (!m_connection) + return; + const QString &dir = QFileInfo(currentConfig().server.privateKeyFile).path(); QString publicKeyFileName = QFileDialog::getOpenFileName(this, tr("Choose Public Key File"), dir, tr("Public Key Files(*.pub);;All Files (*)")); - if (publicKeyFileName.isEmpty()) + if (publicKeyFileName.isEmpty()) { + stopDeploying(); return; + } QFile keyFile(publicKeyFileName); QByteArray key; @@ -353,32 +375,45 @@ void MaemoSettingsWidget::deployKey() if (!keyFileAccessible || keyFile.error() != QFile::NoError) { QMessageBox::critical(this, tr("Deployment Failed"), tr("Could not read public key file '%1'.").arg(publicKeyFileName)); + stopDeploying(); return; } - m_ui->deployKeyButton->disconnect(); - const QString command = QLatin1String("test -d .ssh " - "|| mkdir .ssh && chmod 0700 .ssh && echo '") - + key + QLatin1String("' >> .ssh/authorized_keys"); - m_keyDeployer = new MaemoSshRunner(currentConfig().server, command); - connect(m_keyDeployer, SIGNAL(finished()), - this, SLOT(handleDeployThreadFinished())); - m_ui->deployKeyButton->setText(tr("Stop Deploying")); - connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(stopDeploying())); + const QByteArray command = "test -d .ssh " + "|| mkdir .ssh && chmod 0700 .ssh && echo '" + + key + "' >> .ssh/authorized_keys"; + m_keyDeployer = m_connection->createRemoteProcess(command); + connect(m_keyDeployer.data(), SIGNAL(closed(int)), this, + SLOT(handleKeyUploadFinished(int))); m_keyDeployer->start(); } -void MaemoSettingsWidget::handleDeployThreadFinished() +void MaemoSettingsWidget::handleConnectionFailure() { - if (!m_keyDeployer) + if (!m_connection) return; - if (m_keyDeployer->hasError()) { - QMessageBox::critical(this, tr("Deployment Failed"), - tr("Key deployment failed: %1").arg(m_keyDeployer->error())); - } else { + QMessageBox::critical(this, tr("Deployment Failed"), + tr("Could not connect to host: %1").arg(m_connection->errorString())); + stopDeploying(); +} + +void MaemoSettingsWidget::handleKeyUploadFinished(int exitStatus) +{ + Q_ASSERT(exitStatus == SshRemoteProcess::FailedToStart + || exitStatus == SshRemoteProcess::KilledBySignal + || exitStatus == SshRemoteProcess::ExitedNormally); + + if (!m_connection) + return; + + if (exitStatus == SshRemoteProcess::ExitedNormally + && m_keyDeployer->exitCode() == 0) { QMessageBox::information(this, tr("Deployment Succeeded"), tr("Key was successfully deployed.")); + } else { + QMessageBox::critical(this, tr("Deployment Failed"), + tr("Key deployment failed: %1.").arg(m_keyDeployer->errorString())); } stopDeploying(); } @@ -386,14 +421,14 @@ void MaemoSettingsWidget::handleDeployThreadFinished() void MaemoSettingsWidget::stopDeploying() { if (m_keyDeployer) { - m_ui->deployKeyButton->disconnect(); - m_keyDeployer->disconnect(); - m_keyDeployer->stop(); - delete m_keyDeployer; - m_keyDeployer = 0; - m_ui->deployKeyButton->setText(tr("Deploy Public Key ...")); - connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(deployKey())); + disconnect(m_keyDeployer.data(), 0, this, 0); + m_keyDeployer = SshRemoteProcess::Ptr(); } + if (m_connection) + disconnect(m_connection.data(), 0, this, 0); + m_ui->deployKeyButton->disconnect(); + m_ui->deployKeyButton->setText(tr("Deploy Public Key ...")); + connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(deployKey())); } void MaemoSettingsWidget::currentConfigChanged(int index) diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h index e8c676696a..cc17e2262c 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h +++ b/src/plugins/qt4projectmanager/qt-maemo/maemosettingswidget.h @@ -38,6 +38,7 @@ #include "maemodeviceconfigurations.h" #include <QtCore/QList> +#include <QtCore/QSharedPointer> #include <QtCore/QString> #include <QtGui/QWidget> @@ -47,6 +48,11 @@ class QLineEdit; class Ui_MaemoSettingsWidget; QT_END_NAMESPACE +namespace Core { +class SshConnection; +class SshRemoteProcess; +} + namespace Qt4ProjectManager { namespace Internal { @@ -86,8 +92,10 @@ private slots: // For key deploying. void deployKey(); - void handleDeployThreadFinished(); void stopDeploying(); + void handleConnected(); + void handleConnectionFailure(); + void handleKeyUploadFinished(int exitStatus); private: void initGui(); @@ -102,7 +110,8 @@ private: MaemoDeviceConfig m_lastConfigHW; MaemoDeviceConfig m_lastConfigSim; NameValidator * const m_nameValidator; - MaemoSshRunner *m_keyDeployer; + QSharedPointer<Core::SshConnection> m_connection; + QSharedPointer<Core::SshRemoteProcess> m_keyDeployer; }; } // namespace Internal diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp index 6052b702a2..48d48fb0f9 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp +++ b/src/plugins/qt4projectmanager/qt-maemo/maemosshconfigdialog.cpp @@ -45,13 +45,13 @@ #include <QtGui/QMessageBox> #include <QtNetwork/QHostInfo> - +using namespace Core; using namespace Qt4ProjectManager::Internal; MaemoSshConfigDialog::MaemoSshConfigDialog(QWidget *parent) : QDialog(parent) , home(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)) - , m_keyGenerator(new Core::SshKeyGenerator) + , m_keyGenerator(new SshKeyGenerator) { m_ui.setupUi(this); @@ -75,16 +75,16 @@ void MaemoSshConfigDialog::slotToggled() void MaemoSshConfigDialog::generateSshKey() { - const Core::SshKeyGenerator::KeyType keyType = m_ui.rsa->isChecked() - ? Core::SshKeyGenerator::Rsa - : Core::SshKeyGenerator::Dsa; + const SshKeyGenerator::KeyType keyType = m_ui.rsa->isChecked() + ? SshKeyGenerator::Rsa + : SshKeyGenerator::Dsa; QByteArray userId = QString(home.mid(home.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char('@') + QHostInfo::localHostName()).toUtf8(); QApplication::setOverrideCursor(Qt::BusyCursor); - if (m_keyGenerator->generateKeys(keyType, userId, + if (m_keyGenerator->generateKeys(keyType, SshKeyGenerator::OpenSsl, m_ui.comboBox->currentText().toUShort())) { m_ui.plainTextEdit->setPlainText(m_keyGenerator->publicKey()); m_ui.savePublicKey->setEnabled(true); @@ -117,7 +117,7 @@ void MaemoSshConfigDialog::saveKey(bool publicKey) { checkSshDir(); const QString suggestedTypeSuffix = - m_keyGenerator->type() == Core::SshKeyGenerator::Rsa ? "rsa" : "dsa"; + m_keyGenerator->type() == SshKeyGenerator::Rsa ? "rsa" : "dsa"; const QString suggestedName = home + QString::fromLatin1("/.ssh/id_%1%2") .arg(suggestedTypeSuffix).arg(publicKey ? ".pub" : ""); const QString dlgTitle @@ -131,8 +131,8 @@ void MaemoSshConfigDialog::saveKey(bool publicKey) const bool canOpen = file.open(QIODevice::WriteOnly); if (canOpen) file.write(publicKey - ? m_keyGenerator->publicKey().toUtf8() - : m_keyGenerator->privateKey().toUtf8()); + ? m_keyGenerator->publicKey() + : m_keyGenerator->privateKey()); if (!canOpen || file.error() != QFile::NoError) { QMessageBox::critical(this, tr("Error writing file"), tr("Could not write file '%1':\n %2") diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp deleted file mode 100644 index a31c06dec4..0000000000 --- a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Creator. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "maemosshthread.h" - -namespace Qt4ProjectManager { -namespace Internal { - -template <class SshConnection> MaemoSshThread<SshConnection>::MaemoSshThread(const Core::SshServerInfo &server) - : m_server(server), m_stopRequested(false) -{ -} - -template <class SshConnection> MaemoSshThread<SshConnection>::~MaemoSshThread() -{ - stop(); - wait(); -} - -template <class SshConnection> void MaemoSshThread<SshConnection>::run() -{ - if (m_stopRequested) - return; - - if (!runInternal()) - m_error = m_connection->error(); -} - -template<class SshConnection> void MaemoSshThread<SshConnection>::stop() -{ - m_mutex.lock(); - m_stopRequested = true; - m_waitCond.wakeAll(); - const bool hasConnection = !m_connection.isNull(); - if (hasConnection) - m_connection->quit(); - m_mutex.unlock(); -} - -template<class SshConnection> void MaemoSshThread<SshConnection>::waitForStop() -{ - m_mutex.lock(); - while (!stopRequested()) - m_waitCond.wait(&m_mutex); - m_mutex.unlock(); -} - -template<class SshConnection> void MaemoSshThread<SshConnection>::createConnection() -{ - typename SshConnection::Ptr connection = SshConnection::create(m_server); - m_mutex.lock(); - m_connection = connection; - m_mutex.unlock(); -} - -MaemoSshRunner::MaemoSshRunner(const Core::SshServerInfo &server, - const QString &command) - : MaemoSshThread<Core::InteractiveSshConnection>(server), - m_command(command) -{ - m_prompt = server.uname == QLatin1String("root") ? "#" : "$"; -} - -bool MaemoSshRunner::runInternal() -{ - createConnection(); - connect(m_connection.data(), SIGNAL(remoteOutputAvailable()), - this, SLOT(handleRemoteOutput())); - initState(); - if (!m_connection->start()) - return false; - if (stopRequested()) - return true; - - waitForStop(); - return !m_connection->hasError(); -} - -void MaemoSshRunner::initState() -{ - m_endMarkerCount = 0; - m_promptEncountered = false; - m_potentialEndMarkerPrefix.clear(); -} - -void MaemoSshRunner::handleRemoteOutput() -{ - const QByteArray output - = m_potentialEndMarkerPrefix + m_connection->waitForRemoteOutput(0); - - // Wait for a prompt before sending the command. - if (!m_promptEncountered) { - if (output.indexOf(m_prompt) != -1) { - m_promptEncountered = true; - - /* - * We don't have access to the remote process management, so we - * try to track the lifetime of the process by adding a second command - * that prints a rare character. When it occurs for the second time (the - * first one is the echo from the remote terminal), we assume the - * process has finished. If anyone actually prints this special character - * in their application, they are out of luck. - */ - const QString finalCommand = m_command + QLatin1String(";echo ") - + QString::fromUtf8(EndMarker) + QLatin1Char('\n'); - if (!m_connection->sendInput(finalCommand.toUtf8())) - stop(); - } - return; - } - - /* - * The output the user should see is everything after the first - * and before the last occurrence of our marker string. - */ - int firstCharToEmit; - int charsToEmitCount; - const int endMarkerPos = output.indexOf(EndMarker); - if (endMarkerPos != -1) { - if (m_endMarkerCount++ == 0) { - firstCharToEmit = endMarkerPos + EndMarker.count() + 1; - int endMarkerPos2 - = output.indexOf(EndMarker, firstCharToEmit); - if (endMarkerPos2 != -1) { - ++ m_endMarkerCount; - charsToEmitCount = endMarkerPos2 - firstCharToEmit; - } else { - charsToEmitCount = -1; - } - } else { - firstCharToEmit = m_potentialEndMarkerPrefix.count(); - charsToEmitCount = endMarkerPos - firstCharToEmit; - } - } else { - if (m_endMarkerCount == 0) { - charsToEmitCount = 0; - } else { - firstCharToEmit = m_potentialEndMarkerPrefix.count(); - charsToEmitCount = -1; - } - } - - if (charsToEmitCount != 0) { - emit remoteOutput(QString::fromUtf8(output.data() + firstCharToEmit, - charsToEmitCount)); - } - if (m_endMarkerCount == 2) - stop(); - - m_potentialEndMarkerPrefix = output.right(EndMarker.count() - 1); -} - -const QByteArray MaemoSshRunner::EndMarker(QString(QChar(0x13a0)).toUtf8()); - - -MaemoSshDeployer::MaemoSshDeployer(const Core::SshServerInfo &server, - const QList<Core::SftpTransferInfo> &deploySpecs) - : MaemoSshThread<Core::SftpConnection>(server), - m_deploySpecs(deploySpecs) -{ -} - -bool MaemoSshDeployer::runInternal() -{ - createConnection(); - if (!m_connection->start()) - return false; - if (stopRequested()) - return true; - - connect(m_connection.data(), SIGNAL(fileCopied(QString)), - this, SIGNAL(fileCopied(QString))); - return m_connection->transferFiles(m_deploySpecs); -} - -} // namespace Internal -} // namespace Qt4ProjectManager diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h b/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h deleted file mode 100644 index 79107d0f60..0000000000 --- a/src/plugins/qt4projectmanager/qt-maemo/maemosshthread.h +++ /dev/null @@ -1,132 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Creator. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef MAEMOSSHTHREAD_H -#define MAEMOSSHTHREAD_H - -#include "maemodeviceconfigurations.h" - -#include <coreplugin/ssh/sshconnection.h> - -#include <QtCore/QByteArray> -#include <QtCore/QList> -#include <QtCore/QMutex> -#include <QtCore/QThread> -#include <QtCore/QWaitCondition> - -namespace Qt4ProjectManager { -namespace Internal { - -template<class SshConnection> class MaemoSshThread : public QThread -{ - Q_DISABLE_COPY(MaemoSshThread) -public: - QString error() const { return m_error; } - bool hasError() const { return !m_error.isEmpty(); } - void stop(); - virtual void run(); - ~MaemoSshThread(); - -protected: - MaemoSshThread(const Core::SshServerInfo &server); - void createConnection(); - bool stopRequested() const { return m_stopRequested; } - void waitForStop(); - - typename SshConnection::Ptr m_connection; - -private: - virtual bool runInternal() = 0; - - const Core::SshServerInfo m_server; - bool m_stopRequested; - QString m_error; - QMutex m_mutex; - QWaitCondition m_waitCond; -}; - - -class MaemoSshRunner : public MaemoSshThread<Core::InteractiveSshConnection> -{ - Q_OBJECT - Q_DISABLE_COPY(MaemoSshRunner) -public: - MaemoSshRunner(const Core::SshServerInfo &server, const QString &command); - -signals: - void remoteOutput(const QString &output); - -private: - virtual bool runInternal(); - Q_SLOT void handleRemoteOutput(); - void initState(); - - static const QByteArray EndMarker; - - const QString m_command; - const char *m_prompt; - int m_endMarkerCount; - bool m_promptEncountered; - QByteArray m_potentialEndMarkerPrefix; -}; - - -class MaemoSshDeployer : public MaemoSshThread<Core::SftpConnection> -{ - Q_OBJECT - Q_DISABLE_COPY(MaemoSshDeployer) -public: - MaemoSshDeployer(const Core::SshServerInfo &server, - const QList<Core::SftpTransferInfo> &deploySpecs); - -signals: - void fileCopied(const QString &filePath); - -private: - virtual bool runInternal(); - - const QList<Core::SftpTransferInfo> m_deploySpecs; -}; - -} // namespace Internal -} // namespace Qt4ProjectManager - -#endif // MAEMOSSHTHREAD_H diff --git a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri index 346c1b8a06..2ea996a8c0 100644 --- a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri +++ b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri @@ -10,7 +10,6 @@ HEADERS += \ $$PWD/maemosettingspage.h \ $$PWD/maemosettingswidget.h \ $$PWD/maemosshconfigdialog.h \ - $$PWD/maemosshthread.h \ $$PWD/maemotoolchain.h \ $$PWD/maemopackagecreationstep.h \ $$PWD/maemopackagecreationfactory.h \ @@ -20,7 +19,11 @@ HEADERS += \ $$PWD/profilewrapper.h \ $$PWD/maemodeployables.h \ $$PWD/maemodeployable.h \ - $$PWD/maemodeployablelistwidget.h + $$PWD/maemodeployablelistwidget.h \ + $$PWD/maemodeploystep.h \ + $$PWD/maemodeploystepwidget.h \ + $$PWD/maemodeploystepfactory.h \ + $$PWD/maemoglobal.h SOURCES += \ $$PWD/maemoconfigtestdialog.cpp \ @@ -33,7 +36,6 @@ SOURCES += \ $$PWD/maemosettingspage.cpp \ $$PWD/maemosettingswidget.cpp \ $$PWD/maemosshconfigdialog.cpp \ - $$PWD/maemosshthread.cpp \ $$PWD/maemotoolchain.cpp \ $$PWD/maemopackagecreationstep.cpp \ $$PWD/maemopackagecreationfactory.cpp \ @@ -42,13 +44,18 @@ SOURCES += \ $$PWD/qemuruntimemanager.cpp \ $$PWD/profilewrapper.cpp \ $$PWD/maemodeployables.cpp \ - $$PWD/maemodeployablelistwidget.cpp + $$PWD/maemodeployablelistwidget.cpp \ + $$PWD/maemodeploystep.cpp \ + $$PWD/maemodeploystepwidget.cpp \ + $$PWD/maemodeploystepfactory.cpp \ + $$PWD/maemoglobal.cpp FORMS += \ $$PWD/maemoconfigtestdialog.ui \ $$PWD/maemosettingswidget.ui \ $$PWD/maemosshconfigdialog.ui \ $$PWD/maemopackagecreationwidget.ui \ - $$PWD/maemodeployablelistwidget.ui + $$PWD/maemodeployablelistwidget.ui \ + $$PWD/maemodeploystepwidget.ui RESOURCES += $$PWD/qt-maemo.qrc diff --git a/src/plugins/qt4projectmanager/qt-s60/s60createpackagestep.cpp b/src/plugins/qt4projectmanager/qt-s60/s60createpackagestep.cpp index 3683accd37..54a88c18aa 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60createpackagestep.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60createpackagestep.cpp @@ -37,6 +37,8 @@ #include <projectexplorer/target.h> #include <projectexplorer/gnumakeparser.h> +#include <QtCore/QDir> + using namespace Qt4ProjectManager::Internal; namespace { diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.cpp b/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.cpp index c92eab72e1..3070b8a212 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.cpp @@ -41,6 +41,7 @@ #include "qt4buildconfiguration.h" #include "qt4projectmanagerconstants.h" #include "s60createpackagestep.h" +#include "qtoutputformatter.h" #include <coreplugin/icore.h> #include <coreplugin/messagemanager.h> @@ -207,6 +208,11 @@ QWidget *S60DeviceRunConfiguration::createConfigurationWidget() return new S60DeviceRunConfigurationWidget(this); } +ProjectExplorer::OutputFormatter *S60DeviceRunConfiguration::createOutputFormatter() const +{ + return new QtOutputFormatter(qt4Target()->qt4Project()); +} + QVariantMap S60DeviceRunConfiguration::toMap() const { QVariantMap map(ProjectExplorer::RunConfiguration::toMap()); diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.h b/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.h index a1cb5d4590..7776692d25 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60devicerunconfiguration.h @@ -74,6 +74,8 @@ public: bool isEnabled(ProjectExplorer::BuildConfiguration *configuration) const; QWidget *createConfigurationWidget(); + ProjectExplorer::OutputFormatter *createOutputFormatter() const; + QString serialPortName() const; void setSerialPortName(const QString &name); diff --git a/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.cpp b/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.cpp index 9ae33ff517..20dc6b8c2c 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.cpp @@ -37,6 +37,7 @@ #include "s60devices.h" #include "qt4buildconfiguration.h" #include "qt4projectmanagerconstants.h" +#include "qtoutputformatter.h" #include <coreplugin/icore.h> #include <coreplugin/messagemanager.h> @@ -131,6 +132,11 @@ QWidget *S60EmulatorRunConfiguration::createConfigurationWidget() return new S60EmulatorRunConfigurationWidget(this); } +ProjectExplorer::OutputFormatter *S60EmulatorRunConfiguration::createOutputFormatter() const +{ + return new QtOutputFormatter(qt4Target()->qt4Project()); +} + QVariantMap S60EmulatorRunConfiguration::toMap() const { QVariantMap map(ProjectExplorer::RunConfiguration::toMap()); diff --git a/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.h b/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.h index 917eac35b3..62f0435e5f 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60emulatorrunconfiguration.h @@ -67,6 +67,8 @@ public: bool isEnabled(ProjectExplorer::BuildConfiguration *configuration) const; QWidget *createConfigurationWidget(); + ProjectExplorer::OutputFormatter *createOutputFormatter() const; + QString executable() const; QVariantMap toMap() const; diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp index 9d91bf4eff..bd2b15dc51 100644 --- a/src/plugins/qt4projectmanager/qt4nodes.cpp +++ b/src/plugins/qt4projectmanager/qt4nodes.cpp @@ -674,7 +674,7 @@ bool Qt4PriFileNode::priFileWritable(const QString &path) switch (Core::EditorManager::promptReadOnlyFile(path, versionControl, core->mainWindow(), false)) { case Core::EditorManager::RO_OpenVCS: if (!versionControl->vcsOpen(path)) { - QMessageBox::warning(core->mainWindow(), tr("Failed!"), tr("Could not open the file for edit with SCC.")); + QMessageBox::warning(core->mainWindow(), tr("Failed!"), tr("Could not open the file for edit with VCS.")); return false; } break; diff --git a/src/plugins/qt4projectmanager/qt4projectmanager.cpp b/src/plugins/qt4projectmanager/qt4projectmanager.cpp index 46039b131a..24536e1f60 100644 --- a/src/plugins/qt4projectmanager/qt4projectmanager.cpp +++ b/src/plugins/qt4projectmanager/qt4projectmanager.cpp @@ -243,6 +243,8 @@ void Qt4Manager::runQMakeContextMenu() void Qt4Manager::runQMake(ProjectExplorer::Project *p, ProjectExplorer::Node *node) { + if (!ProjectExplorer::ProjectExplorerPlugin::instance()->saveModifiedFiles()) + return; Qt4Project *qt4pro = qobject_cast<Qt4Project *>(p); QTC_ASSERT(qt4pro, return); diff --git a/src/plugins/qt4projectmanager/qt4projectmanager.pro b/src/plugins/qt4projectmanager/qt4projectmanager.pro index f4c492ac33..678bd5a9bc 100644 --- a/src/plugins/qt4projectmanager/qt4projectmanager.pro +++ b/src/plugins/qt4projectmanager/qt4projectmanager.pro @@ -44,7 +44,8 @@ HEADERS += qt4projectmanagerplugin.h \ gettingstartedwelcomepage.h \ qt4buildconfiguration.h \ qt4target.h \ - qmakeparser.h + qmakeparser.h \ + qtoutputformatter.h SOURCES += qt4projectmanagerplugin.cpp \ qt4projectmanager.cpp \ qt4project.cpp \ @@ -85,7 +86,8 @@ SOURCES += qt4projectmanagerplugin.cpp \ gettingstartedwelcomepage.cpp \ qt4buildconfiguration.cpp \ qt4target.cpp \ - qmakeparser.cpp + qmakeparser.cpp \ + qtoutputformatter.cpp FORMS += makestep.ui \ qmakestep.ui \ qt4projectconfigwidget.ui \ diff --git a/src/plugins/qt4projectmanager/qt4runconfiguration.cpp b/src/plugins/qt4projectmanager/qt4runconfiguration.cpp index a50d08eca1..d866299aa5 100644 --- a/src/plugins/qt4projectmanager/qt4runconfiguration.cpp +++ b/src/plugins/qt4projectmanager/qt4runconfiguration.cpp @@ -36,6 +36,7 @@ #include "qt4target.h" #include "qt4buildconfiguration.h" #include "qt4projectmanagerconstants.h" +#include "qtoutputformatter.h" #include <coreplugin/icore.h> #include <coreplugin/messagemanager.h> @@ -669,6 +670,11 @@ ProjectExplorer::ToolChain::ToolChainType Qt4RunConfiguration::toolChainType() c return qt4bc->toolChainType(); } +ProjectExplorer::OutputFormatter *Qt4RunConfiguration::createOutputFormatter() const +{ + return new QtOutputFormatter(qt4Target()->qt4Project()); +} + /// /// Qt4RunConfigurationFactory /// This class is used to restore run settings (saved in .user files) diff --git a/src/plugins/qt4projectmanager/qt4runconfiguration.h b/src/plugins/qt4projectmanager/qt4runconfiguration.h index 10ac7960f6..882131c5c5 100644 --- a/src/plugins/qt4projectmanager/qt4runconfiguration.h +++ b/src/plugins/qt4projectmanager/qt4runconfiguration.h @@ -93,6 +93,8 @@ public: // TODO detectQtShadowBuild() ? how did this work ? QVariantMap toMap() const; + ProjectExplorer::OutputFormatter *createOutputFormatter() const; + signals: void commandLineArgumentsChanged(const QString&); void workingDirectoryChanged(const QString&); diff --git a/src/plugins/qt4projectmanager/qt4target.cpp b/src/plugins/qt4projectmanager/qt4target.cpp index 198e2e7eff..4f262dbea0 100644 --- a/src/plugins/qt4projectmanager/qt4target.cpp +++ b/src/plugins/qt4projectmanager/qt4target.cpp @@ -35,6 +35,7 @@ #include "qt4project.h" #include "qt4runconfiguration.h" #include "qt4projectmanagerconstants.h" +#include "qt-maemo/maemodeploystep.h" #include "qt-maemo/maemopackagecreationstep.h" #include "qt-maemo/maemorunconfiguration.h" #include "qt-s60/s60devicerunconfiguration.h" @@ -285,7 +286,10 @@ Qt4BuildConfiguration *Qt4Target::addQt4BuildConfiguration(QString displayName, S60CreatePackageStep *packageStep = new S60CreatePackageStep(bc); bc->insertStep(ProjectExplorer::BuildStep::Deploy, 2, packageStep); } else if (id() == Constants::MAEMO_DEVICE_TARGET_ID) { - bc->insertStep(ProjectExplorer::BuildStep::Deploy, 2, new MaemoPackageCreationStep(bc)); + bc->insertStep(ProjectExplorer::BuildStep::Deploy, 2, + new MaemoPackageCreationStep(bc)); + bc->insertStep(ProjectExplorer::BuildStep::Deploy, 3, + new MaemoDeployStep(bc)); } MakeStep* cleanStep = new MakeStep(bc); diff --git a/src/plugins/qt4projectmanager/qtoptionspage.h b/src/plugins/qt4projectmanager/qtoptionspage.h index 5f066880c0..65a3956a3a 100644 --- a/src/plugins/qt4projectmanager/qtoptionspage.h +++ b/src/plugins/qt4projectmanager/qtoptionspage.h @@ -35,8 +35,6 @@ #include <QtCore/QFutureInterface> #include <QtGui/QWidget> -#include <QtGui/QPixmap> -#include <QtGui/QIcon> QT_BEGIN_NAMESPACE class QTreeWidgetItem; diff --git a/src/plugins/qmlprojectmanager/qmloutputformatter.cpp b/src/plugins/qt4projectmanager/qtoutputformatter.cpp index bd371334db..4fe349be51 100644 --- a/src/plugins/qmlprojectmanager/qmloutputformatter.cpp +++ b/src/plugins/qt4projectmanager/qtoutputformatter.cpp @@ -27,24 +27,28 @@ ** **************************************************************************/ -#include "qmloutputformatter.h" +#include "qtoutputformatter.h" #include <texteditor/basetexteditor.h> +#include <qt4projectmanager/qt4project.h> +#include <QtCore/QFileInfo> #include <QtGui/QPlainTextEdit> using namespace ProjectExplorer; -using namespace QmlProjectManager::Internal; +using namespace Qt4ProjectManager; -QmlOutputFormatter::QmlOutputFormatter(QObject *parent) - : OutputFormatter(parent) +QtOutputFormatter::QtOutputFormatter(Qt4Project *project) + : OutputFormatter() , m_qmlError(QLatin1String("(file:///[^:]+:\\d+:\\d+):")) - , m_linksActive(true) - , m_mousePressed(false) + , m_qtError(QLatin1String("Object::.*in (.*:\\d+)")) + , m_project(project) + { + } -void QmlOutputFormatter::appendApplicationOutput(const QString &text, bool onStdErr) +void QtOutputFormatter::appendApplicationOutput(const QString &text, bool onStdErr) { QTextCharFormat linkFormat; linkFormat.setForeground(plainTextEdit()->palette().link().color()); @@ -52,37 +56,33 @@ void QmlOutputFormatter::appendApplicationOutput(const QString &text, bool onStd linkFormat.setAnchor(true); // Create links from QML errors (anything of the form "file:///...:[line]:[column]:") - int index = 0; - while (m_qmlError.indexIn(text, index) != -1) { + if (m_qmlError.indexIn(text) != -1) { const int matchPos = m_qmlError.pos(1); - const QString leader = text.mid(index, matchPos - index); + const QString leader = text.left(matchPos); append(leader, onStdErr ? StdErrFormat : StdOutFormat); const QString matched = m_qmlError.cap(1); linkFormat.setAnchorHref(matched); append(matched, linkFormat); - index = matchPos + m_qmlError.matchedLength() - 1; - } - append(text.mid(index), onStdErr ? StdErrFormat : StdOutFormat); -} - -void QmlOutputFormatter::mousePressEvent(QMouseEvent * /*e*/) -{ - m_mousePressed = true; -} + int index = matchPos + m_qmlError.matchedLength() - 1; + append(text.mid(index), onStdErr ? StdErrFormat : StdOutFormat); + } else if (m_qtError.indexIn(text) != -1) { + const int matchPos = m_qtError.pos(1); + const QString leader = text.left(matchPos); + append(leader, onStdErr ? StdErrFormat : StdOutFormat); -void QmlOutputFormatter::mouseReleaseEvent(QMouseEvent *e) -{ - m_mousePressed = false; + const QString matched = m_qtError.cap(1); + linkFormat.setAnchorHref(m_qtError.cap(1)); + append(matched, linkFormat); - if (!m_linksActive) { - // Mouse was released, activate links again - m_linksActive = true; - return; + int index = matchPos + m_qtError.matchedLength() - 1; + append(text.mid(index), onStdErr ? StdErrFormat : StdOutFormat); } +} - const QString href = plainTextEdit()->anchorAt(e->pos()); +void QtOutputFormatter::handleLink(const QString &href) +{ if (!href.isEmpty()) { QRegExp qmlErrorLink(QLatin1String("^file://(/[^:]+):(\\d+):(\\d+)")); @@ -91,18 +91,30 @@ void QmlOutputFormatter::mouseReleaseEvent(QMouseEvent *e) const int line = qmlErrorLink.cap(2).toInt(); const int column = qmlErrorLink.cap(3).toInt(); TextEditor::BaseTextEditor::openEditorAt(fileName, line, column - 1); + return; } - } -} -void QmlOutputFormatter::mouseMoveEvent(QMouseEvent *e) -{ - // Cursor was dragged to make a selection, deactivate links - if (m_mousePressed && plainTextEdit()->textCursor().hasSelection()) - m_linksActive = false; - - if (!m_linksActive || plainTextEdit()->anchorAt(e->pos()).isEmpty()) - plainTextEdit()->viewport()->setCursor(Qt::IBeamCursor); - else - plainTextEdit()->viewport()->setCursor(Qt::PointingHandCursor); + QRegExp qtErrorLink(QLatin1String("^(.*):(\\d+)$")); + if (qtErrorLink.indexIn(href) != 1) { + QString fileName = qtErrorLink.cap(1); + const int line = qtErrorLink.cap(2).toInt(); + QFileInfo fi(fileName); + if (fi.isRelative()) { + // Yeah fileName is relative, no suprise + Qt4Project *pro = m_project.data(); + if (pro) { + QString baseName = fi.fileName(); + foreach (const QString &file, pro->files(Project::AllFiles)) { + if (file.endsWith(baseName)) { + // pick the first one... + fileName = file; + break; + } + } + } + } + TextEditor::BaseTextEditor::openEditorAt(fileName, line, 0); + return; + } + } } diff --git a/src/plugins/qmlprojectmanager/qmloutputformatter.h b/src/plugins/qt4projectmanager/qtoutputformatter.h index 8d0e64d6d5..9a039581db 100644 --- a/src/plugins/qmlprojectmanager/qmloutputformatter.h +++ b/src/plugins/qt4projectmanager/qtoutputformatter.h @@ -31,30 +31,29 @@ #define QMLOUTPUTFORMATTER_H #include <projectexplorer/outputformatter.h> - #include <QtCore/QRegExp> +#include <QSharedPointer> -namespace QmlProjectManager { -namespace Internal { +namespace Qt4ProjectManager +{ +class Qt4Project; -class QmlOutputFormatter: public ProjectExplorer::OutputFormatter +class QtOutputFormatter: public ProjectExplorer::OutputFormatter { public: - QmlOutputFormatter(QObject *parent = 0); + QtOutputFormatter(Qt4Project *project); virtual void appendApplicationOutput(const QString &text, bool onStdErr); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); + virtual void handleLink(const QString &href); private: QRegExp m_qmlError; - bool m_linksActive; - bool m_mousePressed; + QRegExp m_qtError; + QWeakPointer<Qt4Project> m_project; }; -} // namespace Internal + } // namespace QmlProjectManager #endif // QMLOUTPUTFORMATTER_H diff --git a/src/plugins/qt4projectmanager/qtversionmanager.h b/src/plugins/qt4projectmanager/qtversionmanager.h index 385c22730f..60f77252f3 100644 --- a/src/plugins/qt4projectmanager/qtversionmanager.h +++ b/src/plugins/qt4projectmanager/qtversionmanager.h @@ -30,8 +30,8 @@ #ifndef QTVERSIONMANAGER_H #define QTVERSIONMANAGER_H -#include <projectexplorer/taskwindow.h> #include <projectexplorer/toolchain.h> +#include <projectexplorer/task.h> #include <QtCore/QHash> #include <QtCore/QSet> diff --git a/src/plugins/texteditor/basetextdocument.cpp b/src/plugins/texteditor/basetextdocument.cpp index be2214d7fc..1e13af7e10 100644 --- a/src/plugins/texteditor/basetextdocument.cpp +++ b/src/plugins/texteditor/basetextdocument.cpp @@ -32,6 +32,7 @@ #include "basetextdocumentlayout.h" #include "basetexteditor.h" #include "storagesettings.h" +#include "syntaxhighlighter.h" #include <QtCore/QFile> #include <QtCore/QDir> @@ -162,7 +163,16 @@ bool BaseTextDocument::save(const QString &fileName) { QTextCursor cursor(m_document); + // When saving the current editor, make sure to maintain the cursor position for undo + Core::IEditor *currentEditor = Core::EditorManager::instance()->currentEditor(); + if (BaseTextEditorEditable *editable = qobject_cast<BaseTextEditorEditable*>(currentEditor)) { + if (editable->file() == this) + cursor = editable->editor()->textCursor(); + } + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::Start); + if (m_storageSettings.m_cleanWhitespace) cleanWhitespace(cursor, m_storageSettings.m_cleanIndentation, m_storageSettings.m_inEntireDocument); if (m_storageSettings.m_addFinalNewLine) @@ -357,7 +367,7 @@ void BaseTextDocument::reload(ReloadFlag flag, ChangeType type) } } -void BaseTextDocument::setSyntaxHighlighter(QSyntaxHighlighter *highlighter) +void BaseTextDocument::setSyntaxHighlighter(SyntaxHighlighter *highlighter) { if (m_highlighter) delete m_highlighter; diff --git a/src/plugins/texteditor/basetextdocument.h b/src/plugins/texteditor/basetextdocument.h index 7447d8167d..cd503276b3 100644 --- a/src/plugins/texteditor/basetextdocument.h +++ b/src/plugins/texteditor/basetextdocument.h @@ -40,11 +40,12 @@ QT_BEGIN_NAMESPACE class QTextCursor; class QTextDocument; -class QSyntaxHighlighter; QT_END_NAMESPACE namespace TextEditor { +class SyntaxHighlighter; + class DocumentMarker : public ITextMarkable { Q_OBJECT @@ -101,8 +102,8 @@ public: virtual void reload(); QTextDocument *document() const { return m_document; } - void setSyntaxHighlighter(QSyntaxHighlighter *highlighter); - QSyntaxHighlighter *syntaxHighlighter() const { return m_highlighter; } + void setSyntaxHighlighter(SyntaxHighlighter *highlighter); + SyntaxHighlighter *syntaxHighlighter() const { return m_highlighter; } inline bool isBinaryData() const { return m_isBinaryData; } @@ -127,7 +128,7 @@ private: TabSettings m_tabSettings; QTextDocument *m_document; DocumentMarker *m_documentMarker; - QSyntaxHighlighter *m_highlighter; + SyntaxHighlighter *m_highlighter; enum LineTerminatorMode { LFLineTerminator, diff --git a/src/plugins/texteditor/basetextdocumentlayout.cpp b/src/plugins/texteditor/basetextdocumentlayout.cpp index 087eb09ccf..c69808f8de 100644 --- a/src/plugins/texteditor/basetextdocumentlayout.cpp +++ b/src/plugins/texteditor/basetextdocumentlayout.cpp @@ -366,28 +366,6 @@ TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor * return NoMatch; } -int TextBlockUserData::lexerState(const QTextBlock &block) -{ - if (!block.isValid()) - return -1; - - int data = block.userState(); - if (data == -1) - return -1; - return data & 0xFF; -} - -void TextBlockUserData::setLexerState(QTextBlock block, int state) -{ - if (!block.isValid()) - return; - - int data = block.userState(); - if (data == -1) - data = 0; - block.setUserState((data & ~0xFF) | (state & 0xFF)); -} - void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data) { if (m_codeFormatterData) @@ -400,6 +378,7 @@ BaseTextDocumentLayout::BaseTextDocumentLayout(QTextDocument *doc) :QPlainTextDocumentLayout(doc) { lastSaveRevision = 0; hasMarks = 0; + m_requiredWidth = 0; } BaseTextDocumentLayout::~BaseTextDocumentLayout() @@ -479,6 +458,23 @@ void BaseTextDocumentLayout::changeBraceDepth(QTextBlock &block, int delta) setBraceDepth(block, braceDepth(block) + delta); } +void BaseTextDocumentLayout::setLexerState(const QTextBlock &block, int state) +{ + if (state == 0) { + if (TextBlockUserData *userData = testUserData(block)) + userData->setLexerState(0); + } else { + userData(block)->setLexerState(qMax(0,state)); + } +} + +int BaseTextDocumentLayout::lexerState(const QTextBlock &block) +{ + if (TextBlockUserData *userData = testUserData(block)) + return userData->lexerState(); + return 0; +} + void BaseTextDocumentLayout::setFoldingIndent(const QTextBlock &block, int indent) { if (indent == 0) { diff --git a/src/plugins/texteditor/basetextdocumentlayout.h b/src/plugins/texteditor/basetextdocumentlayout.h index d6fa481ec7..e70802d009 100644 --- a/src/plugins/texteditor/basetextdocumentlayout.h +++ b/src/plugins/texteditor/basetextdocumentlayout.h @@ -68,6 +68,7 @@ public: : m_folded(false), m_ifdefedOut(false), m_foldingIndent(0), + m_lexerState(0), m_foldingStartIncluded(false), m_foldingEndIncluded(false), m_codeFormatterData(0) @@ -106,15 +107,15 @@ public: static bool findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition = false); static bool findNextBlockClosingParenthesis(QTextCursor *cursor); - int foldingIndent() const { return m_foldingIndent; } - void setFoldingIndent(int indent) { m_foldingIndent = indent; } - void setFoldingStartIncluded(bool included) { m_foldingStartIncluded = included; } - bool foldingStartIncluded() const { return m_foldingStartIncluded; } - void setFoldingEndIncluded(bool included) { m_foldingEndIncluded = included; } - bool foldingEndIncluded() const { return m_foldingEndIncluded; } + inline int foldingIndent() const { return m_foldingIndent; } + inline void setFoldingIndent(int indent) { m_foldingIndent = indent; } + inline void setFoldingStartIncluded(bool included) { m_foldingStartIncluded = included; } + inline bool foldingStartIncluded() const { return m_foldingStartIncluded; } + inline void setFoldingEndIncluded(bool included) { m_foldingEndIncluded = included; } + inline bool foldingEndIncluded() const { return m_foldingEndIncluded; } + inline int lexerState() const { return m_lexerState; } + inline void setLexerState(int state) {m_lexerState = state; } - static int lexerState(const QTextBlock &block); - static void setLexerState(QTextBlock block, int state); CodeFormatterData *codeFormatterData() const { return m_codeFormatterData; } void setCodeFormatterData(CodeFormatterData *data); @@ -124,6 +125,7 @@ private: uint m_folded : 1; uint m_ifdefedOut : 1; uint m_foldingIndent : 16; + uint m_lexerState : 4; uint m_foldingStartIncluded : 1; uint m_foldingEndIncluded : 1; Parentheses m_parentheses; @@ -152,6 +154,8 @@ public: static void changeBraceDepth(QTextBlock &block, int delta); static void setFoldingIndent(const QTextBlock &block, int indent); static int foldingIndent(const QTextBlock &block); + static void setLexerState(const QTextBlock &block, int state); + static int lexerState(const QTextBlock &block); static void changeFoldingIndent(QTextBlock &block, int delta); static bool canFold(const QTextBlock &block); static void doFoldOrUnfold(const QTextBlock& block, bool unfold); @@ -177,6 +181,7 @@ public: void setRequiredWidth(int width); QSizeF documentSize() const; + }; } // namespace TextEditor diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp index a21aba0b5a..6a6fd7451c 100644 --- a/src/plugins/texteditor/basetexteditor.cpp +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -39,6 +39,7 @@ #include "tabsettings.h" #include "texteditorconstants.h" #include "texteditorplugin.h" +#include "syntaxhighlighter.h" #include <aggregation/aggregate.h> #include <coreplugin/actionmanager/actionmanager.h> @@ -572,8 +573,19 @@ void BaseTextEditor::triggerQuickFix() emit requestQuickFix(editableInterface()); } +QString BaseTextEditor::msgTextTooLarge(quint64 size) +{ + return tr("The text is too large to be displayed (%1 MB)."). + arg(size >> 20); +} + bool BaseTextEditor::createNew(const QString &contents) { + if (contents.size() > Core::EditorManager::maxTextFileSize()) { + setPlainText(msgTextTooLarge(contents.size())); + document()->setModified(false); + return false; + } setPlainText(contents); document()->setModified(false); return true; @@ -4027,11 +4039,7 @@ int BaseTextEditor::paragraphSeparatorAboutToBeInserted(QTextCursor &cursor) return 0; // verify that we indeed do have an extra opening brace in the document - int braceDepth = document()->lastBlock().userState(); - if (braceDepth >= 0) - braceDepth >>= 8; - else - braceDepth= 0; + int braceDepth = BaseTextDocumentLayout::braceDepth(document()->lastBlock()); if (braceDepth <= 0) return 0; // braces are all balanced or worse, no need to do anything @@ -4048,9 +4056,16 @@ int BaseTextEditor::paragraphSeparatorAboutToBeInserted(QTextCursor &cursor) const TabSettings &ts = tabSettings(); QTextBlock block = cursor.block(); int indentation = ts.indentationColumn(block.text()); - if (block.next().isValid() - && ts.indentationColumn(block.next().text()) > indentation) - return 0; + + if (block.next().isValid()) { // not the last block + block = block.next(); + //skip all empty blocks + while (block.isValid() && ts.onlySpace(block.text())) + block = block.next(); + if (block.isValid() + && ts.indentationColumn(block.text()) > indentation) + return 0; + } int pos = cursor.position(); @@ -4269,6 +4284,25 @@ int BaseTextEditor::verticalBlockSelection() const return qAbs(b.positionInBlock() - e.positionInBlock()) + d->m_blockSelectionExtraX; } +QRegion BaseTextEditor::translatedLineRegion(int lineStart, int lineEnd) const +{ + QRegion region; + for (int i = lineStart ; i <= lineEnd; i++) { + QTextBlock block = document()->findBlockByNumber(i); + QPoint topLeft = blockBoundingGeometry(block).translated(contentOffset()).topLeft().toPoint(); + + if (block.isValid()) { + QTextLayout *layout = block.layout(); + + for (int i = 0; i < layout->lineCount();i++) { + QTextLine line = layout->lineAt(i); + region += line.naturalTextRect().translated(topLeft).toRect(); + } + } + } + return region; +} + void BaseTextEditor::setFindScope(const QTextCursor &start, const QTextCursor &end, int verticalBlockSelection) { if (start != d->m_findScopeStart || end != d->m_findScopeEnd) { @@ -4631,7 +4665,6 @@ void BaseTextEditor::maybeClearSomeExtraSelections(const QTextCursor &cursor) if (cursor.selectionEnd() - cursor.selectionStart() < smallSelectionSize) return; - d->m_extraSelections[TypeSelection].clear(); d->m_extraSelections[UndefinedSymbolSelection].clear(); d->m_extraSelections[ObjCSelection].clear(); d->m_extraSelections[CodeWarningsSelection].clear(); @@ -4940,7 +4973,7 @@ void BaseTextEditor::setDisplaySettings(const DisplaySettings &ds) setCenterOnScroll(ds.m_centerCursorOnScroll); if (d->m_displaySettings.m_visualizeWhitespace != ds.m_visualizeWhitespace) { - if (QSyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter()) + if (SyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter()) highlighter->rehighlight(); QTextOption option = document()->defaultTextOption(); if (ds.m_visualizeWhitespace) diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index 087d67e642..450c335073 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -216,6 +216,8 @@ public: int verticalBlockSelection() const; + QRegion translatedLineRegion(int lineStart, int lineEnd) const; + public slots: void setDisplayName(const QString &title); @@ -292,6 +294,8 @@ protected: bool canInsertFromMimeData(const QMimeData *source) const; void insertFromMimeData(const QMimeData *source); + static QString msgTextTooLarge(quint64 size); + private: void maybeSelectLine(); @@ -345,7 +349,6 @@ public: FakeVimSelection, OtherSelection, SnippetPlaceholderSelection, - TypeSelection, ObjCSelection, NExtraSelectionKinds }; diff --git a/src/plugins/texteditor/completionsupport.cpp b/src/plugins/texteditor/completionsupport.cpp index 3b06580a74..bd6cc6c12e 100644 --- a/src/plugins/texteditor/completionsupport.cpp +++ b/src/plugins/texteditor/completionsupport.cpp @@ -67,7 +67,7 @@ CompletionSupport::CompletionSupport() void CompletionSupport::performCompletion(const CompletionItem &item) { - item.collector->complete(item); + item.collector->complete(item, m_completionList->typedChar()); m_checkCompletionTrigger = true; } diff --git a/src/plugins/texteditor/completionwidget.cpp b/src/plugins/texteditor/completionwidget.cpp index c8a6eae08a..5b005c17b0 100644 --- a/src/plugins/texteditor/completionwidget.cpp +++ b/src/plugins/texteditor/completionwidget.cpp @@ -199,6 +199,11 @@ void CompletionWidget::showCompletions(int startPos) setFocus(); } +QChar CompletionWidget::typedChar() const +{ + return m_completionListView->m_typedChar; +} + void CompletionWidget::updatePositionAndSize(int startPos) { // Determine size by calculating the space of the visible items @@ -415,6 +420,16 @@ bool CompletionListView::event(QEvent *e) } if (forwardKeys && ! m_quickFix) { + if (ke->text().length() == 1 && currentIndex().isValid() && qApp->focusWidget() == this) { + QChar typedChar = ke->text().at(0); + const CompletionItem &item = m_model->itemAt(currentIndex()); + if (item.collector->typedCharCompletes(item, typedChar)) { + m_typedChar = typedChar; + m_completionWidget->closeList(currentIndex()); + return true; + } + } + m_blockFocusOut = true; QApplication::sendEvent(m_editorWidget, e); m_blockFocusOut = false; diff --git a/src/plugins/texteditor/completionwidget.h b/src/plugins/texteditor/completionwidget.h index ece02904b1..813fd89f30 100644 --- a/src/plugins/texteditor/completionwidget.h +++ b/src/plugins/texteditor/completionwidget.h @@ -61,6 +61,8 @@ public: void setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems); void showCompletions(int startPos); + QChar typedChar() const; + signals: void itemSelected(const TextEditor::CompletionItem &item); void completionListClosed(); @@ -115,6 +117,7 @@ private: CompletionSupport *m_support; QPointer<CompletionInfoFrame> m_infoFrame; QTimer m_infoTimer; + QChar m_typedChar; }; } // namespace Internal diff --git a/src/plugins/texteditor/displaysettings.cpp b/src/plugins/texteditor/displaysettings.cpp index 5c51b7cb09..575d2fd228 100644 --- a/src/plugins/texteditor/displaysettings.cpp +++ b/src/plugins/texteditor/displaysettings.cpp @@ -44,6 +44,7 @@ static const char * const animateMatchingParenthesesKey= "AnimateMatchingParenth static const char * const markTextChangesKey = "MarkTextChanges"; static const char * const autoFoldFirstCommentKey = "AutoFoldFirstComment"; static const char * const centerCursorOnScrollKey = "CenterCursorOnScroll"; +static const char * const integrateDocsIntoTooltips = "IntegrateDocsIntoTooltips"; static const char * const groupPostfix = "DisplaySettings"; namespace TextEditor { @@ -60,7 +61,8 @@ DisplaySettings::DisplaySettings() : m_animateMatchingParentheses(true), m_markTextChanges(true), m_autoFoldFirstComment(true), - m_centerCursorOnScroll(false) + m_centerCursorOnScroll(false), + m_integrateDocsIntoTooltips(true) { } @@ -82,6 +84,7 @@ void DisplaySettings::toSettings(const QString &category, QSettings *s) const s->setValue(QLatin1String(markTextChangesKey), m_markTextChanges); s->setValue(QLatin1String(autoFoldFirstCommentKey), m_autoFoldFirstComment); s->setValue(QLatin1String(centerCursorOnScrollKey), m_centerCursorOnScroll); + s->setValue(QLatin1String(integrateDocsIntoTooltips), m_integrateDocsIntoTooltips); s->endGroup(); } @@ -106,6 +109,7 @@ void DisplaySettings::fromSettings(const QString &category, const QSettings *s) m_markTextChanges = s->value(group + QLatin1String(markTextChangesKey), m_markTextChanges).toBool(); m_autoFoldFirstComment = s->value(group + QLatin1String(autoFoldFirstCommentKey), m_autoFoldFirstComment).toBool(); m_centerCursorOnScroll = s->value(group + QLatin1String(centerCursorOnScrollKey), m_centerCursorOnScroll).toBool(); + m_integrateDocsIntoTooltips = s->value(group + QLatin1String(integrateDocsIntoTooltips), m_integrateDocsIntoTooltips).toBool(); } bool DisplaySettings::equals(const DisplaySettings &ds) const @@ -122,6 +126,7 @@ bool DisplaySettings::equals(const DisplaySettings &ds) const && m_markTextChanges == ds.m_markTextChanges && m_autoFoldFirstComment== ds.m_autoFoldFirstComment && m_centerCursorOnScroll == ds.m_centerCursorOnScroll + && m_integrateDocsIntoTooltips == ds.m_integrateDocsIntoTooltips ; } diff --git a/src/plugins/texteditor/displaysettings.h b/src/plugins/texteditor/displaysettings.h index 374be16969..a421ea7ee3 100644 --- a/src/plugins/texteditor/displaysettings.h +++ b/src/plugins/texteditor/displaysettings.h @@ -57,6 +57,7 @@ struct TEXTEDITOR_EXPORT DisplaySettings bool m_markTextChanges; bool m_autoFoldFirstComment; bool m_centerCursorOnScroll; + bool m_integrateDocsIntoTooltips; bool equals(const DisplaySettings &ds) const; }; diff --git a/src/plugins/texteditor/displaysettingspage.cpp b/src/plugins/texteditor/displaysettingspage.cpp index 6a30c609c5..27d00766c5 100644 --- a/src/plugins/texteditor/displaysettingspage.cpp +++ b/src/plugins/texteditor/displaysettingspage.cpp @@ -93,7 +93,8 @@ QWidget *DisplaySettingsPage::createPage(QWidget *parent) << ' ' << m_d->m_page.animateMatchingParentheses->text() << ' ' << m_d->m_page.enableTextWrapping->text() << ' ' << m_d->m_page.autoFoldFirstComment->text() - << ' ' << m_d->m_page.centerOnScroll->text(); + << ' ' << m_d->m_page.centerOnScroll->text() + << ' ' << m_d->m_page.integrateDocsIntoTooltips->text(); m_d->m_searchKeywords.remove(QLatin1Char('&')); } return w; @@ -121,6 +122,7 @@ void DisplaySettingsPage::settingsFromUI(DisplaySettings &displaySettings) const displaySettings.m_markTextChanges = m_d->m_page.markTextChanges->isChecked(); displaySettings.m_autoFoldFirstComment = m_d->m_page.autoFoldFirstComment->isChecked(); displaySettings.m_centerCursorOnScroll = m_d->m_page.centerOnScroll->isChecked(); + displaySettings.m_integrateDocsIntoTooltips = m_d->m_page.integrateDocsIntoTooltips->isChecked(); } void DisplaySettingsPage::settingsToUI() @@ -138,6 +140,7 @@ void DisplaySettingsPage::settingsToUI() m_d->m_page.markTextChanges->setChecked(displaySettings.m_markTextChanges); m_d->m_page.autoFoldFirstComment->setChecked(displaySettings.m_autoFoldFirstComment); m_d->m_page.centerOnScroll->setChecked(displaySettings.m_centerCursorOnScroll); + m_d->m_page.integrateDocsIntoTooltips->setChecked(displaySettings.m_integrateDocsIntoTooltips); } const DisplaySettings &DisplaySettingsPage::displaySettings() const diff --git a/src/plugins/texteditor/displaysettingspage.ui b/src/plugins/texteditor/displaysettingspage.ui index c402d98a64..28e6b71397 100644 --- a/src/plugins/texteditor/displaysettingspage.ui +++ b/src/plugins/texteditor/displaysettingspage.ui @@ -96,6 +96,13 @@ </property> </widget> </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="integrateDocsIntoTooltips"> + <property name="text"> + <string>Integrate Qt docs into tooltips</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/plugins/texteditor/findincurrentfile.cpp b/src/plugins/texteditor/findincurrentfile.cpp index 736bc78ae4..51f9eb3fe8 100644 --- a/src/plugins/texteditor/findincurrentfile.cpp +++ b/src/plugins/texteditor/findincurrentfile.cpp @@ -57,7 +57,7 @@ QString FindInCurrentFile::id() const return "Current File"; } -QString FindInCurrentFile::name() const +QString FindInCurrentFile::displayName() const { return tr("Current File"); } diff --git a/src/plugins/texteditor/findincurrentfile.h b/src/plugins/texteditor/findincurrentfile.h index 7c6f28c93a..b532e4cfa7 100644 --- a/src/plugins/texteditor/findincurrentfile.h +++ b/src/plugins/texteditor/findincurrentfile.h @@ -54,7 +54,7 @@ public: explicit FindInCurrentFile(Find::SearchResultWindow *resultWindow); QString id() const; - QString name() const; + QString displayName() const; QKeySequence defaultShortcut() const; bool isEnabled() const; QWidget *createConfigWidget(); diff --git a/src/plugins/texteditor/findinfiles.cpp b/src/plugins/texteditor/findinfiles.cpp index 963cb0dea1..a2d45a06b5 100644 --- a/src/plugins/texteditor/findinfiles.cpp +++ b/src/plugins/texteditor/findinfiles.cpp @@ -52,7 +52,7 @@ QString FindInFiles::id() const return "Files on Disk"; } -QString FindInFiles::name() const +QString FindInFiles::displayName() const { return tr("Files on File System"); } diff --git a/src/plugins/texteditor/findinfiles.h b/src/plugins/texteditor/findinfiles.h index baf899439d..45323c1c1d 100644 --- a/src/plugins/texteditor/findinfiles.h +++ b/src/plugins/texteditor/findinfiles.h @@ -52,7 +52,7 @@ public: explicit FindInFiles(Find::SearchResultWindow *resultWindow); QString id() const; - QString name() const; + QString displayName() const; QKeySequence defaultShortcut() const; void findAll(const QString &txt, QTextDocument::FindFlags findFlags); QWidget *createConfigWidget(); diff --git a/src/plugins/texteditor/generichighlighter/highlighter.cpp b/src/plugins/texteditor/generichighlighter/highlighter.cpp index 00f51ade2b..98de86d7fa 100644 --- a/src/plugins/texteditor/generichighlighter/highlighter.cpp +++ b/src/plugins/texteditor/generichighlighter/highlighter.cpp @@ -53,7 +53,7 @@ namespace { const Highlighter::KateFormatMap Highlighter::m_kateFormats; Highlighter::Highlighter(QTextDocument *parent) : - QSyntaxHighlighter(parent), + TextEditor::SyntaxHighlighter(parent), m_regionDepth(0), m_indentationBasedFolding(false), m_tabSettings(0), diff --git a/src/plugins/texteditor/generichighlighter/highlighter.h b/src/plugins/texteditor/generichighlighter/highlighter.h index ee3b94019c..a7135982f2 100644 --- a/src/plugins/texteditor/generichighlighter/highlighter.h +++ b/src/plugins/texteditor/generichighlighter/highlighter.h @@ -31,6 +31,7 @@ #define HIGHLIGHTER_H #include "basetextdocumentlayout.h" +#include <texteditor/syntaxhighlighter.h> #include <QtCore/QString> #include <QtCore/QVector> @@ -52,8 +53,10 @@ class Context; class HighlightDefinition; class ProgressData; -class Highlighter : public QSyntaxHighlighter +class Highlighter : public TextEditor::SyntaxHighlighter { + Q_OBJECT + public: Highlighter(QTextDocument *parent = 0); virtual ~Highlighter(); diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h index 354e45c75d..dc8549635b 100644 --- a/src/plugins/texteditor/icompletioncollector.h +++ b/src/plugins/texteditor/icompletioncollector.h @@ -111,9 +111,19 @@ public: */ virtual void completions(QList<CompletionItem> *completions) = 0; - /* This method should complete the given completion item. + /** + * This method should return true when the given typed character should cause + * the selected completion item to be completed. */ - virtual void complete(const CompletionItem &item) = 0; + virtual bool typedCharCompletes(const CompletionItem &item, QChar typedChar) = 0; + + /** + * This method should complete the given completion item. + * + * \param typedChar Non-null when completion was triggered by typing a + * character. Possible values depend on typedCharCompletes() + */ + virtual void complete(const CompletionItem &item, QChar typedChar) = 0; /* This method gives the completion collector a chance to partially complete * based on a set of items. The general use case is to complete the common @@ -153,6 +163,17 @@ public: IQuickFixCollector(QObject *parent = 0) : ICompletionCollector(parent) {} virtual ~IQuickFixCollector() {} + virtual bool typedCharCompletes(const CompletionItem &, QChar) + { return false; } + + virtual void fix(const TextEditor::CompletionItem &item) = 0; + + virtual void complete(const CompletionItem &item, QChar typedChar) + { + Q_UNUSED(typedChar) + fix(item); + } + virtual bool triggersCompletion(TextEditor::ITextEditable *) { return false; } diff --git a/src/plugins/texteditor/outlinefactory.cpp b/src/plugins/texteditor/outlinefactory.cpp index 421c31f440..f280458ce6 100644 --- a/src/plugins/texteditor/outlinefactory.cpp +++ b/src/plugins/texteditor/outlinefactory.cpp @@ -17,6 +17,11 @@ OutlineWidgetStack::OutlineWidgetStack(OutlineFactory *factory) : { QLabel *label = new QLabel(tr("No outline available"), this); label->setAlignment(Qt::AlignCenter); + + // set background to be white + label->setAutoFillBackground(true); + label->setBackgroundRole(QPalette::Base); + addWidget(label); m_toggleSync = new QToolButton; diff --git a/src/plugins/texteditor/quickfix.cpp b/src/plugins/texteditor/quickfix.cpp index 9918ad4f5b..8e5e024750 100644 --- a/src/plugins/texteditor/quickfix.cpp +++ b/src/plugins/texteditor/quickfix.cpp @@ -171,7 +171,7 @@ void QuickFixCollector::completions(QList<TextEditor::CompletionItem> *quickFixI } } -void QuickFixCollector::complete(const TextEditor::CompletionItem &item) +void QuickFixCollector::fix(const TextEditor::CompletionItem &item) { const int index = item.data.toInt(); diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h index fd731c6c5a..2ad179e0ac 100644 --- a/src/plugins/texteditor/quickfix.h +++ b/src/plugins/texteditor/quickfix.h @@ -111,7 +111,7 @@ public: virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual void complete(const TextEditor::CompletionItem &item); + virtual void fix(const TextEditor::CompletionItem &item); virtual void cleanup(); virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::ITextEditable *editable) = 0; diff --git a/src/plugins/texteditor/refactoringchanges.cpp b/src/plugins/texteditor/refactoringchanges.cpp index 2bee7d79b0..94e7a9d1e3 100644 --- a/src/plugins/texteditor/refactoringchanges.cpp +++ b/src/plugins/texteditor/refactoringchanges.cpp @@ -39,6 +39,11 @@ using namespace TextEditor; +RefactoringChanges::RefactoringChanges() + : m_lineToShow(0) + , m_columnToShow(0) +{} + RefactoringChanges::~RefactoringChanges() {} @@ -149,12 +154,20 @@ QStringList RefactoringChanges::apply() // ### } + if (!m_fileNameToShow.isEmpty()) { + Core::EditorManager *editorManager = Core::EditorManager::instance(); + BaseTextEditor *editor = editorForFile(m_fileNameToShow); + editorManager->activateEditor(editor->editableInterface()); + if (m_lineToShow != -1) + editor->gotoLine(m_lineToShow + 1, m_columnToShow + 1); + } + return changed.toList(); } int RefactoringChanges::positionInFile(const QString &fileName, int line, int column) const { - if (BaseTextEditor *editor = editorForFile(fileName)) { + if (BaseTextEditor *editor = editorForFile(fileName, true)) { return editor->document()->findBlockByNumber(line).position() + column; } else { return -1; @@ -191,3 +204,10 @@ BaseTextEditor *RefactoringChanges::editorForNewFile(const QString &fileName) f.close(); return editorForFile(fileName, true); } + +void RefactoringChanges::openEditor(const QString &fileName, int line, int column) +{ + m_fileNameToShow = fileName; + m_lineToShow = line; + m_columnToShow = column; +} diff --git a/src/plugins/texteditor/refactoringchanges.h b/src/plugins/texteditor/refactoringchanges.h index 93b576496c..f111123acd 100644 --- a/src/plugins/texteditor/refactoringchanges.h +++ b/src/plugins/texteditor/refactoringchanges.h @@ -46,6 +46,7 @@ public: typedef Utils::ChangeSet::Range Range; public: + RefactoringChanges(); virtual ~RefactoringChanges(); void createFile(const QString &fileName, const QString &contents); @@ -63,10 +64,16 @@ public: bool openIfClosed = false); static BaseTextEditor *editorForNewFile(const QString &fileName); + /** line and column are zero-based */ + void openEditor(const QString &fileName, int line, int column); + private: QMap<QString, QString> m_contentsByCreatedFile; QMap<QString, Utils::ChangeSet> m_changesByFile; QMap<QString, QList<Range> > m_indentRangesByFile; + QString m_fileNameToShow; + int m_lineToShow; + int m_columnToShow; }; } // namespace TextEditor diff --git a/src/plugins/texteditor/syntaxhighlighter.cpp b/src/plugins/texteditor/syntaxhighlighter.cpp new file mode 100644 index 0000000000..a2245f7923 --- /dev/null +++ b/src/plugins/texteditor/syntaxhighlighter.cpp @@ -0,0 +1,743 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "syntaxhighlighter.h" + +#include <qtextdocument.h> +#include <qtextlayout.h> +#include <qpointer.h> +#include <qtextobject.h> +#include <qtextcursor.h> +#include <qdebug.h> +#include <qtextedit.h> +#include <qtimer.h> + +using namespace TextEditor; + +class TextEditor::SyntaxHighlighterPrivate +{ + SyntaxHighlighter *q_ptr; + Q_DECLARE_PUBLIC(SyntaxHighlighter) +public: + inline SyntaxHighlighterPrivate() + : q_ptr(0), rehighlightPending(false), inReformatBlocks(false) + {} + + QPointer<QTextDocument> doc; + + void _q_reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlock(const QTextBlock &block, int from, int charsRemoved, int charsAdded); + + inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) { + inReformatBlocks = true; + cursor.beginEditBlock(); + int from = cursor.position(); + cursor.movePosition(operation); + reformatBlocks(from, 0, cursor.position() - from); + cursor.endEditBlock(); + inReformatBlocks = false; + } + + inline void _q_delayedRehighlight() { + if (!rehighlightPending) + return; + rehighlightPending = false; + q_func()->rehighlight(); + } + + void applyFormatChanges(int from, int charsRemoved, int charsAdded); + QVector<QTextCharFormat> formatChanges; + QTextBlock currentBlock; + bool rehighlightPending; + bool inReformatBlocks; +}; + +static bool adjustRange(QTextLayout::FormatRange &range, int from, int charsRemoved, int charsAdded) { + + if (range.start >= from) { + range.start += charsAdded - charsRemoved; + return true; + } else if (range.start + range.length > from) { + range.length += charsAdded - charsRemoved; + return true; + } + return false; +} + +void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, int charsAdded) +{ + bool formatsChanged = false; + + QTextLayout *layout = currentBlock.layout(); + + QList<QTextLayout::FormatRange> ranges = layout->additionalFormats(); + + const int preeditAreaStart = layout->preeditAreaPosition(); + const int preeditAreaLength = layout->preeditAreaText().length(); + bool doAdjustRange = currentBlock.contains(from); + + if (preeditAreaLength != 0) { + QList<QTextLayout::FormatRange>::Iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->format.property(QTextFormat::UserProperty).toBool()) { + if (doAdjustRange) + formatsChanged = adjustRange(*it, from - currentBlock.position(), charsRemoved, charsAdded) + || formatsChanged; + ++it; + } else if (it->start >= preeditAreaStart + && it->start + it->length <= preeditAreaStart + preeditAreaLength) { + ++it; + } else { + it = ranges.erase(it); + formatsChanged = true; + } + } + } else if (!ranges.isEmpty()) { + QList<QTextLayout::FormatRange>::Iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->format.property(QTextFormat::UserProperty).toBool()) { + if (doAdjustRange) + formatsChanged = adjustRange(*it, from - currentBlock.position(), charsRemoved, charsAdded) + || formatsChanged; + ++it; + } else { + it = ranges.erase(it); + formatsChanged = true; + } + } + } + + QTextCharFormat emptyFormat; + + QTextLayout::FormatRange r; + r.start = -1; + + int i = 0; + while (i < formatChanges.count()) { + + while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat) + ++i; + + if (i >= formatChanges.count()) + break; + + r.start = i; + r.format = formatChanges.at(i); + + while (i < formatChanges.count() && formatChanges.at(i) == r.format) + ++i; + + if (i >= formatChanges.count()) + break; + + r.length = i - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + r.start = -1; + } + + if (r.start != -1) { + r.length = formatChanges.count() - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + } + + if (formatsChanged) { + layout->setAdditionalFormats(ranges); + doc->markContentsDirty(currentBlock.position(), currentBlock.length()); + } +} + +void SyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + if (!inReformatBlocks) + reformatBlocks(from, charsRemoved, charsAdded); +} + +void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + rehighlightPending = false; + + QTextBlock block = doc->findBlock(from); + if (!block.isValid()) + return; + + int endPosition; + QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0)); + if (lastBlock.isValid()) + endPosition = lastBlock.position() + lastBlock.length(); + else + endPosition = doc->lastBlock().position() + doc->lastBlock().length(); //doc->docHandle()->length(); + + bool forceHighlightOfNextBlock = false; + + while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) { + const int stateBeforeHighlight = block.userState(); + + reformatBlock(block, from, charsRemoved, charsAdded); + + forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); + + block = block.next(); + } + + formatChanges.clear(); +} + +void SyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block, int from, int charsRemoved, int charsAdded) +{ + Q_Q(SyntaxHighlighter); + + Q_ASSERT_X(!currentBlock.isValid(), "SyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively"); + + currentBlock = block; + + formatChanges.fill(QTextCharFormat(), block.length() - 1); + q->highlightBlock(block.text()); + applyFormatChanges(from, charsRemoved, charsAdded); + + currentBlock = QTextBlock(); +} + +/*! + \class SyntaxHighlighter + \reentrant + + \brief The SyntaxHighlighter class allows you to define syntax + highlighting rules, and in addition you can use the class to query + a document's current formatting or user data. + + \since 4.1 + + \ingroup richtext-processing + + The SyntaxHighlighter class is a base class for implementing + QTextEdit syntax highlighters. A syntax highligher automatically + highlights parts of the text in a QTextEdit, or more generally in + a QTextDocument. Syntax highlighters are often used when the user + is entering text in a specific format (for example source code) + and help the user to read the text and identify syntax errors. + + To provide your own syntax highlighting, you must subclass + SyntaxHighlighter and reimplement highlightBlock(). + + When you create an instance of your SyntaxHighlighter subclass, + pass it the QTextEdit or QTextDocument that you want the syntax + highlighting to be applied to. For example: + + \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 0 + + After this your highlightBlock() function will be called + automatically whenever necessary. Use your highlightBlock() + function to apply formatting (e.g. setting the font and color) to + the text that is passed to it. SyntaxHighlighter provides the + setFormat() function which applies a given QTextCharFormat on + the current text block. For example: + + \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 1 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text is deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment": + + \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 2 + + In the example above, we first set the current block state to + 0. Then, if the previous block ended within a comment, we higlight + from the beginning of the current block (\c {startIndex = + 0}). Otherwise, we search for the given start expression. If the + specified end expression cannot be found in the text block, we + change the current block state by calling setCurrentBlockState(), + and make sure that the rest of the block is higlighted. + + In addition you can query the current formatting and user data + using the format() and currentBlockUserData() functions + respectively. You can also attach user data to the current text + block using the setCurrentBlockUserData() function. + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. For an example, see the + setCurrentBlockUserData() documentation. + + \sa QTextEdit, {Syntax Highlighter Example} +*/ + +/*! + Constructs a SyntaxHighlighter with the given \a parent. +*/ +SyntaxHighlighter::SyntaxHighlighter(QObject *parent) + : QObject(parent), d_ptr(new SyntaxHighlighterPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Constructs a SyntaxHighlighter and installs it on \a parent. + The specified QTextDocument also becomes the owner of the + SyntaxHighlighter. +*/ +SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) + : QObject(parent), d_ptr(new SyntaxHighlighterPrivate) +{ + d_ptr->q_ptr = this; + setDocument(parent); +} + +/*! + Constructs a SyntaxHighlighter and installs it on \a parent 's + QTextDocument. The specified QTextEdit also becomes the owner of + the SyntaxHighlighter. +*/ +SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent) + : QObject(parent), d_ptr(new SyntaxHighlighterPrivate) +{ + d_ptr->q_ptr = this; + setDocument(parent->document()); +} + +/*! + Destructor. Uninstalls this syntax highlighter from the text document. +*/ +SyntaxHighlighter::~SyntaxHighlighter() +{ + setDocument(0); +} + +/*! + Installs the syntax highlighter on the given QTextDocument \a doc. + A SyntaxHighlighter can only be used with one document at a time. +*/ +void SyntaxHighlighter::setDocument(QTextDocument *doc) +{ + Q_D(SyntaxHighlighter); + if (d->doc) { + disconnect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + + QTextCursor cursor(d->doc); + cursor.beginEditBlock(); + for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next()) + blk.layout()->clearAdditionalFormats(); + cursor.endEditBlock(); + } + d->doc = doc; + if (d->doc) { + connect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + d->rehighlightPending = true; + QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight())); + } +} + +/*! + Returns the QTextDocument on which this syntax highlighter is + installed. +*/ +QTextDocument *SyntaxHighlighter::document() const +{ + Q_D(const SyntaxHighlighter); + return d->doc; +} + +/*! + \since 4.2 + + Reapplies the highlighting to the whole document. + + \sa rehighlightBlock() +*/ +void SyntaxHighlighter::rehighlight() +{ + Q_D(SyntaxHighlighter); + if (!d->doc) + return; + + QTextCursor cursor(d->doc); + d->rehighlight(cursor, QTextCursor::End); +} + +/*! + \since 4.6 + + Reapplies the highlighting to the given QTextBlock \a block. + + \sa rehighlight() +*/ +void SyntaxHighlighter::rehighlightBlock(const QTextBlock &block) +{ + Q_D(SyntaxHighlighter); + if (!d->doc || !block.isValid() || block.document() != d->doc) + return; + + const bool rehighlightPending = d->rehighlightPending; + + QTextCursor cursor(block); + d->rehighlight(cursor, QTextCursor::EndOfBlock); + + if (rehighlightPending) + d->rehighlightPending = rehighlightPending; +} + +/*! + \fn void SyntaxHighlighter::highlightBlock(const QString &text) + + Highlights the given text block. This function is called when + necessary by the rich text engine, i.e. on text blocks which have + changed. + + To provide your own syntax highlighting, you must subclass + SyntaxHighlighter and reimplement highlightBlock(). In your + reimplementation you should parse the block's \a text and call + setFormat() as often as necessary to apply any font and color + changes that you require. For example: + + \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 3 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text gets deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment". For a text block + that ended in the middle of a comment you'd set 1 using + setCurrentBlockState, and for other paragraphs you'd set 0. + In your parsing code if the return value of previousBlockState() + is 1, you would highlight the text as a C++ comment until you + reached the closing \c{*}\c{/}. + + \sa previousBlockState(), setFormat(), setCurrentBlockState() +*/ + +/*! + This function is applied to the syntax highlighter's current text + block (i.e. the text that is passed to the highlightBlock() + function). + + The specified \a format is applied to the text from the \a start + position for a length of \a count characters (if \a count is 0, + nothing is done). The formatting properties set in \a format are + merged at display time with the formatting information stored + directly in the document, for example as previously set with + QTextCursor's functions. Note that the document itself remains + unmodified by the format set through this function. + + \sa format(), highlightBlock() +*/ +void SyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format) +{ + Q_D(SyntaxHighlighter); + if (start < 0 || start >= d->formatChanges.count()) + return; + + const int end = qMin(start + count, d->formatChanges.count()); + for (int i = start; i < end; ++i) + d->formatChanges[i] = format; +} + +/*! + \overload + + The specified \a color is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void SyntaxHighlighter::setFormat(int start, int count, const QColor &color) +{ + QTextCharFormat format; + format.setForeground(color); + setFormat(start, count, format); +} + +/*! + \overload + + The specified \a font is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void SyntaxHighlighter::setFormat(int start, int count, const QFont &font) +{ + QTextCharFormat format; + format.setFont(font); + setFormat(start, count, format); +} + +/*! + \fn QTextCharFormat SyntaxHighlighter::format(int position) const + + Returns the format at \a position inside the syntax highlighter's + current text block. +*/ +QTextCharFormat SyntaxHighlighter::format(int pos) const +{ + Q_D(const SyntaxHighlighter); + if (pos < 0 || pos >= d->formatChanges.count()) + return QTextCharFormat(); + return d->formatChanges.at(pos); +} + +/*! + Returns the end state of the text block previous to the + syntax highlighter's current block. If no value was + previously set, the returned value is -1. + + \sa highlightBlock(), setCurrentBlockState() +*/ +int SyntaxHighlighter::previousBlockState() const +{ + Q_D(const SyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + const QTextBlock previous = d->currentBlock.previous(); + if (!previous.isValid()) + return -1; + + return previous.userState(); +} + +/*! + Returns the state of the current text block. If no value is set, + the returned value is -1. +*/ +int SyntaxHighlighter::currentBlockState() const +{ + Q_D(const SyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + return d->currentBlock.userState(); +} + +/*! + Sets the state of the current text block to \a newState. + + \sa highlightBlock() +*/ +void SyntaxHighlighter::setCurrentBlockState(int newState) +{ + Q_D(SyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserState(newState); +} + +/*! + Attaches the given \a data to the current text block. The + ownership is passed to the underlying text document, i.e. the + provided QTextBlockUserData object will be deleted if the + corresponding text block gets deleted. + + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. + + For example while parsing the text, you can keep track of + parenthesis characters that you encounter ('{[(' and the like), + and store their relative position and the actual QChar in a simple + class derived from QTextBlockUserData: + + \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 4 + + During cursor navigation in the associated editor, you can ask the + current QTextBlock (retrieved using the QTextCursor::block() + function) if it has a user data object set and cast it to your \c + BlockData object. Then you can check if the current cursor + position matches with a previously recorded parenthesis position, + and, depending on the type of parenthesis (opening or closing), + find the next opening or closing parenthesis on the same level. + + In this way you can do a visual parenthesis matching and highlight + from the current cursor position to the matching parenthesis. That + makes it easier to spot a missing parenthesis in your code and to + find where a corresponding opening/closing parenthesis is when + editing parenthesis intensive code. + + \sa QTextBlock::setUserData() +*/ +void SyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data) +{ + Q_D(SyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserData(data); +} + +/*! + Returns the QTextBlockUserData object previously attached to the + current text block. + + \sa QTextBlock::userData(), setCurrentBlockUserData() +*/ +QTextBlockUserData *SyntaxHighlighter::currentBlockUserData() const +{ + Q_D(const SyntaxHighlighter); + if (!d->currentBlock.isValid()) + return 0; + + return d->currentBlock.userData(); +} + +/*! + \since 4.4 + + Returns the current text block. +*/ +QTextBlock SyntaxHighlighter::currentBlock() const +{ + Q_D(const SyntaxHighlighter); + return d->currentBlock; +} + +void SyntaxHighlighter::setExtraAdditionalFormats(const QTextBlock& block, + const QList<QTextLayout::FormatRange> &formats) +{ + +// qDebug() << "setAdditionalFormats() on block" << block.blockNumber(); +// for (int i = 0; i < overrides.count(); ++i) +// qDebug() << " from " << overrides.at(i).start << "length" +// << overrides.at(i).length +// << "color:" << overrides.at(i).format.foreground().color(); + Q_D(SyntaxHighlighter); + + if (block.layout() == 0) + return; + + QList<QTextLayout::FormatRange> all = block.layout()->additionalFormats(); + + bool modified = false; + + int skip = 0; + + QList<QTextLayout::FormatRange>::Iterator it = all.begin(); + while (it != all.end()) { + if (it->format.property(QTextFormat::UserProperty).toBool()) { + if (skip < formats.size() + && it->start == formats.at(skip).start + && it->length == formats.at(skip).length) { + ++skip; + ++it; + } else { + it = all.erase(it); + modified = true; + } + } else { + ++it; + } + } + + if (!modified && skip == formats.length()) + return; // skip'em all + + for (int i = skip; i < formats.length(); ++i) { + QTextLayout::FormatRange range = formats.at(i); + range.format.setProperty(QTextFormat::UserProperty, true); + all.append(range); + } + + bool wasInReformatBlocks = d->inReformatBlocks; + d->inReformatBlocks = true; + block.layout()->setAdditionalFormats(all); + document()->markContentsDirty(block.position(), block.length()-1); + d->inReformatBlocks = wasInReformatBlocks; +} + +#include "moc_syntaxhighlighter.cpp" diff --git a/src/plugins/texteditor/syntaxhighlighter.h b/src/plugins/texteditor/syntaxhighlighter.h new file mode 100644 index 0000000000..5b6722b5a3 --- /dev/null +++ b/src/plugins/texteditor/syntaxhighlighter.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TEXTEDITOR_SYNTAXHIGHLIGHTER_H +#define TEXTEDITOR_SYNTAXHIGHLIGHTER_H + +#include "texteditor_global.h" +#include <QtCore/qglobal.h> +#include <QtCore/qobject.h> +#include <QtGui/qtextobject.h> +#include <QtGui/QTextLayout> + +QT_BEGIN_NAMESPACE +class QTextDocument; +class QSyntaxHighlighterPrivate; +class QTextCharFormat; +class QFont; +class QColor; +class QTextBlockUserData; +class QTextEdit; +QT_END_NAMESPACE + +namespace TextEditor { + +class SyntaxHighlighterPrivate; + +class TEXTEDITOR_EXPORT SyntaxHighlighter : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(SyntaxHighlighter) +public: + SyntaxHighlighter(QObject *parent); + SyntaxHighlighter(QTextDocument *parent); + SyntaxHighlighter(QTextEdit *parent); + virtual ~SyntaxHighlighter(); + + void setDocument(QTextDocument *doc); + QTextDocument *document() const; + + void setExtraAdditionalFormats(const QTextBlock& block, const QList<QTextLayout::FormatRange> &formats); + +public Q_SLOTS: + void rehighlight(); + void rehighlightBlock(const QTextBlock &block); + +protected: + virtual void highlightBlock(const QString &text) = 0; + + void setFormat(int start, int count, const QTextCharFormat &format); + void setFormat(int start, int count, const QColor &color); + void setFormat(int start, int count, const QFont &font); + QTextCharFormat format(int pos) const; + + int previousBlockState() const; + int currentBlockState() const; + void setCurrentBlockState(int newState); + + void setCurrentBlockUserData(QTextBlockUserData *data); + QTextBlockUserData *currentBlockUserData() const; + + QTextBlock currentBlock() const; + +private: + Q_DISABLE_COPY(SyntaxHighlighter) + Q_PRIVATE_SLOT(d_ptr, void _q_reformatBlocks(int from, int charsRemoved, int charsAdded)) + Q_PRIVATE_SLOT(d_ptr, void _q_delayedRehighlight()) + + QScopedPointer<SyntaxHighlighterPrivate> d_ptr; +}; + +} // end of namespace TextEditor + +#endif // TEXTEDITOR_SYNTAXHIGHLIGHTER_H diff --git a/src/plugins/texteditor/tabsettings.h b/src/plugins/texteditor/tabsettings.h index 3a15787b09..ae63e116a8 100644 --- a/src/plugins/texteditor/tabsettings.h +++ b/src/plugins/texteditor/tabsettings.h @@ -59,6 +59,7 @@ struct TEXTEDITOR_EXPORT TabSettings int lineIndentPosition(const QString &text) const; int firstNonSpace(const QString &text) const; + inline bool onlySpace(const QString &text) const { return firstNonSpace(text) == text.length(); } int columnAt(const QString &text, int position) const; int spacesLeftFromPosition(const QString &text, int position) const; int indentedColumn(int column, bool doIndent = true) const; diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro index 931f561490..55cb8c92c6 100644 --- a/src/plugins/texteditor/texteditor.pro +++ b/src/plugins/texteditor/texteditor.pro @@ -42,6 +42,7 @@ SOURCES += texteditorplugin.cpp \ normalindenter.cpp \ indenter.cpp \ quickfix.cpp \ + syntaxhighlighter.cpp \ generichighlighter/itemdata.cpp \ generichighlighter/specificrules.cpp \ generichighlighter/rule.cpp \ @@ -103,6 +104,7 @@ HEADERS += texteditorplugin.h \ normalindenter.h \ indenter.h \ quickfix.h \ + syntaxhighlighter.h \ generichighlighter/reuse.h \ generichighlighter/itemdata.h \ generichighlighter/specificrules.h \ diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp index e314ae3de3..3c68486cac 100644 --- a/src/plugins/texteditor/texteditorplugin.cpp +++ b/src/plugins/texteditor/texteditorplugin.cpp @@ -157,7 +157,7 @@ void TextEditorPlugin::extensionsInitialized() ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance(); - m_searchResultWindow = pluginManager->getObject<Find::SearchResultWindow>(); + m_searchResultWindow = Find::SearchResultWindow::instance(); m_outlineFactory->setWidgetFactories(pluginManager->getObjects<TextEditor::IOutlineWidgetFactory>()); diff --git a/src/plugins/vcsbase/baseannotationhighlighter.cpp b/src/plugins/vcsbase/baseannotationhighlighter.cpp index 7ce6058453..b272ea03ae 100644 --- a/src/plugins/vcsbase/baseannotationhighlighter.cpp +++ b/src/plugins/vcsbase/baseannotationhighlighter.cpp @@ -47,7 +47,7 @@ struct BaseAnnotationHighlighterPrivate { BaseAnnotationHighlighter::BaseAnnotationHighlighter(const ChangeNumbers &changeNumbers, QTextDocument *document) : - QSyntaxHighlighter(document), + TextEditor::SyntaxHighlighter(document), m_d(new BaseAnnotationHighlighterPrivate) { setChangeNumbers(changeNumbers); diff --git a/src/plugins/vcsbase/baseannotationhighlighter.h b/src/plugins/vcsbase/baseannotationhighlighter.h index 4119adb109..2859745aed 100644 --- a/src/plugins/vcsbase/baseannotationhighlighter.h +++ b/src/plugins/vcsbase/baseannotationhighlighter.h @@ -31,7 +31,7 @@ #define BASEANNOTATIONHIGHLIGHTER_H #include "vcsbase_global.h" - +#include <texteditor/syntaxhighlighter.h> #include <QtCore/QMap> #include <QtCore/QSet> #include <QtGui/QSyntaxHighlighter> @@ -47,7 +47,7 @@ struct BaseAnnotationHighlighterPrivate; // 112: text1 <color 1> // 113: text2 <color 2> // 112: text3 <color 1> -class VCSBASE_EXPORT BaseAnnotationHighlighter : public QSyntaxHighlighter +class VCSBASE_EXPORT BaseAnnotationHighlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT public: diff --git a/src/plugins/vcsbase/diffhighlighter.cpp b/src/plugins/vcsbase/diffhighlighter.cpp index 7628763d62..6b1ca5dee1 100644 --- a/src/plugins/vcsbase/diffhighlighter.cpp +++ b/src/plugins/vcsbase/diffhighlighter.cpp @@ -88,7 +88,7 @@ DiffFormats DiffHighlighterPrivate::analyzeLine(const QString &text) const // --- DiffHighlighter DiffHighlighter::DiffHighlighter(const QRegExp &filePattern, QTextDocument *document) : - QSyntaxHighlighter(document), + TextEditor::SyntaxHighlighter(document), m_d(new DiffHighlighterPrivate(filePattern)) { } diff --git a/src/plugins/vcsbase/diffhighlighter.h b/src/plugins/vcsbase/diffhighlighter.h index a1a9c3d082..346b87ea3d 100644 --- a/src/plugins/vcsbase/diffhighlighter.h +++ b/src/plugins/vcsbase/diffhighlighter.h @@ -31,8 +31,7 @@ #define DIFFHIGHLIGHTER_H #include "vcsbase_global.h" - -#include <QtGui/QSyntaxHighlighter> +#include <texteditor/syntaxhighlighter.h> #include <QtGui/QTextCharFormat> #include <QtCore/QVector> @@ -66,7 +65,7 @@ struct DiffHighlighterPrivate; * \endcode * */ -class VCSBASE_EXPORT DiffHighlighter : public QSyntaxHighlighter +class VCSBASE_EXPORT DiffHighlighter : public TextEditor::SyntaxHighlighter { Q_OBJECT public: diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp index 5186570852..af18459009 100644 --- a/src/plugins/vcsbase/vcsbaseeditor.cpp +++ b/src/plugins/vcsbase/vcsbaseeditor.cpp @@ -627,7 +627,11 @@ void VCSBaseEditor::jumpToChangeFromDiff(QTextCursor cursor) void VCSBaseEditor::setPlainTextData(const QByteArray &data) { - setPlainText(codec()->toUnicode(data)); + if (data.size() > Core::EditorManager::maxTextFileSize()) { + setPlainText(msgTextTooLarge(data.size())); + } else { + setPlainText(codec()->toUnicode(data)); + } } void VCSBaseEditor::setFontSettings(const TextEditor::FontSettings &fs) |