Repository: psychopy/psychopy

Issue:
Icons for components without a start time aren't greyed out when disabled
This is because the code to draw the greyed out icon is within the same if statement (psychopy -> app -> builder -> builder.py, line 2057 in dev) as the code to draw a line, which is only executed if a line is needed (i.e. if there is a start time)


Discussion:

Resolving patch:
diff --git a/psychopy/app/builder/builder.py b/psychopy/app/builder/builder.py
--- a/psychopy/app/builder/builder.py
+++ b/psychopy/app/builder/builder.py
@@ -2020,8 +2020,28 @@ def drawComponent(self, dc, component, yPos):
 
         iconYOffset = (6, 6, 0)[self.drawSize]
         icons = self.app.iconCache
-
+        # get default icon and bar color
         thisIcon = icons.getComponentBitmap(component, self.iconSize)
+        thisColor = ThemeMixin.appColors['rt_comp']
+        thisStyle = wx.BRUSHSTYLE_SOLID
+
+        # check True/False on ForceEndRoutine
+        if 'forceEndRoutine' in component.params:
+            if component.params['forceEndRoutine'].val:
+                thisColor = ThemeMixin.appColors['rt_comp_force']
+        # check True/False on ForceEndRoutineOnPress
+        if 'forceEndRoutineOnPress' in component.params:
+            if component.params['forceEndRoutineOnPress'].val:
+                thisColor = ThemeMixin.appColors['rt_comp_force']
+        # check True aliases on EndRoutineOn
+        if 'endRoutineOn' in component.params:
+            if component.params['endRoutineOn'].val in ['look at', 'look away']:
+                thisColor = ThemeMixin.appColors['rt_comp_force']
+        # grey bar if comp is disabled
+        if component.params['disabled'].val:
+            thisIcon = thisIcon.ConvertToDisabled()
+            thisColor = ThemeMixin.appColors['rt_comp_disabled']
+
         dc.DrawBitmap(thisIcon, self.iconXpos, yPos + iconYOffset, True)
         fullRect = wx.Rect(self.iconXpos, yPos,
                            thisIcon.GetWidth(), thisIcon.GetHeight())
@@ -2054,49 +2074,31 @@ def drawComponent(self, dc, component, yPos):
         # deduce start and stop times if possible
         startTime, duration, nonSlipSafe = component.getStartAndDuration()
         # draw entries on timeline (if they have some time definition)
-        if startTime is not None and duration is not None:
+        if duration is not None:
             # then we can draw a sensible time bar!
-            dc.SetPen(wx.Pen(ThemeMixin.appColors['rt_comp'],
-                             style=wx.TRANSPARENT))
-
-            if component.params['disabled'].val:
-                # Grey bar if comp is disabled
-                dc.SetBrush(wx.Brush(ThemeMixin.appColors['rt_comp_disabled']))
-                dc.DrawBitmap(thisIcon.ConvertToDisabled(), self.iconXpos, yPos + iconYOffset, True)
-            elif any(key in component.params for key in ['forceEndRoutine', 'forceEndRoutineOnPress', 'endRoutineOn']):
-                # if component has force end params, check them and set bar as orange or blue accordingly
-                col = ThemeMixin.appColors['rt_comp']
-                # check True/False on ForceEndRoutine
-                if 'forceEndRoutine' in component.params:
-                    if component.params['forceEndRoutine'].val:
-                        col = ThemeMixin.appColors['rt_comp_force']
-                # check True/False on ForceEndRoutineOnPress
-                if 'forceEndRoutineOnPress' in component.params:
-                    if component.params['forceEndRoutineOnPress'].val:
-                        col = ThemeMixin.appColors['rt_comp_force']
-                # check True aliases on EndRoutineOn
-                if 'endRoutineOn' in component.params:
-                    if component.params['endRoutineOn'].val in ['look at', 'look away']:
-                        col = ThemeMixin.appColors['rt_comp_force']
-                dc.SetBrush(wx.Brush(col))
-                dc.DrawBitmap(thisIcon, self.iconXpos, yPos + iconYOffset, True)
-            else:
-                # Blue bar otherwise
-                dc.SetBrush(wx.Brush(ThemeMixin.appColors['rt_comp']))
-                dc.DrawBitmap(thisIcon, self.iconXpos, yPos + iconYOffset, True)
-
-            xScale = self.getSecsPerPixel()
-            yOffset = (3.5, 3.5, 0.5)[self.drawSize]
-            h = self.componentStep // (4, 3.25, 2.5)[self.drawSize]
-            xSt = self.timeXposStart + startTime // xScale
-            w = duration // xScale + 1
-            if w > 10000:
-                w = 10000  # limit width to 10000 pixels!
-            if w < 2:
-                w = 2  # make sure at least one pixel shows
-            dc.DrawRectangle(xSt, y + yOffset, w, h)
-            # update bounds to include time bar
-            fullRect.Union(wx.Rect(xSt, y + yOffset, w, h))
+            thisPen = wx.Pen(thisColor, style=wx.TRANSPARENT)
+            thisBrush = wx.Brush(thisColor, style=thisStyle)
+            dc.SetPen(thisPen)
+            dc.SetBrush(thisBrush)
+            # If there's a fixed end time and no start time, start 20px before 0
+            if component.params['stopType'] == 'time (s)' and startTime is None:
+                startTime = -20 * self.getSecsPerPixel()
+                # thisBrush.SetStyle(wx.BRUSHSTYLE_BDIAGONAL_HATCH)
+                # dc.SetBrush(thisBrush)
+
+            if startTime is not None:
+                xScale = self.getSecsPerPixel()
+                yOffset = (3.5, 3.5, 0.5)[self.drawSize]
+                h = self.componentStep // (4, 3.25, 2.5)[self.drawSize]
+                xSt = self.timeXposStart + startTime // xScale
+                w = duration // xScale + 1
+                if w > 10000:
+                    w = 10000  # limit width to 10000 pixels!
+                if w < 2:
+                    w = 2  # make sure at least one pixel shows
+                dc.DrawRectangle(xSt, y + yOffset, w, h)
+                # update bounds to include time bar
+                fullRect.Union(wx.Rect(xSt, y + yOffset, w, h))
         dc.SetIdBounds(id, fullRect)
 
     def copyCompon(self, event=None, component=None):
diff --git a/psychopy/app/builder/dialogs/__init__.py b/psychopy/app/builder/dialogs/__init__.py
--- a/psychopy/app/builder/dialogs/__init__.py
+++ b/psychopy/app/builder/dialogs/__init__.py
@@ -431,6 +431,12 @@ def setVisible(self, visible=True):
             self.estimLabel.Show(visible)
         if hasattr(self, "label"):
             self.label.Show(visible)
+        # Set value to None if hidden (specific to start/stop)
+        if not visible:
+            if "startVal" in self.ctrls:
+                self.ctrls["startVal"].Value = None
+            if "stopVal" in self.ctrls:
+                self.ctrls["stopVal"].Value = None
         # Layout
         self.parent.Layout()
 
diff --git a/psychopy/experiment/components/_base.py b/psychopy/experiment/components/_base.py
--- a/psychopy/experiment/components/_base.py
+++ b/psychopy/experiment/components/_base.py
@@ -559,8 +559,8 @@ def getStartAndDuration(self):
         else:
             startTime = None
 
-        if stopType == 'time (s)' and numericStop and startTime is not None:
-            duration = float(self.params['stopVal'].val) - startTime
+        if stopType == 'time (s)' and numericStop:
+            duration = float(self.params['stopVal'].val) - (startTime or 0)
         elif stopType == 'duration (s)' and numericStop:
             duration = float(self.params['stopVal'].val)
         else:


Test patch: