mirror-chatterino2/src/widgets/dialogs/switcher/QuickSwitcherPopup.cpp

201 lines
6 KiB
C++
Raw Normal View History

Discord-like Quick Switcher (#1588) * Proof of Concept for Quick Switcher * Fix crash when suggestions are empty * QuickSwitcher: Use tab name instead of a single channel * Rebase later * Add missing include for <functional> * Move QuickSwitcher related classes into own subfolder * Refactor switcher list items Now, items are responsible for taking the right action when selected in the switcher list. This should allow for more focused code and responsibilities. * Add note about memory management * Add option to open channel in a new tab * Add support for using the mouse * Spawn switcher popup in the middle of the window Works reliably on i3 at least. Might need some additional testing on other WMs (and especially on Windows!). * Add some icons for switcher items Note that the final design of the list is not final but I do plan to incorporate these in the future. * Set Qt::Dialog window flag on switcher popup Prevents tiling window managers like i3 from trying to tile the window. * Rename "SwitcherItem" to "AbstractSwitcherItem" * Add comments about what items are inserted * Use custom model and view Still missing: Currently selected item is not highlighted yet. You can move between selected items with tab and arrow keys though. * Add helper function to convert QVariant to AbstractSwitcherItem * * Remove useless constant * Highlight currently selected switcher item * Use a different method for centering QuickSwitcherPopup window * QuickSwitcherModel: Add documentation * Add default parameter to QuickSwitcherModel::rowCount * QuickSwitcherPopup: Add comments * Remove outdated TODO * QuickSwitcherModel: Init vector with default capacity * Remove outdated comment * Add comment about 0 ms timeout interval * NewTabItem: Simplify interface * Only fetch opened splits once This is better than the prior approach since opened splits cannot change anyways while the switcher is open. * Use SplitContainer to pass information instead of custom type * Allow searching for tab titles as well Before this commit, only channel names could be searched. * Refactor switcher item interface to be more flexible Also show tab name and channel name in the switcher list. * Add documentation for AbstractSwitcherItem * Add documentation for NewTabItem * Add comments about {begin,end}{Insert,Remove}Rows * Remove unused method * Replace magic size with named constant * Add change log entry Co-authored-by: fourtf <tf.four@gmail.com>
2020-08-13 19:25:51 +02:00
#include "widgets/dialogs/switcher/QuickSwitcherPopup.hpp"
#include "Application.hpp"
#include "singletons/WindowManager.hpp"
#include "util/LayoutCreator.hpp"
#include "widgets/Notebook.hpp"
#include "widgets/Window.hpp"
#include "widgets/dialogs/switcher/NewTabItem.hpp"
#include "widgets/dialogs/switcher/SwitchSplitItem.hpp"
#include "widgets/helper/NotebookTab.hpp"
namespace chatterino {
namespace {
using namespace chatterino;
QSet<SplitContainer *> openPages()
{
QSet<SplitContainer *> pages;
auto &nb = getApp()->windows->getMainWindow().getNotebook();
for (int i = 0; i < nb.getPageCount(); ++i)
{
pages.insert(static_cast<SplitContainer *>(nb.getPageAt(i)));
}
return pages;
}
} // namespace
const QSize QuickSwitcherPopup::MINIMUM_SIZE(500, 300);
QuickSwitcherPopup::QuickSwitcherPopup(QWidget *parent)
: BasePopup(FlagsEnum<BaseWindow::Flags>{BaseWindow::Flags::Frameless,
BaseWindow::Flags::TopMost},
parent)
, switcherModel_(this)
, switcherItemDelegate_(this)
, openPages_(openPages())
{
this->setWindowFlag(Qt::Dialog);
this->setActionOnFocusLoss(BaseWindow::ActionOnFocusLoss::Delete);
this->setMinimumSize(QuickSwitcherPopup::MINIMUM_SIZE);
this->initWidgets();
this->setStayInScreenRect(true);
const QRect geom = parent->geometry();
// This places the popup in the middle of the parent widget
this->setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter,
this->size(), geom));
}
QuickSwitcherPopup::~QuickSwitcherPopup()
{
}
void QuickSwitcherPopup::initWidgets()
{
LayoutCreator<QWidget> creator(this->BaseWindow::getLayoutContainer());
auto vbox = creator.setLayoutType<QVBoxLayout>();
{
vbox.emplace<QLineEdit>().assign(&this->ui_.searchEdit);
QObject::connect(this->ui_.searchEdit, &QLineEdit::textChanged, this,
&QuickSwitcherPopup::updateSuggestions);
this->ui_.searchEdit->installEventFilter(this);
}
{
vbox.emplace<QListView>().assign(&this->ui_.list);
this->ui_.list->setSelectionMode(QAbstractItemView::SingleSelection);
this->ui_.list->setSelectionBehavior(QAbstractItemView::SelectItems);
this->ui_.list->setModel(&this->switcherModel_);
this->ui_.list->setItemDelegate(&this->switcherItemDelegate_);
/*
* I also tried handling key events using the according slots but
* it lead to all kind of problems that did not occur with the
* eventFilter approach.
*/
QObject::connect(
this->ui_.list, &QListView::clicked, this,
[this](const QModelIndex &index) {
auto *item = AbstractSwitcherItem::fromVariant(index.data());
item->action();
this->close();
});
}
}
void QuickSwitcherPopup::updateSuggestions(const QString &text)
{
this->switcherModel_.clear();
// Add items for navigating to different splits
for (auto *sc : this->openPages_)
{
const QString &tabTitle = sc->getTab()->getTitle();
const auto splits = sc->getSplits();
// First, check for splits on this page
for (auto *split : splits)
{
if (split->getChannel()->getName().contains(text,
Qt::CaseInsensitive))
{
2020-08-13 20:05:54 +02:00
auto item = std::make_unique<SwitchSplitItem>(split);
this->switcherModel_.addItem(std::move(item));
Discord-like Quick Switcher (#1588) * Proof of Concept for Quick Switcher * Fix crash when suggestions are empty * QuickSwitcher: Use tab name instead of a single channel * Rebase later * Add missing include for <functional> * Move QuickSwitcher related classes into own subfolder * Refactor switcher list items Now, items are responsible for taking the right action when selected in the switcher list. This should allow for more focused code and responsibilities. * Add note about memory management * Add option to open channel in a new tab * Add support for using the mouse * Spawn switcher popup in the middle of the window Works reliably on i3 at least. Might need some additional testing on other WMs (and especially on Windows!). * Add some icons for switcher items Note that the final design of the list is not final but I do plan to incorporate these in the future. * Set Qt::Dialog window flag on switcher popup Prevents tiling window managers like i3 from trying to tile the window. * Rename "SwitcherItem" to "AbstractSwitcherItem" * Add comments about what items are inserted * Use custom model and view Still missing: Currently selected item is not highlighted yet. You can move between selected items with tab and arrow keys though. * Add helper function to convert QVariant to AbstractSwitcherItem * * Remove useless constant * Highlight currently selected switcher item * Use a different method for centering QuickSwitcherPopup window * QuickSwitcherModel: Add documentation * Add default parameter to QuickSwitcherModel::rowCount * QuickSwitcherPopup: Add comments * Remove outdated TODO * QuickSwitcherModel: Init vector with default capacity * Remove outdated comment * Add comment about 0 ms timeout interval * NewTabItem: Simplify interface * Only fetch opened splits once This is better than the prior approach since opened splits cannot change anyways while the switcher is open. * Use SplitContainer to pass information instead of custom type * Allow searching for tab titles as well Before this commit, only channel names could be searched. * Refactor switcher item interface to be more flexible Also show tab name and channel name in the switcher list. * Add documentation for AbstractSwitcherItem * Add documentation for NewTabItem * Add comments about {begin,end}{Insert,Remove}Rows * Remove unused method * Replace magic size with named constant * Add change log entry Co-authored-by: fourtf <tf.four@gmail.com>
2020-08-13 19:25:51 +02:00
// We want to continue the outer loop so we need a goto
goto nextPage;
}
}
// Then check if tab title matches
if (tabTitle.contains(text, Qt::CaseInsensitive))
{
2020-08-13 20:05:54 +02:00
auto item = std::make_unique<SwitchSplitItem>(sc);
this->switcherModel_.addItem(std::move(item));
Discord-like Quick Switcher (#1588) * Proof of Concept for Quick Switcher * Fix crash when suggestions are empty * QuickSwitcher: Use tab name instead of a single channel * Rebase later * Add missing include for <functional> * Move QuickSwitcher related classes into own subfolder * Refactor switcher list items Now, items are responsible for taking the right action when selected in the switcher list. This should allow for more focused code and responsibilities. * Add note about memory management * Add option to open channel in a new tab * Add support for using the mouse * Spawn switcher popup in the middle of the window Works reliably on i3 at least. Might need some additional testing on other WMs (and especially on Windows!). * Add some icons for switcher items Note that the final design of the list is not final but I do plan to incorporate these in the future. * Set Qt::Dialog window flag on switcher popup Prevents tiling window managers like i3 from trying to tile the window. * Rename "SwitcherItem" to "AbstractSwitcherItem" * Add comments about what items are inserted * Use custom model and view Still missing: Currently selected item is not highlighted yet. You can move between selected items with tab and arrow keys though. * Add helper function to convert QVariant to AbstractSwitcherItem * * Remove useless constant * Highlight currently selected switcher item * Use a different method for centering QuickSwitcherPopup window * QuickSwitcherModel: Add documentation * Add default parameter to QuickSwitcherModel::rowCount * QuickSwitcherPopup: Add comments * Remove outdated TODO * QuickSwitcherModel: Init vector with default capacity * Remove outdated comment * Add comment about 0 ms timeout interval * NewTabItem: Simplify interface * Only fetch opened splits once This is better than the prior approach since opened splits cannot change anyways while the switcher is open. * Use SplitContainer to pass information instead of custom type * Allow searching for tab titles as well Before this commit, only channel names could be searched. * Refactor switcher item interface to be more flexible Also show tab name and channel name in the switcher list. * Add documentation for AbstractSwitcherItem * Add documentation for NewTabItem * Add comments about {begin,end}{Insert,Remove}Rows * Remove unused method * Replace magic size with named constant * Add change log entry Co-authored-by: fourtf <tf.four@gmail.com>
2020-08-13 19:25:51 +02:00
continue;
}
nextPage:;
}
// Add item for opening a channel in a new tab
if (!text.isEmpty())
{
2020-08-13 20:05:54 +02:00
auto item = std::make_unique<NewTabItem>(text);
this->switcherModel_.addItem(std::move(item));
Discord-like Quick Switcher (#1588) * Proof of Concept for Quick Switcher * Fix crash when suggestions are empty * QuickSwitcher: Use tab name instead of a single channel * Rebase later * Add missing include for <functional> * Move QuickSwitcher related classes into own subfolder * Refactor switcher list items Now, items are responsible for taking the right action when selected in the switcher list. This should allow for more focused code and responsibilities. * Add note about memory management * Add option to open channel in a new tab * Add support for using the mouse * Spawn switcher popup in the middle of the window Works reliably on i3 at least. Might need some additional testing on other WMs (and especially on Windows!). * Add some icons for switcher items Note that the final design of the list is not final but I do plan to incorporate these in the future. * Set Qt::Dialog window flag on switcher popup Prevents tiling window managers like i3 from trying to tile the window. * Rename "SwitcherItem" to "AbstractSwitcherItem" * Add comments about what items are inserted * Use custom model and view Still missing: Currently selected item is not highlighted yet. You can move between selected items with tab and arrow keys though. * Add helper function to convert QVariant to AbstractSwitcherItem * * Remove useless constant * Highlight currently selected switcher item * Use a different method for centering QuickSwitcherPopup window * QuickSwitcherModel: Add documentation * Add default parameter to QuickSwitcherModel::rowCount * QuickSwitcherPopup: Add comments * Remove outdated TODO * QuickSwitcherModel: Init vector with default capacity * Remove outdated comment * Add comment about 0 ms timeout interval * NewTabItem: Simplify interface * Only fetch opened splits once This is better than the prior approach since opened splits cannot change anyways while the switcher is open. * Use SplitContainer to pass information instead of custom type * Allow searching for tab titles as well Before this commit, only channel names could be searched. * Refactor switcher item interface to be more flexible Also show tab name and channel name in the switcher list. * Add documentation for AbstractSwitcherItem * Add documentation for NewTabItem * Add comments about {begin,end}{Insert,Remove}Rows * Remove unused method * Replace magic size with named constant * Add change log entry Co-authored-by: fourtf <tf.four@gmail.com>
2020-08-13 19:25:51 +02:00
}
const auto &startIdx = this->switcherModel_.index(0);
this->ui_.list->setCurrentIndex(startIdx);
/*
* Timeout interval 0 means the call will be delayed until all window events
* have been processed (cf. https://doc.qt.io/qt-5/qtimer.html#interval-prop).
*/
QTimer::singleShot(0, [this] { this->adjustSize(); });
}
bool QuickSwitcherPopup::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
auto *keyEvent = static_cast<QKeyEvent *>(event);
int key = keyEvent->key();
const QModelIndex &curIdx = this->ui_.list->currentIndex();
const int curRow = curIdx.row();
const int count = this->switcherModel_.rowCount(curIdx);
if (key == Qt::Key_Down || key == Qt::Key_Tab)
{
if (count <= 0)
return true;
const int newRow = (curRow + 1) % count;
this->ui_.list->setCurrentIndex(curIdx.siblingAtRow(newRow));
return true;
}
else if (key == Qt::Key_Up || key == Qt::Key_Backtab)
{
if (count <= 0)
return true;
int newRow = curRow - 1;
if (newRow < 0)
newRow += count;
this->ui_.list->setCurrentIndex(curIdx.siblingAtRow(newRow));
return true;
}
else if (key == Qt::Key_Enter || key == Qt::Key_Return)
{
if (count <= 0)
return true;
const auto index = this->ui_.list->currentIndex();
auto *item = AbstractSwitcherItem::fromVariant(index.data());
item->action();
this->close();
return true;
}
else
{
return false;
}
}
return false;
}
} // namespace chatterino