Logo Search packages:      
Sourcecode: passepartout version File versions  Download package

docview.cc

///
// Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING
///
#include "docview.h"

#include <cassert>
#include <string>
#include <cstdlib>
#include <vector>
#include <algorithm>

#include <gtkmm/style.h>

#include "document/loader.h"
#include "document/textframe.h"
#include "document/imageframe.h"
#include "document/rasterframe.h"
#include "document/document.h"
#include "document/page.h"

#include "util/warning.h"
#include "util/stringutil.h"
#include "util/filesys.h"

#include "widget/usererror.h"

#include "groupmeta.h"
#include "pptcore.h"
#include "pagesel.h"
#include "config.h"
#include "lengthunits.h"

float DocumentView::pt2scr(float pt) const {
   return int(resolution * zoom_factor 
            * length_units.from_base(pt, "in") + 0.5);
}

float DocumentView::scr2pt(float scr) const {
   return length_units.to_base(scr / (resolution * zoom_factor), "in");
}  

Gdk::Point DocumentView::pt2scr(const Vector& pt) const {
  double x, y;
  world_to_window(pt.x, -pt.y, x, y);
  return Gdk::Point(int(x + 0.5), int(y + 0.5));
}

Vector DocumentView::scr2pt(const Gdk::Point& scr) const {
  Vector w;
  window_to_world(scr.get_x(), scr.get_y(), w.x, w.y);
  w.y = -w.y;
  return w;
}


//////////////////////////////////// BEGIN JUNK
void DocumentView::insert_page_before() {
  if(document)
    template_page_dialog->show_it(true, 
                          document->get_num_of_pages() 
                          ? current_page_num
                          : document->get_first_page_num());
}

void DocumentView::insert_page_after() {
  if(document)
    template_page_dialog->show_it(true, 
                          document->get_num_of_pages()
                          ? current_page_num + 1
                          : document->get_first_page_num());
}

void DocumentView::delete_page() {
  if(document) {
    pageview.clear(); // Do this first, so the viewent won't be
                      // listening to the selection_changed_signal
                      // which is emitted after the page has been
                      // deleted.
    document->delete_page(current_page_num);
  }
}


void DocumentView::new_text_frame(TextStream *stream) {
  Page *page = get_page();
  if(!page || !document)
    return;
  
  int w = 200, h = 300;
  // default to the first (alphabetically) stream
  page->addObject(new TextFrame(page, stream, w, h));
}

void DocumentView::new_image_frame(std::string filename, float res) {
  Page *page = get_page();
  if(!page)
    return;
  get_document()->select_all(false);
  
  /// \todo more helpful error messages
  if(ImageFrame::is_postscript(filename))
    page->addObject(new ImageFrame(page, filename));
  else {
    try {
      page->addObject(new RasterFrame(page, filename, res));
    } catch(const std::exception& e){
      throw UserError("Could not open \"" + filename
                  + "\"", e);
    }
  }
}

void DocumentView::select_all_frames() {
  if(Page *page = get_page()) {
      page->select_all(true); 
    }
}

void DocumentView::unselect_all_frames() {
  if(Page *page = get_page()) {
      page->select_all(false); 
    }
}

void DocumentView::delete_selected() {
  if(DocRef doc = get_document()) {
    doc->delete_selected();
  }
}  

void DocumentView::group_selected() {
  if(Page *page = get_page()) {
    page->group_selected();
  }
}

void DocumentView::ungroup_selected() {
  if(Page *page = get_page()) {
    page->ungroup_selected();
  }
}

void DocumentView::rearrange_selected(RearrangeTarget target) {
  if(Page *page = get_page()) {
    page->rearrange_selected(target);
  }
}  
//////////////////////////////////////END JUNK

Page *DocumentView::get_page() {
  if(document) {
    Page *page = document->get_page(current_page_num);
    // if page has changed, release memory from old page
    if(old_page && page != old_page)
      pageview.clear();
    old_page = page;
    return page;
  } else
    return 0;
}

