Commit d284d5bb authored by Milian Wolff's avatar Milian Wolff

Introduce QuickOpenEmbeddedWidgetCombiner

This mouthful of a class is essentially just a QWidget with a
QVBoxLayout which you can add widgets to. The fun comes from
implementing the QuickOpenEmbeddedWidgetInterface too, by
delegating to the child widgets that got added.

Overall, this allows us to navigate via Alt+Arrow keyboard actions
between combined widgets, as you'd get when you have both, an error
and a declaration at a given cursor declaration.
parent 35277af9
......@@ -113,6 +113,7 @@ set(KDevPlatformLanguage_LIB_SRCS
duchain/navigation/abstractincludenavigationcontext.cpp
duchain/navigation/useswidget.cpp
duchain/navigation/usescollector.cpp
duchain/navigation/quickopenembeddedwidgetcombiner.cpp
interfaces/abbreviations.cpp
interfaces/iastcontainer.cpp
......
......@@ -271,7 +271,7 @@ NavigationContextPointer AbstractNavigationContext::registerChild(const Declarat
const int lineJump = 3;
void AbstractNavigationContext::down()
bool AbstractNavigationContext::down()
{
//Make sure link-count is valid
if (d->m_linkCount == -1) {
......@@ -279,6 +279,13 @@ void AbstractNavigationContext::down()
html();
}
// select first link when we enter via down
if (d->m_selectedLink == -1 && d->m_linkCount) {
d->m_selectedLink = 0;
d->m_currentPositionLine = -1;
return true;
}
int fromLine = d->m_currentPositionLine;
// try to select the next link within our lineJump distance
......@@ -290,13 +297,13 @@ void AbstractNavigationContext::down()
if (d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) {
d->m_selectedLink = newSelectedLink;
d->m_currentPositionLine = -1;
return;
return true;
}
}
}
if (fromLine == d->m_currentLine - 1) // nothing to do, we are at the end of the document
return;
return false;
// scroll down by applying the lineJump
if (fromLine == -1)
......@@ -304,11 +311,13 @@ void AbstractNavigationContext::down()
d->m_currentPositionLine = fromLine + lineJump;
if (d->m_currentPositionLine >= d->m_currentLine)
if (d->m_currentPositionLine >= d->m_currentLine) {
d->m_currentPositionLine = d->m_currentLine - 1;
}
return fromLine != d->m_currentPositionLine;
}
void AbstractNavigationContext::up()
bool AbstractNavigationContext::up()
{
//Make sure link-count is valid
if (d->m_linkCount == -1) {
......@@ -316,6 +325,13 @@ void AbstractNavigationContext::up()
html();
}
// select last link when we enter via up
if (d->m_selectedLink == -1 && d->m_linkCount) {
d->m_selectedLink = d->m_linkCount - 1;
d->m_currentPositionLine = -1;
return true;
}
int fromLine = d->m_currentPositionLine;
if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) {
......@@ -326,7 +342,7 @@ void AbstractNavigationContext::up()
if (d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) {
d->m_selectedLink = newSelectedLink;
d->m_currentPositionLine = -1;
return;
return true;
}
}
}
......@@ -337,9 +353,11 @@ void AbstractNavigationContext::up()
d->m_currentPositionLine = fromLine - lineJump;
if (d->m_currentPositionLine < 0)
d->m_currentPositionLine = 0;
return fromLine || d->m_currentPositionLine;
}
void AbstractNavigationContext::nextLink()
bool AbstractNavigationContext::nextLink()
{
//Make sure link-count is valid
if (d->m_linkCount == -1) {
......@@ -347,13 +365,20 @@ void AbstractNavigationContext::nextLink()
html();
}
if (!d->m_linkCount)
return false;
d->m_currentPositionLine = -1;
if (d->m_linkCount > 0)
d->m_selectedLink = (d->m_selectedLink + 1) % d->m_linkCount;
d->m_selectedLink++;
if (d->m_selectedLink >= d->m_linkCount) {
d->m_selectedLink = 0;
return false;
}
return true;
}
void AbstractNavigationContext::previousLink()
bool AbstractNavigationContext::previousLink()
{
//Make sure link-count is valid
if (d->m_linkCount == -1) {
......@@ -361,15 +386,17 @@ void AbstractNavigationContext::previousLink()
html();
}
if (!d->m_linkCount)
return false;
d->m_currentPositionLine = -1;
if (d->m_linkCount > 0) {
--d->m_selectedLink;
if (d->m_selectedLink < 0)
d->m_selectedLink += d->m_linkCount;
d->m_selectedLink--;
if (d->m_selectedLink < 0) {
d->m_selectedLink = d->m_linkCount - 1;
return false;
}
Q_ASSERT(d->m_selectedLink >= 0);
return true;
}
int AbstractNavigationContext::linkCount() const
......@@ -377,6 +404,13 @@ int AbstractNavigationContext::linkCount() const
return d->m_linkCount;
}
void AbstractNavigationContext::resetNavigation()
{
d->m_currentPositionLine = -1;
d->m_selectedLink = -1;
d->m_selectedLinkAction = {};
}
NavigationContextPointer AbstractNavigationContext::back()
{
if (d->m_previousContext)
......
......@@ -66,13 +66,12 @@ public:
AbstractNavigationContext* previousContext = nullptr);
~AbstractNavigationContext() override;
void nextLink();
void previousLink();
int linkCount() const;
void up();
void down();
bool nextLink();
bool previousLink();
bool up();
bool down();
void resetNavigation();
NavigationContextPointer accept();
NavigationContextPointer back();
......
......@@ -266,18 +266,20 @@ void AbstractNavigationWidgetPrivate::anchorClicked(const QUrl& url)
q->setContext(nextContext);
}
void AbstractNavigationWidget::next()
bool AbstractNavigationWidget::next()
{
Q_ASSERT(d->m_context);
d->m_context->nextLink();
auto ret = d->m_context->nextLink();
update();
return ret;
}
void AbstractNavigationWidget::previous()
bool AbstractNavigationWidget::previous()
{
Q_ASSERT(d->m_context);
d->m_context->previousLink();
auto ret = d->m_context->previousLink();
update();
return ret;
}
void AbstractNavigationWidget::accept()
......@@ -300,15 +302,23 @@ void AbstractNavigationWidget::back()
setContext(nextContext);
}
void AbstractNavigationWidget::up()
bool AbstractNavigationWidget::up()
{
d->m_context->up();
auto ret = d->m_context->up();
update();
return ret;
}
void AbstractNavigationWidget::down()
bool AbstractNavigationWidget::down()
{
d->m_context->down();
auto ret = d->m_context->down();
update();
return ret;
}
void AbstractNavigationWidget::resetNavigationState()
{
d->m_context->resetNavigation();
update();
}
......
......@@ -55,12 +55,13 @@ public:
public Q_SLOTS:
/// keyboard navigation support
void next() override;
void previous() override;
bool next() override;
bool previous() override;
bool up() override;
bool down() override;
void accept() override;
void up() override;
void down() override;
void back() override;
void resetNavigationState() override;
///These are temporarily for gettings these events directly from kate
///@todo Do this through a public interface post 4.2
......
/*
* This file is part of KDevelop
*
* Copyright 2019 Milian Wolff <mail@milianw.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "quickopenembeddedwidgetcombiner.h"
#include <QVBoxLayout>
using namespace KDevelop;
QuickOpenEmbeddedWidgetInterface* toInterface(QObject *object)
{
return dynamic_cast<QuickOpenEmbeddedWidgetInterface*>(object);
}
struct QuickOpenEmbeddedWidgetCombiner::Private
{
QuickOpenEmbeddedWidgetInterface* currentChild = nullptr;
bool init(const QObjectList& children)
{
if (currentChild)
return true;
currentChild = nextChild(Next, children);
return currentChild;
}
enum Action
{
Next,
Previous,
Up,
Down,
};
QuickOpenEmbeddedWidgetInterface* nextChild(Action action, const QObjectList& children) const
{
if (action == Next || action == Down) {
return nextChild(children.begin(), children.end());
} else {
return nextChild(children.rbegin(), children.rend());
}
}
template<typename It>
QuickOpenEmbeddedWidgetInterface* nextChild(const It start, const It end) const
{
if (start == end)
return nullptr;
auto current = start;
if (currentChild) {
current = std::find_if(start, end, [this](QObject *child) {
return toInterface(child) == currentChild;
});
}
auto it = std::find_if(current + 1, end, toInterface);
if (it == end && current != start) {
// cycle
it = std::find_if(start, current, toInterface);
}
return (it != end) ? toInterface(*it) : nullptr;
}
bool navigate(Action action, const QObjectList& children)
{
if (!init(children)) {
return false;
}
auto applyAction = [action](QuickOpenEmbeddedWidgetInterface* interface)
{
switch (action) {
case Next:
return interface->next();
case Previous:
return interface->previous();
case Up:
return interface->up();
case Down:
return interface->down();
}
Q_UNREACHABLE();
};
if (applyAction(currentChild)) {
return true;
}
if (auto *next = nextChild(action, children)) {
currentChild->resetNavigationState();
currentChild = next;
auto ret = applyAction(currentChild);
return ret;
}
return false;
}
};
QuickOpenEmbeddedWidgetCombiner::QuickOpenEmbeddedWidgetCombiner(QWidget* parent)
: QWidget(parent)
, d(new Private)
{
setLayout(new QVBoxLayout);
layout()->setContentsMargins(2, 2, 2, 2);
layout()->setSpacing(0);
}
QuickOpenEmbeddedWidgetCombiner::~QuickOpenEmbeddedWidgetCombiner() = default;
bool QuickOpenEmbeddedWidgetCombiner::next()
{
return d->navigate(Private::Next, children());
}
bool QuickOpenEmbeddedWidgetCombiner::previous()
{
return d->navigate(Private::Previous, children());
}
bool QuickOpenEmbeddedWidgetCombiner::up()
{
return d->navigate(Private::Up, children());
}
bool QuickOpenEmbeddedWidgetCombiner::down()
{
return d->navigate(Private::Down, children());
}
void QuickOpenEmbeddedWidgetCombiner::back()
{
if (d->currentChild) {
d->currentChild->back();
}
}
void QuickOpenEmbeddedWidgetCombiner::accept()
{
if (d->currentChild) {
d->currentChild->accept();
}
}
void QuickOpenEmbeddedWidgetCombiner::resetNavigationState()
{
for (auto* child : children()) {
if (auto* interface = toInterface(child)) {
interface->resetNavigationState();
}
}
}
/*
* This file is part of KDevelop
*
* Copyright 2019 Milian Wolff <mail@milianw.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H
#define KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H
#include <QWidget>
#include <interfaces/quickopendataprovider.h>
namespace KDevelop {
/**
* A widget that implements the QuickOpenEmbeddedWidgetInterface by asking its direct children.
*
* I.e. add widgets into the combiner's layout. If the widgets support the QuickOpenEmbeddedWidgetInterface,
* then they will be used to implement the keyboard navigation features.
*/
class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenEmbeddedWidgetCombiner : public QWidget, public QuickOpenEmbeddedWidgetInterface
{
public:
explicit QuickOpenEmbeddedWidgetCombiner(QWidget* parent = nullptr);
~QuickOpenEmbeddedWidgetCombiner() override;
bool next() override;
bool previous() override;
bool up() override;
bool down() override;
void back() override;
void accept() override;
void resetNavigationState() override;
private:
struct Private;
QScopedPointer<Private> d;
};
}
#endif // KDEVPLATFORM_QUICKOPENEMBEDDEDWIDGETCOMBINER_H
......@@ -61,17 +61,19 @@ class KDEVPLATFORMLANGUAGE_EXPORT QuickOpenEmbeddedWidgetInterface
public:
virtual ~QuickOpenEmbeddedWidgetInterface();
///Is called when the keyboard-shortcut "next" is triggered on the widget, which currently is ALT+Right
virtual void next() = 0;
virtual bool next() = 0;
///Is called when the keyboard-shortcut "previous" is triggered on the widget, which currently is ALT+Left
virtual void previous() = 0;
///Is called when the keyboard-shortcut "accept" is triggered on the widget, which currently is ALT+Return
virtual void accept() = 0;
virtual bool previous() = 0;
///Is called when the keyboard-shortcut "scroll up" is triggered on the widget, which currently is ALT+Up
virtual void up() = 0;
virtual bool up() = 0;
///Is called when the keyboard-shortcut "scroll down" is triggered on the widget, which currently is ALT+Down
virtual void down() = 0;
virtual bool down() = 0;
///Is called when the keyboard-shortcut "back" is triggered on the widget, which currently is ALT+Backspace
virtual void back() = 0;
///Is called when the keyboard-shortcut "accept" is triggered on the widget, which currently is ALT+Return
virtual void accept() = 0;
///Reset the navigation state to the state before keyboard interaction
virtual void resetNavigationState() = 0;
};
/**
......
......@@ -71,6 +71,7 @@
#include <language/duchain/types/functiontype.h>
#include <language/duchain/navigation/abstractnavigationwidget.h>
#include <language/duchain/navigation/problemnavigationcontext.h>
#include <language/duchain/navigation/quickopenembeddedwidgetcombiner.h>
#include <language/util/navigationtooltip.h>
......@@ -625,7 +626,7 @@ QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* vi
problemWidget->setContext(NavigationContextPointer(context));
}
QWidget* declWidget = nullptr;
AbstractNavigationWidget* declWidget = nullptr;
if (decl) {
if (itemRange.isValid()) {
itemRange.expandToRange(itemUnderCursor.range);
......@@ -636,13 +637,9 @@ QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* vi
}
if (problemWidget && declWidget) {
QWidget* combinedWidget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(2, 2, 2, 2);
layout->setSpacing(0);
layout->addWidget(problemWidget);
layout->addWidget(declWidget);
combinedWidget->setLayout(layout);
auto* combinedWidget = new QuickOpenEmbeddedWidgetCombiner;
combinedWidget->layout()->addWidget(problemWidget);
combinedWidget->layout()->addWidget(declWidget);
return combinedWidget;
} else if (problemWidget) {
return problemWidget;
......@@ -1514,29 +1511,26 @@ void ContextBrowserPlugin::doNavigate(NavigationActionType action)
widget = contextView->navigationWidget();
}
if (widget) {
auto* navWidget = qobject_cast<AbstractNavigationWidget*>(widget);
if (navWidget) {
switch (action) {
case Accept:
navWidget->accept();
break;
case Back:
navWidget->back();
break;
case Left:
navWidget->previous();
break;
case Right:
navWidget->next();
break;
case Up:
navWidget->up();
break;
case Down:
navWidget->down();
break;
}
if (auto* navWidget = dynamic_cast<QuickOpenEmbeddedWidgetInterface*>(widget)) {
switch (action) {
case Accept:
navWidget->accept();
break;
case Back:
navWidget->back();
break;
case Left:
navWidget->previous();
break;
case Right:
navWidget->next();
break;
case Up:
navWidget->up();
break;
case Down:
navWidget->down();
break;
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment