diff --git a/shell/assistantpopup.cpp b/shell/assistantpopup.cpp index f79cd516e4..7ba6887919 100644 --- a/shell/assistantpopup.cpp +++ b/shell/assistantpopup.cpp @@ -1,361 +1,383 @@ /* 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 +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(); 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())); for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) { - m_shortcuts.append(new QShortcut(Qt::ALT + i, this)); + m_shortcuts.append(new QShortcut(ASSISTANT_MODIFIER + i, this)); } + setActive(false); } 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; } - m_config->setActive(false); + 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())); } } +void AssistantPopup::setActive(bool active) +{ + m_config->setActive(active); + for (auto shortcut : m_shortcuts) { + shortcut->setEnabled(active); + } +} 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); } 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::KeyPress) { auto keyEvent = static_cast(event); - if (keyEvent->modifiers() == Qt::AltModifier) { - m_config->setActive(true); + 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() == Qt::AltModifier || keyEvent->key() == Qt::Key_Alt) { - m_config->setActive(false); + 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 && !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())); } } 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); + 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 98e9db0118..2a3fa9bf8d 100644 --- a/shell/assistantpopup.h +++ b/shell/assistantpopup.h @@ -1,130 +1,131 @@ /* 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 bool viewportEvent(QEvent *event); 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; bool m_shownAtBottom; bool m_reopening; QTimer* m_updateTimer; QList m_shortcuts; }; #endif // KDEVPLATFORM_ASSISTANTPOPUP_H