root/branch/joao/umitMapper/RadialNet.py @ 620

Revision 620, 18.2 kB (checked in by ignotus21, 6 years ago)

Popup improvemets.

Line 
1# Copyright (C) 2007 Adriano Monteiro Marques <py.adriano@gmail.com>
2#
3# Author: Joao Paulo de Souza Medeiros <ignotus21@gmail.com>
4#
5# This program 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# This program 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 this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19import gtk
20import math
21import pango
22import drawing
23import geometry
24from gtk import gdk
25from Coordinate import PolarCoordinate
26from Graph import Graph, Node
27
28
29class RadialNet(gtk.DrawingArea):
30    """
31    Radial network visualization widget
32    """
33
34    def __init__(self, numberOfRings=3):
35        """
36        Constructor method of RadialNet widget class
37        @type  numberOfRings: number
38        @param numberOfRings: Number of rings in radial layout
39        """
40        self.__numberOfRings = numberOfRings
41        """Number of rings in radial layout"""
42        self.__ringGap = 50
43        """Gap between rings"""
44        self.__graph = None
45        """Graph displayed in radial layout"""
46        self.__centerOfWidget = (0, 0)
47        """Store the center of widget"""
48        self.__isInAnimation = False
49
50        super(RadialNet, self).__init__()
51        self.connect("expose_event", self.expose)
52        self.connect("button_press_event", self.button_press)
53        self.connect("button_release_event", self.button_release)
54        self.connect("motion_notify_event", self.motion_notify)
55
56        self.add_events(gdk.BUTTON_PRESS_MASK |
57                        gdk.BUTTON_RELEASE_MASK |
58                        gdk.POINTER_MOTION_MASK)
59
60
61    def __getNodeByCoordinate(self, point):
62        """
63        """
64        xc, yc = self.__centerOfWidget
65
66        for id in range(self.__graph.getNumberOfNodes()):
67
68            node = self.__graph.getNodeByID(id)
69
70            xn, yn = node.getCartesianCoordinate()
71            center = (xc + xn, yc - yn)
72            radius = node.getDrawInfo('radius')
73
74            if ( geometry.pointIsInCircle(point, radius, center) == True ):
75                return node, center
76
77        return None
78
79
80    def button_press(self, widget, event):
81        """
82        Drawing callback
83        @type  widget: GtkWidget
84        @param widget: Gtk widget superclass
85        @type  event: GtkEvent
86        @param event: Gtk event of widget
87        @rtype: boolean
88        @return: Indicator of the event propagation
89        """
90        event.window.set_cursor(gdk.Cursor(gtk.gdk.X_CURSOR))
91
92        if event.button == 3:
93
94            result = self.__getNodeByCoordinate(self.get_pointer())
95
96            if ( result != None ):
97
98                xw, yw = self.window.get_origin()
99                node, point = result
100                x, y = point
101
102                nodeView = NodeView(node, (int(xw + x), int(yw + y)), self)
103                nodeView.show_all()
104       
105        return True
106
107
108    def button_release(self, widget, event):
109        """
110        Drawing callback
111        @type  widget: GtkWidget
112        @param widget: Gtk widget superclass
113        @type  event: GtkEvent
114        @param event: Gtk event of widget
115        @rtype: boolean
116        @return: Indicator of the event propagation
117        """
118        event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
119
120        return True
121
122
123    def motion_notify(self, widget, event):
124        """
125        Drawing callback
126        @type  widget: GtkWidget
127        @param widget: Gtk widget superclass
128        @type  event: GtkEvent
129        @param event: Gtk event of widget
130        @rtype: boolean
131        @return: Indicator of the event propagation
132        """
133        xc, yc = self.__centerOfWidget
134        pointer = self.get_pointer()
135
136        for id in range(self.__graph.getNumberOfNodes()):
137            node = self.__graph.getNodeByID(id)
138            node.setDrawInfo('over', False)
139
140        result = self.__getNodeByCoordinate(self.get_pointer())
141
142        if result != None:
143            result[0].setDrawInfo('over', True)
144
145        self.queue_draw()
146       
147        return True
148
149
150    def expose(self, widget, event):
151        """
152        Drawing callback
153        @type  widget: GtkWidget
154        @param widget: Gtk widget superclass
155        @type  event: GtkEvent
156        @param event: Gtk event of widget
157        @rtype: boolean
158        @return: Indicator of the event propagation
159        """
160        self.context = widget.window.cairo_create()
161
162        self.context.rectangle(*event.area)
163        self.context.set_source_rgb(1.0, 1.0, 1.0)
164        self.context.fill_preserve()
165        self.context.clip()
166
167        self.draw()
168
169        return False
170
171
172    def draw(self):
173        """
174        Drawing method
175        """
176        # Getting allocation reference
177        allocation = self.get_allocation()
178        self.__centerOfWidget = (allocation.x + allocation.width / 2,
179                                 allocation.y + allocation.height / 2)
180
181        (xc, yc) = self.__centerOfWidget
182
183        # Drawing network rings
184        for i in range(self.__numberOfRings,0,-1):
185
186            self.context.arc(xc, yc, self.__ringGap * (i + 1), 0, 2 * math.pi)
187            self.context.set_source_rgb(0.2, 0.2, 0.2)
188            self.context.set_dash([4, 6])
189            self.context.set_line_width(0.2)
190            self.context.stroke()
191
192        # Drawing nodes and your connections
193
194        self.__calculateNodePositions()
195
196        connectionsList = set()
197
198        for i in range(self.__graph.getNumberOfNodes()):
199
200            node = self.__graph.getNodeByID(i)
201
202            for j in self.__graph.getNodeConnections(node):
203
204                if (i, j) not in connectionsList:
205                    self.__drawConnection(node, self.__graph.getNodeByID(j))
206                    connectionsList.add((i, j))
207                    connectionsList.add((j, i))
208
209        for i in range(self.__graph.getNumberOfNodes()):
210
211            self.__drawNode(self.__graph.getNodeByID(i))
212
213
214    def __drawConnection(self, nodeA, nodeB):
215        """
216        Draw the connection between two nodes
217        @type  : NetNode
218        @param : The first node that will be connected
219        @type  : NetNode
220        @param : The second node that will be connected
221        """
222        # Draw connections
223        self.context.save()
224        (xA, yA) = nodeA.getCartesianCoordinate()
225        (xB, yB) = nodeB.getCartesianCoordinate()
226
227        (xc, yc) = self.__centerOfWidget
228
229        self.context.set_source_rgb(0.5, 0.5, 1.0)
230        self.context.set_dash([1,0])
231        self.context.move_to(xc + xA, yc - yA)
232        self.context.line_to(xc + xB, yc - yB)
233        self.context.set_line_width(0.5)
234        self.context.stroke()
235        self.context.restore()
236
237
238    def __drawNode(self, node):
239        """
240        Draw nodes and your informations
241        @type  : NetNode
242        @param : The node will be draw
243        """
244        x, y = node.getCartesianCoordinate()
245        r, g, b = node.getDrawInfo('color')
246        xc, yc = self.__centerOfWidget
247
248        radius = node.getDrawInfo('radius')
249
250        if node.getDrawInfo('over') == True:
251            self.context.arc(xc + x, yc - y, radius + 2, 0, 2 * math.pi)
252            self.context.set_source_rgba(0.0, 0.0, 1.0, 0.3)
253            self.context.fill_preserve()
254            self.context.stroke()
255
256        self.context.arc(xc + x, yc - y, radius, 0, 2 * math.pi)
257        self.context.set_source_rgb(r, g, b)
258        self.context.fill_preserve()
259        self.context.set_source_rgb(0.0, 0.0, 0.0)
260        self.context.set_font_size(9)
261        self.context.show_text(node.getInformation('ip'))
262        self.context.set_dash([1,0])
263        self.context.set_line_width(0.5)
264        self.context.stroke()
265
266
267    def __calculateNodePositions(self):
268        """
269        Make some operations to determine the initial node positions
270        """
271        newNodes = [self.__graph.getMainNodeID()]
272        oldNodes = []
273
274        angleRange = range(self.__graph.getNumberOfNodes())
275        angleRange[self.__graph.getMainNodeID()] = (0,360)
276
277        self.__graph.getMainNode().setPolarCoordinate(0, 0)
278
279        for i in range(self.__numberOfRings):
280
281            locNodes = []
282
283            for nodeID in newNodes:
284
285                oldNodes.append(nodeID)
286
287                node = self.__graph.getNodeByID(nodeID)
288                connections = self.__graph.getNodeConnections(node, oldNodes)
289                (min, max) = angleRange[node.getID()]
290                alpha = 0
291
292                radius = self.__ringGap * (i + 2)
293
294                for j in connections:
295
296                    child = self.__graph.getNodeByID(j)
297                    alpha += 1
298
299                    factor = (max - min) / len(connections)
300
301                    if factor > 180:
302                        factor = 180
303
304                    a = min + (alpha - 1) * factor
305                    b = min + alpha * factor
306                    angleRange[j] = (a, b)
307
308                    theta = ((b - a) / 2) + a
309                    child.setPolarCoordinate(radius, math.radians(theta))
310
311                    locNodes.append(j)
312
313            for j in locNodes:
314                newNodes.append(j)
315
316            for j in oldNodes:
317                if j in newNodes:
318                    newNodes.remove(j)
319
320            if len(newNodes) == 0: break
321
322
323    def setGraph(self, graph):
324        """
325        Set graph to be displayed in layout
326        @type  : Graph
327        @param : Set the graph used in visualization
328        """
329        self.__graph = graph
330
331
332
333class NodeView(gtk.Window):
334    """
335    """
336
337    __COMPACTED = 1
338    __COLLAPSED = 0
339
340    def __init__(self, node, position, parent):
341        """
342        """
343        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
344        self.move(position[0], position[1])
345        self.__button_press_position = self.get_pointer()
346        self.resize(200, 120)
347        self.modify_bg(gtk.STATE_NORMAL, gdk.color_parse("#ffffe0"))
348
349        self.__node = node
350        self.__pressed = False
351        self.__parent = parent
352
353        self.connect("button_press_event", self.button_press)
354        self.connect("button_release_event", self.button_release)
355        self.connect("enter_notify_event", self.enter_notify)
356        self.connect("leave_notify_event", self.leave_notify)
357        self.connect("motion_notify_event", self.motion_notify)
358
359        self.add_events(gdk.BUTTON_PRESS_MASK |
360                        gdk.BUTTON_RELEASE_MASK |
361                        gdk.POINTER_MOTION_MASK |
362                        gdk.ENTER_NOTIFY |
363                        gdk.LEAVE_NOTIFY |
364                        gdk.POINTER_MOTION_HINT_MASK)
365
366        self.__draw_compact_layout()
367
368
369    def __draw_compact_layout(self):
370        """
371        """
372        body = gtk.VBox()
373        body.set_spacing(5)
374        body.set_border_width(5)
375        head = gtk.HBox()
376        head.set_spacing(5)
377        head.set_border_width(5)
378
379        bold_font = pango.FontDescription('monospace bold 8')
380        normal_font = pango.FontDescription('monospace 8')
381
382        color = gtk.EventBox()
383        color.set_size_request(15, 15)
384        r, g, b = drawing.cairoToGdkColor(self.__node.getDrawInfo('color'))
385        color.modify_bg(gtk.STATE_NORMAL, gdk.Color(r, g, b))
386
387        title = gtk.Label()
388        title.set_markup(self.__node.getInformation('ip'))
389        title.modify_font(bold_font)
390
391        close_event = gtk.EventBox()
392        close = gtk.Image()
393        close.set_from_file('image/close.png')
394        close_event.add(close)
395        close_event.connect('button_press_event', self.close_window)
396        close_event.add_events(gdk.BUTTON_PRESS_MASK)
397
398        expand_event = gtk.EventBox()
399        expand = gtk.Image()
400        expand.set_from_file('image/expand.png')
401        expand_event.add(expand)
402        expand_event.connect('button_press_event', self.expand_window)
403        expand_event.add_events(gdk.BUTTON_PRESS_MASK)
404
405        collapse_event = gtk.EventBox()
406        collapse = gtk.Image()
407        collapse.set_from_file('image/collapse.png')
408        collapse_event.add(collapse)
409        collapse_event.connect('button_press_event', self.collapse_window)
410        collapse_event.add_events(gdk.BUTTON_PRESS_MASK)
411
412        head.pack_start(color, False, False, 0)
413        head.pack_start(title, False, False, 0)
414        head.pack_end(close_event, False, False, 0)
415        head.pack_end(expand_event, False, False, 0)
416        head.pack_end(collapse_event, False, False, 0)
417        body.pack_start(head, False, False, 0)
418
419        self.add(body)
420
421
422    def close_window(self, widget, event):
423        """
424        """
425        self.__node.setDrawInfo('over', False)
426        self.destroy()
427        self.__parent.queue_draw()
428
429
430    def expand_window(self, widget, event):
431        """
432        """
433        if self.__COMPACTED == 1:
434            self.resize(400, 240)
435            self.__COMPACTED = 0
436        else:
437            self.resize(200, 120)
438            self.__COMPACTED = 1
439
440
441    def collapse_window(self, widget, event):
442        """
443        """
444        if self.__COLLAPSED == 0:
445            self.resize(200, 30)
446            self.__COLLAPSED = 1
447        else:
448            if self.__COMPACTED == 0:
449                self.resize(400, 240)
450            else:
451                self.resize(200, 120)
452            self.__COLLAPSED = 0
453
454
455    def button_press(self, widget, event):
456        """
457        """
458        self.__pressed = True
459        self.__button_press_position = self.get_pointer()
460
461        return True
462
463
464    def button_release(self, widget, event):
465        """
466        """
467        self.__pressed = False
468
469        return True
470
471
472    def enter_notify(self, widget, event):
473        """
474        """
475        self.__node.setDrawInfo('over', True)
476        self.__parent.queue_draw()
477
478
479    def leave_notify(self, widget, event):
480        """
481        """
482        self.__node.setDrawInfo('over', False)
483
484
485    def motion_notify(self, widget, event):
486        """
487        """
488        self.__node.setDrawInfo('over', True)
489
490        if event.is_hint == True:
491
492            x, y, button_state = event.window.get_pointer()
493
494            if button_state & gtk.gdk.BUTTON1_MASK and self.__pressed:
495
496                xw, yw = event.window.get_root_origin()
497                xd, yd = self.__button_press_position
498                self.move(x + xw - xd, y + yw - yd)
499
500        return True
501
502
503
504class NetNode(Node):
505    """
506    Node class for radial network widget
507    """
508    def __init__(self, id=Node):
509        """
510        """
511        self.__drawInfo = None 
512        """Hash with draw information"""
513        self.__coordinate = PolarCoordinate()
514
515        super(NetNode, self).__init__(id)
516
517
518    def setPolarCoordinate(self, r, t):
519        """
520        Set polar coordinate
521        @type  r: number
522        @param r: The radius of coordinate
523        @type  t: number
524        @param t: The angle (theta) of coordinate in radians
525        """
526        self.__coordinate.setCoordinate(r, t)
527
528
529    def getPolarCoordinate(self):
530        """
531        Get cartesian coordinate
532        @rtype: tuple
533        @return: Cartesian coordinates (x, y)
534        """
535        return self.__coordinate.getCoordinate()
536
537
538    def getCartesianCoordinate(self):
539        """
540        Get cartesian coordinate
541        @rtype: tuple
542        @return: Cartesian coordinates (x, y)
543        """
544        return self.__coordinate.toCartesian()
545
546
547    def getDrawInfo(self, info):
548        """
549        Get draw information about node
550        @type  : string
551        @param : Information name
552        @rtype: mixed
553        @return: The requested information
554        """
555        try:
556            return self.__drawInfo[ info ]
557        except:
558            return None
559
560
561    def setDrawInfo(self, info, value=None):
562        """
563        Set draw information
564        @type  : dict
565        @param : Draw information dictionary
566        """
567        if value == None:
568            self.__drawInfo = info
569        else:
570            self.__drawInfo[info] = value
571
572
573
574
575if __name__ == "__main__":
576
577    # Testing application
578
579    window = gtk.Window()
580    radialnet = RadialNet(5)
581
582    # Creating nodes
583    nodes = range(10)
584
585    nodes[0] = NetNode(0)
586    nodes[0].setDrawInfo({"color":(0,1,0), "radius":10})
587    nodes[0].setInformation({"ip":"192.168.1.1", "host":"joao.test.net"})
588
589    nodes[1] = NetNode(1)
590    nodes[1].setDrawInfo({"color":(1,1,0), "radius":8})
591    nodes[1].setInformation({"ip":"192.168.1.2", "host":"luis.test.net"})
592
593    nodes[2] = NetNode(2)
594    nodes[2].setDrawInfo({"color":(1,0,0), "radius":6})
595    nodes[2].setInformation({"ip":"192.168.1.3", "host":"polo.test.net"})
596
597    nodes[3] = NetNode(3)
598    nodes[3].setDrawInfo({"color":(0,1,0), "radius":10})
599    nodes[3].setInformation({"ip":"192.168.2.5", "host":"mara.test.net"})
600
601    nodes[4] = NetNode(4)
602    nodes[4].setDrawInfo({"color":(1,0,0), "radius":12})
603    nodes[4].setInformation({"ip":"192.168.3.2", "host":"nara.test.net"})
604
605    nodes[5] = NetNode(5)
606    nodes[5].setDrawInfo({"color":(0,1,0), "radius":8})
607    nodes[5].setInformation({"ip":"192.168.3.3", "host":"luca.test.net"})
608
609    nodes[6] = NetNode(6)
610    nodes[6].setDrawInfo({"color":(1,1,0), "radius":8})
611    nodes[6].setInformation({"ip":"192.168.3.1", "host":"jose.test.net"})
612
613    nodes[7] = NetNode(7)
614    nodes[7].setDrawInfo({"color":(1,1,0), "radius":10})
615    nodes[7].setInformation({"ip":"192.168.1.8", "host":"mark.test.net"})
616
617    nodes[8] = NetNode(8)
618    nodes[8].setDrawInfo({"color":(1,1,0), "radius":8})
619    nodes[8].setInformation({"ip":"192.168.1.7", "host":"mama.test.net"})
620
621    nodes[9] = NetNode(9)
622    nodes[9].setDrawInfo({"color":(1,0,0), "radius":6})
623    nodes[9].setInformation({"ip":"192.168.4.7", "host":"papa.test.net"})
624
625    graph = Graph(nodes)
626    graph.setMainNodeByID(0)
627    graph.setConnections(0, 1)
628    graph.setConnections(0, 2)
629    graph.setConnections(0, 7)
630    graph.setConnections(0, 8)
631    graph.setConnections(1, 3)
632    graph.setConnections(2, 4)
633    graph.setConnections(2, 5)
634    graph.setConnections(2, 6)
635    graph.setConnections(5, 9)
636
637    radialnet.setGraph(graph)
638    radialnet.set_size_request(400, 400)
639
640    window.add(radialnet)
641    window.connect("destroy", gtk.main_quit)
642    window.show_all()
643
644    gtk.main()
645
Note: See TracBrowser for help on using the browser.