klineal.cpp 25 KB
Newer Older
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
1 2 3
/***************************************************************************
                          klineal.cpp  -  description
                             -------------------
4
    Begin                : Fri Oct 13 2000
Mathias Soeken's avatar
Mathias Soeken committed
5 6
    Copyright            : 2000 by Till Krech <till@snafu.de>
                           2008 by Mathias Soeken <msoeken@informatik.uni-bremen.de>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
7
                           2017 by Aurélien Gâteau <agateau@kde.org>
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
8 9 10 11 12 13 14 15 16 17 18
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

Pino Toscano's avatar
Pino Toscano committed
19 20
#include "klineal.h"

Christoph Feck's avatar
Christoph Feck committed
21 22
#include <QAction>
#include <QApplication>
Urs Wolfer's avatar
Urs Wolfer committed
23
#include <QBitmap>
24
#include <QBrush>
25
#include <QClipboard>
Christoph Feck's avatar
Christoph Feck committed
26 27 28 29
#include <QDesktopWidget>
#include <QFontDatabase>
#include <QInputDialog>
#include <QMenu>
Urs Wolfer's avatar
Urs Wolfer committed
30
#include <QMouseEvent>
Christoph Feck's avatar
Christoph Feck committed
31 32
#include <QPainter>
#include <QScreen>
33
#include <QSlider>
34
#include <QToolButton>
35
#include <QWidgetAction>
Andreas Simon's avatar
Andreas Simon committed
36

Christoph Feck's avatar
Christoph Feck committed
37
#include <KAboutData>
38
#include <KActionCollection>
Mathias Soeken's avatar
Mathias Soeken committed
39
#include <KConfig>
40
#include <KConfigDialog>
Christoph Feck's avatar
Christoph Feck committed
41
#include <KHelpClient>
Mathias Soeken's avatar
Mathias Soeken committed
42
#include <KHelpMenu>
Christoph Feck's avatar
Christoph Feck committed
43
#include <KLocalizedString>
Mathias Soeken's avatar
Mathias Soeken committed
44
#include <KNotification>
45
#include <KShortcutsDialog>
Mathias Soeken's avatar
Mathias Soeken committed
46
#include <KStandardAction>
Mathias Soeken's avatar
Mathias Soeken committed
47
#include <KWindowSystem>
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
48

49 50 51 52
#include "krulerconfig.h"

#ifdef KRULER_HAVE_X11
#include <QX11Info>
53
#include <netwm.h>
54
#endif
55

56
#include "kruler.h"
57
#include "krulersystemtray.h"
58

59
#include "ui_cfg_appearance.h"
60
#include "ui_cfg_advanced.h"
61

Aurélien Gâteau's avatar
Aurélien Gâteau committed
62 63
static const int RESIZE_HANDLE_LENGTH = 7;
static const int RESIZE_HANDLE_THICKNESS = 24;
64
static const qreal RESIZE_HANDLE_OPACITY = 0.3;
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
65

Aurélien Gâteau's avatar
Aurélien Gâteau committed
66 67 68 69
static const int SMALL_TICK_SIZE = 6;
static const int MEDIUM1_TICK_SIZE = 10;
static const int MEDIUM2_TICK_SIZE = 15;
static const int LARGE_TICK_SIZE = 18;
70
static const qreal TICK_OPACITY = 0.3;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
71 72

static const int THICKNESS = 70;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
73 74 75 76 77 78 79 80

static const qreal OVERLAY_OPACITY = 0.1;
static const qreal OVERLAY_BORDER_OPACITY = 0.3;

static const int INDICATOR_MARGIN = 6;
static const int INDICATOR_RECT_RADIUS = 3;
static const qreal INDICATOR_RECT_OPACITY = 0.6;

Aurélien Gâteau's avatar
Aurélien Gâteau committed
81
static const int CURSOR_SIZE = 15; // Must be an odd number
Aurélien Gâteau's avatar
Aurélien Gâteau committed
82

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
83
/**
Mathias Soeken's avatar
Mathias Soeken committed
84 85 86
 * create the thingy with no borders and set up
 * its members
 */
Mathias Soeken's avatar
Mathias Soeken committed
87
KLineal::KLineal( QWidget *parent )
Mathias Soeken's avatar
Mathias Soeken committed
88
  : QWidget( parent ),
89
    mRulerState( StateNone ),
Mathias Soeken's avatar
Mathias Soeken committed
90
    mCloseAction( 0 ),
91
    mScaleDirectionAction( 0 ),
92 93
    mCenterOriginAction( 0 ),
    mOffsetAction( 0 ),
94
    mClicked( false ),
95
    mActionCollection( 0 ),
96
    mTrayIcon( 0 )
