/// // 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 #include #include Group::Group(const ElementWrap& xml, Group *parent) : Pagent(parent, xml.get_attribute("name", "group")) { matrix = xml.get_attribute("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(*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::const_iterator begin, std::list::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::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(); } 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(*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(); } } } 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(); } 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(); 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(); geometry_changed_signal(); } void Group::child_props_changed() { // if(pagent!=this) // no infinite loops, please props_changed_signal(); } void Group::child_geometry_changed() { // if(pagent!=this) // no infinite loops, please geometry_changed_signal(); }