mirror-chatterino2/src/widgets/splitcontainer.cpp

682 lines
19 KiB
C++
Raw Normal View History

2017-11-12 17:21:50 +01:00
#include "widgets/splitcontainer.hpp"
#include "common.hpp"
2017-12-31 00:50:07 +01:00
#include "singletons/thememanager.hpp"
#include "util/helpers.hpp"
#include "util/layoutcreator.hpp"
2017-11-12 17:21:50 +01:00
#include "widgets/helper/notebooktab.hpp"
#include "widgets/notebook.hpp"
2017-11-12 17:21:50 +01:00
#include "widgets/split.hpp"
2016-12-29 17:31:07 +01:00
#include <QApplication>
#include <QDebug>
2017-01-18 04:52:47 +01:00
#include <QHBoxLayout>
#include <QMimeData>
2017-01-29 12:26:22 +01:00
#include <QObject>
2017-01-18 04:52:47 +01:00
#include <QPainter>
#include <QVBoxLayout>
#include <QWidget>
#include <boost/foreach.hpp>
2017-01-18 04:52:47 +01:00
#include <algorithm>
2017-04-14 17:52:22 +02:00
namespace chatterino {
namespace widgets {
2017-01-18 21:30:23 +01:00
2017-11-12 17:21:50 +01:00
bool SplitContainer::isDraggingSplit = false;
Split *SplitContainer::draggingSplit = nullptr;
2018-05-10 19:50:31 +02:00
// SplitContainer::Position SplitContainer::dragOriginalPosition;
2017-01-01 02:30:42 +01:00
SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab)
: BaseWidget(parent)
, tab(_tab)
, dropPreview(this)
2017-01-01 02:30:42 +01:00
{
this->tab->page = this;
2017-04-12 17:46:44 +02:00
2018-05-10 19:50:31 +02:00
// this->setLayout(&this->ui.parentLayout);
2017-04-12 17:46:44 +02:00
2018-05-10 19:50:31 +02:00
// this->setHidden(true);
// this->setAcceptDrops(true);
2017-05-29 21:26:55 +02:00
2018-05-10 19:50:31 +02:00
// this->ui.parentLayout.addSpacing(1);
// this->ui.parentLayout.addLayout(&this->ui.hbox);
// this->ui.parentLayout.setMargin(0);
2017-05-29 21:26:55 +02:00
2018-05-10 19:50:31 +02:00
// this->ui.hbox.setSpacing(1);
// this->ui.hbox.setMargin(0);
2018-05-10 19:50:31 +02:00
this->refreshTabTitle();
2017-05-29 21:26:55 +02:00
2018-05-10 19:50:31 +02:00
this->managedConnect(Split::altPressedStatusChanged, [this](auto) { this->layout(); });
2017-11-12 17:21:50 +01:00
2018-05-10 19:50:31 +02:00
this->setAcceptDrops(true);
2017-11-12 17:21:50 +01:00
}
2018-05-10 19:50:31 +02:00
NotebookTab *SplitContainer::getTab() const
{
2018-05-10 19:50:31 +02:00
return this->tab;
}
2018-05-10 19:50:31 +02:00
void SplitContainer::appendNewSplit(bool openChannelNameDialog)
2017-04-12 17:46:44 +02:00
{
2018-05-10 19:50:31 +02:00
Split *split = new Split(this);
this->appendSplit(split);
2018-05-10 19:50:31 +02:00
if (openChannelNameDialog) {
split->showChangeChannelPopup("Open channel name", true, [=](bool ok) {
if (!ok) {
this->deleteSplit(split);
}
2018-05-10 19:50:31 +02:00
});
2017-01-01 02:30:42 +01:00
}
}
2018-05-10 19:50:31 +02:00
void SplitContainer::appendSplit(Split *split)
2017-01-01 02:30:42 +01:00
{
2018-05-10 19:50:31 +02:00
this->insertSplit(split, Direction::Right);
2017-01-01 02:30:42 +01:00
}
2018-05-10 19:50:31 +02:00
void SplitContainer::insertSplit(Split *split, const Position &position)
{
2018-05-10 19:50:31 +02:00
this->insertSplit(split, position.direction, position.relativeNode);
}
2018-05-10 19:50:31 +02:00
void SplitContainer::insertSplit(Split *split, Direction direction, Split *relativeTo)
{
2018-05-10 19:50:31 +02:00
Node *node = this->baseNode.findNodeContainingSplit(relativeTo);
assert(node != nullptr);
2018-05-10 19:50:31 +02:00
this->insertSplit(split, direction, node);
}
2018-05-10 19:50:31 +02:00
void SplitContainer::insertSplit(Split *split, Direction direction, Node *relativeTo)
{
if (relativeTo == nullptr) {
if (this->baseNode.type == Node::EmptyRoot) {
this->baseNode.setSplit(split);
} else if (this->baseNode.type == Node::_Split) {
this->baseNode.nestSplitIntoCollection(split, direction);
} else {
this->baseNode.insertSplitRelative(split, direction);
}
2018-05-10 19:50:31 +02:00
} else {
assert(this->baseNode.isOrContainsNode(relativeTo));
2018-05-10 19:50:31 +02:00
relativeTo->insertSplitRelative(split, direction);
}
2018-05-10 19:50:31 +02:00
split->setParent(this);
split->show();
this->splits.push_back(split);
this->layout();
}
2018-05-10 19:50:31 +02:00
SplitContainer::Position SplitContainer::releaseSplit(Split *split)
{
2018-05-10 19:50:31 +02:00
Node *node = this->baseNode.findNodeContainingSplit(split);
assert(node != nullptr);
this->splits.erase(std::find(this->splits.begin(), this->splits.end(), split));
split->setParent(nullptr);
Position position = node->releaseSplit();
this->layout();
return position;
}
2018-05-10 19:50:31 +02:00
SplitContainer::Position SplitContainer::deleteSplit(Split *split)
{
2018-05-10 19:50:31 +02:00
assert(split != nullptr);
2018-05-10 19:50:31 +02:00
split->deleteLater();
return releaseSplit(split);
}
2018-05-10 19:50:31 +02:00
void SplitContainer::layout()
{
2018-05-10 19:50:31 +02:00
this->baseNode.geometry = this->rect();
2018-05-10 19:50:31 +02:00
std::vector<DropRect> _dropRects;
this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects);
_dropRects.clear();
this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects);
2018-05-10 19:50:31 +02:00
this->dropRects = std::move(_dropRects);
this->update();
}
2018-05-10 19:50:31 +02:00
/// EVENTS
void SplitContainer::resizeEvent(QResizeEvent *event)
{
BaseWidget::resizeEvent(event);
2018-05-10 19:50:31 +02:00
this->layout();
}
2018-05-10 19:50:31 +02:00
void SplitContainer::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (this->splits.size() == 0) {
// "Add Chat" was clicked
this->appendNewSplit(true);
this->setCursor(QCursor(Qt::ArrowCursor));
} else {
auto it =
std::find_if(this->dropRects.begin(), this->dropRects.end(),
[event](DropRect &rect) { return rect.rect.contains(event->pos()); });
if (it != this->dropRects.end()) {
this->insertSplit(new Split(this), it->position);
}
}
}
}
2018-05-10 19:50:31 +02:00
void SplitContainer::paintEvent(QPaintEvent *)
{
2018-05-10 19:50:31 +02:00
QPainter painter(this);
2018-05-10 19:50:31 +02:00
if (this->splits.size() == 0) {
painter.fillRect(rect(), this->themeManager->splits.background);
2018-05-10 19:50:31 +02:00
painter.setPen(this->themeManager->splits.header.text);
2018-05-10 19:50:31 +02:00
QString text = "Click to add a <split>";
2018-05-10 19:50:31 +02:00
Notebook *notebook = dynamic_cast<Notebook *>(this->parentWidget());
2018-05-10 19:50:31 +02:00
if (notebook != nullptr) {
if (notebook->tabCount() > 1) {
text += "\n\ntip: you can drag a <split> while holding <Alt>";
text += "\nor add another one by pressing <Ctrl+T>";
}
}
2018-05-10 19:50:31 +02:00
painter.drawText(rect(), text, QTextOption(Qt::AlignCenter));
} else {
painter.fillRect(rect(), this->themeManager->splits.messageSeperator);
}
2018-05-10 19:50:31 +02:00
for (DropRect &dropRect : this->dropRects) {
painter.setPen("#774");
painter.setBrush(QBrush("#553"));
2018-05-10 19:50:31 +02:00
painter.drawRect(dropRect.rect.marginsRemoved(QMargins(2, 2, 2, 2)));
}
2018-05-10 19:50:31 +02:00
QBrush accentColor = (QApplication::activeWindow() == this->window()
? this->themeManager->tabs.selected.backgrounds.regular
: this->themeManager->tabs.selected.backgrounds.unfocused);
2018-05-10 19:50:31 +02:00
painter.fillRect(0, 0, width(), 1, accentColor);
}
2017-11-12 17:21:50 +01:00
void SplitContainer::dragEnterEvent(QDragEnterEvent *event)
2017-01-01 02:30:42 +01:00
{
2017-01-11 18:52:09 +01:00
if (!event->mimeData()->hasFormat("chatterino/split"))
return;
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
if (!SplitContainer::isDraggingSplit) {
2017-04-12 17:46:44 +02:00
return;
}
2018-05-10 19:50:31 +02:00
this->isDragging = true;
this->layout();
2017-04-12 17:46:44 +02:00
2018-05-10 19:50:31 +02:00
event->acceptProposedAction();
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// this->dropRegions.clear();
2018-05-10 19:50:31 +02:00
// if (this->ui.hbox.count() == 0) {
// this->dropRegions.push_back(DropRegion(rect(), std::pair<int, int>(-1, -1)));
// } else {
// for (int i = 0; i < this->ui.hbox.count() + 1; ++i) {
// this->dropRegions.push_back(
// DropRegion(QRect(((i * 4 - 1) * width() / this->ui.hbox.count()) / 4, 0,
// width() / this->ui.hbox.count() / 2 + 1, height() + 1),
// std::pair<int, int>(i, -1)));
// }
2017-01-24 19:51:57 +01:00
2018-05-10 19:50:31 +02:00
// for (int i = 0; i < this->ui.hbox.count(); ++i) {
// auto vbox = static_cast<QVBoxLayout *>(this->ui.hbox.itemAt(i));
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// for (int j = 0; j < vbox->count() + 1; ++j) {
// this->dropRegions.push_back(DropRegion(
// QRect(i * width() / this->ui.hbox.count(),
// ((j * 2 - 1) * height() / vbox->count()) / 2,
// width() / this->ui.hbox.count() + 1, height() / vbox->count() + 1),
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// std::pair<int, int>(i, j)));
// }
// }
// }
// setPreviewRect(event->pos());
// event->acceptProposedAction();
2017-01-01 02:30:42 +01:00
}
2017-11-12 17:21:50 +01:00
void SplitContainer::dragMoveEvent(QDragMoveEvent *event)
2017-01-01 02:30:42 +01:00
{
2018-05-10 19:50:31 +02:00
// setPreviewRect(event->pos());
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
for (auto &dropRect : this->dropRects) {
if (dropRect.rect.contains(event->pos())) {
event->acceptProposedAction();
2017-01-01 02:30:42 +01:00
return;
}
}
2018-05-10 19:50:31 +02:00
event->setAccepted(false);
2017-01-01 02:30:42 +01:00
}
2017-11-12 17:21:50 +01:00
void SplitContainer::dragLeaveEvent(QDragLeaveEvent *event)
2017-01-01 02:30:42 +01:00
{
2018-05-10 19:50:31 +02:00
this->isDragging = false;
this->layout();
// this->dropPreview.hide();
2017-01-01 02:30:42 +01:00
}
2017-11-12 17:21:50 +01:00
void SplitContainer::dropEvent(QDropEvent *event)
2017-01-01 02:30:42 +01:00
{
2018-05-10 19:50:31 +02:00
// if (isDraggingSplit) {
// event->acceptProposedAction();
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// SplitContainer::draggingSplit->setParent(this);
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// addToLayout(SplitContainer::draggingSplit, dropPosition);
// }
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// this->dropPreview.hide();
2018-05-10 19:50:31 +02:00
for (auto &dropRect : this->dropRects) {
if (dropRect.rect.contains(event->pos())) {
this->insertSplit(SplitContainer::draggingSplit, dropRect.position);
event->acceptProposedAction();
break;
}
}
2018-05-10 19:50:31 +02:00
this->isDragging = false;
this->layout();
}
2018-05-10 19:50:31 +02:00
// void SplitContainer::updateFlexValues()
//{
// for (int i = 0; i < this->ui.hbox.count(); i++) {
// QVBoxLayout *vbox = (QVBoxLayout *)ui.hbox.itemAt(i)->layout();
// if (vbox->count() != 0) {
// ui.hbox.setStretch(i, (int)(1000 * ((Split *)vbox->itemAt(0))->getFlexSizeX()));
// }
// }
//}
2016-12-30 18:00:25 +01:00
2018-05-10 19:50:31 +02:00
// int SplitContainer::splitCount() const
//{
// return this->splits.size();
//}
2017-01-01 02:30:42 +01:00
2018-05-10 19:50:31 +02:00
// std::pair<int, int> SplitContainer::removeFromLayout(Split *widget)
//{
// widget->getChannelView().tabHighlightRequested.disconnectAll();
2018-05-10 19:50:31 +02:00
// // remove reference to chat widget from chatWidgets vector
// auto it = std::find(std::begin(this->splits), std::end(this->splits), widget);
// if (it != std::end(this->splits)) {
// this->splits.erase(it);
2018-05-10 19:50:31 +02:00
// this->refreshTitle();
// }
2018-05-10 19:50:31 +02:00
// Split *neighbouringSplit = nullptr;
2018-05-10 19:50:31 +02:00
// // Position the split was found at
// int positionX = -1, positionY = -1;
2018-05-10 19:50:31 +02:00
// bool removed = false;
2018-05-10 19:50:31 +02:00
// QVBoxLayout *layoutToRemove = nullptr;
2018-05-10 19:50:31 +02:00
// // Find widget in box, remove it, return its position
// for (int i = 0; i < this->ui.hbox.count(); ++i) {
// auto vbox = static_cast<QVBoxLayout *>(this->ui.hbox.itemAt(i));
2018-05-10 19:50:31 +02:00
// auto vboxCount = vbox->count();
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// for (int j = 0; j < vboxCount; ++j) {
// if (vbox->itemAt(j)->widget() != widget) {
// neighbouringSplit = dynamic_cast<Split *>(vbox->itemAt(j)->widget());
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// if (removed && neighbouringSplit != nullptr) {
// // The widget we searched for has been found, and we have a split to switch
// // focus to
// break;
// }
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// continue;
// }
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// removed = true;
// positionX = i;
2017-01-29 12:26:22 +01:00
2018-05-10 19:50:31 +02:00
// // Remove split from box
// widget->setParent(nullptr);
2018-05-10 19:50:31 +02:00
// if (vbox->count() == 0) {
// // The split was the last item remaining in the vbox
// // Remove the vbox once all iteration is done
// layoutToRemove = vbox;
// positionY = -1;
// break;
// }
2018-05-10 19:50:31 +02:00
// // Don't break here yet, we want to keep iterating this vbox if possible to find the
// // closest still-alive neighbour that we can switch focus to
// positionY = j;
// --j;
// --vboxCount;
// }
// if (removed && neighbouringSplit != nullptr) {
// // The widget we searched for has been found, and we have a split to switch focus to
// break;
// }
// }
// if (removed) {
// if (layoutToRemove != nullptr) {
// // The split we removed was the last split in its box. Remove the box
// // We delay the removing of the box so we can keep iterating over hbox safely
// this->ui.hbox.removeItem(layoutToRemove);
// delete layoutToRemove;
// }
// if (neighbouringSplit != nullptr) {
// // We found a neighbour split we can switch focus to
// neighbouringSplit->giveFocus(Qt::MouseFocusReason);
// }
// }
// return std::make_pair(positionX, positionY);
//}
// void SplitContainer::addToLayout(Split *widget, std::pair<int, int> position)
//{
// this->splits.push_back(widget);
// widget->getChannelView().tabHighlightRequested.connect(
// [this](HighlightState state) { this->tab->setHighlightState(state); });
// this->refreshTitle();
// widget->giveFocus(Qt::MouseFocusReason);
// // add vbox at the end
// if (position.first < 0 || position.first >= this->ui.hbox.count()) {
// auto vbox = new QVBoxLayout();
// vbox->addWidget(widget);
// this->ui.hbox.addLayout(vbox, 1);
// this->refreshCurrentFocusCoordinates();
// return;
// }
// // insert vbox
// if (position.second == -1) {
// auto vbox = new QVBoxLayout();
// vbox->addWidget(widget);
// this->ui.hbox.insertLayout(position.first, vbox, 1);
// this->refreshCurrentFocusCoordinates();
// return;
// }
// // add to existing vbox
// auto vbox = static_cast<QVBoxLayout *>(this->ui.hbox.itemAt(position.first));
// vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget);
// this->refreshCurrentFocusCoordinates();
//}
// const std::vector<Split *> &SplitContainer::getSplits() const
//{
// return this->splits;
//}
// std::vector<std::vector<Split *>> SplitContainer::getColumns() const
//{
// std::vector<std::vector<Split *>> columns;
// for (int i = 0; i < this->ui.hbox.count(); i++) {
// std::vector<Split *> cells;
// QLayout *vbox = this->ui.hbox.itemAt(i)->layout();
// for (int j = 0; j < vbox->count(); j++) {
// cells.push_back(dynamic_cast<Split *>(vbox->itemAt(j)->widget()));
// }
// columns.push_back(cells);
// }
// return columns;
//}
// void SplitContainer::refreshCurrentFocusCoordinates(bool alsoSetLastRequested)
//{
// int setX = -1;
// int setY = -1;
// bool doBreak = false;
// for (int x = 0; x < this->ui.hbox.count(); ++x) {
// QLayoutItem *item = this->ui.hbox.itemAt(x);
// if (item->isEmpty()) {
// setX = x;
// break;
// }
// QVBoxLayout *vbox = static_cast<QVBoxLayout *>(item->layout());
// for (int y = 0; y < vbox->count(); ++y) {
// QLayoutItem *innerItem = vbox->itemAt(y);
// if (innerItem->isEmpty()) {
// setX = x;
// setY = y;
// doBreak = true;
// break;
// }
// QWidget *w = innerItem->widget();
// if (w) {
// Split *chatWidget = static_cast<Split *>(w);
// if (chatWidget->hasFocus()) {
// setX = x;
// setY = y;
// doBreak = true;
// break;
// }
// }
// }
// if (doBreak) {
// break;
// }
// }
// if (setX != -1) {
// this->currentX = setX;
// if (setY != -1) {
// this->currentY = setY;
// if (alsoSetLastRequested) {
// this->lastRequestedY[setX] = setY;
// }
// }
// }
//}
// void SplitContainer::requestFocus(int requestedX, int requestedY)
//{
// // XXX: Perhaps if we request an Y coordinate out of bounds, we shuold set all previously set
// // requestedYs to 0 (if -1 is requested) or that x-coordinates vbox count (if requestedY >=
// // currentvbox.count() is requested)
// if (requestedX < 0 || requestedX >= this->ui.hbox.count()) {
// return;
// }
// QLayoutItem *item = this->ui.hbox.itemAt(requestedX);
// QWidget *xW = item->widget();
// if (item->isEmpty()) {
// qDebug() << "Requested hbox item " << requestedX << "is empty";
// if (xW) {
// qDebug() << "but xW is not null";
// // TODO: figure out what to do here
// }
// return;
// }
// QVBoxLayout *vbox = static_cast<QVBoxLayout *>(item->layout());
// if (requestedY < 0) {
// requestedY = 0;
// } else if (requestedY >= vbox->count()) {
// requestedY = vbox->count() - 1;
// }
// this->lastRequestedY[requestedX] = requestedY;
// QLayoutItem *innerItem = vbox->itemAt(requestedY);
// if (innerItem->isEmpty()) {
// qDebug() << "Requested vbox item " << requestedY << "is empty";
// return;
// }
// QWidget *w = innerItem->widget();
// if (w) {
// Split *chatWidget = static_cast<Split *>(w);
// chatWidget->giveFocus(Qt::OtherFocusReason);
// }
//}
// void SplitContainer::enterEvent(QEvent *)
//{
// if (this->ui.hbox.count() == 0) {
// this->setCursor(QCursor(Qt::PointingHandCursor));
// } else {
// this->setCursor(QCursor(Qt::ArrowCursor));
// }
//}
// void SplitContainer::leaveEvent(QEvent *)
//{
//}
// void SplitContainer::setPreviewRect(QPoint mousePos)
//{
// for (DropRegion region : this->dropRegions) {
// if (region.rect.contains(mousePos)) {
// this->dropPreview.setBounds(region.rect);
// if (!this->dropPreview.isVisible()) {
// this->dropPreview.setGeometry(this->rect());
// this->dropPreview.show();
// this->dropPreview.raise();
// }
// dropPosition = region.position;
// return;
// }
// }
// this->dropPreview.hide();
//}
// bool SplitContainer::eventFilter(QObject *object, QEvent *event)
//{
// if (event->type() == QEvent::FocusIn) {
// QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
// this->refreshCurrentFocusCoordinates((focusEvent->reason() == Qt::MouseFocusReason));
// }
// return false;
//}
// void SplitContainer::showEvent(QShowEvent *event)
//{
// // Whenever this notebook page is shown, give focus to the last focused chat widget
// // If this is the first time this notebook page is shown, it will give focus to the
// top-left
// // chat widget
// this->requestFocus(this->currentX, this->currentY);
//}
// static std::pair<int, int> getWidgetPositionInLayout(QLayout *layout, const Split
// *chatWidget)
//{
// for (int i = 0; i < layout->count(); ++i) {
// printf("xD\n");
// }
// return std::make_pair(-1, -1);
//}
// std::pair<int, int> SplitContainer::getChatPosition(const Split *chatWidget)
//{
// auto layout = this->ui.hbox.layout();
// if (layout == nullptr) {
// return std::make_pair(-1, -1);
// }
// return getWidgetPositionInLayout(layout, chatWidget);
//}
// Split *SplitContainer::createChatWidget()
//{
// auto split = new Split(this);
// return split;
//}
void SplitContainer::refreshTabTitle()
{
2018-05-10 19:50:31 +02:00
assert(this->tab != nullptr);
if (!this->tab->useDefaultTitle) {
return;
}
2018-05-10 19:50:31 +02:00
this->tab->setTitle("default title");
2018-05-10 19:50:31 +02:00
// QString newTitle = "";
// bool first = true;
2018-05-10 19:50:31 +02:00
// for (const auto &chatWidget : this->splits) {
// auto channelName = chatWidget->getChannel()->name;
// if (channelName.isEmpty()) {
// continue;
// }
2018-05-10 19:50:31 +02:00
// if (!first) {
// newTitle += ", ";
// }
// newTitle += channelName;
2018-05-10 19:50:31 +02:00
// first = false;
// }
// if (newTitle.isEmpty()) {
// newTitle = "empty";
// }
2018-05-10 19:50:31 +02:00
// this->tab->setTitle(newTitle);
}
2017-04-14 17:52:22 +02:00
} // namespace widgets
} // namespace chatterino