File indexing completed on 2024-04-28 08:21:12
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"