File indexing completed on 2024-05-19 16:01:25

0001 /* This file is part of the KDE project
0002    Copyright (C) 2010 KO GmbH <jos.van.den.oever@kogmbh.com>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 #include "slideview.h"
0020 #include "slideloader.h"
0021 #include <QDebug>
0022 #include <QEvent>
0023 #include <QGraphicsItem>
0024 #include <QImage>
0025 #include <QVBoxLayout>
0026 #include <QPixmapCache>
0027 #include <QGraphicsSceneEvent>
0028 #include <QScrollBar>
0029 #include <QTransform>
0030 #include <QWheelEvent>
0031 #include <QGLWidget>
0032 #include <cmath>
0033 
0034 class GraphicsSlideItem : public QGraphicsItem {
0035 public:
0036 #ifdef QT46
0037     class Cache {
0038     private:
0039         QPixmapCache::Key key;
0040     public:
0041         bool find(QPixmap& pixmap) const {
0042             return QPixmapCache::find(key, &pixmap);
0043         }
0044         void clear() const { QPixmapCache::remove(key); }
0045         void add(const QPixmap& pixmap) {
0046             clear();
0047             key = QPixmapCache::insert(pixmap);
0048         }
0049     };
0050 #else
0051     class Cache {
0052     private:
0053         const QString key;
0054     public:
0055         Cache() :key(QString("%1 GraphicsSlideItem").arg((qlonglong)this)) {}
0056         bool find(QPixmap& pixmap) const {
0057             return QPixmapCache::find(key, pixmap);
0058         }
0059         void clear() const { QPixmapCache::remove(key); }
0060         void add(const QPixmap& pixmap) {
0061             clear();
0062             QPixmapCache::insert(key, pixmap);
0063         }
0064     };
0065 #endif
0066     Cache cache;
0067     QRectF rect;
0068     QPainterPath path;
0069     SlideView* const view;
0070     int position;
0071     int slideVersion;
0072 
0073     explicit GraphicsSlideItem(int pos, SlideView* v)
0074             :QGraphicsItem(), view(v), position(pos) {
0075         slideVersion = -1;
0076     }
0077     void setPosition(int pos) {
0078         if (position != pos) {
0079             position = pos;
0080             slideVersion = -1;
0081         }
0082         if (view->loader->slideVersion(position) != slideVersion) {
0083             update(rect);
0084         }
0085     } 
0086     void paint(QPainter* painter, const QStyleOptionGraphicsItem * /*option*/,
0087                QWidget * /*widget*/ = 0) {
0088         QPixmap pixmap;
0089         if (slideVersion != view->loader->slideVersion(position)
0090                 || !cache.find(pixmap)) {
0091             slideVersion = view->loader->slideVersion(position);
0092             pixmap = view->loader->loadSlide(position, rect.size().toSize());
0093             cache.add(pixmap);
0094         }
0095         if (pixmap.isNull()) {
0096             painter->fillRect(rect, Qt::gray);
0097         } else {
0098             painter->drawPixmap(rect.topLeft(), pixmap);
0099         }
0100     }
0101     QRectF boundingRect() const {
0102         return rect;
0103     }
0104     QPainterPath opaqueArea() const {
0105         return path;
0106     }
0107     void setBoundingRect(const QRectF& newrect) {
0108         if (rect != newrect) {
0109             if (rect.size() != newrect.size()) {
0110                 cache.clear();
0111             }
0112             prepareGeometryChange();
0113             rect = newrect;
0114             path = QPainterPath();
0115             path.addRect(rect);
0116         }
0117     }
0118     void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/) {
0119         view->toggleSlideZoom(this);
0120     }
0121 };
0122 
0123 SlideView::SlideView(SlideLoader* l, QWidget* parent) :QWidget(parent),
0124         loader(l), zoomfactor(0.25), sendingChange(false)
0125 {
0126     // use opengl canvas
0127     view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
0128     // listen to resize and wheel events
0129     view.viewport()->installEventFilter(this);
0130 
0131     connect(loader, SIGNAL(slidesChanged()), this, SLOT(slotUpdateSlides()));
0132     connect(view.verticalScrollBar(), SIGNAL(valueChanged(int)),
0133         this, SLOT(slotViewChanged()));
0134     connect(view.horizontalScrollBar(), SIGNAL(valueChanged(int)),
0135         this, SLOT(slotViewChanged()));
0136 
0137     QVBoxLayout *layout = new QVBoxLayout();
0138     layout->setContentsMargins(0, 0, 0, 0);
0139     layout->setSpacing(0);
0140     layout->addWidget(&view);
0141     layout->addWidget(&progressBar);
0142     setLayout(layout);
0143 
0144     progressBar.setVisible(false);
0145     scene.setBackgroundBrush(Qt::Dense4Pattern);
0146     view.setScene(&scene);
0147 
0148     slotUpdateSlides();
0149 }
0150 
0151 void SlideView::slotUpdateSlides() {
0152     int numberOfSlides = loader->numberOfSlides();
0153     QList<QGraphicsItem*> items = scene.items();
0154     if (items.size() > numberOfSlides) {
0155         // remove surplus items
0156         for (int i = numberOfSlides; i < items.size(); ++i) {
0157             scene.removeItem(items[i]);
0158         }
0159     } else if (numberOfSlides > items.size()) {
0160         // add new items
0161         for (int i=items.size(); i<numberOfSlides; ++i) {
0162             GraphicsSlideItem* item = new GraphicsSlideItem(i, this);
0163             item->setVisible(true);
0164             scene.addItem(item);
0165         }
0166     }
0167     items = scene.items();
0168     for (int i=0; i<items.size(); ++i) {
0169         static_cast<GraphicsSlideItem*>(items[i])->setPosition(i);
0170     }
0171     layout();
0172 }
0173 void SlideView::layout() {
0174     const qreal spacing = 2;
0175     QSizeF slidesize = loader->slideSize();
0176     int slidesPerRow = (zoomfactor > 1) ?1 :1/zoomfactor;
0177 
0178     if (zoomfactor <= 1) {
0179         view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0180     } else {
0181         view.setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0182     }
0183     qreal viewwidth = view.viewport()->width();
0184 
0185     qreal x = 0;
0186     qreal y = 0;
0187     qreal screenslidewidth = zoomfactor * viewwidth;
0188 
0189     qreal w, h, viewscale;
0190     if (screenslidewidth > slidesize.width()) {
0191         viewscale = screenslidewidth / slidesize.width();
0192         w = slidesize.width();
0193         h = slidesize.height();
0194     } else {
0195         viewscale = 1;
0196         qreal slidescale = screenslidewidth / slidesize.width();
0197         w = slidesize.width() * slidescale;
0198         h = slidesize.height() * slidescale;
0199     }
0200     QTransform transform;
0201     transform.scale(viewscale, viewscale);
0202     view.setTransform(transform);
0203 
0204     const QList<QGraphicsItem *> items = scene.items();
0205     qreal dx = w + spacing;
0206     qreal dy = h + spacing;
0207     for (int i=0; i < items.size(); ++i) {
0208         GraphicsSlideItem* item = static_cast<GraphicsSlideItem*>(items[i]);
0209         item->setBoundingRect(QRectF(x, y, w, h));
0210         if ((i+1) % slidesPerRow) {
0211             x += dx;
0212         } else {
0213             x = 0;
0214             y += dy;
0215         }
0216     }
0217     if (x == 0) {
0218         scene.setSceneRect(0, 0, slidesPerRow*dx, y);
0219     } else {
0220         scene.setSceneRect(0, 0, slidesPerRow*dx, y + dy);
0221     }
0222 }
0223 bool SlideView::eventFilter(QObject * obj, QEvent *event) {
0224     if (obj != view.viewport()) return false;
0225     if (event->type() == QEvent::Wheel) {
0226         const QWheelEvent* e = static_cast<QWheelEvent*>(event);
0227         if (e->modifiers() == Qt::ControlModifier) {
0228             zoomfactor *= pow(1.1, e->delta()/120.0);
0229             layout();
0230             slotViewChanged();
0231             return true;
0232         }
0233     } else if (event->type() == QEvent::Resize) {
0234         layout();
0235         slotViewChanged();
0236     }
0237     return false;
0238 }
0239 void
0240 SlideView::setView(qreal zoomfactor, int h, int v) {
0241     if (sendingChange) return;
0242     this->zoomfactor = zoomfactor;
0243     if (scene.items().size())
0244     layout();
0245     view.horizontalScrollBar()->setValue(h);
0246     view.verticalScrollBar()->setValue(v);
0247 }
0248 void SlideView::slotViewChanged() {
0249     sendingChange = true;
0250     int h = view.horizontalScrollBar()->value();
0251     int v = view.verticalScrollBar()->value();
0252     emit viewChanged(zoomfactor, h, v);
0253     sendingChange = false;
0254 }
0255 void SlideView::toggleSlideZoom(const GraphicsSlideItem* item) {
0256     // zoom to zoomfactor 1 with clicked slide on the top, unless that slide
0257     // is already active, then zoom to level 0.25 in the range of the clicked
0258     // slide
0259     // this function does not work well, the behavior of the scrollbar is a
0260     // mystery, but the magic dance seems to work ok so far
0261     QScrollBar* sb = view.verticalScrollBar();
0262     int offset = 2;
0263     qreal y = item->boundingRect().top();
0264     qDebug() << y << " " << sb->value();
0265     if (zoomfactor == 1
0266             && (qAbs(y - sb->value()) <= offset+1
0267                 || sb->value()+1 >= sb->maximum())) {
0268         zoomfactor = 0.25;
0269     } else {
0270         zoomfactor = 1;
0271     }
0272     layout();
0273     // magic dance
0274     layout();
0275     view.mapToScene(0, 0).y();
0276     // end of magic dance
0277     y = item->boundingRect().top();
0278     view.verticalScrollBar()->setValue(y - offset); // small offset looks nice
0279 }
0280 void SlideView::SlideGraphicsScene::dragEnterEvent(
0281         QGraphicsSceneDragDropEvent *event)
0282 {
0283     event->ignore();
0284 }