#!/usr/bin/env python

"""
$Id: wxnsc.py,v 1.10.2.6 2009/03/27 14:30:29 irmen Exp $

A wxPython gui to nsc (Pyro Name Server Control tool).
This gui doesn't have as many features as the xnsc that ships with Pyro,
but it has some nice features that the xnsc doesn't have ;)

'Pyro' - Python Remote Objects is
(c) Irmen de Jong - irmen@razorvine.net

This file 'wxnsc.py' is
(c) Jan Finell - finell@users.sourceforge.net


Usage (from the commandline):

# to use set PYRO environment variables or broadcasting
# for finding the nameserver host...
> wxnsc.py

"""

__author__   = "Jan Finell"
__date__     = "$Date: 2009/03/27 14:30:29 $"
__revision__ = "$Revision: 1.10.2.6 $"

#
# Standard modules
#
import os, sys, socket
import traceback, cStringIO

#
# GUI modules
#
import wx

#
# Pyro modules
#
from Pyro.naming import NameServerLocator
from Pyro.errors import NamingError, ConnectionClosedError,\
                        ConnectionDeniedError
import Pyro.core

#----------------------------------------------------------------------#
# Module constants
DEFAULT_GROUPNAME = ':Default'
PROTECTED_GROUPS  = [DEFAULT_GROUPNAME, ':Pyro']

GROUP_XPM = [
"16 16 9 1",
"       c None",
".      c #FFFFFF",
"+      c #000000",
"@      c #C3C3C3",
"#      c #A0A0A0",
"$      c #8080FF",
"%      c #585858",
"&      c #FFFFFF",
"*      c #808080",
"  +%+           ",
" +%$$++         ",
" +&%%$$+++++    ",
" +&&@%%$$$$$+   ",
" +&@&@@%$$$$%   ",
" +@&@@@@%%%%$+  ",
" +&@@@@@@@#@+$% ",
" +@@@@@@@#@#+$% ",
" +@@@#@@#@##+$% ",
" +@@@@@#@###+$% ",
" ++*@@#@####+$% ",
"   ++*@#####+$% ",
"     ++#####+$% ",
"       ++###+$++",
"         ++#+$++",
"           ++++ "]

GROUP_OPEN_XPM =[
"16 16 12 1",
"       c None",
".      c #FFFFFF",
"+      c #000000",
"@      c #8080FF",
"#      c #808080",
"$      c #C3C3C3",
"%      c #C0C0FF",
"&      c #A0A0A0",
"*      c #303030",
"=      c #FFFFFF",
"-      c #DCDCDC",
";      c #585858",
"    +++         ",
"    +@#++       ",
"    +@%@#++++   ",
"+++ +@%%%%%%@+  ",
"+&&*;@@@%%%%%@+ ",
"+#&&&#@@@@%%%@#+",
" *&&&&##@@@@%@#+",
" +#&&$$&##@@@@#+",
"  *&$&$$$$##@@#+",
"  +#$$$$-$-&@@#+",
"   *#-$$$-=%;@#+",
"    +*&--===&@#+",
"      +*$===%;#+",
"        +*$==##+",
"          +*$=#+",
"            +**+"]

ITEM_XPM = [
"16 16 11 1",
"       c None",
".      c #FFFFFF",
"+      c #FFFFFF",
"@      c #000000",
"#      c #585858",
"$      c #DCDCDC",
"%      c #FFFFC0",
"&      c #FFDCA8",
"*      c #303030",
"=      c #C3C3C3",
"-      c #A0A0A0",
"    #####**@@   ",
"    #$$$$$=*=@  ",
"    #$+++++#+$@ ",
"    #$+++++#$+=@",
"    #$+++++###*@",
"    #$++++++$=-@",
"    #$++++++%+$@",
"    #$+++++++++@",
"    #$++++%+%+%@",
"    #$+++++++&+@",
"    #$++%+%+%+%@",
"    #$+++++&+&+@",
"    #$%+%+%+%+%@",
"    #$+++&+&+&%@",
"    #$%+%+%+%%%@",
"    *@@@@@@@@@@@"]
#----------------------------------------------------------------------#
# Helper functions

def cmp_name(n1, n2):
   return cmp(n1[0], n2[0])