void DocumentView::act_on_document_change(DocRef document_) {
  if(!document || document != document_)
    return;
  int first = document->get_first_page_num();
  int num = document->get_num_of_pages();
  int last = first + num - 1;
  if(current_page_num < first) {
    current_page_num = first;
    current_page_num_changed_signal();
  } else if(current_page_num > last) {
    current_page_num = last;
    current_page_num_changed_signal();
  }
  document_changed_signal();
}

void DocumentView::act_on_selection_change(DocRef document_) {
  if(!document || document != document_)
    return;
  update_handles();
}

void DocumentView::set_current_page_num(int current) {
  if(!document)
    return;
  int first = document->get_first_page_num();
  int num = document->get_num_of_pages();
  int last = first + num - 1;
  if(current < first)
    current = first;
  else if(current > last)
    current = last;
  if(current != current_page_num) {
      current_page_num = current;
      current_page_num_changed_signal();
    }
  if(Page *page = get_page()) {
    pageview = core.getMeta("page")->create_viewent(*this, *page);
  }
}

void DocumentView::update_document_size(DocRef document_) {
  if(!document || document != document_)
    return;
  float w = document->get_width();
  float h = document->get_height();
  
  reset_scroll_region();
  
  no_pages_frame->property_x1() = 0;
  no_pages_frame->property_y1() = 0;
  no_pages_frame->property_x2() = w;
  no_pages_frame->property_y2() = -h;
  
  no_pages_text->property_x() = w / 2;
  no_pages_text->property_y() = -h / 1.8;
}

void DocumentView::set_document(DocMeta d) {
  if(document == d)
    return;

  document = d;
  update_handles();
  if(document) {
    update_document_size(document.get_document());
    no_pages_group.show();
    
    current_page_num = document->get_first_page_num();
    
    Document::size_changed_signal.connect
      (slot(*this, &DocumentView::update_document_size));
    Document::changed_signal.connect
      (slot(*this, &DocumentView::act_on_document_change));
    Document::selection_changed_signal.connect
      (slot(*this, &DocumentView::act_on_selection_change));
  } else {
    no_pages_group.hide();
  }

  document_changed_signal(); //?
  document_set_signal();

  if(Page *page = get_page()) {
    pageview = core.getMeta("page")->create_viewent(*this, *page);
  } else {
    pageview.clear();
  }
}

DocumentView::DocumentView(DocMeta d, float zoom_factor)
  : no_pages_group(*root()), pagent_group(*root()), guide_group(*root()),
    handle_group(*root()),
    snap_mode(snap::NONE), zoom_factor(zoom_factor),
    document(DocRef()), old_page(0)
{
  set_events(Gdk::EXPOSURE_MASK
           | Gdk::LEAVE_NOTIFY_MASK
           | Gdk::BUTTON_PRESS_MASK
           | Gdk::BUTTON_RELEASE_MASK
           | Gdk::POINTER_MOTION_MASK
           | Gdk::POINTER_MOTION_HINT_MASK);
  Glib::RefPtr<Gdk::Colormap> colormap = get_default_colormap();
  white = Gdk::Color("white");
  black = Gdk::Color("black");
  gray = Gdk::Color("gray");
  red = Gdk::Color("red");
  colormap->alloc_color(white);
  colormap->alloc_color(black);
  colormap->alloc_color(gray);
  colormap->alloc_color(red);
  
  Glib::RefPtr<Gdk::Screen> screen =
    Gdk::Display::get_default()->get_default_screen();
  float xresolution = screen->get_width() / (screen->get_width_mm() / 25.4);
  float yresolution = screen->get_height() / (screen->get_height_mm() / 25.4);
  resolution = (xresolution + yresolution) / 2;  // average :-)
  verbose << "Screen resolution: " << resolution << " dpi" << std::endl;

  set_pixels_per_unit(resolution / 72.0 * zoom_factor);
  
  using namespace Gnome::Canvas;
  no_pages_frame.reset(new Rect(no_pages_group));
  no_pages_frame->property_outline_color_gdk() = get_color(Color::frame);
  no_pages_frame->property_width_pixels() = 1;
  no_pages_text.reset(new Text(no_pages_group, 0, 0, "No pages"));
  no_pages_text->property_anchor() = Gtk::ANCHOR_CENTER;
  no_pages_group.hide();
  
  for(int i = 0; i < 8; i++) {
    handles[i] = manage(new Rect(handle_group));
    handles[i]->property_fill_color_gdk() = get_color(Color::bg);
    handles[i]->property_outline_color_gdk() = get_color(Color::frame);
    handles[i]->property_width_pixels() = 1;
  }

  set_document(d);

  reshaping = moving = false;
  margin_size = 30;

  // drag'n drop stuff:
  std::list<Gtk::TargetEntry> targets;
  //  targets.push_back(Gtk::TargetEntry(""));
  targets.push_back(Gtk::TargetEntry("text/uri-list"));
  targets.push_back(Gtk::TargetEntry("_NETSCAPE_URL"));
  targets.push_back(Gtk::TargetEntry("text/unicode"));
  targets.push_back(Gtk::TargetEntry("text/plain"));
  targets.push_back(Gtk::TargetEntry("UTF8_STRING"));
  targets.push_back(Gtk::TargetEntry("STRING"));
  drag_dest_set(targets);

  // Update handles when we turn pages
  current_page_num_changed_signal.connect
    (slot(*this, &DocumentView::update_handles));
}

