klineal.cpp 24.8 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>
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
7 8 9 10 11 12 13 14 15 16 17
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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
18 19
#include "klineal.h"

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

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

48 49 50 51
#include "krulerconfig.h"

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

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

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

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

Aurélien Gâteau's avatar
Aurélien Gâteau committed
65 66 67 68
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;
69
static const qreal TICK_OPACITY = 0.3;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
70 71

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

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
80
static const int CURSOR_SIZE = 15; // Must be an odd number
Aurélien Gâteau's avatar
Aurélien Gâteau committed
81

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Aurélien Gâteau's avatar
Aurélien Gâteau committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209
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 );
}

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

338 339
  updateScaleDirectionMenuItem();

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

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

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

  QString label;

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

  mScaleDirectionAction->setText( label );
}

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

QRect KLineal::endRect() const
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
373
  int shortLen = RESIZE_HANDLE_THICKNESS;
374
  return mHorizontal
Aurélien Gâteau's avatar
Aurélien Gâteau committed
375 376
    ? 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 );
377 378 379 380 381 382 383
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

476

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

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

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

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

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

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

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

Aurélien Gâteau's avatar
Aurélien Gâteau committed
523 524 525 526 527 528 529
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
530
QString KLineal::indicatorText() const
Mathias Soeken's avatar
Mathias Soeken committed
531
{
Aurélien Gâteau's avatar
Aurélien Gâteau committed
532
  int xy = mHorizontal ? localCursorPos().x() : localCursorPos().y();
533
  if ( !mRelativeScale ) {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
534 535
    int len = mLeftToRight ? xy + 1 : length() - xy;
    return i18n( "%1 px", len );
536
  } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
537
    int len = ( xy * 100.f ) / length();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
538

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

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

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

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

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

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

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

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

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

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

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

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

Aurélien Gâteau's avatar
Aurélien Gâteau committed
594 595 596 597
  if ( mRulerState >= StateMove ) {
    if ( mouseGrabber() != this ) {
      return;
    }
598
    if ( mRulerState == StateMove ) {
599
      move( QCursor::pos() - mDragOffset );
600 601 602 603 604 605 606 607 608 609 610 611 612 613
    } 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 );
614
    }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
615
  } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
616
    QPoint cpos = localCursorPos();
617 618 619
    mRulerState = StateNone;
    if ( beginRect().contains( cpos ) || endRect().contains( cpos) ) {
      setCursor( resizeCursor() );
620
    } else {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
621
      setCursor( mCrossCursor );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
622
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
623
    update();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
624 625 626 627
  }
}

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

  QRect gr = geometry();
635
  mDragOffset = mLastClickPos - gr.topLeft();
Mathias Soeken's avatar
Mathias Soeken committed
636
  if ( inEvent->button() == Qt::LeftButton ) {
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
    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 );
        }
652
      }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
653
    }
Mathias Soeken's avatar
Mathias Soeken committed
654
  } else if ( inEvent->button() == Qt::MidButton ) {
655
    mClicked = true;
656
    rotate();
Mathias Soeken's avatar
Mathias Soeken committed
657
  } else if ( inEvent->button() == Qt::RightButton ) {
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
658 659 660
    showMenu();
  }
}
Mathias Soeken's avatar
Mathias Soeken committed
661

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

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

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

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

  QWidget::wheelEvent( e );
727 728
}

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

739 740 741 742 743 744 745 746 747 748 749
  if ( !mRelativeScale ) {
    int digit;
    int len;
    for ( int x = 0; x < longLen; ++x ) {
      if ( mLeftToRight ) {
        digit = x + mOffset;
      } else {
        digit = longLen - x + mOffset;
      }

      if ( digit % 2 ) continue;
750

Aurélien Gâteau's avatar
Aurélien Gâteau committed
751 752 753 754 755 756 757 758 759
      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;
      }
760

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

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

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

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

786
      drawScaleTick( painter, x, len );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
787 788 789
    }
  }
}
Mathias Soeken's avatar
Mathias Soeken committed
790

791 792 793 794 795 796 797 798 799
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();

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

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

829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
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
844 845
    int y1 = ( THICKNESS - RESIZE_HANDLE_THICKNESS ) / 2;
    int y2 = y1 + RESIZE_HANDLE_THICKNESS - 1;
846 847 848 849
    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
850 851
    int x1 = ( THICKNESS - RESIZE_HANDLE_THICKNESS ) / 2;
    int x2 = x1 + RESIZE_HANDLE_THICKNESS - 1;
852 853 854 855 856 857 858
    for ( int y = rect.top() + 1; y < rect.bottom(); y += 2 ) {
      painter.drawLine( x1, y, x2, y );
    }
  }
  painter.setOpacity( 1 );
}

Aurélien Gâteau's avatar
Aurélien Gâteau committed
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876