コード例 #1
0
ファイル: flow.py プロジェクト: dantheman39/psychopy
class FlowPanel(wx.ScrolledWindow):
    def __init__(self, frame, id=-1):
        """A panel that shows how the routines will fit together
        """
        self.frame = frame
        self.app = frame.app
        self.dpi = self.app.dpi
        wx.ScrolledWindow.__init__(self,
                                   frame,
                                   id, (0, 0),
                                   size=wx.Size(8 * self.dpi, 3 * self.dpi),
                                   style=wx.HSCROLL | wx.VSCROLL)
        self.SetBackgroundColour(canvasColor)
        self.needUpdate = True
        self.maxWidth = 50 * self.dpi
        self.maxHeight = 2 * self.dpi
        self.mousePos = None
        # if we're adding a loop or routine then add spots to timeline
        # self.drawNearestRoutinePoint = True
        # self.drawNearestLoopPoint = False
        # lists the x-vals of points to draw, eg loop locations:
        self.pointsToDraw = []
        # for flowSize, showLoopInfoInFlow:
        self.appData = self.app.prefs.appData

        # self.SetAutoLayout(True)
        self.SetScrollRate(self.dpi / 4, self.dpi / 4)

        # create a PseudoDC to record our drawing
        self.pdc = PseudoDC()
        if wx.version() < "4":
            self.pdc.DrawRoundedRectangle = self.pdc.DrawRoundedRectangleRect
        self.pen_cache = {}
        self.brush_cache = {}
        # vars for handling mouse clicks
        self.hitradius = 5
        self.dragid = -1
        self.entryPointPosList = []
        self.entryPointIDlist = []
        self.gapsExcluded = []
        # mode can also be 'loopPoint1','loopPoint2','routinePoint'
        self.mode = 'normal'
        self.insertingRoutine = ""

        # for the context menu use the ID of the drawn icon to retrieve
        # the component (loop or routine)
        self.componentFromID = {}
        self.contextMenuLabels = {
            'remove': _translate('remove'),
            'rename': _translate('rename')
        }
        self.contextMenuItems = ['remove', 'rename']
        self.contextItemFromID = {}
        self.contextIDFromItem = {}
        for item in self.contextMenuItems:
            id = wx.NewId()
            self.contextItemFromID[id] = item
            self.contextIDFromItem[item] = id

        # self.btnInsertRoutine = wx.Button(self,-1,
        #                                  'Insert Routine', pos=(10,10))
        # self.btnInsertLoop = wx.Button(self,-1,'Insert Loop', pos=(10,30))
        labelRoutine = _translate('Insert Routine ')
        labelLoop = _translate('Insert Loop     ')
        self.btnInsertRoutine = platebtn.PlateButton(self,
                                                     -1,
                                                     labelRoutine,
                                                     pos=(10, 10))
        self.btnInsertLoop = platebtn.PlateButton(
            self, -1, labelLoop, pos=(10, 30))  # spaces give size for CANCEL

        self.btnInsertRoutine.SetBackgroundColour(canvasColor)
        self.btnInsertLoop.SetBackgroundColour(canvasColor)

        self.labelTextRed = {
            'normal': wx.Colour(250, 10, 10, 250),
            'hlight': wx.Colour(250, 10, 10, 250)
        }
        self.labelTextBlack = {
            'normal': wx.Colour(0, 0, 0, 250),
            'hlight': wx.Colour(250, 250, 250, 250)
        }

        # use self.appData['flowSize'] to index a tuple to get a specific
        # value, eg: (4,6,8)[self.appData['flowSize']]
        self.flowMaxSize = 2  # upper limit on increaseSize

        self.draw()

        # bind events
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
        self.Bind(wx.EVT_BUTTON, self.onInsertRoutine, self.btnInsertRoutine)
        self.Bind(wx.EVT_BUTTON, self.setLoopPoint1, self.btnInsertLoop)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        idClear = wx.NewId()
        self.Bind(wx.EVT_MENU, self.clearMode, id=idClear)
        aTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, idClear)
                                      ])
        self.SetAcceleratorTable(aTable)

    def clearMode(self, event=None):
        """If we were in middle of doing something (like inserting routine)
        then end it, allowing user to cancel
        """
        self.mode = 'normal'
        self.insertingRoutine = None
        for id in self.entryPointIDlist:
            self.pdc.RemoveId(id)
        self.entryPointPosList = []
        self.entryPointIDlist = []
        self.gapsExcluded = []
        self.draw()
        self.frame.SetStatusText("")
        self.btnInsertRoutine.SetLabel(_translate('Insert Routine'))
        self.btnInsertLoop.SetLabel(_translate('Insert Loop'))
        self.btnInsertRoutine.SetLabelColor(**self.labelTextBlack)
        self.btnInsertLoop.SetLabelColor(**self.labelTextBlack)

    def ConvertEventCoords(self, event):
        xView, yView = self.GetViewStart()
        xDelta, yDelta = self.GetScrollPixelsPerUnit()
        return (event.GetX() + (xView * xDelta),
                event.GetY() + (yView * yDelta))

    def OffsetRect(self, r):
        """Offset the rectangle, r, to appear in the given position
        in the window
        """
        xView, yView = self.GetViewStart()
        xDelta, yDelta = self.GetScrollPixelsPerUnit()
        r.Offset((-(xView * xDelta), -(yView * yDelta)))

    def onInsertRoutine(self, evt):
        """For when the insert Routine button is pressed - bring up
        dialog and present insertion point on flow line.
        see self.insertRoutine() for further info
        """
        if self.mode.startswith('loopPoint'):
            self.clearMode()
        elif self.mode == 'routine':
            # clicked again with label now being "Cancel..."
            self.clearMode()
            return
        self.frame.SetStatusText(
            _translate("Select a Routine to insert (Esc to exit)"))
        menu = wx.Menu()
        self.routinesFromID = {}
        id = wx.NewId()
        menu.Append(id, '(new)')
        self.routinesFromID[id] = '(new)'
        menu.Bind(wx.EVT_MENU, self.insertNewRoutine, id=id)
        for routine in self.frame.exp.routines:
            id = wx.NewId()
            menu.Append(id, routine)
            self.routinesFromID[id] = routine
            menu.Bind(wx.EVT_MENU, self.onInsertRoutineSelect, id=id)
        btnPos = self.btnInsertRoutine.GetRect()
        menuPos = (btnPos[0], btnPos[1] + btnPos[3])
        self.PopupMenu(menu, menuPos)
        menu.Bind(wx.EVT_MENU_CLOSE, self.clearMode)
        menu.Destroy()  # destroy to avoid mem leak

    def insertNewRoutine(self, event):
        """selecting (new) is a short-cut for:
        make new routine, insert it into the flow
        """
        newRoutine = self.frame.routinePanel.createNewRoutine(returnName=True)
        if newRoutine:
            self.routinesFromID[event.GetId()] = newRoutine
            self.onInsertRoutineSelect(event)
        else:
            self.clearMode()

    def onInsertRoutineSelect(self, event):
        """User has selected a routine to be entered so bring up the
        entrypoint marker and await mouse button press.
        see self.insertRoutine() for further info
        """
        self.mode = 'routine'
        self.btnInsertRoutine.SetLabel(_translate('CANCEL Insert'))
        self.btnInsertRoutine.SetLabelColor(**self.labelTextRed)
        self.frame.SetStatusText(
            _translate(
                'Click where you want to insert the Routine, or CANCEL insert.'
            ))
        self.insertingRoutine = self.routinesFromID[event.GetId()]
        x = self.getNearestGapPoint(0)
        self.drawEntryPoints([x])

    def insertRoutine(self, ii):
        """Insert a routine into the Flow knowing its name and location

        onInsertRoutine() the button has been pressed so present menu
        onInsertRoutineSelect() user selected the name so present entry points
        OnMouse() user has selected a point on the timeline to insert entry

        """
        rtn = self.frame.exp.routines[self.insertingRoutine]
        self.frame.exp.flow.addRoutine(rtn, ii)
        self.frame.addToUndoStack("ADD Routine `%s`" % rtn.name)
        # reset flow drawing (remove entry point)
        self.clearMode()

    def setLoopPoint1(self, evt=None):
        """Someone pushed the insert loop button.
        Fetch the dialog
        """
        if self.mode == 'routine':
            self.clearMode()
        # clicked again, label is "Cancel..."
        elif self.mode.startswith('loopPoint'):
            self.clearMode()
            return
        self.btnInsertLoop.SetLabel(_translate('CANCEL insert'))
        self.btnInsertLoop.SetLabelColor(**self.labelTextRed)
        self.mode = 'loopPoint1'
        self.frame.SetStatusText(
            _translate(
                'Click where you want the loop to start/end, or CANCEL insert.'
            ))
        x = self.getNearestGapPoint(0)
        self.drawEntryPoints([x])

    def setLoopPoint2(self, evt=None):
        """We have the location of the first point, waiting to get the second
        """
        self.mode = 'loopPoint2'
        self.frame.SetStatusText(
            _translate('Click the other end for the loop'))
        thisPos = self.entryPointPosList[0]
        self.gapsExcluded = [thisPos]
        self.gapsExcluded.extend(self.getGapPointsCrossingStreams(thisPos))
        # is there more than one available point
        diff = wx.GetMousePosition()[0] - self.GetScreenPosition()[0]
        x = self.getNearestGapPoint(diff, exclude=self.gapsExcluded)
        self.drawEntryPoints([self.entryPointPosList[0], x])
        nAvailableGaps = len(self.gapMidPoints) - len(self.gapsExcluded)
        if nAvailableGaps == 1:
            self.insertLoop()  # there's only one place - use it

    def insertLoop(self, evt=None):
        # bring up listbox to choose the routine to add, and / or a new one
        loopDlg = DlgLoopProperties(frame=self.frame,
                                    helpUrl=self.app.urls['builder.loops'])
        startII = self.gapMidPoints.index(min(self.entryPointPosList))
        endII = self.gapMidPoints.index(max(self.entryPointPosList))
        if loopDlg.OK:
            handler = loopDlg.currentHandler
            self.frame.exp.flow.addLoop(handler,
                                        startPos=startII,
                                        endPos=endII)
            action = "ADD Loop `%s` to Flow" % handler.params['name'].val
            self.frame.addToUndoStack(action)
        self.clearMode()
        self.draw()

    def increaseSize(self, event=None):
        if self.appData['flowSize'] == self.flowMaxSize:
            self.appData['showLoopInfoInFlow'] = True
        self.appData['flowSize'] = min(self.flowMaxSize,
                                       self.appData['flowSize'] + 1)
        self.clearMode()  # redraws

    def decreaseSize(self, event=None):
        if self.appData['flowSize'] == 0:
            self.appData['showLoopInfoInFlow'] = False
        self.appData['flowSize'] = max(0, self.appData['flowSize'] - 1)
        self.clearMode()  # redraws

    def editLoopProperties(self, event=None, loop=None):
        # add routine points to the timeline
        self.setDrawPoints('loops')
        self.draw()
        if 'conditions' in loop.params:
            condOrig = loop.params['conditions'].val
            condFileOrig = loop.params['conditionsFile'].val
        title = loop.params['name'].val + ' Properties'
        loopDlg = DlgLoopProperties(frame=self.frame,
                                    helpUrl=self.app.urls['builder.loops'],
                                    title=title,
                                    loop=loop)
        if loopDlg.OK:
            prevLoop = loop
            if loopDlg.params['loopType'].val == 'staircase':
                loop = loopDlg.stairHandler
            elif loopDlg.params['loopType'].val == 'interleaved staircases':
                loop = loopDlg.multiStairHandler
            else:
                # ['random','sequential', 'fullRandom', ]
                loop = loopDlg.trialHandler
            # if the loop is a whole new class then we can't just update the
            # params
            if loop.getType() != prevLoop.getType():
                # get indices for start and stop points of prev loop
                flow = self.frame.exp.flow
                # find the index of the initiator
                startII = flow.index(prevLoop.initiator)
                # minus one because initiator will have been deleted
                endII = flow.index(prevLoop.terminator) - 1
                # remove old loop completely
                flow.removeComponent(prevLoop)
                # finally insert the new loop
                flow.addLoop(loop, startII, endII)
            self.frame.addToUndoStack("EDIT Loop `%s`" %
                                      (loop.params['name'].val))
        elif 'conditions' in loop.params:
            loop.params['conditions'].val = condOrig
            loop.params['conditionsFile'].val = condFileOrig
        # remove the points from the timeline
        self.setDrawPoints(None)
        self.draw()

    def OnMouse(self, event):
        x, y = self.ConvertEventCoords(event)
        handlerTypes = ('StairHandler', 'TrialHandler', 'MultiStairHandler')
        if self.mode == 'normal':
            if event.LeftDown():
                icons = self.pdc.FindObjectsByBBox(x, y)
                for thisIcon in icons:
                    # might intersect several and only one has a callback
                    if thisIcon in self.componentFromID:
                        comp = self.componentFromID[thisIcon]
                        if comp.getType() in handlerTypes:
                            self.editLoopProperties(loop=comp)
                        if comp.getType() == 'Routine':
                            self.frame.routinePanel.setCurrentRoutine(
                                routine=comp)
            elif event.RightDown():
                icons = self.pdc.FindObjectsByBBox(x, y)
                # todo: clean-up remove `comp`, its unused
                comp = None
                for thisIcon in icons:
                    # might intersect several and only one has a callback
                    if thisIcon in self.componentFromID:
                        # loop through comps looking for Routine, or a Loop if
                        # no routine
                        thisComp = self.componentFromID[thisIcon]
                        if thisComp.getType() in handlerTypes:
                            comp = thisComp  # unused
                            icon = thisIcon
                        if thisComp.getType() == 'Routine':
                            comp = thisComp
                            icon = thisIcon
                            break  # we've found a Routine so stop looking
                try:
                    self._menuComponentID = icon
                    xy = wx.Point(x + self.GetPosition()[0],
                                  y + self.GetPosition()[1])
                    self.showContextMenu(self._menuComponentID, xy=xy)
                except UnboundLocalError:
                    # right click but not on an icon
                    # might as well do something
                    self.Refresh()
        elif self.mode == 'routine':
            if event.LeftDown():
                pt = self.entryPointPosList[0]
                self.insertRoutine(ii=self.gapMidPoints.index(pt))
            else:  # move spot if needed
                point = self.getNearestGapPoint(mouseX=x)
                self.drawEntryPoints([point])
        elif self.mode == 'loopPoint1':
            if event.LeftDown():
                self.setLoopPoint2()
            else:  # move spot if needed
                point = self.getNearestGapPoint(mouseX=x)
                self.drawEntryPoints([point])
        elif self.mode == 'loopPoint2':
            if event.LeftDown():
                self.insertLoop()
            else:  # move spot if needed
                point = self.getNearestGapPoint(mouseX=x,
                                                exclude=self.gapsExcluded)
                self.drawEntryPoints([self.entryPointPosList[0], point])

    def getNearestGapPoint(self, mouseX, exclude=()):
        """Get gap that is nearest to a particular mouse location
        """
        d = 1000000000
        nearest = None
        for point in self.gapMidPoints:
            if point in exclude:
                continue
            if (point - mouseX)**2 < d:
                d = (point - mouseX)**2
                nearest = point
        return nearest

    def getGapPointsCrossingStreams(self, gapPoint):
        """For a given gap point, identify the gap points that are
        excluded by crossing a loop line
        """
        gapArray = numpy.array(self.gapMidPoints)
        nestLevels = numpy.array(self.gapNestLevels)
        thisLevel = nestLevels[gapArray == gapPoint]
        invalidGaps = (gapArray[nestLevels != thisLevel]).tolist()
        return invalidGaps

    def showContextMenu(self, component, xy):
        menu = wx.Menu()
        # get ID
        # the ID is also the index to the element in the flow list
        compID = self._menuComponentID
        flow = self.frame.exp.flow
        component = flow[compID]
        compType = component.getType()
        if compType == 'Routine':
            for item in self.contextMenuItems:
                id = self.contextIDFromItem[item]
                menu.Append(id, self.contextMenuLabels[item])
                menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
            self.frame.PopupMenu(menu, xy)
            # destroy to avoid mem leak:
            menu.Destroy()
        else:
            for item in self.contextMenuItems:
                if item == 'rename':
                    continue
                id = self.contextIDFromItem[item]
                menu.Append(id, self.contextMenuLabels[item])
                menu.Bind(wx.EVT_MENU, self.onContextSelect, id=id)
            self.frame.PopupMenu(menu, xy)
            # destroy to avoid mem leak:
            menu.Destroy()

    def onContextSelect(self, event):
        """Perform a given action on the component chosen
        """
        # get ID
        op = self.contextItemFromID[event.GetId()]
        # the ID is also the index to the element in the flow list
        compID = self._menuComponentID
        flow = self.frame.exp.flow
        component = flow[compID]
        # if we have a Loop Initiator, remove the whole loop
        if component.getType() == 'LoopInitiator':
            component = component.loop
        if op == 'remove':
            self.removeComponent(component, compID)
            self.frame.addToUndoStack("REMOVE `%s` from Flow" %
                                      component.params['name'])
        if op == 'rename':
            self.frame.renameRoutine(component)

    def removeComponent(self, component, compID):
        """Remove either a Routine or a Loop from the Flow
        """
        flow = self.frame.exp.flow
        if component.getType() == 'Routine':
            # check whether this will cause a collapsed loop
            # prev and next elements on flow are a loop init/end
            prevIsLoop = nextIsLoop = False
            if compID > 0:  # there is at least one preceding
                prevIsLoop = (flow[compID - 1]).getType() == 'LoopInitiator'
            if len(flow) > (compID + 1):  # there is at least one more compon
                nextIsLoop = (flow[compID + 1]).getType() == 'LoopTerminator'
            if prevIsLoop and nextIsLoop:
                # because flow[compID+1] is a terminator
                loop = flow[compID + 1].loop
                msg = _translate('The "%s" Loop is about to be deleted as '
                                 'well (by collapsing). OK to proceed?')
                title = _translate('Impending Loop collapse')
                warnDlg = dialogs.MessageDialog(parent=self.frame,
                                                message=msg %
                                                loop.params['name'],
                                                type='Warning',
                                                title=title)
                resp = warnDlg.ShowModal()
                if resp in [wx.ID_CANCEL, wx.ID_NO]:
                    return  # abort
                elif resp == wx.ID_YES:
                    # make recursive calls to this same method until success
                    # remove the loop first
                    self.removeComponent(loop, compID)
                    # because the loop has been removed ID is now one less
                    self.removeComponent(component, compID - 1)
                    return  # have done the removal in final successful call
        # remove name from namespace only if it's a loop;
        # loops exist only in the flow
        elif 'conditionsFile' in component.params:
            conditionsFile = component.params['conditionsFile'].val
            if conditionsFile and conditionsFile not in ['None', '']:
                try:
                    trialList, fieldNames = data.importConditions(
                        conditionsFile, returnFieldNames=True)
                    for fname in fieldNames:
                        self.frame.exp.namespace.remove(fname)
                except Exception:
                    msg = ("Conditions file %s couldn't be found so names not"
                           " removed from namespace")
                    logging.debug(msg % conditionsFile)
            self.frame.exp.namespace.remove(component.params['name'].val)
        # perform the actual removal
        flow.removeComponent(component, id=compID)
        self.draw()

    def OnPaint(self, event):
        # Create a buffered paint DC.  It will create the real
        # wx.PaintDC and then blit the bitmap to it when dc is
        # deleted.
        dc = wx.GCDC(wx.BufferedPaintDC(self))
        # use PrepareDC to set position correctly
        self.PrepareDC(dc)
        # we need to clear the dc BEFORE calling PrepareDC
        bg = wx.Brush(self.GetBackgroundColour())
        dc.SetBackground(bg)
        dc.Clear()
        # create a clipping rect from our position and size
        # and the Update Region
        xv, yv = self.GetViewStart()
        dx, dy = self.GetScrollPixelsPerUnit()
        x, y = (xv * dx, yv * dy)
        rgn = self.GetUpdateRegion()
        rgn.Offset(x, y)
        r = rgn.GetBox()
        # draw to the dc using the calculated clipping rect
        self.pdc.DrawToDCClipped(dc, r)

    def draw(self, evt=None):
        """This is the main function for drawing the Flow panel.
        It should be called whenever something changes in the exp.

        This then makes calls to other drawing functions,
        like drawEntryPoints...
        """
        if not hasattr(self.frame, 'exp'):
            # we haven't yet added an exp
            return
        # retrieve the current flow from the experiment
        expFlow = self.frame.exp.flow
        pdc = self.pdc

        # use the ID of the drawn icon to retrieve component (loop or routine)
        self.componentFromID = {}

        pdc.Clear()  # clear the screen
        pdc.RemoveAll()  # clear all objects (icon buttons)

        font = self.GetFont()

        # draw the main time line
        self.linePos = (2.5 * self.dpi, 0.5 * self.dpi)  # x,y of start
        gap = self.dpi / (6, 4, 2)[self.appData['flowSize']]
        dLoopToBaseLine = (15, 25, 43)[self.appData['flowSize']]
        dBetweenLoops = (20, 24, 30)[self.appData['flowSize']]

        # guess virtual size; nRoutines wide by nLoops high
        # make bigger than needed and shrink later
        nRoutines = len(expFlow)
        nLoops = 0
        for entry in expFlow:
            if entry.getType() == 'LoopInitiator':
                nLoops += 1
        sizeX = nRoutines * self.dpi * 2
        sizeY = nLoops * dBetweenLoops + dLoopToBaseLine * 3
        self.SetVirtualSize(size=(sizeX, sizeY))

        # step through components in flow, get spacing from text size, etc
        currX = self.linePos[0]
        lineId = wx.NewId()
        pdc.DrawLine(x1=self.linePos[0] - gap,
                     y1=self.linePos[1],
                     x2=self.linePos[0],
                     y2=self.linePos[1])
        # NB the loop is itself the key, value is further info about it
        self.loops = {}
        nestLevel = 0
        maxNestLevel = 0
        self.gapMidPoints = [currX - gap / 2]
        self.gapNestLevels = [0]
        for ii, entry in enumerate(expFlow):
            if entry.getType() == 'LoopInitiator':
                # NB the loop is itself the dict key!?
                self.loops[entry.loop] = {
                    'init': currX,
                    'nest': nestLevel,
                    'id': ii
                }
                nestLevel += 1  # start of loop so increment level of nesting
                maxNestLevel = max(nestLevel, maxNestLevel)
            elif entry.getType() == 'LoopTerminator':
                # NB the loop is itself the dict key!
                self.loops[entry.loop]['term'] = currX
                nestLevel -= 1  # end of loop so decrement level of nesting
            elif entry.getType() == 'Routine':
                # just get currX based on text size, don't draw anything yet:
                currX = self.drawFlowRoutine(pdc,
                                             entry,
                                             id=ii,
                                             pos=[currX, self.linePos[1] - 10],
                                             draw=False)
            self.gapMidPoints.append(currX + gap / 2)
            self.gapNestLevels.append(nestLevel)
            pdc.SetId(lineId)
            pdc.SetPen(wx.Pen(wx.Colour(0, 0, 0, 255)))
            pdc.DrawLine(x1=currX,
                         y1=self.linePos[1],
                         x2=currX + gap,
                         y2=self.linePos[1])
            currX += gap
        lineRect = wx.Rect(self.linePos[0] - 2, self.linePos[1] - 2,
                           currX - self.linePos[0] + 2, 4)
        pdc.SetIdBounds(lineId, lineRect)

        # draw the loops first:
        maxHeight = 0
        for thisLoop in self.loops:
            thisInit = self.loops[thisLoop]['init']
            thisTerm = self.loops[thisLoop]['term']
            thisNest = maxNestLevel - self.loops[thisLoop]['nest'] - 1
            thisId = self.loops[thisLoop]['id']
            height = (self.linePos[1] + dLoopToBaseLine +
                      thisNest * dBetweenLoops)
            self.drawLoop(pdc,
                          thisLoop,
                          id=thisId,
                          startX=thisInit,
                          endX=thisTerm,
                          base=self.linePos[1],
                          height=height)
            self.drawLoopStart(pdc, pos=[thisInit, self.linePos[1]])
            self.drawLoopEnd(pdc, pos=[thisTerm, self.linePos[1]])
            if height > maxHeight:
                maxHeight = height

        # draw routines second (over loop lines):
        currX = self.linePos[0]
        for ii, entry in enumerate(expFlow):
            if entry.getType() == 'Routine':
                currX = self.drawFlowRoutine(pdc,
                                             entry,
                                             id=ii,
                                             pos=[currX, self.linePos[1] - 10])
            pdc.SetPen(wx.Pen(wx.Colour(0, 0, 0, 255)))
            pdc.DrawLine(x1=currX,
                         y1=self.linePos[1],
                         x2=currX + gap,
                         y2=self.linePos[1])
            currX += gap

        self.SetVirtualSize(size=(currX + 100, maxHeight + 50))

        self.drawLineStart(pdc, (self.linePos[0] - gap, self.linePos[1]))
        self.drawLineEnd(pdc, (currX, self.linePos[1]))

        # refresh the visible window after drawing (using OnPaint)
        self.Refresh()

    def drawEntryPoints(self, posList):
        ptSize = (3, 4, 5)[self.appData['flowSize']]
        for n, pos in enumerate(posList):
            if n >= len(self.entryPointPosList):
                # draw for first time
                id = wx.NewId()
                self.entryPointIDlist.append(id)
                self.pdc.SetId(id)
                self.pdc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 255)))
                self.pdc.DrawCircle(pos, self.linePos[1], ptSize)
                r = self.pdc.GetIdBounds(id)
                self.OffsetRect(r)
                self.RefreshRect(r, False)
            elif pos == self.entryPointPosList[n]:
                pass  # nothing to see here, move along please :-)
            else:
                # move to new position
                dx = pos - self.entryPointPosList[n]
                dy = 0
                r = self.pdc.GetIdBounds(self.entryPointIDlist[n])
                self.pdc.TranslateId(self.entryPointIDlist[n], dx, dy)
                r2 = self.pdc.GetIdBounds(self.entryPointIDlist[n])
                # combine old and new locations to get redraw area
                rectToRedraw = r.Union(r2)
                rectToRedraw.Inflate(4, 4)
                self.OffsetRect(rectToRedraw)
                self.RefreshRect(rectToRedraw, False)

        self.entryPointPosList = posList
        # refresh the visible window after drawing (using OnPaint)
        self.Refresh()

    def setDrawPoints(self, ptType, startPoint=None):
        """Set the points of 'routines', 'loops', or None
        """
        if ptType == 'routines':
            self.pointsToDraw = self.gapMidPoints
        elif ptType == 'loops':
            self.pointsToDraw = self.gapMidPoints
        else:
            self.pointsToDraw = []

    def drawLineStart(self, dc, pos):
        # draw bar at start of timeline; circle looked bad, offset vertically
        ptSize = (3, 3, 4)[self.appData['flowSize']]
        dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 255)))
        dc.SetPen(wx.Pen(wx.Colour(0, 0, 0, 255)))
        dc.DrawPolygon([[0, -ptSize], [1, -ptSize], [1, ptSize], [0, ptSize]],
                       pos[0], pos[1])

    def drawLineEnd(self, dc, pos):
        # draws arrow at end of timeline
        # tmpId = wx.NewId()
        # dc.SetId(tmpId)
        dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 255)))
        dc.SetPen(wx.Pen(wx.Colour(0, 0, 0, 255)))
        dc.DrawPolygon([[0, -3], [5, 0], [0, 3]], pos[0], pos[1])
        # dc.SetIdBounds(tmpId,wx.Rect(pos[0],pos[1]+3,5,6))

    def drawLoopEnd(self, dc, pos, downwards=True):
        # define the right side of a loop but draw nothing
        # idea: might want an ID for grabbing and relocating the loop endpoint
        tmpId = wx.NewId()
        dc.SetId(tmpId)
        # dc.SetBrush(wx.Brush(wx.Colour(0,0,0, 250)))
        # dc.SetPen(wx.Pen(wx.Colour(0,0,0, 255)))
        size = (3, 4, 5)[self.appData['flowSize']]
        # if downwards:
        #   dc.DrawPolygon([[size, 0], [0, size], [-size, 0]],
        #                  pos[0], pos[1] + 2 * size)  # points down
        # else:
        #   dc.DrawPolygon([[size, size], [0, 0], [-size, size]],
        #   pos[0], pos[1]-3*size)  # points up
        dc.SetIdBounds(
            tmpId, wx.Rect(pos[0] - size, pos[1] - size, 2 * size, 2 * size))
        return

    def drawLoopStart(self, dc, pos, downwards=True):
        # draws direction arrow on left side of a loop
        tmpId = wx.NewId()
        dc.SetId(tmpId)
        dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 250)))
        dc.SetPen(wx.Pen(wx.Colour(0, 0, 0, 255)))
        size = (3, 4, 5)[self.appData['flowSize']]
        offset = (3, 2, 0)[self.appData['flowSize']]
        if downwards:
            dc.DrawPolygon([[size, size], [0, 0], [-size, size]], pos[0],
                           pos[1] + 3 * size - offset)  # points up
        else:
            dc.DrawPolygon([[size, 0], [0, size], [-size, 0]], pos[0],
                           pos[1] - 4 * size)  # points down
        dc.SetIdBounds(
            tmpId, wx.Rect(pos[0] - size, pos[1] - size, 2 * size, 2 * size))

    def drawFlowRoutine(self, dc, routine, id, pos=[0, 0], draw=True):
        """Draw a box to show a routine on the timeline
        draw=False is for a dry-run, esp to compute and return size
        without drawing or setting a pdc ID
        """
        name = routine.name
        if self.appData['flowSize'] == 0 and len(name) > 5:
            name = ' ' + name[:4] + '..'
        else:
            name = ' ' + name + ' '
        if draw:
            dc.SetId(id)
        font = self.GetFont()
        if sys.platform == 'darwin':
            fontSizeDelta = (9, 6, 0)[self.appData['flowSize']]
            font.SetPointSize(1400 / self.dpi - fontSizeDelta)
        elif sys.platform.startswith('linux'):
            fontSizeDelta = (6, 4, 0)[self.appData['flowSize']]
            font.SetPointSize(1400 / self.dpi - fontSizeDelta)
        else:
            fontSizeDelta = (8, 4, 0)[self.appData['flowSize']]
            font.SetPointSize(1000 / self.dpi - fontSizeDelta)

        maxTime, nonSlip = routine.getMaxTime()
        if nonSlip:
            rgbFill = nonSlipFill
            rgbEdge = nonSlipEdge
        else:
            rgbFill = relTimeFill
            rgbEdge = relTimeEdge

        # get size based on text
        self.SetFont(font)
        if draw:
            dc.SetFont(font)
        w, h = self.GetFullTextExtent(name)[0:2]
        pad = (5, 10, 20)[self.appData['flowSize']]
        # draw box
        pos[1] += 2 - self.appData['flowSize']
        rect = wx.Rect(pos[0], pos[1], w + pad, h + pad)
        endX = pos[0] + w + pad
        # the edge should match the text
        if draw:
            dc.SetPen(
                wx.Pen(
                    wx.Colour(rgbEdge[0], rgbEdge[1], rgbEdge[2],
                              wx.ALPHA_OPAQUE)))
            dc.SetBrush(wx.Brush(rgbFill))
            dc.DrawRoundedRectangle(rect, (4, 6, 8)[self.appData['flowSize']])
            # draw text
            dc.SetTextForeground(rgbEdge)
            dc.DrawLabel(name, rect, alignment=wx.ALIGN_CENTRE)
            if nonSlip and self.appData['flowSize'] != 0:
                font.SetPointSize(font.GetPointSize() * 0.6)
                dc.SetFont(font)
                _align = wx.ALIGN_CENTRE | wx.ALIGN_BOTTOM
                dc.DrawLabel("(%.2fs)" % maxTime, rect, alignment=_align)

            self.componentFromID[id] = routine
            # set the area for this component
            dc.SetIdBounds(id, rect)

        return endX

    def drawLoop(self,
                 dc,
                 loop,
                 id,
                 startX,
                 endX,
                 base,
                 height,
                 rgb=(0, 0, 0),
                 downwards=True):
        if downwards:
            up = -1
        else:
            up = +1

        # draw loop itself, as transparent rect with curved corners
        tmpId = wx.NewId()
        dc.SetId(tmpId)
        # extra distance, in both h and w for curve
        curve = (6, 11, 15)[self.appData['flowSize']]
        yy = [base, height + curve * up, height + curve * up / 2,
              height]  # for area
        r, g, b = rgb
        dc.SetPen(wx.Pen(wx.Colour(r, g, b, 200)))
        vertOffset = 0  # 1 is interesting too
        area = wx.Rect(startX, base + vertOffset, endX - startX,
                       max(yy) - min(yy))
        dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 0), style=wx.TRANSPARENT))
        # draws outline:
        dc.DrawRoundedRectangle(area, curve)
        dc.SetIdBounds(tmpId, area)

        flowsize = self.appData['flowSize']  # 0, 1, or 2

        # add a name label, loop info, except at smallest size
        name = loop.params['name'].val
        _show = self.appData['showLoopInfoInFlow']
        if _show and flowsize:
            _cond = 'conditions' in list(loop.params)
            if _cond and loop.params['conditions'].val:
                xnumTrials = 'x' + str(len(loop.params['conditions'].val))
            else:
                xnumTrials = ''
            name += '  (' + str(loop.params['nReps'].val) + xnumTrials
            abbrev = [
                '',  # for flowsize == 0
                {
                    'random': 'rand.',
                    'sequential': 'sequ.',
                    'fullRandom': 'f-ran.',
                    'staircase': 'stair.',
                    'interleaved staircases': "int-str."
                },
                {
                    'random': 'random',
                    'sequential': 'sequential',
                    'fullRandom': 'fullRandom',
                    'staircase': 'staircase',
                    'interleaved staircases': "interl'vd stairs"
                }
            ]
            name += ' ' + abbrev[flowsize][loop.params['loopType'].val] + ')'
        if flowsize == 0:
            if len(name) > 9:
                name = ' ' + name[:8] + '..'
            else:
                name = ' ' + name[:9]
        else:
            name = ' ' + name + ' '

        dc.SetId(id)
        font = self.GetFont()
        if sys.platform == 'darwin':
            basePtSize = (650, 750, 900)[flowsize]
        elif sys.platform.startswith('linux'):
            basePtSize = (750, 850, 1000)[flowsize]
        else:
            basePtSize = (700, 750, 800)[flowsize]
        font.SetPointSize(basePtSize / self.dpi)
        self.SetFont(font)
        dc.SetFont(font)

        # get size based on text
        pad = (5, 8, 10)[self.appData['flowSize']]
        w, h = self.GetFullTextExtent(name)[0:2]
        x = startX + (endX - startX) / 2 - w / 2 - pad / 2
        y = (height - h / 2)

        # draw box
        rect = wx.Rect(x, y, w + pad, h + pad)
        # the edge should match the text
        dc.SetPen(wx.Pen(wx.Colour(r, g, b, 100)))
        # try to make the loop fill brighter than the background canvas:
        dc.SetBrush(wx.Brush(wx.Colour(235, 235, 235, 250)))

        dc.DrawRoundedRectangle(rect, (4, 6, 8)[flowsize])
        # draw text
        dc.SetTextForeground([r, g, b])
        dc.DrawText(name, x + pad / 2, y + pad / 2)

        self.componentFromID[id] = loop
        # set the area for this component
        dc.SetIdBounds(id, rect)
コード例 #2
0
class Calendrier(wx.ScrolledWindow):
    def __init__(self,
                 parent,
                 ID=-1,
                 multiSelections=True,
                 selectionInterdite=False,
                 typeCalendrier="mensuel"):
        wx.ScrolledWindow.__init__(self, parent, ID, style=wx.NO_BORDER)
        self.multiSelections = multiSelections
        self.selectionInterdite = selectionInterdite

        self.SetMinSize((100, 140))

        # Variables à ne surtout pas changer
        self.caseSurvol = None
        self.multiSelect = None
        self.onLeave = True

        # Variables qui peuvent être changées
        self.listeSelections = []

        self.ecartCases = 2  # Ecart en pixels entre les cases jours d'un mois
        self.ecartMois = 8  # Ecart en pixels entre chaque mois dan un calendrier annuel

        self.couleurFond = (
            255, 255, 255
        )  #(255, 255, 255)       # Couleur du fond du calendrier
        self.couleurNormal = (
            255, 255, 255
        )  #(214, 223, 247) #(175, 225, 251)     # Couleur d'un jour normal du lundi au vendredi
        self.couleurWE = (
            231, 245, 252
        )  #(171, 249, 150)         # Couleur des samedis et dimanche
        self.couleurSelect = (
            55, 228, 9
        )  # Couleur du fond de la case si celle-ci est sélectionnée
        self.couleurSurvol = (
            0, 0, 0)  # Couleur du bord de la case si celle-ci est survolée
        self.couleurFontJours = (0, 0, 0)
        self.couleurVacances = (
            255, 255, 255
        )  # Couleur des cases dates d'ouverture de la structure
        self.couleurFontJoursAvecPresents = (255, 0, 0)
        self.couleurFerie = (180, 180, 180)  # couleur des jours fériés

        self.headerMois = True
        self.headerJours = True

        self.typeCalendrier = typeCalendrier
        self.moisCalendrier = 2
        self.anneeCalendrier = 2008

        self.selectExclureWE = True  # Inclure les WE quand une période de vacs est sélectionnée dans le menu contextuel

        # Pour statusBar :
        try:
            self.frameParente = self.GetGrandParent().GetGrandParent(
            ).GetParent()
        except:
            pass

        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
        self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
        self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)

        self.Bind(wx.EVT_SIZE, self.OnSize)

        # Init DC :
        self.CreatePseudoDC()

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)

    def MAJpanel(self):
        self.joursVacances, self.listePeriodesVacs = self.Importation_Vacances(
            self.anneeCalendrier)
        self.listeFeriesFixes, self.listeFeriesVariables = self.Importation_Feries(
            self.anneeCalendrier)
        self.CreatePseudoDC()
        self.Refresh()

    def CreatePseudoDC(self):
        # create a PseudoDC to record our drawing
        self.pdc = PseudoDC()
        self.DoDrawing(self.pdc)

    def ConvertEventCoords(self, event):
        xView, yView = self.GetViewStart()
        xDelta, yDelta = self.GetScrollPixelsPerUnit()
        return (event.GetX() + (xView * xDelta),
                event.GetY() + (yView * yDelta))

    def OffsetRect(self, r):
        xView, yView = self.GetViewStart()
        xDelta, yDelta = self.GetScrollPixelsPerUnit()
        if 'phoenix' in wx.PlatformInfo:
            r.Offset(-(xView * xDelta), -(yView * yDelta))
        else:
            r.OffsetXY(-(xView * xDelta), -(yView * yDelta))

    def OnPaint(self, event):
        # Create a buffered paint DC.  It will create the real
        # wx.PaintDC and then blit the bitmap to it when dc is
        # deleted.
        dc = wx.BufferedPaintDC(self)
        # use PrepateDC to set position correctly
        if wx.VERSION < (2, 9, 0, 0):
            self.PrepareDC(dc)
        # we need to clear the dc BEFORE calling PrepareDC
        bg = wx.Brush(self.GetBackgroundColour())
        dc.SetBackground(bg)
        dc.Clear()
        # create a clipping rect from our position and size
        # and the Update Region
        xv, yv = self.GetViewStart()
        dx, dy = self.GetScrollPixelsPerUnit()
        x, y = (xv * dx, yv * dy)
        rgn = self.GetUpdateRegion()
        rgn.Offset(x, y)
        r = rgn.GetBox()
        # draw to the dc using the calculated clipping rect
        self.pdc.DrawToDCClipped(dc, r)

    def DoDrawing(self, dc):
        dc.RemoveAll()
        if 'phoenix' not in wx.PlatformInfo:
            dc.BeginDrawing()
        self.caseSurvol = None
        self.Calendrier(dc)
        if 'phoenix' not in wx.PlatformInfo:
            dc.EndDrawing()

    def MAJAffichage(self):
        self.DoDrawing(self.pdc)
        self.Refresh()

    def OnSize(self, event):
        self.MAJAffichage()
        event.Skip()

    def Calendrier(self, dc):
        try:
            self.joursVacances
        except:
            self.joursVacances, self.listePeriodesVacs = self.Importation_Vacances(
                self.anneeCalendrier)
        try:
            self.listeFeriesFixes
        except:
            self.listeFeriesFixes, self.listeFeriesVariables = self.Importation_Feries(
                self.anneeCalendrier)

        self.dictCases = {}
        self.listeCasesJours = []

        if 'phoenix' in wx.PlatformInfo:
            largeur, hauteur = self.GetClientSize()
        else:
            largeur, hauteur = self.GetClientSizeTuple()

        annee = self.anneeCalendrier

        if self.typeCalendrier == "mensuel":
            # Création d'un calendrier mensuel
            mois = self.moisCalendrier
            self.listeJoursAvecPresents = self.Importation_JoursAvecPresents(
                annee=annee, mois=mois)
            self.DrawMonth(dc, mois, annee, 0, 0, largeur, hauteur)
        else:
            # Création d'un calendrier annuel
            largeurMois = largeur / 4.0
            hauteurMois = hauteur / 3.0
            numMois = 1
            self.listeJoursAvecPresents = self.Importation_JoursAvecPresents(
                annee=annee)
            for colonne in range(3):
                y = colonne * (hauteurMois + (self.ecartMois / 2.0))
                for ligne in range(4):
                    x = ligne * (largeurMois + (self.ecartMois / 3.0))
                    self.DrawMonth(dc, numMois, annee, x, y,
                                   largeurMois - self.ecartMois,
                                   hauteurMois - self.ecartMois)
                    numMois += 1

    def OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        if keycode == wx.WXK_CONTROL:
            self.multiSelect = "CONTROL"
        if keycode == wx.WXK_SHIFT:
            self.multiSelect = "SHIFT"
            self.multiSelectWE = True
        if keycode == wx.WXK_ALT:
            self.multiSelect = "SHIFT"
            self.multiSelectWE = False
        event.Skip()

    def OnKeyUp(self, event):
        self.multiSelect = None
        self.multiSelectWE = False
        event.Skip()

    def VerifKeyStates(self):
        """ est utilisé pour être sûr que le programme a bien remarqué les touches pressées """
        etat_Control = wx.GetKeyState(wx.WXK_CONTROL)
        etat_Shift = wx.GetKeyState(wx.WXK_SHIFT)
        etat_Alt = wx.GetKeyState(wx.WXK_ALT)

        if etat_Control == True:
            self.multiSelect = "CONTROL"
        if etat_Shift == True:
            self.multiSelect = "SHIFT"
            self.multiSelectWE = True
        if etat_Alt == True:
            self.multiSelect = "SHIFT"
            self.multiSelectWE = False

    def OnLeftDown(self, event):
        """ Sélection de la case cliquée """
        self.VerifKeyStates()
        if self.multiSelections == False:
            self.multiSelect = None
            self.multiSelectWE = False

        if self.selectionInterdite == True:
            return

        x, y = self.ConvertEventCoords(event)
        listeObjets = self.pdc.FindObjectsByBBox(x, y)
        IDobjet = 0
        if len(listeObjets) != 0:
            IDobjet = listeObjets[0]
            date = self.IDobjetEnDate(IDobjet)
            x = 1
            if x == 1:

                # CASES DATES -----------------------------------------------------------------------

                # Si la case est déjà sélectionnée, on la supprime de la liste des sélections
                if len(self.listeSelections) != 0:
                    if date in self.listeSelections:
                        self.listeSelections.remove(date)
                        self.RedrawCase(IDobjet, survol=True)
                        self.SendDates()
                        return

                # Ajout de la case à la liste des sélections
                if self.multiSelect == "CONTROL":
                    # MultiSelections avec la touche CTRL
                    self.listeSelections.append(date)
                    IDobjet = self.DateEnIDobjet(date)
                    self.RedrawCase(IDobjet, survol=True)
                    self.SendDates()

                elif self.multiSelect == "SHIFT":
                    # MultisSelections avec la touche SHIFT
                    self.listeSelections.append(date)
                    self.listeSelections.sort()
                    posTemp = self.listeSelections.index(date) - 1
                    previousDate = self.listeSelections[posTemp]
                    jourDebut = previousDate
                    jourFin = date

                    # Si sélection inverse
                    if jourDebut > jourFin:
                        jourDebut = date
                        jourFin = previousDate

                    nbreJours = (jourFin - jourDebut).days

                    dateEnCours = jourDebut
                    for j in range(nbreJours):
                        dateEnCours = dateEnCours + datetime.timedelta(days=1)

                        # si MultiSelectWE=False, on ne garde ni les samedis ni les dimanches
                        if self.multiSelectWE == False:
                            if dateEnCours.isoweekday(
                            ) == 6 or dateEnCours.isoweekday() == 7:
                                continue

                        # Mémorisation de la date
                        if not dateEnCours in self.listeSelections:
                            self.listeSelections.append(dateEnCours)

                    # Si le dernier jour est dans le week-end et si MultiSelectWE=False, on le supprime
                    if self.multiSelectWE == False:
                        if dateEnCours.isoweekday(
                        ) == 6 or dateEnCours.isoweekday() == 7:
                            self.listeSelections.remove(date)

                    self.listeSelections.sort()
                    self.MAJAffichage()
                    self.RedrawCase(IDobjet, survol=True)
                    self.SendDates()

                else:
                    # Sélection Unique
                    self.listeSelections = []
                    self.listeSelections.append(date)
                    self.MAJAffichage()
                    self.RedrawCase(IDobjet, survol=True)
                    self.SendDates()

        else:

            # CASES NOMS DE JOURS -----------------------------------------------------------------
            if self.multiSelections == False:
                return

            for caseJour in self.listeCasesJours:
                if (caseJour[0] <= x <= (caseJour[0] + caseJour[2])) and (
                        caseJour[1] <= y <= (caseJour[1] + caseJour[3])):

                    # Sélection par colonne
                    if self.multiSelect == "CONTROL" or self.multiSelect == "SHIFT":
                        deselect = []
                        nbreSemaines = 0
                        # avec la touche CTRL
                        numJour, mois, annee = caseJour[4]
                        datesMois = calendar.monthcalendar(annee, mois)
                        for semaine in datesMois:
                            selTemp = semaine[numJour]
                            if selTemp != 0:
                                nbreSemaines += 1
                                dateTemp = datetime.date(annee, mois, selTemp)
                                if dateTemp not in self.listeSelections:
                                    self.listeSelections.append(dateTemp)
                                else:
                                    deselect.append(dateTemp)

                        # Si tous les jours ont déjà été sélectionnés, c'est que l'on doit désélectionner tout le mois :
                        if len(deselect) == nbreSemaines:
                            for date in deselect:
                                self.listeSelections.remove(date)

                    else:
                        # Sélection Unique
                        tempSelections = []
                        deselect = []
                        nbreSemaines = 0
                        numJour, mois, annee = caseJour[4]
                        datesMois = calendar.monthcalendar(annee, mois)
                        for semaine in datesMois:
                            selTemp = semaine[numJour]
                            if selTemp != 0:
                                nbreSemaines += 1
                                dateTemp = datetime.date(annee, mois, selTemp)
                                if dateTemp not in self.listeSelections:
                                    tempSelections.append(dateTemp)
                                else:
                                    deselect.append(dateTemp)

                        # Si tous les jours ont déjà été sélectionnés, c'est que l'on doit désélectionner tout le mois :
                        if len(deselect) == nbreSemaines:
                            self.listeSelections = []
                        else:
                            # Sélection de tout le mois
                            self.listeSelections = []
                            for date in tempSelections:
                                self.listeSelections.append(date)
                            for date in deselect:
                                self.listeSelections.append(date)

            self.SendDates()
            self.MAJAffichage()

        event.Skip()

    def SendDates(self):
        """ Envoie la liste des dates sélectionnées """
        event = SelectDatesEvent(selections=self.listeSelections)
        wx.PostEvent(self.GetParent(), event)

    def OnLeave(self, event):
        if self.onLeave == True:
            if self.caseSurvol != None:
                self.RedrawCase(self.caseSurvol, survol=False)
                self.caseSurvol = None
                try:
                    wx.GetApp().GetTopWindow().SetStatusText("", 0)
                except:
                    pass
        try:
            wx.GetApp().GetTopWindow().SetStatusText("", 0)
        except:
            pass
        event.Skip()

    def OnEnter(self, event):
        txt = _(
            u"Restez appuyer sur les touches CONTROL, SHIFT ou ALT pour sélectionner plusieurs jours à la fois."
        )
        try:
            wx.GetApp().GetTopWindow().SetStatusText(txt, 1)
        except:
            pass
        event.Skip()

    def RedrawCase(self, IDobjet, survol=False):
        """ Redessine une case """
        # DC
        dc = self.pdc
        # Efface l'id de la liste des objets du pseudoDC
        dc.ClearId(IDobjet)
        # Redessine la case
        caractCase = self.dictCases[IDobjet]
        self.DrawCase(dc,
                      caractCase[4],
                      caractCase[0],
                      caractCase[1],
                      caractCase[2],
                      caractCase[3],
                      survol=survol)
        # Redessine uniquement la zone modifiée
        r = dc.GetIdBounds(IDobjet)
        self.OffsetRect(r)
        self.RefreshRect(r, False)

    def OnMotion(self, event):
        # Allume la case
        x, y = self.ConvertEventCoords(event)
        listeObjets = self.pdc.FindObjectsByBBox(x, y)
        IDobjet = 0
        if len(listeObjets) != 0:
            IDobjet = listeObjets[0]
            # Si cette case est déjà actuellement survolée, on passe...
            if self.caseSurvol != None:
                if self.caseSurvol == IDobjet: return
            # Activation de la case sélectionnée
            # Si une case a déjà été survolée avant, on l'annule
            if self.caseSurvol != None:
                self.RedrawCase(self.caseSurvol, survol=False)
            # Redessine la nouvelle case
            self.caseSurvol = IDobjet
            self.RedrawCase(IDobjet, survol=True)

            # Ecriture d'un texte dans la statutBar
            date = self.IDobjetEnDate(IDobjet)
            listeJours = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi",
                          "Samedi", "Dimanche")
            listeMois = (_(u"janvier"), _(u"février"), _(u"mars"),
                         _(u"avril"), _(u"mai"), _(u"juin"), _(u"juillet"),
                         _(u"août"), _(u"septembre"), _(u"octobre"),
                         _(u"novembre"), _(u"décembre"))
            dateStr = listeJours[date.weekday()] + " " + str(
                date.day) + " " + listeMois[date.month - 1] + " " + str(
                    date.year)
            texteStatusBar = dateStr

            # Si c'est un jour de vacances
            if date in self.joursVacances:
                texteStatusBar += _(u" | Jour de vacances")

            # Si c'est un jour férié
            if (date.day, date.month) in self.listeFeriesFixes:
                texteStatusBar += _(u" | Jour férié")
            else:
                if date in self.listeFeriesVariables:
                    texteStatusBar += _(u" | Jour férié")

            # Actualisation la statusBar
            try:
                wx.GetApp().GetTopWindow().SetStatusText(texteStatusBar, 0)
            except:
                pass

            return

        # Si on ne survole aucune case : Désactivation de la case précédemment sélectionnée
        if self.caseSurvol != None:
            self.RedrawCase(self.caseSurvol, survol=False)
            self.caseSurvol = None
            try:
                wx.GetApp().GetTopWindow().SetStatusText("", 0)
            except:
                pass

    def DrawMonth(self, dc, mois, annee, xMois, yMois, largMois, hautMois):
        """ Dessine un mois entier """

        # Recherche une liste des dates du mois
        datesMois = calendar.monthcalendar(annee, mois)
        nbreSemaines = len(datesMois)

        # Création de l'entete avec le nom du mois
        if self.headerMois == True:
            hautMois, yMois = self.DrawHeaderMois(dc, nbreSemaines, mois,
                                                  annee, xMois, yMois,
                                                  largMois, hautMois)

        # Création de l'entete avec les noms des jours
        if self.headerJours == True:
            hautMois, yMois = self.DrawHeaderJours(dc, nbreSemaines, mois,
                                                   annee, xMois, yMois,
                                                   largMois, hautMois)

        # Calcule la taille d'une case
        largCase = (largMois / 7.0)
        hautCase = (hautMois / float(nbreSemaines))

        # Créée les cases jours
        for numSemaine in range(nbreSemaines):
            for numJour in range(7):

                jour = datesMois[numSemaine][numJour]

                if jour != 0:
                    # Crée les données de la case
                    x = xMois + (largCase * numJour)
                    y = yMois + (hautCase * numSemaine)
                    l = largCase - self.ecartCases
                    h = hautCase - self.ecartCases
                    texteDate = datetime.date(annee, mois, jour)

                    # Enregistrement des données dans une liste
                    caractCase = (x, y, l, h, texteDate)
                    IDcase = self.DateEnIDobjet(texteDate)
                    self.dictCases[IDcase] = caractCase

                    # Dessin de la case
                    self.DrawCase(dc, texteDate, x, y, l, h)

    def DateEnIDobjet(self, date):
        annee = str(date.year)
        mois = str(date.month)
        jour = str(date.day)
        if len(mois) == 1: mois = "0" + mois
        if len(jour) == 1: jour = "0" + jour
        IDobjet = int(annee + mois + jour)
        return IDobjet

    def IDobjetEnDate(self, IDobjet):
        IDobjet = str(IDobjet)
        annee = int(IDobjet[:4])
        mois = int(IDobjet[4:6])
        jour = int(IDobjet[6:8])
        date = datetime.date(annee, mois, jour)
        return date

    def DrawHeaderJours(self, dc, nbreSemaines, mois, annee, xMois, yMois,
                        largMois, hautMois):
        """ Dessine un header comportant les noms des jours """
        #self.listeCasesJours = []
        listeJours = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi",
                      "Samedi", "Dimanche")
        largCase = (largMois / 7.0)
        hautCase = (hautMois / float(nbreSemaines))
        # Réglage de la police
        dc.SetTextForeground(self.couleurFontJours)
        taille = self.tailleFont(largCase, hautCase)
        font = wx.Font(taille, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_NORMAL)
        dc.SetFont(font)
        hautHeader = taille * 2
        x = 0
        for jour in range(7):
            texte = listeJours[jour]
            texteComplet = (jour, mois, annee)
            # Réglage du format du texte en fonction de la taille de la case
            if largCase < 50:
                texte = texte[:3]
            if largCase < 25:
                texte = texte[0]
            largTexte, hautTexte = self.GetTextExtent(texte)
            coordX = xMois + x + (largCase / 2) - (largTexte / 2)
            coordY = yMois + (hautHeader / 2) - (hautTexte / 2)
            dc.DrawText(texte, coordX, coordY)
            # Mémorisation des jours et de leurs coordonnées
            self.listeCasesJours.append(
                (coordX, coordY, largTexte, hautTexte, texteComplet))
            x += largCase

        return hautMois - hautHeader, yMois + hautHeader

    def DrawHeaderMois(self, dc, nbreSemaines, mois, annee, xMois, yMois,
                       largMois, hautMois):
        """ Dessine un header comportant le nom du mois """
        listeMois = (_(u"Janvier"), _(u"Février"), _(u"Mars"), _(u"Avril"),
                     _(u"Mai"), _(u"Juin"), _(u"Juillet"), _(u"Août"),
                     _(u"Septembre"), _(u"Octobre"), _(u"Novembre"),
                     _(u"Décembre"))
        largCase = (largMois / 7.0)
        hautCase = (hautMois / float(nbreSemaines))
        # Réglage de la police
        dc.SetTextForeground(self.couleurFontJours)
        taille = self.tailleFont(largCase, hautCase)
        font = wx.Font(taille, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)
        dc.SetFont(font)
        hautHeader = taille * 3
        # Dessin du texte
        texte = listeMois[mois - 1] + " " + str(annee)
        largTexte, hautTexte = self.GetTextExtent(texte)
        dc.DrawText(texte, xMois + (largMois / 2) - (largTexte / 2),
                    yMois + (hautHeader / 2) - (hautTexte / 2))
        # Dessin de la ligne
        dc.SetPen(wx.Pen((210, 210, 210), 1))
        dc.DrawLine(xMois + 2, yMois + hautHeader - 2, xMois + largMois - 2,
                    yMois + hautHeader - 2)

        return hautMois - hautHeader, yMois + hautHeader

    def DrawCase(self, dc, texteDate, x, y, l, h, survol=False):

        IDobjet = self.DateEnIDobjet(texteDate)
        dc.SetId(IDobjet)

        # Application des couleurs normales
        dc.SetBrush(wx.Brush(self.couleurNormal))
        dc.SetPen(wx.Pen(self.couleurFond, 1))

        # Si c'est un jour de vacances
        if texteDate in self.joursVacances:
            dc.SetBrush(wx.Brush(self.couleurVacances))

        # Si c'est un jour de Week-end
        jourSemaine = texteDate.isoweekday()
        if jourSemaine == 6 or jourSemaine == 7:
            dc.SetBrush(wx.Brush(self.couleurWE))

        # Si c'est un jour férié
        if (texteDate.day, texteDate.month) in self.listeFeriesFixes:
            dc.SetBrush(wx.Brush(self.couleurFerie))
        else:
            if texteDate in self.listeFeriesVariables:
                dc.SetBrush(wx.Brush(self.couleurFerie))

        # Si c'est une case survolée
        if survol == True:
            #dc.SetBrush(wx.Brush('black', wx.TRANSPARENT))
            dc.SetPen(wx.Pen(self.couleurSurvol, 1))

        # Dessine d'une case sélectionnée
        if len(self.listeSelections) != 0:
            if texteDate in self.listeSelections:
                dc.SetBrush(wx.Brush(self.couleurSelect))

        # Dessin de la case
        dc.DrawRectangle(x, y, l, h)

        # Dessin du symbole Aujourd'hui
        if texteDate == datetime.date.today():
            dc.SetBrush(wx.Brush((255, 0, 0)))
            dc.SetPen(wx.Pen((255, 0, 0), 0))
            posG = x + l - 2
            posH = y + 1
            tailleTriangle = 5
            dc.DrawPolygon([(posG, posH), (posG - tailleTriangle, posH),
                            (posG, posH + tailleTriangle)])

        # Dessin texte
        if unicode(texteDate) in self.listeJoursAvecPresents:
            dc.SetTextForeground(self.couleurFontJoursAvecPresents)
        else:
            dc.SetTextForeground(self.couleurFontJours)

        font = self.GetFont()
        font.SetPointSize(self.tailleFont(l, h))
        dc.SetFont(font)
        dc.DrawText(str(texteDate.day), x + 3, y + 2)

        # Traitement pour le PseudoDC
        r = wx.Rect(x, y, l, h)
        dc.SetIdBounds(IDobjet, r)

    def tailleFont(self, l, h):

        # On prend le côté le plus petit
        if l > h:
            cote = h
        else:
            cote = l
        # On définit des ordres de grandeur
        if cote <= 14: return 6
        if cote <= 20: return 7
        if cote <= 40: return 7
        if cote <= 60: return 8
        if cote <= 80: return 9
        if cote <= 120: return 10
        return 12

    def SetTypeCalendrier(self, typeCal):
        self.typeCalendrier = typeCal
        self.MAJAffichage()

    def GetTypeCalendrier(self):
        return self.typeCalendrier

    def SetMoisAnneeCalendrier(self, mois=0, annee=0):
        if mois != 0: self.moisCalendrier = mois
        if annee != 0: self.anneeCalendrier = annee
        self.MAJAffichage()

    def GetMoisAnneeCalendrier(self):
        return self.moisCalendrier, self.anneeCalendrier

    def GetTypeCalendrier(self):
        return self.typeCalendrier

    def Importation_Vacances(self, anneeCalendrier=None):
        """ Importation des dates de vacances """

        req = "SELECT * FROM vacances ORDER BY date_debut;"
        DB = GestionDB.DB()
        DB.ExecuterReq(req)
        listeVacances1 = DB.ResultatReq()
        DB.Close()

        listeVacances2 = []
        listePeriodesVacs = []

        for id, nom, annee, date_debut, date_fin in listeVacances1:
            datedebut = datetime.date(int(date_debut[:4]),
                                      int(date_debut[5:7]),
                                      int(date_debut[8:10]))
            datefin = datetime.date(int(date_fin[:4]), int(date_fin[5:7]),
                                    int(date_fin[8:10]))
            listeVacances2.append(datedebut)
            listeTemp = []
            for x in range((datefin - datedebut).days):
                # Ajout à la liste des jours de vacances (qui sert au coloriage de la case)
                datedebut = datedebut + datetime.timedelta(days=1)
                listeVacances2.append(datedebut)
                listeTemp.append(datedebut)
            # Ajout au dictionnaire des vacances (qui sert à sélectionner une période de vacs dans le calendrier)
            listePeriodesVacs.append((annee, nom, tuple(listeTemp)))

        return listeVacances2, listePeriodesVacs

    def Importation_Feries(self, anneeCalendrier=None):
        """ Importation des dates de vacances """

        req = "SELECT * FROM jours_feries;"
        DB = GestionDB.DB()
        DB.ExecuterReq(req)
        listeFeriesTmp = DB.ResultatReq()
        DB.Close()

        listeFeriesFixes = []
        listeFeriesVariables = []
        for ID, type, nom, jour, mois, annee in listeFeriesTmp:
            if type == "fixe":
                date = (jour, mois)
                listeFeriesFixes.append(date)
            else:
                date = datetime.date(annee, mois, jour)
                listeFeriesVariables.append(date)
        return listeFeriesFixes, listeFeriesVariables

    def Importation_JoursAvecPresents(self, annee=None, mois=None):
        return []
        ###### MODIFIE ICI #############

        if mois == None:
            dateDebut = str(annee) + "-01-01"
            dateFin = str(annee) + "-12-31"
        else:
            strMois = str(mois)
            if len(strMois) == 1: strMois = "0" + strMois
            dateDebut = str(annee) + "-" + strMois + "-01"
            dateFin = str(annee) + "-" + strMois + "-31"
        """ Importation des dates ou il y a des présents """
        DB = GestionDB.DB()
        req = """
        SELECT date
        FROM presences
        WHERE date >= '%s' And date <= '%s'
        GROUP BY presences.date;
        """ % (dateDebut, dateFin)
        DB.executerReq(req)
        listeDonnees = DB.resultatReq()
        DB.close()

        # Transformation en bonne liste
        listeDonnees2 = []
        for date in listeDonnees:
            listeDonnees2.append(date[0])

        return listeDonnees2

    def OnContextMenu(self, nomCase):
        """ Menu contextuel du calendrier """
        if self.caseSurvol != None:
            texteDate = self.IDobjetEnDate(self.caseSurvol)
        else:
            texteDate = None

        if self.selectionInterdite == True:
            return

        # Création du menu
        menu = wx.Menu()

        if self.multiSelections == True:

            # Si une date a bien été cliquée :
            if texteDate != None:
                # Vérifie si date déjà sélectionnée
                if texteDate in self.listeSelections:
                    select = True
                else:
                    select = False
                # Sélection/déselection du jour cliqué
                self.popupID1 = wx.NewId()
                if select == False:
                    texte = _(u"Sélectionner le %02d/%02d/%04d") % (
                        texteDate.day, texteDate.month, texteDate.year)
                else:
                    texte = _(u"Désélectionner le %02d/%02d/%04d") % (
                        texteDate.day, texteDate.month, texteDate.year)
                menu.Append(self.popupID1, texte)
                self.Bind(wx.EVT_MENU, self.OnPopup1, id=self.popupID1)

                menu.AppendSeparator()

        # Choisir la date d'aujourd'hui
        self.popupID4 = wx.NewId()
        menu.Append(self.popupID4, _(u"Sélectionner aujourd'hui"))
        self.Bind(wx.EVT_MENU, self.OnPopup4, id=self.popupID4)

        if self.multiSelections == True:

            # Choisir tout le mois
            self.popupID5 = wx.NewId()
            menu.Append(self.popupID5, _(u"Sélectionner tout le mois"))
            self.Bind(wx.EVT_MENU, self.OnPopup5, id=self.popupID5)

            # Choisir une période de vacances
            self.popupID3 = wx.NewId()
            if len(self.listePeriodesVacs) != 0:
                sm = wx.Menu()
                index = 0
                self.listePeriodesVacs.reverse()
                # Seules les 20 dernières périodes sont retenues
                for annee, nomPeriode, listeJours in self.listePeriodesVacs[:
                                                                            20]:
                    id = 1000 + index
                    sm.Append(id, nomPeriode + " " + str(annee))
                    self.Bind(wx.EVT_MENU, self.OnPopup3, id=id)
                    index += 1
                # Inclus le sous-menu dans le menu
                menu.AppendMenu(self.popupID3,
                                _(u"Sélectionner une période de vacances"),
                                sm)

            # Tout désélectionner
            self.popupID7 = wx.NewId()
            menu.Append(self.popupID7, _(u"Tout désélectionner"))
            self.Bind(wx.EVT_MENU, self.OnPopup7, id=self.popupID7)

            # Exclure les jours de week-end dans les sélections
            self.popupID6 = wx.NewId()
            menu.Append(self.popupID6,
                        _(u"Exclure les week-ends des sélections"),
                        _(u"Exclure les week-ends de la sélection"),
                        wx.ITEM_CHECK)
            if self.selectExclureWE == True:
                menu.Check(self.popupID6, True)
            self.Bind(wx.EVT_MENU, self.OnPopup6, id=self.popupID6)

        # Aide sur le calendrier
        menu.AppendSeparator()
        self.popupID2 = wx.NewId()
        menu.Append(self.popupID2, _(u"Aide sur le calendrier"))
        self.Bind(wx.EVT_MENU, self.OnPopup2, id=self.popupID2)

        # make a submenu
        #sm = wx.Menu()
        #sm.Append(self.popupID8, "sub item 1")
        #sm.Append(self.popupID9, "sub item 1")
        #menu.AppendMenu(self.popupID7, "Test Submenu", sm)

        self.onLeave = False
        self.PopupMenu(menu)
        menu.Destroy()
        self.onLeave = True
        #self.RedrawCase(caseSurvol, survol=True)

    def OnPopup1(self, event):
        """ Sélection ou désélection """
        texteDate = self.IDobjetEnDate(self.caseSurvol)

        # Vérifie si date déjà sélectionnée
        if texteDate in self.listeSelections:
            select = True
        else:
            select = False

        # Désélection de la date
        if select == True:
            self.listeSelections.remove(texteDate)
            self.SelectJours(self.listeSelections)
            ##            self.RedrawCase(self.caseSurvol, survol=True)
            return

        # Sélection de la date
        else:
            self.listeSelections.append(texteDate)
            self.SelectJours(self.listeSelections)
            ##            self.RedrawCase(self.caseSurvol, survol=True)
            return

    def OnPopup2(self, event):
        """ Aide sur le calendrier """
        print "Aide..."
        # FonctionsPerso.Aide(51)

    def OnPopup3(self, event):
        """ Sélection d'une période de vacances """
        index = event.GetId() - 1000
        nomPeriode, annee, listeJours = self.listePeriodesVacs[index]
        # Mets les jours de vacances dans la liste de sélections

        # Enleve les week-ends si nécessaires :
        if self.selectExclureWE == True:
            listeJoursTmp = []
            for jour in listeJours:
                jourSemaine = jour.isoweekday()
                if jourSemaine != 6 and jourSemaine != 7:
                    listeJoursTmp.append(jour)
            listeJours = listeJoursTmp

        self.SelectJours(list(listeJours))

    def OnPopup6(self, event):
        """ Inclure ou non les week-ends dans la sélection """
        self.selectExclureWE = event.IsChecked()

    def OnPopup4(self, event):
        """ Choisir la date d'aujourd'hui """
        self.SelectJours([
            datetime.date.today(),
        ])

    def OnPopup5(self, event):
        """ Choisir tout le mois """
        listeJours = []
        datesDuMois = calendar.monthcalendar(self.anneeCalendrier,
                                             self.moisCalendrier)
        for semaine in datesDuMois:
            for jour in semaine:
                if jour != 0:
                    jourTmp = datetime.date(year=self.anneeCalendrier,
                                            month=self.moisCalendrier,
                                            day=jour)
                    if self.selectExclureWE == True:
                        jourSemaine = jourTmp.isoweekday()
                        if jourSemaine != 6 and jourSemaine != 7:
                            listeJours.append(jourTmp)
                    else:
                        listeJours.append(jourTmp)
        self.SelectJours(listeJours)

    def OnPopup7(self, event):
        """ Tout désélectionner """
        self.SelectJours(listeJours=[])

    def SelectJours(self, listeJours=[]):
        """ Met à jour l'affichage du calendrier et le planning en fonction des sélections """
        self.listeSelections = listeJours
        if len(listeJours) != 0:
            # Se place sur le mois ou l'année du premier jour de la période de vacances sélectionnée
            moisDebut, anneeDebut = listeJours[0].month, listeJours[0].year
            self.moisCalendrier = moisDebut
            self.anneeCalendrier = anneeDebut
        # Actualisation de l'affichage
        self.SendDates()
        self.MAJAffichage()
        # Met à jour l'affichage des contrôles de navigation du calendrier
        try:
            self.GetParent().MAJcontrolesNavigation(self.moisCalendrier,
                                                    self.anneeCalendrier)
        except:
            pass

    def GetSelections(self):
        return self.listeSelections