缩率图放大缩小qt

#include "thumbnailview.h"

ThumbnailView::ThumbnailView(Qt::Orientation _orientation, QWidget *parent)
    : QGraphicsView(parent),//成员变量
      blockThumbnailLoading(false),
      mCropThumbnails(false),
      mouseReleaseSelect(false),
      mDrawScrollbarIndicator(true),
      mThumbnailSize(120),
      rangeSelection(false),
      selectMode(ACTIVATE_BY_PRESS),
      lastScrollFrameTime(0),
      scrollTimeLine(nullptr)
{
    setAccessibleName("thumbnailView");//设置可访问名称为"thumbnailView"
    this->setMouseTracking(true);//设置鼠标跟踪为启用
    this->setAcceptDrops(false);
    this->setScene(&scene);//设置场景为scene,表示视图中的图形场景。
    setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);//SmartViewportUpdate,用于智能地更新视口。
    setAttribute(Qt::WA_TranslucentBackground, false);//设置Qt属性Qt::,表示不透明的背景。
    this->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true);//设置优化
    this->setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
    setRenderHint(QPainter::Antialiasing, false);
    setRenderHint(QPainter::SmoothPixmapTransform, false);//禁用平滑像素转换和抗锯齿,这里为什么要禁用这个平滑操作不理解?

    setOrientation(_orientation);//设置缩略图视图的方向

    lastTouchpadScroll.start();//启动lastTouchpadScroll计时器,这个计时器啥作用?

    connect(&loadTimer, &QTimer::timeout, this, &ThumbnailView::loadVisibleThumbnails);//定时器 loadTimer 超时时,将触发 loadVisibleThumbnails
    loadTimer.setInterval(static_cast<const int>(LOAD_DELAY));//超时时间间隔,load是个超参
    loadTimer.setSingleShot(true);//把定时器触发设为单次触发,超时后只触发一次

    qreal screenMaxRefreshRate = 60;//最大屏幕刷新率
    for(auto screen : qApp->screens())//遍历所有的屏幕
        if(screen->refreshRate() > screenMaxRefreshRate)//这里就是要找到最大的屏幕刷新率,但是原理我不太清楚
            screenMaxRefreshRate = screen->refreshRate();
    scrollRefreshRate = 1000 / screenMaxRefreshRate;//鼠标滚轮的当前刷新率由最大屏幕刷新率计算,具体原理搞不懂

    createScrollTimeLine();//这里尤为重要,是在平滑处理这个滑轮的效果

    horizontalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);// 调用horizontalScrollBar获取水平滚动条,set这里设为nocontext就是禁用菜单栏
    horizontalScrollBar()->installEventFilter(this);//这里是搞成this是把他设置为事件过滤器,允许处理水平滚动条相关的事件
    connect(horizontalScrollBar(), &QScrollBar::valueChanged, [this]() {
        loadVisibleThumbnails();//这里不懂滚动条我在软件上看的只有垂直的滚动条,没有水平的
    });
    verticalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);
    verticalScrollBar()->installEventFilter(this);
    connect(verticalScrollBar(), &QScrollBar::valueChanged, [this]() {
        loadVisibleThumbnails();
    });
}

Qt::Orientation ThumbnailView::orientation() {//返回一个水平或者垂直方向
    return mOrientation;
}

void ThumbnailView::setOrientation(Qt::Orientation _orientation) {//就是一个滚动条水平或者垂直的具体设置
    mOrientation = _orientation;
    if(mOrientation == Qt::Horizontal) {
        this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        scrollBar = this->horizontalScrollBar();
        centerOn = [this](int value) {
            QGraphicsView::centerOn(value + 1, viewportCenter.y());
        };
    } else {
        this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        scrollBar = this->verticalScrollBar();
        centerOn = [this](int value) {
            QGraphicsView::centerOn(viewportCenter.x(), value + 1);
        };
    }
    // todo: layout


// from PyQt5.QtCore import Qt
// from PyQt5.QtWidgets import QGraphicsView

// class ThumbnailView(QGraphicsView):
//     def __init__(self):
//         super().__init__()
//         # Initialize other properties and set up the scene
        
//     def setOrientation(self, _orientation):
//         self.mOrientation = _orientation
        
//         if self.mOrientation == Qt.Horizontal:
//             self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
//             self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
//             self.scrollBar = self.horizontalScrollBar()
//             self.centerOn = lambda value: self.centerOn(value + 1, self.viewportCenter().y())
//         else:
//             self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
//             self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
//             self.scrollBar = self.verticalScrollBar()
//             self.centerOn = lambda value: self.centerOn(self.viewportCenter().x(), value + 1
}

void ThumbnailView::hideEvent(QHideEvent *event) {//不懂这里在干什么,里面参数说明是按下shift就变ture
    QGraphicsView::hideEvent(event);
    rangeSelection = false;
}

void ThumbnailView::createScrollTimeLine() {//平滑滚动效果,创建滚动时间线的一个函数
    if(scrollTimeLine) {
        scrollTimeLine->stop();
        scrollTimeLine->deleteLater();
    }
    scrollTimeLine = new QTimeLine(SCROLL_DURATION, this);//是个超参,设为120,滚动的总持续时间?
    scrollTimeLine->setEasingCurve(QEasingCurve::OutSine);//QEasingCurve是一种外正弦缓动曲线,用于实现平滑的滚动效果。
    scrollTimeLine->setUpdateInterval(scrollRefreshRate);//超参,跟新率16?

    connect(scrollTimeLine, &QTimeLine::frameChanged, [this](int value) {
        scrollFrameTimer.start();
        this->centerOn(value);//当调用centerOn函数后,视图会自动调整其位置,使得指定的坐标或图元在视图的中心位置
        // trigger repaint immediately
        qApp->processEvents();//在每个滚动帧更新时,通过强制执行一次事件处理,可以使视图立即重新绘制,并实现平滑滚动的效果。这样可以避免滚动的视图在用户眼中出现明显的卡顿或不流畅的情况,因为事件处理可以立即更新视图的显示,使得滚动效果看起来更加平滑。
        lastScrollFrameTime = scrollFrameTimer.elapsed();
        if(scrollTimeLine->state() == QTimeLine::Running && lastScrollFrameTime > scrollRefreshRate) {//检查当前滚动时间线是否正在运行,并且上一个滚动帧的时间(lastScrollFrameTime)是否超过了预设的滚动刷新率(scrollRefreshRate)。
            //如果上述条件成立,说明上一个滑轮滚动的帧率超过了所设定的刷新率,可能是发生了卡顿,导致帧率更新不及时,所以以下是补救措施
            scrollTimeLine->setPaused(true);//暂停现在的时间线
            int newTime = qMin(scrollTimeLine->duration(), scrollTimeLine->currentTime() + lastScrollFrameTime);//返回一个小于总时间线的值,这个值是当前时间线和上一次时间线的和
            scrollTimeLine->setCurrentTime(newTime);//把这个设置为新的当前时间线
            scrollTimeLine->setPaused(false);//恢复滚动
        }
    });

    connect(scrollTimeLine, &QTimeLine::finished, [this]() {
        blockThumbnailLoading = false;
        loadVisibleThumbnails();//预加载缩略图函数
    });
}

bool ThumbnailView::eventFilter(QObject *o, QEvent *ev) {//鼠标滚轮操作,还有滚动条的绘制操作,在哪调用呢?啥时候调用呢?
    if (o == horizontalScrollBar() || o == verticalScrollBar()) {
        if(ev->type() == QEvent::Wheel) {//判断ev是否是鼠标滑轮事件
            this->wheelEvent(dynamic_cast<QWheelEvent*>(ev));//转化围殴whheeleven类型
            return true;
        } else if(ev->type() == QEvent::Paint && mDrawScrollbarIndicator) {//绘制?md这个参数不懂
            QPainter p(scrollBar);//创建一个滚动条
            p.setOpacity(0.3f);//透明度的值
            p.fillRect(indicator, QBrush(Qt::gray));//创建为灰色
            p.setOpacity(1.0f);
            return false;//但是这里返回false,说明后续还要继续,但是在哪儿目前不知道。。。
        }

    }
    return QObject::eventFilter(o, ev);
}

void ThumbnailView::setDirectoryPath(QString path) {//这是个路径接口
    Q_UNUSED(path)
}

void ThumbnailView::select(QList<int> indices) {//这个应该就是传缩略图进来(包含缩略图索引)
    for(auto i : mSelection)//这里的ms不太清楚是多少?
        thumbnails.at(i)->setHighlighted(false);//这个关高亮设置不太懂
    QList<int>::iterator it = indices.begin();
    while(it != indices.end()) {//检查下符合正常数据吗
        // sanity check
        if(*it < 0 || *it >= itemCount()) {
            it = indices.erase(it);
        } else {
            thumbnails.at(*it)->setHighlighted(true);
            ++it;
        }
    }
    mSelection = indices;
    updateScrollbarIndicator();//没有这个函数?是代码不全吗?应该也不是qt内置函数
}

void ThumbnailView::select(int index) {//指定缩略图索引
    // fallback
    if(!checkRange(index))
        index = 0;
    this->select(QList<int>() << index);
}

void ThumbnailView::deselect(int index) {//删除指定索引的缩略图
    if(!checkRange(index))
            return;
    if(mSelection.count() > 1) {//缩略图数量大于1,不止一张缩略图
        mSelection.removeAll(index);//移除指定的索引,为啥移除?没懂?是因为缩放不在界面所以移除?
        thumbnails.at(index)->setHighlighted(false);//这里完事又是取消高亮操作,依然没懂
    }
}

void ThumbnailView::addSelectionRange(int indexTo) {//这里是在添加新的缩略图进去,大概率是这样,不太确定
    if(!rangeSelectionSnapshot.count() || !selection().count())
        return;
    auto list = rangeSelectionSnapshot;//把range的东西复制给list,这样可以保存原有的东西
    if(indexTo > rangeSelectionSnapshot.last()) {
        for(int i = rangeSelectionSnapshot.last() + 1; i <= indexTo; i++) {
            if(list.contains(i))//这里不太清楚,里面的预测高宽大于真实高宽才返回true,在这里起什么作用呢?删除不合理项?
                list.removeAll(i);
            list << i;
        }
    } else {
        for(int i = rangeSelectionSnapshot.last() - 1; i >= indexTo; i--) {
            if(list.contains(i))
                list.removeAll(i);
            list << i;//将i添加到list里
        }
    }
    select(list);
}

QList<int> ThumbnailView::selection() {//这里返回的ms和select里面的ms是同一个东西?干嘛在这里返回,不懂?
    return mSelection;
}

void ThumbnailView::clearSelection() {//清除已选定的缩略图
    for(auto i : mSelection)
        thumbnails.at(i)->setHighlighted(false);
    mSelection.clear();
}

int ThumbnailView::lastSelected() {//返回最后一个缩略图的索引,在缩略图的尺寸更新中有调用这个函数
    if(!selection().count())
        return -1;
    else
        return selection().last();
}

int ThumbnailView::itemCount() {
    return thumbnails.count();
}

void ThumbnailView::show() {//显示缩略图
    QGraphicsView::show();
    focusOnSelection();//将最后选择的缩略图滚动到可见区域
    loadVisibleThumbnails();//加载
}

void ThumbnailView::showEvent(QShowEvent *event) {//保证显示事件的正确处理
    QGraphicsView::showEvent(event);
    // ensure we are properly resized
    qApp->processEvents();//保证事件的及时处理
    updateScrollbarIndicator();
    loadVisibleThumbnails();
}

void ThumbnailView::populate(int newCount) {
    // wait for possible queued layout events before removing items
    qApp->processEvents();

    clearSelection();
    // reset
    lastScrollDirection = SCROLL_FORWARDS;
    // pause updates until the layout is calculated
    // without this you will see scene moving when scrollbar appears
    this->setUpdatesEnabled(false);
    QElapsedTimer t;
    t.start();
    if(newCount >= 0) {
        /* test this later
        // reuse existing items
        auto currentCount = thumbnails.count();
        if(currentCount > newCount) {
            qDebug() << this << "removing";
            for(auto i = currentCount - 1; i >= newCount; i--) {
                //removeItemFromLayout(i); // slow. is this needed?
                delete thumbnails.takeLast();
            }
            // reset existing
            QList<ThumbnailWidget*>::iterator i;
            for(i = thumbnails.begin(); i != thumbnails.end(); ++i)
                (*i)->reset();
        } else if(currentCount <= newCount) {
            // reset existing
            QList<ThumbnailWidget*>::iterator i;
            for(i = thumbnails.begin(); i != thumbnails.end(); ++i)
                (*i)->reset();
            qDebug() << this << "adding";
            for(auto i = currentCount; i < newCount; i++) {
                ThumbnailWidget *widget = createThumbnailWidget();
                widget->setThumbnailSize(mThumbnailSize);
                thumbnails.append(widget);
                addItemToLayout(widget, i);
            }
        }
        */

        if(newCount == thumbnails.count()) {
            QList<ThumbnailWidget*>::iterator i;
            for (i = thumbnails.begin(); i != thumbnails.end(); ++i) {
                (*i)->reset();
            }
        } else {
            removeAll();
            for(int i = 0; i < newCount; i++) {
                ThumbnailWidget *widget = createThumbnailWidget();
                widget->setThumbnailSize(mThumbnailSize);
                thumbnails.append(widget);
                addItemToLayout(widget, i);
            }
        }
    }
    updateLayout();
    fitSceneToContents();
    resetViewport();
    //qDebug() << "_______POPULATE" << this << t.elapsed();
    // wait for layout before updating
    qApp->processEvents();
    this->setUpdatesEnabled(true);
    loadVisibleThumbnails();
}

void ThumbnailView::addItem() {
    insertItem(thumbnails.count());
}


void ThumbnailView::insertItem(int index) {//插入新的缩略图,里面会涉及到更新布局和更新图片尺寸大小问题
    ThumbnailWidget *widget = createThumbnailWidget();//来了一个新的缩略图,里面包含了展示模式,整体尺寸大小的改变和布局的变化
    thumbnails.insert(index, widget);//添加到thumbnails,这两个index我没太懂什么意思
    addItemToLayout(widget, index);//添加到布局里面
    updateLayout();//前面的create有更新布局的操作了,为什么这里还要更新,而且这个函数还是空的
    fitSceneToContents();//调整试图的宽高以便于符合界面的试图展示

    auto newSelection = mSelection;//把新的缩略图插入
    for(int i=0; i < newSelection.count(); i++) {
        if(index <= newSelection[i])
            newSelection[i]++;
    }
    select(newSelection);//缩略图传入函数

    updateScrollbarIndicator();//这个函数一直没找到,但是都用在了预加载的前面
    loadVisibleThumbnails();
}

void ThumbnailView::removeItem(int index) {
    if(checkRange(index)) {
        auto newSelection = mSelection;
        clearSelection();
        removeItemFromLayout(index);
        delete thumbnails.takeAt(index);
        fitSceneToContents();//调整试图的宽高以便于符合界面的试图展示
        newSelection.removeAll(index);
        for(int i=0; i < newSelection.count(); i++) {
            if(newSelection[i] >= index)
                newSelection[i]--;
        }
        if(!newSelection.count() && itemCount())
            newSelection << ((index >= itemCount()) ? itemCount() - 1 : index);
        select(newSelection);
        updateScrollbarIndicator();
        loadVisibleThumbnails();
    }
}

void ThumbnailView::reloadItem(int index) {
    if(!checkRange(index))
        return;
    auto thumb = thumbnails.at(index);
    if(thumb->isLoaded)
        thumb->unsetThumbnail();
    emit thumbnailsRequested(QList<int>() << index, static_cast<int>(qApp->devicePixelRatio() * mThumbnailSize), mCropThumbnails, true);
}

void ThumbnailView::setDragHover(int index) {

}

void ThumbnailView::setCropThumbnails(bool mode) {
    if(mode != mCropThumbnails) {
        unloadAllThumbnails();
        mCropThumbnails = mode;
        loadVisibleThumbnails();
    }
}

void ThumbnailView::setDrawScrollbarIndicator(bool mode) {
    mDrawScrollbarIndicator = mode;
}

void ThumbnailView::setThumbnail(int pos, std::shared_ptr<Thumbnail> thumb) {
    if(thumb && thumb->size() == floor(mThumbnailSize * qApp->devicePixelRatio()) && checkRange(pos)) {
        thumbnails.at(pos)->setThumbnail(thumb);
    }
}

void ThumbnailView::unloadAllThumbnails() {
    for(int i = 0; i < thumbnails.count(); i++) {
        thumbnails.at(i)->unsetThumbnail();
    }
}

void ThumbnailView::loadVisibleThumbnails() {//加载缩略图
    loadTimer.stop();//暂停定时器,不懂这里为什么要暂停
    if(isVisible() && !blockThumbnailLoading) {//检查
    //mapToScene(),viewport() 是 QGraphicsView 对象的成员函数,用于将视口几何矩形从视图坐标系映射到场景坐标系。geometry() 是获取视口几何矩形的函数,表示视口在视图坐标系中的位置和大小。
    //这里有个概念视口几何矩形就是指用于显示视图内容的矩形区域,它决定了在视图中显示的内容的范围和位置。
        QRectF visRect = mapToScene(viewport()->geometry()).boundingRect();//先用view获取试图几何矩形,然后在map映射到场景坐标系,最后bound计算矩形区域的外接矩形,最后这个外接矩形值赋值给vis
        QRectF offRectBack;//这里设置Back和Front是滚动的优化技术,预加载可能会出现在试图中的内容,避免在需要时才显示出现的卡顿现象。
        QRectF offRectFront;
        if(mOrientation == Qt::Horizontal) {//判定是否是垂直分布
            offRectBack  = QRectF(visRect.left() - offscreenPreloadArea, visRect.top(),//(x,t,width,height)
                                  offscreenPreloadArea, visRect.height());
            offRectFront = QRectF(visRect.right(), visRect.top(),
                                  offscreenPreloadArea, visRect.height());
        } else {
            offRectBack  = QRectF(visRect.left(), visRect.top() - offscreenPreloadArea,
                                  visRect.width(), offscreenPreloadArea);
            offRectFront = QRectF(visRect.left(), visRect.bottom(),
                                  visRect.width(), offscreenPreloadArea);
        }
        QList<QGraphicsItem *>visibleItems;
        if(lastScrollDirection == SCROLL_FORWARDS)//这里主要是对鼠标滑轮向前向后做不同操作
            visibleItems = scene.items(visRect, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder);
        else
            visibleItems = scene.items(visRect, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder);
        visibleItems.append(scene.items(offRectBack,  Qt::IntersectsItemBoundingRect, Qt::DescendingOrder));
        visibleItems.append(scene.items(offRectFront, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder));//列表的连接
        //from PyQt5.QtCore import Qt 这是改写成python的形式
       // from PyQt5.QtWidgets import QGraphicsScene
       //visibleItems = []

      // if lastScrollDirection == SCROLL_FORWARDS:
      //     visibleItems = scene.items(visRect, Qt.IntersectsItemBoundingRect, Qt.AscendingOrder)
     // else:
      //     visibleItems = scene.items(visRect, Qt.IntersectsItemBoundingRect, Qt.DescendingOrder)
    // visibleItems += scene.items(offRectBack,  Qt.IntersectsItemBoundingRect, Qt.DescendingOrder)
    // visibleItems += scene.items(offRectFront, Qt.IntersectsItemBoundingRect, Qt.AscendingOrder)

        // select
        QList<int> loadList;
        for(int i = 0; i < visibleItems.count(); i++) {//循环遍历每个图像项
            ThumbnailWidget* widget = qgraphicsitem_cast<ThumbnailWidget*>(visibleItems.at(i));//强制转换为ThumbnailWidget类
            if(widget && !widget->isLoaded) {//检查转换后的wdiget是否存在并已加载
                int idx = thumbnails.indexOf(widget);//这一行查找 thumbnails 列表中 widget 对象的索引。thumbnails 列表是另一个可能已经存在的列表。
                if(!loadList.contains(idx))//这是一个条件判断,检查 loadList 是否已经包含了当前索引。
                    loadList.append(idx);//如果前面的条件都满足,将当前索引 idx 添加到 loadList 列表中。
            }
        }
// loadList = []

// // for item in visibleItems:
// //     widget = qgraphicsitem_cast[ThumbnailWidget](item)
// //     if widget and not widget.isLoaded:
// //         idx = thumbnails.index(widget)
// //         if idx not in loadList:
// //             loadList.append(idx)


        // load
        if(loadList.count())
            emit thumbnailsRequested(loadList, static_cast<int>(qApp->devicePixelRatio() * mThumbnailSize), mCropThumbnails, false);
    //         if loadList:
    // thumbnailsRequested.emit(loadList, int(qApp.devicePixelRatio() * mThumbnailSize), mCropThumbnails, False)

        // unload offscreen卸载不可见的缩略图
        if(settings->unloadThumbs()) {
            for(int i = 0; i < thumbnails.count(); i++)
                if(!visibleItems.contains(thumbnails.at(i)))
                    thumbnails.at(i)->unsetThumbnail();
        }

    //     if settings.unloadThumbs():
    //          for thumbnail in thumbnails:
    //               if thumbnail not in visibleItems:
    //                   thumbnail.unsetThumbnail()

    }
}

void ThumbnailView::loadVisibleThumbnailsDelayed() {
    loadTimer.stop();
    loadTimer.start();
}

void ThumbnailView::resetViewport() {
    if(scrollTimeLine->state() == QTimeLine::Running)
        scrollTimeLine->stop();
    scrollBar->setValue(0);
}

int ThumbnailView::thumbnailSize() {
    return mThumbnailSize;
}

bool ThumbnailView::atSceneStart() {
    if(mOrientation == Qt::Horizontal) {
        if(viewportTransform().dx() == 0.0)
            return true;
    } else {
        if(viewportTransform().dy() == 0.0)
            return true;
    }
    return false;
}

bool ThumbnailView::atSceneEnd() {
    if(mOrientation == Qt::Horizontal) {
        if(viewportTransform().dx() == viewport()->width() - sceneRect().width())
            return true;
    } else {
        if(viewportTransform().dy() == viewport()->height() - sceneRect().height())
            return true;
    }
    return false;
}

bool ThumbnailView::checkRange(int pos) {//这里是检查载入的缩略图索引是否符合数量上的规定
    return pos >= 0 && pos < thumbnails.count();
}

void ThumbnailView::updateLayout() {

}

// fit scene to it's contents size
void ThumbnailView::fitSceneToContents() {//调整试图的宽高以便于符合界面的试图展示
    QPointF center;
    if(this->mOrientation == Qt::Vertical) {//查看视图是否是垂直分布
        int height = qMax((int)scene.itemsBoundingRect().height(), this->height());//计算边界最大高度
        scene.setSceneRect(QRectF(0,0, this->width(), height));//调整试图的宽高
        center = mapToScene(viewport()->rect().center());
        QGraphicsView::centerOn(0, center.y() + 1);
    } else {
        int width = qMax((int)scene.itemsBoundingRect().width(), this->width());
        scene.setSceneRect(QRectF(0,0, width, this->height()));
        center = mapToScene(viewport()->rect().center());
        QGraphicsView::centerOn(center.x() + 1, 0);
    }
}

//################### scrolling ######################
void ThumbnailView::wheelEvent(QWheelEvent *event) {
    event->accept();
    int pixelDelta = event->pixelDelta().y();//垂直方向上滚动的像素数值
    int angleDelta = event->angleDelta().ry();//滚轮转动角度
    bool isWheel = angleDelta && !(angleDelta % 120) && lastTouchpadScroll.elapsed() > 100;
    if(isWheel) {
        if(!settings->enableSmoothScroll()) {//如果平滑未启用,采用像素或滚轮角度方式处理平滑?
            if(pixelDelta)
                scrollByItem(pixelDelta);
            else if(angleDelta)
                scrollByItem(angleDelta);
        } else if(angleDelta) { // what about pixelDelta?
            scrollSmooth(angleDelta, SCROLL_MULTIPLIER, SCROLL_ACCELERATION, true);
        }
    } else {
        lastTouchpadScroll.restart();
        if(pixelDelta)
            scrollPrecise(pixelDelta);
        else if(angleDelta)
            scrollPrecise(angleDelta);
    }
}

