675 lines
17 KiB
C++
675 lines
17 KiB
C++
#include "ImageView.h"
|
||
|
||
#include <QEvent>
|
||
#include <QFont>
|
||
#include <QGestureEvent>
|
||
#include <QGraphicsPixmapItem>
|
||
#include <QGraphicsScene>
|
||
#include <QLabel>
|
||
#include <QMouseEvent>
|
||
#include <QPinchGesture>
|
||
#include <QResizeEvent>
|
||
#include <QTabBar>
|
||
#include <QtWidgets/QScrollBar>
|
||
#include <QStyle>
|
||
#include <QWheelEvent>
|
||
#include <QtMath>
|
||
|
||
namespace
|
||
{
|
||
constexpr double kDefaultMinimumZoom = 0.02;
|
||
constexpr double kDefaultMaximumZoom = 20.0;
|
||
constexpr double kZoomStep = 1.25;
|
||
const QColor kDefaultBackgroundColor(37, 38, 42);
|
||
|
||
// 标签栏:把可用宽度平均分给每个标签页,使标签页铺满控件宽度。
|
||
// QTabBar::expanding 只按各标签的 sizeHint 比例分配,无法保证等宽,故在此重写。
|
||
class EqualWidthTabBar : public QTabBar
|
||
{
|
||
public:
|
||
explicit EqualWidthTabBar(QWidget* parent = nullptr)
|
||
: QTabBar(parent)
|
||
{
|
||
setExpanding(false);
|
||
}
|
||
|
||
protected:
|
||
QSize tabSizeHint(int index) const override
|
||
{
|
||
QSize hint = QTabBar::tabSizeHint(index);
|
||
const int tabCount = count();
|
||
if (tabCount > 0 && width() > 0) {
|
||
hint.setWidth(width() / tabCount);
|
||
}
|
||
return hint;
|
||
}
|
||
};
|
||
}
|
||
|
||
ImageView::ImageView(QWidget* parent)
|
||
: QGraphicsView(parent),
|
||
m_scene(new QGraphicsScene(this)),
|
||
m_pixmapItem(nullptr),
|
||
m_zoomLabel(new QLabel(this)),
|
||
m_tabBar(new EqualWidthTabBar(this)),
|
||
m_currentImageIndex(-1),
|
||
m_tabBarHeight(0),
|
||
m_multiImageMode(false),
|
||
m_zoomRatio(1.0),
|
||
m_minimumZoomRatio(kDefaultMinimumZoom),
|
||
m_maximumZoomRatio(kDefaultMaximumZoom),
|
||
m_imageAlignment(Qt::AlignCenter),
|
||
m_panning(false),
|
||
m_pinching(false),
|
||
m_needsInitialFit(false),
|
||
m_pinchStartZoomRatio(1.0)
|
||
{
|
||
setScene(m_scene);
|
||
setBackgroundBrush(kDefaultBackgroundColor);
|
||
setFrameShape(QFrame::NoFrame);
|
||
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
||
setResizeAnchor(QGraphicsView::AnchorViewCenter);
|
||
setAlignment(m_imageAlignment);
|
||
setDragMode(QGraphicsView::NoDrag);
|
||
setCursor(Qt::OpenHandCursor);
|
||
|
||
// 手势由 QGraphicsView 的视口接收,支持触摸屏双指捏合缩放。
|
||
viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
|
||
viewport()->grabGesture(Qt::PinchGesture);
|
||
|
||
m_zoomLabel->setObjectName(QStringLiteral("imageViewZoomLabel"));
|
||
m_zoomLabel->setAlignment(Qt::AlignCenter);
|
||
m_zoomLabel->setMinimumWidth(58);
|
||
m_zoomLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||
m_zoomLabel->setStyleSheet(QStringLiteral(
|
||
"QLabel { color: white; background: transparent; border: none; }"));
|
||
updateZoomLabel();
|
||
|
||
// 标签栏固定贴在控件顶部,多图模式下才显示。
|
||
m_tabBar->setObjectName(QStringLiteral("imageViewTabBar"));
|
||
m_tabBar->setDrawBase(false);
|
||
m_tabBar->setExpanding(false);
|
||
m_tabBar->setMovable(false);
|
||
m_tabBar->setUsesScrollButtons(false);
|
||
m_tabBar->setFocusPolicy(Qt::NoFocus);
|
||
m_tabBar->setStyleSheet(QStringLiteral(
|
||
"QTabBar::tab { background: #25262A; color: #EFF1F5; padding: 6px 18px;"
|
||
" border: 1px solid #4A4C52; border-bottom: none; }"
|
||
"QTabBar::tab:selected { background: #3A3C42; }"
|
||
"QTabBar::tab:hover { background: #31333A; }"));
|
||
|
||
// 标签字体在控件默认字体基础上放大一倍。
|
||
QFont tabFont = m_tabBar->font();
|
||
if (tabFont.pointSizeF() > 0.0) {
|
||
tabFont.setPointSizeF(tabFont.pointSizeF() * 2.0);
|
||
} else if (tabFont.pixelSize() > 0) {
|
||
tabFont.setPixelSize(tabFont.pixelSize() * 2);
|
||
}
|
||
m_tabBar->setFont(tabFont);
|
||
|
||
m_tabBar->hide();
|
||
connect(m_tabBar, &QTabBar::currentChanged, this, [this](int index) {
|
||
showImageAt(index);
|
||
});
|
||
}
|
||
|
||
ImageView::~ImageView() = default;
|
||
|
||
void ImageView::setImage(const QImage& image)
|
||
{
|
||
if (image.isNull()) {
|
||
clearImage();
|
||
return;
|
||
}
|
||
|
||
m_multiImageMode = false;
|
||
m_images.clear();
|
||
m_currentImageIndex = -1;
|
||
refreshTabBar();
|
||
updatePixmap(image);
|
||
}
|
||
|
||
void ImageView::setImage(const QPixmap& pixmap)
|
||
{
|
||
setImage(pixmap.toImage());
|
||
}
|
||
|
||
bool ImageView::loadImage(const QString& filePath)
|
||
{
|
||
QImage loaded;
|
||
if (!loaded.load(filePath)) {
|
||
return false;
|
||
}
|
||
setImage(loaded);
|
||
return true;
|
||
}
|
||
|
||
void ImageView::clearImage()
|
||
{
|
||
m_multiImageMode = false;
|
||
m_images.clear();
|
||
m_currentImageIndex = -1;
|
||
refreshTabBar();
|
||
resetScene();
|
||
}
|
||
|
||
void ImageView::setImages(const QVector<NamedImage>& images)
|
||
{
|
||
// 图片集合刷新后尽量停留在同一名字的标签页上。
|
||
const QString previousName = (m_currentImageIndex >= 0 && m_currentImageIndex < m_images.size())
|
||
? m_images.at(m_currentImageIndex).first
|
||
: QString();
|
||
|
||
m_images = images;
|
||
m_multiImageMode = true;
|
||
m_currentImageIndex = -1;
|
||
|
||
if (!m_images.isEmpty()) {
|
||
int index = 0;
|
||
for (int i = 0; i < m_images.size(); ++i) {
|
||
if (!previousName.isEmpty() && m_images.at(i).first == previousName) {
|
||
index = i;
|
||
break;
|
||
}
|
||
}
|
||
// 停留在无图页上时改显示第一张有图的页,避免切换后画面空白。
|
||
if (m_images.at(index).second.isNull()) {
|
||
for (int i = 0; i < m_images.size(); ++i) {
|
||
if (!m_images.at(i).second.isNull()) {
|
||
index = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
m_currentImageIndex = index;
|
||
}
|
||
|
||
refreshTabBar();
|
||
if (m_currentImageIndex >= 0) {
|
||
showImageAt(m_currentImageIndex);
|
||
} else {
|
||
resetScene();
|
||
}
|
||
}
|
||
|
||
void ImageView::setNamedImage(const QString& name, const QImage& image)
|
||
{
|
||
QVector<NamedImage> images = m_images;
|
||
bool replaced = false;
|
||
for (int i = 0; i < images.size(); ++i) {
|
||
if (images.at(i).first == name) {
|
||
images[i].second = image;
|
||
replaced = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!replaced) {
|
||
images.append(NamedImage(name, image));
|
||
}
|
||
setImages(images);
|
||
}
|
||
|
||
void ImageView::clearImages()
|
||
{
|
||
clearImage();
|
||
}
|
||
|
||
bool ImageView::isMultiImageMode() const
|
||
{
|
||
return m_multiImageMode;
|
||
}
|
||
|
||
int ImageView::imageCount() const
|
||
{
|
||
return m_multiImageMode ? m_images.size() : (hasImage() ? 1 : 0);
|
||
}
|
||
|
||
QString ImageView::imageName(int index) const
|
||
{
|
||
if (!m_multiImageMode || index < 0 || index >= m_images.size()) {
|
||
return QString();
|
||
}
|
||
return m_images.at(index).first;
|
||
}
|
||
|
||
QImage ImageView::namedImage(const QString& name) const
|
||
{
|
||
if (!m_multiImageMode) {
|
||
return QString() == name ? m_image : QImage();
|
||
}
|
||
for (const NamedImage& image : m_images) {
|
||
if (image.first == name) {
|
||
return image.second;
|
||
}
|
||
}
|
||
return QImage();
|
||
}
|
||
|
||
int ImageView::currentImageIndex() const
|
||
{
|
||
return m_multiImageMode ? m_currentImageIndex : (hasImage() ? 0 : -1);
|
||
}
|
||
|
||
void ImageView::setCurrentImageIndex(int index)
|
||
{
|
||
if (!m_multiImageMode) {
|
||
return;
|
||
}
|
||
showImageAt(index);
|
||
}
|
||
|
||
bool ImageView::hasImage() const
|
||
{
|
||
return !m_image.isNull();
|
||
}
|
||
|
||
QImage ImageView::image() const
|
||
{
|
||
return m_image;
|
||
}
|
||
|
||
double ImageView::zoomRatio() const
|
||
{
|
||
return m_zoomRatio;
|
||
}
|
||
|
||
void ImageView::setZoomRatio(double ratio)
|
||
{
|
||
updateZoomRatio(ratio, true);
|
||
}
|
||
|
||
void ImageView::zoomIn(double factor)
|
||
{
|
||
if (factor <= 1.0) {
|
||
factor = kZoomStep;
|
||
}
|
||
updateZoomRatio(m_zoomRatio * factor, true);
|
||
}
|
||
|
||
void ImageView::zoomOut(double factor)
|
||
{
|
||
if (factor <= 1.0) {
|
||
factor = kZoomStep;
|
||
}
|
||
updateZoomRatio(m_zoomRatio / factor, true);
|
||
}
|
||
|
||
void ImageView::resetView()
|
||
{
|
||
if (!hasImage() || !m_pixmapItem) {
|
||
return;
|
||
}
|
||
|
||
const double previousRatio = m_zoomRatio;
|
||
resetTransform();
|
||
fitInView(m_pixmapItem, Qt::KeepAspectRatio);
|
||
|
||
const double fittedRatio = qBound(m_minimumZoomRatio,
|
||
transform().m11(),
|
||
m_maximumZoomRatio);
|
||
resetTransform();
|
||
scale(fittedRatio, fittedRatio);
|
||
m_zoomRatio = fittedRatio;
|
||
centerOn(m_pixmapItem);
|
||
updateZoomLabel();
|
||
|
||
if (!qFuzzyCompare(previousRatio, m_zoomRatio)) {
|
||
emit zoomRatioChanged(m_zoomRatio);
|
||
}
|
||
}
|
||
|
||
void ImageView::setZoomRange(double minimum, double maximum)
|
||
{
|
||
if (minimum <= 0.0 || maximum < minimum) {
|
||
return;
|
||
}
|
||
m_minimumZoomRatio = minimum;
|
||
m_maximumZoomRatio = maximum;
|
||
updateZoomRatio(m_zoomRatio, true);
|
||
}
|
||
|
||
double ImageView::minimumZoomRatio() const
|
||
{
|
||
return m_minimumZoomRatio;
|
||
}
|
||
|
||
double ImageView::maximumZoomRatio() const
|
||
{
|
||
return m_maximumZoomRatio;
|
||
}
|
||
|
||
void ImageView::setImageAlignment(Qt::Alignment alignment)
|
||
{
|
||
if (!alignment) {
|
||
alignment = Qt::AlignCenter;
|
||
}
|
||
m_imageAlignment = alignment;
|
||
setAlignment(m_imageAlignment);
|
||
if (hasImage()) {
|
||
viewport()->update();
|
||
}
|
||
}
|
||
|
||
Qt::Alignment ImageView::imageAlignment() const
|
||
{
|
||
return m_imageAlignment;
|
||
}
|
||
|
||
void ImageView::updatePixmap(const QImage& image)
|
||
{
|
||
if (image.isNull()) {
|
||
resetScene();
|
||
return;
|
||
}
|
||
|
||
m_image = image;
|
||
if (!m_pixmapItem) {
|
||
m_pixmapItem = m_scene->addPixmap(QPixmap::fromImage(m_image));
|
||
} else {
|
||
m_pixmapItem->setPixmap(QPixmap::fromImage(m_image));
|
||
}
|
||
m_scene->setSceneRect(QRectF(QPointF(0.0, 0.0), QSizeF(m_image.size())));
|
||
m_needsInitialFit = true;
|
||
if (viewport()->width() > 0 && viewport()->height() > 0) {
|
||
m_needsInitialFit = false;
|
||
resetView();
|
||
}
|
||
emit imageChanged();
|
||
}
|
||
|
||
void ImageView::resetScene()
|
||
{
|
||
m_image = QImage();
|
||
m_scene->clear();
|
||
m_pixmapItem = nullptr;
|
||
m_scene->setSceneRect(QRectF());
|
||
m_zoomRatio = 1.0;
|
||
m_needsInitialFit = false;
|
||
updateZoomLabel();
|
||
viewport()->update();
|
||
emit imageChanged();
|
||
}
|
||
|
||
void ImageView::refreshTabBar()
|
||
{
|
||
if (!m_tabBar) {
|
||
return;
|
||
}
|
||
|
||
m_tabBar->blockSignals(true);
|
||
while (m_tabBar->count() > m_images.size()) {
|
||
m_tabBar->removeTab(m_tabBar->count() - 1);
|
||
}
|
||
for (int i = 0; i < m_images.size(); ++i) {
|
||
if (i < m_tabBar->count()) {
|
||
m_tabBar->setTabText(i, m_images.at(i).first);
|
||
} else {
|
||
m_tabBar->addTab(m_images.at(i).first);
|
||
}
|
||
}
|
||
if (m_currentImageIndex >= 0 && m_currentImageIndex < m_tabBar->count()) {
|
||
m_tabBar->setCurrentIndex(m_currentImageIndex);
|
||
}
|
||
// 是否显示标签栏由多图模式和图组内容决定,而不是 isVisible();
|
||
// 这样窗口尚未显示时也能正确预留视口边距。
|
||
const bool showTabBar = m_multiImageMode && !m_images.isEmpty();
|
||
m_tabBar->setVisible(showTabBar);
|
||
m_tabBar->blockSignals(false);
|
||
|
||
const int topMargin = showTabBar ? m_tabBar->sizeHint().height() : 0;
|
||
if (topMargin != m_tabBarHeight) {
|
||
m_tabBarHeight = topMargin;
|
||
// 视口让出标签栏高度,图片显示在标签栏下方。
|
||
setViewportMargins(0, m_tabBarHeight, 0, 0);
|
||
}
|
||
if (showTabBar) {
|
||
m_tabBar->setGeometry(0, 0, width(), m_tabBarHeight);
|
||
m_tabBar->raise();
|
||
}
|
||
}
|
||
|
||
void ImageView::showImageAt(int index)
|
||
{
|
||
if (!m_multiImageMode || index < 0 || index >= m_images.size()) {
|
||
return;
|
||
}
|
||
|
||
if (m_tabBar && m_tabBar->currentIndex() != index) {
|
||
m_tabBar->blockSignals(true);
|
||
m_tabBar->setCurrentIndex(index);
|
||
m_tabBar->blockSignals(false);
|
||
}
|
||
|
||
const bool indexChanged = (m_currentImageIndex != index);
|
||
m_currentImageIndex = index;
|
||
updatePixmap(m_images.at(index).second);
|
||
if (indexChanged) {
|
||
emit currentImageIndexChanged(index);
|
||
}
|
||
}
|
||
|
||
bool ImageView::viewportEvent(QEvent* event)
|
||
{
|
||
if (event->type() == QEvent::Gesture)
|
||
{
|
||
return handleGestureEvent(static_cast<QGestureEvent*>(event));
|
||
}
|
||
return QGraphicsView::viewportEvent(event);
|
||
}
|
||
|
||
bool ImageView::handleGestureEvent(QGestureEvent* event)
|
||
{
|
||
QGesture* gesture = event->gesture(Qt::PinchGesture);
|
||
if (!gesture)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
QPinchGesture* pinchGesture = static_cast<QPinchGesture*>(gesture);
|
||
handlePinchGesture(pinchGesture);
|
||
event->accept(pinchGesture);
|
||
return true;
|
||
}
|
||
|
||
void ImageView::handlePinchGesture(QPinchGesture* gesture)
|
||
{
|
||
if (!gesture)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (gesture->state() == Qt::GestureStarted)
|
||
{
|
||
// 双指手势开始后停止单指模拟产生的鼠标拖拽。
|
||
m_pinching = true;
|
||
m_panning = false;
|
||
m_pinchStartZoomRatio = m_zoomRatio;
|
||
setCursor(Qt::OpenHandCursor);
|
||
}
|
||
|
||
if (hasImage() &&
|
||
(gesture->state() == Qt::GestureStarted || gesture->state() == Qt::GestureUpdated))
|
||
{
|
||
const double scaleFactor = gesture->totalScaleFactor();
|
||
if (qIsFinite(scaleFactor) && scaleFactor > 0.0)
|
||
{
|
||
// 使用累计缩放比例,避免连续事件带来的比例漂移。
|
||
updateZoomRatioAt(m_pinchStartZoomRatio * scaleFactor,
|
||
gesture->centerPoint().toPoint());
|
||
}
|
||
}
|
||
|
||
if (gesture->state() == Qt::GestureFinished ||
|
||
gesture->state() == Qt::GestureCanceled)
|
||
{
|
||
m_pinching = false;
|
||
setCursor(Qt::OpenHandCursor);
|
||
}
|
||
}
|
||
|
||
void ImageView::resizeEvent(QResizeEvent* event)
|
||
{
|
||
QGraphicsView::resizeEvent(event);
|
||
if (m_tabBar && m_tabBarHeight > 0) {
|
||
m_tabBar->setGeometry(0, 0, width(), m_tabBarHeight);
|
||
m_tabBar->raise();
|
||
}
|
||
if (m_needsInitialFit && hasImage() && viewport()->width() > 0 && viewport()->height() > 0) {
|
||
m_needsInitialFit = false;
|
||
resetView();
|
||
}
|
||
repositionZoomLabel();
|
||
}
|
||
|
||
void ImageView::wheelEvent(QWheelEvent* event)
|
||
{
|
||
if (!hasImage() || event->angleDelta().y() == 0) {
|
||
event->ignore();
|
||
return;
|
||
}
|
||
|
||
const double factor = event->angleDelta().y() > 0 ? kZoomStep : 1.0 / kZoomStep;
|
||
updateZoomRatio(m_zoomRatio * factor, true);
|
||
event->accept();
|
||
}
|
||
|
||
void ImageView::mousePressEvent(QMouseEvent* event)
|
||
{
|
||
if (m_pinching)
|
||
{
|
||
event->accept();
|
||
return;
|
||
}
|
||
if (event->button() == Qt::LeftButton && hasImage()) {
|
||
m_panning = true;
|
||
m_lastMousePosition = event->pos();
|
||
setCursor(Qt::ClosedHandCursor);
|
||
event->accept();
|
||
return;
|
||
}
|
||
QGraphicsView::mousePressEvent(event);
|
||
}
|
||
|
||
void ImageView::mouseMoveEvent(QMouseEvent* event)
|
||
{
|
||
if (m_pinching)
|
||
{
|
||
event->accept();
|
||
return;
|
||
}
|
||
if (m_panning) {
|
||
const QPoint delta = event->pos() - m_lastMousePosition;
|
||
m_lastMousePosition = event->pos();
|
||
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x());
|
||
verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y());
|
||
event->accept();
|
||
return;
|
||
}
|
||
QGraphicsView::mouseMoveEvent(event);
|
||
}
|
||
|
||
void ImageView::mouseReleaseEvent(QMouseEvent* event)
|
||
{
|
||
if (m_pinching)
|
||
{
|
||
event->accept();
|
||
return;
|
||
}
|
||
if (event->button() == Qt::LeftButton && m_panning) {
|
||
m_panning = false;
|
||
setCursor(Qt::OpenHandCursor);
|
||
event->accept();
|
||
return;
|
||
}
|
||
QGraphicsView::mouseReleaseEvent(event);
|
||
}
|
||
|
||
void ImageView::mouseDoubleClickEvent(QMouseEvent* event)
|
||
{
|
||
if (event->button() == Qt::LeftButton && hasImage()) {
|
||
resetView();
|
||
event->accept();
|
||
return;
|
||
}
|
||
QGraphicsView::mouseDoubleClickEvent(event);
|
||
}
|
||
|
||
void ImageView::updateZoomLabel()
|
||
{
|
||
if (!hasImage()) {
|
||
m_zoomLabel->setText(QStringLiteral("--"));
|
||
} else {
|
||
m_zoomLabel->setText(QStringLiteral("%1%")
|
||
.arg(m_zoomRatio * 100.0, 0, 'f', 1));
|
||
}
|
||
m_zoomLabel->adjustSize();
|
||
repositionZoomLabel();
|
||
}
|
||
|
||
void ImageView::updateZoomRatio(double ratio, bool keepCenter)
|
||
{
|
||
if (!hasImage()) {
|
||
return;
|
||
}
|
||
|
||
ratio = qBound(m_minimumZoomRatio, ratio, m_maximumZoomRatio);
|
||
if (qFuzzyCompare(ratio, m_zoomRatio) && !transform().isIdentity()) {
|
||
return;
|
||
}
|
||
|
||
const QPoint viewportCenter = viewport()->rect().center();
|
||
const QPointF sceneCenter = mapToScene(viewportCenter);
|
||
resetTransform();
|
||
scale(ratio, ratio);
|
||
m_zoomRatio = ratio;
|
||
if (keepCenter) {
|
||
centerOn(sceneCenter);
|
||
}
|
||
updateZoomLabel();
|
||
emit zoomRatioChanged(m_zoomRatio);
|
||
}
|
||
|
||
void ImageView::updateZoomRatioAt(double ratio, const QPoint& viewportAnchor)
|
||
{
|
||
if (!hasImage())
|
||
{
|
||
return;
|
||
}
|
||
|
||
ratio = qBound(m_minimumZoomRatio, ratio, m_maximumZoomRatio);
|
||
if (qFuzzyCompare(ratio, m_zoomRatio) && !transform().isIdentity())
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 缩放前后保持双指中心对应同一个图像位置。
|
||
const QPointF sceneAnchor = mapToScene(viewportAnchor);
|
||
resetTransform();
|
||
scale(ratio, ratio);
|
||
m_zoomRatio = ratio;
|
||
const QPointF mappedAnchor = mapFromScene(sceneAnchor);
|
||
const QPointF viewportDelta = mappedAnchor - viewportAnchor;
|
||
horizontalScrollBar()->setValue(
|
||
horizontalScrollBar()->value() + qRound(viewportDelta.x()));
|
||
verticalScrollBar()->setValue(
|
||
verticalScrollBar()->value() + qRound(viewportDelta.y()));
|
||
updateZoomLabel();
|
||
emit zoomRatioChanged(m_zoomRatio);
|
||
}
|
||
|
||
void ImageView::repositionZoomLabel()
|
||
{
|
||
if (!m_zoomLabel) {
|
||
return;
|
||
}
|
||
const int margin = 8;
|
||
const int scrollBarHeight = horizontalScrollBar()->isVisible()
|
||
? horizontalScrollBar()->height()
|
||
: 0;
|
||
m_zoomLabel->move(margin,
|
||
height() - m_zoomLabel->height() - margin - scrollBarHeight);
|
||
m_zoomLabel->raise();
|
||
}
|