Vidalia 0.3.1
ZImageView.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If you
4** did not receive the LICENSE file with this file, you may obtain it from the
5** Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11/*
12** \file ZImageView.cpp
13** \brief Displays an image and allows zooming and panning
14*/
15
16#include "ZImageView.h"
17
18#include <QPainter>
19#include <QMouseEvent>
20
21#include <cmath>
22
23#if QT_VERSION >= 0x040200
24#define CURSOR_NORMAL QCursor(Qt::OpenHandCursor)
25#define CURSOR_MOUSE_PRESS QCursor(Qt::ClosedHandCursor)
26#else
27#define CURSOR_NORMAL QCursor(Qt::CrossCursor)
28#define CURSOR_MOUSE_PRESS QCursor(Qt::SizeAllCursor)
29#endif
30
31
32/** Constructor. */
33ZImageView::ZImageView(QWidget *parent)
34 : QWidget(parent)
35{
36 /* Initialize members */
37 _zoom = 0.0;
38 _desiredX = 0.0;
39 _desiredY = 0.0;
40 _maxZoomFactor = 2.0;
41 _padding = 60;
42
43 setCursor(CURSOR_NORMAL);
46 repaint();
47}
48
49/** Sets the displayed image. */
50void
52{
53 _image = img.copy();
56
57 if (isVisible()) {
58 repaint();
59 }
60}
61
62/** Draws the scaled image on the widget. */
63void
65{
66 if (!isVisible()) {
67 return;
68 }
69
70 QBrush background(QColor("#fdfdfd"));
71 if (_image.isNull()) {
72 QPainter p(this);
73 p.fillRect(rect(), background);
74 return;
75 }
76
77 QRect sRect = rect();
78 QRect iRect = _image.rect();
79 QRect r = _view;
80
81 // Think of the _view as being overlaid on the image. The _view has the same
82 // aspect ratio as the screen, so we cut the _view region out of the _image
83 // and scale it to the screen dimensions and paint it.
84
85 // There is a slight catch in that the _view may be larger than the image in
86 // one or both directions. In that case, we need to reduce the _view region
87 // to lie within the image, then paint the background around it. Copying
88 // a region from an image where the region is bigger than the image results
89 // in the parts outside the image being black, which is not what we want.
90
91 // The view has the same aspect ratio as the screen, so the vertical and
92 // horizontal scale factors will be equal.
93
94 double scaleFactor = double(sRect.width()) / double(_view.width());
95
96 // Constrain r to lie entirely within the image.
97 if (r.top() < 0) {
98 r.setTop(0);
99 }
100 if (iRect.bottom() < r.bottom()) {
101 r.setBottom(iRect.bottom());
102 }
103 if (r.left() < 0) {
104 r.setLeft(0);
105 }
106 if (iRect.right() < r.right()) {
107 r.setRight(iRect.right());
108 }
109
110 // Figure out the size that the 'r' region will be when drawn to the screen.
111 QSize scaleTo(int(double(r.width()) * scaleFactor),
112 int(double(r.height()) * scaleFactor));
113
114 /** Make a copy of the image so we don't ruin the original */
115 QImage i = _image.copy();
116
117 /** Create a QPainter that draws directly on the copied image and call the
118 * virtual function to draw whatever the subclasses need to on the image. */
119 QPainter painter;
120 painter.begin(&i);
121 paintImage(&painter);
122 painter.end();
123
124 /** Rescale the image copy */
125 i = i.copy(r).scaled(scaleTo,
126 Qt::KeepAspectRatioByExpanding,
127 Qt::SmoothTransformation);
128
129 int extraWidth = int(double(sRect.width() - i.width()) / 2.0);
130 int extraHeight = int(double(sRect.height() - i.height()) / 2.0);
131
132 // We don't want to paint the background
133 // because this isn't double buffered and that would flicker.
134 // We could double buffer it, but that would cost ~3 MB of memory.
135
136 QPainter p(this);
137 if (extraWidth > 0) {
138 p.fillRect(0, 0, extraWidth, sRect.height(), background);
139 p.fillRect(sRect.width() - extraWidth, 0,
140 sRect.width(), sRect.height(), background);
141 }
142
143 if (extraHeight > 0) {
144 p.fillRect(0, 0, sRect.width(), extraHeight, background);
145 p.fillRect(0, sRect.height() - extraHeight,
146 sRect.width(), sRect.height(), background);
147 }
148
149 // Finally, paint the image copy.
150 p.drawImage(extraWidth, extraHeight, i);
151}
152
153/** Updates the displayed viewport. */
154void
155ZImageView::updateViewport(int screendx, int screendy)
156{
157 /* The gist of this is to find the biggest and smallest possible viewports,
158 * then use the _zoom factor to interpolate between them. Also pan the
159 * viewport, but constrain each dimension to lie within the image or to be
160 * centered if the image is too small in that direction. */
161
162 QRect sRect = rect();
163 QRect iRect = _image.rect();
164
165 float sw = float(sRect.width());
166 float sh = float(sRect.height());
167 float iw = float(iRect.width());
168 float ih = float(iRect.height());
169
170 // Get the initial max and min sizes for the viewport. These won't have the
171 // correct aspect ratio. They will actually be the least upper bound and
172 // greatest lower bound of the set containing the screen and image rects.
173 float maxw = float(std::max<int>(sRect.width(), iRect.width())) + _padding;
174 float maxh = float(std::max<int>(sRect.height(), iRect.height())) + _padding;
175 float minw = std::ceil(float(sRect.width()) / _maxZoomFactor);
176 float minh = std::ceil(float(sRect.height()) / _maxZoomFactor);
177
178 // Now that we have the glb and the lub, we expand/shrink them until
179 // the aspect ratio is that of the screen.
180 float aspect = sw / sh;
181
182 // Fix the max rect.
183 float newmaxh = maxh;
184 float newmaxw = aspect * newmaxh;
185 if (newmaxw < maxw) {
186 newmaxw = maxw;
187 newmaxh = maxw / aspect;
188 }
189
190 // Fix the min rect.
191 float newminh = minh;
192 float newminw = aspect * newminh;
193 if (minw < newminw) {
194 newminw = minw;
195 newminh = newminw / aspect;
196 }
197
198 // Now interpolate between max and min.
199 float vw = (1.0f - _zoom) * (newmaxw - newminw) + newminw;
200 float vh = (1.0f - _zoom) * (newmaxh - newminh) + newminh;
201
202 _view.setWidth(int(vw));
203 _view.setHeight(int(vh));
204
205 // Now pan the view
206
207 // Convert the pan delta from screen coordinates to view coordinates.
208 float vdx = vw * (float(screendx) / sw);
209 float vdy = vh * (float(screendy) / sh);
210
211 // Constrain the center of the viewport to the image rect.
212 _desiredX = qBound(0.0f, _desiredX + vdx, iw);
213 _desiredY = qBound(0.0f, _desiredY + vdy, ih);
214 _view.moveCenter(QPoint(int(_desiredX), int(_desiredY)));
215
216 QPoint viewCenter = _view.center();
217 float vx = viewCenter.x();
218 float vy = viewCenter.y();
219
220 // The viewport may be wider than the height and/or width. In that case,
221 // center the view over the image in the appropriate directions.
222 //
223 // If the viewport is smaller than the image in either direction, then make
224 // sure the edge of the viewport isn't past the edge of the image.
225
226 vdx = 0;
227 vdy = 0;
228
229 if (iw <= vw) {
230 vdx = (iw / 2.0f) - vx; // Center horizontally.
231 } else {
232 // Check that the edge of the view isn't past the edge of the image.
233 float vl = float(_view.left());
234 float vr = float(_view.right());
235 if (vl < 0) {
236 vdx = -vl;
237 } else if (vr > iw) {
238 vdx = iw - vr;
239 }
240 }
241
242 if (ih <= vh) {
243 vdy = (ih / 2.0f) - vy; // Center vertically.
244 } else {
245 // Check that the edge of the view isn't past the edge of the image.
246 float vt = float(_view.top());
247 float vb = float(_view.bottom());
248 if (vt < 0) {
249 vdy = -vt;
250 } else if (vb > ih) {
251 vdy = ih - vb;
252 }
253 }
254
255 _view.translate(int(vdx), int(vdy));
256}
257
258/** Resets the zoom point back to the center of the viewport. */
259void
261{
262 QPoint viewCenter = _view.center();
263 _desiredX = viewCenter.x();
264 _desiredY = viewCenter.y();
265}
266
267/** Handles repainting this widget by updating the viewport and drawing the
268 * scaled image. */
269void
271{
274}
275
276/** Sets the current zoom percentage to the given value and scrolls the
277 * viewport to center the given point. */
278void
279ZImageView::zoom(QPoint zoomAt, float pct)
280{
281 _desiredX = zoomAt.x();
282 _desiredY = zoomAt.y();
283 zoom(pct);
284}
285
286/** Sets the current zoom percentage to the given value. */
287void
289{
290 _zoom = qBound(0.0f, pct, 1.0f);
291 repaint();
292}
293
294/** Zooms into the image by 10% */
295void
297{
298 zoom(_zoom + .1);
299}
300
301/** Zooms away from the image by 10% */
302void
304{
305 zoom(_zoom - .1);
306}
307
308/** Responds to the user pressing a mouse button. */
309void
311{
312 e->accept();
313 setCursor(CURSOR_MOUSE_PRESS);
314 _mouseX = e->x();
315 _mouseY = e->y();
316}
317
318/** Responds to the user releasing a mouse button. */
319void
321{
322 e->accept();
323 setCursor(CURSOR_NORMAL);
326}
327
328/** Responds to the user double-clicking a mouse button on the image. A left
329 * double-click zooms in on the image and a right double-click zooms out.
330 * Zooming is centered on the location of the double-click. */
331void
333{
334 e->accept();
335
336 QPoint center = rect().center();
337 int dx = e->x() - center.x();
338 int dy = e->y() - center.y();
339 updateViewport(dx, dy);
341
342 Qt::MouseButton btn = e->button();
343 if (btn == Qt::LeftButton)
344 zoomIn();
345 else if (btn == Qt::RightButton)
346 zoomOut();
347}
348
349/** Responds to the user moving the mouse. */
350void
352{
353 e->accept();
354 int dx = _mouseX - e->x();
355 int dy = _mouseY - e->y();
356 _mouseX = e->x();
357 _mouseY = e->y();
358
359 updateViewport(dx, dy);
360 if (0.001 <= _zoom) {
361 repaint();
362 }
363}
364
365void
367{
368 if (e->delta() > 0) {
369 zoomIn();
370 } else {
371 zoomOut();
372 }
373}
#define CURSOR_NORMAL
Definition: ZImageView.cpp:27
#define CURSOR_MOUSE_PRESS
Definition: ZImageView.cpp:28
QRect _view
Definition: ZImageView.h:81
float _zoom
Definition: ZImageView.h:73
ZImageView(QWidget *parent=0)
Definition: ZImageView.cpp:33
virtual void mouseReleaseEvent(QMouseEvent *e)
Definition: ZImageView.cpp:320
void zoom(float pct)
Definition: ZImageView.cpp:288
int _mouseX
Definition: ZImageView.h:78
float _maxZoomFactor
Definition: ZImageView.h:76
float _padding
Definition: ZImageView.h:75
void zoomIn()
Definition: ZImageView.cpp:296
QImage _image
Definition: ZImageView.h:74
void setImage(QImage &pixmap)
Definition: ZImageView.cpp:51
float _desiredY
Definition: ZImageView.h:83
virtual void mouseDoubleClickEvent(QMouseEvent *e)
Definition: ZImageView.cpp:332
virtual void mousePressEvent(QMouseEvent *e)
Definition: ZImageView.cpp:310
virtual void mouseMoveEvent(QMouseEvent *e)
Definition: ZImageView.cpp:351
void resetZoomPoint()
Definition: ZImageView.cpp:260
void updateViewport(int screendx=0, int screendy=0)
Definition: ZImageView.cpp:155
void drawScaledImage()
Definition: ZImageView.cpp:64
void zoomOut()
Definition: ZImageView.cpp:303
int _mouseY
Definition: ZImageView.h:79
virtual void wheelEvent(QWheelEvent *e)
Definition: ZImageView.cpp:366
virtual void paintImage(QPainter *painter)
Definition: ZImageView.h:49
float _desiredX
Definition: ZImageView.h:82
virtual void paintEvent(QPaintEvent *)
Definition: ZImageView.cpp:270
QString i(QString str)
Definition: html.cpp:32
QString p(QString str)
Definition: html.cpp:22