class ObjectInspector(Inspector):
    
    @classmethod
    def name(cls):
        return cls.objectClass().displayName()
    
    
    @classmethod
    def shouldBeRegistered(cls):
        return cls != ObjectInspector.__class__
    
    
    @classmethod
    def objectClass(cls):
        return Object
    
    
    @classmethod
    def inspectedAttributes(cls):
        return []
    
    
    @classmethod
    def bitmap(cls, size = 32):
        image = cls.objectClass().image()
        if image == None:
            return Inspector.bitmap(cls)
        else:
            scaledImage = image.Rescale(size, size, wx.IMAGE_QUALITY_HIGH)
            return wx.BitmapFromImage(scaledImage)
    
    
    @classmethod
    def canInspectDisplay(cls, display):
        # This inspector can be used if there is at least one object of this type selected.
        if display:
            for visible in display.selection():
                if visible.client.__class__ == cls.objectClass():
                    return True
        return False
    

    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)
            
            self.iconField = wx.StaticBitmap(self._window, wx.ID_ANY)
            self.iconField.SetMinSize(wx.Size(32, 32))
            self.iconField.SetMaxSize(wx.Size(32, 32))
            self.titleField = wx.StaticText(self._window, wx.ID_ANY, "")
            headerBox = wx.BoxSizer(wx.HORIZONTAL)
            headerBox.Add(self.iconField, 0, wx.EXPAND)
            headerBox.AddSpacer(8)
            headerBox.Add(self.titleField, 1, wx.EXPAND)
            
            objectSizer = self.objectSizer(self._window)
            
            mainSizer = wx.BoxSizer(wx.VERTICAL)
            mainSizer.Add(headerBox, 0, wx.EXPAND | wx.ALL, 5)
            if objectSizer is not None:
                mainSizer.Add(objectSizer, 1, wx.ALL, 5)
            self._window.SetSizer(mainSizer)
        
        return self._window

    
    def inspectDisplay(self, display):
        # Object inspectors are supposed to work purely at the network/biological layer so they don't need to know what the display is.
        self.display = display
        
        self.objects = ObjectList()
        self.updatingObjects = False
        for visible in display.selection():
            if visible.client.__class__ == self.__class__.objectClass():
                self.objects.append(visible.client)
                dispatcher.connect(self.nameChanged, ('set', 'name'), visible.client)
                for attributePath in self.inspectedAttributes():
                    if '.' in attributePath:
                        (subObject, attribute) = attributePath.split('.')
                        object = getattr(visible.client, subObject)
                    else:
                        object = visible.client
                        attribute = attributePath
                    dispatcher.connect(self.inspectedAttributeChanged, ('set', attribute), object)
        
        # Set the icon
        image = self.objects[0].__class__.image()
        if image == None:
            pass
        else:
            scaledImage = image.Rescale(32, 32, wx.IMAGE_QUALITY_HIGH)
            self.iconField.SetBitmap(wx.BitmapFromImage(scaledImage))
        
        self.populateNameField()
        self.populateObjectSizer()
        self._window.Layout()
    
    
    def objectSizer(self, parentWindow):
        return None
    
    
    def populateNameField(self):
        if self.objects.haveEqualAttr('name'):
            self.titleField.SetLabel(self.objects[0].name or ('<' + gettext('unnamed ') + self.__class__.objectClass().displayName().lower() + '>'))
        else:
            self.titleField.SetLabel(gettext('Multiple names'))
    
    
    def populateObjectSizer(self, attribute = None):
        pass
    
    
    def nameChanged(self, signal, sender, event=None, value=None, **arguments):
        self.populateNameField()
    
    
    def inspectedAttributeChanged(self, signal, sender, **arguments):
        if not self.updatingObjects:
            self.populateObjectSizer(signal[1])
    
    
    def selectObject(self, object):
        self.display.selectObject(object)
