text: # Name:         AttributePanel.py
  # Purpose:      View components for editing attributes
  # Author:       Roman Rolinsky <rolinsky@mema.ucl.ac.be>
  # Created:      17.06.2007
  # RCS-ID:       $Id: AttributePanel.py 73215 2012-12-19 18:05:05Z RD $
  
  import string
  import wx
  import wx.lib.buttons as buttons
  from globals import *
  import params
  import component
  import undo
  import images
  
  
  labelSize = (100,-1)
  
  # Panel class is the attribute panel containing class name, XRC ID and
  # a notebook with particular pages.
  
  class ScrolledPage(wx.ScrolledWindow):
      def __init__(self, parent):
          wx.ScrolledWindow.__init__(self, parent)
          self.topSizer = wx.BoxSizer(wx.VERTICAL)
          self.SetSizer(self.topSizer)
          self.panel = None
          self.SetScrollRate(5, 5)
  
      def Reset(self):
          if self.panel:
              self.panel.Destroy()
              self.panel = None
  
      def SetPanel(self, panel):
          self.Reset()
          self.panel = panel
          self.topSizer.Add(panel, 0, wx.ALL | wx.EXPAND, 2)
          self.topSizer.Layout()
          self.SendSizeEvent()
  
  # No highlighting please
  class ToggleButton(buttons.GenBitmapToggleButton):
      def GetBackgroundBrush(self, dc):
          colBg = self.GetBackgroundColour()
          brush = wx.Brush(colBg, wx.SOLID)
          if self.style & wx.BORDER_NONE:
              myAttr = self.GetDefaultAttributes()
              parAttr = self.GetParent().GetDefaultAttributes()
              myDef = colBg == myAttr.colBg
              parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
              if myDef and parDef:
                  if wx.Platform == "__WXMAC__":
                      brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
                  elif wx.Platform == "__WXMSW__":
                      if self.DoEraseBackground(dc):
                          brush = None
              elif myDef and not parDef:
                  colBg = self.GetParent().GetBackgroundColour()
                  brush = wx.Brush(colBg, wx.SOLID)
          return brush
  
  class Panel(wx.Panel):
      '''Attribute panel main class.'''
      def __init__(self, *args, **kw):
          wx.Panel.__init__(self, *args, **kw)
  
          # Set common sizes
          params.InitParams(self)
  
          topSizer = wx.BoxSizer(wx.VERTICAL)
          pinSizer = wx.BoxSizer(wx.HORIZONTAL)
          sizer = wx.FlexGridSizer(cols=2, vgap=1, hgap=5)
          self.labelRef = wx.StaticText(self, -1, 'ref:')
          self.textRef = params.ParamText(self, 'ref', textWidth=200)
          sizer.AddMany([ (self.labelRef, 0, wx.ALIGN_CENTER_VERTICAL),
                          (self.textRef, 0, wx.LEFT, 5) ])
          self.labelClass = wx.StaticText(self, -1, 'class:')
          self.textClass = params.ParamText(self, 'class', textWidth=200)
          sizer.AddMany([ (self.labelClass, 0, wx.ALIGN_CENTER_VERTICAL),
                          (self.textClass, 0, wx.LEFT, 5) ])
          self.labelName = wx.StaticText(self, -1, 'name:')
          self.textName = params.ParamText(self, 'name', textWidth=200)
          sizer.AddMany([ (self.labelName, 0, wx.ALIGN_CENTER_VERTICAL),
                          (self.textName, 0, wx.LEFT, 5) ])
          pinSizer.Add(sizer, 0, wx.ALL, 5)
          pinSizer.Add((0, 0), 1)
          self.pinButton = ToggleButton(self, bitmap=images.ToolPin.GetBitmap(),
                                        style=wx.BORDER_NONE)
          self.pinButton.SetBitmapSelected(images.ToolPinDown.GetBitmap())
          self.pinButton.SetToggle(g.conf.panelPinState)
          self.pinButton.SetToolTipString('Sticky page selection')
          pinSizer.Add(self.pinButton)
          topSizer.Add(pinSizer, 0, wx.EXPAND)
          self.sizer = sizer
  
          self.nb = wx.Notebook(self, -1)
          if wx.Platform == '__WXGTK__':
              # Redefine AddPage on GTK to fix when page added is not shown
              _oldAddPage = wx.Notebook.AddPage
              def _newAddPage(self, page, label):
                  _oldAddPage(self, page, label)
                  page.Show(True)
              wx.Notebook.AddPage = _newAddPage
  
          # Create scrolled windows for panels
          self.pageA = ScrolledPage(self.nb)
          self.nb.AddPage(self.pageA, 'Attributes')
          # Style page
          self.pageStyle = ScrolledPage(self.nb)
          self.pageStyle.Hide()
          # Extra style page
          self.pageExStyle = ScrolledPage(self.nb)
          self.pageExStyle.Hide()
          # Window attributes page
          self.pageWA = ScrolledPage(self.nb)
          self.pageWA.Hide()
          # Implicit attributes page
          self.pageIA = ScrolledPage(self.nb)
          self.pageIA.Hide()
          # Code page
          self.pageCode = ScrolledPage(self.nb)
          self.pageCode.Hide()
  
          topSizer.Add(self.nb, 1, wx.EXPAND)
          self.SetSizer(topSizer)
  
          self.undo = None        # pending undo object
  
      def SetData(self, container, comp, node):
  
          oldLabel = self.nb.GetPageText(self.nb.GetSelection())
          self.nb.SetSelection(0)
          map(self.nb.RemovePage, range(self.nb.GetPageCount()-1, 0, -1))
  
          # Don't freeze while removing the pages, but do it
          # after the removes instead.  See
          # https://groups.google.com/d/topic/wxpython-users/I8AJgkUCPj8/discussion
          self.Freeze()
          
          self.container = container
          self.comp = comp
          self.node = node        # main node
          self.refNode = node
          panels = []
          # Class and name
          if node.nodeType == node.COMMENT_NODE:
              self.labelRef.Hide()
              self.textRef.Hide()
              self.labelClass.Hide()
              self.textClass.Hide()            
          elif node.tagName == 'object_ref':
              self.labelRef.Show()
              self.textRef.Show()
              self.textRef.SetValue(node.getAttribute('ref'))
              self.labelClass.Hide()
              self.textClass.Hide()
              # 'class' can be present for ref?
              self.textClass.SetValue(node.getAttribute('class'))
          else:
              self.labelRef.Hide()
              self.textRef.Hide()
              if comp.klass != 'root':
                  self.labelClass.Show()
                  self.textClass.Show()
                  subclass = node.getAttribute('subclass')
                  if not subclass:
                      self.textClass.SetValue(node.getAttribute('class'))
                  else:
                      self.textClass.SetValue(subclass + '(%s)' % node.getAttribute('class'))
              else:                   # root node
                  self.labelClass.Hide()
                  self.textClass.Hide()
                  self.labelRef.Hide()
                  self.textRef.Hide()
          self.labelName.Show(comp.hasName)
          self.textName.Show(comp.hasName)
          if comp.hasName:
              self.textName.SetValue(node.getAttribute('name'))
  
          self.Layout()           # update after hiding/showing
  
          panel = AttributePanel(self.pageA, comp.attributes, comp.params, comp.renameDict)
          panels.append(panel)
          self.pageA.SetPanel(panel)
          self.SetValues(panel, node)
  
          if comp.windowAttributes:
              panel = AttributePanel(self.pageWA, comp.windowAttributes,
                                     rename_dict = params.WARenameDict)
              panels.append(panel)
              self.pageWA.SetPanel(panel)
              self.nb.AddPage(self.pageWA, "Look'n'Feel")
              self.SetValues(panel, node)
  
          if comp.styles or comp.genericStyles:
              # Create style page
              panel = params.StylePanel(self.pageStyle, comp.styles, comp.genericStyles,
                                        equiv = comp.equivStyles)
              panels.append(panel)
              self.pageStyle.SetPanel(panel)
              self.nb.AddPage(self.pageStyle, 'Style')
              self.SetStyleValues(panel, comp.getAttribute(node, 'style'))
  
          if comp.exStyles or comp.genericExStyles:
              # Create extra style page
              panel = params.StylePanel(self.pageExStyle, comp.exStyles + comp.genericExStyles, 
                                        tag='exstyle', equiv = comp.equivStyles)
              panels.append(panel)
              self.pageExStyle.SetPanel(panel)
              self.nb.AddPage(self.pageExStyle, 'ExStyle')
              self.SetStyleValues(panel, comp.getAttribute(node, 'exstyle'))
  
          # Additional panel for hidden node
          if container and container.requireImplicit(node) and container.implicitAttributes:
              panel = AttributePanel(self.pageIA, 
                                     container.implicitAttributes, 
                                     container.implicitParams,
                                     container.implicitRenameDict)
              panel.comp = container
              panels.append(panel)
              self.pageIA.SetPanel(panel)
              self.nb.AddPage(self.pageIA, container.implicitPageName)
              self.SetValues(panel, node.parentNode)
  
          if comp.hasCode:
              # Create code page
              panel = CodePanel(self.pageCode, comp.events)
              panel.node = node
              panels.append(panel)
              self.pageCode.SetPanel(panel)
              self.nb.AddPage(self.pageCode, 'Code')
              self.SetCodeValues(panel, comp.getAttribute(node, 'XRCED'))
  
          # Select old page if possible and pin is down
          if g.conf.panelPinState:
              for i in range(1, self.nb.GetPageCount()):
                  if oldLabel == self.nb.GetPageText(i):
                      self.nb.SetSelection(i)
                      break
  
          self.Thaw()
  
          return panels
          
      def Clear(self):
          self.comp = None
          self.nb.SetSelection(0)
          map(self.nb.RemovePage, range(self.nb.GetPageCount()-1, 0, -1))
          self.pageA.Reset()
          self.undo = None
  
          self.textClass.SetValue('')
          self.labelName.Show(False)
          self.textName.Show(False)
  
          self.Layout()
  
      def GetActivePanel(self):
          if self.nb.GetSelection() >= 0:
              return self.nb.GetPage(self.nb.GetSelection()).panel
          else:
              return None
  
      # Set data for a panel
      def SetValues(self, panel, node):
          panel.node = node
          if isinstance(panel, AttributePanel) and panel.comp:
              comp = panel.comp
          else:
              comp = self.comp
          for a,w in panel.controls:
              value = comp.getAttribute(node, a)
              w.SetValue(value)
  
      # Set data for a style panel
      def SetStyleValues(self, panel, style):
          panel.style = style
          panel.node = self.node
          panel.SetValues([('XRCED', style)])
  
      # Set data for a style panel
      def SetCodeValues(self, panel, data):
          panel.SetValues([('XRCED', data)])
  
  
  ################################################################################
  
  class AttributePanel(wx.Panel):
      '''Particular attribute panel, normally inside a notebook.'''
      def __init__(self, parent, attributes, param_dict={}, rename_dict={}):
          wx.Panel.__init__(self, parent, -1)
          self.controls = []
          self.comp = None                # if not None overrides default component
          sizer = wx.FlexGridSizer(len(attributes), 2, 0, 0)
          sizer.AddGrowableCol(1, 0)
          for a in attributes:
              # Find good control class
              paramClass = param_dict.get(a, params.paramDict.get(a, params.ParamText))
              sParam = rename_dict.get(a, a)
              control = paramClass(self, sParam)
              labelPanel = wx.Panel(self, -1)
              labelSizer = wx.BoxSizer()
              labelPanel.SetSizer(labelSizer)
              if control.isCheck: # checkbox-like control
                  label = wx.StaticText(labelPanel, -1, control.defaultString)
                  sizer.AddMany([ (control, 1, wx.EXPAND),
                                  (labelPanel, 1, wx.EXPAND) ])
                  labelSizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 2)
              else:
                  if sParam:
                      label = wx.StaticText(labelPanel, -1, sParam, size=labelSize)
                      sizer.AddMany([ (labelPanel, 1, wx.EXPAND),
                                      (control, 1, wx.EXPAND) ])
                  else:           # for node-level params
                      label = wx.StaticText(labelPanel, -1, '')
                      sizer.Add(control, 1, wx.LEFT, 20)
                  labelSizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20)
              self.controls.append((a, control))
          self.SetSizerAndFit(sizer)
  
      def GetValues(self):
          '''Generic method used for creating XML and for other operations.'''
          return [(a,c.GetValue()) for a,c in self.controls]
          
      def SetValues(self, values):
          '''Generic method used for undo.'''
          for ac,a2v in zip(self.controls, values):
              a,c = ac
              v = a2v[1]
              c.SetValue(v)
  
  ################################################################################
  
  class CodePanel(wx.Panel):
      ID_BUTTON_DEL = wx.NewId()
      ID_COMBO_EVENT = wx.NewId()
      ART_REMOVE = 'ART_REMOVE'
      
      '''Code generation panel.'''
      def __init__(self, parent, events):
          wx.Panel.__init__(self, parent, -1)
          self.SetFont(g.smallerFont())
          self.events = events
          self.checks = []
          topSizer = wx.BoxSizer(wx.HORIZONTAL)
          # Events on the left
          leftSizer = wx.BoxSizer(wx.VERTICAL)
          sizer = wx.GridSizer(cols=1, vgap=0, hgap=5)
          label = wx.StaticText(self, label='Events')
          label.SetFont(g.labelFont())
          sizer.Add(label, 0, wx.LEFT, 20)
          for ev in events:
              check = wx.CheckBox(self, label=ev)
              sizer.Add(check)
              self.checks.append((ev, check))
          leftSizer.Add(sizer)
          # Additional comboboxes
          self.extra = []
          self.eventSizer = wx.FlexGridSizer(cols=2)
          leftSizer.Add(self.eventSizer)
          topSizer.Add(leftSizer)
          # Right sizer
          rightSizer = wx.BoxSizer(wx.VERTICAL)
          rightSizer.Add((0, 10))
          if g.Presenter.container is not component.Manager.rootComponent:
              self.checkVar = wx.CheckBox(self, label='assign variable')
              rightSizer.Add(self.checkVar, 0, wx.LEFT, 20)
          else:
              self.checkVar = None
          topSizer.Add(rightSizer)
          # Cach all checkbox events
          self.Bind(wx.EVT_CHECKBOX, self.OnCheck)
          self.SetSizerAndFit(topSizer)
          # Extra combos and buttons
          self.Bind(wx.EVT_BUTTON, self.OnButtonDel, id=self.ID_BUTTON_DEL)
          self.Bind(wx.EVT_COMBOBOX, self.OnComboEvent, id=self.ID_COMBO_EVENT)
          self.Bind(wx.EVT_TEXT, self.OnComboText, id=self.ID_COMBO_EVENT)
  
      def GetValues(self):
          events = []
          for s,check in self.checks:
              if check.IsChecked(): events.append(s)
          # Encode data to a dictionary and the cPicke it
          data = {}
          for btn,combo in self.extra[:-1]:
              events.append(combo.GetValue())
          if events: data['events'] = '|'.join(events)
          if self.checkVar and self.checkVar.GetValue(): data['assign_var'] = '1'
          if data:
              return [('XRCED', data)]
          else:
              return []
  
      def AddExtraEvent(self, event=''):
          btn = wx.BitmapButton(self, self.ID_BUTTON_DEL,
                                bitmap=wx.ArtProvider.GetBitmap(self.ART_REMOVE, wx.ART_BUTTON),
                                size=(20,20))
          if not event: btn.Disable()
          self.eventSizer.Add(btn, 0, wx.ALIGN_CENTRE_VERTICAL)
          combo = wx.ComboBox(self, self.ID_COMBO_EVENT, value=event, choices=component.Component.genericEvents)
          btn.combo = combo
          self.eventSizer.Add(combo)
          self.extra.append((btn, combo))
  
      def SetValues(self, values):
          data = values[0][1]
          events = data.get('events', '').split('|')
          if events == ['']: events = []
          for ev,check in self.checks:
              check.SetValue(ev in events)
          # Add comboboxes for other events
          for ev in events:
              if ev not in self.events:
                  self.AddExtraEvent(ev)
          # Empty combo box for adding new events
          self.AddExtraEvent()
          self.Fit()
          self.SetMinSize(self.GetBestSize())
          if self.checkVar:
              self.checkVar.SetValue(int(data.get('assign_var', '0')))
  
      def OnCheck(self, evt):
          g.Presenter.setApplied(False)
  
      def OnButtonDel(self, evt):
          btn = evt.GetEventObject()
          self.extra.remove((btn, btn.combo))
          btn.combo.Destroy()
          btn.Destroy()
          self.eventSizer.Layout()
          self.Fit()
          self.SetMinSize(self.GetBestSize())
          g.Presenter.setApplied(False)
  
      def OnComboText(self, evt):
          if evt.GetEventObject() == self.extra[-1][1]:
              self.extra[-1][0].Enable()
              self.AddExtraEvent()
              self.eventSizer.Layout()
              self.Fit()
              self.SetMinSize(self.GetBestSize())
          g.Presenter.setApplied(False)
  
      def OnComboEvent(self, evt):
          if evt.GetEventObject() == self.extra[-1][1]:
              self.extra[-1][0].Enable()
              self.AddExtraEvent()
              self.eventSizer.Layout()
              self.Fit()
              self.SetMinSize(self.GetBestSize())
          g.Presenter.setApplied(False)
              
  