/*
 * Kivio - Visual Modelling and Flowcharting
 * Copyright (C) 2000-2001 theKompany.com & Dave Marotti
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include "kivio_canvas.h"
#include "kivio_page.h"
#include "kivio_map.h"
#include "kivio_view.h"
#include "kivio_doc.h"

#include "kivio_icon_view.h"
#include "kivio_stencil.h"
#include "kivio_stencil_spawner.h"
#include "kivio_stencil_spawner_info.h"
#include "kivio_stackbar.h"
#include "kivio_screen_painter.h"
#include "kivio_grid_data.h"
#include "kivio_layer.h"

#include "kivio_pluginmanager.h"

#include <kdebug.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kcursor.h>
#include <KoGlobal.h>
#include <KoZoomHandler.h>
#include <KoSize.h>
#include <KoRuler.h>
#include <KoPoint.h>
#include <KoTabBar.h>
#include <tdeapplication.h>

#include <assert.h>
#include <stdio.h>
#include <tqlabel.h>
#include <tqpixmap.h>
#include <tqscrollbar.h>
#include <tqtimer.h>
#include <tqsize.h>

using namespace Kivio;

KivioCanvas::KivioCanvas( TQWidget *par, KivioView* view, KivioDoc* doc, TQScrollBar* vs, TQScrollBar* hs)
: TQWidget(par, "KivioCanvas", WResizeNoErase | WRepaintNoErase),
  m_pView(view),
  m_pDoc(doc),
  m_pVertScrollBar(vs),
  m_pHorzScrollBar(hs),
  m_guides(view, view->zoomHandler())
{
  setBackgroundMode(NoBackground);
  setAcceptDrops(true);
  setMouseTracking(true);
  setFocusPolicy(TQWidget::StrongFocus);
  setFocus();

  m_showConnectorTargets = false;
  delegateThisEvent = true;

  m_pVertScrollBar->setLineStep(1);
  m_pHorzScrollBar->setLineStep(1);


  m_pVertScrollBar->setPageStep(10);
  m_pHorzScrollBar->setPageStep(10);

  connect(m_pVertScrollBar, TQ_SIGNAL(valueChanged(int)), TQ_SLOT(scrollV(int)));
  connect( m_pHorzScrollBar, TQ_SIGNAL(valueChanged(int)), TQ_SLOT(scrollH(int)));

  m_iXOffset = 0;
  m_iYOffset = 0;

  m_pScrollX = 0;
  m_pScrollY = 0;

  m_pageOffsetX = 0;
  m_pageOffsetY = 0;

  m_pasteMoving = false;

  m_buffer = new TQPixmap();

  m_pDragStencil = 0L;
  unclippedSpawnerPainter = 0L;
  unclippedPainter = 0L;

  m_borderTimer = new TQTimer(this);
  connect(m_borderTimer,TQ_SIGNAL(timeout()),TQ_SLOT(borderTimerTimeout()));
}

KivioCanvas::~KivioCanvas()
{
  delete m_buffer;
  delete m_borderTimer;
  delete unclippedPainter;
}

KivioPage* KivioCanvas::findPage( const TQString& _name )
{
  return m_pDoc->map()->findPage( _name );
}

const KivioPage* KivioCanvas::activePage() const
{
  return m_pView->activePage();
}

KivioPage* KivioCanvas::activePage()
{
  return m_pView->activePage();
}

void KivioCanvas::scrollH( int value )
{
  // Relative movement
  int dx = m_iXOffset - value;
  // New absolute position
  m_iXOffset = value;

  bitBlt(m_buffer, dx, 0, m_buffer);
  scroll(dx, 0);

  emit visibleAreaChanged();
}

void KivioCanvas::scrollV( int value )
{
  // Relative movement
  int dy = m_iYOffset - value;
  // New absolute position
  m_iYOffset = value;

  bitBlt(m_buffer, 0, dy, m_buffer);
  scroll(0, dy);

  emit visibleAreaChanged();
}

void KivioCanvas::scrollDx( int dx )
{
  if ( dx == 0 )
    return;

  int value = m_iXOffset - dx;
  m_pHorzScrollBar->setValue(value);
}

void KivioCanvas::scrollDy( int dy )
{
  if ( dy == 0 )
    return;

  int value = m_iYOffset - dy;
  m_pVertScrollBar->setValue(value);
}

void KivioCanvas::resizeEvent( TQResizeEvent* )
{
  m_buffer->resize(size());
  updateScrollBars();

  emit visibleAreaChanged();
}

void KivioCanvas::wheelEvent( TQWheelEvent* ev )
{
  ev->accept();
  
  if( (ev->delta()>0))
  {
     if(ev->state() == ControlButton) {
       zoomIn(ev->pos());
     } else if(ev->state() == ShiftButton) {
       m_pVertScrollBar->setValue(m_pVertScrollBar->value() - height());
     } else {
       m_pVertScrollBar->setValue(m_pVertScrollBar->value() - 30);
     }
  }
  else
  {
     if(ev->state() == ControlButton) {
       zoomOut(ev->pos());
     } else if(ev->state() == ShiftButton) {
       m_pVertScrollBar->setValue(m_pVertScrollBar->value() + height());
     } else {
       m_pVertScrollBar->setValue(m_pVertScrollBar->value() + 30);
     }
  }
}

void KivioCanvas::setUpdatesEnabled( bool isUpdate )
{
  static int i = 0;

  TQWidget::setUpdatesEnabled(isUpdate);
  if (isUpdate) {
    --i;
    if (i == 0) {
      update();
      updateScrollBars();

      blockSignals(false);

      emit visibleAreaChanged();
    }
  } else {
    i++;
    blockSignals(true);
  }
}

void KivioCanvas::zoomIn(const TQPoint &p)
{
  setUpdatesEnabled(false);
  KoPoint p0 = mapFromScreen(p);
  m_pView->viewZoom(m_pView->zoomHandler()->zoom() + 25);
  TQPoint p1 = mapToScreen(p0);
  scrollDx(-p1.x()+p.x());
  scrollDy(-p1.y()+p.y());
  setUpdatesEnabled(true);
}

void KivioCanvas::zoomOut(const TQPoint &p)
{
  setUpdatesEnabled(false);
  KoPoint p0 = mapFromScreen(p);
  int newZoom = m_pView->zoomHandler()->zoom() - 25;

  if(newZoom > 0) {
    m_pView->viewZoom(newZoom);
    TQPoint p1 = mapToScreen(p0);
    scrollDx(-p1.x()+p.x());
    scrollDy(-p1.y()+p.y());
  }
  setUpdatesEnabled(true);
}

void KivioCanvas::paintEvent( TQPaintEvent* ev )
{
  if ( m_pDoc->isLoading() || !activePage() )
    return;

  KivioPage* page = activePage();

  TQPainter painter;
  painter.begin(m_buffer);

  KoPageLayout pl = page->paperLayout();
  int pw = m_pView->zoomHandler()->zoomItX(pl.ptWidth);
  int ph = m_pView->zoomHandler()->zoomItY(pl.ptHeight);
  TQRect fillRect(0, 0, pw, ph);
  TQRect paintRect = ev->rect();
  TQRegion grayRegion(paintRect);
  grayRegion.translate(m_iXOffset - m_pageOffsetX, m_iYOffset - m_pageOffsetY);
  grayRegion -= fillRect;
  grayRegion.translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);

  // This code comes from KPresenter's kprcanvas.cpp
  // Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
  painter.save();
  painter.setClipRegion(grayRegion, TQPainter::CoordPainter);
  painter.setPen(TQt::NoPen);
  painter.fillRect(grayRegion.boundingRect(), TDEApplication::palette().active().brush(TQColorGroup::Mid));
  painter.restore();
  // end of copy...

  painter.translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset +m_pageOffsetY);
  painter.fillRect(fillRect, white);

  // Draw Grid
  if(m_pDoc->grid().isShow) {
    KoPoint topLeft(0, 0);
    KoPoint bottomRight(pl.ptWidth, pl.ptHeight);
    TQPoint zoomedTL = m_pView->zoomHandler()->zoomPoint(topLeft);
    TQPoint zoomedBR = m_pView->zoomHandler()->zoomPoint(bottomRight);

    KoSize freq = m_pDoc->grid().freq;

    painter.setPen(m_pDoc->grid().color);

    double x = tqRound(topLeft.x() / freq.width()) * freq.width();
    int zoomed = 0;

    while(x <= bottomRight.x()) {
      zoomed = m_pView->zoomHandler()->zoomItX(x);
      painter.drawLine(zoomed, zoomedTL.y(), zoomed, zoomedBR.y());
      x += freq.width();
    }

    double y = tqRound(topLeft.y() / freq.height()) * freq.height();

    while(y <= bottomRight.y()) {
      zoomed = m_pView->zoomHandler()->zoomItY(y);
      painter.drawLine(zoomedTL.x(), zoomed, zoomedBR.x(), zoomed);
      y += freq.height();
    }
  }

  if(m_pView->isShowPageMargins()) {
    int ml = m_pView->zoomHandler()->zoomItX(pl.ptLeft);
    int mt = m_pView->zoomHandler()->zoomItY(pl.ptTop);
    int mr = m_pView->zoomHandler()->zoomItX(pl.ptRight);
    int mb = m_pView->zoomHandler()->zoomItY(pl.ptBottom);

    painter.save();
    painter.setPen(TQPen("#C7C7C7",1,SolidLine));
    painter.drawRect(ml,mt,pw-ml-mr,ph-mt-mb);
    painter.restore();
  }

  // Draw page borders
  painter.setPen(black);
  painter.fillRect(pw, 3, 5, ph, gray);
  painter.fillRect(3, ph, pw, 3, gray);
  painter.drawRect(0, 0, pw, ph);

  // Draw content
  KivioScreenPainter kpainter;
  kpainter.start(m_buffer);
  kpainter.translateBy(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);
  m_pDoc->paintContent(kpainter, paintRect, false, page, TQPoint(0, 0), m_pView->zoomHandler(), showConnectorTargets(), true);
  kpainter.stop();

  if(m_pView->isShowGuides()) {
    m_guides.paintGuides(painter);
  }

  painter.end();

  bitBlt(this, paintRect.left(), paintRect.top(), m_buffer,
         paintRect.left(), paintRect.top(), paintRect.width(), paintRect.height());
}

void KivioCanvas::updateScrollBars()
{
  if(!activePage()) {
    return;
  }

  KoPageLayout pl = activePage()->paperLayout();
  int pw = m_pView->zoomHandler()->zoomItX(pl.ptWidth);
  int ph = m_pView->zoomHandler()->zoomItY(pl.ptHeight);

  m_pScrollX = TQMAX(pw - width(), 0);
  m_pScrollY = TQMAX(ph - height(), 0);

  m_pHorzScrollBar->setRange(0, m_pScrollX);
  if ( m_pHorzScrollBar->value() > m_pHorzScrollBar->maxValue() ||
       m_pHorzScrollBar->value() < m_pHorzScrollBar->minValue() )
  {
    m_pHorzScrollBar->setValue(0);
  }

  m_pVertScrollBar->setRange(0, m_pScrollY);
  if ( m_pVertScrollBar->value() > m_pVertScrollBar->maxValue() ||
       m_pVertScrollBar->value() < m_pVertScrollBar->minValue() )
  {
    m_pVertScrollBar->setValue(0);
  }

  m_pVertScrollBar->setPageStep( height() );
  m_pHorzScrollBar->setPageStep( width() );

  if(pw >= width()) {
    m_pageOffsetX = 0;
  } else {
    m_pageOffsetX = (width() - pw) / 2;
  }

  if(ph >= height()) {
    m_pageOffsetY = 0;
  } else {
    m_pageOffsetY = (height() - ph) / 2;
  }
}

TQSize KivioCanvas::actualSize() const
{
  return TQSize(m_pScrollX, m_pScrollY);
}

bool KivioCanvas::event( TQEvent* e )
{
  bool f = TQWidget::event(e);

  if (m_pView->pluginManager() && delegateThisEvent) {
    f = m_pView->pluginManager()->delegateEvent(e);
  }

  delegateThisEvent = true;
  return f;
}

void KivioCanvas::enterEvent( TQEvent* )
{
}

void KivioCanvas::leaveEvent( TQEvent* )
{
  m_pView->setMousePos(-1, -1);
}

void KivioCanvas::mousePressEvent(TQMouseEvent* e)
{
    if(!m_pDoc->isReadWrite())
        return;

    if(m_pasteMoving) {
      endPasteMoving();
      return;
    }

    Kivio::PluginManager* pluginManager = view()->pluginManager();

    if(m_pView->isShowGuides() && (!pluginManager || (pluginManager->defaultTool() == pluginManager->activeTool()))) {
      if(m_guides.mousePressEvent(e)) {
        delegateThisEvent = false;
        return;
      }
    }
}

void KivioCanvas::mouseReleaseEvent(TQMouseEvent* e)
{
  if(!m_pDoc->isReadWrite())
    return;

  Kivio::PluginManager* pluginManager = view()->pluginManager();

  if(view()->isShowGuides() && (!pluginManager || (pluginManager->defaultTool() == pluginManager->activeTool()))) {
    if(m_guides.mouseReleaseEvent(e)) {
      delegateThisEvent = false;
      return;
    }
  }
}

void KivioCanvas::mouseMoveEvent(TQMouseEvent* e)
{
  if(!m_pDoc->isReadWrite())
    return;

  if(m_pasteMoving) {
    continuePasteMoving(e->pos());
  } else {
    Kivio::PluginManager* pluginManager = view()->pluginManager();

    if(m_pView->isShowGuides() && (!pluginManager || (pluginManager->defaultTool() == pluginManager->activeTool()))) {
      delegateThisEvent = !m_guides.mouseMoveEvent(e);
    }
  }

  lastPoint = e->pos();
  view()->setMousePos(lastPoint.x(), lastPoint.y());
}

TQPoint KivioCanvas::mapToScreen(const KoPoint& pos)
{
  TQPoint p;
  int x = m_pView->zoomHandler()->zoomItX(pos.x());
  int y = m_pView->zoomHandler()->zoomItY(pos.y());

  p.setX( x - m_iXOffset + m_pageOffsetX);
  p.setY( y - m_iYOffset + m_pageOffsetY);

  return p;
}

KoPoint KivioCanvas::mapFromScreen( const TQPoint & pos )
{
  int x = pos.x() + m_iXOffset - m_pageOffsetX;
  int y = pos.y() + m_iYOffset - m_pageOffsetY;
  double xf = m_pView->zoomHandler()->unzoomItX(x);
  double yf = m_pView->zoomHandler()->unzoomItY(y);

  KoPoint p(xf, yf);
  return p;
}

void KivioCanvas::startRectDraw( const TQPoint &p, RectType )
{
  currRect = TQRect( 0, 0, -1, -1 );

  TQPoint pos( p );
  oldRectValid = false;
  beginUnclippedPainter();
  rectAnchor = pos;
  currRect = TQRect( rectAnchor, TQPoint(0,0) );

  m_borderTimer->start(100);
}

void KivioCanvas::continueRectDraw( const TQPoint &p, RectType )
{
  TQPoint pos = p;
  TQPoint p2 = pos;
  TQRect r( rectAnchor, p2 );
  r = r.normalize();

  if ( oldRectValid )
    unclippedPainter->drawRect( currRect );
  if ( r.width() > 1 || r.height() > 1 ) {
    oldRectValid = true;
    currRect = r;
    unclippedPainter->drawRect( currRect );
  } else {
    oldRectValid = false;
  }
}

void KivioCanvas::endRectDraw()
{
  m_borderTimer->stop();

  if ( !unclippedPainter )
    return;

  if ( oldRectValid )
    unclippedPainter->drawRect( currRect );

  endUnclippedPainter();
}

/**
 * Starts a new drag & draw (called from the drag enter event)
 *
 * @param p The point to begin at
 *
 * This will allocate a new KivioStencil for drawing with and
 * set some class variables used during redraws.
 */
void KivioCanvas::startSpawnerDragDraw( const TQPoint &p )
{
  currRect = TQRect( 0, 0, -1, -1 );

  KivioStencilSpawner *pSpawner = KivioIconView::curDragSpawner();
  if( !pSpawner )
    return;

  // If we for some reason didn't delete an old drag stencil,
  // do so now.
  if( m_pDragStencil )
  {
    kdDebug(43000) << "KivioCanvas::startSpawnerDragDraw() - m_pDragStencil still exists.  BUG!" << endl;
    delete m_pDragStencil;
    m_pDragStencil = 0L;
  }

  // Map the point from screenspace to page space
  KoPoint qp = mapFromScreen( p );
  qp = snapToGrid(qp);

  // Allocate a new stencil for dragging around
  m_pDragStencil = pSpawner->newStencil();
  m_pDragStencil->setPosition( qp.x(), qp.y() );

  // Invalidate the rectangle
  oldRectValid = true;

  // Create a new painter object
  beginUnclippedSpawnerPainter();

  // Translate the painter so that 0,0 means where the page starts on the canvas
  unclippedSpawnerPainter->painter()->save();
  unclippedSpawnerPainter->painter()->translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);

  // Assign the painter object to the intra-stencil data object, as well
  // as the zoom factor
  m_dragStencilData.painter = unclippedSpawnerPainter;
  m_dragStencilData.zoomHandler = m_pView->zoomHandler();

  // Draw the outline of the stencil
  m_pDragStencil->paintOutline( &m_dragStencilData );

  unclippedSpawnerPainter->painter()->restore();
}

/**
 * Undraws the old stencil outline, draws the new one
 */
void KivioCanvas::continueSpawnerDragDraw( const TQPoint &p )
{
  bool snappedX, snappedY;

  // Translate the painter so that 0,0 means where the page starts on the canvas
  unclippedSpawnerPainter->painter()->save();
  unclippedSpawnerPainter->painter()->translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);

  // Undraw the old outline
  if( oldRectValid )
  {
    m_pDragStencil->paintOutline( &m_dragStencilData );
  }

  // Map the new point from screenspace to page space
  KoPoint orig = mapFromScreen(p);
  KoPoint qp = snapToGrid( orig );

  // First snap to screen
  qp = snapToGrid(qp);
  m_pDragStencil->setPosition( qp.x(), qp.y() );

  // Now snap to the guides
  qp.setCoords(orig.x() + m_pDragStencil->w(), orig.y() + m_pDragStencil->h());
  qp = snapToGuides(qp, snappedX, snappedY);

  if(snappedX) {
    m_pDragStencil->setX(qp.x() - m_pDragStencil->w());
  }

  if(snappedY) {
    m_pDragStencil->setY(qp.y() - m_pDragStencil->h());
  }

  qp.setCoords(orig.x() + (m_pDragStencil->w() / 2.0), orig.y() + (m_pDragStencil->h() / 2.0));
  qp = snapToGuides(qp, snappedX, snappedY);

  if(snappedX) {
    m_pDragStencil->setX(qp.x() - (m_pDragStencil->w() / 2.0));
  }

  if(snappedY) {
    m_pDragStencil->setY(qp.y() - (m_pDragStencil->h() / 2.0));
  }

  qp.setCoords(orig.x(), orig.y());
  qp = snapToGuides(qp, snappedX, snappedY);

  if(snappedX) {
    m_pDragStencil->setX(qp.x());
  }

  if(snappedY) {
    m_pDragStencil->setY(qp.y());
  }

  // Redraw the new outline
  oldRectValid = true;
  m_pDragStencil->paintOutline( &m_dragStencilData );
  unclippedSpawnerPainter->painter()->restore();

}


/**
 * Ends the ability to draw a drag & drop spawner object
 */
void KivioCanvas::endSpawnerDragDraw()
{
  // Avoid the noid
  if ( !unclippedSpawnerPainter )
    return;

  // If we have a valid old drawing spot, undraw it
  if ( oldRectValid )
  {
    unclippedSpawnerPainter->painter()->save();
    unclippedSpawnerPainter->painter()->translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);
    m_pDragStencil->paintOutline( &m_dragStencilData );
    unclippedSpawnerPainter->painter()->restore();
  }

  // Smack the painter around a bit
  endUnclippedSpawnerPainter();

  // If we have a stencil we were dragging around, delete it.
  if( m_pDragStencil )
  {
    delete m_pDragStencil;
    m_pDragStencil = 0L;
  }

  setFocus();
}


/**
 * Creates a new spawner drawing object
 */
void KivioCanvas::beginUnclippedSpawnerPainter()
{
    // End any previous attempts
    endUnclippedSpawnerPainter();

    // I have no idea what this does.  Max?
    bool unclipped = testWFlags( WPaintUnclipped );
    setWFlags( WPaintUnclipped );


    // Allocate a new painter object for use in drawing
    unclippedSpawnerPainter = new KivioScreenPainter();

    // Tell it to start (allocates a Qpainter object)
    unclippedSpawnerPainter->start(this);

    // Uhhhhh??
    if( !unclipped )
        clearWFlags( WPaintUnclipped );


    // Make sure it's doing NOT drawing.
    unclippedSpawnerPainter->painter()->setRasterOp( NotROP );
    unclippedSpawnerPainter->painter()->setPen( TQColor(0,0,250) );

}


/**
 * Deletes the current spawner drawing object
 */
void KivioCanvas::endUnclippedSpawnerPainter()
{
    if( unclippedSpawnerPainter )
    {
        unclippedSpawnerPainter->stop();
        delete unclippedSpawnerPainter;
        unclippedSpawnerPainter = 0L;
    }
}

void KivioCanvas::beginUnclippedPainter()
{
    endUnclippedPainter();
    bool unclipped = testWFlags( WPaintUnclipped );

    setWFlags( WPaintUnclipped );
    unclippedPainter = new TQPainter;
    unclippedPainter->begin( this );

    if ( !unclipped )
        clearWFlags( WPaintUnclipped );

    unclippedPainter->setRasterOp( NotROP );
    unclippedPainter->setPen( TQPen(blue,1,DotLine) );
}

void KivioCanvas::endUnclippedPainter()
{
    if ( unclippedPainter )
    {
        unclippedPainter->end();
        delete unclippedPainter;
        unclippedPainter = 0;
    }
}

void KivioCanvas::borderTimerTimeout()
{
  TQPoint p = mapFromGlobal(TQCursor::pos());
  int dx = 0;
  int dy = 0;
  int d = 10;

  TQRect r(currRect);
  int vpos = m_pVertScrollBar->value();
  int vmax = m_pVertScrollBar->maxValue();
  int vmin = m_pVertScrollBar->minValue();

  int hpos = m_pHorzScrollBar->value();
  int hmax = m_pHorzScrollBar->maxValue();
  int hmin = m_pHorzScrollBar->minValue();

  if ( p.x() < 0 && hpos > hmin ) {
    dx = TQMIN(d,hpos-hmin);
    r.setRight(r.right()+dx);
    rectAnchor.setX(rectAnchor.x()+dx);
  }

  if ( p.y() < 0 && vpos > vmin ) {
    dy = TQMIN(d,vpos-vmin);
    r.setBottom(r.bottom()+dy);
    rectAnchor.setY(rectAnchor.y()+dy);
  }

  if ( p.x() > width() && hpos < hmax ) {
    dx = -TQMIN(d,hmax-hpos);
    r.setLeft(r.left()+dx);
    rectAnchor.setX(rectAnchor.x()+dx);
  }

  if ( p.y() > height() && vpos < vmax  ) {
    dy = -TQMIN(d,vmax-vpos);
    r.setTop(r.top()+dy);
    rectAnchor.setY(rectAnchor.y()+dy);
  }

  if ( dx != 0 || dy != 0 ) {
    unclippedPainter->drawRect( currRect );
    scrollDx(dx);
    scrollDy(dy);
    unclippedPainter->drawRect( r );
    currRect = r;
  }
}


/**
 * Handles the initial drag event
 *
 * @param e The event
 *
 * This will check to make sure the drag object is of the correct mimetype.
 * If it is, it accepts it, and the calls startSpawnerDragDraw which will
 * allocate a new drawing object and set some class variables for future
 * drawing.
 */
void KivioCanvas::dragEnterEvent( TQDragEnterEvent *e )
{
    if( e->provides("kivio/stencilSpawner") )
    {
        e->accept();
        startSpawnerDragDraw( e->pos() );
        activePage()->unselectAllStencils();
        updateAutoGuideLines();
    }
}



/**
 * Handles drag-move events
 *
 * @param e The event
 *
 * This makes sure the drag object is of type kivio/stencilSpawner since these
 * are currently the only type of mime-types accepted.  If so, it accepts the
 * event, and then tells the drawing object to update itself with a new position.
 */
void KivioCanvas::dragMoveEvent( TQDragMoveEvent *e )
{
    // Does it speak our language?
    if( e->provides("kivio/stencilSpawner") )
    {
        e->accept();
        continueSpawnerDragDraw( e->pos() );
    }
}


/**
 * Handles drops for this object
 *
 * @param e The drop event object
 *
 * This function takes care of handling the final drop event of this object.  It will
 * allocate a new object of the currently dragged Spawner type (stencil), and add it
 * to the active page of the document (which actually adds it to the active layer
 * of the active page).
 */
void KivioCanvas::dropEvent( TQDropEvent *e )
{
    // Terminate the drawing object
    endSpawnerDragDraw();

    // Get a pointer to the currently dragged KivioStencilSpawner object
    KivioStencilSpawner *pSpawner = KivioIconView::curDragSpawner();
    
    if( !pSpawner )
        return;
        
    TQPoint pos = e->pos();
    KoPoint pagePoint = snapToGrid(mapFromScreen( pos ));
    view()->addStencilFromSpawner(pSpawner, pagePoint.x(), pagePoint.y());

    // FIXME Select the "selection tool" in case it's not done
}


/**
 * Handles when a drag leaves this object
 *
 * @param e The event object
 *
 * This will call endSpawnerDragDraw() which terminates the drawing
 * object and ends interaction with the drag.
 */
void KivioCanvas::dragLeaveEvent( TQDragLeaveEvent * )
{
    endSpawnerDragDraw();
}



void KivioCanvas::drawSelectedStencilsXOR()
{
    // This should never happen, but check just in case
    if ( !unclippedSpawnerPainter )
        return;

    // Translate the painter so that 0,0 means where the page starts on the canvas
    unclippedSpawnerPainter->painter()->save();
    unclippedSpawnerPainter->painter()->translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);

    // Assign the painter object to the intra-stencil data object, as well
    // as the zoom factor
    m_dragStencilData.painter = unclippedSpawnerPainter;
    m_dragStencilData.zoomHandler = m_pView->zoomHandler();

    KivioStencil *pStencil = activePage()->selectedStencils()->first();
    while( pStencil )
    {
        pStencil->paintOutline( &m_dragStencilData );
        pStencil->paintSelectionHandles( &m_dragStencilData );

        pStencil = activePage()->selectedStencils()->next();
    }

    unclippedSpawnerPainter->painter()->restore();
}

void KivioCanvas::drawStencilXOR( KivioStencil *pStencil )
{
    // This should never happen, but check just in case
    if ( !unclippedSpawnerPainter )
        return;

    // Translate the painter so that 0,0 means where the page starts on the canvas
    unclippedSpawnerPainter->painter()->save();
    unclippedSpawnerPainter->painter()->translate(-m_iXOffset + m_pageOffsetX, -m_iYOffset + m_pageOffsetY);

    // Assign the painter object to the intra-stencil data object, as well
    // as the zoom factor
    m_dragStencilData.painter = unclippedSpawnerPainter;
    m_dragStencilData.zoomHandler = m_pView->zoomHandler();

    pStencil->paintOutline( &m_dragStencilData );
    pStencil->paintSelectionHandles( &m_dragStencilData );

    unclippedSpawnerPainter->painter()->restore();
}

void KivioCanvas::keyPressEvent( TQKeyEvent *e )
{
  if(view()->isShowGuides() && m_guides.keyPressEvent(e)) {
    return;
  }
}

KoPoint KivioCanvas::snapToGridAndGuides(const KoPoint& point)
{
  KoPoint p = point;

  p = snapToGrid(p);

  bool snappedX, snappedY;
  KoPoint guidePoint = snapToGuides(point, snappedX, snappedY);

  if(snappedX) {
    p.setX(guidePoint.x());
  }

  if(snappedY) {
    p.setY(guidePoint.y());
  }

  return p;
}

