~ubuntu-branches/ubuntu/intrepid/digikam/intrepid

« back to all changes in this revision

Viewing changes to digikam/libs/widgets/common/previewwidget.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Mark Purcell
  • Date: 2008-07-17 20:25:39 UTC
  • mfrom: (1.3.2 upstream) (37 hardy)
  • mto: This revision was merged to the branch mainline in revision 39.
  • Revision ID: james.westby@ubuntu.com-20080717202539-1bw3w3nrsso7yj4z
* New upstream release
  - digiKam 0.9.4 Release Plan (KDE3) ~ 13 July 08 (Closes: #490144)
* DEB_CONFIGURE_EXTRA_FLAGS := --without-included-sqlite3
* Debhelper compatibility level V7
* Install pixmaps in debian/*.install
* Add debian/digikam.lintian-overrides

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ============================================================
 
2
 *
 
3
 * This file is a part of digiKam project
 
4
 * http://www.digikam.org
 
5
 *
 
6
 * Date        : 2006-06-13
 
7
 * Description : a widget to display an image preview
 
8
 *
 
9
 * Copyright (C) 2006-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
 
10
 *
 
11
 * This program is free software; you can redistribute it
 
12
 * and/or modify it under the terms of the GNU General
 
13
 * Public License as published by the Free Software Foundation;
 
14
 * either version 2, or (at your option)
 
15
 * any later version.
 
16
 *
 
17
 * This program is distributed in the hope that it will be useful,
 
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
20
 * GNU General Public License for more details.
 
21
 *
 
22
 * ============================================================ */
 
23
 
 
24
// C++ includes.
 
25
 
 
26
#include <cmath>
 
27
 
 
28
// Qt includes.
 
29
 
 
30
#include <qstring.h>
 
31
#include <qcache.h>
 
32
#include <qpainter.h>
 
33
#include <qimage.h>
 
34
#include <qpixmap.h>
 
35
#include <qrect.h>
 
36
#include <qtimer.h>
 
37
#include <qguardedptr.h>
 
38
 
 
39
// KDE include.
 
40
 
 
41
#include <kcursor.h>
 
42
#include <klocale.h>
 
43
 
 
44
// Local includes.
 
45
 
 
46
#include "ddebug.h"
 
47
#include "previewwidget.h"
 
48
#include "previewwidget.moc"
 
49
 
 
50
namespace Digikam
 
51
{
 
52
 
 
53
class PreviewWidgetPriv
 
54
{
 
55
public:
 
56
 
 
57
    PreviewWidgetPriv() :
 
58
        tileSize(128), zoomMultiplier(1.2) 
 
59
    {
 
60
        midButtonX = 0;
 
61
        midButtonY = 0;
 
62
        autoZoom   = false;
 
63
        fullScreen = false;
 
64
        zoom       = 1.0;
 
65
        minZoom    = 0.1;
 
66
        maxZoom    = 12.0;
 
67
        zoomWidth  = 0;
 
68
        zoomHeight = 0;
 
69
        tileTmpPix = new QPixmap(tileSize, tileSize);
 
70
 
 
71
        tileCache.setMaxCost((10*1024*1024)/(tileSize*tileSize*4));
 
72
        tileCache.setAutoDelete(true);
 
73
    }
 
74
 
 
75
    bool            autoZoom;
 
76
    bool            fullScreen;
 
77
 
 
78
    const int       tileSize;
 
79
    int             midButtonX;
 
80
    int             midButtonY;
 
81
    int             zoomWidth;
 
82
    int             zoomHeight;
 
83
 
 
84
    double          zoom;
 
85
    double          minZoom;
 
86
    double          maxZoom;
 
87
    const double    zoomMultiplier;
 
88
 
 
89
    QPoint          centerZoomPoint;
 
90
 
 
91
    QRect           pixmapRect;
 
92
 
 
93
    QCache<QPixmap> tileCache;
 
94
 
 
95
    QPixmap*        tileTmpPix;
 
96
 
 
97
    QColor          bgColor;
 
98
};
 
99
 
 
100
PreviewWidget::PreviewWidget(QWidget *parent)
 
101
             : QScrollView(parent, 0, Qt::WDestructiveClose)
 
102
{
 
103
    d = new PreviewWidgetPriv;
 
104
    d->bgColor.setRgb(0, 0, 0);
 
105
    m_movingInProgress = false;
 
106
 
 
107
    viewport()->setBackgroundMode(Qt::NoBackground);
 
108
    viewport()->setMouseTracking(false);
 
109
 
 
110
    horizontalScrollBar()->setLineStep( 1 );
 
111
    horizontalScrollBar()->setPageStep( 1 );
 
112
    verticalScrollBar()->setLineStep( 1 );
 
113
    verticalScrollBar()->setPageStep( 1 );
 
114
 
 
115
    setFrameStyle(QFrame::GroupBoxPanel|QFrame::Plain); 
 
116
    setMargin(0); 
 
117
    setLineWidth(1);
 
118
}
 
119
 
 
120
PreviewWidget::~PreviewWidget()
 
121
{
 
122
    delete d->tileTmpPix;
 
123
    delete d;
 
124
}
 
125
 
 
126
void PreviewWidget::setBackgroundColor(const QColor& color)
 
127
{
 
128
    if (d->bgColor == color)
 
129
        return;
 
130
 
 
131
    d->bgColor = color;
 
132
    viewport()->update();
 
133
}
 
134
 
 
135
void PreviewWidget::slotReset()
 
136
{
 
137
    d->tileCache.clear();
 
138
    resetPreview();
 
139
}
 
140
 
 
141
QRect PreviewWidget::previewRect()
 
142
{
 
143
    return d->pixmapRect;
 
144
}
 
145
 
 
146
int PreviewWidget::tileSize()
 
147
{
 
148
    return d->tileSize;
 
149
}
 
150
 
 
151
int PreviewWidget::zoomWidth()
 
152
{
 
153
    return d->zoomWidth;
 
154
}
 
155
 
 
156
int PreviewWidget::zoomHeight()
 
157
{
 
158
    return d->zoomHeight;
 
159
}
 
160
 
 
161
double PreviewWidget::zoomMax()
 
162
{
 
163
    return d->maxZoom;
 
164
}
 
165
 
 
166
double PreviewWidget::zoomMin()
 
167
{
 
168
    return d->minZoom;
 
169
}
 
170
 
 
171
void PreviewWidget::setZoomMax(double z)
 
172
{
 
173
    d->maxZoom = ceilf(z * 10000.0) / 10000.0;
 
174
}
 
175
 
 
176
void PreviewWidget::setZoomMin(double z)
 
177
{
 
178
    d->minZoom = floor(z * 10000.0) / 10000.0;
 
179
}
 
180
 
 
181
bool PreviewWidget::maxZoom()
 
182
{
 
183
    return (d->zoom >= d->maxZoom);
 
184
}
 
185
 
 
186
bool PreviewWidget::minZoom()
 
187
{
 
188
    return (d->zoom <= d->minZoom);
 
189
}
 
190
 
 
191
double PreviewWidget::snapZoom(double zoom)
 
192
{
 
193
    // If the zoom value gets changed from d->zoom to zoom
 
194
    // across 50%, 100% or fit-to-window, then return the
 
195
    // the corresponding special value. Otherwise zoom is returned unchanged.
 
196
    double fit = calcAutoZoomFactor(ZoomInOrOut);
 
197
    QValueList<double> snapValues;
 
198
    snapValues.append(0.5);
 
199
    snapValues.append(1.0);
 
200
    snapValues.append(fit);
 
201
    qHeapSort(snapValues);
 
202
    QValueList<double>::const_iterator it;
 
203
 
 
204
    if (d->zoom < zoom) 
 
205
    {
 
206
        for(it = snapValues.constBegin(); it != snapValues.constEnd(); ++it)
 
207
        {
 
208
            double z = *it;
 
209
            if ((d->zoom < z) && (zoom > z))
 
210
            {
 
211
                 zoom = z;
 
212
                 break;
 
213
            }
 
214
        }
 
215
    } 
 
216
    else
 
217
    {
 
218
        for(it = snapValues.constEnd(); it != snapValues.constBegin(); --it)
 
219
        {
 
220
            double z = *it;
 
221
            if ((d->zoom > z) && (zoom < z))
 
222
            {
 
223
                 zoom = z;
 
224
                 break;
 
225
            }
 
226
        }
 
227
    }
 
228
 
 
229
    return zoom;
 
230
}
 
231
 
 
232
void PreviewWidget::slotIncreaseZoom()
 
233
{
 
234
    double zoom = d->zoom * d->zoomMultiplier;
 
235
    zoom = snapZoom(zoom > zoomMax() ? zoomMax() : zoom);
 
236
    setZoomFactor(zoom);
 
237
}
 
238
 
 
239
void PreviewWidget::slotDecreaseZoom()
 
240
{
 
241
    double zoom = d->zoom / d->zoomMultiplier;
 
242
    zoom = snapZoom(zoom < zoomMin() ? zoomMin() : zoom);
 
243
    setZoomFactor(zoom);
 
244
}
 
245
 
 
246
void PreviewWidget::setZoomFactorSnapped(double zoom)
 
247
{
 
248
    double fit = calcAutoZoomFactor(ZoomInOrOut);
 
249
    if (fabs(zoom-1.0) < 0.05) 
 
250
    {
 
251
        zoom = 1.0;
 
252
    }
 
253
    if (fabs(zoom-0.5) < 0.05) 
 
254
    {
 
255
        zoom = 0.5;
 
256
    }
 
257
    if (fabs(zoom-fit) < 0.05) 
 
258
    {
 
259
        zoom = fit;
 
260
    }
 
261
 
 
262
    setZoomFactor(zoom);
 
263
}
 
264
 
 
265
void PreviewWidget::setZoomFactor(double zoom)
 
266
{
 
267
    setZoomFactor(zoom, false);
 
268
}
 
269
 
 
270
void PreviewWidget::setZoomFactor(double zoom, bool centerView)
 
271
{
 
272
    // Zoom using center of canvas and given zoom factor.
 
273
 
 
274
    double oldZoom = d->zoom;
 
275
    double cpx, cpy;
 
276
 
 
277
    if (d->centerZoomPoint.isNull())
 
278
    {
 
279
        // center on current center
 
280
        // store old center pos
 
281
        cpx = contentsX() + visibleWidth()  / 2.0;
 
282
        cpy = contentsY() + visibleHeight() / 2.0;
 
283
 
 
284
        cpx = ( cpx / d->tileSize ) * floor(d->tileSize / d->zoom);
 
285
        cpy = ( cpy / d->tileSize ) * floor(d->tileSize / d->zoom);
 
286
    }
 
287
    else
 
288
    {
 
289
        // keep mouse pointer position constant
 
290
        // store old content pos
 
291
        cpx = contentsX();
 
292
        cpy = contentsY();
 
293
    }
 
294
 
 
295
    // To limit precision of zoom value and reduce error with check of max/min zoom.
 
296
    d->zoom       = floor(zoom * 10000.0) / 10000.0;
 
297
    d->zoomWidth  = (int)(previewWidth()  * d->zoom);
 
298
    d->zoomHeight = (int)(previewHeight() * d->zoom);
 
299
 
 
300
    updateContentsSize();
 
301
 
 
302
    // adapt step size to zoom factor. Overall, using a finer step size than scrollbar default.
 
303
    int step = QMAX(2, 2*lround(d->zoom));
 
304
    horizontalScrollBar()->setLineStep( step );
 
305
    horizontalScrollBar()->setPageStep( step * 10 );
 
306
    verticalScrollBar()->setLineStep( step );
 
307
    verticalScrollBar()->setPageStep( step * 10 );
 
308
 
 
309
    viewport()->setUpdatesEnabled(false);
 
310
    if (d->centerZoomPoint.isNull())
 
311
    {
 
312
        cpx = ( cpx * d->tileSize ) / floor(d->tileSize / d->zoom);
 
313
        cpy = ( cpy * d->tileSize ) / floor(d->tileSize / d->zoom);
 
314
 
 
315
        if (centerView)
 
316
        {
 
317
            cpx = d->zoomWidth/2.0;
 
318
            cpy = d->zoomHeight/2.0;
 
319
        }
 
320
 
 
321
        center((int)cpx, (int)(cpy));
 
322
    }
 
323
    else
 
324
    {
 
325
        cpx = d->zoom * d->centerZoomPoint.x() / oldZoom - d->centerZoomPoint.x() + cpx;
 
326
        cpy = d->zoom * d->centerZoomPoint.y() / oldZoom - d->centerZoomPoint.y() + cpy;
 
327
 
 
328
        setContentsPos((int)cpx, (int)(cpy));
 
329
    }
 
330
    viewport()->setUpdatesEnabled(true);
 
331
    viewport()->update();
 
332
 
 
333
    zoomFactorChanged(d->zoom);
 
334
}
 
335
 
 
336
double PreviewWidget::zoomFactor()
 
337
{
 
338
    return d->zoom; 
 
339
}
 
340
 
 
341
bool PreviewWidget::isFitToWindow()
 
342
{
 
343
    return d->autoZoom;
 
344
}
 
345
 
 
346
void PreviewWidget::fitToWindow()
 
347
{
 
348
    updateAutoZoom();
 
349
    updateContentsSize();
 
350
    zoomFactorChanged(d->zoom);
 
351
    viewport()->update();
 
352
}
 
353
 
 
354
void PreviewWidget::toggleFitToWindow()
 
355
{
 
356
    d->autoZoom = !d->autoZoom;
 
357
 
 
358
    if (d->autoZoom)
 
359
    {
 
360
        updateAutoZoom();
 
361
    }
 
362
    else
 
363
    {
 
364
        d->zoom = 1.0;
 
365
        zoomFactorChanged(d->zoom);
 
366
    }
 
367
 
 
368
    updateContentsSize();
 
369
    viewport()->update();
 
370
}
 
371
 
 
372
void PreviewWidget::toggleFitToWindowOr100()
 
373
{
 
374
    // If the current zoom is 100%, then fit to window.
 
375
    if (d->zoom == 1.0) 
 
376
    {
 
377
        fitToWindow();
 
378
    }
 
379
    else
 
380
    {
 
381
        setZoomFactor(1.0, true);
 
382
    }
 
383
}
 
384
 
 
385
void PreviewWidget::updateAutoZoom(AutoZoomMode mode)
 
386
{
 
387
    d->zoom       = calcAutoZoomFactor(mode);
 
388
    d->zoomWidth  = (int)(previewWidth()  * d->zoom);
 
389
    d->zoomHeight = (int)(previewHeight() * d->zoom);
 
390
 
 
391
    zoomFactorChanged(d->zoom);
 
392
}
 
393
 
 
394
double PreviewWidget::calcAutoZoomFactor(AutoZoomMode mode)
 
395
{
 
396
    if (previewIsNull()) return d->zoom;
 
397
 
 
398
    double srcWidth  = previewWidth();
 
399
    double srcHeight = previewHeight();
 
400
    double dstWidth  = contentsRect().width();
 
401
    double dstHeight = contentsRect().height();
 
402
 
 
403
    double zoom = QMIN(dstWidth/srcWidth, dstHeight/srcHeight);
 
404
    // limit precision as above
 
405
    zoom = floor(zoom * 10000.0) / 10000.0;
 
406
    if (mode == ZoomInOrOut)
 
407
        // fit to available space, scale up or down
 
408
        return zoom;
 
409
    else
 
410
        // ZoomInOnly: accept that an image is smaller than available space, dont scale up
 
411
        return QMIN(1.0, zoom);
 
412
}
 
413
 
 
414
void PreviewWidget::updateContentsSize()
 
415
{
 
416
    viewport()->setUpdatesEnabled(false);
 
417
 
 
418
    if (visibleWidth() > d->zoomWidth || visibleHeight() > d->zoomHeight)
 
419
    {
 
420
        // Center the image
 
421
        int centerx = contentsRect().width()/2;
 
422
        int centery = contentsRect().height()/2;
 
423
        int xoffset = int(centerx - d->zoomWidth/2);
 
424
        int yoffset = int(centery - d->zoomHeight/2);
 
425
        xoffset     = QMAX(xoffset, 0);
 
426
        yoffset     = QMAX(yoffset, 0);
 
427
 
 
428
        d->pixmapRect = QRect(xoffset, yoffset, d->zoomWidth, d->zoomHeight);
 
429
    }
 
430
    else
 
431
    {
 
432
        d->pixmapRect = QRect(0, 0, d->zoomWidth, d->zoomHeight);
 
433
    }
 
434
 
 
435
    d->tileCache.clear();
 
436
    setContentsSize();
 
437
    viewport()->setUpdatesEnabled(true);
 
438
}
 
439
 
 
440
void PreviewWidget::setContentsSize()
 
441
{
 
442
    resizeContents(d->zoomWidth, d->zoomHeight);
 
443
}
 
444
 
 
445
void PreviewWidget::resizeEvent(QResizeEvent* e)
 
446
{
 
447
    if (!e) return;
 
448
 
 
449
    QScrollView::resizeEvent(e);
 
450
 
 
451
    if (d->autoZoom)
 
452
        updateAutoZoom();
 
453
 
 
454
    updateContentsSize();
 
455
 
 
456
    // No need to repaint. its called   
 
457
    // automatically after resize
 
458
 
 
459
    // To be sure than corner widget used to pan image will be hide/show 
 
460
    // accordinly with resize event.
 
461
    zoomFactorChanged(d->zoom);
 
462
}
 
463
 
 
464
void PreviewWidget::viewportPaintEvent(QPaintEvent *e)
 
465
{
 
466
    QRect er(e->rect());
 
467
    er = QRect(QMAX(er.x()      - 1, 0),
 
468
               QMAX(er.y()      - 1, 0),
 
469
               QMIN(er.width()  + 2, contentsRect().width()),
 
470
               QMIN(er.height() + 2, contentsRect().height()));
 
471
    
 
472
    bool antialias = (d->zoom <= 1.0) ? true : false;
 
473
 
 
474
    QRect o_cr(viewportToContents(er.topLeft()), viewportToContents(er.bottomRight()));
 
475
    QRect cr = o_cr;
 
476
 
 
477
    QRegion clipRegion(er);
 
478
    cr = d->pixmapRect.intersect(cr);
 
479
 
 
480
    if (!cr.isEmpty() && !previewIsNull())
 
481
    {
 
482
        clipRegion -= QRect(contentsToViewport(cr.topLeft()), cr.size());
 
483
 
 
484
        QRect pr = QRect(cr.x() - d->pixmapRect.x(), cr.y() - d->pixmapRect.y(),
 
485
                         cr.width(), cr.height());
 
486
 
 
487
        int x1 = (int)floor((double)pr.x()      / (double)d->tileSize) * d->tileSize;
 
488
        int y1 = (int)floor((double)pr.y()      / (double)d->tileSize) * d->tileSize;
 
489
        int x2 = (int)ceilf((double)pr.right()  / (double)d->tileSize) * d->tileSize;
 
490
        int y2 = (int)ceilf((double)pr.bottom() / (double)d->tileSize) * d->tileSize;
 
491
 
 
492
        QPixmap pix(d->tileSize, d->tileSize);
 
493
        int sx, sy, sw, sh;
 
494
        int step = (int)floor(d->tileSize / d->zoom); 
 
495
 
 
496
        for (int j = y1 ; j < y2 ; j += d->tileSize)
 
497
        {
 
498
            for (int i = x1 ; i < x2 ; i += d->tileSize)
 
499
            {
 
500
                QString key  = QString("%1,%2").arg(i).arg(j);
 
501
                QPixmap *pix = d->tileCache.find(key);
 
502
                
 
503
                if (!pix)
 
504
                {
 
505
                    if (antialias)
 
506
                    {
 
507
                        pix = new QPixmap(d->tileSize, d->tileSize);
 
508
                        d->tileCache.insert(key, pix);
 
509
                    }
 
510
                    else
 
511
                    {
 
512
                        pix = d->tileTmpPix;
 
513
                    }
 
514
 
 
515
                    pix->fill(d->bgColor);
 
516
 
 
517
                    sx = (int)floor((double)i / d->tileSize ) * step;
 
518
                    sy = (int)floor((double)j / d->tileSize ) * step;
 
519
                    sw = step;
 
520
                    sh = step;
 
521
 
 
522
                    paintPreview(pix, sx, sy, sw, sh);
 
523
                }
 
524
 
 
525
                QRect  r(i, j, d->tileSize, d->tileSize);
 
526
                QRect  ir = pr.intersect(r);
 
527
                QPoint pt(contentsToViewport(QPoint(ir.x() + d->pixmapRect.x(),
 
528
                                                    ir.y() + d->pixmapRect.y())));
 
529
 
 
530
                bitBlt(viewport(), pt.x(), pt.y(),
 
531
                       pix,
 
532
                       ir.x()-r.x(), ir.y()-r.y(),
 
533
                       ir.width(), ir.height());
 
534
            }
 
535
        }
 
536
    }
 
537
 
 
538
    QPainter p(viewport());
 
539
    p.setClipRegion(clipRegion);
 
540
    p.fillRect(er, d->bgColor);
 
541
    p.end();
 
542
 
 
543
    viewportPaintExtraData();
 
544
}
 
545
 
 
546
void PreviewWidget::contentsMousePressEvent(QMouseEvent *e)
 
547
{
 
548
    if (!e || e->button() == Qt::RightButton)
 
549
        return;
 
550
 
 
551
    m_movingInProgress = false;
 
552
 
 
553
    if (e->button() == Qt::LeftButton)
 
554
    {
 
555
        emit signalLeftButtonClicked();
 
556
    }
 
557
    else if (e->button() == Qt::MidButton)
 
558
    {
 
559
        if (visibleWidth()  < d->zoomWidth ||
 
560
            visibleHeight() < d->zoomHeight)
 
561
        {
 
562
            m_movingInProgress = true;
 
563
            d->midButtonX      = e->x();
 
564
            d->midButtonY      = e->y();
 
565
            viewport()->repaint(false);
 
566
            viewport()->setCursor(Qt::SizeAllCursor);            
 
567
        }
 
568
        return;
 
569
    }
 
570
    
 
571
    viewport()->setMouseTracking(false);
 
572
}
 
573
 
 
574
void PreviewWidget::contentsMouseMoveEvent(QMouseEvent *e)
 
575
{
 
576
    if (!e) return;
 
577
 
 
578
    if (e->state() & Qt::MidButton)
 
579
    {
 
580
        if (m_movingInProgress)
 
581
        {
 
582
            scrollBy(d->midButtonX - e->x(),
 
583
                     d->midButtonY - e->y());
 
584
            emit signalContentsMovedEvent(false);
 
585
        }
 
586
    }
 
587
}
 
588
    
 
589
void PreviewWidget::contentsMouseReleaseEvent(QMouseEvent *e)
 
590
{
 
591
    if (!e) return;
 
592
 
 
593
    m_movingInProgress = false;
 
594
 
 
595
    if (e->button() == Qt::MidButton)
 
596
    {
 
597
        emit signalContentsMovedEvent(true);
 
598
        viewport()->unsetCursor();
 
599
        viewport()->repaint(false);
 
600
    }
 
601
 
 
602
    if (e->button() == Qt::RightButton)
 
603
    {
 
604
        emit signalRightButtonClicked();
 
605
    }
 
606
}
 
607
 
 
608
void PreviewWidget::contentsWheelEvent(QWheelEvent *e)
 
609
{
 
610
    e->accept();
 
611
 
 
612
    if (e->state() & Qt::ShiftButton)
 
613
    {
 
614
        if (e->delta() < 0)
 
615
            emit signalShowNextImage();
 
616
        else if (e->delta() > 0)
 
617
            emit signalShowPrevImage();
 
618
        return;
 
619
    }
 
620
    else if (e->state() & Qt::ControlButton)
 
621
    {
 
622
        // When zooming with the mouse-wheel, the image center is kept fixed.
 
623
        d->centerZoomPoint = e->pos();
 
624
        if (e->delta() < 0 && !minZoom())
 
625
            slotDecreaseZoom();
 
626
        else if (e->delta() > 0 && !maxZoom())
 
627
            slotIncreaseZoom();
 
628
        d->centerZoomPoint = QPoint();
 
629
        return;
 
630
    }
 
631
 
 
632
    QScrollView::contentsWheelEvent(e);
 
633
}
 
634
 
 
635
void PreviewWidget::zoomFactorChanged(double zoom)
 
636
{
 
637
    emit signalZoomFactorChanged(zoom);
 
638
}
 
639
 
 
640
}  // NameSpace Digikam