41e98a51d5a9cf2c728837705086d9b12ba34321
[quassel.git] / src / qtui / bufferwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QLayout>
22 #include <QKeyEvent>
23 #include <QMenu>
24 #include <QScrollBar>
25
26 #include "action.h"
27 #include "actioncollection.h"
28 #include "bufferwidget.h"
29 #include "chatline.h"
30 #include "chatview.h"
31 #include "chatviewsearchbar.h"
32 #include "chatviewsearchcontroller.h"
33 #include "chatviewsettings.h"
34 #include "client.h"
35 #include "icon.h"
36 #include "multilineedit.h"
37 #include "qtui.h"
38 #include "settings.h"
39
40 BufferWidget::BufferWidget(QWidget *parent)
41     : AbstractBufferContainer(parent),
42     _chatViewSearchController(new ChatViewSearchController(this)),
43     _autoMarkerLine(true),
44     _autoMarkerLineOnLostFocus(true)
45 {
46     ui.setupUi(this);
47     layout()->setContentsMargins(0, 0, 0, 0);
48     layout()->setSpacing(0);
49     // ui.searchBar->hide();
50
51     _chatViewSearchController->setCaseSensitive(ui.searchBar->caseSensitiveBox()->isChecked());
52     _chatViewSearchController->setSearchSenders(ui.searchBar->searchSendersBox()->isChecked());
53     _chatViewSearchController->setSearchMsgs(ui.searchBar->searchMsgsBox()->isChecked());
54     _chatViewSearchController->setSearchOnlyRegularMsgs(ui.searchBar->searchOnlyRegularMsgsBox()->isChecked());
55
56     connect(ui.searchBar, &ChatViewSearchBar::searchChanged,
57         _chatViewSearchController, &ChatViewSearchController::setSearchString);
58     connect(ui.searchBar->caseSensitiveBox(), &QAbstractButton::toggled,
59         _chatViewSearchController, &ChatViewSearchController::setCaseSensitive);
60     connect(ui.searchBar->searchSendersBox(), &QAbstractButton::toggled,
61         _chatViewSearchController, &ChatViewSearchController::setSearchSenders);
62     connect(ui.searchBar->searchMsgsBox(), &QAbstractButton::toggled,
63         _chatViewSearchController, &ChatViewSearchController::setSearchMsgs);
64     connect(ui.searchBar->searchOnlyRegularMsgsBox(), &QAbstractButton::toggled,
65         _chatViewSearchController, &ChatViewSearchController::setSearchOnlyRegularMsgs);
66     connect(ui.searchBar->searchUpButton(), &QAbstractButton::clicked,
67         _chatViewSearchController, &ChatViewSearchController::highlightPrev);
68     connect(ui.searchBar->searchDownButton(), &QAbstractButton::clicked,
69         _chatViewSearchController, &ChatViewSearchController::highlightNext);
70
71     connect(ui.searchBar, &ChatViewSearchBar::hidden, this, selectOverload<>(&QWidget::setFocus));
72
73     connect(_chatViewSearchController, &ChatViewSearchController::newCurrentHighlight,
74         this, &BufferWidget::scrollToHighlight);
75
76     ActionCollection *coll = QtUi::actionCollection();
77     coll->addActions({
78         {"ZoomInChatView", new Action{icon::get("zoom-in"), tr("Zoom In"), coll, this, &BufferWidget::zoomIn, QKeySequence::ZoomIn}},
79         {"ZoomOutChatView", new Action{icon::get("zoom-out"), tr("Zoom Out"), coll, this, &BufferWidget::zoomOut, QKeySequence::ZoomOut}},
80         {"ZoomOriginalChatView", new Action{icon::get("zoom-original"), tr("Actual Size"), coll, this, &BufferWidget::zoomOriginal}},
81         {"SetMarkerLineToBottom", new Action{tr("Set Marker Line"), coll, this, [this]() { setMarkerLine(); }, QKeySequence(Qt::CTRL + Qt::Key_R)}}
82     });
83     coll = QtUi::actionCollection("Navigation");
84     coll->addAction("JumpToMarkerLine", new Action{tr("Go to Marker Line"), coll, this, [this]() { jumpToMarkerLine(); }, QKeySequence(Qt::CTRL + Qt::Key_K)});
85
86     ChatViewSettings s;
87     s.initAndNotify("AutoMarkerLine", this, SLOT(setAutoMarkerLine(QVariant)), true);
88     s.initAndNotify("AutoMarkerLineOnLostFocus", this, SLOT(setAutoMarkerLineOnLostFocus(QVariant)), true);
89 }
90
91
92 BufferWidget::~BufferWidget()
93 {
94     delete _chatViewSearchController;
95     _chatViewSearchController = nullptr;
96 }
97
98
99 void BufferWidget::setAutoMarkerLine(const QVariant &v)
100 {
101     _autoMarkerLine = v.toBool();
102 }
103
104 void BufferWidget::setAutoMarkerLineOnLostFocus(const QVariant &v)
105 {
106     _autoMarkerLineOnLostFocus = v.toBool();
107 }
108
109
110 AbstractChatView *BufferWidget::createChatView(BufferId id)
111 {
112     ChatView *chatView;
113     chatView = new ChatView(id, this);
114     chatView->setBufferContainer(this);
115     _chatViews[id] = chatView;
116     ui.stackedWidget->addWidget(chatView);
117     chatView->setFocusProxy(this);
118     return chatView;
119 }
120
121
122 void BufferWidget::removeChatView(BufferId id)
123 {
124     QWidget *view = _chatViews.value(id, 0);
125     if (!view) return;
126     ui.stackedWidget->removeWidget(view);
127     view->deleteLater();
128     _chatViews.take(id);
129 }
130
131
132 void BufferWidget::showChatView(BufferId id)
133 {
134     if (!id.isValid()) {
135         ui.stackedWidget->setCurrentWidget(ui.page);
136     }
137     else {
138         auto *view = qobject_cast<ChatView *>(_chatViews.value(id));
139         Q_ASSERT(view);
140         ui.stackedWidget->setCurrentWidget(view);
141         _chatViewSearchController->setScene(view->scene());
142     }
143 }
144
145
146 void BufferWidget::scrollToHighlight(QGraphicsItem *highlightItem)
147 {
148     auto *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
149     if (view) {
150         view->centerOn(highlightItem);
151     }
152 }
153
154
155 void BufferWidget::zoomIn()
156 {
157     auto *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
158     if (view)
159         view->zoomIn();
160 }
161
162
163 void BufferWidget::zoomOut()
164 {
165     auto *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
166     if (view)
167         view->zoomOut();
168 }
169
170
171 void BufferWidget::zoomOriginal()
172 {
173     auto *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
174     if (view)
175         view->zoomOriginal();
176 }
177
178
179 void BufferWidget::addActionsToMenu(QMenu *menu, const QPointF &pos)
180 {
181     Q_UNUSED(pos);
182     ActionCollection *coll = QtUi::actionCollection();
183     menu->addSeparator();
184     menu->addAction(coll->action("ZoomInChatView"));
185     menu->addAction(coll->action("ZoomOutChatView"));
186     menu->addAction(coll->action("ZoomOriginalChatView"));
187 }
188
189
190 bool BufferWidget::eventFilter(QObject *watched, QEvent *event)
191 {
192     if (event->type() != QEvent::KeyPress)
193         return false;
194
195     auto *keyEvent = static_cast<QKeyEvent *>(event);
196
197     auto *inputLine = qobject_cast<MultiLineEdit *>(watched);
198     if (!inputLine)
199         return false;
200
201     // Intercept copy key presses
202     if (keyEvent == QKeySequence::Copy) {
203         if (inputLine->hasSelectedText())
204             return false;
205         auto *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
206         if (view)
207             view->scene()->selectionToClipboard();
208         return true;
209     }
210
211     // We don't want to steal cursor movement keys if the input line is in multiline mode
212     if (!inputLine->isSingleLine())
213         return false;
214
215     switch (keyEvent->key()) {
216     case Qt::Key_Up:
217     case Qt::Key_Down:
218         if (!(keyEvent->modifiers() & Qt::ShiftModifier))
219             return false;
220         // fallthrough
221     case Qt::Key_PageUp:
222     case Qt::Key_PageDown:
223         // static cast to access public qobject::event
224         return static_cast<QObject *>(ui.stackedWidget->currentWidget())->event(event);
225     default:
226         return false;
227     }
228 }
229
230
231 void BufferWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous)
232 {
233     auto *prevView = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
234
235     AbstractBufferContainer::currentChanged(current, previous); // switch first to avoid a redraw
236
237     // we need to hide the marker line if it's already/still at the bottom of the view (and not scrolled up)
238     auto *curView = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
239     if (curView) {
240         BufferId curBufferId = current.data(NetworkModel::BufferIdRole).value<BufferId>();
241         if (curBufferId.isValid()) {
242             MsgId markerMsgId = Client::networkModel()->markerLineMsgId(curBufferId);
243             if (markerMsgId == curView->lastMsgId() && markerMsgId == curView->lastVisibleMsgId())
244                 curView->setMarkerLineVisible(false);
245             else
246                 curView->setMarkerLineVisible(true);
247         }
248     }
249
250     if (prevView && autoMarkerLine())
251         setMarkerLine(prevView, false);
252 }
253
254
255 void BufferWidget::setMarkerLine(ChatView *view, bool allowGoingBack)
256 {
257     if (!view)
258         view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
259     if (!view)
260         return;
261
262     ChatLine *lastLine = view->lastVisibleChatLine();
263     if (lastLine) {
264         QModelIndex idx = lastLine->index();
265         MsgId msgId = idx.data(MessageModel::MsgIdRole).value<MsgId>();
266         BufferId bufId = view->scene()->singleBufferId();
267
268         if (!allowGoingBack) {
269             MsgId oldMsgId = Client::markerLine(bufId);
270             if (oldMsgId.isValid() && msgId <= oldMsgId)
271                 return;
272         }
273         Client::setMarkerLine(bufId, msgId);
274     }
275 }
276
277
278 void BufferWidget::jumpToMarkerLine(ChatView *view, bool requestBacklog)
279 {
280     if (!view)
281         view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget());
282     if (!view)
283         return;
284
285     view->jumpToMarkerLine(requestBacklog);
286 }