
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2016 Matthias Koefferlein

  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 St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent>
#include <QtGui/QKeyEvent>

#include "layViewObject.h"
#include "layCanvasPlane.h"
#include "layBitmap.h"
#include "tlException.h"
#include "tlAlgorithm.h"
#include "layExceptions.h"

#include <memory>

namespace lay
{

//  The distance by which the mouse must move in order to create a press/move/release
//  event rather than a single click event:
const int click_tolerance = 5;

// ---------------------------------------------------------------
//  Implementation of DragDropDataBase 

const char *drag_drop_mime_type = "application/klayout-ddd";

QMimeData *
DragDropDataBase::to_mime_data () const
{
  QMimeData *mimeData = new QMimeData();
  mimeData->setData (QString::fromAscii (drag_drop_mime_type), serialized ());
  return mimeData;
}

// ---------------------------------------------------------------
//  Implementation of CellDragDropData

QByteArray
CellDragDropData::serialized () const
{
  QByteArray data;
  QDataStream stream (&data, QIODevice::WriteOnly);

  stream << QString::fromAscii ("CellDragDropData");
  stream << (quintptr) mp_layout;
  stream << m_cell_index;

  return data;
}

bool
CellDragDropData::deserialize (const QByteArray &ba)
{
  QDataStream stream (const_cast<QByteArray *> (&ba), QIODevice::ReadOnly);

  QString tag;
  stream >> tag;

  if (tag == QString::fromAscii ("CellDragDropData")) {

    quintptr p = 0;
    stream >> p;
    mp_layout = reinterpret_cast <const db::Layout *> (p);
    stream >> m_cell_index;
    return true;

  } else {

    return false;

  }
}

// ---------------------------------------------------------------
//  A helper function to convert a Qt modifier/buttons to klayout buttons

static unsigned int 
qt_to_buttons (Qt::MouseButtons b, Qt::KeyboardModifiers m)
{
  // this is a straightforward conversion with the excepton that
  // MetaModifyer+LeftButton is taken as a RightButton
  // This is useful on MAC OX for people wizh a one-button mouse.
  // They can do a right click by doing a ctrl-leftclick.
  // BTW: On a MAC's keyboard, the cmd-key is received here as a ControlModifier
  // while the ctrl-key is received as a MetaModifier
  return ((b & Qt::LeftButton) ? ((m & Qt::MetaModifier) ? RightButton : LeftButton) : 0) |
         ((b & Qt::MidButton) ? MidButton : 0) |
         ((b & Qt::RightButton) ? RightButton : 0) |
         ((m & Qt::ShiftModifier) != 0 ? ShiftButton : 0) |
         ((m & Qt::ControlModifier) != 0 ? ControlButton : 0) |
         ((m & Qt::AltModifier) != 0 ? AltButton : 0);
}

// ---------------------------------------------------------------
//  BackgroundViewObject implementation

BackgroundViewObject::BackgroundViewObject (ViewObjectWidget *widget)
  : mp_widget (widget), m_visible (true), m_z_order (0)
{
  if (widget) {
    widget->m_background_objects.insert (this, false /*not owned by the widget*/);
    redraw ();
  }
}

BackgroundViewObject::~BackgroundViewObject ()
{
  redraw ();
}

void 
BackgroundViewObject::visible (bool vis)
{
  if (vis != m_visible) {
    m_visible = vis;
    redraw ();
  }
}

void 
BackgroundViewObject::redraw ()
{
  if (widget ()) {
    widget ()->touch_bg ();
  }
}

void 
BackgroundViewObject::z_order (int z)
{
  if (z != m_z_order) {
    m_z_order = z;
    redraw ();
  }
}

// ---------------------------------------------------------------
//  ViewObject implementation

ViewObject::ViewObject (ViewObjectWidget *widget, bool _static)
  : mp_widget (widget), m_static (_static), m_visible (true)
{
  if (widget) {
    widget->m_objects.insert (this, false /*not owned by the widget*/);
    redraw ();
  }
}

ViewObject::~ViewObject ()
{
  redraw ();
}

void 
ViewObject::visible (bool vis)
{
  if (vis != m_visible) {
    m_visible = vis;
    redraw ();
  }
}

void 
ViewObject::redraw ()
{
  if (widget ()) {
    if (m_static) {
      widget ()->touch ();
    } else {
      widget ()->update ();
    }
  }
}

void
ViewObject::thaw ()
{
  if (widget ()) {
    widget ()->thaw (this);
  }
}

void
ViewObject::freeze ()
{
  if (widget ()) {
    widget ()->freeze (this);
  }
}

// ---------------------------------------------------------------
//  ViewService implementation

ViewService::ViewService (ViewObjectWidget *widget)
  : mp_widget (widget), m_abs_grab (false), m_enabled (true)
{
  if (widget) {
    widget->register_service (this);
  }
}

ViewService::~ViewService ()
{
  if (mp_widget) {
    mp_widget->unregister_service (this);
  }
  mp_widget = 0;
}

void
ViewService::enable (bool en)
{
  m_enabled = en;
}

void 
ViewService::set_cursor (lay::Cursor::cursor_shape cursor)
{
  mp_widget->set_cursor (cursor);
}

// ---------------------------------------------------------------
//  ViewObject implementation

ViewObjectWidget::ViewObjectWidget (QWidget *parent, const char *name)
  : QWidget (parent), 
    m_objects (this), m_services (this),
    m_needs_update_static (false),
    m_needs_update_bg (false),
    mp_active_service (0),
    m_mouse_pressed_state (false),
    m_mouse_buttons (0),
    m_in_mouse_move (false),
    m_cursor (lay::Cursor::none),
    m_default_cursor (lay::Cursor::none)
{
  setMouseTracking (true); 
  setObjectName (QString::fromAscii (name));
  setAcceptDrops (true);
}

ViewObjectWidget::~ViewObjectWidget ()
{
  //  release any mouse grabs now
  while (m_grabbed.begin () != m_grabbed.end ()) {
    ungrab_mouse (*m_grabbed.begin ());
  }

  //  explicitly clear all services. If we left that to the destructor, we would get in trouble when the services' destructors
  //  access the ViewObjectWidget.
  m_services.clear ();
}

void
ViewObjectWidget::register_service (lay::ViewService *svc)
{
  m_services.insert (svc);
}

void
ViewObjectWidget::unregister_service (lay::ViewService *svc)
{
  ungrab_mouse (svc);
  m_services.remove (svc);
}

void
ViewObjectWidget::activate (lay::ViewService *service)
{
  if (mp_active_service != service) {
    if (mp_active_service) {
BEGIN_PROTECTED  
      mp_active_service->deactivated ();
END_PROTECTED
    }
    mp_active_service = service;
    if (mp_active_service) {
BEGIN_PROTECTED  
      mp_active_service->activated ();
END_PROTECTED
    }
  }
}

void 
ViewObjectWidget::set_cursor (lay::Cursor::cursor_shape cursor)
{
  m_cursor = cursor;
}

void
ViewObjectWidget::set_default_cursor (lay::Cursor::cursor_shape cursor)
{
  if (cursor != m_default_cursor) {
    m_default_cursor = cursor;
    if (m_cursor == lay::Cursor::none) {
      if (m_default_cursor == lay::Cursor::none) {
        unsetCursor ();
      } else {
        setCursor (lay::Cursor::qcursor (m_default_cursor));
      }
    }
  }
}

void 
ViewObjectWidget::begin_mouse_event (lay::Cursor::cursor_shape cursor)
{
  m_cursor = cursor;
}

void 
ViewObjectWidget::end_mouse_event ()
{
  if (m_cursor == lay::Cursor::none) {
    if (m_default_cursor == lay::Cursor::none) {
      unsetCursor ();
    } else {
      setCursor (lay::Cursor::qcursor (m_default_cursor));
    }
  } else if (m_cursor != lay::Cursor::keep) {
    setCursor (lay::Cursor::qcursor (m_cursor));
  }
}

void 
ViewObjectWidget::keyPressEvent (QKeyEvent *e)
{
BEGIN_PROTECTED  

  unsigned int buttons = qt_to_buttons (Qt::MouseButtons (), e->modifiers ());

  bool done = false;
  if (mp_active_service) {
    done = (mp_active_service->enabled () && mp_active_service->key_event ((unsigned int) e->key(), buttons));
  }

  if (! done) {
    key_event ((unsigned int) e->key (), buttons);
  }

END_PROTECTED
}

DragDropDataBase *get_drag_drop_data (const QMimeData *data)
{
  if (! data || ! data->hasFormat (QString::fromAscii (drag_drop_mime_type))) {
    return 0;
  }

  QByteArray ba = data->data (QString::fromAscii (drag_drop_mime_type));

  //  TODO: provide some global mechanism to register drag & drop classes
  std::auto_ptr<DragDropDataBase> cd (new CellDragDropData ());
  if (cd->deserialize (ba)) {
    return cd.release ();
  } 

  //  TODO: more ...

  return 0;
}

void 
ViewObjectWidget::dragEnterEvent (QDragEnterEvent *event)
{
BEGIN_PROTECTED  
  const DragDropDataBase *dd = get_drag_drop_data (event->mimeData ());
  if (dd) {

    db::DPoint p = m_trans.inverted () * db::DPoint (event->pos ().x (), height () - 1 - event->pos ().y ());

    bool done = drag_enter_event (p, dd);
    service_iterator svc = begin_services ();
    while (svc != end_services () && !done) {
      service_iterator next = svc;
      ++next;
      done = svc->drag_enter_event (p, dd);
      svc = next;
    }

    if (done) {
      event->acceptProposedAction ();
    }

  }

END_PROTECTED
}

void 
ViewObjectWidget::dragLeaveEvent (QDragLeaveEvent *event)
{
BEGIN_PROTECTED  

  drag_leave_event ();
  service_iterator svc = begin_services ();
  while (svc != end_services ()) {
    service_iterator next = svc;
    ++next;
    svc->drag_leave_event ();
    svc = next;
  }

END_PROTECTED
}

void 
ViewObjectWidget::dragMoveEvent (QDragMoveEvent *event)
{
BEGIN_PROTECTED  

  const DragDropDataBase *dd = get_drag_drop_data (event->mimeData ());
  if (dd) {

    db::DPoint p = m_trans.inverted () * db::DPoint (event->pos ().x (), height () - 1 - event->pos ().y ());

    bool done = drag_move_event (p, dd);
    service_iterator svc = begin_services ();
    while (svc != end_services () && !done) {
      service_iterator next = svc;
      ++next;
      done = svc->drag_move_event (p, dd);
      svc = next;
    }

  }

END_PROTECTED
}

void 
ViewObjectWidget::dropEvent (QDropEvent *event)
{
BEGIN_PROTECTED  

  const DragDropDataBase *dd = get_drag_drop_data (event->mimeData ());
  if (dd) {

    db::DPoint p = m_trans.inverted () * db::DPoint (event->pos ().x (), height () - 1 - event->pos ().y ());

    bool done = drop_event (p, dd);
    service_iterator svc = begin_services ();
    while (svc != end_services () && !done) {
      service_iterator next = svc;
      ++next;
      done = svc->drop_event (p, dd);
      svc = next;
    }

  }

END_PROTECTED
}

void 
ViewObjectWidget::mouseMoveEvent (QMouseEvent *e)
{
BEGIN_PROTECTED  
  m_mouse_pos = e->pos ();
  m_mouse_buttons = qt_to_buttons (e->buttons (), e->modifiers ());
  do_mouse_move ();
END_PROTECTED
}

void
ViewObjectWidget::do_mouse_move ()
{
  m_in_mouse_move = true;

  if (m_mouse_pressed_state && 
    (abs (m_mouse_pos.x () - m_mouse_pressed.x ()) > click_tolerance || abs (m_mouse_pos.y () - m_mouse_pressed.y ()) > click_tolerance)) {

    begin_mouse_event (lay::Cursor::none);

    m_mouse_pressed_state = false;

    bool done = false; 
    
    db::DPoint p = m_trans.inverted () * db::DPoint (m_mouse_pressed.x (), height () - 1 - m_mouse_pressed.y ());

    for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
      std::list<ViewService *>::iterator gg = g;
      ++gg;
      done = ((*g)->enabled () && (*g)->mouse_press_event (p, m_mouse_buttons, true));
      g = gg;
    }

    if (! done && mp_active_service) {
      done = (mp_active_service->enabled () && mp_active_service->mouse_press_event (p, m_mouse_buttons, true));
    }

    service_iterator svc = begin_services ();
    while (svc != end_services () && !done) {
      service_iterator next = svc;
      ++next;
      done = (svc->enabled () && svc->mouse_press_event (p, m_mouse_buttons, false));
      svc = next;
    }

    if (! done) {
      mouse_press_event (p, m_mouse_buttons);
    }

    end_mouse_event ();

  }

  if (! m_mouse_pressed_state) {

    begin_mouse_event (lay::Cursor::none);

    bool done = false; 

    db::DPoint p = m_trans.inverted () * db::DPoint (m_mouse_pos.x (), height () - 1 - m_mouse_pos.y ());

    for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
      std::list<ViewService *>::iterator gg = g;
      ++gg;
      done = ((*g)->enabled () && (*g)->mouse_move_event (p, m_mouse_buttons, true));
      g = gg;
    }

    if (! done && mp_active_service) {
      done = (mp_active_service->enabled () && mp_active_service->mouse_move_event (p, m_mouse_buttons, true));
    }

    service_iterator svc = begin_services ();
    while (svc != end_services () && !done) {
      service_iterator next = svc;
      ++next;
      done = (svc->enabled () && svc->mouse_move_event (p, m_mouse_buttons, false));
      svc = next;
    }

    if (! done) {
      mouse_move_event (p, m_mouse_buttons);
    }

    end_mouse_event ();

  }

  m_in_mouse_move = false;
}

void 
ViewObjectWidget::mouseDoubleClickEvent (QMouseEvent *e)
{
BEGIN_PROTECTED  
  begin_mouse_event (lay::Cursor::none);

  setFocus ();

  bool done = false; 

  m_mouse_pos = e->pos ();
  m_mouse_pressed = e->pos ();
  m_mouse_pressed_state = false;

  unsigned int buttons = qt_to_buttons (e->buttons (), e->modifiers ());

  db::DPoint p = m_trans.inverted () * db::DPoint (e->pos ().x (), height () - 1 - e->pos ().y ());

  for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
    std::list<ViewService *>::iterator gg = g;
    ++gg;
    done = ((*g)->m_enabled && (*g)->mouse_double_click_event (p, buttons, true));
    g = gg;
  }

  if (! done && mp_active_service) {
    done = (mp_active_service->enabled () && mp_active_service->mouse_double_click_event (p, buttons, true));
  }

  service_iterator svc = begin_services ();
  while (svc != end_services () && !done) {
    service_iterator next = svc;
    ++next;
    done = (svc->enabled () && svc->mouse_double_click_event (p, buttons, false));
    svc = next;
  }

  if (! done) {
    mouse_double_click_event (p, buttons);
  }

  end_mouse_event ();
END_PROTECTED
}

void 
ViewObjectWidget::enterEvent (QEvent *e)
{
BEGIN_PROTECTED  
  begin_mouse_event ();

  bool done = false; 

  for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
    std::list<ViewService *>::iterator gg = g;
    ++gg;
    done = ((*g)->enabled () && (*g)->enter_event (true));
    g = gg;
  }

  if (! done && mp_active_service) {
    done = (mp_active_service->enabled () && mp_active_service->enter_event (true));
  }

  service_iterator svc = begin_services ();
  while (svc != end_services () && !done) {
    service_iterator next = svc;
    ++next;
    done = (svc->enabled () && svc->enter_event (false));
    svc = next;
  }

  if (! done) {
    enter_event ();
  }

  end_mouse_event ();
END_PROTECTED
}

void 
ViewObjectWidget::leaveEvent (QEvent *e)
{
BEGIN_PROTECTED  
  begin_mouse_event ();

  bool done = false; 

  for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
    std::list<ViewService *>::iterator gg = g;
    ++gg;
    done = ((*g)->enabled () && (*g)->leave_event (true));
    g = gg;
  }

  if (! done && mp_active_service) {
    done = (mp_active_service->enabled () && mp_active_service->leave_event (true));
  }

  service_iterator svc = begin_services ();
  while (svc != end_services () && !done) {
    service_iterator next = svc;
    ++next;
    done = (svc->enabled () && svc->leave_event (false));
    svc = next;
  }

  if (! done) {
    leave_event ();
  }

  end_mouse_event ();
