File indexing completed on 2024-04-28 04:30:20

0001 /*
0002     SPDX-FileCopyrightText: 2000 Till Krech <till@snafu.de>
0003     SPDX-FileCopyrightText: 2008 Mathias Soeken <msoeken@informatik.uni-bremen.de>
0004     SPDX-FileCopyrightText: 2017 Aurélien Gâteau <agateau@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "klineal.h"
0010 
0011 #include <QAction>
0012 #include <QApplication>
0013 #include <QBrush>
0014 #include <QInputDialog>
0015 #include <QMenu>
0016 #include <QMouseEvent>
0017 #include <QPainter>
0018 #include <QScreen>
0019 #include <QSlider>
0020 #include <QWidgetAction>
0021 #include <QWindow>
0022 
0023 #include <KAboutData>
0024 #include <KActionCollection>
0025 #include <KConfig>
0026 #include <KConfigDialog>
0027 #include <KHelpClient>
0028 #include <KHelpMenu>
0029 #include <KLocalizedString>
0030 #include <KNotification>
0031 #include <KShortcutsDialog>
0032 #include <KStandardAction>
0033 #include <KWindowSystem>
0034 
0035 #include "krulerconfig.h"
0036 
0037 #ifdef KRULER_HAVE_X11
0038 #include <KX11Extras>
0039 #include <netwm.h>
0040 #include <private/qtx11extras_p.h>
0041 #endif
0042 
0043 #include "kruler.h"
0044 #include "krulersystemtray.h"
0045 
0046 #include "ui_cfg_advanced.h"
0047 #include "ui_cfg_appearance.h"
0048 
0049 static const int RESIZE_HANDLE_LENGTH = 7;
0050 static const int RESIZE_HANDLE_THICKNESS = 24;
0051 static const qreal RESIZE_HANDLE_OPACITY = 0.3;
0052 
0053 static const int SMALL_TICK_SIZE = 6;
0054 static const int MEDIUM1_TICK_SIZE = 10;
0055 static const int MEDIUM2_TICK_SIZE = 15;
0056 static const int LARGE_TICK_SIZE = 18;
0057 static const qreal TICK_OPACITY = 0.3;
0058 
0059 static const int THICKNESS = 70;
0060 static const int RULER_MAX_LENGTH = 10000;
0061 
0062 static const qreal OVERLAY_OPACITY = 0.1;
0063 static const qreal OVERLAY_BORDER_OPACITY = 0.3;
0064 
0065 static const int INDICATOR_MARGIN = 6;
0066 static const int INDICATOR_RECT_RADIUS = 3;
0067 static const qreal INDICATOR_RECT_OPACITY = 0.6;
0068 
0069 static const int CURSOR_SIZE = 15; // Must be an odd number
0070 
0071 /**
0072  * create the thingy with no borders and set up
0073  * its members
0074  */
0075 KLineal::KLineal(QWidget *parent)
0076     : QWidget(parent)
0077 {
0078     setAttribute(Qt::WA_TranslucentBackground);
0079 #ifdef KRULER_HAVE_X11
0080     if (KWindowSystem::isPlatformX11()) {
0081         KX11Extras::setType(winId(), NET::Override); // or NET::Normal
0082     }
0083 #endif
0084 
0085     setWindowTitle(i18nc("@title:window", "KRuler"));
0086 
0087     setWhatsThis(
0088         i18n("This is a tool to measure pixel distances on the screen. "
0089              "It is useful for working on layouts of dialogs, web pages etc."));
0090     setMouseTracking(true);
0091 
0092     mColor = RulerSettings::self()->bgColor();
0093     mScaleFont = RulerSettings::self()->scaleFont();
0094     int restoreLength = RulerSettings::self()->length();
0095     mHorizontal = RulerSettings::self()->horizontal();
0096     mLeftToRight = RulerSettings::self()->leftToRight();
0097     mOffset = RulerSettings::self()->offset();
0098     mRelativeScale = RulerSettings::self()->relativeScale();
0099     mAlwaysOnTopLayer = RulerSettings::self()->alwaysOnTop();
0100 
0101     createCrossCursor();
0102 
0103     // BEGIN setup menu and actions
0104     mActionCollection = new KActionCollection(this);
0105     mActionCollection->setConfigGroup(QStringLiteral("Actions"));
0106 
0107     mMenu = new QMenu(this);
0108     addAction(mMenu, QIcon::fromTheme(QStringLiteral("object-rotate-left")), i18n("Rotate"), this, SLOT(rotate()), Qt::Key_R, QStringLiteral("turn_right"));
0109 
0110     QMenu *scaleMenu = new QMenu(i18n("&Scale"), this);
0111     mScaleDirectionAction = addAction(scaleMenu, QIcon(), i18n("Right to Left"), this, SLOT(switchDirection()), Qt::Key_D, QStringLiteral("right_to_left"));
0112     mCenterOriginAction = addAction(scaleMenu, QIcon(), i18n("Center Origin"), this, SLOT(centerOrigin()), Qt::Key_C, QStringLiteral("center_origin"));
0113     mCenterOriginAction->setEnabled(!mRelativeScale);
0114     mOffsetAction = addAction(scaleMenu, QIcon(), i18n("Offset..."), this, SLOT(slotOffset()), Qt::Key_O, QStringLiteral("set_offset"));
0115     mOffsetAction->setEnabled(!mRelativeScale);
0116     scaleMenu->addSeparator();
0117     QAction *relativeScaleAction = addAction(scaleMenu, QIcon(), i18n("Percentage"), nullptr, nullptr, QKeySequence(), QStringLiteral("toggle_percentage"));
0118     relativeScaleAction->setCheckable(true);
0119     relativeScaleAction->setChecked(mRelativeScale);
0120     connect(relativeScaleAction, &QAction::toggled, this, &KLineal::switchRelativeScale);
0121     mMenu->addMenu(scaleMenu);
0122 
0123     mOpacity = RulerSettings::self()->opacity();
0124     QMenu *opacityMenu = new QMenu(i18n("O&pacity"), this);
0125     QWidgetAction *opacityAction = new QWidgetAction(this);
0126     QSlider *slider = new QSlider(this);
0127     slider->setMinimum(15);
0128     slider->setMaximum(255);
0129     slider->setSingleStep(1);
0130     slider->setOrientation(Qt::Horizontal);
0131     slider->setValue(RulerSettings::self()->opacity());
0132     // Show ticks so that the slider is a bit taller, thus easier to grab
0133     slider->setTickPosition(QSlider::TicksBothSides);
0134     slider->setTickInterval(30);
0135     slider->setMinimumWidth(200);
0136     connect(slider, &QSlider::valueChanged, this, &KLineal::slotOpacity);
0137     opacityAction->setDefaultWidget(slider);
0138     opacityMenu->addAction(opacityAction);
0139     mMenu->addMenu(opacityMenu);
0140 
0141     QAction *keyBindings = KStandardAction::keyBindings(this, &KLineal::slotKeyBindings, mActionCollection);
0142     mMenu->addAction(keyBindings);
0143     QAction *preferences = KStandardAction::preferences(this, &KLineal::slotPreferences, mActionCollection);
0144     mMenu->addAction(preferences);
0145     mMenu->addSeparator();
0146     mMenu->addMenu((new KHelpMenu(this, KAboutData::applicationData(), true))->menu());
0147     mMenu->addSeparator();
0148     if (RulerSettings::self()->trayIcon()) {
0149         createSystemTray();
0150     }
0151 
0152     QAction *quit = KStandardAction::quit(qApp, &QGuiApplication::quit, mActionCollection);
0153     mMenu->addAction(quit);
0154 
0155     mActionCollection->associateWidget(this);
0156     mActionCollection->readSettings();
0157 
0158     mLastClickGlobalPos = geometry().topLeft() + QPoint(width() / 2, height() / 2);
0159 
0160     setWindowFlags(mAlwaysOnTopLayer ? Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint : Qt::FramelessWindowHint);
0161 
0162     QSize restoreSize(restoreLength, THICKNESS);
0163     resize(mHorizontal ? restoreSize : restoreSize.transposed());
0164     setMinimumSize(THICKNESS, THICKNESS);
0165     setMaximumSize(RULER_MAX_LENGTH, RULER_MAX_LENGTH);
0166     setHorizontal(mHorizontal);
0167 }
0168 
0169 KLineal::~KLineal()
0170 {
0171     saveSettings();
0172     delete mTrayIcon;
0173 }
0174 
0175 void KLineal::createCrossCursor()
0176 {
0177     QPixmap pix(CURSOR_SIZE, CURSOR_SIZE);
0178     int halfSize = CURSOR_SIZE / 2;
0179     {
0180         pix.fill(Qt::transparent);
0181         QPainter painter(&pix);
0182         painter.setPen(Qt::red);
0183         painter.drawLine(0, halfSize, CURSOR_SIZE - 1, halfSize);
0184         painter.drawLine(halfSize, 0, halfSize, CURSOR_SIZE - 1);
0185     }
0186     mCrossCursor = QCursor(pix, halfSize, halfSize);
0187 }
0188 
0189 void KLineal::createSystemTray()
0190 {
0191     mCloseAction = mActionCollection->addAction(KStandardAction::Close, this, SLOT(slotClose()));
0192     mMenu->addAction(mCloseAction);
0193 
0194     if (!mTrayIcon) {
0195         mTrayIcon = new KRulerSystemTray(QStringLiteral("kruler-symbolic"), this, mActionCollection);
0196         mTrayIcon->setCategory(KStatusNotifierItem::ApplicationStatus);
0197     }
0198 }
0199 
0200 QAction *KLineal::addAction(QMenu *menu,
0201                             const QIcon &icon,
0202                             const QString &text,
0203                             const QObject *receiver,
0204                             const char *member,
0205                             const QKeySequence &shortcut,
0206                             const QString &name)
0207 {
0208     QAction *action = new QAction(icon, text, mActionCollection);
0209     mActionCollection->setDefaultShortcut(action, shortcut);
0210     if (receiver) {
0211         connect(action, SIGNAL(triggered()), receiver, member);
0212     }
0213     menu->addAction(action);
0214     mActionCollection->addAction(name, action);
0215     return action;
0216 }
0217 
0218 void KLineal::slotClose()
0219 {
0220     hide();
0221 }
0222 
0223 void KLineal::move(const QPoint &p)
0224 {
0225     setGeometry(QRect(p, size()));
0226 }
0227 
0228 QPoint KLineal::pos() const
0229 {
0230     return frameGeometry().topLeft();
0231 }
0232 
0233 void KLineal::drawBackground(QPainter &painter)
0234 {
0235     QColor a, b, bg = mColor;
0236     QLinearGradient gradient;
0237     if (mHorizontal) {
0238         a = bg.lighter(120);
0239         b = bg.darker(130);
0240         gradient = QLinearGradient(1, 0, 1, height());
0241     } else {
0242         a = bg.lighter(120);
0243         b = bg.darker(130);
0244         gradient = QLinearGradient(0, 1, width(), 1);
0245     }
0246     a.setAlpha(mOpacity);
0247     b.setAlpha(mOpacity);
0248     gradient.setColorAt(0, a);
0249     gradient.setColorAt(1, b);
0250     painter.fillRect(rect(), QBrush(gradient));
0251 }
0252 
0253 void KLineal::setHorizontal(bool horizontal)
0254 {
0255     QRect r = frameGeometry();
0256     if (mHorizontal != horizontal) {
0257         // relax restrictions before resizing
0258         setMaximumSize(RULER_MAX_LENGTH, RULER_MAX_LENGTH);
0259         r.setSize(r.size().transposed());
0260         if (horizontal) {
0261             setMaximumHeight(THICKNESS);
0262         } else {
0263             setMaximumWidth(THICKNESS);
0264         }
0265     }
0266     mHorizontal = horizontal;
0267     QPoint center = mLastClickGlobalPos;
0268 
0269     if (middleClicked) {
0270         center = mLastClickGlobalPos;
0271         middleClicked = false;
0272     } else {
0273         center = r.topLeft() + QPoint(width() / 2, height() / 2);
0274     }
0275 
0276     QPoint newTopLeft = QPoint(center.x() - height() / 2, center.y() - width() / 2);
0277     r.moveTo(newTopLeft);
0278 
0279     QRect desktop = QGuiApplication::primaryScreen()->geometry();
0280 
0281     if (r.width() > desktop.width()) {
0282         r.setWidth(desktop.width());
0283     }
0284 
0285     if (r.height() > desktop.height()) {
0286         r.setHeight(desktop.height());
0287     }
0288 
0289     if (r.top() < desktop.top()) {
0290         r.moveTop(desktop.top());
0291     }
0292 
0293     if (r.bottom() > desktop.bottom()) {
0294         r.moveBottom(desktop.bottom());
0295     }
0296 
0297     if (r.left() < desktop.left()) {
0298         r.moveLeft(desktop.left());
0299     }
0300 
0301     if (r.right() > desktop.right()) {
0302         r.moveRight(desktop.right());
0303     }
0304 
0305     setGeometry(r);
0306 
0307     updateScaleDirectionMenuItem();
0308 
0309     saveSettings();
0310 }
0311 
0312 void KLineal::rotate()
0313 {
0314     setHorizontal(!mHorizontal);
0315 }
0316 
0317 void KLineal::updateScaleDirectionMenuItem()
0318 {
0319     if (!mScaleDirectionAction)
0320         return;
0321 
0322     QString label;
0323 
0324     if (mHorizontal) {
0325         label = mLeftToRight ? i18n("Right to Left") : i18n("Left to Right");
0326     } else {
0327         label = mLeftToRight ? i18n("Bottom to Top") : i18n("Top to Bottom");
0328     }
0329 
0330     mScaleDirectionAction->setText(label);
0331 }
0332 
0333 QRect KLineal::beginRect() const
0334 {
0335     int shortLen = RESIZE_HANDLE_THICKNESS;
0336     return mHorizontal ? QRect(0, (height() - shortLen) / 2 + 1, RESIZE_HANDLE_LENGTH, shortLen)
0337                        : QRect((width() - shortLen) / 2, 0, shortLen, RESIZE_HANDLE_LENGTH);
0338 }
0339 
0340 QRect KLineal::endRect() const
0341 {
0342     int shortLen = RESIZE_HANDLE_THICKNESS;
0343     return mHorizontal ? QRect(width() - RESIZE_HANDLE_LENGTH, (height() - shortLen) / 2 + 1, RESIZE_HANDLE_LENGTH, shortLen)
0344                        : QRect((width() - shortLen) / 2, height() - RESIZE_HANDLE_LENGTH, shortLen, RESIZE_HANDLE_LENGTH);
0345 }
0346 
0347 Qt::CursorShape KLineal::resizeCursor() const
0348 {
0349     return mHorizontal ? Qt::SizeHorCursor : Qt::SizeVerCursor;
0350 }
0351 
0352 void KLineal::switchDirection()
0353 {
0354     mLeftToRight = !mLeftToRight;
0355     updateScaleDirectionMenuItem();
0356     repaint();
0357     saveSettings();
0358 }
0359 
0360 void KLineal::centerOrigin()
0361 {
0362     mOffset = -qRound(length() * pixelRatio() / 2);
0363     repaint();
0364     saveSettings();
0365 }
0366 
0367 void KLineal::slotOffset()
0368 {
0369     bool ok;
0370     int newOffset = QInputDialog::getInt(this, i18nc("@title:window", "Scale Offset"), i18n("Offset:"), mOffset, -2147483647, 2147483647, 1, &ok);
0371 
0372     if (ok) {
0373         mOffset = newOffset;
0374         repaint();
0375         saveSettings();
0376     }
0377 }
0378 
0379 void KLineal::slotOpacity(int value)
0380 {
0381     mOpacity = value;
0382     repaint();
0383     RulerSettings::self()->setOpacity(value);
0384     RulerSettings::self()->save();
0385 }
0386 
0387 void KLineal::slotKeyBindings()
0388 {
0389     KShortcutsDialog::showDialog(mActionCollection, KShortcutsEditor::LetterShortcutsAllowed, this);
0390 }
0391 
0392 void KLineal::slotPreferences()
0393 {
0394     KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"), RulerSettings::self());
0395 
0396     Ui::ConfigAppearance appearanceConfig;
0397     QWidget *appearanceConfigWidget = new QWidget(dialog);
0398     appearanceConfig.setupUi(appearanceConfigWidget);
0399     dialog->addPage(appearanceConfigWidget, i18n("Appearance"), QStringLiteral("preferences-desktop-default-applications"));
0400 
0401 #ifdef KRULER_HAVE_X11
0402     // Advanced page only contains the "Native moving" and "Always on top" settings, disable when not running on X11
0403     if (QX11Info::isPlatformX11()) {
0404         Ui::ConfigAdvanced advancedConfig;
0405         QWidget *advancedConfigWidget = new QWidget(dialog);
0406         advancedConfig.setupUi(advancedConfigWidget);
0407         dialog->addPage(advancedConfigWidget, i18n("Advanced"), QStringLiteral("preferences-other"));
0408         dialog->setFaceType(KConfigDialog::List);
0409     } else {
0410         dialog->setFaceType(KConfigDialog::Plain);
0411     }
0412 #else
0413     dialog->setFaceType(KConfigDialog::Plain);
0414 #endif
0415 
0416     connect(dialog, &KConfigDialog::settingsChanged, this, &KLineal::loadConfig);
0417     dialog->exec();
0418     delete dialog;
0419 }
0420 
0421 void KLineal::loadConfig()
0422 {
0423     mColor = RulerSettings::self()->bgColor();
0424     mScaleFont = RulerSettings::self()->scaleFont();
0425     mAlwaysOnTopLayer = RulerSettings::self()->alwaysOnTop();
0426     saveSettings();
0427 
0428     setWindowFlags(mAlwaysOnTopLayer ? Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint : Qt::FramelessWindowHint);
0429 
0430     if (RulerSettings::self()->trayIcon()) {
0431         if (!mTrayIcon) {
0432             createSystemTray();
0433         }
0434     } else {
0435         delete mTrayIcon;
0436         mTrayIcon = nullptr;
0437 
0438         if (mCloseAction) {
0439             mCloseAction->setVisible(false);
0440         }
0441     }
0442     show();
0443     repaint();
0444 }
0445 
0446 void KLineal::switchRelativeScale(bool checked)
0447 {
0448     mRelativeScale = checked;
0449 
0450     mCenterOriginAction->setEnabled(!mRelativeScale);
0451     mOffsetAction->setEnabled(!mRelativeScale);
0452 
0453     repaint();
0454     saveSettings();
0455 }
0456 
0457 /**
0458  * save the ruler color to the config file
0459  */
0460 void KLineal::saveSettings()
0461 {
0462     RulerSettings::self()->setBgColor(mColor);
0463     RulerSettings::self()->setScaleFont(mScaleFont);
0464     RulerSettings::self()->setLength(length());
0465     RulerSettings::self()->setHorizontal(mHorizontal);
0466     RulerSettings::self()->setLeftToRight(mLeftToRight);
0467     RulerSettings::self()->setOffset(mOffset);
0468     RulerSettings::self()->setRelativeScale(mRelativeScale);
0469     RulerSettings::self()->setAlwaysOnTop(mAlwaysOnTopLayer);
0470     RulerSettings::self()->save();
0471 }
0472 
0473 /**
0474  * lets the context menu appear at current cursor position
0475  */
0476 void KLineal::showMenu()
0477 {
0478     QPoint pos = QCursor::pos();
0479     mMenu->popup(pos);
0480 }
0481 
0482 int KLineal::length() const
0483 {
0484     return mHorizontal ? width() : height();
0485 }
0486 
0487 inline qreal KLineal::pixelRatio() const
0488 {
0489     if (!windowHandle())
0490         return 1;
0491     return windowHandle()->devicePixelRatio();
0492 }
0493 
0494 QString KLineal::indicatorText(int xy) const
0495 {
0496     if (!mRelativeScale) {
0497         int len = mLeftToRight ? xy + 1 : length() - xy;
0498         return i18n("%1 px", qRound(len * pixelRatio()));
0499     } else {
0500         int len = (xy * 100) / length();
0501 
0502         if (!mLeftToRight) {
0503             len = 100 - len;
0504         }
0505         return i18n("%1%", len);
0506     }
0507 }
0508 
0509 void KLineal::keyPressEvent(QKeyEvent *e)
0510 {
0511     QPoint dist;
0512 
0513     switch (e->key()) {
0514     case Qt::Key_F1:
0515         KHelpClient::invokeHelp();
0516         return;
0517 
0518     case Qt::Key_Left:
0519         dist.setX(-1);
0520         break;
0521 
0522     case Qt::Key_Right:
0523         dist.setX(1);
0524         break;
0525 
0526     case Qt::Key_Up:
0527         dist.setY(-1);
0528         break;
0529 
0530     case Qt::Key_Down:
0531         dist.setY(1);
0532         break;
0533 
0534     default:
0535         QWidget::keyPressEvent(e);
0536         return;
0537     }
0538 
0539     if (e->modifiers() & Qt::ShiftModifier) {
0540         dist *= 10;
0541     }
0542 
0543     if (e->modifiers() & Qt::AltModifier) {
0544         QCursor::setPos(QCursor::pos() + dist);
0545     } else {
0546         move(pos() + dist);
0547         update();
0548     }
0549     KNotification::event(QString(), QStringLiteral("cursormove"), QString());
0550 }
0551 
0552 void KLineal::leaveEvent(QEvent *e)
0553 {
0554     Q_UNUSED(e);
0555     update();
0556 }
0557 
0558 /**
0559  * overwritten for dragging and context menu
0560  */
0561 void KLineal::mousePressEvent(QMouseEvent *e)
0562 {
0563     if (e->button() == Qt::LeftButton) {
0564         if (beginRect().contains(mCursorPos)) {
0565             windowHandle()->startSystemResize(mHorizontal ? Qt::LeftEdge : Qt::TopEdge);
0566         } else if (endRect().contains(mCursorPos)) {
0567             windowHandle()->startSystemResize(mHorizontal ? Qt::RightEdge : Qt::BottomEdge);
0568         } else {
0569             windowHandle()->startSystemMove();
0570         }
0571     } else if (e->button() == Qt::MiddleButton) {
0572         middleClicked = true;
0573         mLastClickGlobalPos = QCursor::pos();
0574         rotate();
0575     } else if (e->button() == Qt::RightButton) {
0576         showMenu();
0577     }
0578 }
0579 
0580 void KLineal::mouseMoveEvent(QMouseEvent *e)
0581 {
0582     mCursorPos = e->position().toPoint();
0583     if (beginRect().contains(mCursorPos) || endRect().contains(mCursorPos)) {
0584         setCursor(resizeCursor());
0585     } else {
0586         setCursor(mCrossCursor);
0587     }
0588     update();
0589 }
0590 
0591 void KLineal::wheelEvent(QWheelEvent *e)
0592 {
0593     int numDegrees = e->angleDelta().y() / 8;
0594     int numSteps = numDegrees / 15;
0595 
0596     // changing offset
0597     if (e->buttons() == Qt::LeftButton) {
0598         if (!mRelativeScale) {
0599             mOffset += numSteps;
0600 
0601             repaint();
0602             saveSettings();
0603         }
0604     }
0605 
0606     QWidget::wheelEvent(e);
0607 }
0608 
0609 /**
0610  * draws the scale according to the orientation
0611  */
0612 void KLineal::drawScale(QPainter &painter)
0613 {
0614     painter.setPen(QPen(Qt::black, 1 / pixelRatio()));
0615     QFont font = mScaleFont;
0616     painter.setFont(font);
0617     int longLen = length() * pixelRatio();
0618 
0619     if (!mRelativeScale) {
0620         int digit;
0621         int len;
0622         // Draw from -1 to longLen rather than from 0 to longLen - 1 to take into
0623         // account the offset applied in drawScaleTick
0624         for (int x = -1; x <= longLen; ++x) {
0625             if (mLeftToRight) {
0626                 digit = x + mOffset;
0627             } else {
0628                 digit = longLen - x + mOffset;
0629             }
0630 
0631             if (digit % 2)
0632                 continue;
0633 
0634             if (digit % 100 == 0) {
0635                 len = LARGE_TICK_SIZE;
0636             } else if (digit % 20 == 0) {
0637                 len = MEDIUM2_TICK_SIZE;
0638             } else if (digit % 10 == 0) {
0639                 len = MEDIUM1_TICK_SIZE;
0640             } else {
0641                 len = SMALL_TICK_SIZE;
0642             }
0643 
0644             if (digit % 100 == 0 && digit != 0) {
0645                 QString units = QStringLiteral("%1").arg(digit);
0646                 drawScaleText(painter, x, units);
0647             }
0648 
0649             drawScaleTick(painter, x, len);
0650         }
0651     } else {
0652         float step = longLen / 100.f;
0653         int len;
0654 
0655         for (int i = 0; i <= 100; ++i) {
0656             int x = (int)(i * step);
0657 
0658             if (i % 10 == 0 && i != 0 && i != 100) {
0659                 int value = mLeftToRight ? i : (100 - i);
0660                 const QString units = QString::asprintf("%d%%", value);
0661                 drawScaleText(painter, x, units);
0662                 len = MEDIUM2_TICK_SIZE;
0663             } else {
0664                 len = SMALL_TICK_SIZE;
0665             }
0666 
0667             drawScaleTick(painter, x, len);
0668         }
0669     }
0670 }
0671 
0672 void KLineal::drawScaleText(QPainter &painter, int x, const QString &text)
0673 {
0674     QFontMetrics metrics = painter.fontMetrics();
0675     QSize textSize = metrics.size(Qt::TextSingleLine, text);
0676     qreal w = width();
0677     qreal h = height();
0678     qreal tw = textSize.width();
0679     qreal th = metrics.ascent();
0680     qreal lx = x / pixelRatio();
0681 
0682     if (mHorizontal) {
0683         painter.drawText(QPointF(lx - tw / 2, (h + th) / 2), text);
0684     } else {
0685         painter.drawText(QPointF((w - tw) / 2, lx + th / 2), text);
0686     }
0687 }
0688 
0689 void KLineal::drawScaleTick(QPainter &painter, int x, int len)
0690 {
0691     painter.setOpacity(TICK_OPACITY);
0692     qreal w = width();
0693     qreal h = height();
0694     // Offset by one because we are measuring lengths, not positions, so when the
0695     // indicator is at position 0 it measures a length of 1 pixel.
0696     if (mLeftToRight) {
0697         --x;
0698     } else {
0699         ++x;
0700     }
0701 
0702     // The value is in physical coords, but Qt is drawing in logical coords.
0703     qreal lx = x / pixelRatio();
0704 
0705     if (mHorizontal) {
0706         painter.drawLine(QLineF(lx, 0, lx, len));
0707         painter.drawLine(QLineF(lx, h, lx, h - len));
0708     } else {
0709         painter.drawLine(QLineF(0, lx, len, lx));
0710         painter.drawLine(QLineF(w, lx, w - len, lx));
0711     }
0712     painter.setOpacity(1);
0713 }
0714 
0715 void KLineal::drawResizeHandle(QPainter &painter, Qt::Edge edge)
0716 {
0717     QRect rect;
0718     switch (edge) {
0719     case Qt::LeftEdge:
0720     case Qt::TopEdge:
0721         rect = beginRect();
0722         break;
0723     case Qt::RightEdge:
0724     case Qt::BottomEdge:
0725         rect = endRect();
0726         break;
0727     }
0728     painter.setOpacity(RESIZE_HANDLE_OPACITY);
0729     if (mHorizontal) {
0730         int y1 = (THICKNESS - RESIZE_HANDLE_THICKNESS) / 2;
0731         int y2 = y1 + RESIZE_HANDLE_THICKNESS - 1;
0732         for (int x = rect.left() + 1; x < rect.right(); x += 2) {
0733             painter.drawLine(x, y1, x, y2);
0734         }
0735     } else {
0736         int x1 = (THICKNESS - RESIZE_HANDLE_THICKNESS) / 2;
0737         int x2 = x1 + RESIZE_HANDLE_THICKNESS - 1;
0738         for (int y = rect.top() + 1; y < rect.bottom(); y += 2) {
0739             painter.drawLine(x1, y, x2, y);
0740         }
0741     }
0742     painter.setOpacity(1);
0743 }
0744 
0745 void KLineal::drawIndicatorOverlay(QPainter &painter, int xy)
0746 {
0747     painter.setPen(Qt::red);
0748     painter.setOpacity(OVERLAY_OPACITY);
0749     if (mHorizontal) {
0750         QPointF p1(mLeftToRight ? 0 : width(), 0);
0751         QPointF p2(xy, THICKNESS);
0752         QRectF rect(p1, p2);
0753         painter.fillRect(rect, Qt::red);
0754 
0755         painter.setOpacity(OVERLAY_BORDER_OPACITY);
0756         painter.drawLine(xy, 0, xy, THICKNESS);
0757     } else {
0758         QPointF p1(0, mLeftToRight ? 0 : height());
0759         QPointF p2(THICKNESS, xy);
0760         QRectF rect(p1, p2);
0761         painter.fillRect(rect, Qt::red);
0762 
0763         painter.setOpacity(OVERLAY_BORDER_OPACITY);
0764         painter.drawLine(0, xy, THICKNESS, xy);
0765     }
0766 }
0767 
0768 void KLineal::drawIndicatorText(QPainter &painter, int xy)
0769 {
0770     QString text = indicatorText(xy);
0771     painter.setFont(font());
0772     QFontMetrics fm = QFontMetrics(font());
0773     int tx, ty;
0774     int tw = fm.boundingRect(text).width();
0775     if (mHorizontal) {
0776         tx = xy + INDICATOR_MARGIN;
0777         if (tx + tw > width()) {
0778             tx = xy - tw - INDICATOR_MARGIN;
0779         }
0780         ty = height() - SMALL_TICK_SIZE - INDICATOR_RECT_RADIUS;
0781     } else {
0782         tx = (width() - tw) / 2;
0783         ty = xy + fm.ascent() + INDICATOR_MARGIN;
0784         if (ty > height()) {
0785             ty = xy - INDICATOR_MARGIN;
0786         }
0787     }
0788 
0789     // Draw background rect
0790     painter.setRenderHint(QPainter::Antialiasing);
0791     painter.setOpacity(INDICATOR_RECT_OPACITY);
0792     painter.setBrush(Qt::red);
0793     QRectF bgRect(tx, ty - fm.ascent() + 1, tw, fm.ascent());
0794     bgRect.adjust(-INDICATOR_RECT_RADIUS, -INDICATOR_RECT_RADIUS, INDICATOR_RECT_RADIUS, INDICATOR_RECT_RADIUS);
0795     bgRect.translate(0.5, 0.5);
0796     painter.drawRoundedRect(bgRect, INDICATOR_RECT_RADIUS, INDICATOR_RECT_RADIUS);
0797 
0798     // Draw text
0799     painter.setOpacity(1);
0800     painter.setPen(Qt::white);
0801     painter.drawText(tx, ty, text);
0802 }
0803 
0804 /**
0805  * actually draws the ruler
0806  */
0807 void KLineal::paintEvent(QPaintEvent *e)
0808 {
0809     Q_UNUSED(e);
0810 
0811     QPainter painter(this);
0812     drawBackground(painter);
0813     drawScale(painter);
0814 
0815     drawResizeHandle(painter, mHorizontal ? Qt::LeftEdge : Qt::TopEdge);
0816     drawResizeHandle(painter, mHorizontal ? Qt::RightEdge : Qt::BottomEdge);
0817     if (underMouse()) {
0818         int xy = mHorizontal ? mCursorPos.x() : mCursorPos.y();
0819         drawIndicatorOverlay(painter, xy);
0820         drawIndicatorText(painter, xy);
0821     }
0822 }
0823 
0824 #include "moc_klineal.cpp"