void ThumbnailView::scrollPrecise(int delta) {
    if(delta < 0)
        lastScrollDirection = SCROLL_FORWARDS;
    else
        lastScrollDirection = SCROLL_BACKWARDS;
    viewportCenter = mapToScene(viewport()->rect().center());
    if(scrollTimeLine->state() == QTimeLine::Running) {
        scrollTimeLine->stop();
        blockThumbnailLoading = false;
    }
    // ignore if we reached boundaries
    if( (delta > 0 && atSceneStart()) || (delta < 0 && atSceneEnd()) )
        return;
    // pixel scrolling (precise)
    if(mOrientation == Qt::Horizontal)
        centerOn(static_cast<int>(viewportCenter.x() - delta));
    else
        centerOn(static_cast<int>(viewportCenter.y() - delta));
}

// windows explorer-like behavior
// scrolls exactly by item width / height
void ThumbnailView::scrollByItem(int delta) {
    // do not scroll less than a certain value in px, to avoid feeling unresponsive
    int minScroll = qMin(thumbnailSize() / 2, 100);
    // grab fully visible thumbs
    QRectF visRect = mapToScene(viewport()->geometry()).boundingRect().adjusted(-minScroll,-minScroll,minScroll,minScroll);
    QList<QGraphicsItem *> visibleItems;
    visibleItems = scene.items(visRect, Qt::ContainsItemBoundingRect, Qt::AscendingOrder);
    if(thumbnails.isEmpty() || visibleItems.isEmpty())
        return;
    int target = 0;
    // select scroll target
    if(delta > 0) { // up / left
        ThumbnailWidget* widget = qgraphicsitem_cast<ThumbnailWidget*>(visibleItems.first());
        if(!widget)
            return;
        target = thumbnails.indexOf(widget) - 1;
    } else { // down / right
        ThumbnailWidget* widget = qgraphicsitem_cast<ThumbnailWidget*>(visibleItems.last());
        if(!widget)
            return;
        target = thumbnails.indexOf(widget) + 1;
    }
    scrollToItem(target);
}

void ThumbnailView::scrollToItem(int index) {
    if(!checkRange(index))
        return;
    ThumbnailWidget *item = thumbnails.at(index);
    QRectF sceneRect = mapToScene(viewport()->rect()).boundingRect();
    QRectF itemRect = item->mapRectToScene(item->rect());
    bool visible = sceneRect.contains(itemRect);
    if(!visible) {
        int delta = 0;
        if(mOrientation == Qt::Vertical) {
            if(itemRect.top() >= sceneRect.top()) // UP
                delta = sceneRect.bottom() - itemRect.bottom();
            else // DOWN
                delta = sceneRect.top() - itemRect.top();
        } else {
            if(itemRect.left() >= sceneRect.left()) // LEFT
                delta = sceneRect.right() - itemRect.right();
            else // RIGHT
                delta = sceneRect.left() - itemRect.left();
        }
        if(settings->enableSmoothScroll())
            scrollSmooth(delta);
        else
            scrollPrecise(delta);
    }
}

void ThumbnailView::scrollSmooth(int delta, qreal multiplier, qreal acceleration, bool additive) {
    if(delta < 0)
        lastScrollDirection = SCROLL_FORWARDS;
    else
        lastScrollDirection = SCROLL_BACKWARDS;
    viewportCenter = mapToScene(viewport()->rect().center());
    // ignore if we reached boundaries
    if( (delta > 0 && atSceneStart()) || (delta < 0 && atSceneEnd()) ) {
        return;
    }
    int center;
    if(mOrientation == Qt::Horizontal)
        center = static_cast<int>(viewportCenter.x());
    else
        center = static_cast<int>(viewportCenter.y());
    bool redirect = false, accelerate = false;
    int newEndFrame = center - static_cast<int>(delta * multiplier);
    if( (newEndFrame < center && center < scrollTimeLine->endFrame()) ||
        (newEndFrame > center && center > scrollTimeLine->endFrame()) )
    {
        redirect = true;
    }
    if(scrollTimeLine->state() == QTimeLine::Running || scrollTimeLine->state() == QTimeLine::Paused) {      
        int oldEndFrame = scrollTimeLine->endFrame();
        if(scrollTimeLine->currentTime() < SCROLL_ACCELERATION_THRESHOLD)
            accelerate = true;
        // QTimeLine has this weird issue when it is already finished (at the last frame)
        // but is stuck in the running state. So we just create a new one.
        if(oldEndFrame == center)
            createScrollTimeLine();
        if(!redirect && additive)
            newEndFrame = oldEndFrame - static_cast<int>(delta * multiplier * acceleration);
        // force load thumbs inbetween scroll animations
        blockThumbnailLoading = false;
        loadVisibleThumbnails();
    }
    scrollTimeLine->stop();
    if(accelerate)
        scrollTimeLine->setDuration(SCROLL_DURATION / SCROLL_ACCELERATION);
    else
        scrollTimeLine->setDuration(SCROLL_DURATION);
    blockThumbnailLoading = true;
    scrollTimeLine->setFrameRange(center, newEndFrame);
    scrollTimeLine->start();
}

void ThumbnailView::scrollSmooth(int delta, qreal multiplier, qreal acceleration) {
    scrollSmooth(delta, multiplier, acceleration, false);
}

void ThumbnailView::scrollSmooth(int angleDelta) {
    scrollSmooth(angleDelta, 1.0, 1.0, false);
}

void ThumbnailView::mousePressEvent(QMouseEvent *event) {
    mouseReleaseSelect = false;
    dragStartPos = QPoint(0,0);
    ThumbnailWidget *item = dynamic_cast<ThumbnailWidget*>(itemAt(event->pos()));
    if(item) {
        int index = thumbnails.indexOf(item);
        if(event->button() == Qt::LeftButton) {
            if(event->modifiers() & Qt::ControlModifier) {
                if(!selection().contains(index))
                    select(selection() << index);
                else
                    deselect(index);
            } else if(event->modifiers() & Qt::ShiftModifier) {
                addSelectionRange(index);
            } else if (selection().count() <= 1) {
                if(selectMode == ACTIVATE_BY_PRESS) {
                    emit itemActivated(index);
                    return;
                } else {
                    select(index);
                }
            } else {
                mouseReleaseSelect = true;
            }
            dragStartPos = event->pos();
        } else if(event->button() == Qt::RightButton) { // todo: context menu maybe?
            select(index);
            return;
        }
    }
    QGraphicsView::mousePressEvent(event);
}

void ThumbnailView::mouseMoveEvent(QMouseEvent *event) {
    QGraphicsView::mouseMoveEvent(event);
    if(event->buttons() != Qt::LeftButton || !selection().count())
        return;
    if(QLineF(dragStartPos, event->pos()).length() >= 40) {
        auto *item = dynamic_cast<ThumbnailWidget*>(itemAt(dragStartPos));
        if(item && selection().contains(thumbnails.indexOf(item)))
            emit draggedOut();
    }
}

void ThumbnailView::mouseReleaseEvent(QMouseEvent *event) {
    QGraphicsView::mouseReleaseEvent(event);
    if(mouseReleaseSelect && QLineF(dragStartPos, event->pos()).length() < 40) {
        ThumbnailWidget *item = dynamic_cast<ThumbnailWidget*>(itemAt(event->pos()));
        if(item) {
            int index = thumbnails.indexOf(item);
            select(index);
        }
    }
}

void ThumbnailView::mouseDoubleClickEvent(QMouseEvent *event) {
    if(event->button() == Qt::LeftButton) {
        ThumbnailWidget *item = dynamic_cast<ThumbnailWidget*>(itemAt(event->pos()));
        if(item) {
            emit itemActivated(thumbnails.indexOf(item));
            return;
        }
    }
    event->ignore();
}

void ThumbnailView::focusOutEvent(QFocusEvent *event) {
    QGraphicsView::focusOutEvent(event);
    rangeSelection = false;
}

void ThumbnailView::keyPressEvent(QKeyEvent *event) {
    if(event->key() == Qt::Key_Shift)
        rangeSelectionSnapshot = selection();
    if(event->modifiers() & Qt::ShiftModifier)
        rangeSelection = true;
}

void ThumbnailView::keyReleaseEvent(QKeyEvent *event) {
    if(event->key() == Qt::Key_Shift)
        rangeSelection = false;
}

void ThumbnailView::resizeEvent(QResizeEvent *event) {
    QGraphicsView::resizeEvent(event);
    updateScrollbarIndicator();
}

void ThumbnailView::setSelectMode(ThumbnailSelectMode mode) {
    selectMode = mode;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值