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 <QApplication> |
22 | #include <QClipboard> |
23 | #include <QDesktopServices> |
24 | #include <QDrag> |
25 | #include <QGraphicsSceneMouseEvent> |
26 | #include <QMenu> |
27 | #include <QMenuBar> |
28 | #include <QMimeData> |
29 | #include <QPersistentModelIndex> |
30 | #include <QUrl> |
31 | |
32 | #ifdef HAVE_KDE |
33 | # include <KMenuBar> |
34 | #else |
35 | # include <QMenuBar> |
36 | #endif |
37 | |
38 | #ifdef HAVE_WEBKIT |
39 | # include <QWebView> |
40 | #endif |
41 | |
42 | #include "chatitem.h" |
43 | #include "chatline.h" |
44 | #include "chatlinemodelitem.h" |
45 | #include "chatscene.h" |
46 | #include "chatview.h" |
47 | #include "client.h" |
48 | #include "clientbacklogmanager.h" |
49 | #include "columnhandleitem.h" |
50 | #include "contextmenuactionprovider.h" |
51 | #include "iconloader.h" |
52 | #include "mainwin.h" |
53 | #include "markerlineitem.h" |
54 | #include "messagefilter.h" |
55 | #include "qtui.h" |
56 | #include "qtuistyle.h" |
57 | #include "chatviewsettings.h" |
58 | #include "webpreviewitem.h" |
59 | |
60 | const qreal minContentsWidth = 200; |
61 | |
62 | ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, ChatView *parent) |
63 | : QGraphicsScene(0, 0, width, 0, (QObject *)parent), |
64 | _chatView(parent), |
65 | _idString(idString), |
66 | _model(model), |
67 | _singleBufferId(BufferId()), |
68 | _sceneRect(0, 0, width, 0), |
69 | _firstLineRow(-1), |
70 | _viewportHeight(0), |
71 | _markerLine(new MarkerLineItem(width)), |
72 | _markerLineVisible(false), |
73 | _markerLineValid(false), |
74 | _markerLineJumpPending(false), |
75 | _cutoffMode(CutoffRight), |
76 | _selectingItem(0), |
77 | _selectionStart(-1), |
78 | _isSelecting(false), |
79 | _clickMode(NoClick), |
80 | _clickHandled(true), |
81 | _leftButtonPressed(false) |
82 | { |
83 | MessageFilter *filter = qobject_cast<MessageFilter *>(model); |
84 | if (filter && filter->isSingleBufferFilter()) { |
85 | _singleBufferId = filter->singleBufferId(); |
86 | } |
87 | |
88 | addItem(_markerLine); |
89 | connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _markerLine, SLOT(sceneRectChanged(const QRectF &))); |
90 | |
91 | ChatViewSettings defaultSettings; |
92 | _defaultFirstColHandlePos = defaultSettings.value("FirstColumnHandlePos" , 80).toInt(); |
93 | _defaultSecondColHandlePos = defaultSettings.value("SecondColumnHandlePos" , 200).toInt(); |
94 | |
95 | ChatViewSettings viewSettings(this); |
96 | _firstColHandlePos = viewSettings.value("FirstColumnHandlePos" , _defaultFirstColHandlePos).toInt(); |
97 | _secondColHandlePos = viewSettings.value("SecondColumnHandlePos" , _defaultSecondColHandlePos).toInt(); |
98 | |
99 | _firstColHandle = new ColumnHandleItem(QtUi::style()->firstColumnSeparator()); |
100 | addItem(_firstColHandle); |
101 | _firstColHandle->setXPos(_firstColHandlePos); |
102 | connect(_firstColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(firstHandlePositionChanged(qreal))); |
103 | connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _firstColHandle, SLOT(sceneRectChanged(const QRectF &))); |
104 | |
105 | _secondColHandle = new ColumnHandleItem(QtUi::style()->secondColumnSeparator()); |
106 | addItem(_secondColHandle); |
107 | _secondColHandle->setXPos(_secondColHandlePos); |
108 | connect(_secondColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(secondHandlePositionChanged(qreal))); |
109 | |
110 | connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _secondColHandle, SLOT(sceneRectChanged(const QRectF &))); |
111 | |
112 | setHandleXLimits(); |
113 | |
114 | if (model->rowCount() > 0) |
115 | rowsInserted(QModelIndex(), 0, model->rowCount() - 1); |
116 | |
117 | connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), |
118 | this, SLOT(rowsInserted(const QModelIndex &, int, int))); |
119 | connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), |
120 | this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); |
121 | connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), |
122 | this, SLOT(rowsRemoved())); |
123 | connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(dataChanged(QModelIndex, QModelIndex))); |
124 | |
125 | #ifdef HAVE_WEBKIT |
126 | webPreview.timer.setSingleShot(true); |
127 | connect(&webPreview.timer, SIGNAL(timeout()), this, SLOT(webPreviewNextStep())); |
128 | #endif |
129 | _showWebPreview = defaultSettings.showWebPreview(); |
130 | defaultSettings.notify("ShowWebPreview" , this, SLOT(showWebPreviewChanged())); |
131 | |
132 | _clickTimer.setInterval(QApplication::doubleClickInterval()); |
133 | _clickTimer.setSingleShot(true); |
134 | connect(&_clickTimer, SIGNAL(timeout()), SLOT(clickTimeout())); |
135 | |
136 | setItemIndexMethod(QGraphicsScene::NoIndex); |
137 | } |
138 | |
139 | |
140 | ChatScene::~ChatScene() |
141 | { |
142 | } |
143 | |
144 | |
145 | ChatView *ChatScene::chatView() const |
146 | { |
147 | return _chatView; |
148 | } |
149 | |
150 | |
151 | ColumnHandleItem *ChatScene::firstColumnHandle() const |
152 | { |
153 | return _firstColHandle; |
154 | } |
155 | |
156 | |
157 | ColumnHandleItem *ChatScene::secondColumnHandle() const |
158 | { |
159 | return _secondColHandle; |
160 | } |
161 | |
162 | void ChatScene::resetColumnWidths() |
163 | { |
164 | //make sure first column is at least 80 px wide, second 120 px |
165 | int firstColHandlePos = qMax(_defaultFirstColHandlePos, |
166 | 80); |
167 | int secondColHandlePos = qMax(_defaultSecondColHandlePos, |
168 | firstColHandlePos + 120); |
169 | |
170 | _firstColHandle->setXPos(firstColHandlePos); |
171 | _secondColHandle->setXPos(secondColHandlePos); |
172 | } |
173 | |
174 | ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange) const |
175 | { |
176 | if (!_lines.count()) |
177 | return 0; |
178 | |
179 | QList<ChatLine *>::ConstIterator start = _lines.begin(); |
180 | QList<ChatLine *>::ConstIterator end = _lines.end(); |
181 | QList<ChatLine *>::ConstIterator middle; |
182 | |
183 | int n = int(end - start); |
184 | int half; |
185 | |
186 | while (n > 0) { |
187 | half = n >> 1; |
188 | middle = start + half; |
189 | if ((*middle)->msgId() < msgId) { |
190 | start = middle + 1; |
191 | n -= half + 1; |
192 | } |
193 | else { |
194 | n = half; |
195 | } |
196 | } |
197 | |
198 | if (start != end && (*start)->msgId() == msgId && (ignoreDayChange ? (*start)->msgType() != Message::DayChange : true)) |
199 | return *start; |
200 | |
201 | if (matchExact) |
202 | return 0; |
203 | |
204 | if (start == _lines.begin()) // not (yet?) in our scene |
205 | return 0; |
206 | |
207 | // if we didn't find the exact msgId, take the next-lower one (this makes sense for lastSeen) |
208 | |
209 | if (start == end) { // higher than last element |
210 | if (!ignoreDayChange) |
211 | return _lines.last(); |
212 | |
213 | for (int i = _lines.count() -1; i >= 0; i--) { |
214 | if (_lines.at(i)->msgType() != Message::DayChange) |
215 | return _lines.at(i); |
216 | } |
217 | return 0; |
218 | } |
219 | |
220 | // return the next-lower line |
221 | if (!ignoreDayChange) |
222 | return *(--start); |
223 | |
224 | do { |
225 | if ((*(--start))->msgType() != Message::DayChange) |
226 | return *start; |
227 | } |
228 | while (start != _lines.begin()); |
229 | return 0; |
230 | } |
231 | |
232 | |
233 | ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const |
234 | { |
235 | foreach(QGraphicsItem *item, items(scenePos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder)) { |
236 | ChatLine *line = qgraphicsitem_cast<ChatLine *>(item); |
237 | if (line) |
238 | return line->itemAt(line->mapFromScene(scenePos)); |
239 | } |
240 | return 0; |
241 | } |
242 | |
243 | |
244 | bool ChatScene::containsBuffer(const BufferId &id) const |
245 | { |
246 | MessageFilter *filter = qobject_cast<MessageFilter *>(model()); |
247 | if (filter) |
248 | return filter->containsBuffer(id); |
249 | else |
250 | return false; |
251 | } |
252 | |
253 | |
254 | void ChatScene::setMarkerLineVisible(bool visible) |
255 | { |
256 | _markerLineVisible = visible; |
257 | if (visible && _markerLineValid) |
258 | markerLine()->setVisible(true); |
259 | else |
260 | markerLine()->setVisible(false); |
261 | } |
262 | |
263 | |
264 | void ChatScene::setMarkerLine(MsgId msgId) |
265 | { |
266 | if (!isSingleBufferScene()) |
267 | return; |
268 | |
269 | if (!msgId.isValid()) |
270 | msgId = Client::markerLine(singleBufferId()); |
271 | |
272 | if (msgId.isValid()) { |
273 | ChatLine *line = chatLine(msgId, false, true); |
274 | if (line) { |
275 | markerLine()->setChatLine(line); |
276 | // if this was the last line, we won't see it because it's outside the sceneRect |
277 | // .. which is exactly what we want :) |
278 | markerLine()->setPos(line->pos() + QPointF(0, line->height())); |
279 | |
280 | // DayChange messages might have been hidden outside the scene rect, don't make the markerline visible then! |
281 | if (markerLine()->pos().y() >= sceneRect().y()) { |
282 | _markerLineValid = true; |
283 | if (_markerLineVisible) |
284 | markerLine()->setVisible(true); |
285 | if (_markerLineJumpPending) { |
286 | _markerLineJumpPending = false; |
287 | if (markerLine()->isVisible()) { |
288 | markerLine()->ensureVisible(QRectF(), 50, 50); |
289 | } |
290 | } |
291 | return; |
292 | } |
293 | } |
294 | } |
295 | _markerLineValid = false; |
296 | markerLine()->setVisible(false); |
297 | } |
298 | |
299 | |
300 | void ChatScene::jumpToMarkerLine(bool requestBacklog) |
301 | { |
302 | if (!isSingleBufferScene()) |
303 | return; |
304 | |
305 | if (markerLine()->isVisible()) { |
306 | markerLine()->ensureVisible(QRectF(), 50, 50); |
307 | return; |
308 | } |
309 | if (!_markerLineValid && requestBacklog) { |
310 | MsgId msgId = Client::markerLine(singleBufferId()); |
311 | if (msgId.isValid()) { |
312 | _markerLineJumpPending = true; |
313 | Client::backlogManager()->requestBacklog(singleBufferId(), msgId, -1, -1, 0); |
314 | |
315 | // If we filtered out the lastSeenMsg (by changing filters after setting it), we'd never jump because the above request |
316 | // won't fetch any prior lines. Thus, trigger a dynamic backlog request just in case, so repeated |
317 | // jump tries will eventually cause enough backlog to be fetched. |
318 | // This is a bit hackish, but not wasteful, as jumping to the top of the ChatView would trigger a dynamic fetch anyway. |
319 | this->requestBacklog(); |
320 | } |
321 | } |
322 | } |
323 | |
324 | |
325 | void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) |
326 | { |
327 | Q_UNUSED(index); |
328 | |
329 | // QModelIndex sidx = model()->index(start, 2); |
330 | // QModelIndex eidx = model()->index(end, 2); |
331 | // qDebug() << "rowsInserted:"; |
332 | // if(start > 0) { |
333 | // QModelIndex ssidx = model()->index(start - 1, 2); |
334 | // qDebug() << "Start--:" << start - 1 << ssidx.data(MessageModel::MsgIdRole).value<MsgId>() |
335 | // << ssidx.data(Qt::DisplayRole).toString(); |
336 | // } |
337 | // qDebug() << "Start:" << start << sidx.data(MessageModel::MsgIdRole).value<MsgId>() |
338 | // << sidx.data(Qt::DisplayRole).toString(); |
339 | // qDebug() << "End:" << end << eidx.data(MessageModel::MsgIdRole).value<MsgId>() |
340 | // << eidx.data(Qt::DisplayRole).toString(); |
341 | // if(end + 1 < model()->rowCount()) { |
342 | // QModelIndex eeidx = model()->index(end + 1, 2); |
343 | // qDebug() << "End++:" << end + 1 << eeidx.data(MessageModel::MsgIdRole).value<MsgId>() |
344 | // << eeidx.data(Qt::DisplayRole).toString(); |
345 | // } |
346 | |
347 | qreal h = 0; |
348 | qreal y = 0; |
349 | qreal width = _sceneRect.width(); |
350 | bool atBottom = (start == _lines.count()); |
351 | bool atTop = !atBottom && (start == 0); |
352 | |
353 | if (start < _lines.count()) { |
354 | y = _lines.value(start)->y(); |
355 | } |
356 | else if (atBottom && !_lines.isEmpty()) { |
357 | y = _lines.last()->y() + _lines.last()->height(); |
358 | } |
359 | |
360 | qreal contentsWidth = width - secondColumnHandle()->sceneRight(); |
361 | qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight(); |
362 | qreal timestampWidth = firstColumnHandle()->sceneLeft(); |
363 | QPointF contentsPos(secondColumnHandle()->sceneRight(), 0); |
364 | QPointF senderPos(firstColumnHandle()->sceneRight(), 0); |
365 | |
366 | if (atTop) { |
367 | for (int i = end; i >= start; i--) { |
368 | ChatLine *line = new ChatLine(i, model(), |
369 | width, |
370 | timestampWidth, senderWidth, contentsWidth, |
371 | senderPos, contentsPos); |
372 | h += line->height(); |
373 | line->setPos(0, y-h); |
374 | _lines.insert(start, line); |
375 | addItem(line); |
376 | } |
377 | } |
378 | else { |
379 | for (int i = start; i <= end; i++) { |
380 | ChatLine *line = new ChatLine(i, model(), |
381 | width, |
382 | timestampWidth, senderWidth, contentsWidth, |
383 | senderPos, contentsPos); |
384 | line->setPos(0, y+h); |
385 | h += line->height(); |
386 | _lines.insert(i, line); |
387 | addItem(line); |
388 | } |
389 | } |
390 | |
391 | // update existing items |
392 | for (int i = end+1; i < _lines.count(); i++) { |
393 | _lines[i]->setRow(i); |
394 | } |
395 | |
396 | // update selection |
397 | if (_selectionStart >= 0) { |
398 | int offset = end - start + 1; |
399 | int oldStart = _selectionStart; |
400 | if (_selectionStart >= start) |
401 | _selectionStart += offset; |
402 | if (_selectionEnd >= start) { |
403 | _selectionEnd += offset; |
404 | if (_selectionStart == oldStart) |
405 | for (int i = start; i < start + offset; i++) |
406 | _lines[i]->setSelected(true); |
407 | } |
408 | if (_firstSelectionRow >= start) |
409 | _firstSelectionRow += offset; |
410 | } |
411 | |
412 | // neither pre- or append means we have to do dirty work: move items... |
413 | if (!(atTop || atBottom)) { |
414 | ChatLine *line = 0; |
415 | for (int i = 0; i <= end; i++) { |
416 | line = _lines.at(i); |
417 | line->setPos(0, line->pos().y() - h); |
418 | if (line == markerLine()->chatLine()) |
419 | markerLine()->setPos(line->pos() + QPointF(0, line->height())); |
420 | } |
421 | } |
422 | |
423 | // check if all went right |
424 | Q_ASSERT(start == 0 || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y()); |
425 | // if(start != 0) { |
426 | // if(_lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() != _lines.at(start)->pos().y()) { |
427 | // qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end; |
428 | // qDebug() << "line[start - 1]:" << _lines.at(start - 1)->pos().y() << "+" << _lines.at(start - 1)->height() << "=" << _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height(); |
429 | // qDebug() << "line[start]" << _lines.at(start)->pos().y(); |
430 | // qDebug() << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset; |
431 | // Q_ASSERT(false) |
432 | // } |
433 | // } |
434 | Q_ASSERT(end + 1 == _lines.count() || _lines.at(end)->pos().y() + _lines.at(end)->height() == _lines.at(end + 1)->pos().y()); |
435 | // if(end + 1 < _lines.count()) { |
436 | // if(_lines.at(end)->pos().y() + _lines.at(end)->height() != _lines.at(end + 1)->pos().y()) { |
437 | // qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end; |
438 | // qDebug() << "line[end]:" << _lines.at(end)->pos().y() << "+" << _lines.at(end)->height() << "=" << _lines.at(end)->pos().y() + _lines.at(end)->height(); |
439 | // qDebug() << "line[end+1]" << _lines.at(end + 1)->pos().y(); |
440 | // qDebug() << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset; |
441 | // Q_ASSERT(false); |
442 | // } |
443 | // } |
444 | |
445 | if (!atBottom) { |
446 | if (start < _firstLineRow) { |
447 | int prevFirstLineRow = _firstLineRow + (end - start + 1); |
448 | for (int i = end + 1; i < prevFirstLineRow; i++) { |
449 | _lines.at(i)->show(); |
450 | } |
451 | } |
452 | // force new search for first proper line |
453 | _firstLineRow = -1; |
454 | } |
455 | updateSceneRect(); |
456 | if (atBottom) { |
457 | emit lastLineChanged(_lines.last(), h); |
458 | } |
459 | |
460 | // now move the marker line if necessary. we don't need to do anything if we appended lines though... |
461 | if (!_markerLineValid) |
462 | setMarkerLine(); |
463 | } |
464 | |
465 | |
466 | void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
467 | { |
468 | Q_UNUSED(parent); |
469 | |
470 | qreal h = 0; // total height of removed items; |
471 | |
472 | bool atTop = (start == 0); |
473 | bool atBottom = (end == _lines.count() - 1); |
474 | |
475 | // clear selection |
476 | if (_selectingItem) { |
477 | int row = _selectingItem->row(); |
478 | if (row >= start && row <= end) |
479 | setSelectingItem(0); |
480 | } |
481 | |
482 | // remove items from scene |
483 | QList<ChatLine *>::iterator lineIter = _lines.begin() + start; |
484 | int lineCount = start; |
485 | while (lineIter != _lines.end() && lineCount <= end) { |
486 | if ((*lineIter) == markerLine()->chatLine()) |
487 | markerLine()->setChatLine(0); |
488 | h += (*lineIter)->height(); |
489 | delete *lineIter; |
490 | lineIter = _lines.erase(lineIter); |
491 | lineCount++; |
492 | } |
493 | |
494 | // update rows of remaining chatlines |
495 | for (int i = start; i < _lines.count(); i++) { |
496 | _lines.at(i)->setRow(i); |
497 | } |
498 | |
499 | // update selection |
500 | if (_selectionStart >= 0) { |
501 | int offset = end - start + 1; |
502 | if (_selectionStart >= start) |
503 | _selectionStart = qMax(_selectionStart - offset, start); |
504 | if (_selectionEnd >= start) |
505 | _selectionEnd -= offset; |
506 | if (_firstSelectionRow >= start) |
507 | _firstSelectionRow -= offset; |
508 | |
509 | if (_selectionEnd < _selectionStart) { |
510 | _isSelecting = false; |
511 | _selectionStart = -1; |
512 | } |
513 | } |
514 | |
515 | // neither removing at bottom or top means we have to move items... |
516 | if (!(atTop || atBottom)) { |
517 | qreal offset = h; |
518 | int moveStart = 0; |
519 | int moveEnd = _lines.count() - 1; |
520 | if (start < _lines.count() - start) { |
521 | // move top part |
522 | moveEnd = start - 1; |
523 | } |
524 | else { |
525 | // move bottom part |
526 | moveStart = start; |
527 | offset = -offset; |
528 | } |
529 | ChatLine *line = 0; |
530 | for (int i = moveStart; i <= moveEnd; i++) { |
531 | line = _lines.at(i); |
532 | line->setPos(0, line->pos().y() + offset); |
533 | } |
534 | } |
535 | |
536 | Q_ASSERT(start == 0 || start >= _lines.count() || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y()); |
537 | |
538 | // update sceneRect |
539 | // when searching for the first non-date-line we have to take into account that our |
540 | // model still contains the just removed lines so we cannot simply call updateSceneRect() |
541 | int numRows = model()->rowCount(); |
542 | QModelIndex firstLineIdx; |
543 | _firstLineRow = -1; |
544 | bool needOffset = false; |
545 | do { |
546 | _firstLineRow++; |
547 | if (_firstLineRow >= start && _firstLineRow <= end) { |
548 | _firstLineRow = end + 1; |
549 | needOffset = true; |
550 | } |
551 | firstLineIdx = model()->index(_firstLineRow, 0); |
552 | } |
553 | while ((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) == Message::DayChange && _firstLineRow < numRows); |
554 | |
555 | if (needOffset) |
556 | _firstLineRow -= end - start + 1; |
557 | updateSceneRect(); |
558 | } |
559 | |
560 | |
561 | void ChatScene::rowsRemoved() |
562 | { |
563 | // move the marker line if necessary |
564 | setMarkerLine(); |
565 | } |
566 | |
567 | |
568 | void ChatScene::dataChanged(const QModelIndex &tl, const QModelIndex &br) |
569 | { |
570 | layout(tl.row(), br.row(), _sceneRect.width()); |
571 | } |
572 | |
573 | |
574 | void ChatScene::updateForViewport(qreal width, qreal height) |
575 | { |
576 | _viewportHeight = height; |
577 | setWidth(width); |
578 | } |
579 | |
580 | |
581 | void ChatScene::setWidth(qreal width) |
582 | { |
583 | if (width == _sceneRect.width()) |
584 | return; |
585 | layout(0, _lines.count()-1, width); |
586 | } |
587 | |
588 | |
589 | void ChatScene::layout(int start, int end, qreal width) |
590 | { |
591 | // clock_t startT = clock(); |
592 | |
593 | // disabling the index while doing this complex updates is about |
594 | // 2 to 10 times faster! |
595 | //setItemIndexMethod(QGraphicsScene::NoIndex); |
596 | |
597 | if (end >= 0) { |
598 | int row = end; |
599 | qreal linePos = _lines.at(row)->scenePos().y() + _lines.at(row)->height(); |
600 | qreal contentsWidth = width - secondColumnHandle()->sceneRight(); |
601 | while (row >= start) { |
602 | _lines.at(row--)->setGeometryByWidth(width, contentsWidth, linePos); |
603 | } |
604 | |
605 | if (row >= 0) { |
606 | // remaining items don't need geometry changes, but maybe repositioning? |
607 | ChatLine *line = _lines.at(row); |
608 | qreal offset = linePos - (line->scenePos().y() + line->height()); |
609 | if (offset != 0) { |
610 | while (row >= 0) { |
611 | line = _lines.at(row--); |
612 | line->setPos(0, line->scenePos().y() + offset); |
613 | } |
614 | } |
615 | } |
616 | } |
617 | |
618 | //setItemIndexMethod(QGraphicsScene::BspTreeIndex); |
619 | |
620 | updateSceneRect(width); |
621 | setHandleXLimits(); |
622 | setMarkerLine(); |
623 | emit layoutChanged(); |
624 | |
625 | // clock_t endT = clock(); |
626 | // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec"; |
627 | } |
628 | |
629 | |
630 | void ChatScene::firstHandlePositionChanged(qreal xpos) |
631 | { |
632 | if (_firstColHandlePos == xpos) |
633 | return; |
634 | |
635 | _firstColHandlePos = xpos >= 0 ? xpos : 0; |
636 | ChatViewSettings viewSettings(this); |
637 | viewSettings.setValue("FirstColumnHandlePos" , _firstColHandlePos); |
638 | ChatViewSettings defaultSettings; |
639 | defaultSettings.setValue("FirstColumnHandlePos" , _firstColHandlePos); |
640 | |
641 | // clock_t startT = clock(); |
642 | |
643 | // disabling the index while doing this complex updates is about |
644 | // 2 to 10 times faster! |
645 | //setItemIndexMethod(QGraphicsScene::NoIndex); |
646 | |
647 | QList<ChatLine *>::iterator lineIter = _lines.end(); |
648 | QList<ChatLine *>::iterator lineIterBegin = _lines.begin(); |
649 | qreal timestampWidth = firstColumnHandle()->sceneLeft(); |
650 | qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight(); |
651 | QPointF senderPos(firstColumnHandle()->sceneRight(), 0); |
652 | |
653 | while (lineIter != lineIterBegin) { |
654 | lineIter--; |
655 | (*lineIter)->setFirstColumn(timestampWidth, senderWidth, senderPos); |
656 | } |
657 | //setItemIndexMethod(QGraphicsScene::BspTreeIndex); |
658 | |
659 | setHandleXLimits(); |
660 | |
661 | // clock_t endT = clock(); |
662 | // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec"; |
663 | } |
664 | |
665 | |
666 | void ChatScene::secondHandlePositionChanged(qreal xpos) |
667 | { |
668 | if (_secondColHandlePos == xpos) |
669 | return; |
670 | |
671 | _secondColHandlePos = xpos; |
672 | ChatViewSettings viewSettings(this); |
673 | viewSettings.setValue("SecondColumnHandlePos" , _secondColHandlePos); |
674 | ChatViewSettings defaultSettings; |
675 | defaultSettings.setValue("SecondColumnHandlePos" , _secondColHandlePos); |
676 | |
677 | // clock_t startT = clock(); |
678 | |
679 | // disabling the index while doing this complex updates is about |
680 | // 2 to 10 times faster! |
681 | //setItemIndexMethod(QGraphicsScene::NoIndex); |
682 | |
683 | QList<ChatLine *>::iterator lineIter = _lines.end(); |
684 | QList<ChatLine *>::iterator lineIterBegin = _lines.begin(); |
685 | qreal linePos = _sceneRect.y() + _sceneRect.height(); |
686 | qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight(); |
687 | qreal contentsWidth = _sceneRect.width() - secondColumnHandle()->sceneRight(); |
688 | QPointF contentsPos(secondColumnHandle()->sceneRight(), 0); |
689 | while (lineIter != lineIterBegin) { |
690 | lineIter--; |
691 | (*lineIter)->setSecondColumn(senderWidth, contentsWidth, contentsPos, linePos); |
692 | } |
693 | //setItemIndexMethod(QGraphicsScene::BspTreeIndex); |
694 | |
695 | updateSceneRect(); |
696 | setHandleXLimits(); |
697 | emit layoutChanged(); |
698 | |
699 | // clock_t endT = clock(); |
700 | // qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec"; |
701 | } |
702 | |
703 | |
704 | void ChatScene::setHandleXLimits() |
705 | { |
706 | _firstColHandle->setXLimits(0, _secondColHandle->sceneLeft()); |
707 | _secondColHandle->setXLimits(_firstColHandle->sceneRight(), width() - minContentsWidth); |
708 | update(); |
709 | } |
710 | |
711 | |
712 | void ChatScene::setSelectingItem(ChatItem *item) |
713 | { |
714 | if (_selectingItem) _selectingItem->clearSelection(); |
715 | _selectingItem = item; |
716 | } |
717 | |
718 | |
719 | void ChatScene::startGlobalSelection(ChatItem *item, const QPointF &itemPos) |
720 | { |
721 | _selectionStart = _selectionEnd = _firstSelectionRow = item->row(); |
722 | _selectionStartCol = _selectionMinCol = item->column(); |
723 | _isSelecting = true; |
724 | _lines[_selectionStart]->setSelected(true, (ChatLineModel::ColumnType)_selectionMinCol); |
725 | updateSelection(item->mapToScene(itemPos)); |
726 | } |
727 | |
728 | |
729 | void ChatScene::updateSelection(const QPointF &pos) |
730 | { |
731 | int curRow = rowByScenePos(pos); |
732 | if (curRow < 0) return; |
733 | int curColumn = (int)columnByScenePos(pos); |
734 | ChatLineModel::ColumnType minColumn = (ChatLineModel::ColumnType)qMin(curColumn, _selectionStartCol); |
735 | if (minColumn != _selectionMinCol) { |
736 | _selectionMinCol = minColumn; |
737 | for (int l = qMin(_selectionStart, _selectionEnd); l <= qMax(_selectionStart, _selectionEnd); l++) { |
738 | _lines[l]->setSelected(true, minColumn); |
739 | } |
740 | } |
741 | int newstart = qMin(curRow, _firstSelectionRow); |
742 | int newend = qMax(curRow, _firstSelectionRow); |
743 | if (newstart < _selectionStart) { |
744 | for (int l = newstart; l < _selectionStart; l++) |
745 | _lines[l]->setSelected(true, minColumn); |
746 | } |
747 | if (newstart > _selectionStart) { |
748 | for (int l = _selectionStart; l < newstart; l++) |
749 | _lines[l]->setSelected(false); |
750 | } |
751 | if (newend > _selectionEnd) { |
752 | for (int l = _selectionEnd+1; l <= newend; l++) |
753 | _lines[l]->setSelected(true, minColumn); |
754 | } |
755 | if (newend < _selectionEnd) { |
756 | for (int l = newend+1; l <= _selectionEnd; l++) |
757 | _lines[l]->setSelected(false); |
758 | } |
759 | |
760 | _selectionStart = newstart; |
761 | _selectionEnd = newend; |
762 | |
763 | if (newstart == newend && minColumn == ChatLineModel::ContentsColumn) { |
764 | if (!_selectingItem) { |
765 | // _selectingItem has been removed already |
766 | return; |
767 | } |
768 | _lines[curRow]->setSelected(false); |
769 | _isSelecting = false; |
770 | _selectionStart = -1; |
771 | _selectingItem->continueSelecting(_selectingItem->mapFromScene(pos)); |
772 | } |
773 | } |
774 | |
775 | |
776 | bool ChatScene::isPosOverSelection(const QPointF &pos) const |
777 | { |
778 | ChatItem *chatItem = chatItemAt(pos); |
779 | if (!chatItem) |
780 | return false; |
781 | if (hasGlobalSelection()) { |
782 | int row = chatItem->row(); |
783 | if (row >= qMin(_selectionStart, _selectionEnd) && row <= qMax(_selectionStart, _selectionEnd)) |
784 | return columnByScenePos(pos) >= _selectionMinCol; |
785 | } |
786 | else { |
787 | return chatItem->isPosOverSelection(chatItem->mapFromScene(pos)); |
788 | } |
789 | return false; |
790 | } |
791 | |
792 | |
793 | bool ChatScene::isScrollingAllowed() const |
794 | { |
795 | if (_isSelecting) |
796 | return false; |
797 | |
798 | // TODO: Handle clicks and single-item selections too |
799 | |
800 | return true; |
801 | } |
802 | |
803 | |
804 | /******** MOUSE HANDLING **************************************************************************/ |
805 | |
806 | void ChatScene::(QGraphicsSceneContextMenuEvent *event) |
807 | { |
808 | QPointF pos = event->scenePos(); |
809 | QMenu ; |
810 | |
811 | // zoom actions and similar |
812 | chatView()->addActionsToMenu(&menu, pos); |
813 | menu.addSeparator(); |
814 | |
815 | // item-specific options (select link etc) |
816 | ChatItem *item = chatItemAt(pos); |
817 | if (item) |
818 | item->addActionsToMenu(&menu, item->mapFromScene(pos)); |
819 | else |
820 | // no item -> default scene actions |
821 | GraphicalUi::contextMenuActionProvider()->addActions(&menu, filter(), BufferId()); |
822 | |
823 | // If we have text selected, insert the Copy Selection as first item |
824 | if (isPosOverSelection(pos)) { |
825 | QAction *sep = menu.insertSeparator(menu.actions().first()); |
826 | QAction *act = new Action(SmallIcon("edit-copy" ), tr("Copy Selection" ), &menu, this, |
827 | SLOT(selectionToClipboard()), QKeySequence::Copy); |
828 | menu.insertAction(sep, act); |
829 | |
830 | QString searchSelectionText = selection(); |
831 | if (searchSelectionText.length() > _webSearchSelectionTextMaxVisible) |
832 | searchSelectionText = searchSelectionText.left(_webSearchSelectionTextMaxVisible).append(QString::fromUtf8("…" )); |
833 | searchSelectionText = tr("Search '%1'" ).arg(searchSelectionText); |
834 | |
835 | menu.addAction(SmallIcon("edit-find" ), searchSelectionText, this, SLOT(webSearchOnSelection())); |
836 | } |
837 | |
838 | if (QtUi::mainWindow()->menuBar()->isHidden()) |
839 | menu.addAction(QtUi::actionCollection("General" )->action("ToggleMenuBar" )); |
840 | |
841 | // show column reset action if columns have been resized in this session or there is at least one very narrow column |
842 | if ((_firstColHandlePos != _defaultFirstColHandlePos) || (_secondColHandlePos != _defaultSecondColHandlePos) || |
843 | (_firstColHandlePos <= 10) || (_secondColHandlePos - _firstColHandlePos <= 10)) |
844 | menu.addAction(new Action(tr("Reset Column Widths" ), &menu, this, SLOT(resetColumnWidths()), 0)); |
845 | |
846 | menu.exec(event->screenPos()); |
847 | } |
848 | |
849 | |
850 | void ChatScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) |
851 | { |
852 | if (event->buttons() == Qt::LeftButton) { |
853 | if (!_clickHandled && (event->scenePos() - _clickPos).toPoint().manhattanLength() >= QApplication::startDragDistance()) { |
854 | if (_clickTimer.isActive()) |
855 | _clickTimer.stop(); |
856 | if (_clickMode == SingleClick && isPosOverSelection(_clickPos)) |
857 | initiateDrag(event->widget()); |
858 | else { |
859 | _clickMode = DragStartClick; |
860 | handleClick(Qt::LeftButton, _clickPos); |
861 | } |
862 | _clickMode = NoClick; |
863 | } |
864 | if (_isSelecting) { |
865 | updateSelection(event->scenePos()); |
866 | emit mouseMoveWhileSelecting(event->scenePos()); |
867 | event->accept(); |
868 | } |
869 | else if (_clickHandled && _clickMode < DoubleClick) |
870 | QGraphicsScene::mouseMoveEvent(event); |
871 | } |
872 | else |
873 | QGraphicsScene::mouseMoveEvent(event); |
874 | } |
875 | |
876 | |
877 | void ChatScene::mousePressEvent(QGraphicsSceneMouseEvent *event) |
878 | { |
879 | if (event->buttons() == Qt::LeftButton) { |
880 | _leftButtonPressed = true; |
881 | _clickHandled = false; |
882 | if (!isPosOverSelection(event->scenePos())) { |
883 | // immediately clear selection if clicked outside; otherwise, wait for potential drag |
884 | clearSelection(); |
885 | } |
886 | if (_clickMode != NoClick && _clickTimer.isActive()) { |
887 | switch (_clickMode) { |
888 | case NoClick: |
889 | _clickMode = SingleClick; break; |
890 | case SingleClick: |
891 | _clickMode = DoubleClick; break; |
892 | case DoubleClick: |
893 | _clickMode = TripleClick; break; |
894 | case TripleClick: |
895 | _clickMode = DoubleClick; break; |
896 | case DragStartClick: |
897 | break; |
898 | } |
899 | handleClick(Qt::LeftButton, _clickPos); |
900 | } |
901 | else { |
902 | _clickMode = SingleClick; |
903 | _clickPos = event->scenePos(); |
904 | } |
905 | _clickTimer.start(); |
906 | } |
907 | if (event->type() == QEvent::GraphicsSceneMouseDoubleClick) |
908 | QGraphicsScene::mouseDoubleClickEvent(event); |
909 | else |
910 | QGraphicsScene::mousePressEvent(event); |
911 | } |
912 | |
913 | |
914 | void ChatScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) |
915 | { |
916 | // we check for doubleclick ourselves, so just call press handler |
917 | mousePressEvent(event); |
918 | } |
919 | |
920 | |
921 | void ChatScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
922 | { |
923 | if (event->button() == Qt::LeftButton && _leftButtonPressed) { |
924 | _leftButtonPressed = false; |
925 | if (_clickMode != NoClick) { |
926 | if (_clickMode == SingleClick) |
927 | clearSelection(); |
928 | event->accept(); |
929 | if (!_clickTimer.isActive()) |
930 | handleClick(Qt::LeftButton, _clickPos); |
931 | } |
932 | else { |
933 | // no click -> drag or selection move |
934 | if (isGloballySelecting()) { |
935 | selectionToClipboard(QClipboard::Selection); |
936 | _isSelecting = false; |
937 | event->accept(); |
938 | return; |
939 | } |
940 | } |
941 | } |
942 | QGraphicsScene::mouseReleaseEvent(event); |
943 | } |
944 | |
945 | |
946 | void ChatScene::clickTimeout() |
947 | { |
948 | if (!_leftButtonPressed && _clickMode == SingleClick) |
949 | handleClick(Qt::LeftButton, _clickPos); |
950 | } |
951 | |
952 | |
953 | void ChatScene::handleClick(Qt::MouseButton button, const QPointF &scenePos) |
954 | { |
955 | if (button == Qt::LeftButton) { |
956 | clearSelection(); |
957 | |
958 | // Now send click down to items |
959 | ChatItem *chatItem = chatItemAt(scenePos); |
960 | if (chatItem) { |
961 | chatItem->handleClick(chatItem->mapFromScene(scenePos), _clickMode); |
962 | } |
963 | _clickHandled = true; |
964 | } |
965 | } |
966 | |
967 | |
968 | void ChatScene::initiateDrag(QWidget *source) |
969 | { |
970 | QDrag *drag = new QDrag(source); |
971 | QMimeData *mimeData = new QMimeData; |
972 | mimeData->setText(selection()); |
973 | drag->setMimeData(mimeData); |
974 | |
975 | drag->exec(Qt::CopyAction); |
976 | } |
977 | |
978 | |
979 | /******** SELECTIONS ******************************************************************************/ |
980 | |
981 | void ChatScene::selectionToClipboard(QClipboard::Mode mode) |
982 | { |
983 | if (!hasSelection()) |
984 | return; |
985 | |
986 | stringToClipboard(selection(), mode); |
987 | } |
988 | |
989 | |
990 | void ChatScene::stringToClipboard(const QString &str_, QClipboard::Mode mode) |
991 | { |
992 | QString str = str_; |
993 | // remove trailing linefeeds |
994 | if (str.endsWith('\n')) |
995 | str.chop(1); |
996 | |
997 | switch (mode) { |
998 | case QClipboard::Clipboard: |
999 | QApplication::clipboard()->setText(str); |
1000 | break; |
1001 | case QClipboard::Selection: |
1002 | if (QApplication::clipboard()->supportsSelection()) |
1003 | QApplication::clipboard()->setText(str, QClipboard::Selection); |
1004 | break; |
1005 | default: |
1006 | break; |
1007 | }; |
1008 | } |
1009 | |
1010 | |
1011 | //!\brief Convert current selection to human-readable string. |
1012 | QString ChatScene::selection() const |
1013 | { |
1014 | //TODO Make selection format configurable! |
1015 | if (hasGlobalSelection()) { |
1016 | int start = qMin(_selectionStart, _selectionEnd); |
1017 | int end = qMax(_selectionStart, _selectionEnd); |
1018 | if (start < 0 || end >= _lines.count()) { |
1019 | qDebug() << "Invalid selection range:" << start << end; |
1020 | return QString(); |
1021 | } |
1022 | QString result; |
1023 | for (int l = start; l <= end; l++) { |
1024 | if (_selectionMinCol == ChatLineModel::TimestampColumn) |
1025 | result += _lines[l]->item(ChatLineModel::TimestampColumn)->data(MessageModel::DisplayRole).toString() + " " ; |
1026 | if (_selectionMinCol <= ChatLineModel::SenderColumn) |
1027 | result += _lines[l]->item(ChatLineModel::SenderColumn)->data(MessageModel::DisplayRole).toString() + " " ; |
1028 | result += _lines[l]->item(ChatLineModel::ContentsColumn)->data(MessageModel::DisplayRole).toString() + "\n" ; |
1029 | } |
1030 | return result; |
1031 | } |
1032 | else if (selectingItem()) |
1033 | return selectingItem()->selection(); |
1034 | return QString(); |
1035 | } |
1036 | |
1037 | |
1038 | bool ChatScene::hasSelection() const |
1039 | { |
1040 | return hasGlobalSelection() || (selectingItem() && selectingItem()->hasSelection()); |
1041 | } |
1042 | |
1043 | |
1044 | bool ChatScene::hasGlobalSelection() const |
1045 | { |
1046 | return _selectionStart >= 0; |
1047 | } |
1048 | |
1049 | |
1050 | bool ChatScene::isGloballySelecting() const |
1051 | { |
1052 | return _isSelecting; |
1053 | } |
1054 | |
1055 | |
1056 | void ChatScene::clearGlobalSelection() |
1057 | { |
1058 | if (hasGlobalSelection()) { |
1059 | for (int l = qMin(_selectionStart, _selectionEnd); l <= qMax(_selectionStart, _selectionEnd); l++) |
1060 | _lines[l]->setSelected(false); |
1061 | _isSelecting = false; |
1062 | _selectionStart = -1; |
1063 | } |
1064 | } |
1065 | |
1066 | |
1067 | void ChatScene::clearSelection() |
1068 | { |
1069 | clearGlobalSelection(); |
1070 | if (selectingItem()) |
1071 | selectingItem()->clearSelection(); |
1072 | } |
1073 | |
1074 | |
1075 | /******** *************************************************************************************/ |
1076 | |
1077 | void ChatScene::webSearchOnSelection() |
1078 | { |
1079 | if (!hasSelection()) |
1080 | return; |
1081 | |
1082 | ChatViewSettings settings; |
1083 | QString webSearchBaseUrl = settings.webSearchUrlFormatString(); |
1084 | QString webSearchUrl = webSearchBaseUrl.replace(QString("%s" ), selection()); |
1085 | QUrl url = QUrl::fromUserInput(webSearchUrl); |
1086 | QDesktopServices::openUrl(url); |
1087 | } |
1088 | |
1089 | |
1090 | /******** *************************************************************************************/ |
1091 | |
1092 | void ChatScene::requestBacklog() |
1093 | { |
1094 | MessageFilter *filter = qobject_cast<MessageFilter *>(model()); |
1095 | if (filter) |
1096 | return filter->requestBacklog(); |
1097 | return; |
1098 | } |
1099 | |
1100 | |
1101 | ChatLineModel::ColumnType ChatScene::columnByScenePos(qreal x) const |
1102 | { |
1103 | if (x < _firstColHandle->x()) |
1104 | return ChatLineModel::TimestampColumn; |
1105 | if (x < _secondColHandle->x()) |
1106 | return ChatLineModel::SenderColumn; |
1107 | |
1108 | return ChatLineModel::ContentsColumn; |
1109 | } |
1110 | |
1111 | |
1112 | int ChatScene::rowByScenePos(qreal y) const |
1113 | { |
1114 | QList<QGraphicsItem *> itemList = items(QPointF(0, y)); |
1115 | |
1116 | // ChatLine should be at the bottom of the list |
1117 | for (int i = itemList.count()-1; i >= 0; i--) { |
1118 | ChatLine *line = qgraphicsitem_cast<ChatLine *>(itemList.at(i)); |
1119 | if (line) |
1120 | return line->row(); |
1121 | } |
1122 | return -1; |
1123 | } |
1124 | |
1125 | |
1126 | void ChatScene::updateSceneRect(qreal width) |
1127 | { |
1128 | if (_lines.isEmpty()) { |
1129 | updateSceneRect(QRectF(0, 0, width, 0)); |
1130 | return; |
1131 | } |
1132 | |
1133 | // we hide day change messages at the top by making the scene rect smaller |
1134 | // and by calling QGraphicsItem::hide() on all leading day change messages |
1135 | // the first one is needed to ensure proper scrollbar ranges |
1136 | // the second for cases where the viewport is larger then the set scenerect |
1137 | // (in this case the items are shown anyways) |
1138 | if (_firstLineRow == -1) { |
1139 | int numRows = model()->rowCount(); |
1140 | _firstLineRow = 0; |
1141 | QModelIndex firstLineIdx; |
1142 | while (_firstLineRow < numRows) { |
1143 | firstLineIdx = model()->index(_firstLineRow, 0); |
1144 | if ((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) != Message::DayChange) |
1145 | break; |
1146 | _lines.at(_firstLineRow)->hide(); |
1147 | _firstLineRow++; |
1148 | } |
1149 | } |
1150 | |
1151 | // the following call should be safe. If it crashes something went wrong during insert/remove |
1152 | if (_firstLineRow < _lines.count()) { |
1153 | ChatLine *firstLine = _lines.at(_firstLineRow); |
1154 | ChatLine *lastLine = _lines.last(); |
1155 | updateSceneRect(QRectF(0, firstLine->pos().y(), width, lastLine->pos().y() + lastLine->height() - firstLine->pos().y())); |
1156 | } |
1157 | else { |
1158 | // empty scene rect |
1159 | updateSceneRect(QRectF(0, 0, width, 0)); |
1160 | } |
1161 | } |
1162 | |
1163 | |
1164 | void ChatScene::updateSceneRect(const QRectF &rect) |
1165 | { |
1166 | _sceneRect = rect; |
1167 | setSceneRect(rect); |
1168 | update(); |
1169 | } |
1170 | |
1171 | |
1172 | // ======================================== |
1173 | // Webkit Only stuff |
1174 | // ======================================== |
1175 | #ifdef HAVE_WEBKIT |
1176 | void ChatScene::loadWebPreview(ChatItem *parentItem, const QUrl &url, const QRectF &urlRect) |
1177 | { |
1178 | if (!_showWebPreview) |
1179 | return; |
1180 | |
1181 | if (webPreview.urlRect != urlRect) |
1182 | webPreview.urlRect = urlRect; |
1183 | |
1184 | if (webPreview.parentItem != parentItem) |
1185 | webPreview.parentItem = parentItem; |
1186 | |
1187 | if (webPreview.url != url) { |
1188 | webPreview.url = url; |
1189 | // prepare to load a different URL |
1190 | if (webPreview.previewItem) { |
1191 | if (webPreview.previewItem->scene()) |
1192 | removeItem(webPreview.previewItem); |
1193 | delete webPreview.previewItem; |
1194 | webPreview.previewItem = 0; |
1195 | } |
1196 | webPreview.previewState = WebPreview::NoPreview; |
1197 | } |
1198 | |
1199 | if (webPreview.url.isEmpty()) |
1200 | return; |
1201 | |
1202 | // qDebug() << Q_FUNC_INFO << webPreview.previewState; |
1203 | switch (webPreview.previewState) { |
1204 | case WebPreview::NoPreview: |
1205 | webPreview.previewState = WebPreview::NewPreview; |
1206 | webPreview.timer.start(500); |
1207 | break; |
1208 | case WebPreview::NewPreview: |
1209 | case WebPreview::DelayPreview: |
1210 | case WebPreview::ShowPreview: |
1211 | // we're already waiting for the next step or showing the preview |
1212 | break; |
1213 | case WebPreview::HidePreview: |
1214 | // we still have a valid preview |
1215 | webPreview.previewState = WebPreview::DelayPreview; |
1216 | webPreview.timer.start(1000); |
1217 | break; |
1218 | } |
1219 | // qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive(); |
1220 | } |
1221 | |
1222 | |
1223 | void ChatScene::webPreviewNextStep() |
1224 | { |
1225 | // qDebug() << Q_FUNC_INFO << webPreview.previewState; |
1226 | switch (webPreview.previewState) { |
1227 | case WebPreview::NoPreview: |
1228 | break; |
1229 | case WebPreview::NewPreview: |
1230 | Q_ASSERT(!webPreview.previewItem); |
1231 | webPreview.previewItem = new WebPreviewItem(webPreview.url); |
1232 | webPreview.previewState = WebPreview::DelayPreview; |
1233 | webPreview.timer.start(1000); |
1234 | break; |
1235 | case WebPreview::DelayPreview: |
1236 | Q_ASSERT(webPreview.previewItem); |
1237 | // calc position and show |
1238 | { |
1239 | qreal previewY = webPreview.urlRect.bottom(); |
1240 | qreal previewX = webPreview.urlRect.x(); |
1241 | if (previewY + webPreview.previewItem->boundingRect().height() > sceneRect().bottom()) |
1242 | previewY = webPreview.urlRect.y() - webPreview.previewItem->boundingRect().height(); |
1243 | |
1244 | if (previewX + webPreview.previewItem->boundingRect().width() > sceneRect().width()) |
1245 | previewX = sceneRect().right() - webPreview.previewItem->boundingRect().width(); |
1246 | |
1247 | webPreview.previewItem->setPos(previewX, previewY); |
1248 | } |
1249 | addItem(webPreview.previewItem); |
1250 | webPreview.previewState = WebPreview::ShowPreview; |
1251 | break; |
1252 | case WebPreview::ShowPreview: |
1253 | qWarning() << "ChatScene::webPreviewNextStep() called while in ShowPreview Step!" ; |
1254 | qWarning() << "removing preview" ; |
1255 | if (webPreview.previewItem && webPreview.previewItem->scene()) |
1256 | removeItem(webPreview.previewItem); |
1257 | // Fall through to deletion! |
1258 | case WebPreview::HidePreview: |
1259 | if (webPreview.previewItem) { |
1260 | delete webPreview.previewItem; |
1261 | webPreview.previewItem = 0; |
1262 | } |
1263 | webPreview.parentItem = 0; |
1264 | webPreview.url = QUrl(); |
1265 | webPreview.urlRect = QRectF(); |
1266 | webPreview.previewState = WebPreview::NoPreview; |
1267 | } |
1268 | // qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive(); |
1269 | } |
1270 | |
1271 | |
1272 | void ChatScene::clearWebPreview(ChatItem *parentItem) |
1273 | { |
1274 | // qDebug() << Q_FUNC_INFO << webPreview.previewState; |
1275 | switch (webPreview.previewState) { |
1276 | case WebPreview::NewPreview: |
1277 | webPreview.previewState = WebPreview::NoPreview; // we haven't loaded anything yet |
1278 | break; |
1279 | case WebPreview::ShowPreview: |
1280 | if (parentItem == 0 || webPreview.parentItem == parentItem) { |
1281 | if (webPreview.previewItem && webPreview.previewItem->scene()) |
1282 | removeItem(webPreview.previewItem); |
1283 | } |
1284 | // fall through into to set hidden state |
1285 | case WebPreview::DelayPreview: |
1286 | // we're just loading, so haven't shown the preview yet. |
1287 | webPreview.previewState = WebPreview::HidePreview; |
1288 | webPreview.timer.start(5000); |
1289 | break; |
1290 | case WebPreview::NoPreview: |
1291 | case WebPreview::HidePreview: |
1292 | break; |
1293 | } |
1294 | // qDebug() << " new State:" << webPreview.previewState << webPreview.timer.isActive(); |
1295 | } |
1296 | |
1297 | |
1298 | #endif |
1299 | |
1300 | // ======================================== |
1301 | // end of webkit only |
1302 | // ======================================== |
1303 | |
1304 | void ChatScene::showWebPreviewChanged() |
1305 | { |
1306 | ChatViewSettings settings; |
1307 | _showWebPreview = settings.showWebPreview(); |
1308 | } |
1309 | |