text: ############################################################################
  #
  # SAGE UI - A Graphical User Interface for SAGE
  # Copyright (C) 2005 Electronic Visualization Laboratory,
  # University of Illinois at Chicago
  #
  # All rights reserved.
  # 
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  # 
  #  * Redistributions of source code must retain the above copyright
  #    notice, this list of conditions and the following disclaimer.
  #  * Redistributions in binary form must reproduce the above
  #    copyright notice, this list of conditions and the following disclaimer
  #    in the documentation and/or other materials provided with the distribution.
  #  * Neither the name of the University of Illinois at Chicago nor
  #    the names of its contributors may be used to endorse or promote
  #    products derived from this software without specific prior written permission.
  # 
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  #
  # Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum
  #
  # Author: Ratko Jagodic
  #         Allan Spale
  #
  ############################################################################
  
  
  
  # my imports
  from SAGEShape import *
  from sageData import *
  from Mywx import *
  import Graph
  from globals import *
  #from pointers import Pointers
  import fileViewer
  import preferences as prefs
  
  # python imports
  import time
  from threading import Timer
  from time import sleep
  from math import floor,ceil
  import os.path
  import ConfigParser
  import wx.lib.buttons as btns
  
  
  DEBUG = False
  if DEBUG: import pdb
  
  
  
  
  class OneDisplay:
      """ contains the shapes for on """
      def __init__(self, l, r, t, b, sageDisplay, canvas):
          self.sageDisplay = sageDisplay
          self.canvas = canvas
          self.diagram = canvas.GetDiagram()
          self.cols = self.sageDisplay.cols
          self.rows = self.sageDisplay.rows
          self.sageW = self.sageDisplay.sageW
          self.sageH = self.sageDisplay.sageH
          self.bounds = Bounds(l,r,t,b)   # these are the bounds of the display... we have to make these panels fit here
          
          # create all the panels (Shapes)
          self.__CreatePanels()
  
  
      def __CreatePanels(self):
          self.__panels = [ [None]*self.cols for i in range(self.rows) ]
          
          panelW = self.bounds.getWidth() / self.cols
          panelH = self.bounds.getHeight() / self.rows
          
          for xOffset in range(self.cols):
              for yOffset in range(self.rows):
                  self.__panels[yOffset][xOffset] = self.__AddShape(
                      ogl.RectangleShape(panelW, panelH), 
                      xOffset*panelW + (panelW/2) + self.bounds.left, 
                      yOffset*panelH + (panelH/2) + self.bounds.top,
                      wx.Pen(wx.Color(51, 102, 102), 1), wx.Brush(tileColor), "")
  
  
      # draw boxes in the background as the frames
      # which should not be choosable or clickable
      def __AddShape(self, shape, x, y, pen, brush, text):
          shape.SetDraggable(False, False)
          shape.SetCanvas(self.canvas)
          shape.SetX(x)
          shape.SetY(y)
          if pen:    shape.SetPen(pen)
          if brush:  shape.SetBrush(brush)
          if text:
              for line in text.split('\n'):
                  shape.AddText(line)
          self.diagram.AddShape(shape)
          shape.Show(True)
          
          return shape
  
  
      def Resize(self, l, r, t, b):
          # resize and reposition the panels here
          self.bounds.setAll(l,r,t,b)
  
          # resize and reposition the display tiles
          panelW = self.bounds.getWidth() / self.cols
          panelH = self.bounds.getHeight() / self.rows
          
          for xOffset in range(self.cols):
              for yOffset in range(self.rows):
                  self.__panels[yOffset][xOffset].SetX(xOffset*panelW + (panelW/2) + self.bounds.left)
                  self.__panels[yOffset][xOffset].SetY(yOffset*panelH + (panelH/2) + self.bounds.top)
                  self.__panels[yOffset][xOffset].SetSize(panelW, panelH)
                      
      
  
  
  class Backdrop:
      """ consists of OneDisplays """
      def __init__(self, sageDisplayInfo, canvas):
          self.displays = {}  # key=displayId, value=OneDisplay
          self.bounds = Bounds() # what's the area we have available for drawing
          self.sageDisplayInfo = sageDisplayInfo
          self.canvas = canvas
          self.dispByPos = {}   # key=placement, value=OneDisplay
          self.widths = {}      # key=placement, value=width in UI coords
          self.heights = {}     # key=placement, value=height in UI coords
          self.positions = {}   # key=placement, value=tuple of (l,r,t,b)
  
  
  
      def GetDisplayBounds(self, displayId):
          return self.displays[displayId].bounds
      
  
      def SetupDisplays(self,l,r,t,b):
          self.bounds.setAll(l,r,t,b)
                  
          # first create all the displays
          for sageDisplay in self.sageDisplayInfo.getAllDisplays():
              newDisp = OneDisplay(l,r,t,b,sageDisplay,self.canvas)
              self.displays[ sageDisplay.getId() ] = newDisp
              self.dispByPos[ sageDisplay.placement ] = newDisp
              
          self.SetSize(self.bounds.left, self.bounds.right, self.bounds.top, self.bounds.bottom)
          
              
      def SetSize(self, l, r, t, b):
          self.bounds.setAll(l,r,t,b)
          
          self.__CalculateDisplayWidths()
          self.__CalculateDisplayHeights()
  
          # loop through each display and set the correct bounds for each
          for pos, disp in self.dispByPos.iteritems():
              if pos == LEFT_DISP:
                  l = self.bounds.left
                  r = l + self.widths[LEFT_DISP]
                  t = self.bounds.top + self.heights[TOP_DISP]
                  b = t + self.heights[LEFT_DISP]
                  
              elif pos == MIDDLE_DISP:
                  l = self.bounds.left + self.widths[LEFT_DISP]
                  r = l + self.widths[MIDDLE_DISP]
                  t = self.bounds.top + self.heights[TOP_DISP]
                  b = t + self.heights[MIDDLE_DISP]
                              
              elif pos == RIGHT_DISP:
                  l = self.bounds.left + self.widths[LEFT_DISP] + self.widths[MIDDLE_DISP]
                  r = l + self.widths[RIGHT_DISP]
                  t = self.bounds.top + self.heights[TOP_DISP]
                  b = t + self.heights[RIGHT_DISP]
                  
              elif pos == TOP_DISP:
                  l = self.bounds.left + self.widths[LEFT_DISP]
                  r = l + self.widths[TOP_DISP]
                  t = self.bounds.top
                  b = t + self.heights[TOP_DISP]
  
              elif pos == BOTTOM_DISP:
                  l = self.bounds.left + self.widths[LEFT_DISP]
                  r = l + self.widths[BOTTOM_DISP]
                  t = self.bounds.top + self.heights[TOP_DISP] + self.heights[MIDDLE_DISP]
                  b = t + self.heights[BOTTOM_DISP]
              
              # finally apply the size
              disp.Resize(l,r,t,b)
              
                  
                              
  
      def __CalculateDisplayWidths(self):
          self.widths = {MIDDLE_DISP:0, LEFT_DISP:0, RIGHT_DISP:0, BOTTOM_DISP:0, TOP_DISP:0}
          totalW = self.sageDisplayInfo.getTotalWidth()
          
          # in UI coords
          for pos, disp in self.dispByPos.iteritems():
              self.widths[pos] = (disp.sageW / float(totalW)) * self.bounds.getWidth()
              
      def __CalculateDisplayHeights(self):
          self.heights = {MIDDLE_DISP:0, LEFT_DISP:0, RIGHT_DISP:0, BOTTOM_DISP:0, TOP_DISP:0}
          totalH = self.sageDisplayInfo.getTotalHeight()
          
          # in UI coords
          for pos, disp in self.dispByPos.iteritems():
              self.heights[pos] = (disp.sageH / float(totalH)) * self.bounds.getHeight()
  
  
  
  
  
  ############################################################################
  #
  #  CLASS: DisplayCanvas
  #  
  #  DESCRIPTION: This class describes the canvas where the tiled display
  #               representation is drawn. It draws all the shapes, updates
  #               the visual state of the UI as a result of messages received
  #               from SAGE, it draws all the decorative graphics pertaining
  #               to it. It also contains all the coordinate conversion methods.
  #               All the drawing is done in the OnPaint() and Redraw() methods
  #               so that way when you call canvas.Refresh() or canvas.Redraw()
  #               it will use these overloaded methods. Another reason for
  #               overloading these methods is to force them to use double buffering
  #
  #  DATE:        October, 2004
  #
  ############################################################################
  
  class DisplayCanvas(ogl.ShapeCanvas):
      def __init__(self, log, parent, sageGate, sageData, gmGraphManager, title):
          # the size passed in here is being changed later so it doesnt really matter
          ogl.ShapeCanvas.__init__(self, parent, -1, (0,0), (710,300), wx.NO_BORDER | wx.NO_FULL_REPAINT_ON_RESIZE)
  
  
          # some global parameters
          displaySize = wx.DisplaySize()  #the resolution of the current screen... we will base our frame size on that
          if displaySize[1] > 900:
              self._maxCanvasWidth = 800
              self._borderWidth = 25
          else:
              self._maxCanvasWidth = 650
              self._borderWidth = 20
          self.frame = parent.GetFrame()#wx.GetApp().GetTopWindow() #parent
          self.parent = parent
          self._maxCanvasHeight = 0
          self._maxDisplayHeight = 0
          self._maxDisplayWidth = 0
          self._aspectRatio = 1   #the aspect ratio for the whole canvas... this is not for the apps
          self.title = title
          self.displayInfoArrived = False
          self._minimizedShapes = {}  # key=displayId, value=hash of minimized shapes
          self._shapesTiled = False
          
          # main data and communication objects
          self.sageGate = sageGate
          self.sageData = sageData
          self.__gmGraphManager = gmGraphManager
  
          # support for file viewing and drag-and-dropping inside the UI
          self.dt = fileViewer.CanvasDropTarget(self)
          self.SetDropTarget(self.dt)
          
          # register sageData callbacks
          self.sageData.registerCallbackFunction(40000, self.OnSAGEStatusMsg)
          self.sageData.registerCallbackFunction(40001, self.OnSAGEAppInfoMsg)
          self.sageData.registerCallbackFunction(40002, self.OnSAGEPerfInfoMsg)
          self.sageData.registerCallbackFunction(40003, self.OnSAGEAppShutdownMsg)
          self.sageData.registerCallbackFunction(40004, self.OnSAGEDisplayInfoMsg)
          self.sageData.registerCallbackFunction(40005, self.OnSAGEZChangeMsg)
          self.sageData.registerCallbackFunction(40006, self.OnSAGEAppExecConfig)
          self.sageData.registerCallbackFunction(40007, self.OnSAGEDisplaysConnection)
  
          # set up the canvas
          self.log = log
          self.SetBackgroundColour('#006666')
          self.diagram = ogl.Diagram()
          self.SetDiagram(self.diagram)
          self.diagram.SetCanvas(self)
          self.shapes = {}  #a hash of all the apps that are CURRENTLY RUNNING (keyed by windowId)
          self.availableApps = {}     #a hash of all the apps available for running
                                          # in this form  key:value = windowId:SAGEAppInitial
          # now tell SAGE that we exist so we can get the current SAGE status
          self.sageGate.registerSage()
          
          # at this point we know we succeeded in connecting to SAGE so load all the images
          self.LoadImages()   #creates a hash with the "Images" keyed by the name        
          self.Bind(wx.EVT_ERASE_BACKGROUND, self.Chillout)
          self.Bind(wx.EVT_MOTION, self.OnMouseEnter)
          self.Bind(wx.EVT_CLOSE, self.OnClose)
          self.cursorSet = False
    
  
  
  #----------------------------------------------------------------------
  # DRAWING METHODS
  #----------------------------------------------------------------------    
      
      def OnPaint( self, event ):
          if DEBUG: print "OnPaint --> DisplayCanvas"; sys.stdout.flush()
          if not self.displayInfoArrived:
              if "__WXMSW__" in wx.PlatformInfo:  #windows needs a PaintDC created even if we dont use it
                  dc = wx.PaintDC(self)
              return
          dc = wx.BufferedPaintDC( self, self.swapBuffer )
  
      def Chillout(self, event):
          """ For intercepting the EVT_ERASE_BACKGROUND. Do nothing here on purpose
              in order to reduce the flicker during drawing"""
          pass
  
  
      def OnClose(self, evt):
          pass
  
  
  #----------------------------------------------------------------------
  
  
      # overridden so that we can specify the order in which the drawing occurs (because of the decorative images)
      def Redraw( self, dc=None):
          if DEBUG: print "Redraw --> DisplayCanvas"; sys.stdout.flush()
          if not self.displayInfoArrived:
              return
          # create the right kind of DC for double buffering
          if dc == None: 
              cdc = wx.ClientDC(self)
              self.PrepareDC(cdc)
              dc = wx.BufferedDC(cdc, self.swapBuffer)
              dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
              dc.Clear()
  
          if self.GetDiagram().GetShapeList():
              self.SetCursor(wx.HOURGLASS_CURSOR)
  
          # first draw the underlying shapes (LCD panels)
          for object in self.GetDiagram().GetShapeList():
              if ((object.__class__.__name__ != MyRectangleShape.__name__ ) and
                 (object.__class__.__name__ != MyControlPoint.__name__ )):
                     object.Draw(dc)
  
          # then draw the decorative images
          for name, image in self.images.iteritems():
              dc.DrawBitmap( image.bitmap, image.GetX(), image.GetY(), image.IsTransparent() )
  
          # draw the title (user @ machine)
          dc.SetTextForeground(wx.WHITE)
          textSize = dc.GetTextExtent(self.title)
          dc.DrawText(self.title, self.GetCanvasWidth()/2 - textSize[0]/2, (self.GetBorderWidth() - textSize[1])/2) 
  
          # finally draw the app shapes on top of everything
          for object in self.GetDiagram().GetShapeList():
              if ((object.__class__.__name__ == MyRectangleShape.__name__ ) or
                 (object.__class__.__name__ == MyControlPoint.__name__ )):
                     object.Draw(dc)
     
          self.SetCursor(wx.STANDARD_CURSOR)
  
  
      #----------------------------------------------------------------------
  
  
      def OnSize(self, event=None):
          if DEBUG: print "OnSize --> DisplayCanvas"; sys.stdout.flush()        
          if not self.displayInfoArrived:
              return
          # manually maintain the aspect ratio of the display canvas
          parentSize = self.parent.GetClientSize()
          self.SetCanvasWidth( parentSize.width )
          self.SetCanvasHeight( round(self.GetCanvasWidth() / self.GetAspectRatio()) )
          self.SetSize((self.GetCanvasWidth(), self.GetCanvasHeight()))
          
          # change the display width now
          self.SetDisplayWidth( self.GetCanvasWidth() - 2 * self.GetBorderWidth() )
          heightFactor = self.tiledDisplayWidth / float (self.GetDisplayWidth() )
          self.SetDisplayHeight( self.GetCanvasHeight() - 1.5*self.GetBorderWidth()) #round (self.tiledDisplayHeight / heightFactor ) )
  
          # update the size of the swap buffer
          self.swapBuffer = wx.EmptyBitmap(self.GetCanvasWidth(), self.GetCanvasHeight())
          
          # reposition and resize all of the following
          self.RecalculateImages() #decorative images
          self.RecalculateTiles()  
          self.RecalculateShapes() 
          
          self.Redraw()
          #event.Skip()
  
             
      #----------------------------------------------------------------------
  
  
      def LoadImages(self):
          # load all the decorative images
          wx.InitAllImageHandlers()
          self.images = {}
          self.imageNames = [ "top_left", "top", "top_right", "left", 
                              "right", "bottom_left", "bottom", "bottom_right" ]
          for i in range(1, 9):
              self.images[ self.imageNames[i-1] ] = MyImage( "images/SAGEapp_0"+str(i)+".jpg" )       
  
  
      def RecalculateImages(self):
          # figure out all the sizes and positions for the decorative images and apply them
          positions = [ (0, 0),                 (self.GetLeft(), 0),                 (self.GetRight(), 0),
                        (0, self.GetTop()),                                          (self.GetRight(), self.GetTop()),
                        (0, self.GetBottom()),  (self.GetLeft(), self.GetBottom()),  (self.GetRight(), self.GetBottom()) ]
  
          sizes = [ (self.GetBorderWidth(), self.GetBorderWidth()),    (self.GetDisplayWidth(), self.GetBorderWidth()),    (self.GetBorderWidth(), self.GetBorderWidth()),
                    (self.GetBorderWidth(), self.GetDisplayHeight()),                                                      (self.GetBorderWidth(), self.GetDisplayHeight()),
                    (self.GetBorderWidth(), self.GetBorderWidth()/2),  (self.GetDisplayWidth(), self.GetBorderWidth()/2),  (self.GetBorderWidth(), self.GetBorderWidth()/2) ]
  
          for i in range(0, 8):
              image = self.images[ self.imageNames[i] ]
              image.Rescale( sizes[i][0], sizes[i][1] )
              image.SetX( positions[i][0] )
              image.SetY( positions[i][1] )
              
  
      def RecalculateTiles(self):
          l = self.GetBorderWidth()
          r = self.GetCanvasWidth() - self.GetBorderWidth()
          t = self.GetBorderWidth()
          b = t + self.GetDisplayHeight()
          self.backdrop.SetSize(l,r,t,b)
          
  
      def RecalculateShapes(self):
          # resize and reposition all the shapes
          # get the SAGE coordinates for the shape and convert them to UI coords again
          for s in self.GetShapeList():
              sageApp = self.sageData.getApp(s.GetId())
              s.Recalculate(
                  self.ToUICoordsX(sageApp.getLeft(), sageApp.getDisplayId()),
                  self.ToUICoordsX(sageApp.getRight(), sageApp.getDisplayId()),
                  self.ToUICoordsY(sageApp.getTop(), sageApp.getDisplayId()),
                  self.ToUICoordsY(sageApp.getBottom(), sageApp.getDisplayId())
                  )
  
  
  
  
  #----------------------------------------------------------------------
  # TILING SHAPES
  #----------------------------------------------------------------------
  
      def TileShapes(self):
          if self._shapesTiled:  # restore the shape to their previous state
              for shape in self.GetShapeList():
                  if not shape.GetMinimized():
                      shape.RestoreOldParams()
                      shape.UpdateSAGEParams()
              self._shapesTiled = False
              return
  
  
  
          # figure out by how much we need to size down each shape so that they have a chance
          # of fitting them inside the tiled display (if perfectly tiled... which is very unlikely to happen))
          displayArea = self.GetDisplayHeight() * self.GetDisplayWidth()
          totalShapeArea = 0
          for shape in self.GetShapeList():
              if not shape.GetMinimized():
                  totalShapeArea = totalShapeArea + shape.GetWidth() * shape.GetHeight()
  
          resizeRatio = 1
          if displayArea <= totalShapeArea*0.8:
              resizeRatio = totalShapeArea*0.8 / displayArea
  
          # set aside the shapes that are eligible for tiling (meaning no minimized shapes)
          # and preserve their size and position for later restore
          hashByArea = {}
          for shape in self.GetShapeList():
              if shape.GetMinimized():
                  continue       #skip minimized shapes
              if not shape.GetMaximized(): 
                  shape.PreserveOldParams()  #preserve the current size and position
              shape.SetSize( shape.GetWidth()/resizeRatio, shape.GetHeight()/resizeRatio )
              shape.UpdateSAGEParams()
              hashByArea[ (shape.GetWidth() * shape.GetHeight()) ] = shape  #key the shapes eligible for tiling by their area
  
  
          # now the hardest part, reposition the shapes so that they fit nicely (or as nice as it gets)
          # actually, try the other way around... first reposition the shapes and then resize them
          # so that they all fit inside the display
          
  
          self._shapesTiled = True
  
          
  #----------------------------------------------------------------------
  # DEALING WITH THE SHAPES
  #----------------------------------------------------------------------
  
      def GetShape(self, windowId):
          if self.shapes.has_key(windowId):
              return self.shapes[windowId]
          else:
              return None
  
      def GetShapeList(self):  # returns a copy of the shape list
          tempList = []
          for s in self.diagram.GetShapeList(): 
              if ( s.__class__.__name__ == MyRectangleShape.__name__ ):
                  tempList.append(s)
          return tempList
  
      def GetTopShape(self):  # returns the windowId of the shape that's on top
          minZ = sys.maxint
          windowId = sys.maxint
          for s in self.shapes.values():
              if s.GetZ() < minZ or (s.GetZ() == minZ and s.GetId() < windowId):
                  minZ = s.GetZ()
                  windowId = s.GetId()
          return windowId
      
  ##     # draw boxes in the background as the frames
  ##     # which should not be choosable or clickable
  ##     def MyAddBackShape(self, shape, x, y, pen, brush, text):
  ##         # Composites have to be moved for all children to get in place
  ##         shape.SetDraggable(False, False)
  ##         shape.SetCanvas(self)
  ##         shape.SetX(x)
  ##         shape.SetY(y)
  ##         if pen:    shape.SetPen(pen)
  ##         if brush:  shape.SetBrush(brush)
  ##         if text:
  ##             for line in text.split('\n'):
  ##                 shape.AddText(line)
  ##         self.diagram.AddShape(shape)
  ##         shape.Show(True)
  
  ##         return shape
  
  
      #----------------------------------------------------------------------
  
  
      # creates the shape that corresponds to the app and adds it to the diagram.
      def MyAddShape(self, app):
          name = app.getName()
          left = self.ToUICoordsX(app.getLeft(), app.getDisplayId())
          right = self.ToUICoordsX(app.getRight(), app.getDisplayId())
          bottom = self.ToUICoordsY(app.getBottom(), app.getDisplayId())
          top = self.ToUICoordsY(app.getTop(), app.getDisplayId())
          width = right - left
          height = bottom - top
          id = app.getId()
          z = app.getZvalue()
          title = app.getTitle()
          
          shape = MyRectangleShape(width, height, app, self.sageGate, self, title) 
          text = name + " " + str(id)
                             
          shape.SetDraggable(True, True)
          shape.SetCanvas(self)
          shape.SetMyX(left)
          shape.SetMyY(top)
          shape.SetId(id) #this is windowId
          shape.SetZ(z) 
          shape.SetPen(wx.Pen(shapeBorderColor))#wx.BLACK_PEN)
          shape.SetBrush( wx.Brush(shapeColor) ) #wx.GREY_BRUSH )
          shape.SetTextColour("grey")
          shape.SetFont(wx.Font(14, wx.MODERN, wx.NORMAL, wx.BOLD))
              
          if text:
              shape.SetName(name)
              for line in text.split('\n'):
                  shape.AddText(line)
              shape.AddText(shape.GetTitle())
          shape.SetShadowMode(ogl.SHADOW_NONE)#RIGHT) # uncomment for shadow
          shape.SetMaintainAspectRatio(prefs.visual.GetKeepAspectRatio()) 
          shape.SetCentreResize(False)   #resize the shape from one corner only
          self.diagram.AddShape(shape)  #add the shape to the end (draws on the top)
          shape.Show(True)
  
          evthandler = MyEvtHandler(self.log, self.frame, self.sageGate, self)
          evthandler.SetShape(shape)
          evthandler.SetPreviousHandler(shape.GetEventHandler())
          shape.SetEventHandler(evthandler)
  
          self.shapes[id] = shape #store the newly created shape in a hash
          return shape
  
      #----------------------------------------------------------------------
  
      # removes the shape from the canvas and deletes it
      def MyRemoveShape(self, id):
          shape = self.shapes[id]
          canvas = shape.GetCanvas()
          self.FreeMinimizedPosition(shape.minimizedPosition, shape.app)
          del self.shapes[id] #remove the shape from the hash
          shape.RemoveFromCanvas(self)
          del shape
  
      #----------------------------------------------------------------------
  
  ##      # here there's no need to refill the diagram since all the shapes are in order
  ##     # we just have to update their z values. No messages are sent to SAGE
  ##     def UpdateZsForAdd(self):
  ##         for s in self.GetShapeList(): #[:]:
  ##             s.SetZ(s.GetZ()+1)
  
      def OrderByZ(self):
          diagram = self.GetDiagram()
          shapeList = self.GetShapeList()
          tempHash = {}  #shapes keyed by Z value for easy ordering
          selectedShape = None
  
          # now go through all the shapes and update them visually according to the new z values
          for s in shapeList[:]:
              s.SetZ( self.sageData.getZvalue(s.GetId()) )
              tempHash[ s.GetZ() ] = s
              if s.Selected():
                  selectedShape = s
              diagram.RemoveShape(s)
          
          # sort the shapes so that they are in the right order for drawing
          tempKeys = tempHash.keys()
          tempKeys.sort()
          tempKeys.reverse()
  
          # now add all the shapes back sorted by Z
          for z in tempKeys:
              tempHash[z].DeleteControlPoints()
              diagram.AddShape(tempHash[z])
  
          # select the previously selected shape
          if selectedShape is not None:
              selectedShape.MakeControlPoints()
  
         
      #----------------------------------------------------------------------
  
      # detects when the mouse pointer is over a control point and changes the pointer itself
      def OnMouseEnter(self, event):
          x = event.GetX()
          y = event.GetY()
  
          # stop this event here so that we can make other events
          # (clicks on the shape to resize it)
          event.StopPropagation()
          event.Skip()
          
          shape = self.FindShape(x,y)[0]
          if shape == None:
              return
  
          # set the correct cursor based on the location of the control point
          if shape.__class__.__name__ == MyControlPoint.__name__:
              if not self.cursorSet:   # set the cursor only once (ie, if it's set, don't set it again)
                  if shape.location == "NE" or shape.location == "SW":
                      self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENESW))
                  else:
                      self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENWSE))
                  self.cursorSet = True
  
          elif self.cursorSet:    # we only want to reset the cursor if it was previously set
              self.cursorSet = False
              self.SetCursor(wx.STANDARD_CURSOR)
  
  
      #----------------------------------------------------------------------
  
          # this keeps track of where the minimized app should be located
          # the hash holds windowIds but the keys are actually more important
          # if a key exists, that means that there is an app in that spot so
          # return the first free spot
      def GetNextMinimizedPosition(self, app):
          for i in range(0,30):
              if not i in self._minimizedShapes[app.getDisplayId()]:
                  self._minimizedShapes[app.getDisplayId()][i] = app
                  return i
  
          # when the app is not minimized anymore, it has to free its spot
      def FreeMinimizedPosition(self, pos, app):
          if pos in self._minimizedShapes[app.getDisplayId()]:
              if self._minimizedShapes[app.getDisplayId()][pos] == app:
                  del self._minimizedShapes[app.getDisplayId()][pos]
              
  
  #----------------------------------------------------------------------
  # CANVAS PREFERENCES AND PROPERTIES
  #----------------------------------------------------------------------
  
      def ShowLibrary(self):
          #reload(fileViewer)  # for debugging only!
          fileViewer.FileLibrary(self, self.frame.GetPositionTuple())
  
      def ChangeBackgroundColor(self, (r,g,b)):
          return
      
      ##     tileColor = wx.Colour(r,g,b)
  ##         for xOffset in range(self.iTileColumns):
  ##             for yOffset in range(self.iTileRows):
  ##                 self._LCDPanels[yOffset][xOffset].SetBrush(wx.Brush(tileColor))
  ##         self.Redraw()
          
  
      def SetMaintainAspectRatio(self, ar):
          prefs.visual.SetKeepAspectRatio(ar)
  
      def GetMaintainAspectRatio(self):
          return prefs.visual.GetKeepAspectRatio()
  
  
          # stop or start receiving performance data
      def ReceivePerformanceData(self, doReceive):
          if doReceive:
              prefs.visual.SetReceivePerformanceData(True)
              for shape in self.GetShapeList():
                  self.sageGate.startPerformance(shape.GetId())
          else:
              prefs.visual.SetReceivePerformanceData(False)
              for shape in self.GetShapeList():
                  self.sageGate.stopPerformance(shape.GetId())
  
  
          # returns the bounds of the display in UI coords
      def GetUIDisplayBounds(self, displayId):
          return self.backdrop.GetDisplayBounds(displayId)
      
      
      # for getting the bounds of the area where the displays will be
      def GetLeft(self):
          return self.GetBorderWidth()
  
      def GetRight(self):
          return self.GetDisplayWidth() + self.GetBorderWidth()
  
      def GetTop(self):
          return self.GetBorderWidth()
  
      def GetBottom(self):
          return self.GetDisplayHeight() + self.GetBorderWidth()
  
  
      # tiled display are size (in the UI) - this is not for every individual display...
      def GetDisplayWidth(self):
          return self._maxDisplayWidth
  
      def GetDisplayHeight(self):
          return self._maxDisplayHeight
  
      def SetDisplayWidth(self, newWidth):
          self._maxDisplayWidth = newWidth
          
      def SetDisplayHeight(self, newHeight):
          self._maxDisplayHeight = newHeight
  
  
      # the top part of the UI (the window that holds the tiled display)
      def GetCanvasWidth(self):
          return self._maxCanvasWidth
  
      def GetCanvasHeight(self):
          return self._maxCanvasHeight
  
      def SetCanvasWidth(self, newWidth):
          self._maxCanvasWidth = newWidth
  
      def SetCanvasHeight(self, newHeight):
          self._maxCanvasHeight = newHeight
  
      def GetBorderWidth(self):
          return self._borderWidth
  
      def SetBorderWidth(self, newWidth):
          self._borderWidth = newWidth
  
      def SetAspectRatio(self, ratio):
          self._aspectRatio = ratio
  
      def GetAspectRatio(self):
          return self._aspectRatio
          
  
      def GetGraphManager(self):
          return self.__gmGraphManager
  
  
  
  #----------------------------------------------------------------------
  # SAGEGATE CALLBACKS
  #----------------------------------------------------------------------
  
      ### 40000 ###
      def OnSAGEStatusMsg(self):
          # this is where we make all the buttons for all the apps that SAGE supports
          hashAvailableApps = self.sageData.getAvailableApps()
  
          # figure out the positions for all the app buttons so that they are evenly spaced and centered
          # then, create the SAGEAppInitial for every one of them and make the buttons on the bottom panel
          buttonOffset = 40
          buttonNum = 1  
  
          # add the remote app button first
          self.parent.GetAppPanel().AddRemoteButton("REMOTE APPS", (buttonNum*buttonOffset, 10), self.sageGate)
  
          # now add the rest of the buttons from the local appLauncher (local to our SAGE)
          apps = hashAvailableApps.keys()
          apps.sort(lambda x, y: cmp(x.lower(), y.lower()))
          
          for appName in apps:
              buttonNum = buttonNum + 2
              temp = hashAvailableApps[appName]
              self.availableApps[appName] = hashAvailableApps[appName]
              self.parent.GetAppPanel().AddButton(self.availableApps[appName], (buttonNum*buttonOffset, 10))
              self.parent.GetAppPanel().AddConfigMenu(self.availableApps[appName])
  
                 
      #----------------------------------------------------------------------
      
  
      ### 40001 ###
      def OnSAGEAppInfoMsg(self, sageApp):
          # Here we just check if a shape for this "sageApp" exists and
          # if it does, well, then we dont wanna create a new one but modify the old one
          windowId = sageApp.getId()
          if not self.shapes.has_key(windowId):  # CREATING A NEW SHAPE
              newShape = self.MyAddShape(sageApp)
              self.parent.GetAppInfoPanel().AddInstanceButton(sageApp)
              self.OrderByZ()  # the shapes may arrive out of order
              
              # (AKS 2005-04-05) Only start performance monitoring when a new application
              # is generated.
              if prefs.visual.GetReceivePerformanceData():
                  self.sageGate.startPerformance( windowId, 2 )
              
          else:                          # MODIFYING A SHAPE
              shape = self.shapes[windowId]
              newWidth = self.ToUIWidth( sageApp.getLeft(), sageApp.getRight(), sageApp.getDisplayId() )
              newHeight = self.ToUIHeight( sageApp.getBottom(), sageApp.getTop(), sageApp.getDisplayId() )
              shape.SetSize(newWidth, newHeight)          
              shape.SetX( round (self.ToUICoordsX( sageApp.getLeft(), sageApp.getDisplayId() ) + int(newWidth/2)) )
              shape.SetY( round (self.ToUICoordsY( sageApp.getTop(), sageApp.getDisplayId() ) + int(newHeight/2)) )
              shape.SetTitle( sageApp.getTitle() )
              shape.SetOrientation( sageApp.getOrientation() )
              shape.ResetControlPoints() 
              
  
          self.Redraw()
              
                  
      #----------------------------------------------------------------------
      
  
      # 40002
      def OnSAGEPerfInfoMsg(self, windowId ):
          # (AKS 2005-04-05) Performance monitoring information
          ## print "**** sageui >> 40002 >> update graph manager"
          self.__gmGraphManager.update( windowId )
          #print
          #print "TOTALS"
          #print "------"
          stats = self.sageData.getTotals()
          #print "Render total bandwidth = ", stats[ self.sageData.I_RENDER_TOTAL_BANDWIDTH ]
          #print "Render average framerate = ", stats[ self.sageData.I_RENDER_AVG_FRAME_RATE ]
          #print "Render total nodes = ", stats[ self.sageData.I_RENDER_TOTAL_NODES ]
          #print "Display total bandwidth = ", stats[ self.sageData.I_DISPLAY_TOTAL_BANDWIDTH ]
          #print "Display average framerate = ", stats[ self.sageData.I_DISPLAY_AVG_FRAME_RATE ]
          #print "Display total nodes = ", stats[ self.sageData.I_DISPLAY_TOTAL_NODES ]
  
  
  
      #----------------------------------------------------------------------
  
  
      # 40003
      def OnSAGEAppShutdownMsg(self, sageApp):
          # turn the button off and close the app
          self.MyRemoveShape(sageApp.getId())
          self.parent.GetAppInfoPanel().RemoveInstanceButton(sageApp)
          self.Redraw()
  
  
          ## # (AKS 2005-04-05) Stop performance monitoring that began "by default"
          # when a new application had started.
          #self.sageGate.stopPerformance( windowId )  #this needs to be called BEFORE we close the app and not after...
          
          self.__gmGraphManager.removeGraph( sageApp.getId() )
  
          
      #----------------------------------------------------------------------
      
  
      # 40004
      def OnSAGEDisplayInfoMsg(self):
          self.displayInfo = self.sageData.getDisplayInfo()
  
          # create the hash for each of the displays that holds the minimized shapes
          for sageDisplay in self.displayInfo.getAllDisplays():
              self._minimizedShapes[sageDisplay.displayId] = {}
          
          # for one display and old SAGE there are no connection messages so just
          # pretend that one came in for drawing everything correctly
          if self.displayInfo.getNumDisplays() == 1:
              self.OnSAGEDisplaysConnection()
  
               
      #----------------------------------------------------------------------
  
  
      # 40007
      def OnSAGEDisplaysConnection(self):
          self.tiledDisplayWidth = self.displayInfo.getTotalWidth()
          self.tiledDisplayHeight = self.displayInfo.getTotalHeight()
          
          # now we have all the data needed to set up the sizes of the canvas and display part
          self.SetDisplayWidth( self.GetCanvasWidth() - 2 * self.GetBorderWidth() )
          heightFactor = self.tiledDisplayWidth / float (self.GetDisplayWidth() )
          self.SetDisplayHeight( round (self.tiledDisplayHeight / heightFactor ) )
          self.SetCanvasHeight( self.GetDisplayHeight() + 1.5 * self.GetBorderWidth())
          
          self.EnableScrolling(False, False)
          self.SetSize((self.GetCanvasWidth(), self.GetCanvasHeight()))
          self.SetAspectRatio( float(self.GetCanvasWidth()) / self.GetCanvasHeight() )
  
  
          # figure out the backdrop panels
          self.displayInfoArrived = True  #set the flag so that we can begin resizing
          l = self.GetBorderWidth()
          r = self.GetCanvasWidth() - self.GetBorderWidth()
          t = self.GetBorderWidth()
          b = self.GetCanvasHeight() - self.GetBorderWidth()
  
          self.backdrop = Backdrop(self.displayInfo, self)
          self.backdrop.SetupDisplays(l,r,t,b)
  
  
          # resize the parent to be of the right size for its contents
          if self.parent.GetNotebook().GetPageCount() < 2:  #if this is the first display, resize the frame
              displaySize = wx.DisplaySize()  #the resolution of the current screen
              if prefs.visual.GetFrameSize() != None:  # read the last state from the preferences
                  self.frame.SetSizeHints(400, 350, displaySize[0], displaySize[1])
                  self.frame.SetClientSize( prefs.visual.GetFrameSize() )
              else:                                    # there are no previous preferences saved so figure it out
                  appCanvasWidth, appCanvasHeight = self.parent.GetAppInfoPanel().GetSize()
                  self.frame.SetSizeHints(400, 350, displaySize[0], displaySize[1])  #minimum size = (400, 400)
                  self.frame.SetClientSize((self.GetCanvasWidth(), self.GetCanvasHeight() + appCanvasHeight)) # set the initial size of the frame
                  if self.frame.GetSize().GetHeight() > displaySize[1]:  #is the frame too big for the desktop??? if so, size it back down
                      heightDifference = self.frame.GetSize().GetHeight() - displaySize[1]
                      self.frame.SetClientSize((self.GetCanvasWidth(), self.GetCanvasHeight() + appCanvasHeight - heightDifference))
              
              # figure out the positions and the sizes of all the decorative images
              self.RecalculateImages()
              ##set up a swap buffer for double buffering
              self.swapBuffer = wx.EmptyBitmap(self.GetCanvasWidth(), self.GetCanvasHeight())
              dc = wx.BufferedDC(None, self.swapBuffer)
              dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
              dc.Clear()
              self.Redraw(dc)
          else:   # since we didn't resize the frame, we have to fit the contents inside of it
              ##set up a swap buffer for double buffering
              self.swapBuffer = wx.EmptyBitmap(self.GetCanvasWidth(), self.GetCanvasHeight())
              dc = wx.BufferedDC(None, self.swapBuffer)
              dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
              dc.Clear()
              self.parent.OnSize()
              #self.Redraw(dc)
          del dc
  
          # call the parent if something needs to be done
          self.parent.OnDisplayInfo()
  
          # add the pointers
          #self.ptrs = Pointers(self.sageGate, self.sageData)
          
          # show the frame when all the buttons have been created (just for visual purposes)
          self.frame.Show()
          if self.frame.usersClient.connected:
              if prefs.visual.IsChatShown() == None or prefs.visual.IsChatShown():
                  self.frame.GetUsersPanel().Show()
  
  
      
          
      #----------------------------------------------------------------------
      
  
      # 40005
      def OnSAGEZChangeMsg(self):
          self.OrderByZ() # redraw the shapes based on the new order
          self.Redraw()
  
          
      #----------------------------------------------------------------------
      
  
      # 40006
      def OnSAGEAppExecConfig(self, appInitial):
          self.parent.GetAppPanel().AddConfigMenu(appInitial)
  
  
  
  #----------------------------------------------------------------------
  #  UI ---> SAGE coordinate conversion
  #----------------------------------------------------------------------
  
      def ToSAGECoordsX (self, uiX, displayId, doRound=True):
          bounds = self.backdrop.GetDisplayBounds(displayId)
          uiX = uiX - bounds.left
          sageX = (float(uiX) / bounds.getWidth()) * self.displayInfo.getDisplay(displayId).sageW
          if doRound:
              sageX = round(sageX)
          return sageX
  
      def ToSAGECoordsY (self, uiY, displayId, doRound=True):
          bounds = self.backdrop.GetDisplayBounds(displayId)
          uiY = uiY - bounds.top
          sageY = (1-(float(uiY) / bounds.getHeight())) * self.displayInfo.getDisplay(displayId).sageH
          if doRound:
              sageY = round(sageY)
          return sageY
  
      def ToSAGEDistX (self, oldX, newX, displayId):   
          return round( self.ToSAGECoordsX( newX, displayId, False ) - self.ToSAGECoordsX( oldX, displayId, False ) )
  
      def ToSAGEDistY (self, oldY, newY, displayId):   
          return round( self.ToSAGECoordsY( newY, displayId, False ) - self.ToSAGECoordsY( oldY, displayId, False ) )
      
  
  #----------------------------------------------------------------------
  #  SAGE ---> UI coordinate conversion   
  #----------------------------------------------------------------------
  
      def ToUICoordsX (self, sageX, displayId, doRound=True):
          bounds = self.backdrop.GetDisplayBounds(displayId)
          uiX = (float(sageX) / self.displayInfo.getDisplay(displayId).sageW) * bounds.getWidth()
          uiX += bounds.left
          
          if doRound:
              uiX = round(uiX)
          return uiX
  
      def ToUICoordsY (self, sageY, displayId, doRound=True):
          bounds = self.backdrop.GetDisplayBounds(displayId)
          uiY = (1 - (float(sageY) / self.displayInfo.getDisplay(displayId).sageH)) * bounds.getHeight()
          uiY += bounds.top
          
          if doRound:
              uiY = round(uiY)
          return uiY
  
      def ToUIWidth (self, leftSageX, rightSageX, displayId): 
          return round( abs(self.ToUICoordsX( leftSageX, displayId, False ) - self.ToUICoordsX( rightSageX, displayId, False )) )
      
      def ToUIHeight (self, bottomSageY, topSageY, displayId):
          return round( abs(self.ToUICoordsY( bottomSageY, displayId, False ) - self.ToUICoordsY( topSageY, displayId, False )) )
  
  
  
  
  
  ############################################################################
  #
  #  CLASS: AppInfoPanel
  #  
  #  DESCRIPTION: It describes the canvas that holds all the buttons for
  #               starting and stopping SAGE apps from within the SAGE UI.
  #               It also draws all the graphics that is used instead of the
  #               regular wx controls. It handles button clicks and other app
  #               specific actions. It does all the drawing with double buffering.
  #
  #  DATE:        December 26, 2004
  #
  ############################################################################
  
  class AppInfoPanel(wx.Panel):
      def __init__(self, frame, sageGate, parent, id, position, size, style):
          wx.Panel.__init__(self, parent, id, position, size, style)
          self.SetBackgroundColour('#888888')
          self.sageGate = sageGate
          self.parent = parent
          self.visible = True
          self.shrinkInfoPanel = False
          self.infoPanelMinimized = False
  
          # load the images for the arrows
          self.arrowLeft = MyBitmapButton(self, (self.GetDisplayCanvas().GetBorderWidth(), 10), (20,40), "images/scrollLeft_up.jpg", "images/scrollLeft_down.jpg", "images/scrollLeft_over.jpg") 
          self.arrowRight = MyBitmapButton(self, (self.GetDisplayCanvas().GetBorderWidth(), 10), (20,40), "images/scrollRight_up.jpg", "images/scrollRight_down.jpg", "images/scrollRight_over.jpg")
          self.arrowLeft.Bind(wx.EVT_LEFT_DOWN, self.OnLeftArrowDown)
          self.arrowLeft.Bind(wx.EVT_LEFT_UP, self.OnLeftArrowUp)
          self.arrowRight.Bind(wx.EVT_LEFT_DOWN, self.OnRightArrowDown)
          self.arrowRight.Bind(wx.EVT_LEFT_UP, self.OnRightArrowUp)
  
          # load the decorative images (edges)
          border = self.GetDisplayCanvas().GetBorderWidth()
          self.borderImages = []
          self.borderImages.append( MyImage( "images/SAGEapp_10.jpg", 0,0, False, border, size[1] - border/2))
          self.borderImages.append( MyImage( "images/SAGEapp_14.jpg", border,0, False, size[0]-2*border, size[1]-border/2))
          self.borderImages.append( MyImage( "images/SAGEapp_15.jpg", size[0]-border,0, False, border, size[1] - border/2))
          self.borderImages.append( MyImage( "images/SAGEapp_17.jpg", 0,size[1]-border/2, False, border, border/2))
          self.borderImages.append( MyImage( "images/SAGEapp_18.jpg", border,size[1]-border/2, False, size[0]-2*border,border/2))
          self.borderImages.append( MyImage( "images/SAGEapp_19.jpg", size[0]-border,size[1]-border/2, False, border,border/2))
          
          # bind events for the panel
          self.Bind(wx.EVT_PAINT, self.OnPaint)
          self.Bind(wx.EVT_ERASE_BACKGROUND, self.Chillout)
          if "__WXMSW__" in wx.PlatformInfo:  # this is needed on windows for proper painting after resize
              self.Bind(wx.EVT_SIZE, self.OnSize)
  
          # set up the area for the buttons to be in
          self.appPanel = AppPanel(self, 20,20, border+self.arrowLeft.GetSize().width,10)
          self.infoPanel = InfoPanel(self, 50,20, 100,20)
  
          # the show/hide buttons
          self.hideAppPanelButton = MyBitmapButton(self, (0,0), (40,12), "images/handle1.jpg")
          self.hideAppPanelButton.Bind(wx.EVT_LEFT_UP, self.OnHideAppPanelButton)
          self.hideInfoPanelButton = MyBitmapButton(self, (0,0), (12,40), "images/handle.jpg")
          self.hideInfoPanelButton.Bind(wx.EVT_LEFT_UP, self.OnHideInfoPanelButton)
  
          
  
      #----------------------------------------------------------------------
  
  
      def OnPaint(self, event=None):
          if DEBUG: print "    OnPaint --> AppInfoPanel"; sys.stdout.flush()
          dc = wx.PaintDC(self)
          if not self.GetDisplayCanvas().displayInfoArrived:
              return
          self.Redraw(dc)  # redraw first and then swap the buffer
              
  
      #----------------------------------------------------------------------
      
  
      def Redraw(self, dc=None):
          if DEBUG: print "    Redraw --> AppInfoPanel"; sys.stdout.flush()
          if not self.GetDisplayCanvas().displayInfoArrived:
              return
          if dc == None:
              if "__WXMAC__" in wx.PlatformInfo:
                  self.Refresh()
                  return
              dc = wx.ClientDC(self)
  
          # decorative images
          for image in self.borderImages:
              dc.DrawBitmap( image.bitmap, image.GetX(), image.GetY(), image.IsTransparent() )        
  
          del dc # to stop mac os from complaining about nested wxDCs
  
          # redraw the children
          self.infoPanel.Redraw()
          self.appPanel.Redraw()
  
          # redraw the controls on the panel
          self.hideAppPanelButton.OnPaint()
          self.hideInfoPanelButton.OnPaint()
          self.arrowLeft.OnPaint()
          self.arrowRight.OnPaint()
          
  
          
  
      #----------------------------------------------------------------------
  
      
      def Chillout(self, event):
          """ For intercepting the EVT_ERASE_BACKGROUND. Do nothing here on purpose
              in order to reduce the flicker during drawing"""
          pass
  
  
      #----------------------------------------------------------------------
  
  
      def OnSize(self, event=None):
          if DEBUG: print "    OnSize --> AppInfoPanel"; sys.stdout.flush()
          if not self.GetDisplayCanvas().displayInfoArrived:
              return
          # move the panel first to the bottom of the canvas
          canvasHeight = self.parent.GetDisplayCanvas().GetCanvasHeight()
          self.MoveXY(0, canvasHeight - 1)
          
          # resize the main AppInfoPanel now
          parentSize = self.parent.GetClientSize()
          if "__WXMSW__" in wx.PlatformInfo:  # a visual hack for windows
              newHeight = parentSize.height - canvasHeight - 21#canvasSize.height - 21
          else:
              newHeight = parentSize.height - canvasHeight #canvasSize.height
  	if newHeight < 1:
  	   newHeight = 1
             
          self.SetSize( (parentSize.width, newHeight) )
  
          # rescale and reposition all the decorative images (edges)
          border = self.parent.GetDisplayCanvas().GetBorderWidth()
          size = self.GetSizeTuple()
          if size[0] > 0 and size[1] > 0:    # resize only if the parent has a reasonable size
              self.borderImages[0].Recalculate( border, size[1] - border/2, 0,0)
              self.borderImages[1].Recalculate( size[0]-2*border, size[1]-border/2, border,0)
              self.borderImages[2].Recalculate( border, size[1] - border/2, size[0]-border,0)
              self.borderImages[3].Recalculate( border, border/2, 0,size[1]-border/2)
              self.borderImages[4].Recalculate( size[0]-2*border,border/2, border,size[1]-border/2)
              self.borderImages[5].Recalculate( border,border/2, size[0]-border,size[1]-border/2)
  
          # as we are sizing down, make the infoPanel disappear if the frame size got small enough
          self.shrinkInfoPanel = parentSize.width < (border*2 + 2*self.arrowLeft.GetSize().width + self.infoPanel.GetCurrentMinSize() + 50)
                  
          if (self.shrinkInfoPanel and self.infoPanel.visible):  # hide the info panel
              self.infoPanel.Minimize()
              self.hideInfoPanelButton.Hide()
          elif (not self.shrinkInfoPanel) and (not self.infoPanel.visible) and (not self.infoPanelMinimized):  #show it (once)
              self.infoPanel.Maximize()
              self.hideInfoPanelButton.Show()
  
          # now resize the children
          self.infoPanel.OnSize()
          self.appPanel.OnSize()
  
          # reposition the scrolling arrows for the appPanel
          self.arrowLeft.MoveXY(self.GetDisplayCanvas().GetBorderWidth(), 20)
          self.arrowRight.MoveXY(self.GetDisplayCanvas().GetBorderWidth()+self.appPanel.GetSize().width+self.GetArrowWidth(), 20)
          self.hideAppPanelButton.MoveXY( size[0]/2 - self.hideAppPanelButton.GetSize()[0]/2,  0 )
          self.hideInfoPanelButton.MoveXY( self.infoPanel.GetPosition().x - 16,  self.infoPanel.GetPosition().y + self.infoPanel.GetSize().height/2 - self.hideInfoPanelButton.GetSize()[1]/2 )
  
          self.Redraw()
  
  
      #----------------------------------------------------------------------
  
  
      def OnHideAppPanelButton(self, event=None):
          if self.visible:
              self.parent.HideAppInfoPanel()
              self.visible = False
              self.arrowRight.Hide()
              self.arrowLeft.Hide()
          else:
              self.parent.ShowAppInfoPanel()
              self.visible = True
              self.arrowRight.Show()
              self.arrowLeft.Show()
  
      def OnHideInfoPanelButton(self, event=None):
          if self.infoPanel.visible:  #hide it
              self.infoPanel.Minimize()
              self.infoPanelMinimized = True
              self.OnSize()
          else:              # it's already hidden so show it
              self.infoPanel.Maximize()
              self.infoPanelMinimized = False
              self.OnSize()
  
              
      def OnLeftArrowDown(self, event):
          self.arrowLeft.OnLeftDown(event)
          self.appPanel.ScrollLeft()
  
      def OnRightArrowDown(self, event):
          self.arrowRight.OnLeftDown(event)
          self.appPanel.ScrollRight()
  
      def OnLeftArrowUp(self, event):
          self.arrowLeft.OnLeftUp(event)
          self.appPanel.ScrollLeft()
  
      def OnRightArrowUp(self, event):
          self.arrowRight.OnLeftUp(event)
          self.appPanel.ScrollRight()
          
  
      #----------------------------------------------------------------------
      
      
      def AddInstanceButton(self, sageApp):
          # once the 40001 message arrives and the app doesnt exists yet, create an instance button for it
          self.appPanel.AddInstanceButton(sageApp)
  	self.Refresh()
  
      def RemoveInstanceButton(self, sageApp):
          # once the 40003 message arrives and we close the app, remove the instance button for it
          self.appPanel.RemoveInstanceButton(sageApp)
          self.Refresh()
  
          
      #----------------------------------------------------------------------
  
      def GetDisplayCanvas(self):
          return self.parent.GetDisplayCanvas()
  
      def GetArrowWidth(self):
          return self.arrowLeft.GetSize().width
  
      def GetInfoPanel(self):
          return self.infoPanel
      
      def GetAppPanel(self):
          return self.appPanel
  
  
  
  ############################################################################
  #
  #  CLASS: AppPanel
  #  
  #  DESCRIPTION: It describes the canvas that holds all the buttons for
  #               starting and stopping SAGE apps from within the SAGE UI.
  #               It also draws all the graphics that is used instead of the
  #               regular wx controls. It handles button clicks and other app
  #               specific actions. It does all the drawing with double buffering.
  #
  #  DATE:        April, 2005
  #
  ############################################################################
  
  class AppPanel(wx.Panel):
  
      def __init__(self, parent, width, height, x, y):
          wx.Panel.__init__(self, parent, -1, (x,y), (width,height), wx.NO_BORDER | wx.NO_FULL_REPAINT_ON_RESIZE)
          self.SetBackgroundColour(appPanelColor)
          self.page = parent.parent
          self.parent = parent
          self.visible = True
          self._appButtonWidth = 40
          self.appButtons = {} # has to store all the buttons keyed by the app_inst_IDs
          self.appNames = [] # list of all app names that we got from sage
          self.buttonOffset = 0 #used for positioning the buttons
          self.scrollWidth = width  #the minimum width required to draw all the app buttons
          self.first = None  # used for scrolling... these are first and last app buttons
          self.last = None
  
          # background for the app buttons
          self.bar = MyImage( "images/menu_bar.jpg", 0,10, False, 1500, self.GetButtonWidth())
          self.background = MyImage( "images/app_panel_background.png", 0,0, False, width, height)
  
          self.Bind(wx.EVT_ERASE_BACKGROUND, self.Chillout)
          self.Bind(wx.EVT_PAINT, self.OnPaint)
          if wx.Platform == "__WXMSW__":  # this is needed on windows for proper painting after resize
              self.Bind(wx.EVT_SIZE, self.OnSize)
          
  
      #----------------------------------------------------------------------
  
      def OnPaint(self, event=None):
          if DEBUG: print "\tOnPaint --> AppPanel"; sys.stdout.flush()
          dc = wx.PaintDC(self)
          self.PrepareDC(dc)
  	self.Redraw(dc)
  
     
      def Redraw(self, dc=None):
          if DEBUG: print "\tRedraw --> AppPanel"; sys.stdout.flush()
  	if dc == None:
              if "__WXMAC__" in wx.PlatformInfo:
                  self.Refresh()
                  return
              dc = wx.ClientDC(self)
              self.PrepareDC(dc)
  
          # first draw the bar and the background
          dc.DrawBitmap( self.background.bitmap, self.background.GetX(), self.background.GetY(), self.background.IsTransparent())
          dc.DrawBitmap( self.bar.bitmap, self.bar.GetX(), self.bar.GetY(), self.bar.IsTransparent() )
          del dc  # for mac os to stop complaining about nested wxDCs
  
          # draw all the app buttons in their respective locations
          for name, button in self.appButtons.iteritems():
              button.OnPaint()
          
  
      def Chillout(self, event):
          """ For intercepting the EVT_ERASE_BACKGROUND. Do nothing here on purpose
              in order to reduce the flicker during drawing"""
          pass
  
  
      def OnSize(self, event=None):
          if DEBUG: print "\tOnSize --> AppPanel"; sys.stdout.flush()
          # get some measures that will help us determine the size of the panel
          parentSize = self.parent.GetClientSize()
          borderWidth = self.parent.GetDisplayCanvas().GetBorderWidth()
          arrowWidth = self.parent.GetArrowWidth()
          infoPanelWidth = self.parent.GetInfoPanel().GetSize().width#GetInfoPanelWidth()
  
          # determine the new size
          newWidth = parentSize.width - infoPanelWidth - 2*arrowWidth - 2*borderWidth - 20
          newHeight = parentSize.height - 1.5*borderWidth
  	if newHeight < 1:
  	   newHeight = 1
  	if newWidth < 1:
  	   newWidth = 1
          self.SetSize( (newWidth, newHeight) )
  
          #resize the background image
          self.background.Rescale(newWidth, newHeight)
          self.Redraw()
  
  
      #----------------------------------------------------------------------
  
      
      def ScrollLeft(self):
          if not self.IsButtonVisible( self.first ):
              self.ScrollWindow( 30, 0 )
              self.Update()
                  
  
      def ScrollRight(self):
          if not self.IsButtonVisible( self.last ):
              self.ScrollWindow( -30, 0 )
              self.Update()
  
  
      def IsButtonVisible(self, button):
          # Used in scrolling... checks if a button is on the screen.
          # If not, return false so that we have to scroll
          x = button.GetPosition().x
          width = button.GetSize().width
  
          if (x > 0) and ((x+width) < self.GetSize().width):
              return True
          else:
              return False
              
  
      #----------------------------------------------------------------------
  
  
      def AddRemoteButton(self, name, (x,y), sageGate):
          self.appNames.append(name)
          
          # create the buttons and check if images are available for them
          if os.path.isfile( ConvertPath("images/remote_up.jpg")):
              bitmapName = "images/remote"
          else:
              bitmapName = "images/default"
          self.appButtons[ name ] = RemoteAppButton( self, bitmapName+"_up.jpg", bitmapName+"_down.jpg", bitmapName+"_over.jpg", (x,y), (self.GetButtonWidth(),self.GetButtonWidth()), sageGate)
          self.buttonOffset = self.buttonOffset + 1
  
          # this is the total area taken by the buttons (so that they are all displayed at once)
          self.scrollWidth = self.GetButtonWidth() * 2 * len(self.appButtons)
      
          # remember which is the first and the last button... used for scrolling
          if self.first == None:
              self.first = self.appButtons[ name ]
              self.last = self.appButtons[ name ]  #the first is also the last
          else:
              self.last = self.appButtons[ name ]
  
  
      def AddButton(self, app, (x,y)):
          self.appNames.append(app.GetName())
          
          # create the buttons and check if images are available for them
          if os.path.isfile( ConvertPath("images/" + string.lower(app.GetName()) + "_up.jpg")):
              bitmapName = "images/" + string.lower(app.GetName())
          else:
              bitmapName = "images/default"
          self.appButtons[ app.GetName() ] = AppButton( self, bitmapName+"_up.jpg", bitmapName+"_down.jpg", bitmapName+"_over.jpg", (x,y), (self.GetButtonWidth(),self.GetButtonWidth()), app)
          self.buttonOffset = self.buttonOffset + 1
  
          # this is the total area taken by the buttons (so that they are all displayed at once)
          self.scrollWidth = self.GetButtonWidth() * 2 * len(self.appButtons)
      
          # remember which is the first and the last button... used for scrolling
          if self.first == None:
              self.first = self.appButtons[ app.GetName() ]
          else:
              self.last = self.appButtons[ app.GetName() ]
  
  
      def RepositionAppButtons(self):
          # repositions all the app buttons and redraws them
          # the app buttons then reposition all the instance buttons and redraw them
          buttonOffset = round( self.GetSize().width / float( len(self.appNames)*2+1 ) )
          buttonNum = 1
          
          for k, btn in self.appButtons.iteritems():
              btn.MoveXY(buttonNum*buttonOffset, 10)
              buttonNum = buttonNum + 2
  
  
      def GetButtonWidth(self):
          return self._appButtonWidth
      
      def GetAppInfoPanel(self):
          return self.parent
  
  
      #----------------------------------------------------------------------
  
      # fills the remote application button with the correct menu data for each app launcher
      def AddLauncherMenu(self, name, launcherHash):
          self.appButtons[ name ].AddLauncherMenu(launcherHash)    
  
      # once the 40006 message arrives, we add a menu to the button
      def AddConfigMenu(self, appInitial):
          self.appButtons[ appInitial.GetName() ].AddConfigMenu(appInitial)
      
      # once the 40001 message arrives and the app doesnt exists yet, create an instance button for it
      def AddInstanceButton(self, sageApp):
          if sageApp.getName() in self.appButtons:
              self.appButtons[ sageApp.getName() ].AddInstanceButton(sageApp)
  
      # once the 40003 message arrives and we close the app, remove the instance button for it
      def RemoveInstanceButton(self, sageApp):
          if sageApp.getName() in self.appButtons:
              self.appButtons[ sageApp.getName() ].RemoveInstanceButton(sageApp.getId())
              self.GetAppInfoPanel().GetInfoPanel().OnPerfPanelRemoved(sageApp.getId())
  
  
  
  
          
  ############################################################################
  #
  #  CLASS: InfoPanel
  #  
  #  DESCRIPTION: It describes the canvas that holds all the buttons for
  #               starting and stopping SAGE apps from within the SAGE UI.
  #               It also draws all the graphics that is used instead of the
  #               regular wx controls. It handles button clicks and other app
  #               specific actions. It does all the drawing with double buffering.
  #
  #  DATE:        April, 2005
  #
  ############################################################################
  
  class InfoPanel(wx.Panel):
  
      def __init__(self, parent, width, height, x, y):
          wx.Panel.__init__(self, parent, -1, (x,y), (width,height), wx.NO_BORDER | wx.NO_FULL_REPAINT_ON_RESIZE)
          self.SetBackgroundColour(appPanelColor)
          self.page = parent.parent
          self.parent = parent
          self.SetVirtualSize( (width, height) )
          self.visible = True
          self._minWidth = 250
          self.width = 0 #initial width  
          self.displayCanvas = self.page.GetDisplayCanvas()
          self.dataPanels = []
          
          self.background = MyImage( "images/app_panel_background.png", 0,0, False, width, height)
          
          if "__WXMSW__" in wx.PlatformInfo:  # this is needed on windows for proper painting after resize
              self.Bind(wx.EVT_SIZE, self.OnSize)
          elif "__WXGTK__" in wx.PlatformInfo:
              self.Bind(wx.EVT_PAINT, self.OnPaint)
          self.Bind(wx.EVT_ERASE_BACKGROUND, self.Chillout)
          
      #----------------------------------------------------------------------
  
      def Chillout(self, event):
          """ For intercepting the EVT_ERASE_BACKGROUND. Do nothing here on purpose
              in order to reduce the flicker during drawing"""
          pass
  
  
      def OnPaint(self, event=None):
          if not self.visible: dc=wx.PaintDC(self); return
          if DEBUG: print "\tOnPaint --> InfoPanel"; sys.stdout.flush()
          dc = wx.PaintDC(self)
          self.PrepareDC(dc)
        	self.Redraw(dc)
  
     
      def Redraw(self, dc=None):
          if not self.visible: return
          if DEBUG: print "\tRedraw --> InfoPanel"; sys.stdout.flush()
          if dc == None:
              if "__WXMAC__" in wx.PlatformInfo:
                  self.Refresh()
                  return
              dc = wx.ClientDC(self)
              self.PrepareDC(dc)
          
          if "__WXMSW__" in wx.PlatformInfo:
              self.Refresh()
              self.Update()
  
          dc.DrawBitmap( self.background.bitmap, self.background.GetX(), self.background.GetY(), self.background.IsTransparent())
          dc.SetBrush(wx.TRANSPARENT_BRUSH)
          dc.SetPen(wx.BLACK_PEN)
          dc.DrawRectangle(2, 2, self.GetClientSize()[0]-2, self.GetClientSize()[1]-2)
          dc.SetPen(wx.Pen(wx.Colour(153, 153, 153)))
          dc.DrawRectangle(0, 0, self.GetClientSize()[0]-2, self.GetClientSize()[1]-2)
          del dc
          
  
          
      def OnSize(self, event=None):
          #if not self.visible: return
          if DEBUG: print "\tOnSize --> InfoPanel"; sys.stdout.flush()
          # get some measures that will help us determine the size of the panel
          parentSize = self.GetAppInfoPanel().GetClientSize()
          borderWidth = self.GetAppInfoPanel().GetDisplayCanvas().GetBorderWidth()
          arrowWidth = self.GetAppInfoPanel().GetArrowWidth()
  
          # the new size depends on if there is a dataPanel in there or not
          # if not, the whole InfoPanel will be of default size
          if not self.visible or self.GetDataPanelWidth() == 0:   #not visible, setting width to 0
              newWidth = 0
              self.Hide()
          else:          #dataPanel, setting width to dataPanelWidth
              self.Show()
              newWidth = self.GetDataPanelWidth() + 7  #+5 added to allow for the border
          newHeight = parentSize.height - 1.5*borderWidth
  
          # apply the changes 
          self.SetSize( (newWidth, newHeight) )
          self.SetVirtualSize( (self.GetVirtualSize().width, self.GetVirtualSize().height) )
          self.MoveXY( parentSize.width - borderWidth - newWidth, self.GetPosition().y)
  
          # hide the little scrolly thingy button from the AppInfoPanel
          if newHeight < 2:
              self.GetAppInfoPanel().hideInfoPanelButton.Hide()
          else:
              self.GetAppInfoPanel().hideInfoPanelButton.Show()
  
          #resize the background image
          self.background.Rescale(newWidth, newHeight)
          self.Redraw()
          
  
      #----------------------------------------------------------------------
      
  
      def Minimize(self):
          self.visible = False
          self.Hide()
  
      def Maximize(self):
          self.visible = True
          self.Show()
  
      def GetCurrentMinSize(self):
          if self.GetDataPanelWidth() == 0:  #no dataPanel, setting width to default
              newWidth = self.GetMinimumWidth()
          else:          #dataPanel, setting width to dataPanelWidth
              newWidth = self.GetDataPanelWidth()
          return newWidth
  
      def GetAppInfoPanel(self):
          return self.page.GetAppInfoPanel()
      
      def GetMinimumWidth(self):
          return self._minWidth
  
      def SetMinimumWidth(self, newMinWidth):
          self._minWidth = newMinWidth
  
          
      #----------------------------------------------------------------------
  
  
      # this shows all the data that's relevant to the app
      def ShowData(self, appName, windowId):
  	if self.visible:
  	    self.ShowPerformanceData(appName, windowId)
          
  
          # shows the performance data panel in this InfoPanel
      def ShowPerformanceData(self, appName, windowId):
          if self.displayCanvas.GetGraphManager().GetSparklineGraph(windowId) == -1: #does the panel exist yet?
              self.displayCanvas.GetGraphManager().addGraph(appName, windowId, self)
              dataPanel = self.displayCanvas.GetGraphManager().GetSparklineGraph(windowId)
              self.dataPanels.append(dataPanel)  # add it to the list of panels
  	    if len(self.dataPanels) < 2:
  		self.Freeze()
  		self.GetAppInfoPanel().OnSize()  #refresh/resize the panels
  		self.Thaw()
          else:   #panel already exists so just show it on top of the other ones
              dataPanel = self.displayCanvas.GetGraphManager().GetSparklineGraph(windowId)
              self.Freeze()
  	    for p in self.dataPanels:  #hide all the other panels but show the one that's selected
                  try:
                      p.Hide()
                  except Exception:  # an exception will occur if there are panels in the list that dont exist anymore
                      del p  #this will delete it from the list as well
              dataPanel.Show()
  	    self.Thaw()
              #if not "__WXGTK__" in wx.PlatformInfo:  #extra refresh needed on Mac and Windows
              #    self.GetAppInfoPanel().OnSize()
  
              
              # returns the width of the dataPanel, if none exist it returns a 0
      def GetDataPanelWidth(self):
          try:
              width = self.dataPanels[0].GetSize().width
          except Exception:
              del self.dataPanels
              self.dataPanels = []
              return 0
          else:
              return width
  
  
          # resizes the parent panel to a default size if there are no more perf panels in here
      def OnPerfPanelRemoved(self, windowId):
          dataPanel = self.displayCanvas.GetGraphManager().GetSparklineGraph(windowId)
  
          try:
              self.dataPanels.remove(dataPanel)
          except Exception:
              pass
          else:
              if len(self.dataPanels) < 1:
                  self.GetAppInfoPanel().OnSize()  #resize and refresh the parent panel
  
  
  
  