// SPDX-FileCopyrightText: 2001-2003 Sarang Lakare <sarang@users.sourceforge.net>
// SPDX-FileCopyrightText: 2003-2004 Olaf Schmidt <ojschmidt@kde.org>
// SPDX-FileCopyrightText: 2008 Matthew Woehlke <mw_triad@users.sourceforge.net>
// SPDX-FileCopyrightText: 2010 Sebastian Sauer <sebsauer@kdab.com>
// SPDX-License-Identifier: GPL-2.0-or-later

// application specific includes
#include "kmagzoomview.h"
#include "colorsim.h"

// include files for Qt
#include <QApplication>
#include <QBitmap>
#include <QScrollBar>
#include <QScreen>
#include <QPainter>

// include files for KF5
#include <KLocalizedString>
#include <chrono>

using namespace std::chrono_literals;

#if HAVE_QACCESSIBILITYCLIENT
#include <qaccessibilityclient/accessibleobject.h>
#endif

// include bitmaps for cursors
static const uchar left_ptr_bits[] = {
   0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00,
   0xf8, 0x01, 0xf8, 0x03, 0xf8, 0x07, 0xf8, 0x00, 0xd8, 0x00, 0x88, 0x01,
   0x80, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00};

static const uchar left_ptrmsk_bits[] = {
   0x0c, 0x00, 0x1c, 0x00, 0x3c, 0x00, 0x7c, 0x00, 0xfc, 0x00, 0xfc, 0x01,
   0xfc, 0x03, 0xfc, 0x07, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x01, 0xdc, 0x03,
   0xcc, 0x03, 0x80, 0x07, 0x80, 0x07, 0x00, 0x03};

static const uchar phand_bits[] = {
  0x00, 0x00, 0x00, 0x00,  0xfe, 0x01, 0x00, 0x00,  0x01, 0x02, 0x00, 0x00,
  0x7e, 0x04, 0x00, 0x00,  0x08, 0x08, 0x00, 0x00,  0x70, 0x08, 0x00, 0x00,
  0x08, 0x08, 0x00, 0x00,  0x70, 0x14, 0x00, 0x00,  0x08, 0x22, 0x00, 0x00,
  0x30, 0x41, 0x00, 0x00,  0xc0, 0x20, 0x00, 0x00,  0x40, 0x12, 0x00, 0x00,
  0x80, 0x08, 0x00, 0x00,  0x00, 0x05, 0x00, 0x00,  0x00, 0x02, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00 };
static const uchar phandm_bits[] = {
  0xfe, 0x01, 0x00, 0x00,  0xff, 0x03, 0x00, 0x00,  0xff, 0x07, 0x00, 0x00,
  0xff, 0x0f, 0x00, 0x00,  0xfe, 0x1f, 0x00, 0x00,  0xf8, 0x1f, 0x00, 0x00,
  0xfc, 0x1f, 0x00, 0x00,  0xf8, 0x3f, 0x00, 0x00,  0xfc, 0x7f, 0x00, 0x00,
  0xf8, 0xff, 0x00, 0x00,  0xf0, 0x7f, 0x00, 0x00,  0xe0, 0x3f, 0x00, 0x00,
  0xc0, 0x1f, 0x00, 0x00,  0x80, 0x0f, 0x00, 0x00,  0x00, 0x07, 0x00, 0x00,
  0x00, 0x02, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00 };


KMagZoomView::KMagZoomView(QWidget *parent, const char *name)
  : QAbstractScrollArea(parent),
    m_selRect(0, 0, 128, 128, this),
    m_grabTimer(parent),
    m_mouseViewTimer(parent),
    m_latestCursorPos(0,0),
    m_followMouse(false),
    m_followFocus(false),
    m_showMouse(1),
    m_zoom(1.0),
    m_rotation(0),
    m_colormode(0),
    m_fitToWindow(true)
{
  setObjectName( QLatin1String( name ));

  viewport()->setMouseTracking(true);
  viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
  viewport()->setAutoFillBackground(false);
  viewport()->setFocusPolicy(Qt::StrongFocus);

  // init the zoom matrix
  setupMatrix();

  m_ctrlKeyPressed = false;
  m_shiftKeyPressed = false;
  m_refreshSwitch = true;
  m_refreshSwitchStateOnHide = m_refreshSwitch;

  // set the refresh rate
  setRefreshRate(10);

  // connect it to grabFrame()
  connect(&m_grabTimer, &QTimer::timeout, this, &KMagZoomView::grabFrame);
  // start the grabTimer
  m_grabTimer.start(static_cast<int>(1000.0/m_fps));

  // connect it to updateMouseView()
  connect(&m_mouseViewTimer, &QTimer::timeout, this, &KMagZoomView::updateMouseView);
  // start the grabTimer @ 25 frames per second!
  m_mouseViewTimer.start(40ms);

  this->setWhatsThis( i18n("This is the main window which shows the contents of the\
 selected region. The contents will be magnified according to the zoom level that is set."));

  // different ways to show the cursor.
  m_showMouseTypes << QStringLiteral( "Hidden" ) << QStringLiteral( "Box" ) << QStringLiteral( "Arrow" ) << QStringLiteral( "Actual" );

  if(m_fitToWindow)
    fitToWindow();

#if HAVE_QACCESSIBILITYCLIENT
  //subscribe to focus events from registry
  m_registry.subscribeEventListeners(QAccessibleClient::Registry::Focus | QAccessibleClient::Registry::TextCaretMoved);
#endif
}

KMagZoomView::~KMagZoomView()
{
}

int KMagZoomView::contentsX() const
{
  return horizontalScrollBar()->value();
}

int KMagZoomView::contentsY() const
{
  return verticalScrollBar()->value();
}

int KMagZoomView::contentsWidth() const
{
  return horizontalScrollBar()->pageStep();
}

int KMagZoomView::contentsHeight() const
{
  return verticalScrollBar()->pageStep();
}

int KMagZoomView::visibleWidth() const
{
  return viewport()->width();
}

int KMagZoomView::visibleHeight() const
{
  return viewport()->height();
}

void KMagZoomView::setContentsPos(int x, int y)
{
  horizontalScrollBar()->setValue(x);
  verticalScrollBar()->setValue(y);
}

void KMagZoomView::setupMatrix()
{
  m_zoomMatrix.reset();
  m_zoomMatrix.scale(m_zoom, m_zoom);
  m_zoomMatrix.rotate(m_rotation);
}

/**
 * This function will set/reset mouse following of grab window.
 */
void KMagZoomView::followMouse(bool follow)
{
  m_followMouse = follow;
  m_mouseMode = Normal;
  if(follow) {
    setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
  } else {
    setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
    setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
  }
}

#if HAVE_QACCESSIBILITYCLIENT

void KMagZoomView::followBoth(bool follow)
{
    m_followBoth = follow;
    if(follow){
        m_followMouse = true;
        m_followFocus = false;
        setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
        setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);

        connect(&m_registry, SIGNAL(focusChanged(QAccessibleClient::AccessibleObject)),
                this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
        connect(&m_registry, SIGNAL(textCaretMoved(QAccessibleClient::AccessibleObject,int)),
                this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
    } else {
        setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
        setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);

        disconnect(this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
    }
}

/**
 * This function will set/reset keyboard focus following of grab window.
 */
void KMagZoomView::followFocus(bool follow)
{
  if(m_followFocus == follow)
      return;
  m_followFocus = follow;
  m_mouseMode = Normal;
  if(follow) {
    setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);

    connect(&m_registry,SIGNAL(focusChanged(QAccessibleClient::AccessibleObject)),
            this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
    connect(&m_registry, SIGNAL(textCaretMoved(QAccessibleClient::AccessibleObject,int)),
            this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
  } else {
    setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
    setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOn);

    disconnect(this, SLOT(focusChanged(QAccessibleClient::AccessibleObject)));
  }
}

void KMagZoomView::focusChanged(const QAccessibleClient::AccessibleObject &object)
{
    m_oldFocus = object.focusPoint();
    if(m_followBoth && !m_selRect.contains(m_oldFocus)) {
        QCursor::setPos(m_oldFocus);
        m_followFocus = true;
        m_followMouse = false;
    }
}

#endif

/**
 * Called when the widget is hidden. Stop refresh when this happens.
 */
void KMagZoomView::hideEvent( QHideEvent* )
{
  // Save the state of the refresh switch.. the state will be restored
  // when showEvent is called
  m_refreshSwitchStateOnHide = m_refreshSwitch;

  // Check if refresh is ON
  if(m_refreshSwitch) {
    toggleRefresh();
  }
}


/**
 * Called when the widget is shown. Start refresh when this happens.
 */
void KMagZoomView::showEvent( QShowEvent* )
{
  // Check if refresh switch was ON when hide was called and if currently it is OFF
  if(m_refreshSwitchStateOnHide && !m_refreshSwitch) {
    // start the refresh in that case
    toggleRefresh();
  }
}

/**
 * Called when the widget is resized. Check if fitToWindow is active when this happens.
 */
void KMagZoomView::resizeEvent( QResizeEvent * e )
{
  horizontalScrollBar()->setRange(0, contentsWidth() - visibleWidth());
  verticalScrollBar()->setRange(0, contentsHeight() - visibleHeight());
  QAbstractScrollArea::resizeEvent(e);
  if(m_fitToWindow)
    fitToWindow();
}

/**
 * Called when the widget is to be repainted.
 *
 * @param p
 */
void KMagZoomView::paintEvent(QPaintEvent *e)
{
  if(m_coloredPixmap.isNull())
    return;

  QPainter p(viewport());
  int clipx = e->rect().x();
  int clipy = e->rect().x();
  int clipw = e->rect().width();
  int cliph = e->rect().height();

  // Paint empty areas Qt::black
  if (contentsX()+contentsWidth() < visibleWidth())
    p.fillRect (
        QRect (contentsX()+contentsWidth(), clipy, visibleWidth()-contentsX()-contentsWidth(), cliph)
        & e->rect(),
        Qt::black);
  if (contentsY()+contentsHeight() < visibleHeight())
    p.fillRect (
        QRect (clipx, contentsY()+contentsHeight(), clipw, visibleHeight()-contentsY()-contentsHeight())
        & e->rect(),
        Qt::black);

  p.translate(visibleWidth() / 2.0, visibleHeight() / 2.0);
  p.setWorldTransform(m_zoomMatrix, true);
  const double ratio = 0.5 / m_coloredPixmap.devicePixelRatio();
  p.translate(-m_coloredPixmap.width() * ratio, -m_coloredPixmap.height() * ratio);
  p.drawPixmap(QPoint(clipx-contentsX(), clipy-contentsY()), m_coloredPixmap);
  p.end();

  if (m_showMouse)
    paintMouseCursor(viewport(), calcMousePos (m_refreshSwitch));
}

/**
 * Draws the mouse cursor according to the current selection of the type of
 * mouse cursor to draw.
 */
void KMagZoomView::paintMouseCursor(QPaintDevice *dev, const QPoint &mousePos)
{
  if(!dev)
    return;

  // painter for the zoom view
  QPainter pz(dev);

    // How to show the mouse :

    switch(m_showMouse) {
    case 1:
      // 1. Square around the pixel
      pz.setPen(Qt::white);
#ifdef __GNUC__
#warning "Port Qt4 pz.setRasterOp(Qt::XorROP);";
#endif
      //pz.setRasterOp(Qt::XorROP);
      pz.drawRect(mousePos.x()-1, mousePos.y()-1, (int)m_zoom+2, (int)m_zoom+2);
      break;

    case 2:
    {
      // 2. Arrow cursor
      pz.setPen(Qt::black);
      pz.setBackground(Qt::white);

      QPixmap sCursor(16, 16);
      QBitmap cursor = QBitmap::fromData( QSize(16,  16),  left_ptr_bits);
      QBitmap mask = QBitmap::fromData( QSize(16,  16),  left_ptrmsk_bits);
      sCursor.setMask(mask);
      QPainter p(&sCursor);
      p.setPen(Qt::gray);
      p.drawPixmap(0, 0, mask);
      p.setPen(Qt::black);
      p.drawPixmap(0, 0, cursor);
      p.end();
      sCursor = sCursor.transformed(m_zoomMatrix);

      // since hot spot is at 3,1
      if (m_rotation == 0)
        pz.drawPixmap(mousePos.x()-(int)(3.0*m_zoom), mousePos.y()-(int)m_zoom, sCursor);
      else if (m_rotation == 90)
        pz.drawPixmap(mousePos.x()-(int)(16.0*m_zoom), mousePos.y()-(int)(3.0*m_zoom), sCursor);
      else if (m_rotation == 180)
        pz.drawPixmap(mousePos.x()-(int)(13.0*m_zoom), mousePos.y()-(int)(16.0*m_zoom), sCursor);
      else if (m_rotation == 270)
        pz.drawPixmap(mousePos.x()-(int)m_zoom, mousePos.y()-(int)(13.0*m_zoom), sCursor);
    }
    break;

    case 3:
    {
      // 3. Actual cursor
      // Get the current cursor type
      QWidget *dummy  = QApplication::topLevelAt(QCursor::pos());
      if(!dummy)
        break;
      switch(this->cursor().shape())  {
			  case Qt::ArrowCursor :
         {
          // 2. Arrow cursor
          pz.setPen(Qt::black);
          pz.setBackground(Qt::white);

          QBitmap sCursor = QBitmap::fromData( QSize(16,  16),  left_ptr_bits);
          QBitmap mask = QBitmap::fromData( QSize(16,  16),  left_ptrmsk_bits);
          sCursor.setMask(mask);
          sCursor = sCursor.transformed(m_zoomMatrix);

          // since hot spot is at 3,1
          pz.drawPixmap(mousePos.x()-(int)(3.0*m_zoom), mousePos.y()-(int)m_zoom, sCursor);
        }
        break;
        default:
          QBitmap sCursor = QBitmap::fromData( QSize(32,  32),  phand_bits);
          QBitmap mask = QBitmap::fromData( QSize(32,  32),  phandm_bits);
          sCursor.setMask(mask);

          pz.drawPixmap(mousePos.x(), mousePos.y(), sCursor);
        break;
      } // switch(cursor)


    }
    break;

    default:
      // do not show anything
      break;
    } // switch(m_showMouse)
}


QPoint KMagZoomView::calcMousePos(bool updateMousePos)
{
  // get position of mouse wrt selRect
  if(updateMousePos) { // get a new position only if asked
    m_latestCursorPos = QCursor::pos();
    m_latestCursorPos -= QPoint(m_selRect.x(), m_selRect.y());
  }

  // get coordinates of the pixel w.r.t. the zoomed pixmap
  if (m_rotation == 90)
    return QPoint ((int)((float)(m_selRect.height()-m_latestCursorPos.y())*m_zoom),
                   (int)((float)m_latestCursorPos.x()*m_zoom));
  else if (m_rotation == 180)
    return QPoint ((int)((float)(m_selRect.width()-m_latestCursorPos.x())*m_zoom),
                   (int)((float)(m_selRect.height()-m_latestCursorPos.y())*m_zoom));
  else if (m_rotation == 270)
    return QPoint ((int)((float)m_latestCursorPos.y()*m_zoom),
                   (int)((float)(m_selRect.width()-m_latestCursorPos.x())*m_zoom));
  else
    return QPoint ((int)((float)m_latestCursorPos.x()*m_zoom),
                   (int)((float)m_latestCursorPos.y()*m_zoom));
}


// MOUSE ACTIONS

/**
 * Called when mouse is clicked inside the window.
 *
 * @param e
 */
void KMagZoomView::mousePressEvent(QMouseEvent *e)
{
  switch(e->button()) {
  case Qt::LeftButton :
    if(m_ctrlKeyPressed) {
      // check if currently in resize mode
      // don't do anything if fitToWindow is enabled
      if ((m_mouseMode != ResizeSelection) && !m_fitToWindow) {
        // set the mode to ResizeSelection
        m_mouseMode = ResizeSelection;

        // set mouse cursor to "resize all direction"
        setCursor(Qt::SizeAllCursor);

        // backup the old position
        m_oldMousePos.setX(qRound(e->globalPosition().x()));
        m_oldMousePos.setY(qRound(e->globalPosition().y()));

        // set the cursor position to the bottom-right of the selected region
        QCursor::setPos(m_selRect.bottomRight());

        // show the selection rectangle
        m_selRect.show();
      }
      else {
        // ignore this button press.. so it goes to the parent
        e->ignore();
      }
    } else if(m_shiftKeyPressed) {
      // check if currently in move mode
      // don't do anything if follow mouse is enabled
      if ((m_mouseMode != MoveSelection) && !m_followMouse) {
        m_mouseMode = MoveSelection;

        // set mouse cursor to cross hair
        setCursor(Qt::CrossCursor);

        // backup the old position
        m_oldMousePos.setX(qRound(e->globalPosition().x()));
        m_oldMousePos.setY(qRound(e->globalPosition().y()));

        // set the cursor position to the center of the selected region
        QCursor::setPos(m_selRect.center());

        // show the selected rectangle
        m_selRect.show();
      }
      else {
        // ignore this button press.. so it goes to the parent
        e->ignore();
      }
    } else {
      // check if currently in move mode
      // don't do anything if follow mouse is enabled
      if ((m_mouseMode != GrabSelection) && !m_followMouse) {
        m_mouseMode = GrabSelection;

        // set mouse cursor to hand
        setCursor(Qt::PointingHandCursor);

        // store the old position
        m_oldMousePos.setX(qRound(e->globalPosition().x()));
        m_oldMousePos.setY(qRound(e->globalPosition().y()));

        m_oldCenter = m_selRect.center();

        // show the selected rectangle
        m_selRect.show();
      }
      else {
        // ignore this button press.. so it goes to the parent
        e->ignore();
      }
    }
    break;

  case Qt::MiddleButton :
    // check if currently in move mode
    // don't do anything if follow mouse is enabled
    if ((m_mouseMode != MoveSelection) && !m_followMouse) {
      m_mouseMode = MoveSelection;

      // set mouse cursor to cross hair
      setCursor(Qt::CrossCursor);

      // backup the old position
        m_oldMousePos.setX(qRound(e->globalPosition().x()));
        m_oldMousePos.setY(qRound(e->globalPosition().y()));

      // set the cursor position to the center of the selected region
      QCursor::setPos(m_selRect.center());

      // show the selected rectangle
      m_selRect.show();
    }
    else {
      // ignore this button press.. so it goes to the parent
      e->ignore();
    }
    break;
  // do nothing
  default:
    // ignore this button press.. so it goes to the parent
    e->ignore();
    break;
  }
}


/**
 * Called when a mouse button is released
 *
 * @param e
 */
void KMagZoomView::mouseReleaseEvent(QMouseEvent *e)
{
  switch(e->button()) {
  case Qt::LeftButton :
  case Qt::MiddleButton :
    // check if currently in move mode
    if(m_mouseMode == MoveSelection) {
      // hide the selection window
      m_selRect.hide();
      // set the mouse mode to normal
      m_mouseMode = Normal;

      // restore the cursor shape
      setCursor(Qt::ArrowCursor);

      // restore the cursor position
      QCursor::setPos(m_oldMousePos);
    } else if(m_mouseMode == ResizeSelection) {
      // hide the selection window
      m_selRect.hide();
      // set the mouse mode to normal
      m_mouseMode = Normal;

      // restore the cursor shape
      setCursor(Qt::ArrowCursor);

      // restore the cursor position
      QCursor::setPos(m_oldMousePos);
    } else if(m_mouseMode == GrabSelection) {
      // hide the selection window
      m_selRect.hide();

      // set the mouse mode to normal
      m_mouseMode = Normal;

      // restore the cursor shape
      setCursor(Qt::ArrowCursor);
    }
    break;

  case Qt::RightButton :
    break;
  case Qt::NoButton :
    break;

  // do nothing
  default:
    ;
  }
}


/**
 * Called when mouse is moved inside the window
 *
 * @param e
 */
void KMagZoomView::mouseMoveEvent(QMouseEvent *e)
{
  const QSize screenSize = screen()->virtualSize();
  if(m_mouseMode == ResizeSelection) {
    // In resize selection mode
    // set the current mouse position as the bottom, right corner
    m_selRect.setRight(qRound(e->globalPosition().x()));
    m_selRect.setBottom(qRound(e->globalPosition().y()));

    m_selRect.update();
    grabFrame();
  } else if(m_mouseMode == MoveSelection) {
     QPoint newCenter;

    // set new center to be the current mouse position
    newCenter = e->globalPosition().toPoint();
    // make sure the mouse position is not taking the grab window outside
    // the display
    if(newCenter.x() < m_selRect.width()/2) {
      // set X to the minimum possible X
      newCenter.setX(m_selRect.width()/2);
    } else if(newCenter.x() >=  screenSize.width()-m_selRect.width()/2) {
      // set X to the maximum possible X
      newCenter.setX(screenSize.width()-m_selRect.width()/2-1);
    }

    if(newCenter.y() < m_selRect.height()/2) {
      // set Y to the minimum possible Y
      newCenter.setY(m_selRect.height()/2);
    } else if(newCenter.y() >=  screenSize.height()-m_selRect.height()/2) {
      // set Y to the maximum possible Y
      newCenter.setY(screenSize.height()-m_selRect.height()/2-1);
    }
    // move to the new center
    m_selRect.moveCenter(newCenter);
    // update the grab rectangle display
    m_selRect.update();
    grabFrame();
  } else if(m_mouseMode == GrabSelection) {
     QPoint newPos;

    // get new position
     newPos = e->globalPosition().toPoint();

    QPoint delta = (newPos - m_oldMousePos)/m_zoom;
    QPoint newCenter = m_oldCenter-delta;

    // make sure the mouse position is not taking the grab window outside
    // the display
    if(newCenter.x() < m_selRect.width()/2) {
      // set X to the minimum possible X
      newCenter.setX(m_selRect.width()/2);
    } else if(newCenter.x() >=  screenSize.width()-m_selRect.width()/2) {
      // set X to the maximum possible X
      newCenter.setX(screenSize.width()-m_selRect.width()/2-1);
    }

    if(newCenter.y() < m_selRect.height()/2) {
      // set Y to the minimum possible Y
      newCenter.setY(m_selRect.height()/2);
    } else if(newCenter.y() >=  screenSize.height()-m_selRect.height()/2) {
      // set Y to the maximum possible Y
      newCenter.setY(screenSize.height()-m_selRect.height()/2-1);
    }

    // move to the new center
    m_selRect.moveCenter(newCenter);
    // update the grab rectangle display
    m_selRect.update();
    grabFrame();
  }
}

void KMagZoomView::keyPressEvent(QKeyEvent *e)
{
  int offset = 16;
  if (e->modifiers() & Qt::ShiftModifier)
    offset = 1;

  if (e->key() == Qt::Key_Control)
    m_ctrlKeyPressed = true;
  else if (e->key() == Qt::Key_Shift)
    m_shiftKeyPressed = true;
  else if (e->key() == Qt::Key_Left)
  {
    if (e->modifiers() & Qt::ControlModifier)
    {
      if (offset >= m_selRect.width())
        m_selRect.setWidth (1);
      else
        m_selRect.setWidth (m_selRect.width()-offset);
  }
    else if (contentsX() > 0)
    {
      offset = (int)(offset*m_zoom);
      if (contentsX() > offset)
        setContentsPos (contentsX()-offset, contentsY());
      else
        setContentsPos (0, contentsY());
    }
    else if (m_followMouse == false)
    {
      if (offset > m_selRect.x())
        m_selRect.setX (0);
      else
        m_selRect.translate (-offset,0);
    }
    m_selRect.update();
  }
  else if (e->key() == Qt::Key_Right)
  {
    if (e->modifiers() & Qt::ControlModifier)
    {
      const QSize screenSize = screen()->virtualSize();
      if (m_selRect.right()+offset >= screenSize.width())
        m_selRect.setRight (screenSize.width()-1);
      else
        m_selRect.setRight (m_selRect.right()+offset);
    }
    else if (contentsX() < contentsWidth()-visibleWidth())
    {
      offset = (int)(offset*m_zoom);
      if (contentsX()+offset < contentsWidth()-visibleWidth())
        setContentsPos (contentsX()+offset, contentsY());
      else
        setContentsPos (contentsWidth()-visibleWidth(), contentsY());
    }
    else if (m_followMouse == false)
    {
      const QSize screenSize = screen()->virtualSize();
      if (m_selRect.right()+offset >= screenSize.width())
        m_selRect.moveTopRight (QPoint (screenSize.width()-1, m_selRect.top()));
      else
        m_selRect.translate (offset,0);
    }
    m_selRect.update();
  }
  else if (e->key() == Qt::Key_Up)
  {
    if (e->modifiers() & Qt::ControlModifier)
    {
      if (offset >= m_selRect.height())
        m_selRect.setHeight (1);
      else
        m_selRect.setHeight (m_selRect.height()-offset);
    }
    else if (contentsY() > 0)
    {
      offset = (int)(offset*m_zoom);
      if (contentsY() > offset)
        setContentsPos (contentsX(), contentsY()-offset);
      else
        setContentsPos (contentsX(), 0);
    }
    else if (m_followMouse == false)
    {
      if (offset > m_selRect.y())
        m_selRect.setY (0);
      else
        m_selRect.translate (0, -offset);
    }
    m_selRect.update();
  }
  else if (e->key() == Qt::Key_Down)
  {
    if (e->modifiers() & Qt::ControlModifier)
    {
      const QSize screenSize = screen()->virtualSize();
      if (m_selRect.bottom()+offset >= screenSize.height())
        m_selRect.setBottom (screenSize.height()-1);
      else
        m_selRect.setBottom (m_selRect.bottom()+offset);
    }
    else if (contentsY() < contentsHeight()-visibleHeight())
    {
      offset = (int)(offset*m_zoom);
      if (contentsY()+offset < contentsHeight()-visibleHeight())
        setContentsPos (contentsX(), contentsY()+offset);
      else
        setContentsPos (contentsX(), contentsHeight()-visibleHeight());
    }
    else if (m_followMouse == false)
    {
      const QSize screenSize = screen()->virtualSize();
      if (m_selRect.bottom()+offset >= screenSize.height())
        m_selRect.moveBottomLeft (QPoint (m_selRect.left(), screenSize.height()-1));
      else
        m_selRect.translate (0, offset);
    }
    m_selRect.update();
  }
  else
    e->ignore();
}

void KMagZoomView::keyReleaseEvent(QKeyEvent *e)
{
  if (e->key() == Qt::Key_Control)
    m_ctrlKeyPressed = false;
  else if (e->key() == Qt::Key_Shift)
    m_shiftKeyPressed = false;
  else
    e->ignore();
}

void KMagZoomView::focusOutEvent(QFocusEvent *e)
{
  if(e->lostFocus() == true) {
    m_ctrlKeyPressed = false;
    m_shiftKeyPressed = false;
  }
}

// SLOTS

/**
 * This will fit the zoom view to the view window, thus using the maximum
 * possible space in the window.
 */
void KMagZoomView::fitToWindow()
{
  unsigned int newWidth, newHeight;

  // this is a temporary solution, cast, maybe newWidth and newHeight should be float
  if ((m_rotation == 90) || (m_rotation == 270))
  {
    newWidth = static_cast<unsigned int>((visibleHeight() + m_zoom - 1) / m_zoom);
    newHeight = static_cast<unsigned int>((visibleWidth() + m_zoom - 1) / m_zoom);
  } else {
    newWidth = static_cast<unsigned int>((visibleWidth() + m_zoom - 1) / m_zoom);
    newHeight = static_cast<unsigned int>((visibleHeight() + m_zoom - 1) / m_zoom);
  }

  QPoint currCenter = m_selRect.center();

  m_selRect.setWidth(newWidth);
  m_selRect.setHeight(newHeight);
  const QSize screenSize = screen()->virtualSize();
   // make sure the selection window does not go outside of the display
   if(currCenter.x() < m_selRect.width()/2) {
     // set X to the minimum possible X
     currCenter.setX(m_selRect.width()/2);
   } else if(currCenter.x() >=  screenSize.width()-m_selRect.width()/2) {
     // set X to the maximum possible X
     currCenter.setX(screenSize.width()-m_selRect.width()/2-1);
   }

   if(currCenter.y() < m_selRect.height()/2) {
     // set Y to the minimum possible Y
     currCenter.setY(m_selRect.height()/2);
   } else if(currCenter.y() >=  screenSize.height()-m_selRect.height()/2) {
     // set Y to the maximum possible Y
     currCenter.setY(screenSize.height()-m_selRect.height()/2-1);
   }

  m_selRect.moveCenter(currCenter);
  // update the grab rectangle display
  m_selRect.update();
//  m_fitToWindow = true;
  viewport()->update();
}

void KMagZoomView::setFitToWindow(bool fit)
{
  m_fitToWindow = fit;
  if (fit)
    fitToWindow();
}


/**
 * Grabs frame from X
 */
void KMagZoomView::grabFrame()
{
  // check refresh status
  if (!m_refreshSwitch)
     return;

  // check if follow-mouse or follow-focus are enabled
  if((m_followMouse || m_followFocus) && (m_mouseMode != ResizeSelection)) {
    // center-position of the grab-area
    QPoint newCenter;

    if(m_followMouse) {
        // set new center to be the current mouse position
        newCenter = QCursor::pos();
#if HAVE_QACCESSIBILITYCLIENT
    } else if(m_followFocus) {
        // set the new center to the current keyboard cursor position
        newCenter = m_oldFocus;
        if(m_followBoth) {
            m_followFocus=false;
            m_followMouse=true;
        }
#endif
    }

    // make sure the mouse position is not taking the grab window outside
    // the display
    const QSize screenSize = screen()->virtualSize();
    if(newCenter.x() < m_selRect.width()/2) {
      // set X to the minimum possible X
      newCenter.setX(m_selRect.width()/2);
    } else if(newCenter.x() >=  screenSize.width()-m_selRect.width()/2) {
      // set X to the maximum possible X
      newCenter.setX(screenSize.width()-m_selRect.width()/2-1);
    }

    if(newCenter.y() < m_selRect.height()/2) {
      // set Y to the minimum possible Y
      newCenter.setY(m_selRect.height()/2);
    } else if(newCenter.y() >=  screenSize.height()-m_selRect.height()/2) {
      // set Y to the maximum possible Y
      newCenter.setY(screenSize.height()-m_selRect.height()/2-1);
    }
    // move to the new center
    m_selRect.moveCenter(newCenter);

    // update the grab rectangle display
    m_selRect.update();
  }

  // define a normalized rectangle
  QRect selRect = m_selRect.normalized();

  auto cursorPos = QCursor::pos();
  {
    // The cursor position (QPoint) for some reason may be equal to the width or height of the screen,
    // for example (1920, foo) or (bar, 1080) for Full HD. At the same time, the geometry rectangle (QRect)
    // due to the 0-based coordinate system will not actually contain such point -- the QRect::contains()
    // method will always return false, which is incorrect in our case. Therefore, we forcibly "project"
    // the coordinates of the cursor position into the screen geometry rectangle.
    auto vg = qApp->primaryScreen()->virtualGeometry();
    cursorPos.rx() = qBound(vg.left(), cursorPos.x(), vg.right());
    cursorPos.ry() = qBound(vg.top(), cursorPos.y(), vg.bottom());
  }

  auto screen = qApp->primaryScreen();
  for (auto s : qApp->screens()) {
    if (s->geometry().contains(cursorPos)) {
      screen = s;
      break;
    }
  }

  // grab screenshot from the screen and put it in the pixmap
  m_coloredPixmap = screen->grabWindow(
    0,  // WId == 0 is interpreted as the root window / desktop
    selRect.x() - screen->geometry().left(),
    selRect.y() - screen->geometry().top(),
    selRect.width(), selRect.height());

  // colorize the grabbed pixmap
  if (m_colormode != 0)
    m_coloredPixmap = QPixmap::fromImage(ColorSim::recolor(m_coloredPixmap.toImage(), m_colormode));

  // erase background covered by kmag view ...
  QRect viewRect = rect();
  viewRect.translate(mapTo(window(), QPoint(0, 0)));
  viewRect.translate(-selRect.topLeft());
  viewRect.translate(window()->geometry().topLeft());
  QRegion region(viewRect);

  // ... but exclude own popups ...
  const QList<QWidget *> siblings = QApplication::topLevelWidgets();
  for (QWidget *sibling : siblings) {
    if (sibling != window() && (sibling->windowType() & Qt::Window) && sibling->isVisible()) {
      QRect rect = sibling->frameGeometry();
      rect.translate(-selRect.topLeft());
      region -= rect;
    }
  }

  QPainter p(&m_coloredPixmap);
  for (const QRect &rect : region) {
    p.fillRect(rect, palette().dark());
  }
  p.end();

  QRect r = m_zoomMatrix.mapRect(m_coloredPixmap.rect());
  // call repaint to display the newly grabbed image
  horizontalScrollBar()->setPageStep(r.width());
  verticalScrollBar()->setPageStep(r.height());
  viewport()->update();
}


/**
 * Updates the mouse cursor in the zoom view.
 */
void KMagZoomView::updateMouseView()
{
    if (m_fps < 8)
        viewport()->update();
}

/**
 * Toggles the state of refreshing.
 */
void KMagZoomView::toggleRefresh()
{
  if(m_refreshSwitch) {
    m_refreshSwitch = false;
    m_grabTimer.stop();
    m_mouseViewTimer.stop();
  } else {
    m_refreshSwitch = true;
    m_grabTimer.start(1000/m_fps);
    m_mouseViewTimer.start(40);
  }
}

/**
 * This function sets the zoom value to be used.
 */
void KMagZoomView::setZoom(float zoom)
{
  // use this zoom
  m_zoom = zoom;

  // update selection window size when zooming in if necessary
  if (m_fitToWindow)
    fitToWindow();

  // recompute the zoom matrix
  setupMatrix();

  viewport()->update();
}

/**
 * This function sets the rotation value to be used.
 */
void KMagZoomView::setRotation(int rotation)
{
  // use this rotation
  m_rotation = rotation;

  // update selection window size if necessary
  if (m_fitToWindow)
    fitToWindow();

  // recompute the zoom matrix
  setupMatrix();

  viewport()->update();
}

/**
 * Set a new color simulation mode.
 */
void KMagZoomView::setColorMode(int mode)
{
  if (m_colormode != mode) {
    m_colormode = mode;
    viewport()->update();
  }
}

/**
 * Set a new refresh rate.
 */
void KMagZoomView::setRefreshRate(float fps)
{
  if(fps < 0.1)
    return;
  m_fps = static_cast<unsigned int>(fps);

  if(m_grabTimer.isActive())
    m_grabTimer.start(static_cast<int>(1000.0/m_fps));
}

void KMagZoomView::showSelRect(bool show)
{
  m_selRect.alwaysVisible(show);
  if(show) {
    m_selRect.show();
  } else if(m_mouseMode == Normal) {
    m_selRect.hide();
  }
}

/**
 * Sets the selection rectangle to the given position.
 */
void KMagZoomView::setSelRectPos(const QRect & rect)
{
  m_selRect.setRect(rect.x(), rect.y(), rect.width(), rect.height());
  m_selRect.update();
  grabFrame();
}

bool KMagZoomView::showMouse(unsigned int type)
{
  if(int(type) > m_showMouseTypes.count()-1)
    return (false);
  else
    m_showMouse = type;

  return(true);
}

unsigned int KMagZoomView::getShowMouseType() const
{
  return (m_showMouse);
}

QStringList KMagZoomView::getShowMouseStringList() const
{
  return (m_showMouseTypes);
}


/**
 * Returns the image which is being displayed. It's again drawn by adding
 * the mouse cursor if needed.
 */
QImage KMagZoomView::getImage()
{
  QImage image = m_coloredPixmap.transformed(m_zoomMatrix).toImage();

  // show the pixel under mouse cursor
  if(m_showMouse && !image.isNull()) {
    // paint the mouse cursor w/o updating to a newer position
    paintMouseCursor(&image, calcMousePos(false));
  }
  return(image);
}

#include "moc_kmagzoomview.cpp"
