diff --git a/interfaces/iuicontroller.h b/interfaces/iuicontroller.h --- a/interfaces/iuicontroller.h +++ b/interfaces/iuicontroller.h @@ -130,6 +130,16 @@ virtual void registerStatus(QObject* status) = 0; /** + * Shows an assistant popup at bottom within the current central widget + * @p assistant the assistant that will be shown in a popup */ + virtual void popUpAssistant(const QExplicitlySharedDataPointer& assistant) = 0; + + /** + * Hides the assistant if it is currently being shown + */ + virtual void hideAssistant() = 0; + + /** * This is meant to be used by IDocument subclasses to initialize the * Sublime::Document. */ diff --git a/language/assistant/assistantcompletionmodel.h b/language/assistant/assistantcompletionmodel.h deleted file mode 100644 --- a/language/assistant/assistantcompletionmodel.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2015 Olivier de Gaalon - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef ASSISTANTCOMPLETIONMODEL_H -#define ASSISTANTCOMPLETIONMODEL_H - -#include - -#include - -namespace KDevelop { - -class IAssistantAction; - -class AssistantCompletionModel : public KTextEditor::CodeCompletionModel -{ - Q_OBJECT -public: - AssistantCompletionModel(QObject* parent = {}); - - void setActions(const QList> &actions); - - void executeCompletionItem(KTextEditor::View* view, const KTextEditor::Range& word, const QModelIndex& index) const override; - - QVariant data(const QModelIndex& index, const int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - -signals: - void hasCompletions(); - -private: - IAssistantAction *actionForIndex(const QModelIndex& index) const; - - QList> m_actions; -}; - -} - -#endif // ASSISTANTCOMPLETIONMODEL_H diff --git a/language/assistant/assistantcompletionmodel.cpp b/language/assistant/assistantcompletionmodel.cpp deleted file mode 100644 --- a/language/assistant/assistantcompletionmodel.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - Copyright 2015 Olivier de Gaalon - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "assistantcompletionmodel.h" - -#include "staticassistantsmanager.h" - -#include "interfaces/iassistant.h" -#include "interfaces/icore.h" -#include "interfaces/ilanguagecontroller.h" - -namespace KDevelop { - -AssistantCompletionModel::AssistantCompletionModel(QObject* parent) - : KTextEditor::CodeCompletionModel(parent) -{ -} - -void AssistantCompletionModel::executeCompletionItem(KTextEditor::View*, const KTextEditor::Range&, const QModelIndex& index) const -{ - if (auto action = actionForIndex(index)) { - action->execute(); - } -} - -void AssistantCompletionModel::setActions(const QList& actions) -{ - if (m_actions == actions) { - return; - } - - beginResetModel(); - m_actions = actions; - endResetModel(); - - if (rowCount()) { - emit hasCompletions(); - } -} - -QVariant AssistantCompletionModel::data(const QModelIndex& index, const int role) const -{ - if (role == Qt::DisplayRole && index.column() == Name) { - if (auto action = actionForIndex(index)) { - return action->description(); - } - } else if (role == ArgumentHintDepth) { - return 1; - } - return {}; -} - -QModelIndex AssistantCompletionModel::index(int row, int column, const QModelIndex& parent) const -{ - if (!hasIndex(row, column, parent)) - return {}; - - return createIndex(row, column); -} - -int AssistantCompletionModel::rowCount(const QModelIndex& parent) const -{ - return parent.isValid() ? 0 : m_actions.size(); -} - -IAssistantAction* AssistantCompletionModel::actionForIndex(const QModelIndex& index) const -{ - if (index.row() < 0 || index.row() >= m_actions.size()) { - return nullptr; - } - - return m_actions.at(index.row()).data(); -} - -} diff --git a/language/assistant/staticassistantsmanager.h b/language/assistant/staticassistantsmanager.h --- a/language/assistant/staticassistantsmanager.h +++ b/language/assistant/staticassistantsmanager.h @@ -65,6 +65,9 @@ public slots: void hideAssistant(); +signals: + void activeAssistantChanged(); + private: struct Private; QScopedPointer const d; diff --git a/language/assistant/staticassistantsmanager.cpp b/language/assistant/staticassistantsmanager.cpp --- a/language/assistant/staticassistantsmanager.cpp +++ b/language/assistant/staticassistantsmanager.cpp @@ -17,7 +17,6 @@ Boston, MA 02110-1301, USA. */ -#include "assistantcompletionmodel.h" #include "staticassistantsmanager.h" #include "util/debug.h" @@ -36,7 +35,6 @@ #include #include -#include #include using namespace KDevelop; @@ -54,6 +52,7 @@ } void eventuallyStartAssistant(); + void startAssistant(KDevelop::IAssistant::Ptr assistant); void checkAssistantForProblems(KDevelop::TopDUContext* top); void documentLoaded(KDevelop::IDocument*); @@ -63,7 +62,6 @@ void documentActivated(KDevelop::IDocument*); void cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&); void timeout(); - void setActiveAssistant(const IAssistant::Ptr& assistant); StaticAssistantsManager* q; @@ -74,7 +72,6 @@ QList m_registeredAssistants; bool m_activeProblemAssistant = false; QTimer* m_timer; - AssistantCompletionModel* m_completionModel; SafeDocumentPointer m_eventualDocument; KTextEditor::Range m_eventualRange; @@ -101,20 +98,6 @@ foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { d->documentLoaded(document); } - - d->m_completionModel = new AssistantCompletionModel(this); - connect(d->m_completionModel, &AssistantCompletionModel::hasCompletions, this, [this]{ - if (auto view = d->m_currentView.data()) { - // We cannot use KTextEditor::CodeCompletionInterface::startCompletion for now - // It randomly disappears and refuses to invoke in many/various situations - // Would be very nice to fix this in KTextEditor, because we /really/ want to - // be able to invoke completion only on the assistants model as we could with - // CodeCompletionInterface::startCompletion - // HACK: private internal slot invocation - QMetaObject::invokeMethod(view, "userInvokedCompletion"); - } - }); - new KDevelop::CodeCompletion(this, d->m_completionModel, {}); } StaticAssistantsManager::~StaticAssistantsManager() @@ -158,7 +141,9 @@ void StaticAssistantsManager::hideAssistant() { - d->setActiveAssistant({}); + d->m_activeAssistant = QExplicitlySharedDataPointer(); + d->m_activeProblemAssistant = false; + emit activeAssistantChanged(); } void StaticAssistantsManager::Private::textInserted(Document* document, const Cursor& cursor, const QString& text) @@ -207,7 +192,7 @@ assistant->textChanged(view, m_eventualRange, m_eventualRemovedText); if (assistant->isUseful()) { - setActiveAssistant(IAssistant::Ptr(assistant.data())); + startAssistant(IAssistant::Ptr(assistant.data())); break; } } @@ -219,6 +204,30 @@ m_eventualRemovedText.clear(); } +void StaticAssistantsManager::Private::startAssistant(IAssistant::Ptr assistant) +{ + if (assistant == m_activeAssistant) { + return; + } + + if (m_activeAssistant) { + m_activeAssistant->doHide(); + } + + if (!m_currentView) + return; + + m_activeAssistant = assistant; + if (m_activeAssistant) { + connect(m_activeAssistant.data(), &IAssistant::hide, q, &StaticAssistantsManager::hideAssistant, Qt::UniqueConnection); + ICore::self()->uiController()->popUpAssistant(IAssistant::Ptr(m_activeAssistant.data())); + + m_assistantStartedAt = m_currentView.data()->cursorPosition(); + } + + emit q->activeAssistantChanged(); +} + void StaticAssistantsManager::Private::updateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) { if (url != m_currentDocument) { @@ -280,7 +289,7 @@ if (m_currentView && m_currentView.data()->cursorPosition().line() == problem->range().start.line) { IAssistant::Ptr solution = problem->solutionAssistant(); if(solution) { - setActiveAssistant(solution); + startAssistant(solution); m_activeProblemAssistant = true; break; } @@ -303,34 +312,4 @@ } } -void StaticAssistantsManager::Private::setActiveAssistant(const IAssistant::Ptr& assistant) -{ - if (assistant == m_activeAssistant) { - return; - } - - m_activeAssistant = QExplicitlySharedDataPointer(); - - if (m_activeAssistant) { - m_activeAssistant->doHide(); - m_activeAssistant->disconnect(q); - m_activeAssistant->disconnect(m_completionModel); - } - - m_activeAssistant = assistant; - - if (auto activeAssistant = m_activeAssistant.data()) { - auto updateActions = [=]{ m_completionModel->setActions(activeAssistant->actions()); }; - connect(activeAssistant, &IAssistant::hide, q, &StaticAssistantsManager::hideAssistant); - connect(activeAssistant, &IAssistant::actionsChanged, m_completionModel, updateActions); - if (m_currentView) { - m_assistantStartedAt = m_currentView.data()->cursorPosition(); - updateActions(); - } - } else { - m_activeProblemAssistant = false; - m_completionModel->setActions({}); - } -} - #include "moc_staticassistantsmanager.cpp" diff --git a/shell/AssistantButton.qml b/shell/AssistantButton.qml new file mode 100644 --- /dev/null +++ b/shell/AssistantButton.qml @@ -0,0 +1,101 @@ +/* + Copyright 2014 Sven Brauch + Copyright 2014 Kevin Funk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.2 + +// Component which provides a single button for the assistant widget. + +Rectangle { + id: root + + property color foreground + property color background + property color highlight + + property bool highlighted: false + // text on the button + property string text + // text in the shortcut field + property int button + // emitted when the button is clicked with the mouse + signal triggered() + + y: -1 + width: text.width + 4 + height: number.height + 4 + + color: Qt.lighter(root.background, 1.5) + border.color: Qt.lighter(root.foreground, 1.5) + + Behavior on opacity { + NumberAnimation { duration: 150; } + } + MouseArea { + id: mouseArea + + anchors.fill: parent + onClicked: root.triggered() + + hoverEnabled: true + + Row { + // row containing the separators, shortcut text, and button text + z: 3 + id: text + anchors.centerIn: parent + spacing: 0 + Rectangle { width: 2; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding + Text { + // shortcut key + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 1 + id: number + color: root.foreground + text: button + z: 2 + } + Rectangle { width: 3; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding + Rectangle { y: 1; width: 1; color: root.foreground; height: root.height - 1; opacity: 0.3 } // line + Rectangle { width: 4; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding + Text { + // actual button text + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 1 + color: root.foreground + text: root.text + textFormat: Text.PlainText + } + Rectangle { width: 2; height: 1; color: Qt.rgba(0, 0, 0, 0) } // padding + } + Rectangle { + // the background color for the shortcut key box, invisible by default. + id: highlightArea + Behavior on opacity { + NumberAnimation { duration: 200 } + } + opacity: (root.highlighted || mouseArea.containsMouse) ? 0.5 : 0.0 + x: 1 + y: 1 + z: 1 + height: text.height + width: number.width + 6 + color: root.highlight + } + } +} diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -11,6 +11,7 @@ workingsets/workingsetwidget.cpp workingsets/closedworkingsetswidget.cpp workingsets/workingsethelpers.cpp + assistantpopup.cpp mainwindow.cpp mainwindow_p.cpp plugincontroller.cpp @@ -115,6 +116,9 @@ KDev::OutputView KDev::Interfaces LINK_PRIVATE + Qt5::Quick + Qt5::QuickWidgets + KF5::GuiAddons KF5::IconThemes KF5::KIOFileWidgets @@ -167,4 +171,6 @@ problemconstants.h filteredproblemstore.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/shell COMPONENT Devel -) \ No newline at end of file +) + +install( FILES AssistantButton.qml assistantpopup.qml DESTINATION ${KDE_INSTALL_DATADIR}/kdevelop ) diff --git a/shell/assistantpopup.h b/shell/assistantpopup.h new file mode 100644 --- /dev/null +++ b/shell/assistantpopup.h @@ -0,0 +1,134 @@ +/* + Copyright 2009 David Nolden + Copyright 2012 Milian Wolff + Copyright 2014 Sven Brauch + Copyright 2014 Kevin Funk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVPLATFORM_ASSISTANTPOPUP_H +#define KDEVPLATFORM_ASSISTANTPOPUP_H + +#include +#include +#include + +namespace KTextEditor +{ +class View; +class Cursor; +} + +class AssistantPopupConfig : public QObject +{ + Q_OBJECT + Q_PROPERTY(QColor foreground READ foreground NOTIFY colorsChanged) + Q_PROPERTY(QColor background READ background NOTIFY colorsChanged) + Q_PROPERTY(QColor highlight READ highlight NOTIFY colorsChanged) + + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QList model READ model NOTIFY modelChanged) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(QSize viewSize READ viewSize WRITE setViewSize NOTIFY viewSizeChanged) + +public: + explicit AssistantPopupConfig(QObject *parent = 0); + + QColor foreground() const { return m_foreground; } + QColor background() const { return m_background; } + QColor highlight() const { return m_highlight; } + + QSize viewSize() const { return m_viewSize; }; + void setViewSize(const QSize &size); + + QString title() const { return m_title; } + void setTitle(const QString& title); + QList model() const { return m_model; } + void setModel(const QList& model); + + void setColorsFromView(QObject *view); + + bool isActive() const; + void setActive(bool active); + +signals: + void colorsChanged(); + + void titleChanged(const QString& title); + void modelChanged(const QList& model); + void activeChanged(bool active); + void viewSizeChanged(const QSize& size); + +private: + QColor m_foreground; + QColor m_background; + QColor m_highlight; + + QString m_title; + QList m_model; + bool m_active; + QSize m_viewSize; +}; + +Q_DECLARE_METATYPE(AssistantPopupConfig*) + +class AssistantPopup : public QQuickWidget +{ + Q_OBJECT + +public: + typedef QExplicitlySharedDataPointer Ptr; + + /** + * The current main window will be used as parent widget for the popup. + * This is to make use of the maximal space available and prevent any lines + * in e.g. the editor to be hidden by the popup. + */ + AssistantPopup(); + + /** + * Reset this popup for view @p view and show assistant @p assistant + * + * @p view The widget below which the assistant should be shown. + */ + void reset(KTextEditor::View *view, const KDevelop::IAssistant::Ptr &assistant); + + KDevelop::IAssistant::Ptr assistant() const; + +private slots: + void updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos); + void updateState(); + void updateLayout(); + + void executeHideAction(); + void hideAssistant(); + +protected: + bool eventFilter(QObject* object, QEvent* event) override; + +private: + void setView(KTextEditor::View* view); + void setAssistant(const KDevelop::IAssistant::Ptr& assistant); + void setActive( bool active ); + + KDevelop::IAssistant::Ptr m_assistant; + QPointer m_view; + AssistantPopupConfig* m_config; + QList m_shortcuts; + bool m_firstLayoutCompleted; +}; + +#endif // KDEVPLATFORM_ASSISTANTPOPUP_H diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp new file mode 100644 --- /dev/null +++ b/shell/assistantpopup.cpp @@ -0,0 +1,380 @@ +/* + Copyright 2009 David Nolden + Copyright 2012 Milian Wolff + Copyright 2014 Sven Brauch + Copyright 2014 Kevin Funk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "assistantpopup.h" +#include "sublime/holdupdates.h" +#include "util/kdevstringhandler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace KDevelop; + +namespace { + +const int ASSISTANT_MODIFIER = +#ifdef Q_OS_MAC +Qt::CTRL; +#else +Qt::ALT; +#endif + +const int ASSISTANT_MOD_KEY = +#ifdef Q_OS_MAC +Qt::Key_Control; +#else +Qt::Key_Alt; +#endif + +QWidget* findByClassname(const KTextEditor::View* view, const QString& klass) +{ + auto children = view->findChildren(); + foreach ( auto child, children ) { + if ( child->metaObject()->className() == klass ) { + return child; + } + } + return nullptr; +}; + +/** + * @brief Get the geometry of the inner part (with the text) of the KTextEditor::View being used. + */ +QRect textWidgetGeometry(const KTextEditor::View *view) +{ + // Subtract the width of the right scrollbar + int scrollbarWidth = 0; + if ( auto scrollbar = findByClassname(view, QStringLiteral("KateScrollBar")) ) { + scrollbarWidth = scrollbar->width(); + } + // Subtract the width of the bottom scrollbar + int bottomScrollbarWidth = 0; + if ( auto bottom = findByClassname(view, QStringLiteral("QScrollBar")) ) { + bottomScrollbarWidth = bottom->height(); + } + auto geom = view->geometry(); + + geom.adjust(0, 0, -scrollbarWidth, -bottomScrollbarWidth); + return geom; +} + +} + +AssistantPopupConfig::AssistantPopupConfig(QObject *parent) + : QObject(parent) + , m_active(false) +{ +} + +void AssistantPopupConfig::setColorsFromView(QObject *view) +{ + auto iface = dynamic_cast(view); + Q_ASSERT(iface); + m_foreground = iface->configValue(QStringLiteral("line-number-color")).value(); + m_background = iface->configValue(QStringLiteral("icon-border-color")).value(); + m_highlight = iface->configValue(QStringLiteral("folding-marker-color")).value(); + if ( KColorUtils::luma(m_background) < 0.3 ) { + m_foreground = KColorUtils::lighten(m_foreground, 0.7); + } + const float lumaDiff = KColorUtils::luma(m_highlight) - KColorUtils::luma(m_background); + if ( qAbs(lumaDiff) < 0.5 ) { + m_highlight = QColor::fromHsv(m_highlight.hue(), + qMin(255, m_highlight.saturation() + 80), + lumaDiff > 0 ? qMin(255, m_highlight.value() + 120) + : qMax(80, m_highlight.value() - 40)); + } + emit colorsChanged(); +} + +bool AssistantPopupConfig::isActive() const +{ + return m_active; +} + +void AssistantPopupConfig::setActive(bool active) +{ + if (m_active == active) { + return; + } + + m_active = active; + emit activeChanged(m_active); +} + +void AssistantPopupConfig::setViewSize(const QSize& size) +{ + if (size != m_viewSize) { + m_viewSize = size; + emit viewSizeChanged(size); + } +} + +void AssistantPopupConfig::setTitle(const QString& title) +{ + if (m_title == title) { + return; + } + + m_title = title; + emit titleChanged(m_title); +} + +void AssistantPopupConfig::setModel(const QList& model) +{ + if (m_model == model) { + return; + } + + qDeleteAll( m_model ); + m_model = model; + emit modelChanged(model); +} + +AssistantPopup::AssistantPopup() +// main window as parent to use maximal space available in worst case + : QQuickWidget(ICore::self()->uiController()->activeMainWindow()) + , m_config(new AssistantPopupConfig(this)) + , m_firstLayoutCompleted(false) +{ + setAttribute(Qt::WA_ShowWithoutActivating); + + rootContext()->setContextProperty(QStringLiteral("config"), m_config); + + setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/assistantpopup.qml")))); + if (!rootObject()) { + qWarning() << "Failed to load assistant markup! The assistant will not work."; + } else { + connect(rootObject(), &QQuickItem::widthChanged, this, &AssistantPopup::updateLayout); + connect(rootObject(), &QQuickItem::heightChanged, this, &AssistantPopup::updateLayout); + } + + for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { + m_shortcuts.append(new QShortcut(ASSISTANT_MODIFIER + i, this)); + } + setActive(false); + + connect(qApp, &QApplication::applicationStateChanged, this, [this]{ setActive(false); }); +} + +void AssistantPopup::reset(KTextEditor::View* view, const IAssistant::Ptr& assistant) +{ + setView(view); + setAssistant(assistant); + updateState(); +} + +void AssistantPopup::setView(KTextEditor::View* view) +{ + if (m_view == view) { + return; + } + + setActive(false); + + if (m_view) { + m_view->removeEventFilter(this); + disconnect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, + this, &AssistantPopup::updatePosition); + } + m_view = view; + m_config->setViewSize(m_view ? m_view->size() : QSize()); + if (m_view) { + m_view->installEventFilter(this); + connect(m_view.data(), &KTextEditor::View::verticalScrollPositionChanged, + this, &AssistantPopup::updatePosition); + } +} + +void AssistantPopup::setAssistant(const IAssistant::Ptr& assistant) +{ + if (m_assistant == assistant) { + return; + } + + if (m_assistant) { + disconnect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); + disconnect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); + } + m_assistant = assistant; + if (m_assistant) { + connect(m_assistant.data(), &IAssistant::hide, this, &AssistantPopup::hideAssistant); + connect(m_assistant.data(), &IAssistant::actionsChanged, this, &AssistantPopup::updateState); + } else { + hide(); + } +} + +void AssistantPopup::setActive(bool active) +{ + m_config->setActive(active); + foreach (auto shortcut, m_shortcuts) { + shortcut->setEnabled(active); + } +} + +bool AssistantPopup::eventFilter(QObject* object, QEvent* event) +{ + Q_UNUSED(object); + + if (!m_view || (object != m_view.data())) + return false; + + if (event->type() == QEvent::Resize) { + updateLayout(); + } else if (event->type() == QEvent::Hide) { + executeHideAction(); + } else if (event->type() == QEvent::KeyPress) { + auto keyEvent = static_cast(event); + if (keyEvent->modifiers() == ASSISTANT_MODIFIER) { + setActive(true); + } + if (keyEvent->key() == Qt::Key_Escape) { + executeHideAction(); + } + } else if (event->type() == QEvent::KeyRelease) { + auto keyEvent = static_cast(event); + if (keyEvent->modifiers() == ASSISTANT_MODIFIER || keyEvent->key() == ASSISTANT_MOD_KEY) { + setActive(false); + } + } + return false; +} + +void AssistantPopup::updatePosition(KTextEditor::View* view, const KTextEditor::Cursor& newPos) +{ + static const int MARGIN = 12; + + if (newPos.isValid() && newPos.line() == 0) { + // the position is not going to change; don't waste time + return; + } + + auto editorGeometry = textWidgetGeometry(view); + const auto startCursorCoordinate = view->cursorToCoordinate(KTextEditor::Cursor(0, 0)); + + // algorithm for popup positioning: + // if we are scrolled to the top: show at bottom + // else: + // if: current cursor position is in upper half => show at bottom + // else: show at top + const bool showAtBottom = startCursorCoordinate.y() == 0 ? true : + view->cursorPositionCoordinates().y() < view->height()/2; + const QPoint targetLocation = showAtBottom ? + parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.bottomRight() + + QPoint(-width() - MARGIN, -MARGIN - height()))) : + parentWidget()->mapFromGlobal(view->mapToGlobal(editorGeometry.topRight() + + QPoint(-width() - MARGIN, MARGIN))); + if (pos() == targetLocation) { + return; + } + + Sublime::HoldUpdates hold(ICore::self()->uiController()->activeMainWindow()); + move(targetLocation); +} + +IAssistant::Ptr AssistantPopup::assistant() const +{ + return m_assistant; +} + +void AssistantPopup::executeHideAction() +{ + if ( isVisible() ) { + m_assistant->doHide(); + } +} + +void AssistantPopup::hideAssistant() +{ + reset(nullptr, {}); // indirectly calls hide() +} + +void AssistantPopup::updateLayout() +{ + if ( !m_view ) { + return; + } + + m_config->setViewSize(m_view->size()); + // https://bugreports.qt.io/browse/QTBUG-44876 + resize(rootObject()->width(), rootObject()->height()); + updatePosition(m_view, KTextEditor::Cursor::invalid()); + + // HACK: QQuickWidget is corrupted due to above resize on the first show + if (!m_firstLayoutCompleted) { + hide(); + show(); + m_firstLayoutCompleted = true; + } +} + +void AssistantPopup::updateState() +{ + if (!m_assistant || m_assistant->actions().isEmpty() || !m_view) { + hide(); + return; + } + + auto curShortcut = m_shortcuts.constBegin(); + auto hideAction = new QAction(i18n("Hide"), this); + connect(*curShortcut, &QShortcut::activated, hideAction, &QAction::trigger); + connect(hideAction, &QAction::triggered, this, &AssistantPopup::executeHideAction); + + QList items; + foreach (IAssistantAction::Ptr action, m_assistant->actions()) { + QAction* asQAction = action->toKAction(); + items << asQAction; + asQAction->setParent(this); + //For some reason, QAction's setShortcut does nothing, so we manage with QShortcut + if (++curShortcut != m_shortcuts.constEnd()) { + connect(*curShortcut, &QShortcut::activated, asQAction, &QAction::trigger); + } + connect(action.data(), SIGNAL(executed(IAssistantAction*)), hideAction, SLOT(trigger())); + } + items << hideAction; + + auto view = ICore::self()->documentController()->activeTextDocumentView(); + m_config->setColorsFromView(view); + m_config->setModel(items); + m_config->setTitle(m_assistant->title()); + setActive(false); + + show(); +} + diff --git a/shell/assistantpopup.qml b/shell/assistantpopup.qml new file mode 100644 --- /dev/null +++ b/shell/assistantpopup.qml @@ -0,0 +1,89 @@ +/* + Copyright 2014 Sven Brauch + Copyright 2014 Kevin Funk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// This file provides the whole assistant, including title and buttons. + +import QtQuick 2.2 + +Rectangle { + id: root + + readonly property int vSpacing: 4 + readonly property int hSpacing: 4 + readonly property real itemsWidth: { + var totalWidth = title.width; + for (var i = 0; i < items.count; ++i) { + totalWidth += items.itemAt(i).width; + } + return totalWidth + (items.count + 2) * hSpacing; + } + readonly property bool useVerticalLayout: config.viewSize.width * 0.90 < itemsWidth + + // QQuickWidget crashes if either of these is zero + // Use ceil to ensure the widget always fits the non-integral content size + width: Math.ceil(Math.max(hSpacing, mainFlow.width + hSpacing * 2)) + height: Math.ceil(Math.max(vSpacing, mainFlow.height + vSpacing * 2)) + + border.width: 1 + border.color: Qt.lighter(config.foreground) + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.lighter(config.background) } + GradientStop { position: 1.0; color: config.background } + } + + Flow { + id: mainFlow + + anchors { + centerIn: parent + } + + flow: root.useVerticalLayout ? Flow.TopToBottom : Flow.LeftToRight + spacing: root.useVerticalLayout ? root.vSpacing : root.hSpacing + + Text { + id: title + + color: config.foreground + font.bold: true + text: config.title + } + + + Repeater { + id: items + objectName: "items" + + y: 5 + model: config.model + + AssistantButton { + text: modelData.text + highlighted: config.active + // what is displayed in the hotkey field of the button + button: index == items.model.length - 1 ? 0 : index + 1 + foreground: config.foreground + background: config.background + highlight: config.highlight + + onTriggered: { modelData.trigger() } + } + } + } +} diff --git a/shell/uicontroller.h b/shell/uicontroller.h --- a/shell/uicontroller.h +++ b/shell/uicontroller.h @@ -85,6 +85,8 @@ /*! @p status must implement KDevelop::IStatus */ void registerStatus(QObject* status) override; + void popUpAssistant(const KDevelop::IAssistant::Ptr& assistant) override; + void showErrorMessage(const QString& message, int timeout) override; /// Returns list of available view factories together with their ToolDocuments. @@ -110,6 +112,8 @@ void slotAreaChanged(Sublime::Area* area); void slotActiveToolViewChanged(Sublime::View* view); + void hideAssistant() override; + private: void addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -49,6 +49,7 @@ #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" +#include "assistantpopup.h" #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" @@ -145,6 +146,8 @@ QPointer activeSublimeWindow; bool areasRestored; + /// Currently shown assistant popup. + QPointer currentShownAssistant; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; @@ -719,6 +722,35 @@ QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } +void UiController::hideAssistant() +{ + if (d->currentShownAssistant) { + d->currentShownAssistant->hide(); + } +} + +void UiController::popUpAssistant(const KDevelop::IAssistant::Ptr& assistant) +{ + if(!assistant) + return; + + Sublime::View* view = d->activeSublimeWindow->activeView(); + if( !view ) + { + qCDebug(SHELL) << "no active view in mainwindow"; + return; + } + + auto editorView = qobject_cast(view->widget()); + Q_ASSERT(editorView); + if (editorView) { + if ( !d->currentShownAssistant ) { + d->currentShownAssistant = new AssistantPopup; + } + d->currentShownAssistant->reset(editorView, assistant); + } +} + const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments;