1 /***************************************************************************
2                           kmagview.cpp  -  description
3                              -------------------
4     begin                : Mon Feb 12 23:45:41 EST 2001
5     copyright            : (C) 2001-2003 by Sarang Lakare
6     email                : sarang#users.sourceforge.net
7     copyright            : (C) 2003-2004 by Olaf Schmidt
8     email                : ojschmidt@kde.org
9     copyright            : (C) 2008 by Matthew Woehlke
10     email                : mw_triad@users.sourceforge.net
11     copyright              (C) 2010 Sebastian Sauer
12     email                  sebsauer@kdab.com
13  ***************************************************************************/
14 
15 /***************************************************************************
16  *                                                                         *
17  *   This program is free software; you can redistribute it and/or modify  *
18  *   it under the terms of the GNU General Public License as published by  *
19  *   the Free Software Foundation; either version 2 of the License, or     *
20  *   (at your option) any later version.                                   *
21  *                                                                         *
22  ***************************************************************************/
23 
24 
25 // application specific includes
26 #include "kmagzoomview.h"
27 #include "colorsim.h"
28 
29 // include files for Qt
30 #include <QApplication>
31 #include <QBitmap>
32 #include <QDesktopWidget>
33 #include <QScrollBar>
34 #include <QScreen>
35 #include <QPainter>
36 
37 // include files for KF5
38 #include <KLocalizedString>
39 #include <chrono>
40 
41 using namespace std::chrono_literals;
42 
43 #ifdef QAccessibilityClient_FOUND
44 #include <qaccessibilityclient/accessibleobject.h>
45 #endif
46 
47 // include bitmaps for cursors
48 static const uchar left_ptr_bits[] = {
49    0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00,
50    0xf8, 0x01, 0xf8, 0x03, 0xf8, 0x07, 0xf8, 0x00, 0xd8, 0x00, 0x88, 0x01,
51    0x80, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00};
52 
53 static const uchar left_ptrmsk_bits[] = {
54    0x0c, 0x00, 0x1c, 0x00, 0x3c, 0x00, 0x7c, 0x00, 0xfc, 0x00, 0xfc, 0x01,
55    0xfc, 0x03, 0xfc, 0x07, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x01, 0xdc, 0x03,
56    0xcc, 0x03, 0x80, 0x07, 0x80, 0x07, 0x00, 0x03};
57 
58 static const uchar phand_bits[] = {
59   0x00, 0x00, 0x00, 0x00,  0xfe, 0x01, 0x00, 0x00,  0x01, 0x02, 0x00, 0x00,
60   0x7e, 0x04, 0x00, 0x00,  0x08, 0x08, 0x00, 0x00,  0x70, 0x08, 0x00, 0x00,
61   0x08, 0x08, 0x00, 0x00,  0x70, 0x14, 0x00, 0x00,  0x08, 0x22, 0x00, 0x00,
62   0x30, 0x41, 0x00, 0x00,  0xc0, 0x20, 0x00, 0x00,  0x40, 0x12, 0x00, 0x00,
63   0x80, 0x08, 0x00, 0x00,  0x00, 0x05, 0x00, 0x00,  0x00, 0x02, 0x00, 0x00,
64   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
65   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
66   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
67   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
68   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
69   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00 };
70 static const uchar phandm_bits[] = {
71   0xfe, 0x01, 0x00, 0x00,  0xff, 0x03, 0x00, 0x00,  0xff, 0x07, 0x00, 0x00,
72   0xff, 0x0f, 0x00, 0x00,  0xfe, 0x1f, 0x00, 0x00,  0xf8, 0x1f, 0x00, 0x00,
73   0xfc, 0x1f, 0x00, 0x00,  0xf8, 0x3f, 0x00, 0x00,  0xfc, 0x7f, 0x00, 0x00,
74   0xf8, 0xff, 0x00, 0x00,  0xf0, 0x7f, 0x00, 0x00,  0xe0, 0x3f, 0x00, 0x00,
75   0xc0, 0x1f, 0x00, 0x00,  0x80, 0x0f, 0x00, 0x00,  0x00, 0x07, 0x00, 0x00,
76   0x00, 0x02, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
77   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
78   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
79   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
80   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
81   0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00 };
82 
83 
KMagZoomView(QWidget * parent,const char * name)84 KMagZoomView::KMagZoomView(QWidget *parent, const char *name)
85   : QAbstractScrollArea(parent),
86     m_selRect(0, 0, 128, 128, this),
87     m_grabTimer(parent),
88     m_mouseViewTimer(parent),
89     m_latestCursorPos(0,0),
90     m_followMouse(false),
91     m_followFocus(false),
92     m_showMouse(1),
93     m_zoom(1.0),
94     m_rotation(0),
95     m_colormode(0),
96     m_fitToWindow(true)
97 {
98   setObjectName( QLatin1String( name ));
99 
100   viewport()->setMouseTracking(true);
101   viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
102   viewport()->setAutoFillBackground(false);
103   viewport()->setFocusPolicy(Qt::StrongFocus);
104 
105   // init the zoom matrix
106   setupMatrix();
107 
108   m_ctrlKeyPressed = false;
109   m_shiftKeyPressed = false;
110   m_refreshSwitch = true;
111   m_refreshSwitchStateOnHide = m_refreshSwitch;
112 
113   // set the refresh rate
114   setRefreshRate(10);
115 
116   // connect it to grabFrame()
117   connect(&m_grabTimer, &QTimer::timeout, this, &KMagZoomView::grabFrame);
118   // start the grabTimer
119   m_grabTimer.start(static_cast<int>(1000.0/m_fps));
120 
121   // connect it to updateMouseView()
122   connect(&m_mouseViewTimer, &QTimer::timeout, this, &KMagZoomView::updateMouseView);
123   // start the grabTimer @ 25 frames per second!
124   m_mouseViewTimer.start(40ms);
125 
126   this->setWhatsThis( i18n("This is the main window which shows the contents of the\
127  selected region. The contents will be magnified according to the zoom level that is set."));
128 
129   // different ways to show the cursor.
130   m_showMouseTypes << QStringLiteral( "Hidden" ) << QStringLiteral( "Box" ) << QStringLiteral( "Arrow" ) << QStringLiteral( "Actual" );
131 
132   if(m_fitToWindow)
133     fitToWindow();
134 
135 #ifdef QAccessibilityClient_FOUND
136   //subscribe to focus events from registry
137   m_registry.subscribeEventListeners(QAccessibleClient::Registry::Focus | QAccessibleClient::Registry::TextCaretMoved);
138 #endif
139 }
140 
~KMagZoomView()141 KMagZoomView::~KMagZoomView()
142 {
143 }
144 
contentsX() const145 int KMagZoomView::contentsX() const
146 {
147   return horizontalScrollBar()->value();
148 }
149 
contentsY() const150 int KMagZoomView::contentsY() const
151 {
152   return verticalScrollBar()->value();
153 }
154 
contentsWidth() const155 int KMagZoomView::contentsWidth() const
156 {
157   return horizontalScrollBar()->pageStep();
158 }
159 
contentsHeight() const160 int KMagZoomView::contentsHeight() const
161 {
162   return verticalScrollBar()->pageStep();
163 }
164 
visibleWidth() const165 int KMagZoomView::visibleWidth() const
166 {
167   return viewport()->width();
168 }
169 
visibleHeight() const170 int KMagZoomView::visibleHeight() const
171 {
172   return viewport()->height();
173 }
174 
setContentsPos(int x,int y)175 void KMagZoomView::setContentsPos(int x, int y)
176 {
177   horizontalScrollBar()->setValue(x);
178   verticalScrollBar()->setValue(y);
179 }
180 
setupMatrix()181 void KMagZoomView::setupMatrix()
182 {
183   m_zoomMatrix.reset();
184   m_zoomMatrix.scale(m_zoom, m_zoom);
185   m_zoomMatrix.rotate(m_rotation);
186 }
187 
188 /**
189  * This function will set/reset mouse following of grab window.
190  */
followMouse(bool follow)191 void KMagZoomView::followMouse(bool follow)
192 {
193   m_followMouse = follow;
194   m_mouseMode = Normal;
195   if(follow) {
196     setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
197     setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
198   } else {
199     setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
200     setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
201   }
202 }
203 
204 #ifdef QAccessibilityClient_FOUND
205 
followBoth(bool follow)206 void KMagZoomView::followBoth(bool follow)
207 {
208     m_followBoth = follow;
209     if(follow){
210         m_followMouse = true;
211         m_followFocus = false;
212         setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
213         setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
214 
215         connect(&m_registry, SIGNAL(focusChanged(QAccessibleClient::AccessibleObject)),
216                 this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
217         connect(&m_registry, SIGNAL(textCaretMoved(QAccessibleClient::AccessibleObject,int)),
218                 this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
219     } else {
220         setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
221         setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
222 
223         disconnect(this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
224     }
225 }
226 
227 /**
228  * This function will set/reset keyboard focus following of grab window.
229  */
followFocus(bool follow)230 void KMagZoomView::followFocus(bool follow)
231 {
232   if(m_followFocus == follow)
233       return;
234   m_followFocus = follow;
235   m_mouseMode = Normal;
236   if(follow) {
237     setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
238     setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
239 
240     connect(&m_registry,SIGNAL(focusChanged(QAccessibleClient::AccessibleObject)),
241             this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
242     connect(&m_registry, SIGNAL(textCaretMoved(QAccessibleClient::AccessibleObject,int)),
243             this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
244   } else {
245     setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
246     setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
247 
248     disconnect(this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
249   }
250 }
251 
focusChanged(const QAccessibleClient::AccessibleObject & object)252 void KMagZoomView::focusChanged(const QAccessibleClient::AccessibleObject &object)
253 {
254     m_oldFocus = object.focusPoint();
255     if(m_followBoth && !m_selRect.contains(m_oldFocus)) {
256         QCursor::setPos(m_oldFocus);
257         m_followFocus = true;
258         m_followMouse = false;
259     }
260 }
261 
262 #endif
263 
264 /**
265  * Called when the widget is hidden. Stop refresh when this happens.
266  */
hideEvent(QHideEvent *)267 void KMagZoomView::hideEvent( QHideEvent* )
268 {
269   // Save the state of the refresh switch.. the state will be restored
270   // when showEvent is called
271   m_refreshSwitchStateOnHide = m_refreshSwitch;
272 
273   // Check if refresh is ON
274   if(m_refreshSwitch) {
275     toggleRefresh();
276   }
277 }
278 
279 
280 /**
281  * Called when the widget is shown. Start refresh when this happens.
282  */
showEvent(QShowEvent *)283 void KMagZoomView::showEvent( QShowEvent* )
284 {
285   // Check if refresh switch was ON when hide was called and if currently it is OFF
286   if(m_refreshSwitchStateOnHide && !m_refreshSwitch) {
287     // start the refresh in that case
288     toggleRefresh();
289   }
290 }
291 
292 /**
293  * Called when the widget is resized. Check if fitToWindow is active when this happens.
294  */
resizeEvent(QResizeEvent * e)295 void KMagZoomView::resizeEvent( QResizeEvent * e )
296 {
297   horizontalScrollBar()->setRange(0, contentsWidth() - visibleWidth());
298   verticalScrollBar()->setRange(0, contentsHeight() - visibleHeight());
299   QAbstractScrollArea::resizeEvent(e);
300   if(m_fitToWindow)
301     fitToWindow();
302 }
303 
304 /**
305  * Called when the widget is to be repainted.
306  *
307  * @param p
308  */
paintEvent(QPaintEvent * e)309 void KMagZoomView::paintEvent(QPaintEvent *e)
310 {
311   if(m_coloredPixmap.isNull())
312     return;
313 
314   QPainter p(viewport());
315   int clipx = e->rect().x();
316   int clipy = e->rect().x();
317   int clipw = e->rect().width();
318   int cliph = e->rect().height();
319 
320   // Paint empty areas Qt::black
321   if (contentsX()+contentsWidth() < visibleWidth())
322     p.fillRect (
323         QRect (contentsX()+contentsWidth(), clipy, visibleWidth()-contentsX()-contentsWidth(), cliph)
324         & e->rect(),
325         Qt::black);
326   if (contentsY()+contentsHeight() < visibleHeight())
327     p.fillRect (
328         QRect (clipx, contentsY()+contentsHeight(), clipw, visibleHeight()-contentsY()-contentsHeight())
329         & e->rect(),
330         Qt::black);
331 
332   p.translate(visibleWidth() / 2.0, visibleHeight() / 2.0);
333   p.setWorldTransform(m_zoomMatrix, true);
334   const double ratio = 0.5 / m_coloredPixmap.devicePixelRatio();
335   p.translate(-m_coloredPixmap.width() * ratio, -m_coloredPixmap.height() * ratio);
336   p.drawPixmap(QPoint(clipx-contentsX(), clipy-contentsY()), m_coloredPixmap);
337   p.end();
338 
339   if (m_showMouse)
340     paintMouseCursor(viewport(), calcMousePos (m_refreshSwitch));
341 }
342 
343 /**
344  * Draws the mouse cursor according to the current selection of the type of
345  * mouse cursor to draw.
346  */
paintMouseCursor(QPaintDevice * dev,const QPoint & mousePos)347 void KMagZoomView::paintMouseCursor(QPaintDevice *dev, const QPoint &mousePos)
348 {
349   if(!dev)
350     return;
351 
352   // painter for the zoom view
353   QPainter pz(dev);
354 
355     // How to show the mouse :
356 
357     switch(m_showMouse) {
358     case 1:
359       // 1. Square around the pixel
360       pz.setPen(Qt::white);
361 #ifdef __GNUC__
362 #warning "Port Qt4 pz.setRasterOp(Qt::XorROP);";
363 #endif
364       //pz.setRasterOp(Qt::XorROP);
365       pz.drawRect(mousePos.x()-1, mousePos.y()-1, (int)m_zoom+2, (int)m_zoom+2);
366       break;
367 
368     case 2:
369     {
370       // 2. Arrow cursor
371       pz.setPen(Qt::black);
372       pz.setBackground(Qt::white);
373 
374       QPixmap sCursor(16, 16);
375       QBitmap cursor = QBitmap::fromData( QSize(16,  16),  left_ptr_bits);
376       QBitmap mask = QBitmap::fromData( QSize(16,  16),  left_ptrmsk_bits);
377       sCursor.setMask(mask);
378       QPainter p(&sCursor);
379       p.setPen(Qt::gray);
380       p.drawPixmap(0, 0, mask);
381       p.setPen(Qt::black);
382       p.drawPixmap(0, 0, cursor);
383       p.end();
384       sCursor = sCursor.transformed(m_zoomMatrix);
385 
386       // since hot spot is at 3,1
387       if (m_rotation == 0)
388         pz.drawPixmap(mousePos.x()-(int)(3.0*m_zoom), mousePos.y()-(int)m_zoom, sCursor);
389       else if (m_rotation == 90)
390         pz.drawPixmap(mousePos.x()-(int)(16.0*m_zoom), mousePos.y()-(int)(3.0*m_zoom), sCursor);
391       else if (m_rotation == 180)
392         pz.drawPixmap(mousePos.x()-(int)(13.0*m_zoom), mousePos.y()-(int)(16.0*m_zoom), sCursor);
393       else if (m_rotation == 270)
394         pz.drawPixmap(mousePos.x()-(int)m_zoom, mousePos.y()-(int)(13.0*m_zoom), sCursor);
395     }
396     break;
397 
398     case 3:
399     {
400       // 3. Actual cursor
401       // Get the current cursor type
402       QWidget *dummy  = QApplication::topLevelAt(QCursor::pos());
403       if(!dummy)
404         break;
405       switch(this->cursor().shape())  {
406 			  case Qt::ArrowCursor :
407          {
408           // 2. Arrow cursor
409           pz.setPen(Qt::black);
410           pz.setBackground(Qt::white);
411 
412           QBitmap sCursor = QBitmap::fromData( QSize(16,  16),  left_ptr_bits);
413           QBitmap mask = QBitmap::fromData( QSize(16,  16),  left_ptrmsk_bits);
414           sCursor.setMask(mask);
415           sCursor = sCursor.transformed(m_zoomMatrix);
416 
417           // since hot spot is at 3,1
418           pz.drawPixmap(mousePos.x()-(int)(3.0*m_zoom), mousePos.y()-(int)m_zoom, sCursor);
419         }
420         break;
421         default:
422           QBitmap sCursor = QBitmap::fromData( QSize(32,  32),  phand_bits);
423           QBitmap mask = QBitmap::fromData( QSize(32,  32),  phandm_bits);
424           sCursor.setMask(mask);
425 
426           pz.drawPixmap(mousePos.x(), mousePos.y(), sCursor);
427         break;
428       } // switch(cursor)
429 
430 
431     }
432     break;
433 
434     default:
435       // do not show anything
436       break;
437     } // switch(m_showMouse)
438 }
439 
440 
calcMousePos(bool updateMousePos)441 QPoint KMagZoomView::calcMousePos(bool updateMousePos)
442 {
443   // get position of mouse wrt selRect
444   if(updateMousePos) { // get a new position only if asked
445     m_latestCursorPos = QCursor::pos();
446     m_latestCursorPos -= QPoint(m_selRect.x(), m_selRect.y());
447   }
448 
449   // get coordinates of the pixel w.r.t. the zoomed pixmap
450   if (m_rotation == 90)
451     return QPoint ((int)((float)(m_selRect.height()-m_latestCursorPos.y())*m_zoom),
452                    (int)((float)m_latestCursorPos.x()*m_zoom));
453   else if (m_rotation == 180)
454     return QPoint ((int)((float)(m_selRect.width()-m_latestCursorPos.x())*m_zoom),
455                    (int)((float)(m_selRect.height()-m_latestCursorPos.y())*m_zoom));
456   else if (m_rotation == 270)
457     return QPoint ((int)((float)m_latestCursorPos.y()*m_zoom),
458                    (int)((float)(m_selRect.width()-m_latestCursorPos.x())*m_zoom));
459   else
460     return QPoint ((int)((float)m_latestCursorPos.x()*m_zoom),
461                    (int)((float)m_latestCursorPos.y()*m_zoom));
462 }
463 
464 
465 // MOUSE ACTIONS
466 
467 /**
468  * Called when mouse is clicked inside the window.
469  *
470  * @param e
471  */
mousePressEvent(QMouseEvent * e)472 void KMagZoomView::mousePressEvent(QMouseEvent *e)
473 {
474   switch(e->button()) {
475   case Qt::LeftButton :
476     if(m_ctrlKeyPressed) {
477       // check if currently in resize mode
478       // don't do anything if fitToWindow is enabled
479       if ((m_mouseMode != ResizeSelection) && !m_fitToWindow) {
480         // set the mode to ResizeSelection
481         m_mouseMode = ResizeSelection;
482 
483         // set mouse cursor to "resize all direction"
484         setCursor(Qt::SizeAllCursor);
485 
486         // backup the old position
487         m_oldMousePos.setX(e->globalX());
488         m_oldMousePos.setY(e->globalY());
489 
490         // set the cursor position to the bottom-right of the selected region
491         QCursor::setPos(m_selRect.bottomRight());
492 
493         // show the selection rectangle
494         m_selRect.show();
495       }
496       else {
497         // ignore this button press.. so it goes to the parent
498         e->ignore();
499       }
500     } else if(m_shiftKeyPressed) {
501       // check if currently in move mode
502       // don't do anything if follow mouse is enabled
503       if ((m_mouseMode != MoveSelection) && !m_followMouse) {
504         m_mouseMode = MoveSelection;
505 
506         // set mouse cursor to cross hair
507         setCursor(Qt::CrossCursor);
508 
509         // backup the old position
510         m_oldMousePos.setX(e->globalX());
511         m_oldMousePos.setY(e->globalY());
512 
513         // set the cursor position to the center of the selected region
514         QCursor::setPos(m_selRect.center());
515 
516         // show the selected rectangle
517         m_selRect.show();
518       }
519       else {
520         // ignore this button press.. so it goes to the parent
521         e->ignore();
522       }
523     } else {
524       // check if currently in move mode
525       // don't do anything if follow mouse is enabled
526       if ((m_mouseMode != GrabSelection) && !m_followMouse) {
527         m_mouseMode = GrabSelection;
528 
529         // set mouse cursor to hand
530         setCursor(Qt::PointingHandCursor);
531 
532         // store the old position
533         m_oldMousePos.setX(e->globalX());
534         m_oldMousePos.setY(e->globalY());
535 
536         m_oldCenter = m_selRect.center();
537 
538         // show the selected rectangle
539         m_selRect.show();
540       }
541       else {
542         // ignore this button press.. so it goes to the parent
543         e->ignore();
544       }
545     }
546     break;
547 
548   case Qt::MiddleButton :
549     // check if currently in move mode
550     // don't do anything if follow mouse is enabled
551     if ((m_mouseMode != MoveSelection) && !m_followMouse) {
552       m_mouseMode = MoveSelection;
553 
554       // set mouse cursor to cross hair
555       setCursor(Qt::CrossCursor);
556 
557       // backup the old position
558       m_oldMousePos.setX(e->globalX());
559       m_oldMousePos.setY(e->globalY());
560 
561       // set the cursor position to the center of the selected region
562       QCursor::setPos(m_selRect.center());
563 
564       // show the selected rectangle
565       m_selRect.show();
566     }
567     else {
568       // ignore this button press.. so it goes to the parent
569       e->ignore();
570     }
571     break;
572   // do nothing
573   default:
574     // ignore this button press.. so it goes to the parent
575     e->ignore();
576     break;
577   }
578 }
579 
580 
581 /**
582  * Called when a mouse button is released
583  *
584  * @param e
585  */
mouseReleaseEvent(QMouseEvent * e)586 void KMagZoomView::mouseReleaseEvent(QMouseEvent *e)
587 {
588   switch(e->button()) {
589   case Qt::LeftButton :
590   case Qt::MiddleButton :
591     // check if currently in move mode
592     if(m_mouseMode == MoveSelection) {
593       // hide the selection window
594       m_selRect.hide();
595       // set the mouse mode to normal
596       m_mouseMode = Normal;
597 
598       // restore the cursor shape
599       setCursor(Qt::ArrowCursor);
600 
601       // restore the cursor position
602       QCursor::setPos(m_oldMousePos);
603     } else if(m_mouseMode == ResizeSelection) {
604       // hide the selection window
605       m_selRect.hide();
606       // set the mouse mode to normal
607       m_mouseMode = Normal;
608 
609       // restore the cursor shape
610       setCursor(Qt::ArrowCursor);
611 
612       // restore the cursor position
613       QCursor::setPos(m_oldMousePos);
614     } else if(m_mouseMode == GrabSelection) {
615       // hide the selection window
616       m_selRect.hide();
617 
618       // set the mouse mode to normal
619       m_mouseMode = Normal;
620 
621       // restore the cursor shape
622       setCursor(Qt::ArrowCursor);
623     }
624     break;
625 
626   case Qt::RightButton :
627     break;
628   case Qt::NoButton :
629     break;
630 
631   // do nothing
632   default:
633     ;
634   }
635 }
636 
637 
638 /**
639  * Called when mouse is moved inside the window
640  *
641  * @param e
642  */
mouseMoveEvent(QMouseEvent * e)643 void KMagZoomView::mouseMoveEvent(QMouseEvent *e)
644 {
645   if(m_mouseMode == ResizeSelection) {
646     // In resize selection mode
647     // set the current mouse position as the bottom, right corner
648     m_selRect.setRight(e->globalX());
649     m_selRect.setBottom(e->globalY());
650     m_selRect.update();
651     grabFrame();
652   } else if(m_mouseMode == MoveSelection) {
653      QPoint newCenter;
654 
655     // set new center to be the current mouse position
656     newCenter = e->globalPos();
657 
658     // make sure the mouse position is not taking the grab window outside
659     // the display
660     if(newCenter.x() < m_selRect.width()/2) {
661       // set X to the minimum possible X
662       newCenter.setX(m_selRect.width()/2);
663     } else if(newCenter.x() >=  QApplication::desktop()->width()-m_selRect.width()/2) {
664       // set X to the maximum possible X
665       newCenter.setX(QApplication::desktop()->width()-m_selRect.width()/2-1);
666     }
667 
668     if(newCenter.y() < m_selRect.height()/2) {
669       // set Y to the minimum possible Y
670       newCenter.setY(m_selRect.height()/2);
671     } else if(newCenter.y() >=  QApplication::desktop()->height()-m_selRect.height()/2) {
672       // set Y to the maximum possible Y
673       newCenter.setY(QApplication::desktop()->height()-m_selRect.height()/2-1);
674     }
675     // move to the new center
676     m_selRect.moveCenter(newCenter);
677     // update the grab rectangle display
678     m_selRect.update();
679     grabFrame();
680   } else if(m_mouseMode == GrabSelection) {
681      QPoint newPos;
682 
683     // get new position
684     newPos = e->globalPos();
685 
686     QPoint delta = (newPos - m_oldMousePos)/m_zoom;
687     QPoint newCenter = m_oldCenter-delta;
688 
689     // make sure the mouse position is not taking the grab window outside
690     // the display
691     if(newCenter.x() < m_selRect.width()/2) {
692       // set X to the minimum possible X
693       newCenter.setX(m_selRect.width()/2);
694     } else if(newCenter.x() >=  QApplication::desktop()->width()-m_selRect.width()/2) {
695       // set X to the maximum possible X
696       newCenter.setX(QApplication::desktop()->width()-m_selRect.width()/2-1);
697     }
698 
699     if(newCenter.y() < m_selRect.height()/2) {
700       // set Y to the minimum possible Y
701       newCenter.setY(m_selRect.height()/2);
702     } else if(newCenter.y() >=  QApplication::desktop()->height()-m_selRect.height()/2) {
703       // set Y to the maximum possible Y
704       newCenter.setY(QApplication::desktop()->height()-m_selRect.height()/2-1);
705     }
706 
707     // move to the new center
708     m_selRect.moveCenter(newCenter);
709     // update the grab rectangle display
710     m_selRect.update();
711     grabFrame();
712   }
713 }
714 
keyPressEvent(QKeyEvent * e)715 void KMagZoomView::keyPressEvent(QKeyEvent *e)
716 {
717   int offset = 16;
718   if (e->modifiers() & Qt::ShiftModifier)
719     offset = 1;
720 
721   if (e->key() == Qt::Key_Control)
722     m_ctrlKeyPressed = true;
723   else if (e->key() == Qt::Key_Shift)
724     m_shiftKeyPressed = true;
725   else if (e->key() == Qt::Key_Left)
726   {
727     if (e->modifiers() & Qt::ControlModifier)
728     {
729       if (offset >= m_selRect.width())
730         m_selRect.setWidth (1);
731       else
732         m_selRect.setWidth (m_selRect.width()-offset);
733   }
734     else if (contentsX() > 0)
735     {
736       offset = (int)(offset*m_zoom);
737       if (contentsX() > offset)
738         setContentsPos (contentsX()-offset, contentsY());
739       else
740         setContentsPos (0, contentsY());
741     }
742     else if (m_followMouse == false)
743     {
744       if (offset > m_selRect.x())
745         m_selRect.setX (0);
746       else
747         m_selRect.translate (-offset,0);
748     }
749     m_selRect.update();
750   }
751   else if (e->key() == Qt::Key_Right)
752   {
753     if (e->modifiers() & Qt::ControlModifier)
754     {
755       if (m_selRect.right()+offset >= QApplication::desktop()->width())
756         m_selRect.setRight (QApplication::desktop()->width()-1);
757       else
758         m_selRect.setRight (m_selRect.right()+offset);
759     }
760     else if (contentsX() < contentsWidth()-visibleWidth())
761     {
762       offset = (int)(offset*m_zoom);
763       if (contentsX()+offset < contentsWidth()-visibleWidth())
764         setContentsPos (contentsX()+offset, contentsY());
765       else
766         setContentsPos (contentsWidth()-visibleWidth(), contentsY());
767     }
768     else if (m_followMouse == false)
769     {
770       if (m_selRect.right()+offset >= QApplication::desktop()->width())
771         m_selRect.moveTopRight (QPoint (QApplication::desktop()->width()-1, m_selRect.top()));
772       else
773         m_selRect.translate (offset,0);
774     }
775     m_selRect.update();
776   }
777   else if (e->key() == Qt::Key_Up)
778   {
779     if (e->modifiers() & Qt::ControlModifier)
780     {
781       if (offset >= m_selRect.height())
782         m_selRect.setHeight (1);
783       else
784         m_selRect.setHeight (m_selRect.height()-offset);
785     }
786     else if (contentsY() > 0)
787     {
788       offset = (int)(offset*m_zoom);
789       if (contentsY() > offset)
790         setContentsPos (contentsX(), contentsY()-offset);
791       else
792         setContentsPos (contentsX(), 0);
793     }
794     else if (m_followMouse == false)
795     {
796       if (offset > m_selRect.y())
797         m_selRect.setY (0);
798       else
799         m_selRect.translate (0, -offset);
800     }
801     m_selRect.update();
802   }
803   else if (e->key() == Qt::Key_Down)
804   {
805     if (e->modifiers() & Qt::ControlModifier)
806     {
807       if (m_selRect.bottom()+offset >= QApplication::desktop()->height())
808         m_selRect.setBottom (QApplication::desktop()->height()-1);
809       else
810         m_selRect.setBottom (m_selRect.bottom()+offset);
811     }
812     else if (contentsY() < contentsHeight()-visibleHeight())
813     {
814       offset = (int)(offset*m_zoom);
815       if (contentsY()+offset < contentsHeight()-visibleHeight())
816         setContentsPos (contentsX(), contentsY()+offset);
817       else
818         setContentsPos (contentsX(), contentsHeight()-visibleHeight());
819     }
820     else if (m_followMouse == false)
821     {
822       if (m_selRect.bottom()+offset >= QApplication::desktop()->height())
823         m_selRect.moveBottomLeft (QPoint (m_selRect.left(), QApplication::desktop()->height()-1));
824       else
825         m_selRect.translate (0, offset);
826     }
827     m_selRect.update();
828   }
829   else
830     e->ignore();
831 }
832 
keyReleaseEvent(QKeyEvent * e)833 void KMagZoomView::keyReleaseEvent(QKeyEvent *e)
834 {
835   if (e->key() == Qt::Key_Control)
836     m_ctrlKeyPressed = false;
837   else if (e->key() == Qt::Key_Shift)
838     m_shiftKeyPressed = false;
839   else
840     e->ignore();
841 }
842 
focusOutEvent(QFocusEvent * e)843 void KMagZoomView::focusOutEvent(QFocusEvent *e)
844 {
845   if(e->lostFocus() == true) {
846     m_ctrlKeyPressed = false;
847     m_shiftKeyPressed = false;
848   }
849 }
850 
851 // SLOTS
852 
853 /**
854  * This will fit the zoom view to the view window, thus using the maximum
855  * possible space in the window.
856  */
fitToWindow()857 void KMagZoomView::fitToWindow()
858 {
859   unsigned int newWidth, newHeight;
860 
861   // this is a temporary solution, cast, maybe newWidth and newHeight should be float
862   if ((m_rotation == 90) || (m_rotation == 270))
863   {
864     newWidth = static_cast<unsigned int>((visibleHeight() + m_zoom - 1) / m_zoom);
865     newHeight = static_cast<unsigned int>((visibleWidth() + m_zoom - 1) / m_zoom);
866   } else {
867     newWidth = static_cast<unsigned int>((visibleWidth() + m_zoom - 1) / m_zoom);
868     newHeight = static_cast<unsigned int>((visibleHeight() + m_zoom - 1) / m_zoom);
869   }
870 
871   QPoint currCenter = m_selRect.center();
872 
873   m_selRect.setWidth(newWidth);
874   m_selRect.setHeight(newHeight);
875 
876    // make sure the selection window does not go outside of the display
877    if(currCenter.x() < m_selRect.width()/2) {
878      // set X to the minimum possible X
879      currCenter.setX(m_selRect.width()/2);
880    } else if(currCenter.x() >=  QApplication::desktop()->width()-m_selRect.width()/2) {
881      // set X to the maximum possible X
882      currCenter.setX(QApplication::desktop()->width()-m_selRect.width()/2-1);
883    }
884 
885    if(currCenter.y() < m_selRect.height()/2) {
886      // set Y to the minimum possible Y
887      currCenter.setY(m_selRect.height()/2);
888    } else if(currCenter.y() >=  QApplication::desktop()->height()-m_selRect.height()/2) {
889      // set Y to the maximum possible Y
890      currCenter.setY(QApplication::desktop()->height()-m_selRect.height()/2-1);
891    }
892 
893   m_selRect.moveCenter(currCenter);
894   // update the grab rectangle display
895   m_selRect.update();
896 //  m_fitToWindow = true;
897   viewport()->update();
898 }
899 
setFitToWindow(bool fit)900 void KMagZoomView::setFitToWindow(bool fit)
901 {
902   m_fitToWindow = fit;
903   if (fit)
904     fitToWindow();
905 }
906 
907 
908 /**
909  * Grabs frame from X
910  */
grabFrame()911 void KMagZoomView::grabFrame()
912 {
913   // check refresh status
914   if (!m_refreshSwitch)
915      return;
916 
917   // check if follow-mouse or follow-focus are enabled
918   if((m_followMouse || m_followFocus) && (m_mouseMode != ResizeSelection)) {
919     // center-position of the grab-area
920     QPoint newCenter;
921 
922     if(m_followMouse) {
923         // set new center to be the current mouse position
924         newCenter = QCursor::pos();
925 #ifdef QAccessibilityClient_FOUND
926     } else if(m_followFocus) {
927         // set the new center to the current keyboard cursor position
928         newCenter = m_oldFocus;
929         if(m_followBoth) {
930             m_followFocus=false;
931             m_followMouse=true;
932         }
933 #endif
934     }
935 
936     // make sure the mouse position is not taking the grab window outside
937     // the display
938     if(newCenter.x() < m_selRect.width()/2) {
939       // set X to the minimum possible X
940       newCenter.setX(m_selRect.width()/2);
941     } else if(newCenter.x() >=  QApplication::desktop()->width()-m_selRect.width()/2) {
942       // set X to the maximum possible X
943       newCenter.setX(QApplication::desktop()->width()-m_selRect.width()/2-1);
944     }
945 
946     if(newCenter.y() < m_selRect.height()/2) {
947       // set Y to the minimum possible Y
948       newCenter.setY(m_selRect.height()/2);
949     } else if(newCenter.y() >=  QApplication::desktop()->height()-m_selRect.height()/2) {
950       // set Y to the maximum possible Y
951       newCenter.setY(QApplication::desktop()->height()-m_selRect.height()/2-1);
952     }
953     // move to the new center
954     m_selRect.moveCenter(newCenter);
955 
956     // update the grab rectangle display
957     m_selRect.update();
958   }
959 
960   // define a normalized rectangle
961   QRect selRect = m_selRect.normalized();
962 
963   // grab screenshot from the screen and put it in the pixmap
964   QScreen *screen = qApp->primaryScreen(); // ## How to select the right screen?
965   m_coloredPixmap = screen->grabWindow(QApplication::desktop()->winId(), selRect.x(), selRect.y(),
966                                       selRect.width(), selRect.height());
967 
968   // colorize the grabbed pixmap
969   if (m_colormode != 0)
970     m_coloredPixmap = QPixmap::fromImage(ColorSim::recolor(m_coloredPixmap.toImage(), m_colormode));
971 
972   // erase background covered by kmag view ...
973   QRect viewRect = rect();
974   viewRect.translate(mapTo(window(), QPoint(0, 0)));
975   viewRect.translate(-selRect.topLeft());
976   viewRect.translate(window()->geometry().topLeft());
977   QRegion region(viewRect);
978 
979   // ... but exclude own popups ...
980   const QList<QWidget *> siblings = QApplication::topLevelWidgets();
981   for (QWidget *sibling : siblings) {
982     if (sibling != window() && (sibling->windowType() & Qt::Window) && sibling->isVisible()) {
983       QRect rect = sibling->frameGeometry();
984       rect.translate(-selRect.topLeft());
985       region -= rect;
986     }
987   }
988 
989   QPainter p(&m_coloredPixmap);
990   for (const QRect &rect : region) {
991     p.fillRect(rect, palette().dark());
992   }
993   p.end();
994 
995   QRect r = m_zoomMatrix.mapRect(m_coloredPixmap.rect());
996   // call repaint to display the newly grabbed image
997   horizontalScrollBar()->setPageStep(r.width());
998   verticalScrollBar()->setPageStep(r.height());
999   viewport()->update();
1000 }
1001 
1002 
1003 /**
1004  * Updates the mouse cursor in the zoom view.
1005  */
updateMouseView()1006 void KMagZoomView::updateMouseView()
1007 {
1008     if (m_fps < 8)
1009         viewport()->update();
1010 }
1011 
1012 /**
1013  * Toggles the state of refreshing.
1014  */
toggleRefresh()1015 void KMagZoomView::toggleRefresh()
1016 {
1017   if(m_refreshSwitch) {
1018     m_refreshSwitch = false;
1019     m_grabTimer.stop();
1020     m_mouseViewTimer.stop();
1021   } else {
1022     m_refreshSwitch = true;
1023     m_grabTimer.start(1000/m_fps);
1024     m_mouseViewTimer.start(40);
1025   }
1026 }
1027 
1028 /**
1029  * This function sets the zoom value to be used.
1030  */
setZoom(float zoom)1031 void KMagZoomView::setZoom(float zoom)
1032 {
1033   // use this zoom
1034   m_zoom = zoom;
1035 
1036   // update selection window size when zooming in if necessary
1037   if (m_fitToWindow)
1038     fitToWindow();
1039 
1040   // recompute the zoom matrix
1041   setupMatrix();
1042 
1043   viewport()->update();
1044 }
1045 
1046 /**
1047  * This function sets the rotation value to be used.
1048  */
setRotation(int rotation)1049 void KMagZoomView::setRotation(int rotation)
1050 {
1051   // use this rotation
1052   m_rotation = rotation;
1053 
1054   // update selection window size if necessary
1055   if (m_fitToWindow)
1056     fitToWindow();
1057 
1058   // recompute the zoom matrix
1059   setupMatrix();
1060 
1061   viewport()->update();
1062 }
1063 
1064 /**
1065  * Set a new color simulation mode.
1066  */
setColorMode(int mode)1067 void KMagZoomView::setColorMode(int mode)
1068 {
1069   if (m_colormode != mode) {
1070     m_colormode = mode;
1071     viewport()->update();
1072   }
1073 }
1074 
1075 /**
1076  * Set a new refresh rate.
1077  */
setRefreshRate(float fps)1078 void KMagZoomView::setRefreshRate(float fps)
1079 {
1080   if(fps < 0.1)
1081     return;
1082   m_fps = static_cast<unsigned int>(fps);
1083 
1084   if(m_grabTimer.isActive())
1085     m_grabTimer.start(static_cast<int>(1000.0/m_fps));
1086 }
1087 
showSelRect(bool show)1088 void KMagZoomView::showSelRect(bool show)
1089 {
1090   m_selRect.alwaysVisible(show);
1091   if(show) {
1092     m_selRect.show();
1093   } else if(m_mouseMode == Normal) {
1094     m_selRect.hide();
1095   }
1096 }
1097 
1098 /**
1099  * Sets the selection rectangle to the given position.
1100  */
setSelRectPos(const QRect & rect)1101 void KMagZoomView::setSelRectPos(const QRect & rect)
1102 {
1103   m_selRect.setRect(rect.x(), rect.y(), rect.width(), rect.height());
1104   m_selRect.update();
1105   grabFrame();
1106 }
1107 
showMouse(unsigned int type)1108 bool KMagZoomView::showMouse(unsigned int type)
1109 {
1110   if(int(type) > m_showMouseTypes.count()-1)
1111     return (false);
1112   else
1113     m_showMouse = type;
1114 
1115   return(true);
1116 }
1117 
getShowMouseType() const1118 unsigned int KMagZoomView::getShowMouseType() const
1119 {
1120   return (m_showMouse);
1121 }
1122 
getShowMouseStringList() const1123 QStringList KMagZoomView::getShowMouseStringList() const
1124 {
1125   return (m_showMouseTypes);
1126 }
1127 
1128 
1129 /**
1130  * Returns the image which is being displayed. It's again drawn by adding
1131  * the mouse cursor if needed.
1132  */
getImage()1133 QImage KMagZoomView::getImage()
1134 {
1135   QImage image = m_coloredPixmap.transformed(m_zoomMatrix).toImage();
1136 
1137   // show the pixel under mouse cursor
1138   if(m_showMouse && !image.isNull()) {
1139     // paint the mouse cursor w/o updating to a newer position
1140     paintMouseCursor(&image, calcMousePos(false));
1141   }
1142   return(image);
1143 }
1144