Try to make the QTBUG-6322 workaround work in more cases
[quassel.git] / src / qtui / chatview.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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 <QGraphicsTextItem>
22 #include <QKeyEvent>
23 #include <QMenu>
24 #include <QScrollBar>
25
26 #include "bufferwidget.h"
27 #include "chatscene.h"
28 #include "chatview.h"
29 #include "client.h"
30 #include "messagefilter.h"
31 #include "qtui.h"
32 #include "qtuistyle.h"
33 #include "clientignorelistmanager.h"
34
35 ChatView::ChatView(BufferId bufferId, QWidget *parent)
36   : QGraphicsView(parent),
37     AbstractChatView(),
38     _bufferContainer(0),
39     _currentScaleFactor(1),
40     _invalidateFilter(false),
41     _verticalOffset(0),
42     _verticalOffsetStable(false)
43 {
44   QList<BufferId> filterList;
45   filterList.append(bufferId);
46   MessageFilter *filter = new MessageFilter(Client::messageModel(), filterList, this);
47   init(filter);
48 }
49
50 ChatView::ChatView(MessageFilter *filter, QWidget *parent)
51   : QGraphicsView(parent),
52     AbstractChatView(),
53     _bufferContainer(0),
54     _currentScaleFactor(1),
55     _invalidateFilter(false)
56 {
57   init(filter);
58 }
59
60 void ChatView::init(MessageFilter *filter) {
61   setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
62   setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
63   setAlignment(Qt::AlignLeft|Qt::AlignBottom);
64   setInteractive(true);
65   //setOptimizationFlags(QGraphicsView::DontClipPainter | QGraphicsView::DontAdjustForAntialiasing);
66   // setOptimizationFlags(QGraphicsView::DontAdjustForAntialiasing);
67   setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
68   // setTransformationAnchor(QGraphicsView::NoAnchor);
69   setTransformationAnchor(QGraphicsView::AnchorViewCenter);
70
71   _scrollTimer.setInterval(100);
72   _scrollTimer.setSingleShot(true);
73   connect(&_scrollTimer, SIGNAL(timeout()), SLOT(scrollTimerTimeout()));
74
75   _scene = new ChatScene(filter, filter->idString(), viewport()->width(), this);
76   connect(_scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(adjustSceneRect()));
77   connect(_scene, SIGNAL(lastLineChanged(QGraphicsItem *, qreal)), this, SLOT(lastLineChanged(QGraphicsItem *, qreal)));
78   connect(_scene, SIGNAL(mouseMoveWhileSelecting(const QPointF &)), this, SLOT(mouseMoveWhileSelecting(const QPointF &)));
79   setScene(_scene);
80
81   connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(verticalScrollbarChanged(int)));
82
83   // only connect if client is synched with a core
84   if(Client::isConnected())
85     connect(Client::ignoreListManager(), SIGNAL(ignoreListChanged()), this, SLOT(invalidateFilter()));
86 }
87
88 bool ChatView::event(QEvent *event) {
89   if(event->type() == QEvent::KeyPress) {
90     QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
91     switch(keyEvent->key()) {
92     case Qt::Key_Up:
93     case Qt::Key_Down:
94     case Qt::Key_PageUp:
95     case Qt::Key_PageDown:
96       if(!verticalScrollBar()->isVisible()) {
97         scene()->requestBacklog();
98         return true;
99       }
100     default:
101       break;
102     }
103   }
104
105   if(event->type() == QEvent::Wheel) {
106     if(!verticalScrollBar()->isVisible()) {
107       scene()->requestBacklog();
108       return true;
109     }
110   }
111
112   if(event->type() == QEvent::Show) {
113     if(_invalidateFilter)
114       invalidateFilter();
115   }
116
117   return QGraphicsView::event(event);
118 }
119
120 void ChatView::resizeEvent(QResizeEvent *event) {
121   QGraphicsView::resizeEvent(event);
122
123   // we can reduce viewport updates if we scroll to the bottom allready at the beginning
124   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
125   scene()->updateForViewport(viewport()->width(), viewport()->height());
126   adjustSceneRect();
127
128   _lastScrollbarPos = verticalScrollBar()->maximum();
129   verticalScrollBar()->setValue(verticalScrollBar()->maximum());
130 }
131
132 // Workaround for QTBUG-6322
133 // The viewport rect gets some margins where it shouldn't, resulting in scrollbars to appear
134 void ChatView::adjustSceneRect() {
135   QRectF rect = scene()->sceneRect(); // qDebug() << "sceneRect" << rect;
136   if(rect.height() <= viewport()->height()) {
137     setSceneRect(rect);
138     return;
139   }
140
141   if(_verticalOffsetStable && rect.height() > viewport()->height())
142     setSceneRect(rect.adjusted(0, 0, 0, _verticalOffset));
143   else
144     setSceneRect(rect);
145
146   QScrollBar *vbar = verticalScrollBar();
147   qreal sceneHeight = rect.height();
148   qreal viewHeight = vbar->maximum() + viewport()->height() - vbar->minimum();
149   if(sceneHeight != viewHeight) {
150     qreal voffset = sceneHeight - viewHeight; //voffset *= _currentScaleFactor;
151     // qDebug() << "Adjusting ChatView offset to" << voffset << "(QTBUG-6322)";
152     if(sceneHeight + voffset <= viewport()->height()) {
153       setSceneRect(rect.adjusted(0, -voffset, 0, 0));
154       _verticalOffsetStable = false;
155       return;
156     } else {
157       _verticalOffsetStable = true;
158       _verticalOffset = voffset;
159       setSceneRect(rect.adjusted(0, 0, 0, voffset));
160     }
161     if(vbar->maximum() + viewport()->height() - vbar->minimum() != sceneHeight) {
162       //qWarning() << "Workaround for QTBUG-6322 failed!1!!" << vbar->maximum() + viewport()->height() - vbar->minimum() << sceneHeight;
163       _verticalOffsetStable = false;
164       adjustSceneRect();
165       return;
166     }
167     if(voffset == _verticalOffset)
168       _verticalOffsetStable = true;
169     else {
170       //if(voffset <= 0)
171         _verticalOffset = voffset;
172     }
173   } //else
174     //_verticalOffsetStable = true;
175 }
176
177 void ChatView::mouseMoveWhileSelecting(const QPointF &scenePos) {
178   int y = (int)mapFromScene(scenePos).y();
179   _scrollOffset = 0;
180   if(y < 0)
181     _scrollOffset = y;
182   else if(y > height())
183     _scrollOffset = y - height();
184
185   if(_scrollOffset && !_scrollTimer.isActive())
186     _scrollTimer.start();
187 }
188
189 void ChatView::scrollTimerTimeout() {
190   // scroll view
191   QAbstractSlider *vbar = verticalScrollBar();
192   if(_scrollOffset < 0 && vbar->value() > 0)
193     vbar->setValue(qMax(vbar->value() + _scrollOffset, 0));
194   else if(_scrollOffset > 0 && vbar->value() < vbar->maximum())
195     vbar->setValue(qMin(vbar->value() + _scrollOffset, vbar->maximum()));
196 }
197
198 void ChatView::lastLineChanged(QGraphicsItem *chatLine, qreal offset) {
199   Q_UNUSED(chatLine)
200   // disabled until further testing/discussion
201   //if(!scene()->isScrollingAllowed())
202   //  return;
203
204   QAbstractSlider *vbar = verticalScrollBar();
205   Q_ASSERT(vbar);
206   if(vbar->maximum() - vbar->value() <= (offset + 5) * _currentScaleFactor ) { // 5px grace area
207     vbar->setValue(vbar->maximum());
208   }
209 }
210
211 void ChatView::verticalScrollbarChanged(int newPos) {
212   QAbstractSlider *vbar = verticalScrollBar();
213   Q_ASSERT(vbar);
214
215   // check for backlog request
216   if(newPos < _lastScrollbarPos) {
217     int relativePos = 100;
218     if(vbar->maximum() - vbar->minimum() != 0)
219       relativePos = (newPos - vbar->minimum()) * 100 / (vbar->maximum() - vbar->minimum());
220
221     if(relativePos < 20) {
222       scene()->requestBacklog();
223     }
224   }
225   _lastScrollbarPos = newPos;
226
227   // FIXME: Fugly workaround for the ChatView scrolling up 1px on buffer switch
228   if(vbar->maximum() - newPos <= 2)
229     vbar->setValue(vbar->maximum());
230 }
231
232 MsgId ChatView::lastMsgId() const {
233   if(!scene())
234     return MsgId();
235
236   QAbstractItemModel *model = scene()->model();
237   if(!model || model->rowCount() == 0)
238     return MsgId();
239
240   return model->data(model->index(model->rowCount() - 1, 0), MessageModel::MsgIdRole).value<MsgId>();
241 }
242
243 void ChatView::addActionsToMenu(QMenu *menu, const QPointF &pos) {
244   // zoom actions
245   BufferWidget *bw = qobject_cast<BufferWidget *>(bufferContainer());
246   if(bw) {
247     bw->addActionsToMenu(menu, pos);
248     menu->addSeparator();
249   }
250 }
251
252 void ChatView::zoomIn() {
253     _currentScaleFactor *= 1.2;
254     scale(1.2, 1.2);
255     scene()->setWidth(viewport()->width() / _currentScaleFactor - 2);
256 }
257
258 void ChatView::zoomOut() {
259     _currentScaleFactor /= 1.2;
260     scale(1 / 1.2, 1 / 1.2);
261     scene()->setWidth(viewport()->width() / _currentScaleFactor - 2);
262 }
263
264 void ChatView::zoomOriginal() {
265     scale(1/_currentScaleFactor, 1/_currentScaleFactor);
266     _currentScaleFactor = 1;
267     scene()->setWidth(viewport()->width() - 2);
268 }
269
270 void ChatView::invalidateFilter() {
271   // if this is the currently selected chatview
272   // invalidate immediately
273   if(isVisible()) {
274     _scene->filter()->invalidateFilter();
275     _invalidateFilter = false;
276   }
277   // otherwise invalidate whenever the view is shown
278   else {
279     _invalidateFilter = true;
280   }
281 }