1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 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 "iconloader.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, SIGNAL(searchChanged(const QString &)), |
57 | _chatViewSearchController, SLOT(setSearchString(const QString &))); |
58 | connect(ui.searchBar->caseSensitiveBox(), SIGNAL(toggled(bool)), |
59 | _chatViewSearchController, SLOT(setCaseSensitive(bool))); |
60 | connect(ui.searchBar->searchSendersBox(), SIGNAL(toggled(bool)), |
61 | _chatViewSearchController, SLOT(setSearchSenders(bool))); |
62 | connect(ui.searchBar->searchMsgsBox(), SIGNAL(toggled(bool)), |
63 | _chatViewSearchController, SLOT(setSearchMsgs(bool))); |
64 | connect(ui.searchBar->searchOnlyRegularMsgsBox(), SIGNAL(toggled(bool)), |
65 | _chatViewSearchController, SLOT(setSearchOnlyRegularMsgs(bool))); |
66 | connect(ui.searchBar->searchUpButton(), SIGNAL(clicked()), |
67 | _chatViewSearchController, SLOT(highlightPrev())); |
68 | connect(ui.searchBar->searchDownButton(), SIGNAL(clicked()), |
69 | _chatViewSearchController, SLOT(highlightNext())); |
70 | |
71 | connect(ui.searchBar, SIGNAL(hidden()), this, SLOT(setFocus())); |
72 | |
73 | connect(_chatViewSearchController, SIGNAL(newCurrentHighlight(QGraphicsItem *)), |
74 | this, SLOT(scrollToHighlight(QGraphicsItem *))); |
75 | |
76 | ActionCollection *coll = QtUi::actionCollection(); |
77 | |
78 | Action *zoomInChatview = coll->add<Action>("ZoomInChatView" , this, SLOT(zoomIn())); |
79 | zoomInChatview->setText(tr("Zoom In" )); |
80 | zoomInChatview->setIcon(SmallIcon("zoom-in" )); |
81 | zoomInChatview->setShortcut(QKeySequence::ZoomIn); |
82 | |
83 | Action *zoomOutChatview = coll->add<Action>("ZoomOutChatView" , this, SLOT(zoomOut())); |
84 | zoomOutChatview->setIcon(SmallIcon("zoom-out" )); |
85 | zoomOutChatview->setText(tr("Zoom Out" )); |
86 | zoomOutChatview->setShortcut(QKeySequence::ZoomOut); |
87 | |
88 | Action *zoomOriginalChatview = coll->add<Action>("ZoomOriginalChatView" , this, SLOT(zoomOriginal())); |
89 | zoomOriginalChatview->setIcon(SmallIcon("zoom-original" )); |
90 | zoomOriginalChatview->setText(tr("Actual Size" )); |
91 | //zoomOriginalChatview->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_0)); // used for RTS switching |
92 | |
93 | Action *setMarkerLine = coll->add<Action>("SetMarkerLineToBottom" , this, SLOT(setMarkerLine())); |
94 | setMarkerLine->setText(tr("Set Marker Line" )); |
95 | setMarkerLine->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); |
96 | |
97 | Action *jumpToMarkerLine = QtUi::actionCollection("Navigation" )->add<Action>("JumpToMarkerLine" , this, SLOT(jumpToMarkerLine())); |
98 | jumpToMarkerLine->setText(tr("Go to Marker Line" )); |
99 | jumpToMarkerLine->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K)); |
100 | |
101 | ChatViewSettings s; |
102 | s.initAndNotify("AutoMarkerLine" , this, SLOT(setAutoMarkerLine(QVariant)), true); |
103 | s.initAndNotify("AutoMarkerLineOnLostFocus" , this, SLOT(setAutoMarkerLineOnLostFocus(QVariant)), true); |
104 | } |
105 | |
106 | |
107 | BufferWidget::~BufferWidget() |
108 | { |
109 | delete _chatViewSearchController; |
110 | _chatViewSearchController = 0; |
111 | } |
112 | |
113 | |
114 | void BufferWidget::setAutoMarkerLine(const QVariant &v) |
115 | { |
116 | _autoMarkerLine = v.toBool(); |
117 | } |
118 | |
119 | void BufferWidget::setAutoMarkerLineOnLostFocus(const QVariant &v) |
120 | { |
121 | _autoMarkerLineOnLostFocus = v.toBool(); |
122 | } |
123 | |
124 | |
125 | AbstractChatView *BufferWidget::createChatView(BufferId id) |
126 | { |
127 | ChatView *chatView; |
128 | chatView = new ChatView(id, this); |
129 | chatView->setBufferContainer(this); |
130 | _chatViews[id] = chatView; |
131 | ui.stackedWidget->addWidget(chatView); |
132 | chatView->setFocusProxy(this); |
133 | return chatView; |
134 | } |
135 | |
136 | |
137 | void BufferWidget::removeChatView(BufferId id) |
138 | { |
139 | QWidget *view = _chatViews.value(id, 0); |
140 | if (!view) return; |
141 | ui.stackedWidget->removeWidget(view); |
142 | view->deleteLater(); |
143 | _chatViews.take(id); |
144 | } |
145 | |
146 | |
147 | void BufferWidget::showChatView(BufferId id) |
148 | { |
149 | if (!id.isValid()) { |
150 | ui.stackedWidget->setCurrentWidget(ui.page); |
151 | } |
152 | else { |
153 | ChatView *view = qobject_cast<ChatView *>(_chatViews.value(id)); |
154 | Q_ASSERT(view); |
155 | ui.stackedWidget->setCurrentWidget(view); |
156 | _chatViewSearchController->setScene(view->scene()); |
157 | } |
158 | } |
159 | |
160 | |
161 | void BufferWidget::scrollToHighlight(QGraphicsItem *highlightItem) |
162 | { |
163 | ChatView *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
164 | if (view) { |
165 | view->centerOn(highlightItem); |
166 | } |
167 | } |
168 | |
169 | |
170 | void BufferWidget::zoomIn() |
171 | { |
172 | ChatView *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
173 | if (view) |
174 | view->zoomIn(); |
175 | } |
176 | |
177 | |
178 | void BufferWidget::zoomOut() |
179 | { |
180 | ChatView *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
181 | if (view) |
182 | view->zoomOut(); |
183 | } |
184 | |
185 | |
186 | void BufferWidget::zoomOriginal() |
187 | { |
188 | ChatView *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
189 | if (view) |
190 | view->zoomOriginal(); |
191 | } |
192 | |
193 | |
194 | void BufferWidget::(QMenu *, const QPointF &pos) |
195 | { |
196 | Q_UNUSED(pos); |
197 | ActionCollection *coll = QtUi::actionCollection(); |
198 | menu->addSeparator(); |
199 | menu->addAction(coll->action("ZoomInChatView" )); |
200 | menu->addAction(coll->action("ZoomOutChatView" )); |
201 | menu->addAction(coll->action("ZoomOriginalChatView" )); |
202 | } |
203 | |
204 | |
205 | bool BufferWidget::eventFilter(QObject *watched, QEvent *event) |
206 | { |
207 | if (event->type() != QEvent::KeyPress) |
208 | return false; |
209 | |
210 | QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); |
211 | |
212 | MultiLineEdit *inputLine = qobject_cast<MultiLineEdit *>(watched); |
213 | if (!inputLine) |
214 | return false; |
215 | |
216 | // Intercept copy key presses |
217 | if (keyEvent == QKeySequence::Copy) { |
218 | if (inputLine->hasSelectedText()) |
219 | return false; |
220 | ChatView *view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
221 | if (view) |
222 | view->scene()->selectionToClipboard(); |
223 | return true; |
224 | } |
225 | |
226 | // We don't want to steal cursor movement keys if the input line is in multiline mode |
227 | if (!inputLine->isSingleLine()) |
228 | return false; |
229 | |
230 | switch (keyEvent->key()) { |
231 | case Qt::Key_Up: |
232 | case Qt::Key_Down: |
233 | if (!(keyEvent->modifiers() & Qt::ShiftModifier)) |
234 | return false; |
235 | case Qt::Key_PageUp: |
236 | case Qt::Key_PageDown: |
237 | // static cast to access public qobject::event |
238 | return static_cast<QObject *>(ui.stackedWidget->currentWidget())->event(event); |
239 | default: |
240 | return false; |
241 | } |
242 | } |
243 | |
244 | |
245 | void BufferWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
246 | { |
247 | ChatView *prevView = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
248 | |
249 | AbstractBufferContainer::currentChanged(current, previous); // switch first to avoid a redraw |
250 | |
251 | // we need to hide the marker line if it's already/still at the bottom of the view (and not scrolled up) |
252 | ChatView *curView = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
253 | if (curView) { |
254 | BufferId curBufferId = current.data(NetworkModel::BufferIdRole).value<BufferId>(); |
255 | if (curBufferId.isValid()) { |
256 | MsgId markerMsgId = Client::networkModel()->markerLineMsgId(curBufferId); |
257 | if (markerMsgId == curView->lastMsgId() && markerMsgId == curView->lastVisibleMsgId()) |
258 | curView->setMarkerLineVisible(false); |
259 | else |
260 | curView->setMarkerLineVisible(true); |
261 | } |
262 | } |
263 | |
264 | if (prevView && autoMarkerLine()) |
265 | setMarkerLine(prevView, false); |
266 | } |
267 | |
268 | |
269 | void BufferWidget::setMarkerLine(ChatView *view, bool allowGoingBack) |
270 | { |
271 | if (!view) |
272 | view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
273 | if (!view) |
274 | return; |
275 | |
276 | ChatLine *lastLine = view->lastVisibleChatLine(); |
277 | if (lastLine) { |
278 | QModelIndex idx = lastLine->index(); |
279 | MsgId msgId = idx.data(MessageModel::MsgIdRole).value<MsgId>(); |
280 | BufferId bufId = view->scene()->singleBufferId(); |
281 | |
282 | if (!allowGoingBack) { |
283 | MsgId oldMsgId = Client::markerLine(bufId); |
284 | if (oldMsgId.isValid() && msgId <= oldMsgId) |
285 | return; |
286 | } |
287 | Client::setMarkerLine(bufId, msgId); |
288 | } |
289 | } |
290 | |
291 | |
292 | void BufferWidget::jumpToMarkerLine(ChatView *view, bool requestBacklog) |
293 | { |
294 | if (!view) |
295 | view = qobject_cast<ChatView *>(ui.stackedWidget->currentWidget()); |
296 | if (!view) |
297 | return; |
298 | |
299 | view->jumpToMarkerLine(requestBacklog); |
300 | } |
301 | |