diff --git a/src/widgets/chatwidget.cpp b/src/widgets/chatwidget.cpp index 07cb0103d..533656213 100644 --- a/src/widgets/chatwidget.cpp +++ b/src/widgets/chatwidget.cpp @@ -68,6 +68,8 @@ ChatWidget::ChatWidget(ChannelManager &_channelManager, NotebookPage *parent) std::bind(&ChatWidget::channelNameUpdated, this, std::placeholders::_1)); this->channelNameUpdated(this->channelName.getValue()); + + this->input.textInput.installEventFilter(parent); } ChatWidget::~ChatWidget() @@ -193,9 +195,14 @@ void ChatWidget::updateGifEmotes() this->view.updateGifEmotes(); } -void ChatWidget::giveFocus() +void ChatWidget::giveFocus(Qt::FocusReason reason) { - this->input.textInput.setFocus(); + this->input.textInput.setFocus(reason); +} + +bool ChatWidget::hasFocus() const +{ + return this->input.textInput.hasFocus(); } void ChatWidget::paintEvent(QPaintEvent *) diff --git a/src/widgets/chatwidget.hpp b/src/widgets/chatwidget.hpp index 1355e6f98..99f20a188 100644 --- a/src/widgets/chatwidget.hpp +++ b/src/widgets/chatwidget.hpp @@ -53,7 +53,8 @@ public: void layoutMessages(bool forceUpdate = false); void updateGifEmotes(); - void giveFocus(); + void giveFocus(Qt::FocusReason reason); + bool hasFocus() const; pajlada::Settings::Setting channelName; diff --git a/src/widgets/chatwidgetinput.cpp b/src/widgets/chatwidgetinput.cpp index 987d0a5fe..948b253c5 100644 --- a/src/widgets/chatwidgetinput.cpp +++ b/src/widgets/chatwidgetinput.cpp @@ -3,6 +3,7 @@ #include "colorscheme.hpp" #include "completionmanager.hpp" #include "ircmanager.hpp" +#include "notebookpage.hpp" #include "settingsmanager.hpp" #include @@ -71,17 +72,61 @@ ChatWidgetInput::ChatWidgetInput(ChatWidget *_chatWidget) } prevIndex = prevMsg.size(); } else if (event->key() == Qt::Key_Up) { - if (prevMsg.size() && prevIndex) { - prevIndex--; - textInput.setText(prevMsg.at(prevIndex)); + if (event->modifiers() == Qt::AltModifier) { + NotebookPage *page = static_cast(this->chatWidget->parentWidget()); + + int reqX = page->currentX; + int reqY = page->lastRequestedY[reqX] - 1; + + qDebug() << "Alt+Down to" << reqX << "/" << reqY; + + page->requestFocus(reqX, reqY); + } else { + if (prevMsg.size() && prevIndex) { + prevIndex--; + textInput.setText(prevMsg.at(prevIndex)); + } } } else if (event->key() == Qt::Key_Down) { - if (prevIndex != (prevMsg.size() - 1) && prevIndex != prevMsg.size()) { - prevIndex++; - textInput.setText(prevMsg.at(prevIndex)); + if (event->modifiers() == Qt::AltModifier) { + NotebookPage *page = static_cast(this->chatWidget->parentWidget()); + + int reqX = page->currentX; + int reqY = page->lastRequestedY[reqX] + 1; + + qDebug() << "Alt+Down to" << reqX << "/" << reqY; + + page->requestFocus(reqX, reqY); } else { - prevIndex = prevMsg.size(); - textInput.setText(QString()); + if (prevIndex != (prevMsg.size() - 1) && prevIndex != prevMsg.size()) { + prevIndex++; + textInput.setText(prevMsg.at(prevIndex)); + } else { + prevIndex = prevMsg.size(); + textInput.setText(QString()); + } + } + } else if (event->key() == Qt::Key_Left) { + if (event->modifiers() == Qt::AltModifier) { + NotebookPage *page = static_cast(this->chatWidget->parentWidget()); + + int reqX = page->currentX - 1; + int reqY = page->lastRequestedY[reqX]; + + qDebug() << "Alt+Left to" << reqX << "/" << reqY; + + page->requestFocus(reqX, reqY); + } + } else if (event->key() == Qt::Key_Right) { + if (event->modifiers() == Qt::AltModifier) { + NotebookPage *page = static_cast(this->chatWidget->parentWidget()); + + int reqX = page->currentX + 1; + int reqY = page->lastRequestedY[reqX]; + + qDebug() << "Alt+Right to" << reqX << "/" << reqY; + + page->requestFocus(reqX, reqY); } } }); @@ -148,7 +193,7 @@ void ChatWidgetInput::resizeEvent(QResizeEvent *) void ChatWidgetInput::mousePressEvent(QMouseEvent *) { - this->chatWidget->giveFocus(); + this->chatWidget->giveFocus(Qt::MouseFocusReason); } } // namespace widgets diff --git a/src/widgets/chatwidgetview.cpp b/src/widgets/chatwidgetview.cpp index 26ee54ee9..0aa537741 100644 --- a/src/widgets/chatwidgetview.cpp +++ b/src/widgets/chatwidgetview.cpp @@ -339,7 +339,7 @@ void ChatWidgetView::mousePressEvent(QMouseEvent *event) this->isMouseDown = true; this->lastPressPosition = event->screenPos(); - this->chatWidget->giveFocus(); + this->chatWidget->giveFocus(Qt::MouseFocusReason); } void ChatWidgetView::mouseReleaseEvent(QMouseEvent *event) diff --git a/src/widgets/notebookpage.cpp b/src/widgets/notebookpage.cpp index 41f3a6d2d..981205c9b 100644 --- a/src/widgets/notebookpage.cpp +++ b/src/widgets/notebookpage.cpp @@ -82,7 +82,7 @@ void NotebookPage::addToLayout(ChatWidget *widget, std::pair position = std::pair(-1, -1)) { this->chatWidgets.push_back(widget); - widget->giveFocus(); + widget->giveFocus(Qt::MouseFocusReason); // add vbox at the end if (position.first < 0 || position.first >= this->ui.hbox.count()) { @@ -90,6 +90,7 @@ void NotebookPage::addToLayout(ChatWidget *widget, vbox->addWidget(widget); this->ui.hbox.addLayout(vbox, 1); + this->refreshCurrentFocusCoordinates(); return; } @@ -99,6 +100,7 @@ void NotebookPage::addToLayout(ChatWidget *widget, vbox->addWidget(widget); this->ui.hbox.insertLayout(position.first, vbox, 1); + this->refreshCurrentFocusCoordinates(); return; } @@ -106,6 +108,8 @@ void NotebookPage::addToLayout(ChatWidget *widget, 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 &NotebookPage::getChatWidgets() const @@ -134,6 +138,103 @@ void NotebookPage::addChat(bool openChannelNameDialog) this->addToLayout(w, std::pair(-1, -1)); } +void NotebookPage::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) { + ChatWidget *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 NotebookPage::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) { + ChatWidget *chatWidget = static_cast(w); + chatWidget->giveFocus(Qt::OtherFocusReason); + } +} + void NotebookPage::enterEvent(QEvent *) { if (this->ui.hbox.count() == 0) { @@ -240,6 +341,17 @@ void NotebookPage::dropEvent(QDropEvent *event) this->dropPreview.hide(); } +bool NotebookPage::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::FocusIn) { + QFocusEvent *focusEvent = static_cast(event); + + this->refreshCurrentFocusCoordinates((focusEvent->reason() == Qt::MouseFocusReason)); + } + + return false; +} + void NotebookPage::paintEvent(QPaintEvent *) { QPainter painter(this); @@ -259,6 +371,14 @@ void NotebookPage::paintEvent(QPaintEvent *) } } +void NotebookPage::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 ChatWidget *chatWidget) { for (int i = 0; i < layout->count(); ++i) { diff --git a/src/widgets/notebookpage.hpp b/src/widgets/notebookpage.hpp index 65ad64c4d..bc367eccd 100644 --- a/src/widgets/notebookpage.hpp +++ b/src/widgets/notebookpage.hpp @@ -44,9 +44,19 @@ public: static ChatWidget *draggingSplit; static std::pair dropPosition; + int currentX = 0; + int currentY = 0; + std::map lastRequestedY; + + void refreshCurrentFocusCoordinates(bool alsoSetLastRequested = false); + void requestFocus(int x, int y); + protected: + virtual bool eventFilter(QObject *object, QEvent *event) override; virtual void paintEvent(QPaintEvent *) override; + virtual void showEvent(QShowEvent *) override; + virtual void enterEvent(QEvent *) override; virtual void leaveEvent(QEvent *) override; virtual void mouseReleaseEvent(QMouseEvent *event) override;