Mathias Soeken's avatar
Mathias Soeken committed
97
{
Christoph Feck's avatar
Christoph Feck committed
98
  setAttribute( Qt::WA_TranslucentBackground );
Mathias Soeken's avatar
Mathias Soeken committed
99 100
  KWindowSystem::setType( winId(), NET::Override );   // or NET::Normal
  KWindowSystem::setState( winId(), NET::KeepAbove );
Mathias Soeken's avatar
Mathias Soeken committed
101

102
  setWindowTitle( i18nc( "@title:window", "KRuler" ) );
103

Aurélien Gâteau's avatar
Aurélien Gâteau committed
104
  setMinimumSize( THICKNESS, THICKNESS );
105
  setMaximumSize( 8000, 8000 );
Aurélien Gâteau's avatar
Aurélien Gâteau committed
106
  setWhatsThis( i18n( "This is a tool to measure pixel distances on the screen. "
Mathias Soeken's avatar
Mathias Soeken committed
107 108
                      "It is useful for working on layouts of dialogs, web pages etc." ) );
  setMouseTracking( true );
Mathias Soeken's avatar
Mathias Soeken committed
109

110 111
  mColor = RulerSettings::self()->bgColor();
  mScaleFont = RulerSettings::self()->scaleFont();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
112
  int len = RulerSettings::self()->length();
113
  mHorizontal = RulerSettings::self()->horizontal();
114 115
  mLeftToRight = RulerSettings::self()->leftToRight();
  mOffset = RulerSettings::self()->offset();
116
  mRelativeScale = RulerSettings::self()->relativeScale();
117
  mAlwaysOnTopLayer = RulerSettings::self()->alwaysOnTop();
Mathias Soeken's avatar
Mathias Soeken committed
118

Aurélien Gâteau's avatar
Aurélien Gâteau committed
119 120 121 122 123
  if ( mHorizontal ) {
    resize( QSize( len, THICKNESS ) );
  } else {
    resize( QSize( THICKNESS, len ) );
  }
Mathias Soeken's avatar
Mathias Soeken committed
124

Aurélien Gâteau's avatar
Aurélien Gâteau committed
125 126
  createCrossCursor();

127
  //BEGIN setup menu and actions
128
  mActionCollection = new KActionCollection( this );
129
  mActionCollection->setConfigGroup( QStringLiteral( "Actions" ) );
130

Christoph Feck's avatar
Christoph Feck committed
131 132
  mMenu = new QMenu( this );
  mMenu->addSection( i18n( "KRuler" ) );
133
  addAction( mMenu, QIcon::fromTheme( QStringLiteral( "object-rotate-left" ) ), i18n( "Rotate" ),
134
             this, SLOT(rotate()), Qt::Key_R, QStringLiteral( "turn_right" ) );
135

Christoph Feck's avatar
Christoph Feck committed
136 137
  QMenu* scaleMenu = new QMenu( i18n( "&Scale" ), this );
  mScaleDirectionAction = addAction( scaleMenu, QIcon(), i18n( "Right to Left" ),
138
                                     this, SLOT(switchDirection()), Qt::Key_D, QStringLiteral( "right_to_left" ) );
Christoph Feck's avatar
Christoph Feck committed
139
  mCenterOriginAction = addAction( scaleMenu, QIcon(), i18n( "Center Origin" ),
140
                                   this, SLOT(centerOrigin()), Qt::Key_C, QStringLiteral( "center_origin" ) );
141
  mCenterOriginAction->setEnabled( !mRelativeScale );
Christoph Feck's avatar
Christoph Feck committed
142
  mOffsetAction = addAction( scaleMenu, QIcon(), i18n( "Offset..." ),
143
                             this, SLOT(slotOffset()), Qt::Key_O, QStringLiteral( "set_offset" ) );
144 145
  mOffsetAction->setEnabled( !mRelativeScale );
  scaleMenu->addSeparator();
Christoph Feck's avatar
Christoph Feck committed
146
  QAction *relativeScaleAction = addAction( scaleMenu, QIcon(), i18n( "Percentage" ),
147
                                            0, 0, QKeySequence(), QStringLiteral( "toggle_percentage" ) );
148 149
  relativeScaleAction->setCheckable( true );
  relativeScaleAction->setChecked( mRelativeScale );
150
  connect(relativeScaleAction, &QAction::toggled, this, &KLineal::switchRelativeScale);
151
  mMenu->addMenu( scaleMenu );
152

153
  mOpacity = RulerSettings::self()->opacity();
Christoph Feck's avatar
Christoph Feck committed
154
  QMenu* opacityMenu = new QMenu( i18n( "O&pacity" ), this );
155 156
  QWidgetAction *opacityAction = new QWidgetAction( this );
  QSlider *slider = new QSlider( this );
157 158
  slider->setMinimum( 0 );
  slider->setMaximum( 255 );
159 160
  slider->setSingleStep( 1 );
  slider->setOrientation( Qt::Horizontal );
161
  slider->setValue( RulerSettings::self()->opacity() );
162
  connect(slider, &QSlider::valueChanged, this, &KLineal::slotOpacity);
163 164 165 166
  opacityAction->setDefaultWidget( slider );
  opacityMenu->addAction( opacityAction );
  mMenu->addMenu( opacityMenu );

167
  QAction *keyBindings = mActionCollection->addAction( KStandardAction::KeyBindings, this, SLOT(slotKeyBindings()) );
168
  mMenu->addAction( keyBindings );
169
  QAction *preferences = mActionCollection->addAction( KStandardAction::Preferences, this, SLOT(slotPreferences()) );
170
  mMenu->addAction( preferences );
171
  mMenu->addSeparator();
Christoph Feck's avatar
Christoph Feck committed
172
  mMenu->addMenu( ( new KHelpMenu( this, KAboutData::applicationData(), true ) )->menu() );
Pascal Létourneau's avatar
porting  
Pascal Létourneau committed
173
  mMenu->addSeparator();
174
  if ( RulerSettings::self()->trayIcon() ) {
175
      createSystemTray();
176 177
  }

178
  QAction *quit = mActionCollection->addAction( KStandardAction::Quit, qApp, SLOT(quit()) );
Mathias Soeken's avatar
Mathias Soeken committed
179
  mMenu->addAction( quit );
Mathias Soeken's avatar
Mathias Soeken committed
180

181 182 183
  mActionCollection->associateWidget( this );
  mActionCollection->readSettings();

Mathias Soeken's avatar
Mathias Soeken committed
184
  mLastClickPos = geometry().topLeft() + QPoint( width() / 2, height() / 2 );
185

186 187 188
  setWindowFlags( mAlwaysOnTopLayer ? Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
                                    : Qt::FramelessWindowHint );

189
  setHorizontal( mHorizontal );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
190
}
191

