1 /*
2    Copyright (C) 2002 Kai Sterker <kai.sterker@gmail.com>
3    Part of the Adonthell Project  <http://adonthell.nongnu.org>
4 
5    Dlgedit is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    Dlgedit is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with Dlgedit.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 /**
20  * @file gui_graph.cc
21  *
22  * @author Kai Sterker
23  * @brief View for the dialogue graph
24  */
25 
26 #include "cfg_data.h"
27 #include "dlg_mover.h"
28 #include "gui_dlgedit.h"
29 #include "gui_graph_events.h"
30 #include "gui_resources.h"
31 #include "gui_circle.h"
32 #include "gui_file.h"
33 
34 // Constructor
GuiGraph(GtkWidget * paned)35 GuiGraph::GuiGraph (GtkWidget *paned) : Scrollable ()
36 {
37     // initialize members to sane values
38     mover = NULL;
39     module = NULL;
40     offset = NULL;
41     surface = NULL;
42     tooltip = NULL;
43 
44     // create drawing area for the graph
45     graph = gtk_drawing_area_new ();
46     gtk_widget_set_size_request (graph, 200, 450);
47     gtk_paned_add2 (GTK_PANED (paned), graph);
48     gtk_widget_show (graph);
49     gtk_widget_grab_focus (graph);
50 
51     // register our event callbacks
52     g_signal_connect (G_OBJECT (graph), "expose_event", G_CALLBACK(expose_event), this);
53     g_signal_connect (G_OBJECT (graph), "configure_event", G_CALLBACK(configure_event), this);
54     g_signal_connect (G_OBJECT (graph), "button_press_event", G_CALLBACK(button_press_event), this);
55     g_signal_connect (G_OBJECT (graph), "button_release_event", G_CALLBACK(button_release_event), this);
56     g_signal_connect (G_OBJECT (graph), "motion_notify_event", G_CALLBACK(motion_notify_event), this);
57     g_signal_connect (G_OBJECT (GuiDlgedit::window->getWindow ()), "key_press_event", G_CALLBACK(key_press_notify_event), this);
58 
59     gtk_widget_set_events (graph, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK |
60         GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK);
61 }
62 
63 // dtor
~GuiGraph()64 GuiGraph::~GuiGraph()
65 {
66     cairo_surface_destroy(surface);
67 }
68 
69 // attach a module
attachModule(DlgModule * m,bool cntr)70 void GuiGraph::attachModule (DlgModule *m, bool cntr)
71 {
72     module = m;
73 
74     // get reference of the module's offset
75     offset = &m->offset ();
76 
77     // center the module in view
78     if (cntr) center ();
79 
80     // if a node is selected, update the instant preview
81     GuiDlgedit::window->list ()->display (module->selected ());
82 
83     // update the module structure
84     GuiDlgedit::window->tree ()->select (module);
85 
86     // update the program state
87     GuiDlgedit::window->setMode (module->state ());
88 
89     GtkAllocation allocation;
90     gtk_widget_get_allocation (graph, &allocation);
91 
92     // set the size of the dialogue
93     drawing_area.resize (allocation.width, allocation.height);
94 
95     // tell the module that it is in view
96     module->setDisplayed (true);
97 
98     // display the module
99     draw ();
100 }
101 
102 // detach a module
detachModule()103 void GuiGraph::detachModule ()
104 {
105     module = NULL;
106 
107     // clear the instant preview
108     GuiDlgedit::window->list ()->clear ();
109 
110     // update the program state
111     GuiDlgedit::window->setMode (IDLE);
112 
113     // remove the tooltip if it is open
114     if (tooltip)
115     {
116         delete tooltip;
117         tooltip = NULL;
118     }
119 }
120 
121 // display a different module
switchModule(DlgModule * m)122 void GuiGraph::switchModule (DlgModule *m)
123 {
124     // we're switching between sub-dialogues
125     if (module) module->setDisplayed (false);
126 
127     detachModule ();
128     attachModule (m);
129 }
130 
131 // create a new circle
newCircle(DlgPoint & point,node_type type)132 bool GuiGraph::newCircle (DlgPoint &point, node_type type)
133 {
134     // if there is no module assigned to the view, there is nothing to do
135     if (module == NULL) return false;
136 
137     // get the serial number to use for this node
138     int &serial = module->serial ();
139 
140     // create the new node ...
141     DlgCircle *circle = new DlgCircle (point, type, serial, module->node_id ());
142 
143     // ... add it to the module ...
144     module->addNode (circle);
145 
146     // ... and select it for editing
147     module->selectNode (circle);
148 
149     // see whether creation was cancelled
150     if (!editNode ())
151     {
152         // cleanup
153         module->deleteNode ();
154 
155         return false;
156     }
157 
158     // update the program state
159     GuiDlgedit::window->setMode (NODE_SELECTED);
160 
161     // node created -> increase serial number for next node
162     serial++;
163 
164     return true;
165 }
166 
167 // create a new arrow
newArrow(DlgPoint & point)168 bool GuiGraph::newArrow (DlgPoint &point)
169 {
170     // if there is no module assigned to the view, there is nothing to do
171     if (module == NULL) return false;
172 
173     // calculate absolute position of the point
174     point.move (-offset->x (), -offset->y ());
175 
176     // get start and end of the circle
177     DlgNode *start = module->selected ();
178     DlgNode *end = module->getNode (point);
179 
180     // sanity checks
181     if (!start || start->type () == LINK) return false;
182     if (end && end->type () == LINK) return false;
183 
184     // if no end selected, create a new circle first
185     if (end == NULL)
186     {
187         // chose a sensible type for the new circle
188         node_type type = (start->type () == NPC ? PLAYER : NPC);
189 
190         // there musn't be a node selected to create a new circle
191         module->deselectNode ();
192 
193         // try to create a new circle
194         if (newCircle (point, type))
195         {
196             // chose the newly created circle as end of the arrow
197             end = module->getNode (point);
198 
199             // restore selection
200             deselectNode ();
201             selectNode (start);
202         }
203 
204         // do we have a valid end now?
205         if (end == NULL) return false;
206     }
207 
208     // no loops and no duplicate connections
209     if (start == end || ((DlgCircle *) start)->hasChild (end)) return false;
210 
211     // no connection between start and end if both are PLAYER nodes
212     if (start->type () == PLAYER && end->type () == PLAYER) return false;
213 
214     // now create the arrow between start and end, ...
215     DlgArrow *arrow = new DlgArrow (start, end);
216 
217     // ... add it to the module ...
218     module->addNode (arrow);
219 
220     // ... and update everything
221     GuiDlgedit::window->list ()->display (start);
222 	arrow->draw (surface, *offset, graph);
223     module->setChanged ();
224 
225     return true;
226 }
227 
228 // add a new subdialogue
newModule(DlgPoint & point)229 bool GuiGraph::newModule (DlgPoint &point)
230 {
231     // if there is no module assigned to the view, there is nothing to do
232     if (module == NULL) return false;
233 
234     // if a project root exists, use that in file selector
235     std::string dir = CfgData::data->getBasedir (module->entry ()->project ());
236 
237     // otherwise revert to directory last opened
238     if (dir == "") dir = GuiDlgedit::window->directory ();
239 
240     // allow the user to select a module
241     GtkWindow *parent = GTK_WINDOW (GuiDlgedit::window->getWindow());
242     GuiFile fs (parent, GTK_FILE_CHOOSER_ACTION_OPEN, "Select sub-dialogue to add", dir + "/");
243     fs.add_filter ("*" FILE_EXT, "Adonthell Dialogue Source");
244 
245     // set shortcuts
246     const std::vector<std::string> & projects = CfgData::data->projectsFromDatadir ();
247     for (std::vector<std::string>::const_iterator i = projects.begin (); i != projects.end (); i++)
248     {
249         const std::string &dir = CfgData::data->getBasedir (*i);
250         fs.add_shortcut (dir);
251     }
252 
253     if (fs.run ())
254     {
255         DlgModule *subdlg = GuiDlgedit::window->loadSubdialogue (fs.getSelection());
256 
257         if (subdlg == NULL) return false;
258 
259         // set parent of the sub-dialogue
260         subdlg->setParent (module);
261 
262         // set id of the sub-dialogue
263         subdlg->setID ();
264 
265         // draw the sub-dialogue
266         subdlg->initShape (point);
267         subdlg->draw (surface, *offset, graph);
268 
269         // update the module
270         module->setChanged ();
271         module->addNode (subdlg);
272 
273         // update the module tree
274         GuiDlgedit::window->tree ()->insert (module, subdlg);
275 
276         return true;
277     }
278 
279     return false;
280 }
281 
282 // delete the selected node
deleteNode()283 bool GuiGraph::deleteNode ()
284 {
285     // if there is no module assigned to the view, there is nothing to select
286     if (module == NULL) return false;
287 
288     // try to delete node
289     if (module->deleteNode ())
290     {
291         // update the program state
292         GuiDlgedit::window->setMode (IDLE);
293 
294         // update the instant preview
295         GuiDlgedit::window->list ()->clear ();
296 
297         // remove tooltip if it is open
298         if (tooltip)
299         {
300             delete tooltip;
301             tooltip = NULL;
302         }
303 
304         // redraw the dialogue
305         draw ();
306 
307         return true;
308     }
309 
310     return false;
311 }
312 
313 // select a node
selectNode(DlgNode * node)314 bool GuiGraph::selectNode (DlgNode *node)
315 {
316     // if there is no module assigned to the view, there is nothing to select
317     if (module == NULL) return false;
318 
319     // a node has been selected
320     if (module->selectNode (node))
321     {
322         // update the program state
323         GuiDlgedit::window->setMode (NODE_SELECTED);
324 
325         // update the instant preview
326         GuiDlgedit::window->list ()->display (node);
327 
328         // redraw the node
329         node->draw (surface, *offset, graph);
330 
331         return true;
332     }
333 
334     return false;
335 }
336 
337 // select the node at the given position
selectNode(DlgPoint & point)338 bool GuiGraph::selectNode (DlgPoint &point)
339 {
340     // if there is no module assigned to the view, there is nothing to select
341     if (module == NULL) return false;
342 
343     // is point within module's boundaries?
344     if (!drawing_area.contains (point)) return false;
345 
346     // calculate absolute position of the point
347     point.move (-offset->x (), -offset->y ());
348 
349     // see if we're over a node
350     DlgNode *node = module->getNode (point);
351 
352     // no node at that position
353     if (node == NULL) return false;
354 
355     // otherwise select the node
356     if (selectNode (node))
357     {
358         module->traverse ()->select (node);
359         return true;
360     }
361 
362     return false;
363 }
364 
365 // select parent
selectParent()366 bool GuiGraph::selectParent ()
367 {
368     // if there is no module assigned to the view, there is nothing to select
369     if (module == NULL) return false;
370 
371     // see if a node is currently selected
372     DlgNode *selected = module->selected ();
373 
374     // if so ...
375     if (selected)
376     {
377         // ... try to retrieve it's parent
378         DlgNode *parent = module->traverse ()->up ();
379 
380         // deselect current
381         deselectNode ();
382 
383         // if we have it, then select it
384         if (parent) return selectNode (parent);
385     }
386 
387     // if no node is selected, we simply select the first one
388     return selectRoot ();
389 }
390 
391 // select the child of a node
selectChild()392 bool GuiGraph::selectChild ()
393 {
394     // if there is no module assigned to the view, there is nothing to select
395     if (module == NULL) return false;
396 
397     // see if a node is currently selected
398     DlgNode *selected = module->selected ();
399 
400     // if so ...
401     if (selected)
402     {
403         // ... try to retrieve it's child
404         DlgNode *child = module->traverse ()->down ();
405 
406         // if we have it, then select it
407         if (child)
408         {
409             deselectNode ();
410             return selectNode (child);
411         }
412     }
413 
414     // if no node is selected, we simply select the first one
415     else return selectRoot ();
416 
417     return false;
418 }
419 
420 // select a sibling of the currently selected node
selectSibling(query_type pos)421 bool GuiGraph::selectSibling (query_type pos)
422 {
423     // if there is no module assigned to the view, there is nothing to select
424     if (module == NULL) return false;
425 
426     // see if a node is currently selected
427     DlgNode *selected = module->selected ();
428 
429     // if so ...
430     if (selected)
431     {
432         DlgNode *sibling;
433 
434         // see if selected is arrow
435         if (selected->type () == LINK)
436             selected = selected->next (FIRST);
437 
438         // ... try to retrieve it's child
439         if (pos == PREV) sibling = module->traverse ()->left ();
440         else sibling = module->traverse ()->right ();
441 
442         // if we have something now
443         if (sibling && sibling != selected)
444         {
445             deselectNode ();
446             return selectNode (sibling);
447         }
448     }
449 
450     // if no node is selected, we simply select the first one
451     else return selectRoot ();
452 
453     return false;
454 }
455 
456 // select the first node in the dialogue
selectRoot()457 bool GuiGraph::selectRoot ()
458 {
459     return selectNode (module->traverse ()->selectRoot (&module->getNodes ()));
460 }
461 
462 // deselect a node
deselectNode()463 void GuiGraph::deselectNode ()
464 {
465     // if there is no module assigned to the view, there is nothing to select
466     if (module == NULL) return;
467 
468     DlgNode *deselected = module->deselectNode ();
469 
470     if (deselected)
471     {
472         // update the program state
473         GuiDlgedit::window->setMode (IDLE);
474 
475         // update the instant preview
476         GuiDlgedit::window->list ()->clear ();
477 
478         // redraw the node
479         deselected->draw (surface, *offset, graph);
480     }
481 
482     return;
483 }
484 
485 // center the view on given node
centerNode(DlgNode * node)486 bool GuiGraph::centerNode (DlgNode *node)
487 {
488     if (module == NULL) return false;
489 
490     if (node == NULL && module->selected () != NULL)
491         node = module->selected ();
492     else return false;
493 
494     // calculate the correct offset for the given node
495     DlgPoint pos = node->center ().offset (*offset);
496     int x, y;
497 
498     x = drawing_area.width () / 5;
499     y = drawing_area.height () / 5;
500 
501     // is node outside the views inner 60% ?
502     if (!drawing_area.inflate (-x, -y).contains (pos))
503     {
504         // then move the view so it is centered on the given point
505         DlgPoint o (-(pos.x()-drawing_area.width()/2), -(pos.y()-drawing_area.height()/2));
506         offset->move (o);
507 
508         draw ();
509         return true;
510     }
511 
512     return false;
513 }
514 
515 // center whole dialogue in view
center()516 void GuiGraph::center ()
517 {
518     if (module == NULL) return;
519 
520     int min_x, max_x, y;
521 
522     module->extension (min_x, max_x, y);
523 
524     GtkAllocation allocation;
525     gtk_widget_get_allocation (graph, &allocation);
526 
527     int x_off = (allocation.width - (max_x - min_x))/2 - min_x;
528     int y_off = -y + 20;
529 
530     offset->move (x_off, y_off);
531 }
532 
533 // edit selected node
editNode()534 bool GuiGraph::editNode ()
535 {
536     if (module == NULL) return false;
537 
538     // see if a node is currently selected
539     DlgNode *selected = module->selected ();
540 
541     // disable scrolling (just in case)
542     stopScrolling();
543 
544     // if we have a sub-dialogue, descent for editing
545     if (selected->type () == MODULE)
546     {
547         switchModule ((DlgModule *) selected);
548         return true;
549     }
550 
551     // if we have a circle, open edit dialog
552     if (selected && selected->type () != LINK)
553     {
554         GtkWindow *parent = GTK_WINDOW (GuiDlgedit::window->getWindow());
555         GuiCircle edit (parent, &selected->type (), ((DlgCircle *) selected)->entry (), module->entry ());
556 
557 	    // editing aborted?
558 	    if (!edit.run ()) return false;
559 
560 	    // otherwise update everything
561         GuiDlgedit::window->list ()->display (selected);
562 	    selected->draw (surface, *offset, graph);
563         module->setChanged ();
564 
565         return true;
566     }
567 
568     return false;
569 }
570 
571 // set everything up for moving nodes around
prepareDragging(DlgPoint & point)572 bool GuiGraph::prepareDragging (DlgPoint &point)
573 {
574     // if there is no module assigned to the view, there is nothing to do
575     if (module == NULL) return false;
576 
577     // calculate absolute position of the point
578     point.move (-offset->x (), -offset->y ());
579 
580     // see if we're over a node
581     DlgNode *node = module->getNode (point);
582 
583     // Not over a node
584     if (node == NULL) return false;
585 
586     // if no node selected, select node for dragging
587     if (module->selected () == NULL) selectNode (node);
588 
589     // else check whether dragged and selected node are the same
590     else if (node != module->selected ()) return false;
591 
592     // Is dragged node circle or arrow?
593     if (node->type () != LINK)
594     {
595         // circles and modules can be dragged directly
596         mover = node;
597 
598         // remove any tooltip, as it only gets in the way
599         if (tooltip)
600         {
601             delete tooltip;
602             tooltip = NULL;
603         }
604     }
605     else
606     {
607         // arrows have to be attached to a (invisible) mover
608         mover = new DlgMover (point);
609 
610         // try to attach arrow to mover
611         if (!((DlgMover *) mover)->attach ((DlgArrow *) node))
612         {
613             // moving of arrow failed, so clean up
614             delete mover;
615             mover = NULL;
616         }
617     }
618 
619     // if we have a mover, update program state
620     if (mover != NULL)
621     {
622         GuiDlgedit::window->setMode (NODE_DRAGGED);
623         module->setState (NODE_DRAGGED);
624 
625         return true;
626     }
627 
628     return false;
629 }
630 
631 // drag a node around
drag(DlgPoint & point)632 void GuiGraph::drag (DlgPoint &point)
633 {
634     static int redraw = 0;
635 
636     // if there is no module assigned to the view, there is nothing to do
637     if (module == NULL) return;
638 
639     // calculate absolute position of the point
640     point.move (-offset->x (), -offset->y ());
641 
642     // move node
643     mover->setPos (DlgPoint (point.x () - (point.x () % CIRCLE_DIAMETER),
644                              point.y () - (point.y () % CIRCLE_DIAMETER)));
645 
646     // update arrows
647     for (DlgNode *a = mover->prev (FIRST); a != NULL; a = mover->prev (NEXT))
648         ((DlgArrow *) a)->initShape ();
649 
650     for (DlgNode *a = mover->next (FIRST); a != NULL; a = mover->next (NEXT))
651         ((DlgArrow *) a)->initShape ();
652 
653     // update view
654     switch (redraw)
655     {
656         case 0:
657         {
658             draw ();
659             redraw++;
660             break;
661         }
662         case 7:
663         {
664             redraw = 0;
665             break;
666         }
667         default:
668         {
669             redraw++;
670             break;
671         }
672     }
673 }
674 
675 // stop dragging node
stopDragging(DlgPoint & point)676 void GuiGraph::stopDragging (DlgPoint &point)
677 {
678     // if there is no module assigned to the view, there is nothing to do
679     if (module == NULL || mover == NULL) return;
680 
681     // calculate absolute position of the point
682     point.move (-offset->x (), -offset->y ());
683 
684     // see whether arrow was dragged
685     if (mover->type () == MOVER)
686     {
687         // see whether we are over a node
688         DlgNode *node = module->getNode (point);
689 
690         // drop the mover onto the node
691         ((DlgMover *) mover)->drop (node);
692 
693         // cleanup
694         delete mover;
695     }
696 
697     // if circle moved, realign it to the grid
698     else
699     {
700         // make sure we drop on an empty location
701         DlgNode *node = module->getNode (point);
702         while (node != NULL && node != mover && node->type() != LINK)
703         {
704             point.move (0, 2*CIRCLE_DIAMETER);
705             node = module->getNode (point);
706         }
707 
708         mover->setPos (DlgPoint (point.x () - (point.x () % CIRCLE_DIAMETER),
709                                  point.y () - (point.y () % CIRCLE_DIAMETER)));
710 
711         // also need to update arrows and reorder children and parents
712         for (DlgNode *a = mover->prev (FIRST); a != NULL; a = mover->prev (NEXT))
713         {
714             a->prev (FIRST)->removeNext (a);
715             a->prev (FIRST)->addNext (a);
716             ((DlgArrow *) a)->initShape ();
717         }
718 
719         for (DlgNode *a = mover->next (FIRST); a != NULL; a = mover->next (NEXT))
720         {
721             a->next (FIRST)->removePrev (a);
722             a->next (FIRST)->addPrev (a);
723             ((DlgArrow *) a)->initShape ();
724         }
725     }
726     // update everything
727     if (mover->type() == MODULE)
728         deselectNode ();
729     else
730     {
731         GuiDlgedit::window->list ()->display (module->selected ());
732         GuiDlgedit::window->setMode (NODE_SELECTED);
733         module->setState (NODE_SELECTED);
734         module->setChanged ();
735     }
736 
737     // clear mover
738     mover = NULL;
739 
740     draw ();
741 }
742 
743 // resize the drawing area
resizeSurface(GtkWidget * widget)744 void GuiGraph::resizeSurface (GtkWidget *widget)
745 {
746     GtkAllocation allocation;
747     gtk_widget_get_allocation (widget, &allocation);
748 
749     // delete the old surface
750     if (surface) cairo_surface_destroy (surface);
751 
752     // create a new one with the proper size
753     surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation.width, allocation.height);
754 
755     // init the surface
756     if (GuiResources::getColor (GC_GREY)) clear ();
757 
758     // set the size of the attached dialogues
759     drawing_area.resize (allocation.width, allocation.height);
760 }
761 
762 // empty the drawing area
clear()763 void GuiGraph::clear ()
764 {
765     GdkRectangle t;
766 
767     GtkAllocation allocation;
768     gtk_widget_get_allocation (graph, &allocation);
769 
770     cairo_t *cr = cairo_create (surface);
771     gdk_cairo_set_source_color(cr, GuiResources::getColor (GC_GREY));
772     cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
773     cairo_fill(cr);
774     cairo_destroy(cr);
775 
776     t.x = 0;
777     t.y = 0;
778     t.width = allocation.width;
779     t.height = allocation.height;
780 
781     gdk_window_invalidate_rect (gtk_widget_get_window (graph), &t, FALSE);
782 }
783 
784 // draw the graph to the surface
draw()785 void GuiGraph::draw ()
786 {
787     // nothing to draw
788     if (module == NULL) return;
789 
790     GdkRectangle t;
791     std::vector<DlgNode*>::reverse_iterator i;
792     std::vector<DlgNode*> nodes = module->getNodes ();
793 
794     // get visible part of graph
795     t.x = -offset->x ();
796     t.y = -offset->y ();
797     t.width = drawing_area.width ();
798     t.height = drawing_area.height ();
799 
800     DlgRect rect (t);
801 
802     // Clear graph
803     cairo_t *cr = cairo_create (surface);
804     gdk_cairo_set_source_color(cr, GuiResources::getColor (GC_WHITE));
805     cairo_rectangle(cr, 0, 0, t.width, t.height);
806     cairo_fill(cr);
807     cairo_destroy(cr);
808 
809     // normalize rect
810     t.x = 0;
811     t.y = 0;
812 
813     // check for each node, wether it is visible
814     for (i = nodes.rbegin (); i != nodes.rend (); i++)
815         // draw nodes and arrows
816         if ((*i)->contains (rect))
817             (*i)->draw (surface, *offset, NULL);
818 
819     // draw backing image to screen
820     gdk_window_invalidate_rect (gtk_widget_get_window (graph), &t, FALSE);
821 }
822 
823 // the mouse has been moved
mouseMoved(DlgPoint & point)824 void GuiGraph::mouseMoved (DlgPoint &point)
825 {
826     // if there is no module assigned to the view, there is nothing to do
827     if (module == NULL) return;
828 
829     // calculate absolute position of the point
830     point.move (-offset->x (), -offset->y ());
831 
832     // see if we're over a node
833     DlgNode *node = module->getNode (point);
834 
835     // get the node that was highlighted before (if any)
836     DlgNode *prev = module->highlightNode (node);
837 
838     // there's no need for action unless old and new highlight differs
839     if (prev != node)
840     {
841         // clear old if necessary
842         if (prev != NULL)
843         {
844             prev->draw (surface, *offset, graph);
845             if (tooltip)
846             {
847                 delete tooltip;
848                 tooltip = NULL;
849             }
850         }
851 
852         // then highlight the new one
853         if (node != NULL && gtk_window_is_active(GTK_WINDOW(gtk_widget_get_toplevel(graph))))
854         {
855             node->draw (surface, *offset, graph, NODE_HILIGHTED);
856             tooltip = new GuiTooltip (node);
857             tooltip->draw (graph, *offset);
858         }
859     }
860 
861     return;
862 }
863 
864 // is scrolling allowed?
scrollingAllowed() const865 bool GuiGraph::scrollingAllowed () const
866 {
867     // if there is no module assigned to the view or no nodes
868     // in the module yet, there is nothing to do
869     if (module == NULL || module->getNodes ().empty ()) return false;
870     return true;
871 }
872 
873 // the actual scrolling
scroll()874 void GuiGraph::scroll ()
875 {
876     offset->move (scroll_offset.x, scroll_offset.y);
877     draw ();
878 }
879 
880 // get the toplevel dialogue module
dialogue()881 DlgModule *GuiGraph::dialogue ()
882 {
883     // if there is no module assigned to the view, there is nothing to do
884     if (module == NULL) return NULL;
885 
886     return module->toplevel ();
887 }
888