END_PROTECTED
}

void 
ViewObjectWidget::wheelEvent (QWheelEvent *e)
{
BEGIN_PROTECTED  
  begin_mouse_event ();

  e->ignore ();

  bool done = false; 

  unsigned int buttons = qt_to_buttons (e->buttons (), e->modifiers ());
  bool horizonal = (e->orientation () == Qt::Horizontal);

  db::DPoint p = m_trans.inverted () * db::DPoint (e->pos ().x (), height () - 1 - e->pos ().y ());

  for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
    std::list<ViewService *>::iterator gg = g;
    ++gg;
    done = ((*g)->enabled () && (*g)->wheel_event (e->delta (), horizonal, p, buttons, true));
    g = gg;
  }

  if (! done && mp_active_service) {
    done = (mp_active_service->enabled () && mp_active_service->wheel_event (e->delta (), horizonal, p, buttons, true));
  }

  service_iterator svc = begin_services ();
  while (svc != end_services () && !done) {
    service_iterator next = svc;
    ++next;
    done = (svc->enabled () && svc->wheel_event (e->delta (), horizonal, p, buttons, false));
    svc = next;
  }

  if (! done) {
    wheel_event (e->delta (), horizonal, p, buttons);
  }

  end_mouse_event ();
END_PROTECTED
}

void 
ViewObjectWidget::mousePressEvent (QMouseEvent *e)
{
  setFocus ();

  m_mouse_pos = e->pos ();
  m_mouse_pressed = e->pos ();

  m_mouse_buttons = qt_to_buttons (e->buttons (), e->modifiers ());
  
  m_mouse_pressed_state = true;
}

void 
ViewObjectWidget::mouseReleaseEvent (QMouseEvent *e)
{
BEGIN_PROTECTED  
  begin_mouse_event ();

  bool done = false; 

  m_mouse_pos = e->pos ();
  db::DPoint p = m_trans.inverted () * db::DPoint (e->pos ().x (), height () - 1 - e->pos ().y ());

  for (std::list<ViewService *>::iterator g = m_grabbed.begin (); !done && g != m_grabbed.end (); ) {
    std::list<ViewService *>::iterator gg = g;
    ++gg;
    if (m_mouse_pressed_state) {
      done = (*g)->enabled () && (*g)->mouse_click_event (p, m_mouse_buttons, true);
    } else {
      done = (*g)->enabled () && (*g)->mouse_release_event (p, m_mouse_buttons, true);
    }
    g = gg;
  }

  if (! done && mp_active_service && mp_active_service->enabled ()) {
    if (m_mouse_pressed_state) {
      done = mp_active_service->mouse_click_event (p, m_mouse_buttons, true);
    } else {
      done = mp_active_service->mouse_release_event (p, m_mouse_buttons, true);
    }
  }

  service_iterator svc = begin_services ();
  while (svc != end_services () && !done) {
    service_iterator next = svc;
    ++next;
    if (svc->enabled ()) {
      if (m_mouse_pressed_state) {
        done = svc->mouse_click_event (p, m_mouse_buttons, false);
      } else {
        done = svc->mouse_release_event (p, m_mouse_buttons, false);
      }
    }
    svc = next;
  }

  if (! done) {
    if (m_mouse_pressed_state) {
      mouse_click_event (p, m_mouse_buttons);
    } else {
      mouse_release_event (p, m_mouse_buttons);
    }
  }

  end_mouse_event ();
END_PROTECTED

  m_mouse_pressed_state = false;
}

void
ViewObjectWidget::mouse_event_trans (const db::DCplxTrans &trans)
{
  if (trans != m_trans) {
    m_trans = trans;
    //  issue a move event in order to reposition the mouse in the new coordinate system
    //  since this may be called from within a mouse move event handler we need the recursion sentinel
    if (! m_in_mouse_move) {
      do_mouse_move ();
    }
  }
}

void 
ViewObjectWidget::drag_cancel ()
{
  for (service_iterator svc = begin_services (); svc != end_services (); ++svc) {
    svc->drag_cancel ();
  }
}

namespace 
{
  struct z_order_compare_f
  {
    bool operator() (lay::BackgroundViewObject *a, lay::BackgroundViewObject *b) const
    {
      int za = a->z_order ();
      int zb = b->z_order ();
      return za < zb;
    }
  };
}

void 
ViewObjectWidget::do_render_bg (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas)
{
  m_needs_update_bg = false;

  std::vector<lay::BackgroundViewObject *> bg_objects;

  for (background_object_iterator obj = begin_background_objects (); obj != end_background_objects (); ++obj) {
    if (obj->is_visible ()) {
      bg_objects.push_back (&*obj);
    }
  }

  // Sort objects by z-order
  tl::sort (bg_objects.begin (), bg_objects.end (), z_order_compare_f ());

  for (std::vector<lay::BackgroundViewObject *>::const_iterator obj = bg_objects.begin (); obj != bg_objects.end (); ++obj) {
    BEGIN_PROTECTED_SILENT
    (*obj)->render_bg (vp, canvas);
    END_PROTECTED_SILENT
  }
}

void 
ViewObjectWidget::do_render (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas, bool st)
{
  if (st) {
    m_needs_update_static = false;
  }

  for (object_iterator obj = begin_objects (); obj != end_objects (); ++obj) {
    if (obj->m_static == st && obj->is_visible ()) {
      BEGIN_PROTECTED_SILENT
      obj->render (vp, canvas);
      END_PROTECTED_SILENT
    }
  }

  canvas.sort_planes ();
}

void 
ViewObjectWidget::grab_mouse (ViewService *obj, bool a)
{
  obj->m_abs_grab = a; // not used currently

  //  only add to m_grabbed if not grabbed already
  std::list<ViewService *>::iterator g;
  for (g = m_grabbed.begin (); g != m_grabbed.end () && *g != obj; ++g) {
    ;
  }
  if (g == m_grabbed.end ()) {
    m_grabbed.push_front (obj);
  }
}

void 
ViewObjectWidget::ungrab_mouse (ViewService *obj)
{
  std::list<ViewService *>::iterator g;
  for (g = m_grabbed.begin (); g != m_grabbed.end () && *g != obj; ++g) {
    ;
  }
  if (g != m_grabbed.end ()) {
    m_grabbed.erase (g);
  }
}

void 
ViewObjectWidget::freeze (ViewObject *obj)
{
  if (! obj->m_static) {
    obj->m_static = true;
    m_needs_update_static = true;
    //  no update needed since the display will not change through this.
  }
}

void 
ViewObjectWidget::thaw (ViewObject *obj)
{
  if (obj->m_static) {
    obj->m_static = false;
    m_needs_update_static = true;
    //  no update needed since the display will not change through this.
  }
}

void
ViewObjectWidget::touch ()
{
  if (! m_needs_update_static) {
    m_needs_update_static = true;
    update ();
  }
}

void
ViewObjectWidget::touch_bg ()
{
  if (! m_needs_update_bg) {
    m_needs_update_bg = true;
    update ();
  }
}

void 
ViewObjectWidget::added (tl::CollectionBase *coll, tl::Collectable *)
{
  if (coll == & m_objects) {
    touch ();
  }
}

void 
ViewObjectWidget::removed (tl::CollectionBase *coll, tl::Collectable *obj)
{
  if (coll == & m_objects) {
    touch ();
    update ();
  }
  if (coll == & m_services) {
    if (mp_active_service == obj) {
      mp_active_service = 0;
    }
  }
}

db::DBox 
ViewObjectWidget::mouse_event_viewport () const
{
  db::DPoint p1 = m_trans.inverted () * db::DPoint (0, 0);
  db::DPoint p2 = m_trans.inverted () * db::DPoint (width (), height ());
  return db::DBox (p1, p2);
}

// ---------------------------------------------------------------
//  BitmapViewObjectCanvas implementation

BitmapViewObjectCanvas::BitmapViewObjectCanvas ()
  : ViewObjectCanvas (), m_renderer (1, 1, 1.0), m_width (1), m_height (1), m_resolution (1.0)
{
  // .. nothing yet ..
}

BitmapViewObjectCanvas::BitmapViewObjectCanvas (unsigned int width, unsigned int height, double resolution)
  : ViewObjectCanvas (), m_renderer (width, height, resolution), 
    m_width (width), m_height (height), m_resolution (resolution)
{
  // .. nothing yet ..
}

BitmapViewObjectCanvas::~BitmapViewObjectCanvas ()
{
  clear_fg_bitmaps ();
}

lay::CanvasPlane *
BitmapViewObjectCanvas::plane (const lay::ViewOp &style)
{
  std::map <lay::ViewOp, unsigned int>::iterator b = m_fg_bitmap_table.find (style);
  if (b == m_fg_bitmap_table.end ()) {

    //  we need to create a new plane
    m_fg_bitmap_table.insert (std::make_pair (style, mp_alloc_bitmaps.size ()));
    lay::Bitmap *bm = new lay::Bitmap (m_width, m_height, m_resolution);
    mp_fg_bitmaps.push_back (bm);
    mp_alloc_bitmaps.push_back (bm);
    m_fg_view_ops.push_back (style);
    return bm;
    
  } else {
    //  we can recycle a current one
    return mp_alloc_bitmaps [b->second];
  }
}

lay::CanvasPlane *
BitmapViewObjectCanvas::plane (const std::vector<lay::ViewOp> &style)
{
  std::map <std::vector<lay::ViewOp>, unsigned int>::iterator b = m_fgv_bitmap_table.find (style);
  if (b == m_fgv_bitmap_table.end ()) {

    //  we need to create a new bitmap
    m_fgv_bitmap_table.insert (std::make_pair (style, mp_alloc_bitmaps.size ()));
    lay::Bitmap *bm = new lay::Bitmap (m_width, m_height, m_resolution);
    mp_alloc_bitmaps.push_back (bm);
    for (std::vector<lay::ViewOp>::const_iterator s = style.begin (); s != style.end (); ++s) {
      mp_fg_bitmaps.push_back (bm);
      m_fg_view_ops.push_back (*s);
    }
    return bm;
    
  } else {
    //  we can recycle a current one
    return mp_alloc_bitmaps [b->second];
  }
}

void 
BitmapViewObjectCanvas::clear_fg_bitmaps ()
{
  for (std::vector <lay::Bitmap *>::iterator i = mp_alloc_bitmaps.begin (); i != mp_alloc_bitmaps.end (); ++i) {
    if (*i) {
      delete *i;
    }
  }
  mp_alloc_bitmaps.clear ();
  mp_fg_bitmaps.clear ();
  m_fg_view_ops.clear ();
  m_fg_bitmap_table.clear ();
  m_fgv_bitmap_table.clear ();
}

void
BitmapViewObjectCanvas::sort_planes ()
{
  //  sort the planes by view operator - this ensures a certain plane order as implied by
  //  the plane index of the plane operators.
  std::vector <std::pair <lay::ViewOp, lay::Bitmap *> > bitmaps;
  bitmaps.reserve (mp_fg_bitmaps.size ());
  for (unsigned int i = 0; i < mp_fg_bitmaps.size (); ++i) {
    bitmaps.push_back (std::make_pair (m_fg_view_ops [i], mp_fg_bitmaps [i]));
  }
  tl::sort (bitmaps.begin (), bitmaps.end ());
  for (unsigned int i = 0; i < mp_fg_bitmaps.size (); ++i) {
    m_fg_view_ops [i] = bitmaps [i].first;
    mp_fg_bitmaps [i] = bitmaps [i].second;
  }
}

void 
BitmapViewObjectCanvas::set_size (unsigned int width, unsigned int height, double resolution)
{
  m_renderer = lay::BitmapRenderer (width, height, resolution);
  m_width = width;
  m_height = height;
  m_resolution = resolution;
}

void 
BitmapViewObjectCanvas::set_size (unsigned int width, unsigned int height)
{
  m_renderer = lay::BitmapRenderer (width, height, m_resolution);
  m_width = width;
  m_height = height;
}

void 
BitmapViewObjectCanvas::set_size (double resolution)
{
  m_renderer = lay::BitmapRenderer (m_width, m_height, resolution);
  m_resolution = resolution;
}

}