def show_message_dialog(parent, msg, title, style):
   if sys.platform[:3] == 'win':
      dlg = WinMessageDialog(parent, msg, title, style)
   else:
      dlg = wx.MessageDialog(parent, msg, title, style)
   dlg.CentreOnParent()
   retval = dlg.ShowModal()
   dlg.Destroy()
   return retval

#----------------------------------------------------------------------#
## Classes


class wx_StdoutLog(wx.TextCtrl):
   """
   :Purpose: A simple text ctrl that can be used for logging standard out
   """
   def write(self, data):
      if data.strip():
         wx.TextCtrl.AppendText(self, '%s\n' % data)

class wx_NSC(wx.Frame):
   """
   :Purpose: The main frame of the GUI.
   """
   def __init__(self, nsHost, nsPort, bcAddr):
      """
      :Parameters:
         - `nsHost`: the name server host to connect to. This is the
                     name of the host or the ip.
         - `nsPort`: the name server port. By default the Pyro name
                     server port is 9090
         - `bcAddr`: override for the broadcast address.
      """
      wx.Frame.__init__(self, None, -1, 'Pyro Name Server')
      self.nsHost           = nsHost
      self.nsPort           = nsPort
      self.bcAddr           = bcAddr
      self.NS               = None

      self._build()
      imageList = wx.ImageList(16,16)
      self.__idGroup = imageList.Add(wx.BitmapFromXPMData(GROUP_XPM))
      self.__idItem  = imageList.Add(wx.BitmapFromXPMData(ITEM_XPM))
      self.__idGroupOpen = imageList.Add(wx.BitmapFromXPMData(GROUP_OPEN_XPM))
      self.treeCtrlItems.SetImageList(imageList)
      self.__imageList = imageList

      self._bindEvents()
      # binding stdout to my own txtCtrl Log.
      sys.stdout = self.txtCtrlLog
      
      self._log("Pyro version: "+Pyro.constants.VERSION)
      
      self.nsc_findNS()
      if self.NS: self.update()
      
   #-- public methods --#      
   def enable(self, enable=True):
      """
      Enabling/disabling some of the buttons.
      """
      self.buttonDeleteGroup.Enable(enable)
      self.buttonCreateGroup.Enable(enable)
      self.buttonDeleteSelected.Enable(enable)
      self.buttonRegisterItem.Enable(enable)
      self.buttonShowMeta.Enable(enable)
      self.buttonSetMeta.Enable(enable)

   def update(self):
      """
      """
      tree = self.treeCtrlItems
      tree.DeleteAllItems()
      root = tree.AddRoot(':')
      tree.SetItemImage(root, self.__idGroup)
      tree.SetItemImage(root, self.__idGroupOpen,
                        wx.TreeItemIcon_Expanded)
      self._populate_tree(tree, root, ':')

      # enabling/disabling buttons, depending on the current state.
      self.enable(self.NS != None)      
      
   def _populate_tree(self, tree, parent, group):
      subgroupsL = self.nsc_list_groups(group)
      subgroupsL.sort()
      itemsL     = self.nsc_list_items(group)
      itemsL.sort()
      for subgroup in subgroupsL:
         groupB = tree.AppendItem(parent, subgroup)
         tree.SetPyData(groupB, 0)
         tree.SetItemImage(groupB, self.__idGroup)
         tree.SetItemImage(groupB, self.__idGroupOpen,
                           wx.TreeItemIcon_Expanded)
         self._populate_tree(tree, groupB, subgroup)
      for item in itemsL:
         itemB = tree.AppendItem(parent, item)
         tree.SetPyData(itemB, 1)
         tree.SetItemImage(itemB, self.__idItem)
         tree.SetItemImage(itemB, self.__idItem,
                           wx.TreeItemIcon_Selected)
   #-- nsc methods --#
   def nsc_findNS(self, ident=None):
      """
      Locating the Name Server by using given nsHost and nsPort
      """
      locator = NameServerLocator(identification=ident)
      try:
         if self.nsHost:
            self._log('connecting to Name Server (%s:%s)' % (self.nsHost,
                                                             self.nsPort))
            self.NS = locator.getNS(self.nsHost, self.nsPort, trace=1, bcaddr=self.bcAddr)
         else:
            self._log('broadcasting to find Name Server')
            self.NS = locator.getNS(None, None, trace = 1, bcaddr=self.bcAddr) 
            self.nsHost = self.NS.URI.address
            self.nsPort = self.NS.URI.port
         self.NS._setIdentification(ident)
         self._log('Name Server found, URI = %s' % self.NS.URI)
         self._setNSData()
      except ConnectionDeniedError, e:
         if str(e).find( Pyro.constants.deniedReasons[Pyro.constants.DENIED_SECURITY] ) != -1:
            msg = 'Authentication required:'
            dlg = wx.TextEntryDialog(self, msg, 'Authentication',
                                    style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
            dlg.CentreOnParent()
            if dlg.ShowModal() == wx.ID_OK:
               ident = dlg.GetValue()
               self.nsc_findNS(ident)
            else:
               self.NS = None
               self._log('Connection to Name Server denied!','error')
         else:
            self.NS = None
            self._logError('Unable to connect to Name Server')
      except:
         self.NS = None
         self._logError('Name Server not found!')


   def nsc_list_groups(self, ingroup):
      """
      Returns a list of group names inside given group.
      """
      return self._nsc_list(ingroup, 0)

   def nsc_list_items(self, ingroup):
      """
      Returns a list of item names inside given group.
      """
      return self._nsc_list(ingroup, 1)

   def _nsc_list(self, ingroup, type):
      """
      Generic method for listing either groups or items inside a given group.

      type = 0 : group
      type = 1 : item
      """
      items = []
      if self.NS:
         for name, t in self.NS.list(ingroup):
            if t == type:
               if type == 1:                  
                  uri  = self.NS.resolve('%s.%s' % (ingroup,name))
                  name = '%s (%s)' % (name, uri)
               elif ingroup != ':':
                  name = '%s.%s' % (ingroup, name)
               else:
                  name = '%s%s' % (ingroup, name)
               items.append(name)
      return items

   def nsc_create_group(self, groupName):
      """
      Creating given group
      """
      if self.NS:
         try:
            self.NS.createGroup(groupName)
            self._log('created group (%s)' % (groupName))
            return 1
         except NamingError, e:
            self._logError('unable to create group %s because %s' % (groupName,
                                                                     e))
      return 0

   def nsc_delete_group(self, groupName):
      """
      Deleting given group
      """
      if self.NS:
         try:
            self.NS.deleteGroup(groupName)
            self._log('group %s deleted' % groupName)
            return 1
         except NamingError, e:
            self._logError('unable to delete group %s because %s' % (groupName,
                                                                     e))
      return 0
   
   def nsc_ping(self):
      """
      Ping the current Name Server
      """
      if self.NS:
         try:
            self.NS.ping()
            self._log('ping Name Server (%s): up and running' % self.nsHost)
         except:
            self._logError('Name Server not responding.')
      else:
         self._logError('Name Server not responding')

   def nsc_delete(self, name):
      """Removing given name from the Name Server.
      
      :Parameters:
         - `name`: the name to delete from the Name Server
      """
      try:
         self.NS.unregister(name)
         self._log('%s deleted successfully' % name)
         return 1
      except NamingError, e:
         self._logError('unable to delete %s because %s' % (name, e))
      except:
         self._logError('deletion of %s failed' % name)
      return 0

   def nsc_register_item(self, name, uri):
      """
      Registering new item with given name and uri
      """
      try:
         self.NS.register(name, uri)
         uri = Pyro.core.PyroURI(uri)
         self._log('registered %s with %s' % (name, uri))
         return 1
      except NamingError, e:
         self._logError('unable to register,\nName Server error: %s' % e)
      except Exception, e:
         self._logError('unable to register, error: %s' % e)
      return 0

   def nsc_set_meta(self, name, meta):
      """
      Set user meta data 
      """
      try:
         self.NS.setMeta(name, meta)
         self._log('set user meta data on '+name)
         return 1
      except NamingError, e:
         self._logError('unable to set user meta data,\nName Server error: %s' % e)
      except Exception, e:
         self._logError('unable to set user meta data, error: %s' % e)
      return 0

   def nsc_show_meta(self, name):
      fullName = self.NS.fullName(name)
      try:
         self._log('"%s" system meta info: %s' % (fullName,
                                             self.NS._getSystemMeta(fullName)))
         self._log('"%s" user meta info: %s' % (fullName,
                                                self.NS.getMeta(name)))
      except NamingError, e:
         self._logError('unable to get meta info,\nName Server error: %s' % e)
      except Exception, e:
         self._logError('unable to get meta info, error: %s' % e)
                        
   #-- gui event methods --#
   def OnCheckNS(self, event):      
      if self._checkNS():
         self.update()

   def OnClose(self, event):
      sys.stdout = sys.__stdout__ #restoring the stdout
      self.Destroy()

   def OnCreateGroup(self, event):
      """
      Creating group in selected parent
      """
      tree = self.treeCtrlItems
      items = tree.GetSelections()
      if items:
         if tree.GetPyData(items[0]) == 0:
            # use the selected group
            parentGroupI = items[0]
            parentGroupName = tree.GetItemText(parentGroupI)
         else:
            # take the parent
            parentGroupI = tree.GetItemParent(items[0])
            parentGroupName = tree.GetItemText(parentGroupI)
      else:
         parentGroupI    = tree.GetRootItem()
         parentGroupName = ':'
      msg = 'Create group in "%s", with name:' % parentGroupName
      dlg = wx.TextEntryDialog(self, msg, 'Enter group name')
      dlg.CentreOnParent()
      if dlg.ShowModal() == wx.ID_OK:
         if parentGroupName != ':':
            groupName = '%s.%s' % (parentGroupName, dlg.GetValue())
         else:
            groupName = ':%s' % (dlg.GetValue())
         if self.nsc_create_group(groupName):
            groupI = tree.AppendItem(parentGroupI, groupName)
            tree.SetPyData(groupI, 0)
            tree.SetItemImage(groupI, self.__idGroup)
            tree.SetItemImage(groupI, self.__idGroupOpen,
                              wx.TreeItemIcon_Expanded)
            tree.Expand(parentGroupI)

   def OnSetMeta(self, ev=None):
      """
      set user meta on selected groups + items
      """
      tree = self.treeCtrlItems
      itemsL, groupsL = self._getSelections()
      namesL  = itemsL + groupsL
      if namesL:     
         namesS = ',\n '.join(namesL)
         msg = 'User meta data string for:\n %s' % namesS
         dlg = wx.TextEntryDialog(self, msg, 'Enter meta data')
         dlg.CentreOnParent()
         if dlg.ShowModal() == wx.ID_OK:
            meta=dlg.GetValue()
            for name in namesL:
               self.nsc_set_meta(name,meta)
      
   def OnDelete(self, ev=None):
      """
      Deleting selected items.
      """
      tree = self.treeCtrlItems
      itemsL = tree.GetSelections()
      namesL  = []
      deleteL = []
      for i in itemsL:
         # only items (ie. no groups)
         if tree.GetPyData(i) == 1:
            parent = tree.GetItemParent(i)
            parentName = tree.GetItemText(parent)
            name   = tree.GetItemText(i).split()[0] #only name
            namesL.append('%s.%s' % (parentName, name))
            deleteL.append(i)
      if namesL:     
         namesS = ',\n '.join(namesL)
         ret = show_message_dialog(self,
                         'Really delete following name(s)?:\n %s' % namesS,
                         '-- Confirm --', wx.YES|wx.NO|wx.ICON_QUESTION)
         if ret == wx.ID_YES:
            for name, i in zip(namesL,deleteL):
               if self.nsc_delete(name):
                  tree.Delete(i)
                  
   def OnDeleteGroup(self, ev=None):
      """
      Deleting selected groups.
      """
      tree = self.treeCtrlItems
      itemsL = tree.GetSelections()
      namesL  = []
      deleteL = []
      for i in itemsL:
          # only groups (ie. no items)
          if tree.GetPyData(i) == 0:             
             name   = tree.GetItemText(i)
             if name not in PROTECTED_GROUPS and tree.GetChildrenCount(i)==0:
                namesL.append(name)
                deleteL.append(i)
      if namesL:
         namesS = ',\n'.join(namesL)
         ret = show_message_dialog(self,
                           'Really delete following group(s)?:\n %s' % namesS,
                           '-- Confirm --', wx.YES|wx.NO|wx.ICON_QUESTION)
         if ret == wx.ID_YES:
            for name, i in zip(namesL, deleteL):
               if self.nsc_delete_group(name):
                  tree.Delete(i)
                  
   def OnKeyPressed(self, event):
      """
      Calling delete for both items and groups
      """
      if event.GetKeyCode() == 127:
         # deleting both selected groups and items
         self.OnDelete()
         self.OnDeleteGroup()
      if event.GetKeyCode() == 105: # 105 == 'i'
         # showing meta information on selected item
         self.OnShowMeta()
      event.Skip()
      
   def OnPing(self, event):
      if self._checkNS():
         self.update()
      self.nsc_ping()

      
   def OnRegisterItem(self, event):
      """
      Registering item in selected parent.
      """
      tree = self.treeCtrlItems
      items = tree.GetSelections()
      if items:
         if tree.GetPyData(items[0]) == 0:
            # use the selected group
            parentGroupI = items[0]
            parentGroupName = tree.GetItemText(parentGroupI)
         else:
            parentGroupI = tree.GetItemParent(items[0])
            parentGroupName = tree.GetItemText(parentGroupI)
      else:
         parentGroupI    = tree.GetRootItem()
         parentGroupName = ':'
         
      msg = 'Register new item in "%s", with:\n <name> <URI>' % parentGroupName
      dlg =  wx.TextEntryDialog(self, msg, 'Register item')
      dlg.CentreOnParent()
      if dlg.ShowModal() == wx.ID_OK:
         try:
            itemName, uri = dlg.GetValue().split()
         except:
            self._log('Invalid arguments, use <name> <URI>', 'error')
         else:
            if parentGroupName != ':':
               itemName = '%s.%s' % (parentGroupName, itemName)
            else:
               itemName = ':%s' % (itemName)
            if self.nsc_register_item(itemName, uri):
               label = '%s (%s)' % (dlg.GetValue().split()[0], uri)
               itemI = tree.AppendItem(parentGroupI, label)
               tree.SetPyData(itemI, 1)
               tree.SetItemImage(itemI, self.__idItem)
               tree.SetItemImage(itemI, self.__idItem,
                                 wx.TreeItemIcon_Selected)
               tree.Expand(parentGroupI)
               
   def OnUpdate(self, event):
      self._checkNS()
      self.update()

   def OnShowMeta(self, event=None):
      itemsL, groupsL = self._getSelections()
      for name in itemsL + groupsL:
         self.nsc_show_meta(name)
         
               
   #-- protected methods --#
   def _checkNS(self):
      """
      Reads the new values from the txtCtrlNSHost and txtCtrlNSPort.
      If changed, it tries to connect to the new Name Server.
      """
      changed = 0
      if self.txtCtrlNSHost.IsModified():
         self.nsHost = self.txtCtrlNSHost.GetValue()
         changed = 1
      if self.txtCtrlNSPort.IsModified():
         try:
            port = int(self.txtCtrlNSPort.GetValue())
            self.nsPort = port
         except ValueError:
            self._logError('Integer required for port')
         changed = 1
      if changed:
         self.nsc_findNS()
      return changed

   def _log(self, line, status='info'):
      """Writing given line to the log-textCtrl.
      
      :Parameters:
         - `line`: text to log
         - `status`: status should be 'info' or 'error'. If 'info' the
                     text will be colored blue, if 'error' the text will
                     be red.
      """
      start = self.txtCtrlLog.GetLastPosition()
      self.txtCtrlLog.AppendText('%s\n' % line)
      color = wx.BLACK
      if status == 'info':
         color = wx.BLUE
      elif status == 'error':
         color = wx.RED
      self.txtCtrlLog.SetStyle(start, self.txtCtrlLog.GetLastPosition(),
                               wx.TextAttr(color))

   def _logError(self, line):
      """
      Getting the traceback of previous error, and logging this.
      """
      a, b, tb = sys.exc_info()
      if a == ConnectionClosedError:
         self.NS = None
         self._log('Connection with Name Server lost', 'error')
         self.enable(False)  
      buf = cStringIO.StringIO()
      traceback.print_exc(file = buf)
      self._log('%s:\n%s' % (line, buf.getvalue()), 'error')

   def _setNSData(self):
      """
      Updates the display of current Name Server information.
      """
      try:
         ns_name, t, ns_ip = socket.gethostbyaddr(self.nsHost)
         ns_ip = ns_ip[0]
      except:
         ns_name, ns_ip = self.nsHost, ''
      self.txtCtrlNSHost.SetValue('%s' % ns_name)
      self.txtCtrlNSPort.SetValue('%s' % self.nsPort)
      self.SetTitle('Pyro Name Server ( %s - %s )' % (ns_name, ns_ip))

   def _getSelections(self):
      tree = self.treeCtrlItems
      selectionsL = tree.GetSelections()
      itemsL  = []
      groupsL = []
      for i in selectionsL:
          if tree.GetPyData(i) == 0:
             # group
             groupsL.append(tree.GetItemText(i))
          elif tree.GetPyData(i) == 1:
             # item
             parentName = tree.GetItemText(tree.GetItemParent(i))
             name = tree.GetItemText(i).split()[0]
             itemsL.append('%s.%s' % (parentName, name))
      return itemsL, groupsL
      
   #-- build / bind methods --#
   def _bindEvents(self):
      """
      Binding events to the gui widgets.
      """
      wx.EVT_BUTTON(self, self.buttonPing.GetId(), self.OnPing)
      wx.EVT_BUTTON(self, self.buttonUpdate.GetId(), self.OnUpdate)
      wx.EVT_BUTTON(self, self.buttonClose.GetId(), self.OnClose)
      wx.EVT_BUTTON(self, self.buttonDeleteGroup.GetId(), self.OnDeleteGroup)
      wx.EVT_BUTTON(self, self.buttonCreateGroup.GetId(), self.OnCreateGroup)
      wx.EVT_BUTTON(self, self.buttonDeleteSelected.GetId(), self.OnDelete)
      wx.EVT_BUTTON(self, self.buttonRegisterItem.GetId(), self.OnRegisterItem)
      wx.EVT_BUTTON(self, self.buttonShowMeta.GetId(), self.OnShowMeta)
      wx.EVT_BUTTON(self, self.buttonSetMeta.GetId(), self.OnSetMeta)
      wx.EVT_TEXT_ENTER(self, self.txtCtrlNSHost.GetId(), self.OnCheckNS)
      wx.EVT_TEXT_ENTER(self, self.txtCtrlNSPort.GetId(), self.OnCheckNS)
      wx.EVT_CHAR(self.treeCtrlItems, self.OnKeyPressed)
      
   def _build(self):
      """
      Building widgets and setting static widget data.      
      """
      parent = wx.Panel(self, -1)
      sizer0 = wx.BoxSizer(wx.VERTICAL)
      sizer0.Add(self._buildTopBar(parent), 0, wx.ALIGN_LEFT|wx.GROW, 5)

      splitter = wx.SplitterWindow(parent, -1)
      #- TOP PART --------------------------------------------------------#
      topParent = wx.Panel(splitter, -1)
      topSizer = wx.BoxSizer(wx.VERTICAL)
      self.treeCtrlItems = wx.TreeCtrl(topParent, -1,
                         style = wx.TR_TWIST_BUTTONS|wx.TR_LINES_AT_ROOT|wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_MULTIPLE)
      topSizer.Add(self.treeCtrlItems, 1, wx.EXPAND, 5)
      topParent.SetAutoLayout( True )
      topParent.SetSizer(topSizer )
      topSizer.Fit(topParent)
      topSizer.SetSizeHints(topParent)
      #-------------------------------------------------------------------#
      #- BOTTOM PART -----------------------------------------------------#
      bottomParent = wx.Panel(splitter,-1)
      bottomSizer = wx.BoxSizer(wx.VERTICAL)
      self.txtCtrlLog=wx_StdoutLog(bottomParent, -1, "",
                                  size= wx.Size(-1, 10),
                                  style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH)
      bottomSizer.Add(self.txtCtrlLog, 1, wx.EXPAND, 5)
      bottomParent.SetAutoLayout( True )
      bottomParent.SetSizer(bottomSizer )
      bottomSizer.Fit(bottomParent)
      bottomSizer.SetSizeHints(bottomParent)
      #-------------------------------------------------------------------#
      splitter.SplitHorizontally(topParent,bottomParent, -100)
      sizer0.Add(splitter, 1, wx.EXPAND|wx.ALIGN_CENTRE, 5)
      
      self.buttonClose = wx.Button(parent, -1, 'Close')         # buttonClose
      sizer0.Add(self.buttonClose, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
      
      parent.SetAutoLayout( True )
      parent.SetSizer( sizer0 )
      sizer0.Fit( parent)
      sizer0.SetSizeHints( parent)

   def _buildTopBar(self, parent):
      """
      Widget building
      """
      sizer0 = wx.BoxSizer(wx.VERTICAL)
      #--
      sizer1 = wx.BoxSizer(wx.HORIZONTAL)
      txt1 = wx.StaticText(parent, -1, 'Name Server:')
      txt1.SetForegroundColour(wx.BLUE)
      sizer1.Add(txt1, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTRE|wx.ALL, 5)
      self.txtCtrlNSHost = wx.TextCtrl(parent, -1, '', size=wx.Size(300,-1),
                                      style=wx.TE_PROCESS_ENTER)
      sizer1.Add(self.txtCtrlNSHost, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_BOTTOM|wx.TOP|wx.BOTTOM, 5)
      txtColon = wx.StaticText(parent, -1, ':')
      txtColon.SetForegroundColour(wx.BLUE)
      sizer1.Add(txtColon, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 5)
      self.txtCtrlNSPort = wx.TextCtrl(parent, -1, '', size=wx.Size(50,-1),
                                      style=wx.TE_PROCESS_ENTER)
      sizer1.Add(self.txtCtrlNSPort, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_BOTTOM|wx.TOP|wx.BOTTOM, 5)
      self.buttonUpdate = wx.Button(parent, -1, 'Update')       # buttonUpdate
      sizer1.Add(self.buttonUpdate, 0, wx.ALIGN_LEFT|wx.ALL, 5)
      self.buttonPing = wx.Button(parent, -1, 'Ping')           # buttonPing
      sizer1.Add(self.buttonPing, 0, wx.ALIGN_LEFT|wx.ALL, 5)
      sizer0.Add(sizer1, 0, wx.ALIGN_LEFT|wx.GROW, 5)
      #--
      lineH1 = wx.StaticLine(parent, -1, style=wx.LI_HORIZONTAL)
      sizer0.Add(lineH1, 0, wx.GROW|wx.ALIGN_CENTRE|wx.LEFT|wx.RIGHT, 5)
      #--
      sizer2 = wx.BoxSizer(wx.HORIZONTAL)
      self.buttonDeleteGroup = wx.Button(parent, -1, ' Delete group(s) ')
      sizer2.Add(self.buttonDeleteGroup, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.TOP|wx.LEFT|wx.BOTTOM, 5)
      self.buttonCreateGroup = wx.Button(parent, -1, ' Create group... ')
      sizer2.Add(self.buttonCreateGroup, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.TOP|wx.LEFT|wx.BOTTOM, 5)
      lineV1 = wx.StaticLine(parent, -1, style=wx.LI_VERTICAL)
      sizer2.Add(lineV1, 0,
                       wx.ALL|wx.GROW, 5)
      self.buttonDeleteSelected = wx.Button(parent, -1,
                                           ' Delete item(s) ')
      sizer2.Add(self.buttonDeleteSelected, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.ALL, 5)
      self.buttonRegisterItem = wx.Button(parent, -1, ' Register item... ')
      sizer2.Add(self.buttonRegisterItem, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, 5)
      lineV2 = wx.StaticLine(parent, -1, style=wx.LI_VERTICAL)
      sizer2.Add(lineV2, 0,
                       wx.ALL|wx.GROW, 5)
      self.buttonShowMeta = wx.Button(parent, -1, ' Show meta ')
      sizer2.Add(self.buttonShowMeta, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, 5)
      self.buttonSetMeta = wx.Button(parent, -1, ' Set meta ')
      sizer2.Add(self.buttonSetMeta, 0,
                       wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, 5)
      sizer0.Add(sizer2, 0, wx.ALIGN_LEFT, 5)
      #--
      return sizer0

#----------------------------------------------------------------------#
class WinMessageDialog(wx.Dialog):
    '''
    :Purpose: Message dialog for MS Win.
              The parameters are the same as for wx.MessageDialog

    :Detail: On Windows the native wx.MessageDialog can not
             be centered on top of the parent or positioned, ie.
             it will always be centered on the screen.
             
    '''
    def __init__(self, parent=None, message='Message:',
                 caption='Message', style=wx.OK|wx.CANCEL,pos=wx.DefaultPosition):
        wx.Dialog.__init__(self, parent, -1, caption, size=wx.DefaultSize,
                          style=wx.CAPTION, pos=pos)
        self._build(message, style)
        self.Fit()
        
    def OnButton(self, ev):
        self.EndModal(ev.GetId())
        
    def _build(self, msg, style):
        parent   = wx.Panel(self, -1)
        sizer    = wx.BoxSizer(wx.VERTICAL)
        #-- icon and message --#
        msgSizer = wx.BoxSizer(wx.HORIZONTAL)

        # icon #
        artID = None
        if style & wx.ICON_EXCLAMATION == wx.ICON_EXCLAMATION \
               or style & wx.ICON_HAND == wx.ICON_HAND:
            artID = wx.ART_WARNING
        elif style & wx.ICON_ERROR == wx.ICON_ERROR:
            artID = wx.ART_ERROR
        elif style & wx.ICON_QUESTION == wx.ICON_QUESTION:
            artID = wx.ART_QUESTION
        elif style & wx.ICON_INFORMATION == wx.ICON_INFORMATION:
            artID = wx.ART_INFORMATION
        if artID:
            bmp = wx.ArtProvider_GetBitmap(artID, wx.ART_MESSAGE_BOX, (48,48))
            bmpIcon = wx.StaticBitmap(parent, -1, bmp)
            msgSizer.Add(bmpIcon, 0, wx.ALIGN_CENTRE|wx.ALL, 5)

        # msg #
        txtMsg = wx.StaticText(parent, -1, msg, style=wx.ALIGN_CENTRE)
        msgSizer.Add(txtMsg, 0, wx.ALIGN_CENTRE|wx.ALL, 5)

        sizer.Add(msgSizer, 0, wx.ALIGN_CENTRE, 5)
        line = wx.StaticLine(parent, -1, style=wx.LI_HORIZONTAL)
        sizer.Add(line, 0, wx.GROW|wx.ALL, 5)
        #-- buttons --#
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        if style & wx.YES_NO == wx.YES_NO:
            btnYes = wx.Button(parent, wx.ID_YES, 'Yes')
            btnSizer.Add(btnYes, 0,
                               wx.ALIGN_CENTRE|wx.LEFT|wx.RIGHT, 10)
            btnNo  = wx.Button(parent, wx.ID_NO, 'No')
            btnSizer.Add(btnNo, 0,
                               wx.ALIGN_CENTRE|wx.LEFT|wx.RIGHT, 10)
            if style & wx.YES_DEFAULT == wx.YES_DEFAULT:
                btnYes.SetDefault()
            elif style & wx.NO_DEFAULT == wx.NO_DEFAULT:
                btnNo.SetDefault()
            wx.EVT_BUTTON(self, wx.ID_YES, self.OnButton)
            wx.EVT_BUTTON(self, wx.ID_NO, self.OnButton)
        else:
            if style & wx.OK == wx.OK:
                btnOK = wx.Button(parent, wx.ID_OK, 'OK')
                btnOK.SetDefault()
                btnSizer.Add(btnOK, 0,
                                   wx.ALIGN_CENTRE|wx.LEFT|wx.RIGHT, 10)
            if style & wx.CANCEL == wx.CANCEL:
                btnCancel = wx.Button(parent, wx.ID_CANCEL, 'Cancel')
                btnSizer.Add(btnCancel, 0,
                                   wx.ALIGN_CENTRE|wx.LEFT|wx.RIGHT, 10)

        sizer.Add(btnSizer, 0, wx.ALIGN_CENTRE|wx.TOP, 5)
        #--
        parent.SetAutoLayout( True )
        parent.SetSizer(sizer )
        sizer.Fit( parent )
        sizer.SetSizeHints( parent )
      
#----------------------------------------------------------------------#
def main(argv):
   """
   The default host will be None if the environment variable
   PYRO_NS_HOSTNAME is not set.
   The default port will be 9090 (Pyro.config.PYRO_NS_PORT) if
   PYRO_NS_BC_PORT environment variable is not set.
   """
   nsHost = os.getenv('PYRO_NS_HOSTNAME')
   nsPort = os.getenv('PYRO_NS_BC_PORT') or Pyro.config.PYRO_NS_PORT
   bcAddr = Pyro.config.PYRO_NS_BC_ADDR
   if bcAddr:
      bcAddr=bcAddr.strip()
   bcAddr=bcAddr or None
      
   class wx_NSCApp(wx.App):
      def OnInit(self):
         Pyro.core.initClient()
         frame = wx_NSC(nsHost, nsPort, bcAddr)
         frame.SetSize(wx.Size(630,500))
         frame.Show(True)
         return True

   app = wx_NSCApp(0)
   app.MainLoop()

# allow easy usage with python -m
if __name__=="__main__":
   main(sys.argv)
