de986cdaccd3876b422667dbb9ebb287c2ed3f71
[quassel.git] / src / qtui / chatscene.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include <QApplication>
22 #include <QClipboard>
23 #include <QGraphicsSceneMouseEvent>
24 #include <QPersistentModelIndex>
25 #include <QWebView>
26
27 #include "chatitem.h"
28 #include "chatline.h"
29 #include "chatlinemodelitem.h"
30 #include "chatscene.h"
31 #include "client.h"
32 #include "clientbacklogmanager.h"
33 #include "columnhandleitem.h"
34 #include "messagefilter.h"
35 #include "qtui.h"
36 #include "qtuistyle.h"
37 #include "chatviewsettings.h"
38 #include "webpreviewitem.h"
39
40 const qreal minContentsWidth = 200;
41
42 ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal width, QObject *parent)
43   : QGraphicsScene(0, 0, width, 0, parent),
44     _idString(idString),
45     _model(model),
46     _singleBufferScene(false),
47     _sceneRect(0, 0, width, 0),
48     _firstLineRow(-1),
49     _viewportHeight(0),
50     _cutoffMode(CutoffRight),
51     _selectingItem(0),
52     _selectionStart(-1),
53     _isSelecting(false)
54 {
55   MessageFilter *filter = qobject_cast<MessageFilter*>(model);
56   if(filter) {
57     _singleBufferScene = filter->isSingleBufferFilter();
58   }
59
60   ChatViewSettings defaultSettings;
61   int defaultFirstColHandlePos = defaultSettings.value("FirstColumnHandlePos", 80).toInt();
62   int defaultSecondColHandlePos = defaultSettings.value("SecondColumnHandlePos", 200).toInt();
63
64   ChatViewSettings viewSettings(this);
65   _firstColHandlePos = viewSettings.value("FirstColumnHandlePos", defaultFirstColHandlePos).toInt();
66   _secondColHandlePos = viewSettings.value("SecondColumnHandlePos", defaultSecondColHandlePos).toInt();
67
68   _firstColHandle = new ColumnHandleItem(QtUi::style()->firstColumnSeparator());
69   addItem(_firstColHandle);
70   _firstColHandle->setXPos(_firstColHandlePos);
71   connect(_firstColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(firstHandlePositionChanged(qreal)));
72   connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _firstColHandle, SLOT(sceneRectChanged(const QRectF &)));
73
74   _secondColHandle = new ColumnHandleItem(QtUi::style()->secondColumnSeparator());
75   addItem(_secondColHandle);
76   _secondColHandle->setXPos(_secondColHandlePos);
77   connect(_secondColHandle, SIGNAL(positionChanged(qreal)), this, SLOT(secondHandlePositionChanged(qreal)));
78
79   connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _secondColHandle, SLOT(sceneRectChanged(const QRectF &)));
80
81   setHandleXLimits();
82
83   connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
84           this, SLOT(rowsInserted(const QModelIndex &, int, int)));
85   connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
86           this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
87
88   if(model->rowCount() > 0)
89     rowsInserted(QModelIndex(), 0, model->rowCount() - 1);
90
91 #ifdef HAVE_WEBKIT
92   webPreview.delayTimer.setSingleShot(true);
93   connect(&webPreview.delayTimer, SIGNAL(timeout()), this, SLOT(showWebPreviewEvent()));
94   webPreview.deleteTimer.setInterval(600000);
95   connect(&webPreview.deleteTimer, SIGNAL(timeout()), this, SLOT(deleteWebPreviewEvent()));
96 #endif
97
98   setItemIndexMethod(QGraphicsScene::NoIndex);
99 }
100
101 ChatScene::~ChatScene() {
102 }
103
104 void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
105   Q_UNUSED(index);
106 //   QModelIndex sidx = model()->index(start, 0);
107 //   QModelIndex eidx = model()->index(end, 0);
108 //   qDebug() << "rowsInserted" << start << end << "-" << sidx.data(MessageModel::MsgIdRole).value<MsgId>() << eidx.data(MessageModel::MsgIdRole).value<MsgId>();
109
110   qreal h = 0;
111   qreal y = _sceneRect.y();
112   qreal width = _sceneRect.width();
113   bool atTop = true;
114   bool atBottom = false;
115   bool moveTop = false;
116
117   if(start > 0 && start < _lines.count()) {
118     y = _lines.value(start)->y();
119     atTop = false;
120   }
121   if(start == _lines.count()) {
122     y = _sceneRect.bottom();
123     atTop = false;
124     atBottom = true;
125   }
126
127   qreal contentsWidth = width - secondColumnHandle()->sceneRight();
128   qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
129   qreal timestampWidth = firstColumnHandle()->sceneLeft();
130   QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
131   QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
132
133   if(atTop) {
134     for(int i = end; i >= start; i--) {
135       ChatLine *line = new ChatLine(i, model(),
136                                     width,
137                                     timestampWidth, senderWidth, contentsWidth,
138                                     senderPos, contentsPos);
139       h += line->height();
140       line->setPos(0, y-h);
141       _lines.insert(start, line);
142       addItem(line);
143     }
144   } else {
145     for(int i = start; i <= end; i++) {
146       ChatLine *line = new ChatLine(i, model(),
147                                     width,
148                                     timestampWidth, senderWidth, contentsWidth,
149                                     senderPos, contentsPos);
150       line->setPos(0, y+h);
151       h += line->height();
152       _lines.insert(i, line);
153       addItem(line);
154     }
155   }
156
157   // update existing items
158   for(int i = end+1; i < _lines.count(); i++) {
159     _lines[i]->setRow(i);
160   }
161
162   // update selection
163   if(_selectionStart >= 0) {
164     int offset = end - start + 1;
165     if(_selectionStart >= start) _selectionStart += offset;
166     if(_selectionEnd >= start) _selectionEnd += offset;
167     if(_firstSelectionRow >= start) _firstSelectionRow += offset;
168     if(_lastSelectionRow >= start) _lastSelectionRow += offset;
169   }
170
171   // neither pre- or append means we have to do dirty work: move items...
172   int moveStart = 0;
173   int moveEnd = _lines.count() - 1;
174   qreal offset = h;
175   if(!(atTop || atBottom)) {
176 //     int moveStart = 0;
177 //     int moveEnd = _lines.count() - 1;
178 //     qreal offset = h;
179     // move top means: moving 0 to end (aka: end + 1)
180     // move top means: moving end + 1 to _lines.count() - 1 (aka: _lines.count() - (end + 1)
181     if(end + 1 < _lines.count() - end - 1) {
182       // move top part
183       moveTop = true;
184       offset = -offset;
185       moveEnd = end;
186     } else {
187       // move bottom part
188       moveStart = end + 1;
189     }
190     ChatLine *line = 0;
191     for(int i = moveStart; i <= moveEnd; i++) {
192       line = _lines.at(i);
193       line->setPos(0, line->pos().y() + offset);
194     }
195   }
196
197   // check if all went right
198   Q_ASSERT(start == 0 || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y());
199   //Q_ASSERT(end + 1 == _lines.count() || _lines.at(end)->pos().y() + _lines.at(end)->height() == _lines.at(end + 1)->pos().y());
200   if(end + 1 < _lines.count()) {
201     if(_lines.at(end)->pos().y() + _lines.at(end)->height() != _lines.at(end + 1)->pos().y()) {
202       qDebug() << "lines:" << _lines.count() << "start:" << start << "end:" << end;
203       qDebug() << "line[end]:" << _lines.at(end)->pos().y() << "+" << _lines.at(end)->height() << "=" << _lines.at(end)->pos().y() + _lines.at(end)->height();
204       qDebug() << "line[end+1]" << _lines.at(end + 1)->pos().y();
205       qDebug() << "needed moving:" << !(atTop || atBottom) << moveTop << moveStart << moveEnd << offset;
206       Q_ASSERT(false);
207     }
208   }
209
210   if(!atBottom) {
211     if(start < _firstLineRow) {
212       int prevFirstLineRow = _firstLineRow + (end - start + 1);
213       for(int i = end + 1; i < prevFirstLineRow; i++) {
214         _lines.at(i)->show();
215       }
216     }
217     // force new search for first proper line
218     _firstLineRow = -1;
219   }
220   updateSceneRect();
221   if(atBottom || (!atTop && !moveTop)) {
222     emit lastLineChanged(_lines.last(), h);
223   }
224 }
225
226 void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
227   Q_UNUSED(parent);
228
229   qreal h = 0; // total height of removed items;
230
231   bool atTop = (start == 0);
232   bool atBottom = (end == _lines.count() - 1);
233   bool moveTop = false;
234
235   // remove items from scene
236   QList<ChatLine *>::iterator lineIter = _lines.begin() + start;
237   int lineCount = start;
238   while(lineIter != _lines.end() && lineCount <= end) {
239     h += (*lineIter)->height();
240     delete *lineIter;
241     lineIter = _lines.erase(lineIter);
242     lineCount++;
243   }
244
245   // update rows of remaining chatlines
246   for(int i = start; i < _lines.count(); i++) {
247     _lines.at(i)->setRow(i);
248   }
249
250   // update selection
251   if(_selectionStart >= 0) {
252     int offset = end - start + 1;
253     if(_selectionStart >= start)
254       _selectionStart -= offset;
255     if(_selectionEnd >= start)
256       _selectionEnd -= offset;
257     if(_firstSelectionRow >= start)
258       _firstSelectionRow -= offset;
259     if(_lastSelectionRow >= start)
260       _lastSelectionRow -= offset;
261   }
262
263   // neither removing at bottom or top means we have to move items...
264   if(!(atTop || atBottom)) {
265     qreal offset = h;
266     int moveStart = 0;
267     int moveEnd = _lines.count() - 1;
268     if(start < _lines.count() - start) {
269       // move top part
270       moveTop = true;
271       moveEnd = start - 1;
272     } else {
273       // move bottom part
274       moveStart = start;
275       offset = -offset;
276     }
277     ChatLine *line = 0;
278     for(int i = moveStart; i <= moveEnd; i++) {
279       line = _lines.at(i);
280       line->setPos(0, line->pos().y() + offset);
281     }
282   }
283
284   Q_ASSERT(start == 0 || start >= _lines.count() || _lines.at(start - 1)->pos().y() + _lines.at(start - 1)->height() == _lines.at(start)->pos().y());
285
286   // update sceneRect
287   // when searching for the first non-date-line we have to take into account that our
288   // model still contains the just removed lines so we cannot simply call updateSceneRect()
289   int numRows = model()->rowCount();
290   QModelIndex firstLineIdx;
291   _firstLineRow = -1;
292   bool needOffset = false;
293   do {
294     _firstLineRow++;
295     if(_firstLineRow >= start && _firstLineRow <= end) {
296       _firstLineRow = end + 1;
297       needOffset = true;
298     }
299     firstLineIdx = model()->index(_firstLineRow, 0);
300   } while((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) == Message::DayChange && _firstLineRow < numRows);
301
302   if(needOffset)
303     _firstLineRow -= end - start + 1;
304   updateSceneRect();
305 }
306
307 void ChatScene::updateForViewport(qreal width, qreal height) {
308   _viewportHeight = height;
309   setWidth(width);
310 }
311
312 void ChatScene::setWidth(qreal width) {
313   if(width == _sceneRect.width())
314     return;
315
316   // clock_t startT = clock();
317
318   // disabling the index while doing this complex updates is about
319   // 2 to 10 times faster!
320   //setItemIndexMethod(QGraphicsScene::NoIndex);
321
322   QList<ChatLine *>::iterator lineIter = _lines.end();
323   QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
324   qreal linePos = _sceneRect.y() + _sceneRect.height();
325   qreal contentsWidth = width - secondColumnHandle()->sceneRight();
326   while(lineIter != lineIterBegin) {
327     lineIter--;
328     (*lineIter)->setGeometryByWidth(width, contentsWidth, linePos);
329   }
330   //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
331
332   updateSceneRect(width);
333   setHandleXLimits();
334
335 //   clock_t endT = clock();
336 //   qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
337 }
338
339 void ChatScene::firstHandlePositionChanged(qreal xpos) {
340   if(_firstColHandlePos == xpos)
341     return;
342
343   _firstColHandlePos = xpos;
344   ChatViewSettings viewSettings(this);
345   viewSettings.setValue("FirstColumnHandlePos", _firstColHandlePos);
346   ChatViewSettings defaultSettings;
347   defaultSettings.setValue("FirstColumnHandlePos", _firstColHandlePos);
348
349   // clock_t startT = clock();
350
351   // disabling the index while doing this complex updates is about
352   // 2 to 10 times faster!
353   //setItemIndexMethod(QGraphicsScene::NoIndex);
354
355   QList<ChatLine *>::iterator lineIter = _lines.end();
356   QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
357   qreal timestampWidth = firstColumnHandle()->sceneLeft();
358   qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
359   QPointF senderPos(firstColumnHandle()->sceneRight(), 0);
360
361   while(lineIter != lineIterBegin) {
362     lineIter--;
363     (*lineIter)->setFirstColumn(timestampWidth, senderWidth, senderPos);
364   }
365   //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
366
367   setHandleXLimits();
368
369 //   clock_t endT = clock();
370 //   qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
371 }
372
373 void ChatScene::secondHandlePositionChanged(qreal xpos) {
374   if(_secondColHandlePos == xpos)
375     return;
376
377   _secondColHandlePos = xpos;
378   ChatViewSettings viewSettings(this);
379   viewSettings.setValue("SecondColumnHandlePos", _secondColHandlePos);
380   ChatViewSettings defaultSettings;
381   defaultSettings.setValue("SecondColumnHandlePos", _secondColHandlePos);
382
383   // clock_t startT = clock();
384
385   // disabling the index while doing this complex updates is about
386   // 2 to 10 times faster!
387   //setItemIndexMethod(QGraphicsScene::NoIndex);
388
389   QList<ChatLine *>::iterator lineIter = _lines.end();
390   QList<ChatLine *>::iterator lineIterBegin = _lines.begin();
391   qreal linePos = _sceneRect.y() + _sceneRect.height();
392   qreal senderWidth = secondColumnHandle()->sceneLeft() - firstColumnHandle()->sceneRight();
393   qreal contentsWidth = _sceneRect.width() - secondColumnHandle()->sceneRight();
394   QPointF contentsPos(secondColumnHandle()->sceneRight(), 0);
395   while(lineIter != lineIterBegin) {
396     lineIter--;
397     (*lineIter)->setSecondColumn(senderWidth, contentsWidth, contentsPos, linePos);
398   }
399   //setItemIndexMethod(QGraphicsScene::BspTreeIndex);
400
401   setHandleXLimits();
402
403 //   clock_t endT = clock();
404 //   qDebug() << "resized" << _lines.count() << "in" << (float)(endT - startT) / CLOCKS_PER_SEC << "sec";
405 }
406
407 void ChatScene::setHandleXLimits() {
408   _firstColHandle->setXLimits(0, _secondColHandle->sceneLeft());
409   _secondColHandle->setXLimits(_firstColHandle->sceneRight(), width() - minContentsWidth);
410 }
411
412 void ChatScene::setSelectingItem(ChatItem *item) {
413   if(_selectingItem) _selectingItem->clearSelection();
414   _selectingItem = item;
415 }
416
417 void ChatScene::startGlobalSelection(ChatItem *item, const QPointF &itemPos) {
418   _selectionStart = _selectionEnd = _lastSelectionRow = _firstSelectionRow = item->row();
419   _selectionStartCol = _selectionMinCol = item->column();
420   _isSelecting = true;
421   _lines[_selectionStart]->setSelected(true, (ChatLineModel::ColumnType)_selectionMinCol);
422   updateSelection(item->mapToScene(itemPos));
423 }
424
425 void ChatScene::updateSelection(const QPointF &pos) {
426   // This is somewhat hacky... we look at the contents item that is at the cursor's y position (ignoring x), since
427   // it has the full height. From this item, we can then determine the row index and hence the ChatLine.
428   ChatItem *contentItem = static_cast<ChatItem *>(itemAt(QPointF(_secondColHandle->sceneRight() + 1, pos.y())));
429   if(!contentItem) return;
430
431   int curRow = contentItem->row();
432   int curColumn;
433   if(pos.x() > _secondColHandle->sceneRight()) curColumn = ChatLineModel::ContentsColumn;
434   else if(pos.x() > _firstColHandlePos) curColumn = ChatLineModel::SenderColumn;
435   else curColumn = ChatLineModel::TimestampColumn;
436
437   ChatLineModel::ColumnType minColumn = (ChatLineModel::ColumnType)qMin(curColumn, _selectionStartCol);
438   if(minColumn != _selectionMinCol) {
439     _selectionMinCol = minColumn;
440     for(int l = qMin(_selectionStart, _selectionEnd); l <= qMax(_selectionStart, _selectionEnd); l++) {
441       _lines[l]->setSelected(true, minColumn);
442     }
443   }
444   int newstart = qMin(curRow, _firstSelectionRow);
445   int newend = qMax(curRow, _firstSelectionRow);
446   if(newstart < _selectionStart) {
447     for(int l = newstart; l < _selectionStart; l++)
448       _lines[l]->setSelected(true, minColumn);
449   }
450   if(newstart > _selectionStart) {
451     for(int l = _selectionStart; l < newstart; l++)
452       _lines[l]->setSelected(false);
453   }
454   if(newend > _selectionEnd) {
455     for(int l = _selectionEnd+1; l <= newend; l++)
456       _lines[l]->setSelected(true, minColumn);
457   }
458   if(newend < _selectionEnd) {
459     for(int l = newend+1; l <= _selectionEnd; l++)
460       _lines[l]->setSelected(false);
461   }
462
463   _selectionStart = newstart;
464   _selectionEnd = newend;
465   _lastSelectionRow = curRow;
466
467   if(newstart == newend && minColumn == ChatLineModel::ContentsColumn) {
468     if(!_selectingItem) {
469       qWarning() << "WARNING: ChatScene::updateSelection() has a null _selectingItem, this should never happen! Please report.";
470       return;
471     }
472     _lines[curRow]->setSelected(false);
473     _isSelecting = false;
474     _selectingItem->continueSelecting(_selectingItem->mapFromScene(pos));
475   }
476 }
477
478 void ChatScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
479   if(_isSelecting && event->buttons() == Qt::LeftButton) {
480     updateSelection(event->scenePos());
481     event->accept();
482   } else {
483     QGraphicsScene::mouseMoveEvent(event);
484   }
485 }
486
487 void ChatScene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
488   if(event->buttons() == Qt::LeftButton && _selectionStart >= 0) {
489     for(int l = qMin(_selectionStart, _selectionEnd); l <= qMax(_selectionStart, _selectionEnd); l++) {
490       _lines[l]->setSelected(false);
491     }
492     _selectionStart = -1;
493     QGraphicsScene::mousePressEvent(event);  // so we can start a new local selection
494   } else {
495     QGraphicsScene::mousePressEvent(event);
496   }
497 }
498
499 void ChatScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
500   if(_isSelecting && !event->buttons() & Qt::LeftButton) {
501     putToClipboard(selectionToString());
502     _isSelecting = false;
503     event->accept();
504   } else {
505     QGraphicsScene::mouseReleaseEvent(event);
506   }
507 }
508
509 void ChatScene::putToClipboard(const QString &selection) {
510   // TODO Configure clipboards
511 #   ifdef Q_WS_X11
512   QApplication::clipboard()->setText(selection, QClipboard::Selection);
513 #   endif
514 //# else
515   QApplication::clipboard()->setText(selection);
516 //# endif
517 }
518
519 //!\brief Convert current selection to human-readable string.
520 QString ChatScene::selectionToString() const {
521   //TODO Make selection format configurable!
522   if(!_isSelecting) return QString();
523   int start = qMin(_selectionStart, _selectionEnd);
524   int end = qMax(_selectionStart, _selectionEnd);
525   if(start < 0 || end >= _lines.count()) {
526     qDebug() << "Invalid selection range:" << start << end;
527     return QString();
528   }
529   QString result;
530   for(int l = start; l <= end; l++) {
531     if(_selectionMinCol == ChatLineModel::TimestampColumn)
532       result += _lines[l]->item(ChatLineModel::TimestampColumn).data(MessageModel::DisplayRole).toString() + " ";
533     if(_selectionMinCol <= ChatLineModel::SenderColumn)
534       result += _lines[l]->item(ChatLineModel::SenderColumn).data(MessageModel::DisplayRole).toString() + " ";
535     result += _lines[l]->item(ChatLineModel::ContentsColumn).data(MessageModel::DisplayRole).toString() + "\n";
536   }
537   return result;
538 }
539
540 void ChatScene::requestBacklog() {
541   MessageFilter *filter = qobject_cast<MessageFilter*>(model());
542   if(filter)
543     return filter->requestBacklog();
544   return;
545 }
546
547 int ChatScene::sectionByScenePos(int x) {
548   if(x < _firstColHandle->x())
549     return ChatLineModel::TimestampColumn;
550   if(x < _secondColHandle->x())
551     return ChatLineModel::SenderColumn;
552
553   return ChatLineModel::ContentsColumn;
554 }
555
556 void ChatScene::updateSceneRect(qreal width) {
557   if(_lines.isEmpty()) {
558     updateSceneRect(QRectF(0, 0, width, 0));
559     return;
560   }
561
562   // we hide day change messages at the top by making the scene rect smaller
563   // and by calling QGraphicsItem::hide() on all leading day change messages
564   // the first one is needed to ensure proper scrollbar ranges
565   // the second for cases where the viewport is larger then the set scenerect
566   //  (in this case the items are shown anyways)
567   if(_firstLineRow == -1) {
568     int numRows = model()->rowCount();
569     _firstLineRow = 0;
570     QModelIndex firstLineIdx;
571     while(_firstLineRow < numRows) {
572       firstLineIdx = model()->index(_firstLineRow, 0);
573       if((Message::Type)(model()->data(firstLineIdx, MessageModel::TypeRole).toInt()) != Message::DayChange)
574         break;
575       _lines.at(_firstLineRow)->hide();
576       _firstLineRow++;
577     }
578   }
579
580   // the following call should be safe. If it crashes something went wrong during insert/remove
581   if(_firstLineRow < _lines.count()) {
582     ChatLine *firstLine = _lines.at(_firstLineRow);
583     ChatLine *lastLine = _lines.last();
584     updateSceneRect(QRectF(0, firstLine->pos().y(), width, lastLine->pos().y() + lastLine->height() - firstLine->pos().y()));
585   } else {
586     // empty scene rect
587     updateSceneRect(QRectF(0, 0, width, 0));
588   }
589 }
590
591 void ChatScene::updateSceneRect(const QRectF &rect) {
592   _sceneRect = rect;
593   setSceneRect(rect);
594   update();
595 }
596
597 bool ChatScene::event(QEvent *e) {
598   if(e->type() == QEvent::ApplicationPaletteChange) {
599     _firstColHandle->setColor(QApplication::palette().windowText().color());
600     _secondColHandle->setColor(QApplication::palette().windowText().color());
601   }
602   return QGraphicsScene::event(e);
603 }
604
605 void ChatScene::loadWebPreview(ChatItem *parentItem, const QString &url, const QRectF &urlRect) {
606 #ifndef HAVE_WEBKIT
607   Q_UNUSED(parentItem)
608   Q_UNUSED(url)
609   Q_UNUSED(urlRect)
610 #else
611   if(webPreview.parentItem != parentItem)
612     webPreview.parentItem = parentItem;
613
614   if(webPreview.url != url) {
615     webPreview.url = url;
616     // load a new web view and delete the old one (if exists)
617     if(webPreview.previewItem && webPreview.previewItem->scene()) {
618       removeItem(webPreview.previewItem);
619       delete webPreview.previewItem;
620     }
621     webPreview.previewItem = new WebPreviewItem(url);
622     webPreview.delayTimer.start(2000);
623     webPreview.deleteTimer.stop();
624   } else if(webPreview.previewItem && !webPreview.previewItem->scene()) {
625       // we just have to readd the item to the scene
626       webPreview.delayTimer.start(2000);
627       webPreview.deleteTimer.stop();
628   }
629   if(webPreview.urlRect != urlRect) {
630     webPreview.urlRect = urlRect;
631     qreal previewY = urlRect.bottom();
632     qreal previewX = urlRect.x();
633     if(previewY + webPreview.previewItem->boundingRect().height() > sceneRect().bottom())
634       previewY = urlRect.y() - webPreview.previewItem->boundingRect().height();
635
636     if(previewX + webPreview.previewItem->boundingRect().width() > sceneRect().width())
637       previewX = sceneRect().right() - webPreview.previewItem->boundingRect().width();
638
639     webPreview.previewItem->setPos(previewX, previewY);
640   }
641 #endif
642 }
643
644 void ChatScene::showWebPreviewEvent() {
645 #ifdef HAVE_WEBKIT
646   if(webPreview.previewItem)
647     addItem(webPreview.previewItem);
648 #endif
649 }
650
651 void ChatScene::clearWebPreview(ChatItem *parentItem) {
652 #ifndef HAVE_WEBKIT
653   Q_UNUSED(parentItem)
654 #else
655   if(parentItem == 0 || webPreview.parentItem == parentItem) {
656     if(webPreview.previewItem && webPreview.previewItem->scene()) {
657       removeItem(webPreview.previewItem);
658       webPreview.deleteTimer.start();
659     }
660     webPreview.delayTimer.stop();
661   }
662 #endif
663 }
664
665 void ChatScene::deleteWebPreviewEvent() {
666 #ifdef HAVE_WEBKIT
667   if(webPreview.previewItem) {
668     delete webPreview.previewItem;
669     webPreview.previewItem = 0;
670   }
671   webPreview.parentItem = 0;
672   webPreview.url = QString();
673   webPreview.urlRect = QRectF();
674 #endif
675 }