diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -51,6 +51,7 @@ doc.setText("Hi World!\nHi\n"); KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); + view1->resize(400, 300); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), diff --git a/src/include/ktexteditor/message.h b/src/include/ktexteditor/message.h --- a/src/include/ktexteditor/message.h +++ b/src/include/ktexteditor/message.h @@ -128,10 +128,17 @@ * KTextEditor::View. */ enum MessagePosition { - AboveView = 0, ///< show message above view - BelowView, ///< show message below view - TopInView, ///< show message as view overlay in the top right corner - BottomInView ///< show message as view overlay om the bottom right corner + /// show message above view. + AboveView = 0, + /// show message below view. + BelowView, + /// show message as view overlay in the top right corner. + TopInView, + /// show message as view overlay in the bottom right corner. + BottomInView, + /// show message as view overlay in the center of the view. + /// @since 5.42 + CenterInView }; /** diff --git a/src/view/kateview.h b/src/view/kateview.h --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -65,13 +65,12 @@ class KateModeMenu; class KateAbstractInputMode; class KateScriptActionMenu; +class KateMessageLayout; class KToggleAction; class KSelectAction; class QAction; -class QGridLayout; -class QVBoxLayout; namespace KTextEditor { @@ -874,23 +873,17 @@ void postMessage(KTextEditor::Message *message, QList > actions); private: - /** Message widget showing KTextEditor::Messages above the View. */ - KateMessageWidget *m_topMessageWidget; - /** Message widget showing KTextEditor::Messages below the View. */ - KateMessageWidget *m_bottomMessageWidget; - /** Message widget showing KTextEditor::Messages as view overlay in top right corner. */ - KateMessageWidget *m_floatTopMessageWidget; - /** Message widget showing KTextEditor::Messages as view overlay in bottom left corner. */ - KateMessageWidget *m_floatBottomMessageWidget; + /** + * Message widgets showing KTextEditor::Messages. + * The index of the array maps to the enum KTextEditor::Message::MessagePosition. + */ + std::array m_messageWidgets{{nullptr}}; /** Layout for floating notifications */ - QVBoxLayout *m_notificationLayout; + KateMessageLayout *m_notificationLayout = nullptr; // for unit test 'tests/messagetest.cpp' public: - KateMessageWidget *messageWidget() - { - return m_floatTopMessageWidget; - } + KateMessageWidget *messageWidget(); private: /** diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -137,8 +137,6 @@ , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) - , m_floatTopMessageWidget(nullptr) - , m_floatBottomMessageWidget(nullptr) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) @@ -173,20 +171,20 @@ m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view - m_topMessageWidget = new KateMessageWidget(this); - m_topMessageWidget->hide(); + m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); + m_messageWidgets[KTextEditor::Message::AboveView]->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view - m_bottomMessageWidget = new KateMessageWidget(this); - m_bottomMessageWidget->hide(); + m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); + m_messageWidgets[KTextEditor::Message::BelowView]->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal - m_notificationLayout = new QVBoxLayout(m_viewInternal); + m_notificationLayout = new KateMessageLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); @@ -221,13 +219,15 @@ slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); - // user interaction (scrolling) starts notification auto-hide timer - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_topMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); + for (auto messageWidget : m_messageWidgets) { + if (messageWidget) { + // user interaction (scrolling) starts notification auto-hide timer + connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); - // user interaction (cursor navigation) starts notification auto-hide timer - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_topMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); + // user interaction (cursor navigation) starts notification auto-hide timer + connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); + } + } // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); @@ -330,7 +330,7 @@ if (frameAroundContents) { // top message widget - layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); @@ -360,7 +360,7 @@ layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message - layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { @@ -381,7 +381,7 @@ } else { // top message widget - layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); @@ -411,7 +411,7 @@ layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message - layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); + layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { @@ -3485,27 +3485,21 @@ QList > actions) { // just forward to KateMessageWidget :-) - if (message->position() == KTextEditor::Message::AboveView) { - m_topMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::BelowView) { - m_bottomMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::TopInView) { - if (!m_floatTopMessageWidget) { - m_floatTopMessageWidget = new KateMessageWidget(m_viewInternal, true); - m_notificationLayout->insertWidget(0, m_floatTopMessageWidget, 0, Qt::Alignment(Qt::AlignTop | Qt::AlignRight)); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); - } - m_floatTopMessageWidget->postMessage(message, actions); - } else if (message->position() == KTextEditor::Message::BottomInView) { - if (!m_floatBottomMessageWidget) { - m_floatBottomMessageWidget = new KateMessageWidget(m_viewInternal, true); - m_notificationLayout->addWidget(m_floatBottomMessageWidget, 0, Qt::Alignment(Qt::AlignBottom | Qt::AlignRight)); - connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); - connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); - } - m_floatBottomMessageWidget->postMessage(message, actions); + auto messageWidget = m_messageWidgets[message->position()]; + if (!messageWidget) { + // this branch is used for: TopInView, CenterInView, and BottomInView + messageWidget = new KateMessageWidget(m_viewInternal, true); + m_messageWidgets[message->position()] = messageWidget; + m_notificationLayout->addWidget(messageWidget, message->position()); + connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); + connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } + messageWidget->postMessage(message, actions); +} + +KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() +{ + return m_messageWidgets[KTextEditor::Message::TopInView]; } void KTextEditor::ViewPrivate::saveFoldingState() diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -34,7 +34,9 @@ #include #include #include +#include +#include #include #include #include "katetextline.h" @@ -58,6 +60,43 @@ class QTimer; class QVBoxLayout; +/** + * Class to layout KTextEditor::Message%s in KateView. Only the floating + * positions TopInView, CenterInView, and BottomInView are supported. + * AboveView and BelowView are not supported and ASSERT. + */ +class KateMessageLayout : public QLayout +{ +public: + explicit KateMessageLayout (QWidget *parent); + ~KateMessageLayout(); + + void addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos); + int count() const Q_DECL_OVERRIDE; + QLayoutItem *itemAt(int index) const Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; + QSize sizeHint() const Q_DECL_OVERRIDE; + QLayoutItem *takeAt(int index) Q_DECL_OVERRIDE; + + void add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos); + +private: + void addItem(QLayoutItem *item) Q_DECL_OVERRIDE; // never called publically + + struct ItemWrapper + { + ItemWrapper(QLayoutItem *i, KTextEditor::Message::MessagePosition pos) + : item(i) + , position(pos) + {} + + QLayoutItem * item = nullptr; + KTextEditor::Message::MessagePosition position; + }; + + QVector m_items; +}; + /** * This class is required because QScrollBar's sliderMoved() signal is * really supposed to be a sliderDragged() signal... so this way we can capture diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -72,6 +72,88 @@ #include +//BEGIN KateMessageLayout +KateMessageLayout::KateMessageLayout(QWidget *parent) + : QLayout(parent) +{ +} + +KateMessageLayout::~KateMessageLayout() +{ + while (QLayoutItem *item = takeAt(0)) + delete item; +} + +void KateMessageLayout::addItem(QLayoutItem *item) +{ + Q_ASSERT(false); + add(item, KTextEditor::Message::CenterInView); +} + +void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos) +{ + add(new QWidgetItem(widget), pos); +} + +int KateMessageLayout::count() const +{ + return m_items.size(); +} + +QLayoutItem *KateMessageLayout::itemAt(int index) const +{ + if (index < 0 || index >= m_items.size()) + return 0; + + return m_items[index]->item; +} + +void KateMessageLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + const int s = spacing(); + const QRect adjustedRect = rect.adjusted(s, s, -s, -s); + + for (auto wrapper : m_items) { + QLayoutItem *item = wrapper->item; + auto position = wrapper->position; + + if (position == KTextEditor::Message::TopInView) { + const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height()); + item->setGeometry(r); + } else if (position == KTextEditor::Message::BottomInView) { + const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height()); + item->setGeometry(r); + } else if (position == KTextEditor::Message::CenterInView) { + QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height()); + r.moveCenter(adjustedRect.center()); + item->setGeometry(r); + } else { + Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported."); + } + } +} + +QSize KateMessageLayout::sizeHint() const +{ + return QSize(); +} + +QLayoutItem *KateMessageLayout::takeAt(int index) +{ + if (index >= 0 && index < m_items.size()) { + ItemWrapper *layoutStruct = m_items.takeAt(index); + return layoutStruct->item; + } + return 0; +} + +void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos) +{ + m_items.push_back(new ItemWrapper(item, pos)); +} +//END KateMessageLayout + //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -538,10 +538,14 @@ if (!calledExternally && qAbs(viewLinesScrolled) < lines && // NOTE: on some machines we must update if the floating widget is visible // otherwise strange painting bugs may occur during scrolling... - !((m_view->m_floatTopMessageWidget && m_view->m_floatTopMessageWidget->isVisible()) || - (m_view->m_floatBottomMessageWidget && m_view->m_floatBottomMessageWidget->isVisible())) - ) - { + !((m_view->m_messageWidgets[KTextEditor::Message::TopInView] && + m_view->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible()) + ||(m_view->m_messageWidgets[KTextEditor::Message::CenterInView] && + m_view->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible()) + ||(m_view->m_messageWidgets[KTextEditor::Message::BottomInView] && + m_view->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()) + ) + ) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight());