Code

Warning cleanup
[inkscape.git] / src / vanishing-point.cpp
1 #define __VANISHING_POINT_C__
3 /*
4  * Vanishing point for 3D perspectives
5  *
6  * Authors:
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9  *   Maximilian Albert <Anhalter42@gmx.de>
10  *
11  * Copyright (C) 2005-2007 authors
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #include <glibmm/i18n.h>
18 #include "vanishing-point.h"
19 #include "desktop-handles.h"
20 #include "box3d.h"
22 #include "knotholder.h" // FIXME: can we avoid direct access to knotholder_update_knots?
24 namespace Box3D {
26 #define VP_KNOT_COLOR_NORMAL 0xffffff00
27 #define VP_KNOT_COLOR_SELECTED 0x0000ff00
29 #define VP_LINE_COLOR_FILL 0x0000ff7f
30 #define VP_LINE_COLOR_STROKE_X 0xff00007f
31 #define VP_LINE_COLOR_STROKE_Y 0x0000ff7f
32 #define VP_LINE_COLOR_STROKE_Z 0xffff007f
34 // screen pixels between knots when they snap:
35 #define SNAP_DIST 5
37 // absolute distance between gradient points for them to become a single dragger when the drag is created:
38 #define MERGE_DIST 0.1
40 // knot shapes corresponding to GrPointType enum
41 SPKnotShapeType vp_knot_shapes [] = {
42         SP_KNOT_SHAPE_SQUARE, // VP_FINITE
43         SP_KNOT_SHAPE_CIRCLE  //VP_INFINITE
44 };
46 // FIXME: We should always require to have both the point (for finite VPs)
47 //        and the direction (for infinite VPs) set. Otherwise toggling
48 //        shows very unexpected behaviour.
49 //        Later on we can maybe infer the infinite direction from the finite point
50 //        and a suitable center of the scene. How to go in the other direction?
51 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &inf_dir, VPState st)
52                              : NR::Point (pt), state (st), v_dir (inf_dir) {}
54 VanishingPoint::VanishingPoint(NR::Point const &pt)
55                              : NR::Point (pt), state (VP_FINITE), v_dir (0.0, 0.0) {}
57 VanishingPoint::VanishingPoint(NR::Point const &pt, NR::Point const &direction)
58                              : NR::Point (pt), state (VP_INFINITE), v_dir (direction) {}
60 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y)
61                              : NR::Point(x, y), state(VP_FINITE), v_dir(0.0, 0.0) {}
63 VanishingPoint::VanishingPoint(NR::Coord dir_x, NR::Coord dir_y, VPState st)
64                              : NR::Point(0.0, 0.0), state(st), v_dir(dir_x, dir_y) {}
66 VanishingPoint::VanishingPoint(NR::Coord x, NR::Coord y, NR::Coord dir_x, NR::Coord dir_y)
67                              : NR::Point(x, y), state(VP_INFINITE), v_dir(dir_x, dir_y) {}
69 VanishingPoint::VanishingPoint(VanishingPoint const &rhs) : NR::Point (rhs)
70 {
71     this->state = rhs.state;
72     //this->ref_pt = rhs.ref_pt;
73     this->v_dir = rhs.v_dir;
74 }
76 VanishingPoint::~VanishingPoint () {}
78 bool VanishingPoint::operator== (VanishingPoint const &other)
79 {
80     // Should we compare the parent perspectives, too? Probably not.
81     if ((*this)[NR::X] == other[NR::X] && (*this)[NR::Y] == other[NR::Y]
82         && this->state == other.state && this->v_dir == other.v_dir) {
83         return true;
84     }
85     return false;
86 }
88 bool VanishingPoint::is_finite() const
89 {
90     return this->state == VP_FINITE;
91 }
93 VPState VanishingPoint::toggle_parallel()
94 {
95     if (this->state == VP_FINITE) {
96         this->state = VP_INFINITE;
97     } else {
98         this->state = VP_FINITE;
99     }
101     return this->state;
104 void VanishingPoint::draw(Box3D::Axis const axis)
106     switch (axis) {
107         case X:
108             if (state == VP_FINITE)
109                 create_canvas_point(*this, 6.0, 0xff000000);
110             else
111                 create_canvas_point(*this, 6.0, 0xffffff00);
112             break;
113         case Y:
114             if (state == VP_FINITE)
115                 create_canvas_point(*this, 6.0, 0x0000ff00);
116             else
117                 create_canvas_point(*this, 6.0, 0xffffff00);
118             break;
119         case Z:
120             if (state == VP_FINITE)
121                 create_canvas_point(*this, 6.0, 0x00770000);
122             else
123                 create_canvas_point(*this, 6.0, 0xffffff00);
124             break;
125         default:
126             g_assert_not_reached();
127             break;
128     }
131 static void
132 vp_drag_sel_changed(Inkscape::Selection */*selection*/, gpointer data)
134     VPDrag *drag = (VPDrag *) data;
135     drag->updateDraggers ();
136     drag->updateLines ();
139 static void
140 vp_drag_sel_modified (Inkscape::Selection */*selection*/, guint flags, gpointer data)
142     VPDrag *drag = (VPDrag *) data;
143     /***
144     if (drag->local_change) {
145         drag->local_change = false;
146     } else {
147         drag->updateDraggers ();
148     }
149     ***/
150     drag->updateLines ();
153 // auxiliary function
154 static GSList *
155 eliminate_remaining_boxes_of_persp_starting_from_list_position (GSList *boxes_to_do, const SP3DBox *start_box, const Perspective3D *persp)
157     GSList *i = g_slist_find (boxes_to_do, start_box);
158     g_return_val_if_fail (i != NULL, boxes_to_do);
160     SP3DBox *box;
161     GSList *successor;
163     i = i->next;
164     while (i != NULL) {
165         successor = i->next;
166         box = SP_3DBOX (i->data);
167         if (persp->has_box (box)) {
168             boxes_to_do = g_slist_remove (boxes_to_do, box);
169         }
170         i = successor;
171     }
173     return boxes_to_do;
176 static bool
177 have_VPs_of_same_perspective (VPDragger *dr1, VPDragger *dr2)
179     Perspective3D *persp;
180     for (GSList *i = dr1->vps; i != NULL; i = i->next) {
181         persp = dr1->parent->document->get_persp_of_VP ((VanishingPoint *) i->data);
182         if (dr2->hasPerspective (persp)) {
183             return true;
184         }
185     }
186     return false;
189 static void
190 vp_knot_moved_handler (SPKnot */*knot*/, NR::Point const *ppointer, guint state, gpointer data)
192     VPDragger *dragger = (VPDragger *) data;
193     VPDrag *drag = dragger->parent;
195     NR::Point p = *ppointer;
197     // FIXME: take from prefs
198     double snap_dist = SNAP_DIST / inkscape_active_desktop()->current_zoom();
200     if (!(state & GDK_SHIFT_MASK)) {
201         // without Shift; see if we need to snap to another dragger
202         for (GList *di = dragger->parent->draggers; di != NULL; di = di->next) {
203             VPDragger *d_new = (VPDragger *) di->data;
204             if ((d_new != dragger) && (NR::L2 (d_new->point - p) < snap_dist)) {
205                 if (have_VPs_of_same_perspective (dragger, d_new)) {
206                     // this would result in degenerate boxes, which we disallow for the time being
207                     continue;
208                 }
210                 // update positions ...
211                 for (GSList *j = dragger->vps; j != NULL; j = j->next) {
212                     ((VanishingPoint *) j->data)->set_pos (d_new->point);
213                 }
214                 // ... join lists of VPs ...
215                 // FIXME: Do we have to copy the second list (i.e, is it invalidated when dragger is deleted below)?
216                 d_new->vps = g_slist_concat (d_new->vps, g_slist_copy (dragger->vps));
218                 // ... delete old dragger ...
219                 drag->draggers = g_list_remove (drag->draggers, dragger);
220                 delete dragger;
221                 dragger = NULL;
223                 // ... and merge any duplicate perspectives
224                 d_new->mergePerspectives();
226                 // TODO: Update the new merged dragger
227                 //d_new->updateKnotShape ();
228                 d_new->updateTip ();
230                 d_new->reshapeBoxes (d_new->point, Box3D::XYZ);
231                 d_new->updateBoxReprs ();
232                 d_new->updateZOrders ();
234                 drag->updateLines ();
236                 // TODO: Undo machinery; this doesn't work yet because perspectives must be created and
237                 //       deleted according to changes in the svg representation, not based on any user input
238                 //       as is currently the case.
240                 //sp_document_done (sp_desktop_document (drag->desktop), SP_VERB_CONTEXT_3DBOX,
241                 //                  _("Merge vanishing points"));
243                 return;
244             }
245         }
246     }
248     dragger->point = p;
250     dragger->reshapeBoxes (p, Box3D::XYZ);
251     dragger->updateBoxReprs ();
252     dragger->updateZOrders ();
254     drag->updateLines ();
256     //drag->local_change = false;
259 /***
260 static void
261 vp_knot_clicked_handler(SPKnot *knot, guint state, gpointer data)
263     VPDragger *dragger = (VPDragger *) data;
265 ***/
267 void
268 vp_knot_grabbed_handler (SPKnot */*knot*/, unsigned int state, gpointer data)
270     VPDragger *dragger = (VPDragger *) data;
271     VPDrag *drag = dragger->parent;
273     drag->dragging = true;
275     //sp_canvas_force_full_redraw_after_interruptions(dragger->parent->desktop->canvas, 5);
277     if ((state & GDK_SHIFT_MASK) && !drag->hasEmptySelection()) { // FIXME: Is the second check necessary?
279         if (drag->allBoxesAreSelected (dragger)) {
280             // if all of the boxes linked to dragger are selected, we don't need to split it
281             return;
282         }
284         // we are Shift-dragging; unsnap if we carry more than one VP
286         // FIXME: Should we distinguish between the following cases:
287         //        1) there are several VPs in a dragger
288         //        2) there is only a single VP but several boxes linked to it
289         //           ?
290         //        Or should we simply unlink all selected boxes? Currently we do the latter.
291         if (dragger->numberOfBoxes() > 1) {
292             // create a new dragger
293             VPDragger *dr_new = new VPDragger (drag, dragger->point, NULL);
294             drag->draggers = g_list_prepend (drag->draggers, dr_new);
296             // move all the VPs from dragger to dr_new
297             dr_new->vps = dragger->vps;
298             dragger->vps = NULL;
300             /* now we move all selected boxes back to the current dragger (splitting perspectives
301                if they also have unselected boxes) so that they are further reshaped during dragging */
303             GSList *boxes_to_do = drag->selectedBoxesWithVPinDragger (dr_new);
305             for (GSList *i = boxes_to_do; i != NULL; i = i->next) {
306                 SP3DBox *box = SP_3DBOX (i->data);
307                 Perspective3D *persp = drag->document->get_persp_of_box (box);
308                 VanishingPoint *vp = dr_new->getVPofPerspective (persp);
309                 if (vp == NULL) {
310                     g_warning ("VP is NULL. We should be okay, though.\n");
311                 }
312                 if (persp->all_boxes_occur_in_list (boxes_to_do)) {
313                     // if all boxes of persp are selected, we can simply move the VP from dr_new back to dragger
314                     dr_new->removeVP (vp);
315                     dragger->addVP (vp);
317                     // some cleaning up for efficiency
318                     boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
319                 } else {
320                     /* otherwise the unselected boxes need to stay linked to dr_new; thus we
321                        create a new perspective and link the VPs to the correct draggers */
322                     Perspective3D *persp_new = new Perspective3D (*persp);
323                     drag->document->add_perspective (persp_new);
325                     Axis vp_axis = persp->get_axis_of_VP (vp);
326                     dragger->addVP (persp_new->get_vanishing_point (vp_axis));
327                     std::pair<Axis, Axis> rem_axes = get_remaining_axes (vp_axis);
328                     drag->addDragger (persp->get_vanishing_point (rem_axes.first));
329                     drag->addDragger (persp->get_vanishing_point (rem_axes.second));
331                     // now we move the selected boxes from persp to persp_new
332                     GSList * selected_boxes_of_perspective = persp->boxes_occurring_in_list (boxes_to_do);
333                     for (GSList *j = selected_boxes_of_perspective; j != NULL; j = j->next) {
334                         persp->remove_box (SP_3DBOX (j->data));
335                         persp_new->add_box (SP_3DBOX (j->data));
336                     }
338                     // cleaning up
339                     boxes_to_do = eliminate_remaining_boxes_of_persp_starting_from_list_position (boxes_to_do, box, persp);
340                 }
341             }
343             // TODO: Something is still wrong with updating the boxes' representations after snapping
344             //dr_new->updateBoxReprs ();
346             dragger->updateTip();
347             dr_new->updateTip();
348         }
349     }
352 static void
353 vp_knot_ungrabbed_handler (SPKnot *knot, guint /*state*/, gpointer data)
355     VPDragger *dragger = (VPDragger *) data;
357     //sp_canvas_end_forced_full_redraws(dragger->parent->desktop->canvas);
359     dragger->point_original = dragger->point = knot->pos;
361     /***
362     VanishingPoint *vp;
363     for (GSList *i = dragger->vps; i != NULL; i = i->next) {
364         vp = (VanishingPoint *) i->data;
365         vp->set_pos (knot->pos);
366     }
367     ***/
369     dragger->parent->updateDraggers ();
370     dragger->updateBoxReprs ();
372     // TODO: Update box's paths and svg representation
374     dragger->parent->dragging = false;
376     // TODO: Undo machinery!!
377     g_return_if_fail (dragger->parent);
378     g_return_if_fail (dragger->parent->document);
379     sp_document_done(dragger->parent->document, SP_VERB_CONTEXT_3DBOX,
380                      _("3D box: Move vanishing point"));
383 VPDragger::VPDragger(VPDrag *parent, NR::Point p, VanishingPoint *vp)
385     this->vps = NULL;
387     this->parent = parent;
389     this->point = p;
390     this->point_original = p;
392     if (vp->is_finite()) {
393         // create the knot
394         this->knot = sp_knot_new (inkscape_active_desktop(), NULL);
395         this->knot->setMode(SP_KNOT_MODE_XOR);
396         this->knot->setFill(VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL, VP_KNOT_COLOR_NORMAL);
397         this->knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
398         sp_knot_update_ctrl(this->knot);
400         // move knot to the given point
401         sp_knot_set_position (this->knot, &this->point, SP_KNOT_STATE_NORMAL);
402         sp_knot_show (this->knot);
404         // connect knot's signals
405         g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (vp_knot_moved_handler), this);
406         /***
407         g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (vp_knot_clicked_handler), this);
408         ***/
409         g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (vp_knot_grabbed_handler), this);
410         g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (vp_knot_ungrabbed_handler), this);
411         /***
412         g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (vp_knot_doubleclicked_handler), this);
413         ***/
415         // add the initial VP (which may be NULL!)
416         this->addVP (vp);
417         //updateKnotShape();
418     }
421 VPDragger::~VPDragger()
423     // unselect if it was selected
424     //this->parent->setDeselected(this);
426     // disconnect signals
427     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_moved_handler), this);
428     /***
429     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_clicked_handler), this);
430     ***/
431     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_grabbed_handler), this);
432     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_ungrabbed_handler), this);
433     /***
434     g_signal_handlers_disconnect_by_func(G_OBJECT(this->knot), (gpointer) G_CALLBACK (vp_knot_doubleclicked_handler), this);
435     ***/
437     /* unref should call destroy */
438     g_object_unref (G_OBJECT (this->knot));
440     g_slist_free (this->vps);
441     this->vps = NULL;
444 /**
445 Updates the statusbar tip of the dragger knot, based on its draggables
446  */
447 void
448 VPDragger::updateTip ()
450     if (this->knot && this->knot->tip) {
451         g_free (this->knot->tip);
452         this->knot->tip = NULL;
453     }
455     guint num = this->numberOfBoxes();
456     if (g_slist_length (this->vps) == 1) {
457         VanishingPoint *vp = (VanishingPoint *) this->vps->data;
458         switch (vp->state) {
459             case VP_FINITE:
460                 this->knot->tip = g_strdup_printf (ngettext("<b>Finite</b> vanishing point shared by <b>%d</b> box",
461                                                             "<b>Finite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
462                                                             num),
463                                                    num);
464                 break;
465             case VP_INFINITE:
466                 // This won't make sense any more when infinite VPs are not shown on the canvas,
467                 // but currently we update the status message anyway
468                 this->knot->tip = g_strdup_printf (ngettext("<b>Infinite</b> vanishing point shared by <b>%d</b> box",
469                                                             "<b>Infinite</b> vanishing point shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
470                                                             num),
471                                                    num);
472                 break;
473         }
474     } else {
475         int length = g_slist_length (this->vps);
476         char *desc1 = g_strdup_printf ("Collection of <b>%d</b> vanishing points ", length);
477         char *desc2 = g_strdup_printf (ngettext("shared by <b>%d</b> box; drag with <b>Shift</b> to separate selected box(es)",
478                                                 "shared by <b>%d</b> boxes; drag with <b>Shift</b> to separate selected box(es)",
479                                                 num),
480                                        num);
481         this->knot->tip = g_strconcat(desc1, desc2, NULL);
482         g_free (desc1);
483         g_free (desc2);
484     }
487 /**
488  * Adds a vanishing point to the dragger (also updates the position)
489  */
490 void
491 VPDragger::addVP (VanishingPoint *vp)
493     if (vp == NULL) {
494         return;
495     }
496     if (!vp->is_finite() || g_slist_find (this->vps, vp)) {
497         // don't add infinite VPs, and don't add the same VP twice
498         return;
499     }
501     vp->set_pos (this->point);
502     this->vps = g_slist_prepend (this->vps, vp);
504     this->updateTip();
507 void
508 VPDragger::removeVP (VanishingPoint *vp)
510     if (vp == NULL) {
511         g_print ("NULL vanishing point will not be removed.\n");
512         return;
513     }
514     g_assert (this->vps != NULL);
515     this->vps = g_slist_remove (this->vps, vp);
517     this->updateTip();
520 // returns the VP contained in the dragger that belongs to persp
521 VanishingPoint *
522 VPDragger::getVPofPerspective (Perspective3D *persp)
524     for (GSList *i = vps; i != NULL; i = i->next) {
525         if (persp->has_vanishing_point ((VanishingPoint *) i->data)) {
526             return ((VanishingPoint *) i->data);
527         }
528     }
529     return NULL;
532 bool
533 VPDragger::hasBox(const SP3DBox *box)
535     for (GSList *i = this->vps; i != NULL; i = i->next) {
536         if (parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->has_box (box)) return true;
537     }
538     return false;
541 guint
542 VPDragger::numberOfBoxes ()
544     guint num = 0;
545     for (GSList *i = this->vps; i != NULL; i = i->next) {
546         num += parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->number_of_boxes ();
547     }
548     return num;
551 bool
552 VPDragger::hasPerspective (const Perspective3D *persp)
554     for (GSList *i = this->vps; i != NULL; i = i->next) {
555         if (*persp == *parent->document->get_persp_of_VP ((VanishingPoint *) i->data)) {
556             return true;
557         }
558     }
559     return false;
562 void
563 VPDragger::mergePerspectives ()
565     Perspective3D *persp1, *persp2;
566     GSList * successor = NULL;
567     for (GSList *i = this->vps; i != NULL; i = i->next) {
568         persp1 = parent->document->get_persp_of_VP ((VanishingPoint *) i->data);
569         for (GSList *j = i->next; j != NULL; j = successor) {
570             // if the perspective is deleted, the VP is invalidated, too, so we must store its successor beforehand
571             successor = j->next;
572             persp2 = parent->document->get_persp_of_VP ((VanishingPoint *) j->data);
573             if (*persp1 == *persp2) {
574                 persp1->absorb (persp2); // persp2 is deleted; hopefully this doesn't screw up the list of vanishing points and thus the loops
575             }
576         }
577     }
580 void
581 VPDragger::reshapeBoxes (NR::Point const &p, Box3D::Axis /*axes*/)
583     Perspective3D *persp;
584     for (GSList const* i = this->vps; i != NULL; i = i->next) {
585         VanishingPoint *vp = (VanishingPoint *) i->data;
586         // TODO: We can extract the VP directly from the box's perspective. Is that vanishing point identical to 'vp'?
587         //       Or is there duplicated information? If so, remove it and simplify the whole construction!
588         vp->set_pos(p);
589         persp = parent->document->get_persp_of_VP (vp);
590         Box3D::Axis axis = persp->get_axis_of_VP (vp);
591         parent->document->get_persp_of_VP (vp)->reshape_boxes (axis); // FIXME: we should only update the direction of the VP
592     }
593     parent->updateBoxHandles();
596 void
597 VPDragger::updateBoxReprs ()
599     for (GSList *i = this->vps; i != NULL; i = i->next) {
600         parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_box_reprs ();
601     }
604 void
605 VPDragger::updateZOrders ()
607     for (GSList *i = this->vps; i != NULL; i = i->next) {
608         parent->document->get_persp_of_VP ((VanishingPoint *) i->data)->update_z_orders ();
609     }
612 VPDrag::VPDrag (SPDocument *document)
614     this->document = document;
615     this->selection = sp_desktop_selection(inkscape_active_desktop());
617     this->draggers = NULL;
618     this->lines = NULL;
619     this->show_lines = true;
620     this->front_or_rear_lines = 0x1;
622     //this->selected = NULL;
623     this->local_change = false;
624     this->dragging = false;
626     this->sel_changed_connection = this->selection->connectChanged(
627         sigc::bind (
628             sigc::ptr_fun(&vp_drag_sel_changed),
629             (gpointer)this )
631         );
632     this->sel_modified_connection = this->selection->connectModified(
633         sigc::bind(
634             sigc::ptr_fun(&vp_drag_sel_modified),
635             (gpointer)this )
636         );
638     this->updateDraggers ();
639     this->updateLines ();
642 VPDrag::~VPDrag()
644     this->sel_changed_connection.disconnect();
645     this->sel_modified_connection.disconnect();
647     for (GList *l = this->draggers; l != NULL; l = l->next) {
648         delete ((VPDragger *) l->data);
649     }
650     g_list_free (this->draggers);
651     this->draggers = NULL;
653     for (GSList const *i = this->lines; i != NULL; i = i->next) {
654         gtk_object_destroy( GTK_OBJECT (i->data));
655     }
656     g_slist_free (this->lines);
657     this->lines = NULL;
660 /**
661  * Select the dragger that has the given VP.
662  */
663 VPDragger *
664 VPDrag::getDraggerFor (VanishingPoint const &vp)
666     for (GList const* i = this->draggers; i != NULL; i = i->next) {
667         VPDragger *dragger = (VPDragger *) i->data;
668         for (GSList const* j = dragger->vps; j != NULL; j = j->next) {
669             VanishingPoint *vp2 = (VanishingPoint *) j->data;
670             g_assert (vp2 != NULL);
672             // TODO: Should we compare the pointers or the VPs themselves!?!?!?!
673             //if ((*vp2) == vp) {
674             if (vp2 == &vp) {
675                 return (dragger);
676             }
677         }
678     }
679     return NULL;
682 /**
683  * Regenerates the draggers list from the current selection; is called when selection is changed or modified
684  */
685 void
686 VPDrag::updateDraggers ()
688     if (this->dragging)
689         return;
690     /***
691     while (selected) {
692         selected = g_list_remove(selected, selected->data);
693     }
694     ***/
695     // delete old draggers
696     for (GList const* i = this->draggers; i != NULL; i = i->next) {
697         delete ((VPDragger *) i->data);
698     }
699     g_list_free (this->draggers);
700     this->draggers = NULL;
702     g_return_if_fail (this->selection != NULL);
704     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
705         SPItem *item = SP_ITEM(i->data);
706         //SPStyle *style = SP_OBJECT_STYLE (item);
708         if (!SP_IS_3DBOX (item)) continue;
709         SP3DBox *box = SP_3DBOX (item);
711         Box3D::Perspective3D *persp = document->get_persp_of_box (box);
712         addDragger (persp->get_vanishing_point(Box3D::X));
713         addDragger (persp->get_vanishing_point(Box3D::Y));
714         addDragger (persp->get_vanishing_point(Box3D::Z));
715     }
718 /**
719 Regenerates the lines list from the current selection; is called on each move
720 of a dragger, so that lines are always in sync with the actual perspective
721 */
722 void
723 VPDrag::updateLines ()
725     // delete old lines
726     for (GSList const *i = this->lines; i != NULL; i = i->next) {
727         gtk_object_destroy( GTK_OBJECT (i->data));
728     }
729     g_slist_free (this->lines);
730     this->lines = NULL;
732     // do nothing if perspective lines are currently disabled
733     if (this->show_lines == 0) return;
735     g_return_if_fail (this->selection != NULL);
737     for (GSList const* i = this->selection->itemList(); i != NULL; i = i->next) {
738         if (!SP_IS_3DBOX(i->data)) continue;
739         SP3DBox *box = SP_3DBOX (i->data);
741         this->drawLinesForFace (box, Box3D::X);
742         this->drawLinesForFace (box, Box3D::Y);
743         this->drawLinesForFace (box, Box3D::Z);
744     }
747 void
748 VPDrag::updateBoxHandles ()
750     // FIXME: Is there a way to update the knots without accessing the
751     //        statically linked function knotholder_update_knots?
753     GSList *sel = (GSList *) selection->itemList();
754     if (g_slist_length (sel) > 1) {
755         // Currently we only show handles if a single box is selected
756         return;
757     }
759     if (!SP_IS_3DBOX (sel->data))
760         return;
762     SPEventContext *ec = inkscape_active_event_context();
763     g_assert (ec != NULL);
764     if (ec->shape_knot_holder != NULL) {
765         knotholder_update_knots(ec->shape_knot_holder, (SPItem *) sel->data);
766     }
769 /**
770  * Depending on the value of all_lines, draw the front and/or rear perspective lines starting from the given corners.
771  */
772 void
773 VPDrag::drawLinesForFace (const SP3DBox *box, Box3D::Axis axis) //, guint corner1, guint corner2, guint corner3, guint corner4)
775     guint color;
776     switch (axis) {
777         // TODO: Make color selectable by user
778         case Box3D::X: color = VP_LINE_COLOR_STROKE_X; break;
779         case Box3D::Y: color = VP_LINE_COLOR_STROKE_Y; break;
780         case Box3D::Z: color = VP_LINE_COLOR_STROKE_Z; break;
781         default: g_assert_not_reached();
782     }
784     NR::Point corner1, corner2, corner3, corner4;
785     sp_3dbox_corners_for_perspective_lines (box, axis, corner1, corner2, corner3, corner4);
787     VanishingPoint *vp = document->get_persp_of_box (box)->get_vanishing_point (axis);
788     if (vp->is_finite()) {
789         // draw perspective lines for finite VPs
790         NR::Point pt = vp->get_pos();
791         if (this->front_or_rear_lines & 0x1) {
792             // draw 'front' perspective lines
793             this->addLine (corner1, pt, color);
794             this->addLine (corner2, pt, color);
795         }
796         if (this->front_or_rear_lines & 0x2) {
797             // draw 'rear' perspective lines
798             this->addLine (corner3, pt, color);
799             this->addLine (corner4, pt, color);
800         }
801     } else {
802         // draw perspective lines for infinite VPs
803         NR::Maybe<NR::Point> pt1, pt2, pt3, pt4;
804         Box3D::Perspective3D *persp = this->document->get_persp_of_box (box);
805         SPDesktop *desktop = inkscape_active_desktop (); // FIXME: Store the desktop in VPDrag
806         Box3D::PerspectiveLine pl (corner1, axis, persp);
807         pt1 = pl.intersection_with_viewbox(desktop);
809         pl = Box3D::PerspectiveLine (corner2, axis, persp);
810         pt2 = pl.intersection_with_viewbox(desktop);
812         pl = Box3D::PerspectiveLine (corner3, axis, persp);
813         pt3 = pl.intersection_with_viewbox(desktop);
815         pl = Box3D::PerspectiveLine (corner4, axis, persp);
816         pt4 = pl.intersection_with_viewbox(desktop);
818         if (!pt1 || !pt2 || !pt3 || !pt4) {
819             // some perspective lines s are outside the canvas; currently we don't draw any of them
820             return;
821         }
822         if (this->front_or_rear_lines & 0x1) {
823             // draw 'front' perspective lines
824             this->addLine (corner1, *pt1, color);
825             this->addLine (corner2, *pt2, color);
826         }
827         if (this->front_or_rear_lines & 0x2) {
828             // draw 'rear' perspective lines
829             this->addLine (corner3, *pt3, color);
830             this->addLine (corner4, *pt4, color);
831         }
832     }
836 /**
837  * Returns true if all boxes that are linked to a VP in the dragger are selected
838  */
839 bool
840 VPDrag::allBoxesAreSelected (VPDragger *dragger) {
841     GSList *selected_boxes = (GSList *) dragger->parent->selection->itemList();
842     for (GSList *i = dragger->vps; i != NULL; i = i->next) {
843         if (!document->get_persp_of_VP ((VanishingPoint *) i->data)->all_boxes_occur_in_list (selected_boxes)) {
844             return false;
845         }
846     }
847     return true;
850 GSList *
851 VPDrag::selectedBoxesWithVPinDragger (VPDragger *dragger)
853     GSList *sel_boxes = g_slist_copy ((GSList *) dragger->parent->selection->itemList());
854     for (GSList const *i = sel_boxes; i != NULL; i = i->next) {
855         SP3DBox *box = SP_3DBOX (i->data);
856         if (!dragger->hasBox (box)) {
857             sel_boxes = g_slist_remove (sel_boxes, box);
858         }
859     }
860     return sel_boxes;
864 /**
865  * If there already exists a dragger within MERGE_DIST of p, add the VP to it;
866  * otherwise create new dragger and add it to draggers list
867  */
868 void
869 VPDrag::addDragger (VanishingPoint *vp)
871     if (vp == NULL) {
872         g_print ("Warning: The VP in addDragger is already NULL. Aborting.\n)");
873         g_assert (vp != NULL);
874     }
875     if (!vp->is_finite()) {
876         // don't create draggers for infinite vanishing points
877         return;
878     }
879     NR::Point p = vp->get_pos();
881     for (GList *i = this->draggers; i != NULL; i = i->next) {
882         VPDragger *dragger = (VPDragger *) i->data;
883         if (NR::L2 (dragger->point - p) < MERGE_DIST) {
884             // distance is small, merge this draggable into dragger, no need to create new dragger
885             dragger->addVP (vp);
886             //dragger->updateKnotShape();
887             return;
888         }
889     }
891     VPDragger *new_dragger = new VPDragger(this, p, vp);
892     // fixme: draggers should be added AFTER the last one: this way tabbing through them will be from begin to end.
893     this->draggers = g_list_append (this->draggers, new_dragger);
896 /**
897 Create a line from p1 to p2 and add it to the lines list
898  */
899 void
900 VPDrag::addLine (NR::Point p1, NR::Point p2, guint32 rgba)
902     SPCanvasItem *line = sp_canvas_item_new(sp_desktop_controls(inkscape_active_desktop()), SP_TYPE_CTRLLINE, NULL);
903     sp_ctrlline_set_coords(SP_CTRLLINE(line), p1, p2);
904     if (rgba != VP_LINE_COLOR_FILL) // fill is the default, so don't set color for it to speed up redraw
905         sp_ctrlline_set_rgba32 (SP_CTRLLINE(line), rgba);
906     sp_canvas_item_show (line);
907     this->lines = g_slist_append (this->lines, line);
910 } // namespace Box3D
912 /*
913   Local Variables:
914   mode:c++
915   c-file-style:"stroustrup"
916   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
917   indent-tabs-mode:nil
918   fill-column:99
919   End:
920 */
921 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :