| Trees | Indices | Help |
|
|---|
|
|
1 """
2 Manages adding, removing, resizing and drawing the canvas
3
4 The Canvas is the main area in Conduit, the area to which DataProviders are
5 dragged onto.
6
7 Copyright: John Stowers, 2006
8 License: GPLv2
9 """
10 import cairo
11 import goocanvas
12 import gtk
13 import pango
14 from gettext import gettext as _
15
16 import logging
17 log = logging.getLogger("gtkui.Canvas")
18
19 import conduit.utils as Utils
20 import conduit.Conduit as Conduit
21 import conduit.gtkui.Tree
22 import conduit.gtkui.Util as GtkUtil
23 import conduit.gtkui.Hints as Hints
24
25 log.info("Module Information: %s" % Utils.get_module_information(goocanvas, "pygoocanvas_version"))
26
28
30 style = self.get_gtk_style()
31 if style:
32 colors = getattr(style, styleName.lower(), None)
33 state = getattr(gtk, "STATE_%s" % stateName.upper(), None)
34 else:
35 colors = None
36 state = None
37
38 return colors,state
39
41 """
42 @returns: The gtk.Style for the widget
43 """
44 #not that clean, we can be mixed into the
45 #canvas, or a canvas item
46 try:
47 return self.get_canvas().style
48 except AttributeError:
49 try:
50 return self.style
51 except AttributeError:
52 return None
53
55 colors,state = self._get_colors_and_state(styleName, stateName)
56 if colors != None and state != None:
57 return GtkUtil.gdk2rgb(colors[state])
58 else:
59 return GtkUtil.gdk2rgb(GtkUtil.str2gdk("red"))
60
62 colors,state = self._get_colors_and_state(styleName, stateName)
63 if colors != None and state != None:
64 return GtkUtil.gdk2rgba(colors[state], a)
65 else:
66 return GtkUtil.gdk2rgba(GtkUtil.str2gdk("red"), a)
67
69 colors,state = self._get_colors_and_state(styleName, stateName)
70 if colors != None and state != None:
71 return GtkUtil.gdk2intrgb(colors[state])
72 else:
73 return GtkUtil.gdk2intrgb(GtkUtil.str2gdk("red"))
74
76 colors,state = self._get_colors_and_state(styleName, stateName)
77 if colors != None and state != None:
78 return GtkUtil.gdk2intrgba(colors[state], int(a*255))
79 else:
80 return GtkUtil.gdk2intrgba(GtkUtil.str2gdk("red"), int(a*255))
81
83
84 #attributes common to Conduit and Dataprovider items
85 RECTANGLE_RADIUS = 4.0
86
88 #FIXME: If parent is None in base constructor then goocanvas segfaults
89 #this means a ref to items may be kept so this may leak...
90 goocanvas.Group.__init__(self, parent=parent)
91 self.model = model
92
93 #this little piece of magic re-applies style properties to the
94 #widgets, when the users theme changes
95 canv = self.get_canvas()
96 if canv:
97 canv.connect("style-set", self._automatic_style_updater)
98
100 if not self.get_gtk_style():
101 #while in the midst of changing theme, the style is sometimes
102 #None, but dont worry, we will get called again
103 return
104 for attr in self.get_styled_item_names():
105 item = getattr(self, attr, None)
106 if item:
107 item.set_properties(
108 **self.get_style_properties(attr)
109 )
110
114
118
122
126
130
134
137
140
142 """
143 This class manages many objects
144 """
145 DND_TARGETS = [
146 ('conduit/element-name', 0, 0)
147 ]
148
149 WELCOME_MESSAGE = _("Drag a Data Provider here to continue")
150 - def __init__(self, parentWindow, typeConverter, syncManager, dataproviderMenu, conduitMenu, msg):
151 """
152 Draws an empty canvas of the appropriate size
153 """
154 #setup the canvas
155 goocanvas.Canvas.__init__(self)
156 self.set_bounds(0, 0,
157 conduit.GLOBALS.settings.get("gui_initial_canvas_width"),
158 conduit.GLOBALS.settings.get("gui_initial_canvas_height")
159 )
160 self.set_size_request(
161 conduit.GLOBALS.settings.get("gui_initial_canvas_width"),
162 conduit.GLOBALS.settings.get("gui_initial_canvas_height")
163 )
164 self.root = self.get_root_item()
165
166 self.sync_manager = syncManager
167 self.typeConverter = typeConverter
168 self.parentWindow = parentWindow
169 self.msg = msg
170
171 self._setup_popup_menus(dataproviderMenu, conduitMenu)
172
173 #set up DND from the treeview
174 self.drag_dest_set( gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK,
175 self.DND_TARGETS,
176 gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK)
177 self.connect('drag-motion', self.on_drag_motion)
178 self.connect('size-allocate', self._canvas_resized)
179
180 #track theme chages for canvas background
181 self.connect('realize', self._update_for_theme)
182 #We need a flag becuase otherwise we recurse forever.
183 #It appears that setting background_color_rgb in the
184 #sync-set handler causes sync-set to be emitted again, and again...
185 self._changing_style = False
186 self.connect("style-set", self._update_for_theme)
187
188 #keeps a reference to the currently selected (most recently clicked)
189 #canvas items
190 self.selectedConduitItem = None
191 self.selectedDataproviderItem = None
192
193 #model is a SyncSet, not set till later because it is loaded from xml
194 self.model = None
195
196 #Show a friendly welcome message on the canvas the first time the
197 #application is launched
198 self.welcome = None
199 self._maybe_show_welcome()
200
202 if respid == Hints.BLANK_CANVAS:
203 new = conduit.GLOBALS.moduleManager.get_module_wrapper_with_instance("FolderTwoWay")
204 self.add_dataprovider_to_canvas(
205 "FolderTwoWay",
206 new,
207 1,1
208 )
209
211 if Hints.HINT_TEXT[hint][2]:
212 buttons = [("Show me",hint)]
213 else:
214 buttons = []
215 h = self.msg.new_from_text_and_icon(
216 gtk.STOCK_INFO,
217 Hints.HINT_TEXT[hint][0],
218 Hints.HINT_TEXT[hint][1],
219 buttons=buttons,
220 timeout=timeout)
221 h.connect("response", self._do_hint)
222 h.show_all()
223
225 if not self.msg:
226 return
227
228 if not conduit.GLOBALS.settings.get("gui_show_hints"):
229 return
230
231 if newItem == conduitCanvasItem:
232 self._make_hint(Hints.ADD_DATAPROVIDER)
233 elif newItem == dataproviderCanvasItem:
234 #check if we have a source and a sink
235 if conduitCanvasItem.model.can_sync():
236 self._make_hint(Hints.RIGHT_CLICK_CONFIGURE)
237
239 if not self.get_gtk_style() or self._changing_style:
240 return
241
242 self._changing_style = True
243 self.set_property(
244 "background_color_rgb",
245 self.get_style_color_int_rgb("bg","normal")
246 )
247 if self.welcome:
248 self.welcome.set_property(
249 "fill_color_rgba",
250 self.get_style_color_int_rgba("text","normal")
251 )
252 self._changing_style = False
253
291
293 idx = self.root.find_child(self.welcome)
294 if idx != -1:
295 self.root.remove_child(idx)
296 self.welcome = None
297
299 c_x,c_y,c_w,c_h = self.get_bounds()
300 self.welcome = ConduitCanvasItem(
301 parent=self.root,
302 model=None,
303 width=c_w
304 )
305
307 """
308 Adds a friendly welcome to the canvas. Only does so only if
309 there are no conduits, otherwise it would just get in the way.
310 """
311 if self.model == None or (self.model != None and self.model.num_conduits() == 0):
312 if self.welcome == None:
313 self._create_welcome()
314 if self.msg and conduit.GLOBALS.settings.get("gui_show_hints"):
315 self._make_hint(Hints.BLANK_CANVAS, timeout=0)
316
317 elif self.welcome:
318 self._delete_welcome()
319
321 items = []
322 for i in range(0, self.root.get_n_children()):
323 condItem = self.root.get_child(i)
324 if isinstance(condItem, ConduitCanvasItem):
325 if condItem != self.welcome:
326 items.append(condItem)
327 return items
328
330 items = []
331 for c in self._get_child_conduit_canvas_items():
332 for i in range(0, c.get_n_children()):
333 dpItem = c.get_child(i)
334 if isinstance(dpItem, DataProviderCanvasItem):
335 items.append(dpItem)
336 return items
337
339 self.set_bounds(
340 0,0,
341 allocation.width,
342 self._get_minimum_canvas_size(allocation.height)
343 )
344 for i in self._get_child_conduit_canvas_items():
345 i.set_width(allocation.width)
346
381
412
414 """
415 Gets the Y coordinate at the bottom of all visible conduits
416
417 @returns: A coordinate (postivive down) from the canvas origin
418 @rtype: C{int}
419 """
420 y = 0.0
421 for i in self._get_child_conduit_canvas_items():
422 y = y + i.get_height()
423 return y
424
426 if not allocH:
427 allocH = self.get_allocation().height
428
429 bottom = self._get_bottom_of_conduits_coord()
430 return max(bottom + ConduitCanvasItem.WIDGET_HEIGHT + 20, allocH)
431
433 """
434 Moves the ConduitCanvasItems to stop them overlapping visually
435 """
436 items = self._get_child_conduit_canvas_items()
437 if len(items) > 0:
438 #special case where the top one was deleted
439 top = items[0].get_top()-(items[0].LINE_WIDTH/2)
440 if top != 0.0:
441 for item in items:
442 #translate all those below
443 item.translate(0,-top)
444 else:
445 for i in xrange(0, len(items)):
446 try:
447 overlap = items[i].get_bottom() - items[i+1].get_top()
448 if overlap != 0.0:
449 #translate all those below
450 for item in items[i+1:]:
451 item.translate(0,overlap)
452 except IndexError:
453 break
454
456 for item in self._get_child_conduit_canvas_items():
457 if item.model == conduitRemoved:
458 #remove the canvas item
459 idx = self.root.find_child(item)
460 if idx != -1:
461 self.root.remove_child(idx)
462 else:
463 log.warn("Error finding item")
464 self._remove_overlap()
465
466 self._maybe_show_welcome()
467 c_x,c_y,c_w,c_h = self.get_bounds()
468 self.set_bounds(
469 0,
470 0,
471 c_w,
472 self._get_minimum_canvas_size()
473 )
474
476 """
477 Creates a ConduitCanvasItem for the new conduit
478 """
479 #check for duplicates to eliminate race condition in set_sync_set
480 if conduitAdded in [i.model for i in self._get_child_conduit_canvas_items()]:
481 return
482
483 c_x,c_y,c_w,c_h = self.get_bounds()
484 #Create the item and move it into position
485 bottom = self._get_bottom_of_conduits_coord()
486 conduitCanvasItem = ConduitCanvasItem(
487 parent=self.root,
488 model=conduitAdded,
489 width=c_w)
490 conduitCanvasItem.connect('button-press-event', self._on_conduit_button_press)
491 conduitCanvasItem.translate(
492 conduitCanvasItem.LINE_WIDTH/2.0,
493 bottom+(conduitCanvasItem.LINE_WIDTH/2.0)
494 )
495
496 for dp in conduitAdded.get_all_dataproviders():
497 self.on_dataprovider_added(None, dp, conduitCanvasItem)
498
499 conduitAdded.connect("dataprovider-added", self.on_dataprovider_added, conduitCanvasItem)
500 conduitAdded.connect("dataprovider-removed", self.on_dataprovider_removed, conduitCanvasItem)
501
502 self._maybe_show_welcome()
503 self.set_bounds(
504 0,
505 0,
506 c_w,
507 self._get_minimum_canvas_size()
508 )
509
510 self._show_hint(conduitCanvasItem, None, conduitCanvasItem)
511
513 for item in self._get_child_dataprovider_canvas_items():
514 if item.model == dataproviderRemoved:
515 conduitCanvasItem.delete_dataprovider_canvas_item(item)
516 self._remove_overlap()
517
519 """
520 Creates a DataProviderCanvasItem for the new dataprovider and adds it to
521 the canvas
522 """
523
524 #check for duplicates to eliminate race condition in set_sync_set
525 if dataproviderAdded in [i.model for i in self._get_child_dataprovider_canvas_items()]:
526 return
527
528 item = DataProviderCanvasItem(
529 parent=conduitCanvasItem,
530 model=dataproviderAdded
531 )
532 item.connect('button-press-event', self._on_dataprovider_button_press)
533 conduitCanvasItem.add_dataprovider_canvas_item(item)
534 self._remove_overlap()
535
536 self._show_hint(conduitCanvasItem, item, item)
537
540
542 self.model = syncSet
543 for c in self.model.get_all_conduits():
544 self.on_conduit_added(None, c)
545
546 self.model.connect("conduit-added", self.on_conduit_added)
547 self.model.connect("conduit-removed", self.on_conduit_removed)
548
552
554 """
555 Delete a conduit and all its associated dataproviders
556 """
557 conduitCanvasItem = self.selectedConduitItem
558 cond = conduitCanvasItem.model
559 self.model.remove_conduit(cond)
560
566
572
574 """
575 Delete the selected dataprovider
576 """
577 dp = self.selectedDataproviderItem.model
578 conduitCanvasItem = self.selectedDataproviderItem.get_parent()
579 cond = conduitCanvasItem.model
580 cond.delete_dataprovider(dp)
581
583 """
584 Calls the configure method on the selected dataprovider
585 """
586 dp = self.selectedDataproviderItem.model.module
587 log.info("Configuring %s" % dp)
588 #May block
589 dp.configure(self.parentWindow)
590 self.selectedDataproviderItem.update_appearance()
591
593 """
594 Refreshes a single dataprovider
595 """
596 dp = self.selectedDataproviderItem.model
597 #dp.module.refresh()
598 cond = self.selectedConduitItem.model
599 cond.refresh_dataprovider(dp)
600
602 """
603 Enables or disables two way sync on dataproviders.
604 """
605 if widget.get_active():
606 self.selectedConduitItem.model.enable_two_way_sync()
607 else:
608 self.selectedConduitItem.model.disable_two_way_sync()
609
611 """
612 Enables or disables slow sync of dataproviders.
613 """
614 if widget.get_active():
615 self.selectedConduitItem.model.enable_slow_sync()
616 else:
617 self.selectedConduitItem.model.disable_slow_sync()
618
620 """
621 Enables or disables slow sync of dataproviders.
622 """
623 if widget.get_active():
624 self.selectedConduitItem.model.enable_auto_sync()
625 else:
626 self.selectedConduitItem.model.disable_auto_sync()
627
631
633 """
634 Adds a new dataprovider to the Canvas
635
636 @param module: The dataprovider wrapper to add to the canvas
637 @type module: L{conduit.Module.ModuleWrapper}.
638 @param x: The x location on the canvas to place the module widget
639 @type x: C{int}
640 @param y: The y location on the canvas to place the module widget
641 @type y: C{int}
642 @returns: The conduit that the dataprovider was added to
643 """
644 parent = None
645 existing = self.get_item_at(x,y,False)
646 c_x,c_y,c_w,c_h = self.get_bounds()
647
648 #if the user dropped on the right half of the canvas try add into the sink position
649 if x < (c_w/2):
650 trySourceFirst = True
651 else:
652 trySourceFirst = False
653
654 #recurse up the canvas objects to determine if we have been dropped
655 #inside an existing conduit
656 if existing:
657 parent = existing.get_parent()
658 while parent != None and not parent == self.welcome and not isinstance(parent, ConduitCanvasItem):
659 parent = parent.get_parent()
660
661 #if we were dropped on the welcome message we first remove that
662 if parent and parent == self.welcome:
663 self._delete_welcome()
664 #ensure a new conduit is created
665 parent = None
666
667 if parent != None:
668 #we were dropped on an existing conduit
669 parent.model.add_dataprovider(dataproviderWrapper, trySourceFirst)
670 return
671
672 #create a new conduit
673 cond = Conduit.Conduit(self.sync_manager)
674 cond.add_dataprovider(dataproviderWrapper, trySourceFirst)
675 self.model.add_conduit(cond)
676
678 self.model.clear()
679
681
682 WIDGET_WIDTH = 130
683 WIDGET_HEIGHT = 50
684 IMAGE_TO_TEXT_PADDING = 5
685 PENDING_MESSAGE = "Pending"
686 MAX_TEXT_LENGTH = 10
687 MAX_TEXT_LINES = 2
688 LINE_WIDTH = 2.0
689
691 _CanvasItem.__init__(self, parent, model)
692
693 self._build_widget()
694 self.set_model(model)
695
697 #FIXME: Goocanvas.Text does not ellipsize text,
698 #so we do it...... poorly
699 text = ""
700 lines = 1
701 for word in self.model.get_name().split(" "):
702 if len(word) > self.MAX_TEXT_LENGTH:
703 word = word[0:self.MAX_TEXT_LENGTH] + "... "
704 else:
705 word = word + " "
706
707 #gross guess for how much of the space we have used
708 if (len(text)+len(word)) > (self.MAX_TEXT_LENGTH*self.MAX_TEXT_LINES):
709 #append final elipsis
710 if not word.endswith("... "):
711 text = text + "..."
712 break
713 else:
714 text = text + word
715
716 return text
717
719 return self.model.get_icon()
720
722 self.box = goocanvas.Rect(
723 x=0,
724 y=0,
725 width=self.WIDGET_WIDTH-(2*self.LINE_WIDTH),
726 height=self.WIDGET_HEIGHT-(2*self.LINE_WIDTH),
727 radius_y=self.RECTANGLE_RADIUS,
728 radius_x=self.RECTANGLE_RADIUS,
729 **self.get_style_properties("box")
730 )
731 pb = self.model.get_icon()
732 pbx = int((1*self.WIDGET_WIDTH/5) - (pb.get_width()/2))
733 pby = int((1*self.WIDGET_HEIGHT/3) - (pb.get_height()/2))
734 self.image = goocanvas.Image(pixbuf=pb,
735 x=pbx,
736 y=pby
737 )
738 self.name = goocanvas.Text(
739 x=pbx + pb.get_width() + self.IMAGE_TO_TEXT_PADDING,
740 y=int(1*self.WIDGET_HEIGHT/3),
741 width=3*self.WIDGET_WIDTH/5,
742 text=self._get_model_name(),
743 anchor=gtk.ANCHOR_WEST,
744 **self.get_style_properties("name")
745 )
746 self.statusText = goocanvas.Text(
747 x=int(1*self.WIDGET_WIDTH/10),
748 y=int(2*self.WIDGET_HEIGHT/3),
749 width=4*self.WIDGET_WIDTH/5,
750 text="",
751 anchor=gtk.ANCHOR_WEST,
752 **self.get_style_properties("statusText")
753 )
754
755 #Add all the visual elements which represent a dataprovider
756 self.add_child(self.box)
757 self.add_child(self.name)
758 self.add_child(self.image)
759 self.add_child(self.statusText)
760
763
767
770
772 if specifier == "box":
773 #color the box differently if it is pending, i.e. unavailable,
774 #disconnected, etc.
775 if self.model.module == None:
776 insensitive = self.get_style_color_int_rgba("mid","insensitive")
777 kwargs = {
778 "line_width":1.5,
779 "stroke_color_rgba":insensitive,
780 "fill_color_rgba":insensitive
781 }
782
783 else:
784 pattern = cairo.LinearGradient(0, 0, 0, 100)
785 pattern.add_color_stop_rgb(
786 0,
787 *self.get_style_color_rgb("dark","active")
788 );
789 pattern.add_color_stop_rgb(
790 0.5,
791 *self.get_style_color_rgb("dark","prelight")
792 );
793
794 kwargs = {
795 "line_width":2.0,
796 "stroke_color":"black",
797 "fill_pattern":pattern
798 }
799 elif specifier == "name":
800 kwargs = {
801 "font":"Sans 8",
802 "fill_color_rgba":self.get_style_color_int_rgba("text","normal")
803 }
804 elif specifier == "statusText":
805 kwargs = {
806 "font":"Sans 7",
807 "fill_color_rgba":self.get_style_color_int_rgba("text_aa","normal")
808 }
809
810 return kwargs
811
813 #the image
814 pb = self._get_icon()
815 pbx = int((1*self.WIDGET_WIDTH/5) - (pb.get_width()/2))
816 pby = int((1*self.WIDGET_HEIGHT/3) - (pb.get_height()/2))
817 self.image.set_property("pixbuf",pb)
818
819 self.name.set_property("text",self._get_model_name())
820
821 if self.model.module == None:
822 statusText = self.PENDING_MESSAGE
823 else:
824 statusText = self.model.module.get_status()
825 self.statusText.set_property("text",statusText)
826
827 self.box.set_properties(
828 **self.get_style_properties("box")
829 )
830
832 self.model = model
833 self.update_appearance()
834 if self.model.module != None:
835 self.model.module.connect("change-detected", self._on_change_detected)
836 self.model.module.connect("status-changed", self._on_status_changed)
837
839
840 DIVIDER = False
841 FLAT_BOX = True
842 WIDGET_HEIGHT = 63.0
843 SIDE_PADDING = 10.0
844 LINE_WIDTH = 2.0
845
847 _CanvasItem.__init__(self, parent, model)
848
849 if self.model:
850 self.model.connect("parameters-changed", self._on_conduit_parameters_changed)
851 self.model.connect("dataprovider-changed", self._on_conduit_dataprovider_changed)
852 self.model.connect("sync-progress", self._on_conduit_progress)
853
854 self.sourceItem = None
855 self.sinkDpItems = []
856 self.connectorItems = {}
857
858 self.l = None
859 self.progressText = None
860 self.boundingBox = None
861
862 #if self.DIVIDER, show an transparent bouding box, and a
863 #simple dividing line
864 self.divider = None
865 #goocanvas.Points need a list of tuples, not a list of lists. Yuck
866 self.dividerPoints = [(),()]
867
868 #Build the widget
869 self._build_widget(width)
870
872 if self.sourceItem != None and len(self.sinkDpItems) > 0:
873 if self.progressText == None:
874 fromx,fromy,tox,toy = self._get_connector_coordinates(self.sourceItem,self.sinkDpItems[0])
875 self.progressText = goocanvas.Text(
876 x=fromx+5,
877 y=fromy-15,
878 width=100,
879 text="",
880 anchor=gtk.ANCHOR_WEST,
881 alignment=pango.ALIGN_LEFT,
882 **self.get_style_properties("progressText")
883 )
884 self.add_child(self.progressText)
885
887 dpx, dpy = self.model.get_dataprovider_position(dpCanvasItem.model)
888 if dpx == 0:
889 #Its a source
890 dpCanvasItem.translate(
891 self.SIDE_PADDING,
892 self.SIDE_PADDING + self.l.get_property("line_width")
893 )
894 else:
895 #Its a sink
896 if dpy == 0:
897 i = self.SIDE_PADDING
898 else:
899 i = (dpy * self.SIDE_PADDING) + self.SIDE_PADDING
900
901 dpCanvasItem.translate(
902 self.get_width() - dpCanvasItem.get_width() - self.SIDE_PADDING,
903 (dpy * dpCanvasItem.get_height()) + i + self.l.get_property("line_width")
904 )
905
907 true_width = width-self.LINE_WIDTH
908
909 #draw a spacer to give some space between conduits
910 points = goocanvas.Points([(0.0, 0.0), (true_width, 0.0)])
911 self.l = goocanvas.Polyline(
912 points=points,
913 line_width=self.LINE_WIDTH,
914 stroke_color_rgba=GtkUtil.TRANSPARENT_COLOR
915 )
916 self.add_child(self.l)
917
918 #draw a box which will contain the dataproviders
919 self.boundingBox = goocanvas.Rect(
920 x=0,
921 y=5,
922 width=true_width,
923 height=self.WIDGET_HEIGHT,
924 radius_y=self.RECTANGLE_RADIUS,
925 radius_x=self.RECTANGLE_RADIUS,
926 **self.get_style_properties("boundingBox")
927 )
928 self.add_child(self.boundingBox)
929 if self.DIVIDER:
930 #draw an underline
931 #from point
932 self.dividerPoints[0] = (true_width*0.33,5+self.WIDGET_HEIGHT)
933 self.dividerPoints[1] = (2*(true_width*0.33),5+self.WIDGET_HEIGHT)
934
935 self.divider = goocanvas.Polyline(
936 points=goocanvas.Points(self.dividerPoints),
937 **self.get_style_properties("divider")
938 )
939 self.add_child(self.divider)
940
942 sourceh = 0.0
943 sinkh = 0.0
944 padding = 0.0
945 for dpw in self.sinkDpItems:
946 sinkh += dpw.get_height()
947 #padding between items
948 numSinks = len(self.sinkDpItems)
949 if numSinks:
950 sinkh += ((numSinks - 1)*self.SIDE_PADDING)
951 if self.sourceItem != None:
952 sourceh += self.sourceItem.get_height()
953
954 self.set_height(
955 max(sourceh, sinkh)+ #expand to the largest
956 (1.5*self.SIDE_PADDING) #padding at the top and bottom
957 )
958
960 """
961 Deletes the connector associated with the sink item
962 """
963 try:
964 connector = self.connectorItems[item]
965 idx = self.find_child(connector)
966 if idx != -1:
967 self.remove_child(idx)
968 else:
969 log.warn("Could not find child connector item")
970
971 del(self.connectorItems[item])
972 except KeyError: pass
973
975 self.update_appearance()
976
978 for item in [self.sourceItem] + self.sinkDpItems:
979 if item.model.get_key() == olddpw.get_key():
980 item.set_model(newdpw)
981
984
986 """
987 Calculates the points a connector shall connect to between fromdp and todp
988 @returns: fromx,fromy,tox,toy
989 """
990 fromx = fromdp.get_right()
991 fromy = fromdp.get_top() + (fromdp.get_height()/2) - self.get_top()
992 tox = todp.get_left()
993 toy = todp.get_top() + (todp.get_height()/2) - self.get_top()
994 return fromx,fromy,tox,toy
995
997 items = self.sinkDpItems
998 if len(items) > 0:
999 #special case where the top one was deleted
1000 top = items[0].get_top()-self.get_top()-self.SIDE_PADDING-items[0].LINE_WIDTH
1001 if top != 0.0:
1002 for item in items:
1003 #translate all those below
1004 item.translate(0,-top)
1005 if self.sourceItem != None:
1006 fromx,fromy,tox,toy = self._get_connector_coordinates(self.sourceItem,item)
1007 self.connectorItems[item].reconnect(fromx,fromy,tox,toy)
1008 else:
1009 for i in xrange(0, len(items)):
1010 try:
1011 overlap = items[i].get_bottom() - items[i+1].get_top()
1012 log.debug("Sink Overlap: %s %s ----> %s" % (overlap,i,i+1))
1013 #If there is anything more than the normal padding gap between then
1014 #the dp must be translated
1015 if overlap < -self.SIDE_PADDING:
1016 #translate all those below, and make their connectors work again
1017 for item in items[i+1:]:
1018 item.translate(0,overlap+self.SIDE_PADDING)
1019 if self.sourceItem != None:
1020 fromx,fromy,tox,toy = self._get_connector_coordinates(self.sourceItem,item)
1021 self.connectorItems[item].reconnect(fromx,fromy,tox,toy)
1022 except IndexError:
1023 break
1024
1027
1029 if specifier == "boundingBox":
1030 if self.DIVIDER:
1031 kwargs = {
1032 "line_width":0
1033 }
1034 else:
1035 if self.FLAT_BOX:
1036 kwargs = {
1037 "line_width":0,
1038 "fill_color_rgba":self.get_style_color_int_rgba("base","prelight")
1039 }
1040 else:
1041 pattern = cairo.LinearGradient(0, -30, 0, 100)
1042 pattern.add_color_stop_rgb(
1043 0,
1044 *self.get_style_color_rgb("dark","selected")
1045 );
1046 pattern.add_color_stop_rgb(
1047 0.7,
1048 *self.get_style_color_rgb("mid","selected")
1049 );
1050
1051 kwargs = {
1052 "line_width":2.0,
1053 "fill_pattern":pattern,
1054 "stroke_color_rgba":self.get_style_color_int_rgba("text","normal")
1055 }
1056
1057 elif specifier == "progressText":
1058 kwargs = {
1059 "font":"Sans 7",
1060 "fill_color_rgba":self.get_style_color_int_rgba("text","normal")
1061 }
1062 elif specifier == "divider":
1063 kwargs = {
1064 "line_width":3.0,
1065 "line_cap":cairo.LINE_CAP_ROUND,
1066 "stroke_color_rgba":self.get_style_color_int_rgba("text_aa","normal")
1067 }
1068 else:
1069 kwargs = {}
1070
1071 return kwargs
1072
1074 self._resize_height()
1075
1076 #update the twowayness of the connectors
1077 for c in self.connectorItems.values():
1078 c.set_two_way(self.model.is_two_way())
1079
1081 self._position_dataprovider(item)
1082
1083 #is it a sink or a source?
1084 dpx, dpy = self.model.get_dataprovider_position(item.model)
1085 if dpx == 0:
1086 self.sourceItem = item
1087 else:
1088 self.sinkDpItems.append(item)
1089
1090 #add a connector. If we just added a source then we need to make all the
1091 #connectors, otherwise we just need to add a connector for the new item
1092 if dpx == 0:
1093 #make all the connectors
1094 for s in self.sinkDpItems:
1095 fromx,fromy,tox,toy = self._get_connector_coordinates(self.sourceItem,s)
1096 c = ConnectorCanvasItem(self,
1097 fromx,
1098 fromy,
1099 tox,
1100 toy,
1101 self.model.is_two_way(),
1102 conduit.GLOBALS.typeConverter.conversion_exists(
1103 self.sourceItem.model.get_output_type(),
1104 s.model.get_input_type()
1105 )
1106 )
1107 self.connectorItems[s] = c
1108 else:
1109 #just make the new connector
1110 if self.sourceItem != None:
1111 fromx,fromy,tox,toy = self._get_connector_coordinates(self.sourceItem,item)
1112 c = ConnectorCanvasItem(self,
1113 fromx,
1114 fromy,
1115 tox,
1116 toy,
1117 self.model.is_two_way(),
1118 conduit.GLOBALS.typeConverter.conversion_exists(
1119 self.sourceItem.model.get_output_type(),
1120 item.model.get_input_type()
1121 )
1122 )
1123 self.connectorItems[item] = c
1124
1125 self._add_progress_text()
1126 self.update_appearance()
1127
1129 """
1130 Removes the DataProviderCanvasItem and its connectors
1131 """
1132 idx = self.find_child(item)
1133 if idx != -1:
1134 self.remove_child(idx)
1135 else:
1136 log.warn("Could not find child dataprovider item")
1137
1138 if item == self.sourceItem:
1139 self.sourceItem = None
1140 #remove all connectors (copy because we modify in place)
1141 for item in self.connectorItems.copy():
1142 self._delete_connector(item)
1143 else:
1144 self.sinkDpItems.remove(item)
1145 self._delete_connector(item)
1146
1147 self._remove_overlap()
1148 self.