class PathInspector(Inspector):
    
    @classmethod
    def name(cls):
        return gettext('Path')
    
    
    @classmethod
    def canInspectDisplay(cls, display):
        if display:
            for visible in display.selectedVisibles:
                if visible.isPath():
                    return True
        return display and not any(display.selectedVisibles)


    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)
            self._sizer = wx.BoxSizer(wx.VERTICAL)
            
            self._flowToBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self._window, wx.ID_ANY, gettext('Flow to')), wx.HORIZONTAL)
            
            flowToGridSizer = wx.FlexGridSizer(6, 2, 8, 0)
            flowToGridSizer.SetFlexibleDirection(wx.HORIZONTAL)
            flowToGridSizer.AddGrowableCol(1, 1)
            
            self._flowToCheckBox = wx.CheckBox(self._window, wx.ID_ANY, '', style=wx.CHK_3STATE)
            self._window.Bind(wx.EVT_CHECKBOX, self.onSetAnimateFlowTo, self._flowToCheckBox)
            self._flowToLabel = wx.StaticText(self._window, wx.ID_ANY, gettext('Animate:'))
            flowToGridSizer.Add(self._flowToLabel, 0)
            flowToGridSizer.Add(self._flowToCheckBox, 1)
            
            self._flowToColorPicker = wx.lib.colourselect.ColourSelect(self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT, self.onSetFlowToColor, self._flowToColorPicker)
            flowToGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            flowToGridSizer.Add(self._flowToColorPicker, 1)
            
            self._flowToSpacingSlider = wx.Slider(self._window, wx.ID_ANY, 100, 0, 1000)
            self._flowToSpacingSlider.Bind(wx.EVT_SCROLL, self.onSetFlowToSpacing, self._flowToSpacingSlider)
            flowToGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Spacing:')), 0)
            flowToGridSizer.Add(self._flowToSpacingSlider, 1, wx.EXPAND)
            
            self._flowToSpeedSlider = wx.Slider(self._window, wx.ID_ANY, 100, 0, 1000)
            self._flowToSpeedSlider.Bind(wx.EVT_SCROLL, self.onSetFlowToSpeed, self._flowToSpeedSlider)
            flowToGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Speed:')), 0)
            flowToGridSizer.Add(self._flowToSpeedSlider, 1, wx.EXPAND)
            
            self._flowToSpreadSlider = wx.Slider(self._window, wx.ID_ANY, 25, 0, 100)
            self._flowToSpreadSlider.Bind(wx.EVT_SCROLL, self.onSetFlowToSpread, self._flowToSpreadSlider)
            flowToGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Spread:')), 0)
            flowToGridSizer.Add(self._flowToSpreadSlider, 1, wx.EXPAND)
            
            self._flowToBoxSizer.Add(flowToGridSizer, 1, wx.EXPAND)
            self._sizer.Add(self._flowToBoxSizer, 1, wx.EXPAND | wx.ALL, 5)
            
            self._flowFromBoxSizer = wx.StaticBoxSizer(wx.StaticBox(self._window, wx.ID_ANY, gettext('Flow to')), wx.HORIZONTAL)
            
            flowFromGridSizer = wx.FlexGridSizer(6, 2, 8, 0)
            flowFromGridSizer.SetFlexibleDirection(wx.HORIZONTAL)
            flowFromGridSizer.AddGrowableCol(1, 1)
            
            self._flowFromCheckBox = wx.CheckBox(self._window, wx.ID_ANY, '', style=wx.CHK_3STATE)
            self._window.Bind(wx.EVT_CHECKBOX, self.onSetAnimateFlowFrom, self._flowFromCheckBox)
            flowFromGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Animate:')), 0)
            flowFromGridSizer.Add(self._flowFromCheckBox, 1)
            
            self._flowFromColorPicker = wx.lib.colourselect.ColourSelect(self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT, self.onSetFlowFromColor, self._flowFromColorPicker)
            flowFromGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            flowFromGridSizer.Add(self._flowFromColorPicker, 1)
            
            self._flowFromSpacingSlider = wx.Slider(self._window, wx.ID_ANY, 100, 0, 1000)
            self._flowFromSpacingSlider.Bind(wx.EVT_SCROLL, self.onSetFlowFromSpacing, self._flowFromSpacingSlider)
            flowFromGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Spacing:')), 0)
            flowFromGridSizer.Add(self._flowFromSpacingSlider, 1, wx.EXPAND)
            
            self._flowFromSpeedSlider = wx.Slider(self._window, wx.ID_ANY, 100, 0, 1000)
            self._flowFromSpeedSlider.Bind(wx.EVT_SCROLL, self.onSetFlowFromSpeed, self._flowFromSpeedSlider)
            flowFromGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Speed:')), 0)
            flowFromGridSizer.Add(self._flowFromSpeedSlider, 1, wx.EXPAND)
            
            self._flowFromSpreadSlider = wx.Slider(self._window, wx.ID_ANY, 25, 0, 100)
            self._flowFromSpreadSlider.Bind(wx.EVT_SCROLL, self.onSetFlowFromSpread, self._flowFromSpreadSlider)
            flowFromGridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Spread:')), 0)
            flowFromGridSizer.Add(self._flowFromSpreadSlider, 1, wx.EXPAND)
            
            self._flowFromBoxSizer.Add(flowFromGridSizer, 1, wx.EXPAND)
            self._sizer.Add(self._flowFromBoxSizer, 1, wx.EXPAND | wx.ALL, 5)
            
            self._window.SetSizer(self._sizer)
        
        return self._window

    
    def inspectDisplay(self, display):
        self.display = display
        
        # Get the subset of visibles that are paths.
        self.paths = ObjectList()
        self.visibles = display.selection()
        for visible in self.visibles:
            if visible.isPath():
                self.paths.append(visible)
        
        if len(self.paths) == 0:
            self._flowToLabel.Hide()
            self._flowToCheckBox.Hide()
            self._sizer.Hide(self._flowFromBoxSizer, True)
            dispatcher.connect(self.refreshGUI, ('set', 'defaultFlowColor'), self.display)
            dispatcher.connect(self.refreshGUI, ('set', 'defaultFlowSpread'), self.display)
        else:
            self._flowToLabel.Show()
            self._flowToCheckBox.Show()
            self._sizer.Show(self._flowFromBoxSizer, True)
            for path in self.paths:
                for attributeName in ['flowTo', 'flowToColor', 'flowToSpread', 'flowFrom', 'flowFromColor', 'flowFromSpread']:
                    dispatcher.connect(self.refreshGUI, ('set', attributeName), visible)
        
        self.refreshGUI()
    
    
    def reasonableSpacingOrSpeed(self):
        # Compute some reasonable spacing/speed based on the bounding box of the visibles in the display.
        self.display.computeVisiblesBound()
        return sqrt(self.display.visiblesSize[0] ** 2 + self.display.visiblesSize[1] ** 2 + self.display.visiblesSize[2] ** 2) / 20.0
    
    
    def refreshGUI(self, signal = ('set', None), **arguments):
        updatedAttribute = signal[1]
        
        usingDefault = len(self.paths) == 0
        
        # The spacing and speed sliders are mapped logarithmically so very small and very large values can be used.
        # The scale is set so that a reasonable spacing and speed are used when the slider is at the midpoint.
        # Unfortunately what is "reasonable" is dependent on the bounding box of the whole display which can change at any time.
        expBase = e ** (log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        
        if usingDefault:
            self._flowToBoxSizer.GetStaticBox().SetLabel(gettext('Default flow appearance'))
        else:
            if len(self.paths) == 1:
                (startPoint, endPoint) = self.paths[0].pathEndPoints()
                flowToName = gettext('anonymous object') if endPoint.client == None else endPoint.client.name or gettext('<unnamed %s>') % ( endPoint.client.__class__.displayName())
                flowFromName = gettext('anonymous object') if startPoint.client == None else startPoint.client.name or gettext('<unnamed %s>') % ( startPoint.client.__class__.displayName())
            else:
                flowToName = gettext('multiple objects')
                flowFromName = gettext('multiple objects')
            self._flowToBoxSizer.GetStaticBox().SetLabel(gettext('Flow to %s') % (flowToName))
            self._flowFromBoxSizer.GetStaticBox().SetLabel(gettext('Flow to %s') % (flowFromName))
        
        if updatedAttribute is None or updatedAttribute == 'flowTo':
            if usingDefault:
                self._flowToCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
                self._flowToCheckBox.Disable()
            else:
                if self.paths.haveEqualAttr('flowTo'):
                    if self.paths[0].flowTo():
                        self._flowToCheckBox.Set3StateValue(wx.CHK_CHECKED)
                    else:
                        self._flowToCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
                else:
                    self._flowToCheckBox.Set3StateValue(wx.CHK_UNDETERMINED)
                self._flowToCheckBox.Enable()
        
        if updatedAttribute is None or updatedAttribute == 'flowToColor' or updatedAttribute == 'defaultFlowColor':
            if usingDefault or self.paths.haveEqualAttr('flowToColor'):
                if usingDefault:
                    red, green, blue, alpha = self.display.defaultFlowColor
                else:
                    red, green, blue, alpha = self.paths[0].flowToColor() or self.display.defaultFlowColor
                self._flowToColorPicker.SetColour(wx.Colour(red * 255, green * 255, blue * 255, alpha * 255))
                self._flowToColorPicker.SetLabel(gettext(''))
            else:
                self._flowToColorPicker.SetColour(wx.NamedColour('GRAY'))  # TODO: be clever and pick the average color?
                self._flowToColorPicker.SetLabel(gettext('Multiple values'))
        
        if updatedAttribute is None or updatedAttribute == 'flowToSpacing' or updatedAttribute == 'defaultFlowSpacing':
            if usingDefault or self.paths.haveEqualAttr('flowToSpacing'):
                if usingDefault:
                    spacing = self.display.defaultFlowSpacing
                else:
                    spacing = self.paths[0].flowToSpacing() or self.display.defaultFlowSpacing
                self._flowToSpacingSlider.SetLabel('')
                self._flowToSpacingSlider.SetValue(log(spacing + 1.0, expBase))
            else:
                self._flowToSpacingSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpacingSlider.SetValue(0)
        
        if updatedAttribute is None or updatedAttribute == 'flowToSpeed' or updatedAttribute == 'defaultFlowSpeed':
            if usingDefault or self.paths.haveEqualAttr('flowToSpeed'):
                if usingDefault:
                    speed = self.display.defaultFlowSpeed
                else:
                    speed = self.paths[0].flowToSpeed() or self.display.defaultFlowSpeed
                self._flowToSpeedSlider.SetLabel('')
                self._flowToSpeedSlider.SetValue(log(speed + 1.0, expBase))
            else:
                self._flowToSpeedSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpeedSlider.SetValue(0)
        
        if updatedAttribute is None or updatedAttribute == 'flowToSpread' or updatedAttribute == 'defaultFlowSpread':
            if usingDefault or self.paths.haveEqualAttr('flowToSpread'):
                if usingDefault:
                    spread = self.display.defaultFlowSpread
                else:
                    spread = self.paths[0].flowToSpread() or self.display.defaultFlowSpread
                self._flowToSpreadSlider.SetLabel('')
                self._flowToSpreadSlider.SetValue(spread * 50.0)
            else:
                self._flowToSpreadSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpreadSlider.SetValue(50)
        
        if (updatedAttribute is None or updatedAttribute == 'flowFrom') and not usingDefault:
            if self.paths.haveEqualAttr('flowFrom'):
                if self.paths[0].flowFrom():
                    self._flowFromCheckBox.Set3StateValue(wx.CHK_CHECKED)
                else:
                    self._flowFromCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
            else:
                self._flowFromCheckBox.Set3StateValue(wx.CHK_UNDETERMINED)
            self._flowFromCheckBox.Enable()
        
        if (updatedAttribute is None or updatedAttribute == 'flowFromColor') and not usingDefault:
            if usingDefault or self.paths.haveEqualAttr('flowFromColor'):
                if usingDefault:
                    red, green, blue, alpha = self.display.defaultFlowColor
                else:
                    red, green, blue, alpha = self.paths[0].flowFromColor() or self.display.defaultFlowColor
                self._flowFromColorPicker.SetColour(wx.Colour(red * 255, green * 255, blue * 255, alpha * 255))
                self._flowFromColorPicker.SetLabel(gettext(''))
            else:
                self._flowFromColorPicker.SetColour(wx.NamedColour('GRAY'))  # TODO: be clever and pick the average color?
                self._flowFromColorPicker.SetLabel(gettext('Multiple values'))
        
        if updatedAttribute is None or updatedAttribute == 'flowFromSpacing' or updatedAttribute == 'defaultFlowSpacing':
            if usingDefault or self.paths.haveEqualAttr('flowFromSpacing'):
                if usingDefault:
                    spacing = self.display.defaultFlowSpacing
                else:
                    spacing = self.paths[0].flowFromSpacing() or self.display.defaultFlowSpacing
                self._flowFromSpacingSlider.SetLabel('')
                self._flowFromSpacingSlider.SetValue(log(spacing + 1.0, expBase))
            else:
                self._flowFromSpacingSlider.SetLabel(gettext('Multiple values'))
                self._flowFromSpacingSlider.SetValue(0)
        
        if updatedAttribute is None or updatedAttribute == 'flowFromSpeed' or updatedAttribute == 'defaultFlowSpeed':
            if usingDefault or self.paths.haveEqualAttr('flowFromSpeed'):
                if usingDefault:
                    speed = self.display.defaultFlowSpeed
                else:
                    speed = self.paths[0].flowFromSpeed() or self.display.defaultFlowSpeed
                self._flowFromSpeedSlider.SetLabel('')
                self._flowFromSpeedSlider.SetValue(log(speed + 1.0, expBase))
            else:
                self._flowFromSpeedSlider.SetLabel(gettext('Multiple values'))
                self._flowFromSpeedSlider.SetValue(0)
        
        if (updatedAttribute is None or updatedAttribute == 'flowFromSpread') and not usingDefault:
            if usingDefault or self.paths.haveEqualAttr('flowFromSpread'):
                if usingDefault:
                    spread = self.display.defaultFlowSpread
                else:
                    spread = self.paths[0].flowFromSpread() or self.display.defaultFlowSpread
                self._flowFromSpreadSlider.SetLabel('')
                self._flowFromSpreadSlider.SetValue(spread * 50.0)
            else:
                self._flowFromSpreadSlider.SetLabel(gettext('Multiple values'))
                self._flowFromSpreadSlider.SetValue(50)
        
        self._window.Layout()
    
    
    def onSetAnimateFlowTo(self, event):
        newValue = self._flowToCheckBox.GetValue()
        for path in self.paths:
            path.setFlowTo(newValue == wx.CHK_CHECKED)
    
    
    def onSetFlowToColor(self, event):
        wxColor = self._flowToColorPicker.GetColour()
        newColor = (wxColor.Red() / 255.0, wxColor.Green() / 255.0, wxColor.Blue() / 255.0, wxColor.Alpha() / 255.0)
        if len(self.paths) == 0:
            self.display.setDefaultFlowColor(newColor)
        else:
            for path in self.paths:
                path.setFlowToColor(newColor)
        
    
    def onSetFlowToSpacing(self, event):
        expBase = e ** (log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpacing = expBase ** self._flowToSpacingSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpacing(newSpacing)
        else:
            for path in self.paths:
                path.setFlowToSpacing(newSpacing)
        wx.GetApp().Yield()
    
    
    def onSetFlowToSpeed(self, event):
        expBase = e ** (log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpeed = expBase ** self._flowToSpeedSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpeed(newSpeed)
        else:
            for path in self.paths:
                path.setFlowToSpeed(newSpeed)
        wx.GetApp().Yield()
    
    
    def onSetFlowToSpread(self, event):
        newSpread = self._flowToSpreadSlider.GetValue() / 50.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpread(newSpread)
        else:
            for path in self.paths:
                path.setFlowToSpread(newSpread)
    
    
    def onSetAnimateFlowFrom(self, event):
        newValue = self._flowFromCheckBox.GetValue()
        for path in self.paths:
            path.setFlowFrom(newValue == wx.CHK_CHECKED)
    
    
    def onSetFlowFromColor(self, event):
        wxColor = self._flowFromColorPicker.GetColour()
        newColor = (wxColor.Red() / 255.0, wxColor.Green() / 255.0, wxColor.Blue() / 255.0, wxColor.Alpha() / 255.0)
        for path in self.paths:
            path.setFlowFromColor(newColor)
        
    
    def onSetFlowFromSpacing(self, event):
        expBase = e ** (log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpacing = expBase ** self._flowFromSpacingSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpacing(newSpacing)
        else:
            for path in self.paths:
                path.setFlowFromSpacing(newSpacing)
    
    
    def onSetFlowFromSpeed(self, event):
        expBase = e ** (log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpeed = expBase ** self._flowFromSpeedSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpeed(newSpeed)
        else:
            for path in self.paths:
                path.setFlowFromSpeed(newSpeed)
        
    
    def onSetFlowFromSpread(self, event):
        newSpread = self._flowFromSpreadSlider.GetValue() / 50.0
        for path in self.paths:
            path.setFlowFromSpread(newSpread)
Exemple #3
0
class PathInspector(Inspector):
    @classmethod
    def name(cls):
        return gettext('Path')

    @classmethod
    def canInspectDisplay(cls, display):
        if display:
            for visible in display.selectedVisibles:
                if visible.isPath():
                    return True
        return display and not any(display.selectedVisibles)

    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)
            self._sizer = wx.BoxSizer(wx.VERTICAL)

            self._flowToBoxSizer = wx.StaticBoxSizer(
                wx.StaticBox(self._window, wx.ID_ANY, gettext('Flow to')),
                wx.HORIZONTAL)

            flowToGridSizer = wx.FlexGridSizer(6, 2, 8, 0)
            flowToGridSizer.SetFlexibleDirection(wx.HORIZONTAL)
            flowToGridSizer.AddGrowableCol(1, 1)

            self._flowToCheckBox = wx.CheckBox(self._window,
                                               wx.ID_ANY,
                                               '',
                                               style=wx.CHK_3STATE)
            self._window.Bind(wx.EVT_CHECKBOX, self.onSetAnimateFlowTo,
                              self._flowToCheckBox)
            self._flowToLabel = wx.StaticText(self._window, wx.ID_ANY,
                                              gettext('Animate:'))
            flowToGridSizer.Add(self._flowToLabel, 0)
            flowToGridSizer.Add(self._flowToCheckBox, 1)

            self._flowToColorPicker = wx.lib.colourselect.ColourSelect(
                self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT,
                              self.onSetFlowToColor, self._flowToColorPicker)
            flowToGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            flowToGridSizer.Add(self._flowToColorPicker, 1)

            self._flowToSpacingSlider = wx.Slider(self._window, wx.ID_ANY, 100,
                                                  0, 1000)
            self._flowToSpacingSlider.Bind(wx.EVT_SCROLL,
                                           self.onSetFlowToSpacing,
                                           self._flowToSpacingSlider)
            flowToGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Spacing:')), 0)
            flowToGridSizer.Add(self._flowToSpacingSlider, 1, wx.EXPAND)

            self._flowToSpeedSlider = wx.Slider(self._window, wx.ID_ANY, 100,
                                                0, 1000)
            self._flowToSpeedSlider.Bind(wx.EVT_SCROLL, self.onSetFlowToSpeed,
                                         self._flowToSpeedSlider)
            flowToGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Speed:')), 0)
            flowToGridSizer.Add(self._flowToSpeedSlider, 1, wx.EXPAND)

            self._flowToSpreadSlider = wx.Slider(self._window, wx.ID_ANY, 25,
                                                 0, 100)
            self._flowToSpreadSlider.Bind(wx.EVT_SCROLL,
                                          self.onSetFlowToSpread,
                                          self._flowToSpreadSlider)
            flowToGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Spread:')), 0)
            flowToGridSizer.Add(self._flowToSpreadSlider, 1, wx.EXPAND)

            self._flowToBoxSizer.Add(flowToGridSizer, 1, wx.EXPAND)
            self._sizer.Add(self._flowToBoxSizer, 1, wx.EXPAND | wx.ALL, 5)

            self._flowFromBoxSizer = wx.StaticBoxSizer(
                wx.StaticBox(self._window, wx.ID_ANY, gettext('Flow to')),
                wx.HORIZONTAL)

            flowFromGridSizer = wx.FlexGridSizer(6, 2, 8, 0)
            flowFromGridSizer.SetFlexibleDirection(wx.HORIZONTAL)
            flowFromGridSizer.AddGrowableCol(1, 1)

            self._flowFromCheckBox = wx.CheckBox(self._window,
                                                 wx.ID_ANY,
                                                 '',
                                                 style=wx.CHK_3STATE)
            self._window.Bind(wx.EVT_CHECKBOX, self.onSetAnimateFlowFrom,
                              self._flowFromCheckBox)
            flowFromGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Animate:')), 0)
            flowFromGridSizer.Add(self._flowFromCheckBox, 1)

            self._flowFromColorPicker = wx.lib.colourselect.ColourSelect(
                self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT,
                              self.onSetFlowFromColor,
                              self._flowFromColorPicker)
            flowFromGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            flowFromGridSizer.Add(self._flowFromColorPicker, 1)

            self._flowFromSpacingSlider = wx.Slider(self._window, wx.ID_ANY,
                                                    100, 0, 1000)
            self._flowFromSpacingSlider.Bind(wx.EVT_SCROLL,
                                             self.onSetFlowFromSpacing,
                                             self._flowFromSpacingSlider)
            flowFromGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Spacing:')), 0)
            flowFromGridSizer.Add(self._flowFromSpacingSlider, 1, wx.EXPAND)

            self._flowFromSpeedSlider = wx.Slider(self._window, wx.ID_ANY, 100,
                                                  0, 1000)
            self._flowFromSpeedSlider.Bind(wx.EVT_SCROLL,
                                           self.onSetFlowFromSpeed,
                                           self._flowFromSpeedSlider)
            flowFromGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Speed:')), 0)
            flowFromGridSizer.Add(self._flowFromSpeedSlider, 1, wx.EXPAND)

            self._flowFromSpreadSlider = wx.Slider(self._window, wx.ID_ANY, 25,
                                                   0, 100)
            self._flowFromSpreadSlider.Bind(wx.EVT_SCROLL,
                                            self.onSetFlowFromSpread,
                                            self._flowFromSpreadSlider)
            flowFromGridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Spread:')), 0)
            flowFromGridSizer.Add(self._flowFromSpreadSlider, 1, wx.EXPAND)

            self._flowFromBoxSizer.Add(flowFromGridSizer, 1, wx.EXPAND)
            self._sizer.Add(self._flowFromBoxSizer, 1, wx.EXPAND | wx.ALL, 5)

            self._window.SetSizer(self._sizer)

        return self._window

    def inspectDisplay(self, display):
        self.display = display

        # Get the subset of visibles that are paths.
        self.paths = ObjectList()
        self.visibles = display.selection()
        for visible in self.visibles:
            if visible.isPath():
                self.paths.append(visible)

        if len(self.paths) == 0:
            self._flowToLabel.Hide()
            self._flowToCheckBox.Hide()
            self._sizer.Hide(self._flowFromBoxSizer, True)
            dispatcher.connect(self.refreshGUI, ('set', 'defaultFlowColor'),
                               self.display)
            dispatcher.connect(self.refreshGUI, ('set', 'defaultFlowSpread'),
                               self.display)
        else:
            self._flowToLabel.Show()
            self._flowToCheckBox.Show()
            self._sizer.Show(self._flowFromBoxSizer, True)
            for path in self.paths:
                for attributeName in [
                        'flowTo', 'flowToColor', 'flowToSpread', 'flowFrom',
                        'flowFromColor', 'flowFromSpread'
                ]:
                    dispatcher.connect(self.refreshGUI, ('set', attributeName),
                                       visible)

        self.refreshGUI()

    def reasonableSpacingOrSpeed(self):
        # Compute some reasonable spacing/speed based on the bounding box of the visibles in the display.
        self.display.computeVisiblesBound()
        return sqrt(self.display.visiblesSize[0]**2 +
                    self.display.visiblesSize[1]**2 +
                    self.display.visiblesSize[2]**2) / 20.0

    def refreshGUI(self, signal=('set', None), **arguments):
        updatedAttribute = signal[1]

        usingDefault = len(self.paths) == 0

        # The spacing and speed sliders are mapped logarithmically so very small and very large values can be used.
        # The scale is set so that a reasonable spacing and speed are used when the slider is at the midpoint.
        # Unfortunately what is "reasonable" is dependent on the bounding box of the whole display which can change at any time.
        expBase = e**(log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)

        if usingDefault:
            self._flowToBoxSizer.GetStaticBox().SetLabel(
                gettext('Default flow appearance'))
        else:
            if len(self.paths) == 1:
                (startPoint, endPoint) = self.paths[0].pathEndPoints()
                flowToName = gettext(
                    'anonymous object'
                ) if endPoint.client == None else endPoint.client.name or gettext(
                    '<unnamed %s>') % (endPoint.client.__class__.displayName())
                flowFromName = gettext(
                    'anonymous object'
                ) if startPoint.client == None else startPoint.client.name or gettext(
                    '<unnamed %s>') % (
                        startPoint.client.__class__.displayName())
            else:
                flowToName = gettext('multiple objects')
                flowFromName = gettext('multiple objects')
            self._flowToBoxSizer.GetStaticBox().SetLabel(
                gettext('Flow to %s') % (flowToName))
            self._flowFromBoxSizer.GetStaticBox().SetLabel(
                gettext('Flow to %s') % (flowFromName))

        if updatedAttribute is None or updatedAttribute == 'flowTo':
            if usingDefault:
                self._flowToCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
                self._flowToCheckBox.Disable()
            else:
                if self.paths.haveEqualAttr('flowTo'):
                    if self.paths[0].flowTo():
                        self._flowToCheckBox.Set3StateValue(wx.CHK_CHECKED)
                    else:
                        self._flowToCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
                else:
                    self._flowToCheckBox.Set3StateValue(wx.CHK_UNDETERMINED)
                self._flowToCheckBox.Enable()

        if updatedAttribute is None or updatedAttribute == 'flowToColor' or updatedAttribute == 'defaultFlowColor':
            if usingDefault or self.paths.haveEqualAttr('flowToColor'):
                if usingDefault:
                    red, green, blue, alpha = self.display.defaultFlowColor
                else:
                    red, green, blue, alpha = self.paths[0].flowToColor(
                    ) or self.display.defaultFlowColor
                self._flowToColorPicker.SetColour(
                    wx.Colour(red * 255, green * 255, blue * 255, alpha * 255))
                self._flowToColorPicker.SetLabel(gettext(''))
            else:
                self._flowToColorPicker.SetColour(wx.NamedColour(
                    'GRAY'))  # TODO: be clever and pick the average color?
                self._flowToColorPicker.SetLabel(gettext('Multiple values'))

        if updatedAttribute is None or updatedAttribute == 'flowToSpacing' or updatedAttribute == 'defaultFlowSpacing':
            if usingDefault or self.paths.haveEqualAttr('flowToSpacing'):
                if usingDefault:
                    spacing = self.display.defaultFlowSpacing
                else:
                    spacing = self.paths[0].flowToSpacing(
                    ) or self.display.defaultFlowSpacing
                self._flowToSpacingSlider.SetLabel('')
                self._flowToSpacingSlider.SetValue(log(spacing + 1.0, expBase))
            else:
                self._flowToSpacingSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpacingSlider.SetValue(0)

        if updatedAttribute is None or updatedAttribute == 'flowToSpeed' or updatedAttribute == 'defaultFlowSpeed':
            if usingDefault or self.paths.haveEqualAttr('flowToSpeed'):
                if usingDefault:
                    speed = self.display.defaultFlowSpeed
                else:
                    speed = self.paths[0].flowToSpeed(
                    ) or self.display.defaultFlowSpeed
                self._flowToSpeedSlider.SetLabel('')
                self._flowToSpeedSlider.SetValue(log(speed + 1.0, expBase))
            else:
                self._flowToSpeedSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpeedSlider.SetValue(0)

        if updatedAttribute is None or updatedAttribute == 'flowToSpread' or updatedAttribute == 'defaultFlowSpread':
            if usingDefault or self.paths.haveEqualAttr('flowToSpread'):
                if usingDefault:
                    spread = self.display.defaultFlowSpread
                else:
                    spread = self.paths[0].flowToSpread(
                    ) or self.display.defaultFlowSpread
                self._flowToSpreadSlider.SetLabel('')
                self._flowToSpreadSlider.SetValue(spread * 50.0)
            else:
                self._flowToSpreadSlider.SetLabel(gettext('Multiple values'))
                self._flowToSpreadSlider.SetValue(50)

        if (updatedAttribute is None
                or updatedAttribute == 'flowFrom') and not usingDefault:
            if self.paths.haveEqualAttr('flowFrom'):
                if self.paths[0].flowFrom():
                    self._flowFromCheckBox.Set3StateValue(wx.CHK_CHECKED)
                else:
                    self._flowFromCheckBox.Set3StateValue(wx.CHK_UNCHECKED)
            else:
                self._flowFromCheckBox.Set3StateValue(wx.CHK_UNDETERMINED)
            self._flowFromCheckBox.Enable()

        if (updatedAttribute is None
                or updatedAttribute == 'flowFromColor') and not usingDefault:
            if usingDefault or self.paths.haveEqualAttr('flowFromColor'):
                if usingDefault:
                    red, green, blue, alpha = self.display.defaultFlowColor
                else:
                    red, green, blue, alpha = self.paths[0].flowFromColor(
                    ) or self.display.defaultFlowColor
                self._flowFromColorPicker.SetColour(
                    wx.Colour(red * 255, green * 255, blue * 255, alpha * 255))
                self._flowFromColorPicker.SetLabel(gettext(''))
            else:
                self._flowFromColorPicker.SetColour(wx.NamedColour(
                    'GRAY'))  # TODO: be clever and pick the average color?
                self._flowFromColorPicker.SetLabel(gettext('Multiple values'))

        if updatedAttribute is None or updatedAttribute == 'flowFromSpacing' or updatedAttribute == 'defaultFlowSpacing':
            if usingDefault or self.paths.haveEqualAttr('flowFromSpacing'):
                if usingDefault:
                    spacing = self.display.defaultFlowSpacing
                else:
                    spacing = self.paths[0].flowFromSpacing(
                    ) or self.display.defaultFlowSpacing
                self._flowFromSpacingSlider.SetLabel('')
                self._flowFromSpacingSlider.SetValue(
                    log(spacing + 1.0, expBase))
            else:
                self._flowFromSpacingSlider.SetLabel(
                    gettext('Multiple values'))
                self._flowFromSpacingSlider.SetValue(0)

        if updatedAttribute is None or updatedAttribute == 'flowFromSpeed' or updatedAttribute == 'defaultFlowSpeed':
            if usingDefault or self.paths.haveEqualAttr('flowFromSpeed'):
                if usingDefault:
                    speed = self.display.defaultFlowSpeed
                else:
                    speed = self.paths[0].flowFromSpeed(
                    ) or self.display.defaultFlowSpeed
                self._flowFromSpeedSlider.SetLabel('')
                self._flowFromSpeedSlider.SetValue(log(speed + 1.0, expBase))
            else:
                self._flowFromSpeedSlider.SetLabel(gettext('Multiple values'))
                self._flowFromSpeedSlider.SetValue(0)

        if (updatedAttribute is None
                or updatedAttribute == 'flowFromSpread') and not usingDefault:
            if usingDefault or self.paths.haveEqualAttr('flowFromSpread'):
                if usingDefault:
                    spread = self.display.defaultFlowSpread
                else:
                    spread = self.paths[0].flowFromSpread(
                    ) or self.display.defaultFlowSpread
                self._flowFromSpreadSlider.SetLabel('')
                self._flowFromSpreadSlider.SetValue(spread * 50.0)
            else:
                self._flowFromSpreadSlider.SetLabel(gettext('Multiple values'))
                self._flowFromSpreadSlider.SetValue(50)

        self._window.Layout()

    def onSetAnimateFlowTo(self, event):
        newValue = self._flowToCheckBox.GetValue()
        for path in self.paths:
            path.setFlowTo(newValue == wx.CHK_CHECKED)

    def onSetFlowToColor(self, event):
        wxColor = self._flowToColorPicker.GetColour()
        newColor = (wxColor.Red() / 255.0, wxColor.Green() / 255.0,
                    wxColor.Blue() / 255.0, wxColor.Alpha() / 255.0)
        if len(self.paths) == 0:
            self.display.setDefaultFlowColor(newColor)
        else:
            for path in self.paths:
                path.setFlowToColor(newColor)

    def onSetFlowToSpacing(self, event):
        expBase = e**(log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpacing = expBase**self._flowToSpacingSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpacing(newSpacing)
        else:
            for path in self.paths:
                path.setFlowToSpacing(newSpacing)
        wx.GetApp().Yield()

    def onSetFlowToSpeed(self, event):
        expBase = e**(log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpeed = expBase**self._flowToSpeedSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpeed(newSpeed)
        else:
            for path in self.paths:
                path.setFlowToSpeed(newSpeed)
        wx.GetApp().Yield()

    def onSetFlowToSpread(self, event):
        newSpread = self._flowToSpreadSlider.GetValue() / 50.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpread(newSpread)
        else:
            for path in self.paths:
                path.setFlowToSpread(newSpread)

    def onSetAnimateFlowFrom(self, event):
        newValue = self._flowFromCheckBox.GetValue()
        for path in self.paths:
            path.setFlowFrom(newValue == wx.CHK_CHECKED)

    def onSetFlowFromColor(self, event):
        wxColor = self._flowFromColorPicker.GetColour()
        newColor = (wxColor.Red() / 255.0, wxColor.Green() / 255.0,
                    wxColor.Blue() / 255.0, wxColor.Alpha() / 255.0)
        for path in self.paths:
            path.setFlowFromColor(newColor)

    def onSetFlowFromSpacing(self, event):
        expBase = e**(log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpacing = expBase**self._flowFromSpacingSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpacing(newSpacing)
        else:
            for path in self.paths:
                path.setFlowFromSpacing(newSpacing)

    def onSetFlowFromSpeed(self, event):
        expBase = e**(log(self.reasonableSpacingOrSpeed() + 1.0) / 500.0)
        newSpeed = expBase**self._flowFromSpeedSlider.GetValue() - 1.0
        if len(self.paths) == 0:
            self.display.setDefaultFlowSpeed(newSpeed)
        else:
            for path in self.paths:
                path.setFlowFromSpeed(newSpeed)

    def onSetFlowFromSpread(self, event):
        newSpread = self._flowFromSpreadSlider.GetValue() / 50.0
        for path in self.paths:
            path.setFlowFromSpread(newSpread)
Exemple #4
0
class AppearanceInspector(Inspector):
    @classmethod
    def name(cls):
        return gettext('Appearance')

    @classmethod
    def canInspectDisplay(cls, display):
        return display and any(display.selection())

    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)
            gridSizer = wx.FlexGridSizer(5, 2, 8, 8)

            # Add a color picker for our fill color.
            self._colorPicker = wx.lib.colourselect.ColourSelect(
                self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT,
                              self.onSetColor, self._colorPicker)
            gridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            gridSizer.Add(self._colorPicker, 1)

            # Add a slider for setting the opacity.
            self._opacitySlider = wx.Slider(self._window, wx.ID_ANY, 100, 0,
                                            100)
            self._opacitySlider.Bind(wx.EVT_SCROLL, self.onSetOpacity,
                                     self._opacitySlider)
            gridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Opacity:')), 0)
            gridSizer.Add(self._opacitySlider, 1)

            # Add a pop-up for choosing the shape.
            self._shapeChoice = wx.Choice(self._window, wx.ID_ANY)
            shapeClasses = [(shapeClass.__name__, shapeClass)
                            for shapeClass in display.shape.shapeClasses()]
            for shapeName, shapeClass in sorted(shapeClasses):
                self._shapeChoice.Append(shapeName, shapeClass)
            self._shapeChoice.Append(gettext('None'), None)
            self._multipleShapesId = wx.NOT_FOUND
            gridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Shape:')), 0)
            gridSizer.Add(self._shapeChoice, 0)
            parentWindow.Bind(wx.EVT_CHOICE, self.onSetShape,
                              self._shapeChoice)

            # Add a pop-up for choosing the texture.
            self._textureChoice = wx.Choice(self._window, wx.ID_ANY)
            if hasattr(neuroptikon.library, 'texture'):
                for texture in neuroptikon.library.textures():
                    self._textureChoice.Append(gettext(texture.name), texture)
            self._textureChoice.Append(gettext('None'), None)
            self._multipleTexturesId = wx.NOT_FOUND
            gridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Texture:')), 0)
            gridSizer.Add(self._textureChoice, 0)
            parentWindow.Bind(wx.EVT_CHOICE, self.onSetTexture,
                              self._textureChoice)

            # Add a slider for setting the weight.
            self._weightSlider = wx.Slider(self._window, wx.ID_ANY, 50, 0, 100)
            self._weightSlider.Bind(wx.EVT_SCROLL, self.onSetWeight,
                                    self._weightSlider)
            gridSizer.Add(
                wx.StaticText(self._window, wx.ID_ANY, gettext('Weight:')), 0)
            gridSizer.Add(self._weightSlider, 1)

            # TODO: label, ???

            mainSizer = wx.BoxSizer(wx.VERTICAL)
            mainSizer.Add(gridSizer, 1, wx.ALL, 5)
            self._window.SetSizer(mainSizer)

        return self._window

    def inspectDisplay(self, display):
        self.visibles = display.selection()
        for visible in self.visibles:
            for attributeName in [
                    'color', 'opacity', 'shape', 'texture', 'weight'
            ]:
                dispatcher.connect(self.refreshGUI, ('set', attributeName),
                                   visible)
        self.refreshGUI()

    def willBeClosed(self):
        for visible in self.visibles:
            for attributeName in [
                    'color', 'opacity', 'shape', 'texture', 'weight'
            ]:
                dispatcher.disconnect(self.refreshGUI, ('set', attributeName),
                                      visible)
        self.visibles = ObjectList()

    def refreshGUI(self, signal=('set', None)):
        if any(self.visibles):
            updatedAttribute = signal[1]

            if updatedAttribute is None or updatedAttribute == 'color':
                if self.visibles.haveEqualAttr('color'):
                    red, green, blue = self.visibles[0].color()
                    self._colorPicker.SetColour(
                        wx.Colour(red * 255, green * 255, blue * 255, 255))
                    self._colorPicker.SetLabel(gettext(''))
                else:
                    self._colorPicker.SetColour(wx.NamedColour(
                        'GRAY'))  # TODO: be clever and pick the average color?
                    self._colorPicker.SetLabel(gettext('Multiple values'))

            if updatedAttribute is None or updatedAttribute == 'opacity':
                if self.visibles.haveEqualAttr('opacity'):
                    self._opacitySlider.SetLabel('')
                    self._opacitySlider.SetValue(self.visibles[0].opacity() *
                                                 100.0)
                else:
                    self._opacitySlider.SetLabel(gettext('Multiple values'))
                    self._opacitySlider.SetValue(100)

            if updatedAttribute is None or updatedAttribute == 'shape':
                shapeClass = type(self.visibles[0].shape())
                equalClasses = True
                for visible in self.visibles[1:]:
                    if type(visible) != shapeClass:
                        equalClasses = False
                if equalClasses:
                    for index in range(0, self._shapeChoice.GetCount()):
                        if self._shapeChoice.GetClientData(
                                index) == shapeClass:
                            self._shapeChoice.SetSelection(index)
                            break
                else:
                    if self._multipleShapesId == wx.NOT_FOUND:
                        self._multipleShapesId = self._shapeChoice.Append(
                            gettext('Multiple values'), None)
                    self._shapeChoice.SetSelection(self._multipleShapesId)

            if updatedAttribute is None or updatedAttribute == 'texture':
                if self.visibles.haveEqualAttr('texture'):
                    for index in range(0, self._textureChoice.GetCount()):
                        if self._textureChoice.GetClientData(
                                index) == self.visibles[0].texture():
                            self._textureChoice.SetSelection(index)
                            break
                else:
                    if self._multipleTexturesId == wx.NOT_FOUND:
                        self._multipleTexturesId = self._textureChoice.Append(
                            gettext('Multiple values'), None)
                    self._textureChoice.SetSelection(self._multipleTexturesId)

            if updatedAttribute is None or updatedAttribute == 'weight':
                if self.visibles.haveEqualAttr('weight'):
                    self._weightSlider.SetLabel('')
                    if self.visibles[0].weight() >= 1.0:
                        self._weightSlider.SetValue(
                            (self.visibles[0].weight() - 1.0) * 50.0 / 9.0 +
                            50.0)
                    else:
                        self._weightSlider.SetValue(
                            50.0 - (1.0 - self.visibles[0].weight()) * 10.0 *
                            50.0 / 9.0)
                else:
                    self._weightSlider.SetLabel(gettext('Multiple values'))
                    self._weightSlider.SetValue(50)

            self._window.Layout()

    def onSetColor(self, event_):
        wxColor = self._colorPicker.GetColour()
        colorTuple = (wxColor.Red() / 255.0, wxColor.Green() / 255.0,
                      wxColor.Blue() / 255.0)
        for visible in self.visibles:
            visible.setColor(colorTuple)

    def onSetOpacity(self, event_):
        newOpacity = self._opacitySlider.GetValue() / 100.0
        for visible in self.visibles:
            visible.setOpacity(newOpacity)

    def onSetShape(self, event_):
        shapeClass = self._shapeChoice.GetClientData(
            self._shapeChoice.GetSelection())
        for visible in self.visibles:
            visible.setShape(None if shapeClass is None else shapeClass())
        # Remove the "multiple values" choice now that all of the visibles have the same value.
        if self._multipleShapesId != wx.NOT_FOUND:
            self._shapeChoice.Delete(self._multipleShapesId)
            self._multipleShapesId = wx.NOT_FOUND

    def onSetTexture(self, event_):
        texture = self._textureChoice.GetClientData(
            self._textureChoice.GetSelection())
        for visible in self.visibles:
            visible.setTexture(texture)
            visible.setTextureScale(10.0)
        # Remove the "multiple values" choice now that all of the visibles have the same value.
        if self._multipleTexturesId != wx.NOT_FOUND:
            self._textureChoice.Delete(self._multipleTexturesId)
            self._multipleTexturesId = wx.NOT_FOUND

    def onSetWeight(self, event_):
        newWeight = self._weightSlider.GetValue()
        if newWeight >= 50:
            newWeight = 1.0 + (newWeight - 50.0) / (50.0 / 9.0)
        else:
            newWeight = 1.0 - (50.0 - newWeight) / (50.0 / 9.0) / 10.0
        for visible in self.visibles:
            visible.setWeight(newWeight)
class ObjectInspector(Inspector):
    @classmethod
    def name(cls):
        return cls.objectClass().displayName()

    @classmethod
    def shouldBeRegistered(cls):
        return cls != ObjectInspector.__class__

    @classmethod
    def objectClass(cls):
        return Object

    @classmethod
    def inspectedAttributes(cls):
        return []

    @classmethod
    def bitmap(cls, size=32):
        image = cls.objectClass().image()
        if image == None:
            return Inspector.bitmap(cls)
        else:
            scaledImage = image.Rescale(size, size, wx.IMAGE_QUALITY_HIGH)
            return wx.BitmapFromImage(scaledImage)

    @classmethod
    def canInspectDisplay(cls, display):
        # This inspector can be used if there is at least one object of this type selected.
        if display:
            for visible in display.selection():
                if visible.client.__class__ == cls.objectClass():
                    return True
        return False

    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)

            self.iconField = wx.StaticBitmap(self._window, wx.ID_ANY)
            self.iconField.SetMinSize(wx.Size(32, 32))
            self.iconField.SetMaxSize(wx.Size(32, 32))
            self.titleField = wx.StaticText(self._window, wx.ID_ANY, "")
            headerBox = wx.BoxSizer(wx.HORIZONTAL)
            headerBox.Add(self.iconField, 0, wx.EXPAND)
            headerBox.AddSpacer(8)
            headerBox.Add(self.titleField, 1, wx.EXPAND)

            objectSizer = self.objectSizer(self._window)

            mainSizer = wx.BoxSizer(wx.VERTICAL)
            mainSizer.Add(headerBox, 0, wx.EXPAND | wx.ALL, 5)
            if objectSizer is not None:
                mainSizer.Add(objectSizer, 1, wx.ALL, 5)
            self._window.SetSizer(mainSizer)

        return self._window

    def inspectDisplay(self, display):
        # Object inspectors are supposed to work purely at the network/biological layer so they don't need to know what the display is.
        self.display = display

        self.objects = ObjectList()
        self.updatingObjects = False
        for visible in display.selection():
            if visible.client.__class__ == self.__class__.objectClass():
                self.objects.append(visible.client)
                dispatcher.connect(self.nameChanged, ('set', 'name'),
                                   visible.client)
                for attributePath in self.inspectedAttributes():
                    if '.' in attributePath:
                        (subObject, attribute) = attributePath.split('.')
                        object = getattr(visible.client, subObject)
                    else:
                        object = visible.client
                        attribute = attributePath
                    dispatcher.connect(self.inspectedAttributeChanged,
                                       ('set', attribute), object)

        # Set the icon
        image = self.objects[0].__class__.image()
        if image == None:
            pass
        else:
            scaledImage = image.Rescale(32, 32, wx.IMAGE_QUALITY_HIGH)
            self.iconField.SetBitmap(wx.BitmapFromImage(scaledImage))

        self.populateNameField()
        self.populateObjectSizer()
        self._window.Layout()

    def objectSizer(self, parentWindow):
        return None

    def populateNameField(self):
        if self.objects.haveEqualAttr('name'):
            self.titleField.SetLabel(
                self.objects[0].name
                or ('<' + gettext('unnamed ') +
                    self.__class__.objectClass().displayName().lower() + '>'))
        else:
            self.titleField.SetLabel(gettext('Multiple names'))

    def populateObjectSizer(self, attribute=None):
        pass

    def nameChanged(self, signal, sender, event=None, value=None, **arguments):
        self.populateNameField()

    def inspectedAttributeChanged(self, signal, sender, **arguments):
        if not self.updatingObjects:
            self.populateObjectSizer(signal[1])

    def selectObject(self, object):
        self.display.selectObject(object)
class AppearanceInspector(Inspector):
    
    @classmethod
    def name(cls):
        return gettext('Appearance')
    
    
    @classmethod
    def canInspectDisplay(cls, display):
        return display and any(display.selection())


    def window(self, parentWindow=None):
        if not hasattr(self, '_window'):
            self._window = wx.Window(parentWindow, wx.ID_ANY)
            gridSizer = wx.FlexGridSizer(5, 2, 8, 8)
            
            # Add a color picker for our fill color.
            self._colorPicker = wx.lib.colourselect.ColourSelect(self._window, wx.ID_ANY)
            self._window.Bind(wx.lib.colourselect.EVT_COLOURSELECT, self.onSetColor, self._colorPicker)
            gridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Color:')), 0)
            gridSizer.Add(self._colorPicker, 1)
            
            # Add a slider for setting the opacity.
            self._opacitySlider = wx.Slider(self._window, wx.ID_ANY, 100, 0, 100)
            self._opacitySlider.Bind(wx.EVT_SCROLL, self.onSetOpacity, self._opacitySlider)
            gridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Opacity:')), 0)
            gridSizer.Add(self._opacitySlider, 1)
            
            # Add a pop-up for choosing the shape.
            self._shapeChoice = wx.Choice(self._window, wx.ID_ANY)
            shapeClasses = [(shapeClass.__name__, shapeClass) for shapeClass in display.shape.shapeClasses()]
            for shapeName, shapeClass in sorted(shapeClasses):
                self._shapeChoice.Append(shapeName, shapeClass)
            self._shapeChoice.Append(gettext('None'), None)
            self._multipleShapesId = wx.NOT_FOUND
            gridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Shape:')), 0)
            gridSizer.Add(self._shapeChoice, 0)
            parentWindow.Bind(wx.EVT_CHOICE, self.onSetShape, self._shapeChoice)
            
            # Add a pop-up for choosing the texture.
            self._textureChoice = wx.Choice(self._window, wx.ID_ANY)
            if hasattr(neuroptikon.library, 'texture'):
                for texture in neuroptikon.library.textures():
                    self._textureChoice.Append(gettext(texture.name), texture)
            self._textureChoice.Append(gettext('None'), None)
            self._multipleTexturesId = wx.NOT_FOUND
            gridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Texture:')), 0)
            gridSizer.Add(self._textureChoice, 0)
            parentWindow.Bind(wx.EVT_CHOICE, self.onSetTexture, self._textureChoice)
            
            # Add a slider for setting the weight.
            self._weightSlider = wx.Slider(self._window, wx.ID_ANY, 50, 0, 100)
            self._weightSlider.Bind(wx.EVT_SCROLL, self.onSetWeight, self._weightSlider)
            gridSizer.Add(wx.StaticText(self._window, wx.ID_ANY, gettext('Weight:')), 0)
            gridSizer.Add(self._weightSlider, 1)
            
            # TODO: label, ???
            
            mainSizer = wx.BoxSizer(wx.VERTICAL)
            mainSizer.Add(gridSizer, 1, wx.ALL, 5)
            self._window.SetSizer(mainSizer)
        
        return self._window
    
    
    def inspectDisplay(self, display):
        self.visibles = display.selection()
        for visible in self.visibles:
            for attributeName in ['color', 'opacity', 'shape', 'texture', 'weight']:
                dispatcher.connect(self.refreshGUI, ('set', attributeName), visible)
        self.refreshGUI()
    
    
    def willBeClosed(self):
        for visible in self.visibles:
            for attributeName in ['color', 'opacity', 'shape', 'texture', 'weight']:
                dispatcher.disconnect(self.refreshGUI, ('set', attributeName), visible)
        self.visibles = ObjectList()
    
    
    def refreshGUI(self, signal = ('set', None)):
        if any(self.visibles):
            updatedAttribute = signal[1]
            
            if updatedAttribute is None or updatedAttribute == 'color':
                if self.visibles.haveEqualAttr('color'):
                    red, green, blue = self.visibles[0].color()
                    self._colorPicker.SetColour(wx.Colour(red * 255, green * 255, blue * 255, 255))
                    self._colorPicker.SetLabel(gettext(''))
                else:
                    self._colorPicker.SetColour(wx.NamedColour('GRAY'))  # TODO: be clever and pick the average color?
                    self._colorPicker.SetLabel(gettext('Multiple values'))
            
            if updatedAttribute is None or updatedAttribute == 'opacity':
                if self.visibles.haveEqualAttr('opacity'):
                    self._opacitySlider.SetLabel('')
                    self._opacitySlider.SetValue(self.visibles[0].opacity() * 100.0)
                else:
                    self._opacitySlider.SetLabel(gettext('Multiple values'))
                    self._opacitySlider.SetValue(100)
            
            if updatedAttribute is None or updatedAttribute == 'shape':
                shapeClass = type(self.visibles[0].shape())
                equalClasses = True
                for visible in self.visibles[1:]:
                    if type(visible) != shapeClass:
                        equalClasses = False
                if equalClasses:
                    for index in range(0, self._shapeChoice.GetCount()):
                        if self._shapeChoice.GetClientData(index) == shapeClass:
                            self._shapeChoice.SetSelection(index)
                            break
                else:
                    if self._multipleShapesId == wx.NOT_FOUND:
                        self._multipleShapesId = self._shapeChoice.Append(gettext('Multiple values'), None)
                    self._shapeChoice.SetSelection(self._multipleShapesId)
            
            if updatedAttribute is None or updatedAttribute == 'texture':
                if self.visibles.haveEqualAttr('texture'):
                    for index in range(0, self._textureChoice.GetCount()):
                        if self._textureChoice.GetClientData(index) == self.visibles[0].texture():
                            self._textureChoice.SetSelection(index)
                            break
                else:
                    if self._multipleTexturesId == wx.NOT_FOUND:
                        self._multipleTexturesId = self._textureChoice.Append(gettext('Multiple values'), None)
                    self._textureChoice.SetSelection(self._multipleTexturesId)
            
            if updatedAttribute is None or updatedAttribute == 'weight':
                if self.visibles.haveEqualAttr('weight'):
                    self._weightSlider.SetLabel('')
                    if self.visibles[0].weight() >= 1.0:
                        self._weightSlider.SetValue((self.visibles[0].weight() - 1.0) * 50.0 / 9.0 + 50.0)
                    else:
                        self._weightSlider.SetValue(50.0 - (1.0 - self.visibles[0].weight()) * 10.0 * 50.0 / 9.0)
                else:
                    self._weightSlider.SetLabel(gettext('Multiple values'))
                    self._weightSlider.SetValue(50)
            
            self._window.Layout()
    
    
    def onSetColor(self, event_):
        wxColor = self._colorPicker.GetColour()
        colorTuple = (wxColor.Red() / 255.0, wxColor.Green() / 255.0, wxColor.Blue() / 255.0)
        for visible in self.visibles:
            visible.setColor(colorTuple)
        
    
    def onSetOpacity(self, event_):
        newOpacity = self._opacitySlider.GetValue() / 100.0
        for visible in self.visibles:
            visible.setOpacity(newOpacity)
        
    
    def onSetShape(self, event_):
        shapeClass = self._shapeChoice.GetClientData(self._shapeChoice.GetSelection())
        for visible in self.visibles:
            visible.setShape(None if shapeClass is None else shapeClass())
        # Remove the "multiple values" choice now that all of the visibles have the same value.
        if self._multipleShapesId != wx.NOT_FOUND:
            self._shapeChoice.Delete(self._multipleShapesId)
            self._multipleShapesId = wx.NOT_FOUND
        
    
    def onSetTexture(self, event_):
        texture = self._textureChoice.GetClientData(self._textureChoice.GetSelection())
        for visible in self.visibles:
            visible.setTexture(texture)
            visible.setTextureScale(10.0)
        # Remove the "multiple values" choice now that all of the visibles have the same value.
        if self._multipleTexturesId != wx.NOT_FOUND:
            self._textureChoice.Delete(self._multipleTexturesId)
            self._multipleTexturesId = wx.NOT_FOUND
        
    
    def onSetWeight(self, event_):
        newWeight = self._weightSlider.GetValue()
        if newWeight >= 50:
            newWeight = 1.0 + (newWeight - 50.0) / (50.0 / 9.0)
        else:
            newWeight = 1.0 - (50.0 - newWeight) / (50.0 / 9.0) / 10.0
        for visible in self.visibles:
            visible.setWeight(newWeight)