#include "widgets/splitcontainer.hpp" #include "common.hpp" #include "singletons/thememanager.hpp" #include "util/helpers.hpp" #include "util/layoutcreator.hpp" #include "widgets/helper/notebooktab.hpp" #include "widgets/notebook.hpp" #include "widgets/split.hpp" #include #include #include #include #include #include #include #include #include #include namespace chatterino { namespace widgets { bool SplitContainer::isDraggingSplit = false; Split *SplitContainer::draggingSplit = nullptr; // SplitContainer::Position SplitContainer::dragOriginalPosition; SplitContainer::SplitContainer(Notebook *parent, NotebookTab *_tab) : BaseWidget(parent) , tab(_tab) , dropPreview(this) { this->tab->page = this; // this->setLayout(&this->ui.parentLayout); // this->setHidden(true); // this->setAcceptDrops(true); // this->ui.parentLayout.addSpacing(1); // this->ui.parentLayout.addLayout(&this->ui.hbox); // this->ui.parentLayout.setMargin(0); // this->ui.hbox.setSpacing(1); // this->ui.hbox.setMargin(0); this->refreshTabTitle(); this->managedConnect(Split::altPressedStatusChanged, [this](auto) { this->layout(); }); this->setAcceptDrops(true); } NotebookTab *SplitContainer::getTab() const { return this->tab; } void SplitContainer::appendNewSplit(bool openChannelNameDialog) { Split *split = new Split(this); this->appendSplit(split); if (openChannelNameDialog) { split->showChangeChannelPopup("Open channel name", true, [=](bool ok) { if (!ok) { this->deleteSplit(split); } }); } } void SplitContainer::appendSplit(Split *split) { this->insertSplit(split, Direction::Right); } void SplitContainer::insertSplit(Split *split, const Position &position) { this->insertSplit(split, position.direction, position.relativeNode); } void SplitContainer::insertSplit(Split *split, Direction direction, Split *relativeTo) { Node *node = this->baseNode.findNodeContainingSplit(relativeTo); assert(node != nullptr); this->insertSplit(split, direction, node); } 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); } } else { assert(this->baseNode.isOrContainsNode(relativeTo)); relativeTo->insertSplitRelative(split, direction); } split->setParent(this); split->show(); this->splits.push_back(split); this->layout(); } SplitContainer::Position SplitContainer::releaseSplit(Split *split) { 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; } SplitContainer::Position SplitContainer::deleteSplit(Split *split) { assert(split != nullptr); split->deleteLater(); return releaseSplit(split); } void SplitContainer::layout() { this->baseNode.geometry = this->rect(); std::vector _dropRects; this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects); _dropRects.clear(); this->baseNode.layout(Split::altPressesStatus | this->isDragging, _dropRects); this->dropRects = std::move(_dropRects); this->update(); } /// EVENTS void SplitContainer::resizeEvent(QResizeEvent *event) { BaseWidget::resizeEvent(event); this->layout(); } 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); } } } } void SplitContainer::paintEvent(QPaintEvent *) { QPainter painter(this); if (this->splits.size() == 0) { painter.fillRect(rect(), this->themeManager->splits.background); painter.setPen(this->themeManager->splits.header.text); QString text = "Click to add a "; Notebook *notebook = dynamic_cast(this->parentWidget()); if (notebook != nullptr) { if (notebook->tabCount() > 1) { text += "\n\ntip: you can drag a while holding "; text += "\nor add another one by pressing "; } } painter.drawText(rect(), text, QTextOption(Qt::AlignCenter)); } else { painter.fillRect(rect(), this->themeManager->splits.messageSeperator); } for (DropRect &dropRect : this->dropRects) { painter.setPen("#774"); painter.setBrush(QBrush("#553")); painter.drawRect(dropRect.rect.marginsRemoved(QMargins(2, 2, 2, 2))); } QBrush accentColor = (QApplication::activeWindow() == this->window() ? this->themeManager->tabs.selected.backgrounds.regular : this->themeManager->tabs.selected.backgrounds.unfocused); painter.fillRect(0, 0, width(), 1, accentColor); } void SplitContainer::dragEnterEvent(QDragEnterEvent *event) { if (!event->mimeData()->hasFormat("chatterino/split")) return; if (!SplitContainer::isDraggingSplit) { return; } this->isDragging = true; this->layout(); event->acceptProposedAction(); // this->dropRegions.clear(); // if (this->ui.hbox.count() == 0) { // this->dropRegions.push_back(DropRegion(rect(), std::pair(-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(i, -1))); // } // for (int i = 0; i < this->ui.hbox.count(); ++i) { // auto vbox = static_cast(this->ui.hbox.itemAt(i)); // 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), // std::pair(i, j))); // } // } // } // setPreviewRect(event->pos()); // event->acceptProposedAction(); } void SplitContainer::dragMoveEvent(QDragMoveEvent *event) { // setPreviewRect(event->pos()); for (auto &dropRect : this->dropRects) { if (dropRect.rect.contains(event->pos())) { event->acceptProposedAction(); return; } } event->setAccepted(false); } void SplitContainer::dragLeaveEvent(QDragLeaveEvent *event) { this->isDragging = false; this->layout(); // this->dropPreview.hide(); } void SplitContainer::dropEvent(QDropEvent *event) { // if (isDraggingSplit) { // event->acceptProposedAction(); // SplitContainer::draggingSplit->setParent(this); // addToLayout(SplitContainer::draggingSplit, dropPosition); // } // this->dropPreview.hide(); for (auto &dropRect : this->dropRects) { if (dropRect.rect.contains(event->pos())) { this->insertSplit(SplitContainer::draggingSplit, dropRect.position); event->acceptProposedAction(); break; } } this->isDragging = false; this->layout(); } // 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())); // } // } //} // int SplitContainer::splitCount() const //{ // return this->splits.size(); //} // std::pair SplitContainer::removeFromLayout(Split *widget) //{ // widget->getChannelView().tabHighlightRequested.disconnectAll(); // // 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); // this->refreshTitle(); // } // Split *neighbouringSplit = nullptr; // // Position the split was found at // int positionX = -1, positionY = -1; // bool removed = false; // QVBoxLayout *layoutToRemove = nullptr; // // Find widget in box, remove it, return its position // for (int i = 0; i < this->ui.hbox.count(); ++i) { // auto vbox = static_cast(this->ui.hbox.itemAt(i)); // auto vboxCount = vbox->count(); // for (int j = 0; j < vboxCount; ++j) { // if (vbox->itemAt(j)->widget() != widget) { // neighbouringSplit = dynamic_cast(vbox->itemAt(j)->widget()); // if (removed && neighbouringSplit != nullptr) { // // The widget we searched for has been found, and we have a split to switch // // focus to // break; // } // continue; // } // removed = true; // positionX = i; // // Remove split from box // widget->setParent(nullptr); // 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; // } // // 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 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(this->ui.hbox.itemAt(position.first)); // vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget); // this->refreshCurrentFocusCoordinates(); //} // const std::vector &SplitContainer::getSplits() const //{ // return this->splits; //} // std::vector> SplitContainer::getColumns() const //{ // std::vector> columns; // for (int i = 0; i < this->ui.hbox.count(); i++) { // std::vector cells; // QLayout *vbox = this->ui.hbox.itemAt(i)->layout(); // for (int j = 0; j < vbox->count(); j++) { // cells.push_back(dynamic_cast(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(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(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(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(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(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 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 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() { assert(this->tab != nullptr); if (!this->tab->useDefaultTitle) { return; } this->tab->setTitle("default title"); // QString newTitle = ""; // bool first = true; // for (const auto &chatWidget : this->splits) { // auto channelName = chatWidget->getChannel()->name; // if (channelName.isEmpty()) { // continue; // } // if (!first) { // newTitle += ", "; // } // newTitle += channelName; // first = false; // } // if (newTitle.isEmpty()) { // newTitle = "empty"; // } // this->tab->setTitle(newTitle); } } // namespace widgets } // namespace chatterino