KoPoint KivioCanvas::snapToGrid(const KoPoint& point)
{
  if (!m_pDoc->grid().isSnap)
    return point;

  KoPoint p = point;

  KoSize dist = m_pDoc->grid().snap;
  KoSize freq = m_pDoc->grid().freq;

  double dx = tqRound(p.x() / freq.width());
  double dy = tqRound(p.y() / freq.height());

  double distx = TQABS(p.x() - (freq.width() * dx));
  double disty = TQABS(p.y() - (freq.height() * dy));

  if(distx < dist.width()) {
    p.setX(freq.width() * dx);
  }

  if(disty < dist.height()) {
    p.setY(freq.height() * dy);
  }

  return p;
}

KoPoint KivioCanvas::snapToGuides(const KoPoint& point, bool &snappedX, bool &snappedY)
{
  snappedX = false;
  snappedY = false;
  KoPoint p = point;

  if (m_pView->isSnapGuides())
  {
    KoGuides::SnapStatus status = KoGuides::SNAP_NONE;
    KoPoint diff;
    m_guides.snapToGuideLines(p, 4, status, diff);
    p += diff;

    if(status & KoGuides::SNAP_HORIZ) {
      snappedY = true;
    }
    if(status & KoGuides::SNAP_VERT) {
      snappedX = true;
    }

    m_guides.repaintSnapping(p, status);
  }

  return p;
}

void KivioCanvas::setViewCenterPoint(const KoPoint &p)
{
  setUpdatesEnabled(false);

  KoRect va = visibleArea();

  double x = TQMAX(0.0, p.x() - (va.width() / 2.0));
  double y = TQMAX(0.0, p.y() - (va.height() / 2.0));

  m_pVertScrollBar->setValue(m_pView->zoomHandler()->zoomItY(y));
  m_pHorzScrollBar->setValue(m_pView->zoomHandler()->zoomItX(x));

  setUpdatesEnabled(true);
}

KoRect KivioCanvas::visibleArea()
{
  KoPoint p0 = mapFromScreen(TQPoint(0,0));
  KoPoint p1 = mapFromScreen(TQPoint(width()-1,height()-1));

  return KoRect(p0.x(), p0.y(), p1.x() - p0.x(), p1.y() - p0.y());
}

void KivioCanvas::setVisibleArea(KoRect r, int margin)
{
  setUpdatesEnabled(false);
  KoZoomHandler zoom;
  zoom.setZoomAndResolution(100, KoGlobal::dpiX(),
    KoGlobal::dpiY());

  float cw = width() - 2 * margin;
  float ch = height() - 2 * margin;

  float zw = cw / (float)zoom.zoomItX(r.width());
  float zh = ch / (float)zoom.zoomItY(r.height());
  float z = TQMIN(zw, zh);

  m_pView->viewZoom(tqRound(z * 100));

  KoPoint c = r.center();

  setViewCenterPoint(KoPoint(c.x(), c.y()));
  setUpdatesEnabled(true);
}

void KivioCanvas::setVisibleAreaByWidth(KoRect r, int margin)
{
  setUpdatesEnabled(false);
  KoZoomHandler zoom;
  zoom.setZoomAndResolution(100, KoGlobal::dpiX(),
    KoGlobal::dpiY());

  float cw = width() - 2*margin;
  float z = cw / (float)zoom.zoomItX(r.width());

  m_pView->viewZoom(tqRound(z * 100));

  KoPoint c = r.center();

  setViewCenterPoint(KoPoint(c.x(), c.y()));
  setUpdatesEnabled(true);
}

void KivioCanvas::setVisibleAreaByHeight(KoRect r, int margin)
{
  setUpdatesEnabled(false);
  KoZoomHandler zoom;
  zoom.setZoomAndResolution(100, KoGlobal::dpiX(),
    KoGlobal::dpiY());

  float ch = height() - 2*margin;
  float z = ch / (float)zoom.zoomItY(r.height());

  m_pView->viewZoom(tqRound(z * 100));

  KoPoint c = r.center();

  setViewCenterPoint(KoPoint(c.x(), c.y()));
  setUpdatesEnabled(true);
}

void KivioCanvas::startPasteMoving()
{
  setEnabled(false);
  KoPoint p = activePage()->getRectForAllSelectedStencils().center();
  m_origPoint.setCoords(p.x(), p.y());

  // Create a new painter object
  beginUnclippedSpawnerPainter();
  drawSelectedStencilsXOR();

  // Build the list of old geometry
  KoRect *pData;
  m_lstOldGeometry.clear();
  KivioStencil* pStencil = activePage()->selectedStencils()->first();

  while( pStencil )
  {
    pData = new KoRect;
    *pData = pStencil->rect();
    m_lstOldGeometry.append(pData);

    pStencil = activePage()->selectedStencils()->next();
  }

  continuePasteMoving(lastPoint);
  m_pasteMoving = true;
  setEnabled(true);
}

void KivioCanvas::continuePasteMoving(const TQPoint &pos)
{
  KoPoint pagePoint = mapFromScreen( pos );

  double dx = pagePoint.x() - m_origPoint.x();
  double dy = pagePoint.y() - m_origPoint.y();

  bool snappedX;
  bool snappedY;

  double newX, newY;

  // Undraw the old stencils
  drawSelectedStencilsXOR();

  // Translate to the new position
  KoPoint p;
  KoRect selectedRect = activePage()->getRectForAllSelectedStencils();

  newX = selectedRect.x() + dx;
  newY = selectedRect.y() + dy;

  // First attempt a snap-to-grid
  p.setCoords(newX, newY);
  p = snapToGrid(p);

  newX = p.x();
  newY = p.y();

  // Now the guides override the grid so we attempt to snap to them
  p.setCoords(selectedRect.x() + dx + selectedRect.width(), selectedRect.y() + dy + selectedRect.height());
  p = snapToGuides(p, snappedX, snappedY);

  if(snappedX) {
    newX = p.x() - selectedRect.width();
  }

  if(snappedY) {
    newY = p.y() - selectedRect.height();
  }

  p.setCoords(selectedRect.x() + dx + (selectedRect.width() / 2.0), selectedRect.y() + dy + (selectedRect.height() / 2.0));
  p = snapToGuides(p, snappedX, snappedY);

  if(snappedX) {
    newX = p.x() - (selectedRect.width() / 2.0);
  }

  if(snappedY) {
    newY = p.y() - (selectedRect.height() / 2.0);
  }

  p.setCoords(selectedRect.x() + dx, selectedRect.y() + dy);
  p = snapToGuides(p, snappedX, snappedY);

  if(snappedX) {
    newX = p.x();
  }

  if(snappedY) {
    newY = p.y();
  }

  dx = newX - selectedRect.x();
  dy = newY - selectedRect.y();

  // Translate to the new position
  KivioStencil *pStencil = activePage()->selectedStencils()->first();
  KoRect* pData = m_lstOldGeometry.first();
  // bool move = true;

  while( pStencil && pData )
  {
    newX = pData->x() + dx;
    newY = pData->y() + dy;

    if( pStencil->protection()->at( kpX ) == false ) {
      pStencil->setX(newX);
    }
    if( pStencil->protection()->at( kpY ) == false ) {
      pStencil->setY(newY);
    }

    pData = m_lstOldGeometry.next();
    pStencil = activePage()->selectedStencils()->next();
  }

  // Draw the stencils
  drawSelectedStencilsXOR();
  m_pView->updateToolBars();
}

void KivioCanvas::endPasteMoving()
{
  KivioStencil *pStencil = activePage()->selectedStencils()->first();
  KoRect *pData = m_lstOldGeometry.first();

  while( pStencil && pData )
  {
    if(pStencil->type() == kstConnector) {
      pStencil->searchForConnections(m_pView->activePage(), m_pView->zoomHandler()->unzoomItY(4));
    }

    pData = m_lstOldGeometry.next();
    pStencil = activePage()->selectedStencils()->next();
  }

  drawSelectedStencilsXOR();

  endUnclippedSpawnerPainter();

  // Clear the list of old geometry
  m_lstOldGeometry.clear();
  m_pasteMoving = false;
}

void KivioCanvas::updateAutoGuideLines()
{
  TQValueList<double> hGuideLines;
  TQValueList<double> vGuideLines;
  TQPtrList<KivioLayer> layerList = *(activePage()->layers());
  TQPtrListIterator<KivioLayer> layerIt(layerList);
  KivioLayer* layer = 0;
  TQPtrList<KivioStencil> stencilList;
  TQPtrListIterator<KivioStencil> stencilIt(stencilList);
  KivioStencil* stencil = 0;

  while((layer = layerIt.current()) != 0) {
    ++layerIt;

    if(layer->visible()) {
      stencilList = *(layer->stencilList());
      stencilIt.toFirst();

      while((stencil = stencilIt.current()) != 0) {
        ++stencilIt;

        if(!stencil->isSelected()) {
          hGuideLines << stencil->y() << (stencil->y() + (stencil->h() / 2.0)) << (stencil->y() + stencil->h());
          vGuideLines << stencil->x() << (stencil->x() + (stencil->w() / 2.0)) << (stencil->x() + stencil->w());
        }
      }
    }
  }

  KoPageLayout pl = activePage()->paperLayout();

  // Add the middle of the page and the margins
  hGuideLines << (pl.ptHeight / 2.0) << pl.ptTop << pl.ptBottom;
  vGuideLines << (pl.ptWidth / 2.0) << pl.ptLeft << pl.ptRight;

  guideLines().setAutoGuideLines(hGuideLines, vGuideLines);
}

#include "kivio_canvas.moc"
