diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp index 483d07610a..f79cd516e4 100644 --- a/shell/assistantpopup.cpp +++ b/shell/assistantpopup.cpp @@ -1,389 +1,361 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /// Interval after which the state of the popup is re-evaluated /// Used to avoid flickering caused when user is quickly inserting code const int UPDATE_STATE_INTERVAL = 300; // ms QWidget* findByClassname(const KTextEditor::View* view, const QString& klass) { auto children = view->findChildren(); for ( 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, "KateScrollBar") ) { scrollbarWidth = scrollbar->width(); } // Subtract the width of the bottom scrollbar int bottomScrollbarWidth = 0; if ( auto bottom = findByClassname(view, "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("line-number-color").value(); m_background = iface->configValue("icon-border-color").value(); m_highlight = iface->configValue("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::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 : QDeclarativeView(ICore::self()->uiController()->activeMainWindow()) , m_config(new AssistantPopupConfig(this)) , m_shownAtBottom(false) , m_reopening(false) , m_updateTimer(new QTimer(this)) { QPalette p = palette(); p.setColor(QPalette::Window, Qt::transparent); setPalette(p); setBackgroundRole(QPalette::Window); setBackgroundBrush(QBrush(QColor(0, 0, 0, 0))); setResizeMode(QDeclarativeView::SizeViewToRootObject); setAttribute(Qt::WA_ShowWithoutActivating); rootContext()->setContextProperty("config", m_config); setSource(QUrl::fromLocalFile(KStandardDirs::locate("data", "kdevelop/assistantpopup.qml"))); if (!rootObject()) { kWarning() << "Failed to load assistant markup! The assistant will not work."; } m_updateTimer->setInterval(UPDATE_STATE_INTERVAL); m_updateTimer->setSingleShot(true); connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(updateState())); -} -void AssistantPopup::grabFocus() -{ - m_config->setActive(true); - if (m_view) { - setFocus(); - } -} - -void AssistantPopup::ungrabFocus() -{ - m_config->setActive(false); - if (m_view && hasFocus()) { - m_view->setFocus(); + for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { + m_shortcuts.append(new QShortcut(Qt::ALT + i, this)); } } void AssistantPopup::reset(KTextEditor::View* view, const IAssistant::Ptr& assistant) { setView(view); setAssistant(assistant); m_updateTimer->start(); } void AssistantPopup::setView(KTextEditor::View* view) { if (m_view == view) { return; } - ungrabFocus(); + m_config->setActive(false); if (m_view) { m_view->removeEventFilter(this); disconnect(m_view, SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updatePosition(KTextEditor::View*,KTextEditor::Cursor))); } m_view = view; if (m_view) { m_view->installEventFilter(this); connect(m_view, SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updatePosition(KTextEditor::View*,KTextEditor::Cursor))); } } void AssistantPopup::setAssistant(const IAssistant::Ptr& assistant) { if (m_assistant == assistant) { return; } if (m_assistant) { disconnect(m_assistant.data(), SIGNAL(actionsChanged()), m_updateTimer, SLOT(start())); disconnect(m_assistant.data(), SIGNAL(hide()), this, SLOT(hideAssistant())); } m_assistant = assistant; if (m_assistant) { connect(m_assistant.data(), SIGNAL(actionsChanged()), m_updateTimer, SLOT(start())); connect(m_assistant.data(), SIGNAL(hide()), this, SLOT(hideAssistant())); } } bool AssistantPopup::viewportEvent(QEvent *event) { // For some reason, QGraphicsView posts a WindowActivate event // when it is shown, even if disabled through setting the WA_ShowWithoutActivate // attribute. This causes all focus-driven popups (QuickOpen, tooltips, ...) // to hide when the assistant opens. Thus, prevent it from processing the Show event here. if ( event->type() == QEvent::Show ) { return true; } return QGraphicsView::viewportEvent(event); } -void AssistantPopup::keyPressEvent(QKeyEvent* event) -{ - if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9) { - auto actions = m_config->model(); - const int field = event->key() - Qt::Key_0; - if (field == 0) { - executeHideAction(); - } else { - auto action = m_assistant->actions().value(field - 1); - if (action) { - action->execute(); - } - } - } else { - QDeclarativeView::keyPressEvent(event); - } -} - - -void AssistantPopup::keyReleaseEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Alt || event->modifiers() == Qt::AltModifier) { - ungrabFocus(); - } else { - QDeclarativeView::keyReleaseEvent(event); - } -} - bool AssistantPopup::eventFilter(QObject* object, QEvent* event) { if (!m_view) return false; Q_ASSERT(object == m_view.data()); Q_UNUSED(object); if (event->type() == QEvent::Resize) { updatePosition(m_view.data(), KTextEditor::Cursor::invalid()); } else if (event->type() == QEvent::Hide) { executeHideAction(); - } else if (event->type() == QEvent::WindowDeactivate) { - ungrabFocus(); } else if (event->type() == QEvent::KeyPress) { auto keyEvent = static_cast(event); if (keyEvent->modifiers() == Qt::AltModifier) { - grabFocus(); + m_config->setActive(true); } - if (static_cast(event)->key() == Qt::Key_Escape) { + if (keyEvent->key() == Qt::Key_Escape) { executeHideAction(); } + } else if (event->type() == QEvent::KeyRelease) { + auto keyEvent = static_cast(event); + if (keyEvent->modifiers() == Qt::AltModifier || keyEvent->key() == Qt::Key_Alt) { + m_config->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 && !m_shownAtBottom) { // 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; } if ( m_reopening ) { // When the assistant is already visible, close to no flickering will occur anyways, // so we can avoid the full repaint of the window. move(targetLocation); } else { 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::updateState() { if (!m_assistant || m_assistant->actions().isEmpty()) { hide(); return; } + auto curShortcut = m_shortcuts.constBegin(); + auto hideAction = new KAction(i18n("Hide"), this); + connect(*curShortcut, SIGNAL(activated()), hideAction, SLOT(trigger())); + connect(hideAction, SIGNAL(triggered()), this, SLOT(executeHideAction())); + QList items; foreach (IAssistantAction::Ptr action, m_assistant->actions()) { items << action->toKAction(); + items.last()->setParent(this); + //For some reason, KAction's setShortcut does nothing, so we manage with QShortcut + if (++curShortcut != m_shortcuts.constEnd()) { + connect(*curShortcut, SIGNAL(activated()), items.last(), SLOT(trigger())); + } } - auto hideAction = new KAction(i18n("Hide"), m_assistant.data()); - connect(hideAction, SIGNAL(triggered()), this, SLOT(executeHideAction())); items << hideAction; auto doc = ICore::self()->documentController()->activeDocument(); m_config->setColorsFromView(doc->textDocument()->activeView()); m_config->setModel(items); m_config->setTitle(m_assistant->title()); m_config->setActive(false); // both changed title or actions may change the appearance of the popup // force recomputing the size hint resize(sizeHint()); updatePosition(m_view, KTextEditor::Cursor::invalid()); show(); } #include "assistantpopup.moc" diff --git a/shell/assistantpopup.h b/shell/assistantpopup.h index abb80652aa..98e9db0118 100644 --- a/shell/assistantpopup.h +++ b/shell/assistantpopup.h @@ -1,135 +1,130 @@ /* 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 #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) public: explicit AssistantPopupConfig(QObject *parent = 0); QColor foreground() const { return m_foreground; } QColor background() const { return m_background; } QColor highlight() const { return m_highlight; } 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); private: QColor m_foreground; QColor m_background; QColor m_highlight; QString m_title; QList m_model; bool m_active; }; Q_DECLARE_METATYPE(AssistantPopupConfig*) class AssistantPopup : public QDeclarativeView { Q_OBJECT public: typedef KSharedPtr 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 executeHideAction(); void hideAssistant(); protected: virtual bool eventFilter(QObject* object, QEvent* event); - virtual void keyPressEvent(QKeyEvent* event); - virtual void keyReleaseEvent(QKeyEvent* event); virtual bool viewportEvent(QEvent *event); private: void setView(KTextEditor::View* view); void setAssistant(const KDevelop::IAssistant::Ptr& assistant); - /// Give the AssistantPopup instance widget focus - void grabFocus(); - /// Return focus back to the editor view - void ungrabFocus(); - KDevelop::IAssistant::Ptr m_assistant; QPointer m_view; AssistantPopupConfig* m_config; bool m_shownAtBottom; bool m_reopening; QTimer* m_updateTimer; + QList m_shortcuts; }; #endif // KDEVPLATFORM_ASSISTANTPOPUP_H