DocumentView::~DocumentView() {
}

void DocumentView::set_zoom_factor(float factor) {
  if(factor > 0 && document) {
      zoom_factor = factor;
      set_pixels_per_unit(resolution / 72.0 * zoom_factor);
      zoom_change_signal(zoom_factor);
      update_handles();
    }
}

bool DocumentView::in_move_area(int x, int y) {
  if(Page* page = get_page()) {
    const Document::Selection selected = get_document()->selected();
    // "paper coordinates"
    const Vector pos = scr2pt(Gdk::Point(x, y));
    const float dist = scr2pt(2);

    for(Document::Selection::const_iterator 
          i = selected.begin(); i != selected.end(); ++i) 
      if(&Page::containing(**i) == page 
       && (*i)->get_box()->isInsideOrClose(pos, dist))
      return true;
  }
  return false;
}

Pagent* DocumentView::in_select_area(int x, int y) {
  if(Page* page = get_page()) {
    const Vector pos = scr2pt(Gdk::Point(x, y));
    const float dist = scr2pt(2);
    
    for(Group::ChildVec::const_iterator
          i = page->pbegin(); i != page->pend(); i++) {
      if((*i)->get_box()->isInsideOrClose(pos, dist))
      return *i;
    }
  }
  return 0;
}

namespace {
  typedef std::vector<Vector> ResizeHandles;
  ResizeHandles get_resize_handles(const Pagent &p) {
    ResizeHandles handles;
    Polygon poly = p.get_box()->get_polygon();
    for(Polygon::const_iterator i = poly.begin(); i != poly.end(); i++) {
      handles.push_back(*i);
      Polygon::const_iterator j = i; j++;
      if(j == poly.end())
      j = poly.begin();
      handles.push_back(Vector((i->x + j->x) / 2, (i->y + j->y) / 2));
    }
    return handles;
  }
}

int DocumentView::hover_reshape(int x, int y) {
  if(!document)
    return -1;
  Vector mouse = scr2pt(Gdk::Point(x, y));
  Gnome::Canvas::Item *item = get_item_at(mouse.x, -mouse.y);
  for(int i = 0; i < 8; i++)
    if(item == handles[i])
      return i;
  return -1;
}

bool DocumentView::on_button_press_event(GdkEventButton *event) {
  // Cursor-browsing hack:
  //  static GdkCursorType foo=GdkCursorType(0);
  //  cerr << foo << endl;
  //  Gdk::Cursor moving_cursor(foo);
  //  foo=GdkCursorType(foo+2);
  //  win.set_cursor(moving_cursor);

  Page *page = get_page();
  if(page && event->button == 1) {
    const Document::Selection selected = get_document()->selected();

    int j = hover_reshape(int(event->x), int(event->y));
    if(j != -1 && selected.size() == 1
       && !selected.front()->get_lock()) { // maybe start moving/resizing?
      // reshape

      begin_reshape(int(event->x), int(event->y), j);
    } else if(Pagent* select = in_select_area(int(event->x), int(event->y))) {
      // select / move

      // is it already selected?
      if(find(selected.begin(), selected.end(), select) != selected.end()) {
      if(event->state & Gdk::CONTROL_MASK) { // deselect
        get_document()->deselect(select);
      } else 
        begin_move(int(event->x), int(event->y));
      } else { // select and begin moving
      get_document()->select(select, !(event->state & Gdk::CONTROL_MASK));
      if(!select->get_lock())
        begin_move(int(event->x), int(event->y));
      }
    } else if((event->state & Gdk::CONTROL_MASK) == 0) { // deselect all
      get_document()->select_all(false);
    }
  }

  return true;
}

// *** copy/paste stuff ***/

namespace {
  std::string clip; // a global clip should suffice
  const std::string pptout_target = "PPTOUT_PAGENTS_2"; // with version number
}

void DocumentView::copy() {
  DocRef doc = get_document();
  if(!doc)
    return;

  /// \todo make sure the selection is sorted
  const Document::Selection selected = document->selected();
  if(selected.empty())
    return;
  
  Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get();
  std::list<Gtk::TargetEntry> target_list;
  target_list.push_back(Gtk::TargetEntry(pptout_target)); 
  clipboard->set(target_list, 
             SigC::slot(*this, &DocumentView::on_clipboard_get), 
             SigC::slot(*this, &DocumentView::on_clipboard_clear));

  // store an XML representation of the selected pagents
  xmlpp::Document rep;
  xmlpp::Element *root = rep.create_root_node("copy_data");

  // save relevant streams
  typedef std::set<const TextStream*> Streams;
  Streams streams;
  for(Document::Selection::const_iterator i = selected.begin();
      i != selected.end(); i++) {
    const TextFrame *frame = dynamic_cast<const TextFrame *>(*i);
    if(!frame)
      continue;
    const TextStream *stream = frame->get_stream();
    if(stream && streams.find(stream) == streams.end()) {
      streams.insert(stream);
      stream->save(*root, FileContext());
    }
  }

  // save pagents
  for(Document::Selection::const_iterator i = selected.begin();
      i != selected.end(); i++) {
    (*i)->save(*root, FileContext());
  }

  clip = rep.write_to_string();
  //std::cerr << clip << std::endl;
}

void DocumentView::on_clipboard_get(Gtk::SelectionData& selection_data, 
                             guint info)
{ 
  const Glib::ustring& target = selection_data.get_target(); 
  
  if(target == pptout_target)
    selection_data.set(pptout_target, clip);
}

void DocumentView::on_clipboard_clear() {
  // not really necessary
  clip.clear(); 
}

void DocumentView::cut() {
  copy(); 
  delete_selected();
}

void DocumentView::paste() {
  Page *page = get_page();
  if(!page)
    return;

  Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get();
  clipboard->request_contents
    (pptout_target, SigC::slot(*this, &DocumentView::on_clipboard_received));
}

void 
DocumentView::on_clipboard_received(const Gtk::SelectionData& selection_data)
{
  Page *page = get_page();
  if(!page)
    return;

  Glib::ustring clipboard_data = selection_data.get_data_as_string();
  try {
    xmlpp::DomParser parser;  
    parser.parse_memory(clipboard_data);
    xmlpp::Element *root = parser.get_document()->get_root_node();
    if(!root)
      throw std::runtime_error("Root node is NULL.");
    xmlpp::Element::NodeList children = root->get_children();

    get_document()->select_all(false);
    for(xmlpp::Element::NodeList::const_iterator i = children.begin();
      i != children.end(); i++)
      {
      if(xmlpp::Element *elem = dynamic_cast<xmlpp::Element*>(*i)) {
        if(elem->get_name() == "text_stream") {
          std::auto_ptr<TextStream> stream(new TextStream
                                   (ElementWrap("", *elem)));
          // Don't try to add a stream with a name that already exists
          /// \todo rename the stream automatically?
          if(!document->get_text_stream(stream->get_name()))
            document->add_text_stream(stream.release());
        } else {
          Pagent *pagent = load(ElementWrap("", *elem), page);
          page->add(pagent);
          get_document()->select(pagent, false); // don't deselect
        }
      }
      }
  } catch(const std::exception& e) {
    throw std::runtime_error("Error encountered when pasting:\n" 
                       + std::string(e.what()));
  }
}  

// Todo: This method should be removed.  Instead, there should exist something
// (not related to a specific view, but to the document), that listens to
// geometry changes, finds all text streams that have frames that intersects
// with the object (before or after the change) and retypesets them.
// What is done here is to much typsetting when objects moved / resized by the
// view, and no typesetting at all when objects are moved / resized by e.g.
// the properties dialog.
void DocumentView::refresh_streams() {
  if(Page *page = get_page()) {
    Document::StreamVec streams;
    for(Group::ChildVec::const_iterator i = page->pbegin();
      i != page->pend();
      i++) 
      {
      TextFrame *tmp = dynamic_cast<TextFrame*>(*i);
      if(tmp) {
        TextStream *stream = tmp->get_stream();
        if(stream 
           && find(streams.begin(), streams.end(), stream) == streams.end())
          streams.push_back(stream);
      }
      }
    for(Document::StreamVec::iterator i = streams.begin();
      i != streams.end();
      i++)
      {
        try {
          (*i)->run_typesetter();
        } catch(const BasicFrame::GenPicError &e) {
          warning << "Failed to refresh stream: " << e.what() << std::endl;
        }
      debug << (*i)->get_name() << ", " 
            << (*i)->get_association() << std::endl;
      }
  }
}

void DocumentView::begin_reshape(int x, int y, int box) {
  DocRef document = get_document();
  if(!get_page() || !document)
    return;

  const Document::Selection selected = document->selected();
  if(selected.size() != 1) 
    return;
  const Pagent& obj = *selected.front();

  ResizeHandles handles = get_resize_handles(obj);
  if(box >= int(handles.size()))
    return;
  Gdk::Point handle = pt2scr(handles[box]);
  // pointer is probably not in center of handle
  offset = IVector(x - handle.get_x(), 
               y - handle.get_y());

  old_size = obj.get_inherent_size();
  old_matrix = obj.get_matrix();
  reshape_box = box; reshaping = true;
}

void DocumentView::end_reshape(bool revert) {
  reshaping = false;
  if(DocRef document = get_document()) {
    if(revert) {
      const Document::Selection selected = document->selected();
      if(selected.size() != 1) {
      Pagent& obj = *selected.front();
      obj.set_matrix(old_matrix);
      if(Pagent::Resizable* o = dynamic_cast<Pagent::Resizable*>(&obj)) {
        o->set_size(old_size.x, old_size.y);
      }
      }
    }
    refresh_streams();
  }
}


namespace {
  // get bounding box of selected pagents
  void box_of_selected(Vector &ll, Vector &ur, DocRef document) {
    bool first = true;
    const Document::Selection &selected = document->selected();
    Document::Selection::const_iterator i;
    for(i = selected.begin(); i != selected.end(); i++) {
      Boundary b = (*i)->get_box();
      Vector b_ur = b->getCorner(Corner::UR);
      Vector b_ll = b->getCorner(Corner::LL);
      if(first) {
      ll = b_ll; ur = b_ur;
      } else {
      ll.x = std::min(ll.x, b_ll.x);  ll.y = std::min(ll.y, b_ll.y);
      ur.x = std::max(ur.x, b_ur.x);  ur.y = std::max(ur.y, b_ur.y);
      }
      first &= false;
    }
  } 
}

void DocumentView::begin_move(int x, int y) {
  if(get_page()) {
    Vector ur, ll;
    box_of_selected(ll, ur, get_document());
    Gdk::Point ll_scr = pt2scr(ll);
    offset = IVector(x, y) - IVector(ll_scr.get_x(), ll_scr.get_y());
    moving = true;  
  }
}

void DocumentView::end_move(bool revert) {
  if(get_page()) {
    moving = false;
    refresh_streams();
  }
}

bool DocumentView::on_key_press_event(GdkEventKey* key) {
  if(key->keyval == 65307) { // escape
    if(moving)
      end_move(true);
    if(reshaping)
      end_reshape(true);
  }
  return false;
}

bool DocumentView::on_button_release_event(GdkEventButton *event) {
  if(moving)
    end_move(false);
  if(reshaping)
    end_reshape(false);
  return true;
}

Gdk::Cursor DocumentView::get_cursor(int x, int y) {
  DocRef document = get_document();
  if(!document)
    return Gdk::LEFT_PTR;
    
  if(moving)
    return Gdk::FLEUR;

  const Document::Selection selection = document->selected();
  Pagent *obj = in_select_area(x, y);
  
  if(!reshaping && obj
     && find(selection.begin(), selection.end(), obj) == selection.end())
    return Gdk::TOP_LEFT_ARROW;
  
  int j = reshape_box;
  if(reshaping || ((j = hover_reshape(x, y)) != -1))
    switch(j) {
    case 0: return Gdk::BOTTOM_LEFT_CORNER;
    case 1: return Gdk::LEFT_SIDE;
    case 2: return Gdk::TOP_LEFT_CORNER;  
    case 3: return Gdk::TOP_SIDE;
    case 4: return Gdk::TOP_RIGHT_CORNER;
    case 5: return Gdk::RIGHT_SIDE;
    case 6: return Gdk::BOTTOM_RIGHT_CORNER;
    case 7: return Gdk::BOTTOM_SIDE;
    default: return Gdk::QUESTION_ARROW;
    }

  if(in_move_area(x, y))
    return Gdk::FLEUR;

  if(in_select_area(x, y))
    return Gdk::TOP_LEFT_ARROW;
  
  return Gdk::LEFT_PTR;
}

void DocumentView::update_cursor(int x, int y) {
  win->set_cursor(get_cursor(x, y));
}

namespace {
  float grid_size_x = 24, grid_size_y = 24;
  Vector grid_offset(0, 0);
  float snap_to_grid(float x, float offset, float step) {
    x -= offset;
    int n = int(x / step);
    float low = n * step, high = (n + 1) * step;
    float dlow = x - low, dhigh = high - x;
    x = dlow < dhigh ? low : high;
    x += offset;
    return x;
  }
}

void DocumentView::move_reshape_box(int x, int y) {
  const Document::Selection selected = document->selected();
  if(selected.size() != 1) 
    return;
  Pagent& obj = *selected.front();

  const Matrix &m = obj.get_matrix();
  // cursor wasn't necessarily in the center of the handle
  x -= offset.x; y -= offset.y;
  Vector p = scr2pt(Gdk::Point(x, y));

  // snap to grid
  if(snap_mode == snap::GRID) {
    p = Vector(snap_to_grid(p.x, grid_offset.x, grid_size_x),
             snap_to_grid(p.y, grid_offset.y, grid_size_y));
  }


  // get pos in matrix coordinates
  Vector pos = m.inv().transform(p);
  Vector ll(0, 0), ur(obj.get_inherent_size()); // lower left / upper right
  switch(reshape_box) {
  case 0: // bottom left
    ll = pos;
    break;
  case 1: // left
    ll.x = pos.x;
    break;
  case 2: // top left
    ll.x = pos.x;
    ur.y = pos.y;
    break;
  case 3: // top
    ur.y = pos.y;
    break;
  case 4: // top right
    ur = pos;
    break;
  case 5: // right
    ur.x = pos.x;
    break;
  case 6: // bottom right;
    ur.x = pos.x;
    ll.y = pos.y;
    break;
  case 7: // bottom
    ll.y = pos.y;
    break;
  default:
    break;
  }

  bool negx = ll.x > ur.x, negy = ll.y > ur.y;
  // change reshape_box if going past zero
  if(negx) {
    switch(reshape_box) {
    case 0: reshape_box = 6; break;
    case 1: reshape_box = 5; break;
    case 2: reshape_box = 4; break;
    case 4: reshape_box = 2; break;
    case 5: reshape_box = 1; break;
    case 6: reshape_box = 0; break;
    default: break;
    }
  }
  if(negy) {
    switch(reshape_box) {
    case 0: reshape_box = 2; break;
    case 2: reshape_box = 0; break;
    case 3: reshape_box = 7; break;
    case 4: reshape_box = 6; break;
    case 6: reshape_box = 4; break;
    case 7: reshape_box = 3; break;
    default: break;
    }
  }

  // avoid negative scaling
  Vector _ll(std::min(ll.x, ur.x), std::min(ll.y, ur.y));
  // Vector _ur(std::max(ll.x, ur.x), std::max(ll.y, ur.y));

  Vector size = ur - ll;

  // can't set size to zero
  float minsize = 1;
  if(fabs(size.x) < minsize || fabs(size.y) < minsize) {
    return;
  }

  if(Pagent::Resizable* o = dynamic_cast<Pagent::Resizable*>(&obj)) {
    o->set_size(fabs(size.x), fabs(size.y));
  } else {
    const Vector osize = obj.get_inherent_size();
    obj.set_scaling(m.sc_x() * size.x / osize.x,
                m.sc_y() * size.y / osize.y);
  }
  obj.set_matrix(Matrix::translation(_ll) * obj.get_matrix());
}

bool DocumentView::on_motion_notify_event(GdkEventMotion *event) {
  if(!get_page())
    return true;

  // some silliness we have to do or we won't get new motion notify events
  if(event->is_hint) {
    Gdk::ModifierType state;
    int x, y;
    win->get_pointer(x, y, state);
  }

  int x = int(event->x);
  int y = int(event->y);
  
  update_cursor(x, y);

  Document::Selection selected = get_document()->selected();
  Document::Selection::const_iterator i;

  if(moving || reshaping) {
    bool locked = false;
    for(i = selected.begin(); i != selected.end(); i++) {
      locked |= (*i)->get_lock();  // if one is locked, all are locked
    }

    if(moving) {
      Vector ur, ll;
      box_of_selected(ll, ur, get_document());
      // new ll is at pointer pos minus offset
      Vector move = scr2pt(Gdk::Point(x - offset.x, y - offset.y)) - ll;  

      if(snap_mode == snap::GRID && !locked) {
      ll += move;  ur += move;
      Vector ll_s(snap_to_grid(ll.x, grid_offset.x, grid_size_x),
                snap_to_grid(ll.y, grid_offset.y, grid_size_y));
      Vector ur_s(snap_to_grid(ur.x, grid_offset.x, grid_size_x),
                snap_to_grid(ur.y, grid_offset.y, grid_size_y));
      Vector dll = ll_s - ll, dur = ur_s - ur;
      // the edge closest to a grid line wins
      Vector d(fabs(dll.x) < fabs(dur.x) ? dll.x : dur.x,
             fabs(dll.y) < fabs(dur.y) ? dll.y : dur.y);
      move += d;
      }

      if(!locked) {
      for(i = selected.begin(); i != selected.end(); i++) {
        Vector t = (*i)->get_matrix().tr();
        t += move;
        (*i)->set_translation(t);
      } 
      }
    } else if(reshaping) {
      for(i = selected.begin(); i != selected.end(); i++) {
      if(!(*i)->get_lock())
        move_reshape_box(x, y);
      }
    }
  }

  return true;
}

void DocumentView::reset_scroll_region() {
  if(document) {
    float w = document->get_width();
    float h = document->get_height();
    set_scroll_region(-margin_size, -h - margin_size, 
                      w + margin_size, margin_size);
  }
}

void DocumentView::on_realize() {
  Gnome::Canvas::Canvas::on_realize();

  // Doing this in the constructor does not seem to work all the time
  reset_scroll_region();

  // get_toplevel doesn't work in the constructor
  Gtk::Window *toplevel = dynamic_cast<Gtk::Window*>(get_toplevel());
  assert(toplevel != 0);
  // template_page_dialog is deleted in the destructor
  template_page_dialog.reset(new TemplatePageDialog(*toplevel, *this));

  win = get_window();
}

void DocumentView::on_drag_data_received
(const Glib::RefPtr<Gdk::DragContext>& context, 
 int x, int y, GtkSelectionData* selection_data, guint info, guint time)
{
  /// \todo unicode ???

  Gtk::SelectionData selection(selection_data);
  debug << "DocumentView::on_drop_drag_data_received:" << std::endl
      << "x = " << x << ", y = " << y << std::endl
      << selection.get_data_type() << " : \"" 
      << selection.get_text() << "\"" << std::endl
      << "\"" << selection.get_data_as_string() << "\"" << std::endl
      << std::endl;

  if(!get_page()) {
    context->drag_finish(false, false, time);
    return;
  }

  // split into lines
  typedef std::vector<std::string> Strings;
  Strings strings;
  std::string data = selection.get_data_as_string();
  unsigned int start = 0;
  unsigned int stop;
  unsigned int length = data.length();
  do {
    unsigned int r = data.find('\r', start);
    unsigned int n = data.find('\n', start);
    stop = std::min(std::min(r, n), length);
    strings.push_back(data.substr(start, stop - start));
    start = stop;
    while(start < length 
        && (data[start] == '\r' || data[start] == '\n'))
      start++;
  } while(start < length);
  
  bool success = false;

  for(Strings::const_iterator i = strings.begin();
      i != strings.end(); i++) {
    try {
      std::string filename = *i;
      if(filename.find("file://") == 0)
      filename.replace(0, 7, "");
      debug << "\"" << filename << "\"" << std::endl;
      new_image_frame(filename, false);
      success = true; // ok if at least one works
    } catch (const std::exception&) {
      debug << "failed to open" << std::endl;
    }
  }

  context->drag_finish(success, false, time);
}

void DocumentView::update_handles() {
  Pagent *pagent;
  // Show handles if there is one and only one selected object and it
  // is on the current page.and it isn't locked
  if(!document 
     || document->selected().size() != 1 
     || !get_page()->has_child(pagent = document->selected().front())
     || pagent->get_lock()) {
    handle_group.hide();
    return;
  }
  handle_group.show();
  // Make sure we connect only to the currently selected pagent
  if(last_selected != pagent) {
    last_selected = pagent;
    selected_geometry_connection.disconnect();
    selected_props_connection.disconnect();
    selected_geometry_connection = pagent->geometry_changed_signal.connect
      (SigC::hide<Pagent*>(slot(*this, &DocumentView::update_handles)));
    // this is for noticing if the pagent is being locked/unlocked
    selected_props_connection = pagent->props_changed_signal.connect
      (SigC::hide<Pagent*>(slot(*this, &DocumentView::update_handles)));
  }
  ResizeHandles pos = get_resize_handles(*pagent);
  assert(pos.size() == 8);
  const float hs = scr2pt(int(config.ReshapeBoxSize.values.front()) / 2);
  for(int i = 0; i < 8; i++) {
    handles[i]->property_x1() = pos[i].x - hs; 
    handles[i]->property_x2() = pos[i].x + hs; 
    handles[i]->property_y1() = -(pos[i].y - hs); 
    handles[i]->property_y2() = -(pos[i].y + hs); 
  }
}

const Gdk::Color& DocumentView::get_color(Color::Id color) const {
  switch(color) {
  case Color::bg:     return white;
  case Color::frame:  return black;
  case Color::locked: return gray;
  case Color::guide:  return red;
  case Color::empty:  return gray;
  default:  throw std::runtime_error("Unknown color id get_color");
  }
}

Generated by  Doxygen 1.6.0   Back to index