Mathias Soeken's avatar
Mathias Soeken committed
192 193
KLineal::~KLineal()
{
Mathias Soeken's avatar
Mathias Soeken committed
194
  delete mTrayIcon;
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
195 196
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210
void KLineal::createCrossCursor()
{
  QPixmap pix( CURSOR_SIZE, CURSOR_SIZE );
  int halfSize = CURSOR_SIZE / 2;
  {
    pix.fill( Qt::transparent );
    QPainter painter( &pix );
    painter.setPen( Qt::red );
    painter.drawLine( 0, halfSize, CURSOR_SIZE - 1, halfSize );
    painter.drawLine( halfSize, 0, halfSize, CURSOR_SIZE - 1 );
  }
  mCrossCursor = QCursor( pix, halfSize, halfSize );
}

211 212
void KLineal::createSystemTray()
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
213 214
  mCloseAction = mActionCollection->addAction( KStandardAction::Close, this, SLOT(slotClose()) );
  mMenu->addAction( mCloseAction );
215

Mathias Soeken's avatar
Mathias Soeken committed
216
  if ( !mTrayIcon ) {
217
    mTrayIcon = new KRulerSystemTray( QStringLiteral( "kruler" ), this, mActionCollection );
218
    mTrayIcon->setCategory( KStatusNotifierItem::ApplicationStatus );
Mathias Soeken's avatar
Mathias Soeken committed
219
  }
220 221 222
}


Christoph Feck's avatar
Christoph Feck committed
223
QAction* KLineal::addAction( QMenu *menu, const QIcon& icon, const QString& text,
224 225 226
                             const QObject* receiver, const char* member,
                             const QKeySequence &shortcut, const QString& name )
{
Christoph Feck's avatar
Christoph Feck committed
227 228
  QAction *action = new QAction( icon, text, mActionCollection );
  mActionCollection->setDefaultShortcut( action, shortcut );
229
  if ( receiver ) {
Laurent Montel's avatar
Laurent Montel committed
230
    connect( action, SIGNAL(triggered()), receiver, member );
231 232 233 234 235 236
  }
  menu->addAction( action );
  mActionCollection->addAction( name, action );
  return action;
}

237 238 239 240 241
void KLineal::slotClose()
{
  hide();
}

Laurent Montel's avatar
Laurent Montel committed
242 243
void KLineal::slotQuit()
{
Christoph Feck's avatar
Christoph Feck committed
244
   qApp->quit();
Laurent Montel's avatar
Laurent Montel committed
245 246
}

Mathias Soeken's avatar
Mathias Soeken committed
247 248 249
void KLineal::move( int x, int y )
{
  move( QPoint( x, y ) );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
250 251
}

Mathias Soeken's avatar
Mathias Soeken committed
252 253 254
void KLineal::move(const QPoint &p)
{
  setGeometry( QRect( p, size() ) );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
255 256
}

Laurent Montel's avatar
Laurent Montel committed
257
QPoint KLineal::pos() const
Mathias Soeken's avatar
Mathias Soeken committed
258 259
{
  return frameGeometry().topLeft();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
260
}
Mathias Soeken's avatar
Mathias Soeken committed
261

Laurent Montel's avatar
Laurent Montel committed
262
int KLineal::x() const
Mathias Soeken's avatar
Mathias Soeken committed
263
{
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
264 265
  return pos().x();
}
Mathias Soeken's avatar
Mathias Soeken committed
266

Laurent Montel's avatar
Laurent Montel committed
267
int KLineal::y() const
Mathias Soeken's avatar
Mathias Soeken committed
268
{
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
269 270 271
  return pos().y();
}

Mathias Soeken's avatar
Mathias Soeken committed
272 273
void KLineal::drawBackground( QPainter& painter )
{
274
  QColor a, b, bg = mColor;
275
  QLinearGradient gradient;
276
  if ( mHorizontal ) {
Mathias Soeken's avatar
Mathias Soeken committed
277 278 279
    a = bg.light( 120 );
    b = bg.dark( 130 );
    gradient = QLinearGradient( 1, 0, 1, height() );
280
  } else {
Mathias Soeken's avatar
Mathias Soeken committed
281 282 283
    a = bg.light( 120 );
    b = bg.dark( 130 );
    gradient = QLinearGradient( 0, 1, width(), 1 );
284
  }
285 286
  a.setAlpha( mOpacity );
  b.setAlpha( mOpacity );
Mathias Soeken's avatar
Mathias Soeken committed
287 288 289
  gradient.setColorAt( 0, a );
  gradient.setColorAt( 1, b );
  painter.fillRect( rect(), QBrush( gradient ) );
290 291
}

292
void KLineal::setHorizontal( bool horizontal )
Mathias Soeken's avatar
Mathias Soeken committed
293
{
294
  QRect r = frameGeometry();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
295 296 297
  if ( mHorizontal != horizontal ) {
    r.setSize( r.size().transposed() );
  }
298 299
  mHorizontal = horizontal;
  QPoint center = mLastClickPos;
Laurent Montel's avatar
Laurent Montel committed
300

301
  if ( mClicked ) {
Ryan Cumming's avatar
Ryan Cumming committed
302
    center = mLastClickPos;
303
    mClicked = false;
304 305 306
  } else {
    center = r.topLeft() + QPoint( width() / 2, height() / 2 );
  }
Urs Wolfer's avatar
Urs Wolfer committed
307

308
  QPoint newTopLeft = QPoint( center.x() - height() / 2, center.y() - width() / 2 );
309
  r.moveTo(newTopLeft);
310

Christoph Feck's avatar
Christoph Feck committed
311
  QRect desktop = QApplication::desktop()->screenGeometry( this );
312

Aurélien Gâteau's avatar
Aurélien Gâteau committed
313 314 315 316 317 318 319 320
  if ( r.width() > desktop.width() ) {
    r.setWidth( desktop.width() );
  }

  if ( r.height() > desktop.height() ) {
    r.setHeight( desktop.height() );
  }

321
  if ( r.top() < desktop.top() ) {
Mathias Soeken's avatar
Mathias Soeken committed
322
    r.moveTop( desktop.top() );
323 324 325
  }

  if ( r.bottom() > desktop.bottom() ) {
Mathias Soeken's avatar
Mathias Soeken committed
326
    r.moveBottom( desktop.bottom() );
327 328 329
  }

  if ( r.left() < desktop.left() ) {
Mathias Soeken's avatar
Mathias Soeken committed
330
    r.moveLeft( desktop.left() );
331
  }
332

333 334 335 336 337
  if ( r.right() > desktop.right() ) {
    r.moveRight( desktop.right() );
  }

  setGeometry( r );
Mathias Soeken's avatar
Mathias Soeken committed
338

339 340
  updateScaleDirectionMenuItem();

341
  saveSettings();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
342
}
343

344
void KLineal::rotate()
Mathias Soeken's avatar
Mathias Soeken committed
345
{
346
  setHorizontal( !mHorizontal );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
347
}
Mathias Soeken's avatar
Mathias Soeken committed
348

349 350 351 352 353 354
void KLineal::updateScaleDirectionMenuItem()
{
  if ( !mScaleDirectionAction ) return;

  QString label;

355
  if ( mHorizontal ) {
356
    label = mLeftToRight ? i18n( "Right to Left" ) : i18n( "Left to Right" );
357
  } else {
358
    label = mLeftToRight ? i18n( "Bottom to Top" ) : i18n( "Top to Bottom" );
359 360 361 362 363
  }

  mScaleDirectionAction->setText( label );
}

364 365
QRect KLineal::beginRect() const
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
366
  int shortLen = RESIZE_HANDLE_THICKNESS;
367
  return mHorizontal
Aurélien Gâteau's avatar
Aurélien Gâteau committed
368 369
    ? QRect( 0, ( height() - shortLen ) / 2 + 1, RESIZE_HANDLE_LENGTH, shortLen )
    : QRect( ( width() - shortLen ) / 2, 0, shortLen, RESIZE_HANDLE_LENGTH );
370 371 372 373
}

QRect KLineal::endRect() const
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
374
  int shortLen = RESIZE_HANDLE_THICKNESS;
375
  return mHorizontal
Aurélien Gâteau's avatar
Aurélien Gâteau committed
376 377
    ? QRect( width() - RESIZE_HANDLE_LENGTH, ( height() - shortLen ) / 2 + 1, RESIZE_HANDLE_LENGTH, shortLen )
    : QRect( ( width() - shortLen ) / 2, height() - RESIZE_HANDLE_LENGTH, shortLen, RESIZE_HANDLE_LENGTH );
378 379 380 381 382 383 384
}

Qt::CursorShape KLineal::resizeCursor() const
{
  return mHorizontal ? Qt::SizeHorCursor : Qt::SizeVerCursor;
}

385 386 387 388 389
void KLineal::switchDirection()
{
  mLeftToRight = !mLeftToRight;
  updateScaleDirectionMenuItem();
  repaint();
390
  saveSettings();
391 392 393 394
}

void KLineal::centerOrigin()
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
395
  mOffset = -( length() / 2 );
396
  repaint();
397
  saveSettings();
398 399 400 401 402
}

void KLineal::slotOffset()
{
  bool ok;
Christoph Feck's avatar
Christoph Feck committed
403
  int newOffset = QInputDialog::getInt( this, i18nc( "@title:window", "Scale Offset" ),
404
                                            i18n( "Offset:" ), mOffset,
Christoph Feck's avatar
Christoph Feck committed
405
                                            -2147483647, 2147483647, 1, &ok );
406 407 408 409

  if ( ok ) {
    mOffset = newOffset;
    repaint();
410
    saveSettings();
411
  }
412 413
}

414 415
void KLineal::slotOpacity( int value )
{
416 417
  mOpacity = value;
  repaint();
418
  RulerSettings::self()->setOpacity( value );
Christoph Feck's avatar
Christoph Feck committed
419
  RulerSettings::self()->save();
420 421
}

422 423 424 425 426
void KLineal::slotKeyBindings()
{
  KShortcutsDialog::configure( mActionCollection );
}

427
void KLineal::slotPreferences()
428
{
429
  KConfigDialog *dialog = new KConfigDialog( this, QStringLiteral( "settings" ), RulerSettings::self() );
430

431 432 433
  Ui::ConfigAppearance appearanceConfig;
  QWidget *appearanceConfigWidget = new QWidget( dialog );
  appearanceConfig.setupUi( appearanceConfigWidget );
434
  dialog->addPage( appearanceConfigWidget, i18n( "Appearance" ), QStringLiteral( "preferences-desktop-default-applications" ) );
435

Christoph Feck's avatar
Christoph Feck committed
436
#ifdef KRULER_HAVE_X11
437
  // Advanced page only contains the "Native moving" and "Always on top" settings, disable when not running on X11
438 439 440 441
  if ( QX11Info::isPlatformX11() ) {
    Ui::ConfigAdvanced advancedConfig;
    QWidget *advancedConfigWidget = new QWidget( dialog );
    advancedConfig.setupUi( advancedConfigWidget );
442
    dialog->addPage( advancedConfigWidget, i18n( "Advanced" ), QStringLiteral( "preferences-other" ) );
443
  }
Christoph Feck's avatar
Christoph Feck committed
444
#endif
445

446
  connect(dialog, &KConfigDialog::settingsChanged, this, &KLineal::loadConfig);
447
  dialog->exec();
Laurent Montel's avatar
Laurent Montel committed
448
  delete dialog;
449
}
450

451 452
void KLineal::loadConfig()
{
Mathias Soeken's avatar
Mathias Soeken committed
453 454
  mColor = RulerSettings::self()->bgColor();
  mScaleFont = RulerSettings::self()->scaleFont();
455
  mAlwaysOnTopLayer = RulerSettings::self()->alwaysOnTop();
Mathias Soeken's avatar
Mathias Soeken committed
456
  saveSettings();
457

458 459 460
  setWindowFlags( mAlwaysOnTopLayer ? Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
                                    : Qt::FramelessWindowHint );

Mathias Soeken's avatar
Mathias Soeken committed
461 462 463
  if ( RulerSettings::self()->trayIcon() ) {
    if ( !mTrayIcon ) {
      createSystemTray();
464
    }
Mathias Soeken's avatar
Mathias Soeken committed
465
  } else {
466 467
    delete mTrayIcon;
    mTrayIcon = 0;
Mathias Soeken's avatar
Mathias Soeken committed
468 469 470 471 472

    if ( mCloseAction ) {
      mCloseAction->setVisible( false );
    }
  }
473
  show();
Mathias Soeken's avatar
Mathias Soeken committed
474
  repaint();
475 476
}

477

478
void KLineal::switchRelativeScale( bool checked )
Mathias Soeken's avatar
Mathias Soeken committed
479
{
480
  mRelativeScale = checked;
481

482 483
  mCenterOriginAction->setEnabled( !mRelativeScale );
  mOffsetAction->setEnabled( !mRelativeScale );
484 485

  repaint();
Mathias Soeken's avatar
Mathias Soeken committed
486
  saveSettings();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
487 488
}

489
/**
Mathias Soeken's avatar
Mathias Soeken committed
490 491 492 493
 * save the ruler color to the config file
 */
void KLineal::saveSettings()
{
494 495
  RulerSettings::self()->setBgColor( mColor );
  RulerSettings::self()->setScaleFont( mScaleFont );
Aurélien Gâteau's avatar
Aurélien Gâteau committed
496
  RulerSettings::self()->setLength( length() );
497
  RulerSettings::self()->setHorizontal( mHorizontal );
498 499
  RulerSettings::self()->setLeftToRight( mLeftToRight );
  RulerSettings::self()->setOffset( mOffset );
500
  RulerSettings::self()->setRelativeScale( mRelativeScale );
501
  RulerSettings::self()->setAlwaysOnTop( mAlwaysOnTopLayer );
Christoph Feck's avatar
Christoph Feck committed
502
  RulerSettings::self()->save();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
503
}
504

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
505
/**
Mathias Soeken's avatar
Mathias Soeken committed
506 507 508 509
 * lets the context menu appear at current cursor position
 */
void KLineal::showMenu()
{
Urs Wolfer's avatar
Urs Wolfer committed
510
  QPoint pos = QCursor::pos();
Mathias Soeken's avatar
Mathias Soeken committed
511
  mMenu->popup( pos );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
512
}
513

Aurélien Gâteau's avatar
Aurélien Gâteau committed
514 515 516 517 518
bool KLineal::isResizing() const
{
  return mouseGrabber() == this && ( mRulerState == StateBegin || mRulerState == StateEnd );
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
519 520 521 522 523
int KLineal::length() const
{
  return mHorizontal ? width() : height();
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
524 525 526 527 528 529 530
QPoint KLineal::localCursorPos() const
{
  // For some reason mapFromGlobal( QCursor::pos() ) thinks the ruler is at 0, 0 at startup.
  // compute the position ourselves to avoid that.
  return QCursor::pos() - pos();
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
531
QString KLineal::indicatorText() const
Mathias Soeken's avatar
Mathias Soeken committed
532
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
533
  int xy = mHorizontal ? localCursorPos().x() : localCursorPos().y();
534
  if ( !mRelativeScale ) {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
535 536
    int len = mLeftToRight ? xy + 1 : length() - xy;
    return i18n( "%1 px", len );
537
  } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
538
    int len = ( xy * 100.f ) / length();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
539

540 541 542
    if ( !mLeftToRight ) {
      len = 100 - len;
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
543
    return i18n( "%1%", len );
544
  }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
545
}
Mathias Soeken's avatar
Mathias Soeken committed
546 547 548 549 550 551

void KLineal::keyPressEvent( QKeyEvent *e )
{
  QPoint dist;

  switch ( e->key() ) {
Urs Wolfer's avatar
Urs Wolfer committed
552
  case Qt::Key_F1:
Christoph Feck's avatar
Christoph Feck committed
553
    KHelpClient::invokeHelp();
Mathias Soeken's avatar
Mathias Soeken committed
554 555
    return;

Urs Wolfer's avatar
Urs Wolfer committed
556
  case Qt::Key_Left:
Mathias Soeken's avatar
Mathias Soeken committed
557
    dist.setX( -1 );
Urs Wolfer's avatar
Urs Wolfer committed
558
    break;
Mathias Soeken's avatar
Mathias Soeken committed
559

Urs Wolfer's avatar
Urs Wolfer committed
560
  case Qt::Key_Right:
Mathias Soeken's avatar
Mathias Soeken committed
561
    dist.setX( 1 );
Urs Wolfer's avatar
Urs Wolfer committed
562
    break;
Mathias Soeken's avatar
Mathias Soeken committed
563

Urs Wolfer's avatar
Urs Wolfer committed
564
  case Qt::Key_Up:
Mathias Soeken's avatar
Mathias Soeken committed
565
    dist.setY( -1 );
Urs Wolfer's avatar
Urs Wolfer committed
566
    break;
Mathias Soeken's avatar
Mathias Soeken committed
567

Urs Wolfer's avatar
Urs Wolfer committed
568
  case Qt::Key_Down:
Mathias Soeken's avatar
Mathias Soeken committed
569
    dist.setY( 1 );
Urs Wolfer's avatar
Urs Wolfer committed
570
    break;
Mathias Soeken's avatar
Mathias Soeken committed
571

Urs Wolfer's avatar
Urs Wolfer committed
572 573 574 575
  default:
    QWidget::keyPressEvent(e);
    return;
  }
Mathias Soeken's avatar
Mathias Soeken committed
576 577

  if ( e->modifiers() & Qt::ShiftModifier ) {
Urs Wolfer's avatar
Urs Wolfer committed
578
    dist *= 10;
579
  }
Mathias Soeken's avatar
Mathias Soeken committed
580 581

  move( pos() + dist );
582
  KNotification::event( QString(), QStringLiteral( "cursormove" ), QString() );
583
}
Mathias Soeken's avatar
Mathias Soeken committed
584

Aurélien Gâteau's avatar
Aurélien Gâteau committed
585 586 587
void KLineal::leaveEvent( QEvent *e )
{
  Q_UNUSED( e );
Aurélien Gâteau's avatar
Aurélien Gâteau committed
588
  update();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
589 590
}

Mathias Soeken's avatar
Mathias Soeken committed
591 592 593 594
void KLineal::mouseMoveEvent( QMouseEvent *inEvent )
{
  Q_UNUSED( inEvent );

Aurélien Gâteau's avatar
Aurélien Gâteau committed
595 596 597 598
  if ( mRulerState >= StateMove ) {
    if ( mouseGrabber() != this ) {
      return;
    }
599
    if ( mRulerState == StateMove ) {
600
      move( QCursor::pos() - mDragOffset );
601 602 603 604 605 606 607 608 609 610 611 612 613 614
    } else if ( mRulerState == StateBegin ) {
      QRect r = geometry();
      if ( mHorizontal ) {
        r.setLeft( QCursor::pos().x() - mDragOffset.x() );
      } else {
        r.setTop( QCursor::pos().y() - mDragOffset.y() );
      }
      setGeometry( r );
    } else if ( mRulerState == StateEnd ) {
      QPoint end = QCursor::pos() + mDragOffset - pos();
      QSize size = mHorizontal
        ? QSize( end.x(), height() )
        : QSize( width(), end.y() );
      resize( size );
615
    }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
616
  } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
617
    QPoint cpos = localCursorPos();
618 619 620
    mRulerState = StateNone;
    if ( beginRect().contains( cpos ) || endRect().contains( cpos) ) {
      setCursor( resizeCursor() );
621
    } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
622
      setCursor( mCrossCursor );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
623
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
624
    update();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
625 626 627 628
  }
}

/**
Mathias Soeken's avatar
Mathias Soeken committed
629 630 631 632
 * overwritten for dragging and context menu
 */
void KLineal::mousePressEvent( QMouseEvent *inEvent )
{
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
633 634 635
  mLastClickPos = QCursor::pos();

  QRect gr = geometry();
636
  mDragOffset = mLastClickPos - gr.topLeft();
Mathias Soeken's avatar
Mathias Soeken committed
637
  if ( inEvent->button() == Qt::LeftButton ) {
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
    if ( mRulerState < StateMove ) {
      if ( beginRect().contains( mDragOffset ) ) {
        mRulerState = StateBegin;
        grabMouse( resizeCursor() );
      } else if ( endRect().contains( mDragOffset ) ) {
        mDragOffset = gr.bottomRight() - mLastClickPos;
        mRulerState = StateEnd;
        grabMouse( resizeCursor() );
      } else {
        if ( nativeMove() ) {
          startNativeMove( inEvent );
        } else {
          mRulerState = StateMove;
          grabMouse( Qt::SizeAllCursor );
        }
653
      }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
654
    }
Mathias Soeken's avatar
Mathias Soeken committed
655
  } else if ( inEvent->button() == Qt::MidButton ) {
656
    mClicked = true;
657
    rotate();
Mathias Soeken's avatar
Mathias Soeken committed
658
  } else if ( inEvent->button() == Qt::RightButton ) {
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
659 660 661
    showMenu();
  }
}
Mathias Soeken's avatar
Mathias Soeken committed
662

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
#ifdef KRULER_HAVE_X11
bool KLineal::nativeMove() const
{
  return QX11Info::isPlatformX11() && RulerSettings::self()->nativeMoving();
}

void KLineal::startNativeMove( QMouseEvent *inEvent )
{
  xcb_ungrab_pointer( QX11Info::connection(), QX11Info::appTime() );
  NETRootInfo wm_root( QX11Info::connection(), NET::WMMoveResize );
  wm_root.moveResizeRequest( winId(), inEvent->globalX(), inEvent->globalY(), NET::Move );
}

void KLineal::stopNativeMove( QMouseEvent *inEvent )
{
  NETRootInfo wm_root( QX11Info::connection(), NET::WMMoveResize );
  wm_root.moveResizeRequest( winId(), inEvent->globalX(), inEvent->globalY(), NET::MoveResizeCancel );
}
#else
bool KLineal::nativeMove() const
{
  return false;
}

void KLineal::startNativeMove( QMouseEvent *inEvent )
{
  Q_UNUSED( inEvent );
}

void KLineal::stopNativeMove( QMouseEvent *inEvent )
{
  Q_UNUSED( inEvent );
}
#endif

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
698
/**
Mathias Soeken's avatar
Mathias Soeken committed
699 700
 * overwritten for dragging
 */
701
void KLineal::mouseReleaseEvent( QMouseEvent *inEvent )
Mathias Soeken's avatar
Mathias Soeken committed
702
{
703 704 705
  if ( mRulerState != StateNone ) {
    mRulerState = StateNone;
    releaseMouse();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
706
    saveSettings();
707 708
  } else if ( nativeMove() ) {
    stopNativeMove( inEvent );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
709 710
  }
}
Mathias Soeken's avatar
Mathias Soeken committed
711

712 713
void KLineal::wheelEvent( QWheelEvent *e )
{
714 715
  int numDegrees = e->delta() / 8;
  int numSteps = numDegrees / 15;
716

717 718 719 720
  // changing offset
  if ( e->buttons() == Qt::LeftButton ) {
    if ( !mRelativeScale ) {
      mOffset += numSteps;
721

722 723 724
      repaint();
      saveSettings();
    }
725 726 727
  }

  QWidget::wheelEvent( e );
728 729
}

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
730
/**
Mathias Soeken's avatar
Mathias Soeken committed
731 732 733 734 735
 * draws the scale according to the orientation
 */
void KLineal::drawScale( QPainter &painter )
{
  painter.setPen( Qt::black );
736
  QFont font = mScaleFont;
Mathias Soeken's avatar
Mathias Soeken committed
737
  painter.setFont( font );
Aurélien Gâteau's avatar
Aurélien Gâteau committed
738
  int longLen = length();
Mathias Soeken's avatar
Mathias Soeken committed
739

740 741 742
  if ( !mRelativeScale ) {
    int digit;
    int len;
743 744 745
    // Draw from -1 to longLen rather than from 0 to longLen - 1 to take into
    // account the offset applied in drawScaleTick
    for ( int x = -1; x <= longLen; ++x ) {
746 747 748 749 750 751 752
      if ( mLeftToRight ) {
        digit = x + mOffset;
      } else {
        digit = longLen - x + mOffset;
      }

      if ( digit % 2 ) continue;
753

Aurélien Gâteau's avatar
Aurélien Gâteau committed
754 755 756 757 758 759 760 761 762
      if ( digit % 100 == 0 ) {
        len = LARGE_TICK_SIZE;
      } else if ( digit % 20 == 0 ) {
        len = MEDIUM2_TICK_SIZE;
      } else if (digit % 10 == 0) {
        len = MEDIUM1_TICK_SIZE;
      } else {
        len = SMALL_TICK_SIZE;
      }
763

764
      if ( digit % 100 == 0 && digit != 0 ) {
765 766
        QString units;
        units.sprintf( "%d", digit );
767
        drawScaleText( painter, x, units );
768 769
      }

770
      drawScaleTick( painter, x, len );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
771
    }
772 773 774 775 776 777 778
  } else {
    float step = longLen / 100.f;
    int len;

    for ( int i = 0; i <= 100; ++i ) {
      int x = (int)( i * step );

779
      if ( i % 10 == 0 && i != 0 && i != 100 ) {
780 781 782
        QString units;
        int value = mLeftToRight ? i : ( 100 - i );
        units.sprintf( "%d%%", value );
783
        drawScaleText( painter, x, units );
Aurélien Gâteau's avatar
Aurélien Gâteau committed
784 785 786
        len = MEDIUM2_TICK_SIZE;
      } else {
        len = SMALL_TICK_SIZE;
787
      }
Mathias Soeken's avatar
Mathias Soeken committed
788

789
      drawScaleTick( painter, x, len );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
790 791 792
    }
  }
}
Mathias Soeken's avatar
Mathias Soeken committed
793

794 795 796 797 798 799 800 801 802
void KLineal::drawScaleText( QPainter &painter, int x, const QString &text )
{
  QFontMetrics metrics = painter.fontMetrics();
  QSize textSize = metrics.size( Qt::TextSingleLine, text );
  int w = width();
  int h = height();
  int tw = textSize.width();
  int th = metrics.ascent();

803
  if ( mHorizontal ) {
804
    painter.drawText( x - tw / 2, (h + th) / 2, text );
805
  } else {
806 807 808 809 810 811
    painter.drawText( (w - tw) / 2, x + th / 2, text );
  }
}

void KLineal::drawScaleTick( QPainter &painter, int x, int len )
{
812
  painter.setOpacity( TICK_OPACITY );
813 814
  int w = width();
  int h = height();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
815
  // Offset by one because we are measuring lengths, not positions, so when the
816
  // indicator is at position 0 it measures a length of 1 pixel.
Aurélien Gâteau's avatar
Aurélien Gâteau committed
817 818 819 820 821
  if ( mLeftToRight ) {
    --x;
  } else {
    ++x;
  }
822
  if ( mHorizontal ) {
823 824
    painter.drawLine( x, 0, x, len );
    painter.drawLine( x, h, x, h - len );
825
  } else {
826 827 828
    painter.drawLine( 0, x, len, x );
    painter.drawLine( w, x, w - len, x );
  }
829
  painter.setOpacity( 1 );
830 831
}

832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
void KLineal::drawResizeHandle( QPainter &painter, Qt::Edge edge )
{
  QRect rect;
  switch ( edge ) {
  case Qt::LeftEdge:
  case Qt::TopEdge:
    rect = beginRect();
    break;
  case Qt::RightEdge:
  case Qt::BottomEdge:
    rect = endRect();
    break;
  }
  painter.setOpacity( RESIZE_HANDLE_OPACITY );
  if ( mHorizontal ) {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
847 848
    int y1 = ( THICKNESS - RESIZE_HANDLE_THICKNESS ) / 2;
    int y2 = y1 + RESIZE_HANDLE_THICKNESS - 1;
849 850 851 852
    for ( int x = rect.left() + 1; x < rect.right(); x += 2 ) {
      painter.drawLine( x, y1, x, y2 );
    }
  } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
853 854
    int x1 = ( THICKNESS - RESIZE_HANDLE_THICKNESS ) / 2;
    int x2 = x1 + RESIZE_HANDLE_THICKNESS - 1;
855 856 857 858 859 860 861
    for ( int y = rect.top() + 1; y < rect.bottom(); y += 2 ) {
      painter.drawLine( x1, y, x2, y );
    }
  }
  painter.setOpacity( 1 );
}