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

group.cc

///
// Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING
///
#include "group.h"
#include "document.h"
#include "loader.h"
#include "util/rectboundary.h"
#include "util/stringutil.h"
#include "util/warning.h"
#include "ps/misc.h"
#include <gdkmm.h>
#include <algorithm>
#include <iostream>

Group::Group(const ElementWrap& xml, Group *parent)
  : Pagent(parent, xml.get_attribute<string>("name", "group"))
{
  matrix = xml.get_attribute<Matrix>("transform");
  
  const xmlpp::Element::NodeList children = xml.element().get_children();
  for(xmlpp::Element::NodeList::const_iterator i = children.begin();
      i != children.end();
      i++) {
    if(const xmlpp::Element *element = dynamic_cast<xmlpp::Element*>(*i)) {
      std::string name = element->get_name();
      if(name == "frame")
      add(load(ElementWrap(xml, *element), this));
      else
      warning << "Unknown node <" << name
            << "> ignored in group" << std::endl;
    }
  }
}

Group::Group(Group* parent, const std::string& name)
  : Pagent(parent, name)
{}

Group::Group(Group* parent, const std::string& name, const Matrix& xform)
  : Pagent(parent, name)
{
  set_matrix(xform);
}

Group::~Group() {
  // Note:  When a group is "ungrouped", the childs should not be removed.
  // Fix that by moving the childs from the group to its parent before
  // deleteing the group.
  for(ChildVec::iterator i = childs.begin(); i != childs.end(); i++)
    delete *i;
}

void Group::print(std::ostream& out, bool grayscale) const {
  out << "gsave  % group\n"
      << PS::Concat(get_matrix());
  for(ChildVec::const_reverse_iterator i = childs.rbegin(); 
      i != childs.rend(); 
      i++)
    (*i)->print(out, grayscale);
  out << "grestore % /group" << std::endl;
}

void Group::print_pdf(PDF::Content::Ptr pdf) const {
  pdf->data() << "q\n"
            << get_matrix() << " cm\n";
  for(ChildVec::const_reverse_iterator i = childs.rbegin(); 
      i != childs.rend(); 
      i++)
    (*i)->print_pdf(pdf);
  pdf->data() << "Q\n";
}

class Rectangle {
public:
  Rectangle(const Vector& v) : lo_x(v.x), lo_y(v.y), hi_x(v.x), hi_y(v.y) {}
  void grow(const Vector& v) {
    if(v.x < lo_x) lo_x = v.x;
    if(v.x > hi_x) hi_x = v.x;
    if(v.y < lo_y) lo_y = v.y;
    if(v.y > hi_y) hi_y = v.y;
  }
  Boundary getBox() const {
    return RectBoundary::create(Matrix::translation(Vector(lo_x, lo_y)),
                        hi_x - lo_x, hi_y - lo_y);
  }
private:
  double lo_x, lo_y, hi_x, hi_y;
};

namespace {
  Boundary get_bounding_box(std::list<Pagent*>::const_iterator begin,
                      std::list<Pagent*>::const_iterator end) {
    // If the group has no members, return a zero-size box at the right place
    // Hmm.  It seems zero-sized boxes isn't supported ...
    /// \todo: It would probably make sense to cache the box.
    /// \todo: create the boundary at the right place if the group
    /// doesn't have any children
    if(begin == end) return RectBoundary::create(Matrix(), 1, 1);
  
    std::list<Pagent*>::const_iterator i = begin;
    const Boundary box = (*i)->get_box();
    Rectangle rect(box->getCorner(Corner::LL));
    rect.grow(box->getCorner(Corner::LR));
    rect.grow(box->getCorner(Corner::UL));
    rect.grow(box->getCorner(Corner::UR));
  
    for(; i != end; i++) {
      const Boundary box = (*i)->get_box();
      rect.grow(box->getCorner(Corner::LL));
      rect.grow(box->getCorner(Corner::LR));
      rect.grow(box->getCorner(Corner::UL));
      rect.grow(box->getCorner(Corner::UR));
    }
    return rect.getBox();
  }
}

Boundary Group::get_untransformed_box() const {
  return get_bounding_box(childs.begin(), childs.end());
}

Boundary Group::get_box() const {
  return get_untransformed_box() * get_matrix();
}

Vector Group::get_inherent_size() const {
  const Boundary box = get_untransformed_box();
  return Vector(box->get_width(), box->get_height());
}

// Note: maybe it's best to let a pagent ask its parent what its 
// policy on flow around is.
void Group::set_flow_around(bool) {} // Todo
bool Group::get_flow_around() const { return false; } // Todo

BoundaryVect Group::obstacle_list() const {
  BoundaryVect result;
  
  for(ChildVec::const_iterator i=childs.begin();
      i!=childs.end();
      i++)
    {
      if(Boundary boundary = (*i)->get_obstacle_boundary())
      result.push_back(boundary);
    }
  return result;
}

Boundary Group::get_obstacle_boundary() const {
  return Boundary(); // Todo
}

std::string Group::getTypeName() const { return "group"; }

xmlpp::Element* Group::save(xmlpp::Element& parent_node,
                      const FileContext &context) const {
  xmlpp::Element *node = parent_node.add_child("frame");
  node->set_attribute("type", "group");
  node->set_attribute("transform", tostr(get_matrix()));

  save_childs(*node, context);
  return node;
}

void Group::save_childs(xmlpp::Element& node,
                  const FileContext &context) const 
{
  for(ChildVec::const_reverse_iterator
      i = childs.rbegin(); i != childs.rend(); i++)
    (*i)->save(node, context);
}

void Group::group_selected() {
  /// \todo make this work by doing matrix operations
  const Document::Selection selection=Document::containing(*this).selected();

  if(selection.size() <= 1) //no one-item groups, please
    return;

  // Calculate translation:
  Vector ll = get_bounding_box(selection.begin(),
                         selection.end())->getCorner(Corner::LL);

  Group *group = new Group(this, "group", Matrix::translation(ll.x, ll.y));
  
  // Use a copy or we will confuse ourselves while manipulating a container 
  // we are iterating over.
  ChildVec childs_copy = childs;

  // reparent selected
  // Loop over childs rather than selection, to get objects in correct order
  for(ChildVec::reverse_iterator i = childs_copy.rbegin();
      i != childs_copy.rend();
      i++)
      {
        if(find(selection.begin(), selection.end(), *i) != selection.end()) {
          (*i)->set_matrix((*i)->get_matrix()*group->get_matrix().inv());
          group->add(ungroup(*i));
        }
      }
  if(group->count()) {
    add(group);
    Document::containing(*this).select(group);
  } else {
    warning << "Got a strange group. Deleting it again." << std::endl;
    delete group;
  }
  group_changed_signal(this);
}

void Group::ungroup_selected() {
  /// \todo preserve stacking order
  const Document::Selection &selection
    = Document::containing(*this).selected();
  
  for(Document::Selection::const_iterator i = selection.begin();
      i != selection.end();
      i++)
    {
      Group *group = dynamic_cast<Group*>(*i);
      if(group) {
      Document::containing(*this).deselect(*i);
      while(group->pbegin() != group->pend()) {
        Pagent *pagent = group->ungroup(*(group->prbegin()));
        if(pagent) {
          pagent->set_matrix(pagent->get_matrix()*group->get_matrix());
          add(pagent);
          Document::containing(*this).select(pagent, false);
        }
      }
      if(!ungroup(group))
        warning << "Group not in this group" << std::endl;
      delete group;
      group_changed_signal(this);
      }
    }
}

bool Group::has_child(const Pagent *pagent) const {
  return std::find(childs.begin(), childs.end(), pagent) != childs.end();
}

void Group::add(Pagent* obj) {
  // the front is the top
  childs.push_front(obj);
  obj->set_parent(this);
  group_changed_signal(this);
}

Pagent* Group::ungroup(Pagent* obj) {
  ChildVec::iterator i = find(childs.begin(), childs.end(), obj);
  if(i == childs.end()) //won't let me compare i==pend()
    return 0;
  childs.erase(i);
  obj->set_parent(parent);
  group_changed_signal(this);
  return obj;
}

void Group::rearrange_selected(RearrangeTarget target) {
  const Document::Selection &selection
    = Document::containing(*this).selected();
  
  if(target == TOP || target == BOTTOM) {
    ChildVec sorted_selection;
    // remove selected items from list
    for(Document::Selection::const_iterator s = selection.begin(); 
      s!=selection.end(); 
      s++)
      {
      ChildVec::iterator i = find(childs.begin(), childs.end(), *s);
      if(i != childs.end()) {
        sorted_selection.push_back(*i);
        childs.erase(i);
      } else
        warning << "Selected item not on this page." << std::endl;
      }
    // append items to front or back
    childs.insert(target == TOP ? childs.begin() : childs.end(), 
              sorted_selection.begin(), 
              sorted_selection.end());
  } else { // UP or DOWN
    // This is basically a single-step bubblesort,
    // except that each element may only be swapped once.
    for(ChildVec::iterator i = childs.begin(); i != childs.end(); i++) {
      ChildVec::iterator j = i;
      j++;
      if(j == childs.end())
      break;
      bool i_selected = (find(selection.begin(), selection.end(), *i) 
                   != selection.end());
      bool j_selected = (find(selection.begin(), selection.end(), *j) 
                   != selection.end());
      if(i_selected && j_selected)
      continue;
      if((i_selected && target == DOWN) || (j_selected && target == UP)) {
      Pagent *tmp = *j; *j = *i; *i = tmp; // swap
      i++; // no swap next time
      }
    }
  }
  group_changed_signal(this);
  geometry_changed_signal(this);
}

void Group::child_props_changed(Pagent *pagent) {
  //  if(pagent!=this) // no infinite loops, please
  props_changed_signal(this);
}

void Group::child_geometry_changed(Pagent *pagent) {
  //  if(pagent!=this) // no infinite loops, please
  geometry_changed_signal(this);
}

Generated by  Doxygen 1.6.0   Back to index