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>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
#include "widgets/dialogs/UpdateDialog.hpp"
|
||||
#include "widgets/dialogs/WelcomeDialog.hpp"
|
||||
#include "widgets/dialogs/switcher/QuickSwitcherPopup.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/helper/TitlebarButton.hpp"
|
||||
@@ -393,6 +394,12 @@ void Window::addShortcuts()
|
||||
getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar);
|
||||
getApp()->windows->forceLayoutChannelViews();
|
||||
});
|
||||
|
||||
createWindowShortcut(this, "CTRL+K", [this] {
|
||||
auto quickSwitcher =
|
||||
new QuickSwitcherPopup(&getApp()->windows->getMainWindow());
|
||||
quickSwitcher->show();
|
||||
});
|
||||
}
|
||||
|
||||
void Window::addMenuBar()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "widgets/dialogs/switcher/AbstractSwitcherItem.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
const QSize AbstractSwitcherItem::ICON_SIZE(32, 32);
|
||||
|
||||
AbstractSwitcherItem *AbstractSwitcherItem::fromVariant(const QVariant &variant)
|
||||
{
|
||||
// See https://stackoverflow.com/a/44503822 .
|
||||
return static_cast<AbstractSwitcherItem *>(variant.value<void *>());
|
||||
}
|
||||
|
||||
AbstractSwitcherItem::AbstractSwitcherItem(const QIcon &icon)
|
||||
: icon_(icon)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class AbstractSwitcherItem
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Attempt to obtain an AbstractSwitcherItem * from the passed QVariant.
|
||||
*
|
||||
* @param variant variant to try to convert to AbstractSwitcherItem *
|
||||
*
|
||||
* @return an AbstractSwitcherItem * if the QVariant could be converted,
|
||||
* or nullptr if the variant did not contain AbstractSwitcherItem *
|
||||
*/
|
||||
static AbstractSwitcherItem *fromVariant(const QVariant &variant);
|
||||
|
||||
virtual ~AbstractSwitcherItem() = default;
|
||||
|
||||
/**
|
||||
* @brief Since all switcher items are required to have an icon, we require it
|
||||
* in the base class constructor.
|
||||
*
|
||||
* @param icon icon to be displayed in the switcher list
|
||||
*/
|
||||
AbstractSwitcherItem(const QIcon &icon);
|
||||
|
||||
/**
|
||||
* @brief Action to perform when this item is activated. Must be implemented in
|
||||
* subclasses.
|
||||
*/
|
||||
virtual void action() = 0;
|
||||
|
||||
virtual void paint(QPainter *painter, const QRect &rect) const = 0;
|
||||
virtual QSize sizeHint(const QRect &rect) const = 0;
|
||||
|
||||
protected:
|
||||
QIcon icon_;
|
||||
static const QSize ICON_SIZE;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
// This allows us to store AbstractSwitcherItem * as a QVariant
|
||||
Q_DECLARE_METATYPE(chatterino::AbstractSwitcherItem *);
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "widgets/dialogs/switcher/NewTabItem.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NewTabItem::NewTabItem(const QString &channelName)
|
||||
: AbstractSwitcherItem(QIcon(":/switcher/plus.svg"))
|
||||
, channelName_(channelName)
|
||||
, text_(QString(TEXT_FORMAT).arg(channelName))
|
||||
{
|
||||
}
|
||||
|
||||
void NewTabItem::action()
|
||||
{
|
||||
auto &nb = getApp()->windows->getMainWindow().getNotebook();
|
||||
SplitContainer *container = nb.addPage(true);
|
||||
|
||||
Split *split = new Split(container);
|
||||
split->setChannel(
|
||||
getApp()->twitch.server->getOrAddChannel(this->channelName_));
|
||||
container->appendSplit(split);
|
||||
}
|
||||
|
||||
void NewTabItem::paint(QPainter *painter, const QRect &rect) const
|
||||
{
|
||||
painter->save();
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// TODO(leon): Right pen/brush/font settings?
|
||||
painter->setPen(getApp()->themes->splits.header.text);
|
||||
painter->setBrush(Qt::SolidPattern);
|
||||
painter->setFont(getApp()->fonts->getFont(FontStyle::UiMediumBold, 1.0));
|
||||
|
||||
QRect iconRect(rect.topLeft(), ICON_SIZE);
|
||||
this->icon_.paint(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
QRect textRect =
|
||||
QRect(iconRect.topRight(),
|
||||
QSize(rect.width() - iconRect.width(), iconRect.height()));
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, this->text_);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize NewTabItem::sizeHint(const QRect &rect) const
|
||||
{
|
||||
return QSize(rect.width(), ICON_SIZE.height());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/dialogs/switcher/AbstractSwitcherItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NewTabItem : public AbstractSwitcherItem
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new NewTabItem that opens a passed channel in a new
|
||||
* tab.
|
||||
*
|
||||
* @param channelName name of channel to open
|
||||
*/
|
||||
NewTabItem(const QString &channelName);
|
||||
|
||||
/**
|
||||
* @brief Open the channel passed in the constructor in a new tab.
|
||||
*/
|
||||
virtual void action() override;
|
||||
|
||||
virtual void paint(QPainter *painter, const QRect &rect) const;
|
||||
virtual QSize sizeHint(const QRect &rect) const;
|
||||
|
||||
private:
|
||||
static constexpr const char *TEXT_FORMAT = "Open channel \"%1\" in new tab";
|
||||
QString channelName_;
|
||||
QString text_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "widgets/dialogs/switcher/QuickSwitcherModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
QuickSwitcherModel::QuickSwitcherModel(QWidget *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, items_(INITIAL_ITEMS_SIZE)
|
||||
{
|
||||
}
|
||||
|
||||
QuickSwitcherModel::~QuickSwitcherModel()
|
||||
{
|
||||
for (AbstractSwitcherItem *item : this->items_)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
int QuickSwitcherModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
QVariant QuickSwitcherModel::data(const QModelIndex &index,
|
||||
int /* role */) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() >= this->items_.size())
|
||||
return QVariant();
|
||||
|
||||
auto item = this->items_.at(index.row());
|
||||
// See https://stackoverflow.com/a/44503822 .
|
||||
return QVariant::fromValue(static_cast<void *>(item));
|
||||
}
|
||||
|
||||
void QuickSwitcherModel::addItem(AbstractSwitcherItem *item)
|
||||
{
|
||||
// {begin,end}InsertRows needs to be called to notify attached views
|
||||
this->beginInsertRows(QModelIndex(), this->items_.size(),
|
||||
this->items_.size());
|
||||
this->items_.append(item);
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void QuickSwitcherModel::clear()
|
||||
{
|
||||
// {begin,end}RemoveRows needs to be called to notify attached views
|
||||
this->beginRemoveRows(QModelIndex(), 0, this->items_.size() - 1);
|
||||
|
||||
for (AbstractSwitcherItem *item : this->items_)
|
||||
{
|
||||
delete item;
|
||||
}
|
||||
this->items_.clear();
|
||||
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/dialogs/switcher/AbstractSwitcherItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class QuickSwitcherModel : public QAbstractListModel
|
||||
{
|
||||
public:
|
||||
QuickSwitcherModel(QWidget *parent = nullptr);
|
||||
~QuickSwitcherModel();
|
||||
|
||||
/**
|
||||
* @brief Reimplements QAbstractItemModel::rowCount.
|
||||
*
|
||||
* @return number of items currrently present in this model
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
|
||||
/**
|
||||
* @brief Reimplements QAbstractItemModel::data. Currently, the role parameter
|
||||
* is not used and an AbstractSwitcherItem * is always returned.
|
||||
*
|
||||
* @param index index of item to fetch data from
|
||||
* @param role (not used)
|
||||
*
|
||||
* @return AbstractSwitcherItem * (wrapped as QVariant) at index
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
|
||||
/**
|
||||
* @brief Add an item to this QuickSwitcherModel. It will be displayed in
|
||||
* attached views.
|
||||
*
|
||||
* NOTE: The model will take ownership of the pointer. In particular,
|
||||
* the same item should not be passed to multiple QuickSwitcherModels.
|
||||
*
|
||||
* @param item item to add to the model
|
||||
*/
|
||||
void addItem(AbstractSwitcherItem *item);
|
||||
|
||||
/**
|
||||
* @brief Clears this QuickSwitcherModel of all items. This will delete all
|
||||
* AbstractSwitcherItems added after the last invokation of
|
||||
* QuickSwitcherModel::clear (and invalidate their pointers).
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
/*
|
||||
* On my system, the default QVector capacity is 0. 20 is an attempt at preventing
|
||||
* frequent reallocations. The number is not backed by any user data but rather a
|
||||
* guess at how many switcher items are probably going to be added.
|
||||
*/
|
||||
static constexpr int INITIAL_ITEMS_SIZE = 20;
|
||||
|
||||
QVector<AbstractSwitcherItem *> items_;
|
||||
};
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,200 @@
|
||||
#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))
|
||||
{
|
||||
SwitchSplitItem *item = new SwitchSplitItem(split);
|
||||
this->switcherModel_.addItem(item);
|
||||
|
||||
// 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))
|
||||
{
|
||||
SwitchSplitItem *item = new SwitchSplitItem(sc);
|
||||
this->switcherModel_.addItem(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
nextPage:;
|
||||
}
|
||||
|
||||
// Add item for opening a channel in a new tab
|
||||
if (!text.isEmpty())
|
||||
{
|
||||
NewTabItem *item = new NewTabItem(text);
|
||||
this->switcherModel_.addItem(item);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "widgets/BasePopup.hpp"
|
||||
#include "widgets/dialogs/switcher/QuickSwitcherModel.hpp"
|
||||
#include "widgets/dialogs/switcher/SwitcherItemDelegate.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class QuickSwitcherPopup : public BasePopup
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new QuickSwitcherPopup.
|
||||
*
|
||||
* @param parent Parent widget of the popup. The popup will be placed
|
||||
* in the center of the parent widget.
|
||||
*/
|
||||
explicit QuickSwitcherPopup(QWidget *parent = nullptr);
|
||||
|
||||
~QuickSwitcherPopup();
|
||||
|
||||
protected:
|
||||
virtual bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void updateSuggestions(const QString &text);
|
||||
|
||||
private:
|
||||
static const QSize MINIMUM_SIZE;
|
||||
|
||||
struct {
|
||||
QLineEdit *searchEdit{};
|
||||
QListView *list{};
|
||||
} ui_;
|
||||
|
||||
QuickSwitcherModel switcherModel_;
|
||||
SwitcherItemDelegate switcherItemDelegate_;
|
||||
|
||||
QSet<SplitContainer *> openPages_;
|
||||
|
||||
void initWidgets();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "widgets/dialogs/switcher/SwitchSplitItem.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
SwitchSplitItem::SwitchSplitItem(Split *split)
|
||||
: AbstractSwitcherItem(QIcon(":switcher/switch.svg"))
|
||||
, split_(split)
|
||||
, container_(split->getContainer())
|
||||
{
|
||||
}
|
||||
|
||||
SwitchSplitItem::SwitchSplitItem(SplitContainer *container)
|
||||
: AbstractSwitcherItem(QIcon(":switcher/switch.svg"))
|
||||
, container_(container)
|
||||
{
|
||||
}
|
||||
|
||||
void SwitchSplitItem::action()
|
||||
{
|
||||
auto &nb = getApp()->windows->getMainWindow().getNotebook();
|
||||
nb.select(this->container_);
|
||||
|
||||
/*
|
||||
* If the item is referring to a specific channel, select the
|
||||
* corresponding split.
|
||||
*/
|
||||
if (this->split_)
|
||||
{
|
||||
this->container_->setSelected(this->split_);
|
||||
}
|
||||
}
|
||||
|
||||
void SwitchSplitItem::paint(QPainter *painter, const QRect &rect) const
|
||||
{
|
||||
painter->save();
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// TODO(leon): Right pen/brush/font settings?
|
||||
painter->setPen(getApp()->themes->splits.header.text);
|
||||
painter->setBrush(Qt::SolidPattern);
|
||||
painter->setFont(getApp()->fonts->getFont(FontStyle::UiMediumBold, 1.0));
|
||||
|
||||
QRect iconRect(rect.topLeft(), ICON_SIZE);
|
||||
this->icon_.paint(painter, iconRect, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
if (this->split_)
|
||||
{
|
||||
// Draw channel name and name of the containing tab
|
||||
const auto availableTextWidth = rect.width() - iconRect.width();
|
||||
QRect leftTextRect =
|
||||
QRect(iconRect.topRight(),
|
||||
QSize(0.3 * availableTextWidth, iconRect.height()));
|
||||
|
||||
painter->drawText(leftTextRect, Qt::AlignLeft | Qt::AlignVCenter,
|
||||
this->split_->getChannel()->getName());
|
||||
|
||||
QRect rightTextRect =
|
||||
QRect(leftTextRect.topRight(),
|
||||
QSize(0.7 * availableTextWidth, iconRect.height()));
|
||||
|
||||
painter->setFont(getApp()->fonts->getFont(FontStyle::UiMedium, 1.0));
|
||||
painter->drawText(rightTextRect, Qt::AlignRight | Qt::AlignVCenter,
|
||||
this->container_->getTab()->getTitle());
|
||||
}
|
||||
else if (!this->split_ && this->container_)
|
||||
{
|
||||
// Only draw name of tab
|
||||
QRect textRect =
|
||||
QRect(iconRect.topRight(),
|
||||
QSize(rect.width() - iconRect.width(), iconRect.height()));
|
||||
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter,
|
||||
this->container_->getTab()->getTitle());
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize SwitchSplitItem::sizeHint(const QRect &rect) const
|
||||
{
|
||||
return QSize(rect.width(), ICON_SIZE.height());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/dialogs/switcher/AbstractSwitcherItem.hpp"
|
||||
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
#include "widgets/helper/NotebookTab.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class SwitchSplitItem : public AbstractSwitcherItem
|
||||
{
|
||||
public:
|
||||
SwitchSplitItem(Split *split);
|
||||
SwitchSplitItem(SplitContainer *container);
|
||||
|
||||
virtual void action() override;
|
||||
|
||||
virtual void paint(QPainter *painter, const QRect &rect) const;
|
||||
virtual QSize sizeHint(const QRect &rect) const;
|
||||
|
||||
private:
|
||||
Split *split_{};
|
||||
SplitContainer *container_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "widgets/dialogs/switcher/SwitcherItemDelegate.hpp"
|
||||
|
||||
#include "widgets/dialogs/switcher/AbstractSwitcherItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
SwitcherItemDelegate::SwitcherItemDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
SwitcherItemDelegate::~SwitcherItemDelegate()
|
||||
{
|
||||
}
|
||||
|
||||
void SwitcherItemDelegate::paint(QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
auto *item = AbstractSwitcherItem::fromVariant(index.data());
|
||||
|
||||
if (item)
|
||||
{
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->fillRect(option.rect, option.palette.highlight());
|
||||
|
||||
item->paint(painter, option.rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
}
|
||||
|
||||
QSize SwitcherItemDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
auto *item = AbstractSwitcherItem::fromVariant(index.data());
|
||||
|
||||
if (item)
|
||||
{
|
||||
return item->sizeHint(option.rect);
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::sizeHint(option, index);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class SwitcherItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SwitcherItemDelegate(QObject *parent = nullptr);
|
||||
~SwitcherItemDelegate();
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -182,6 +182,7 @@ public:
|
||||
Position deleteSplit(Split *split);
|
||||
|
||||
void selectNextSplit(Direction direction);
|
||||
void setSelected(Split *selected_);
|
||||
|
||||
void decodeFromJson(QJsonObject &obj);
|
||||
|
||||
@@ -214,7 +215,6 @@ protected:
|
||||
|
||||
private:
|
||||
void layout();
|
||||
void setSelected(Split *selected_);
|
||||
void selectSplitRecursive(Node *node, Direction direction);
|
||||
void focusSplitRecursive(Node *node, Direction direction);
|
||||
void setPreferedTargetRecursive(Node *node);
|
||||
|
||||
Reference in New Issue
Block a user