"""mems.instrument.client.tk.Client

Primary class for the GUI

Distributed under the terms described in the LICENCE file.

The Client class holds all the subpanels that make up the interface
as a whole, and is used as the intermediary between all the instances.
(There may be some places where a subpanel directly accesses another
subpanel, without going through the Client instances; such places
are oversights that should be corrected somehow.)
"""

__revision__ = "$Id: client.py,v 1.71 2002/04/19 16:03:48 akuchlin Exp $"

import math, string, sys, time
import socket, xmlrpclib
import asyncore

import Tkinter
from PIL import ImageTk, Image
import Pmw
from PmwFileDialog import PmwFileDialog

from mems.instrument.client.tk import state, CIF
from mems.instrument.client.tk.button_frame import ButtonFrame
from mems.instrument.client.tk.chat_frame import ChatFrame
from mems.instrument.client.tk.focus_frame import FocusFrame
from mems.instrument.client.tk.image_display import ImageDisplay
from mems.instrument.client.tk.layout_dialog import LayoutDialog
from mems.instrument.client.tk.login_dialog import LoginDialog
from mems.instrument.client.tk.scope_connection import ScopeConnection
from mems.instrument.client.tk.settings_frame import SettingsFrame
from mems.instrument.server import codecs

# Flag to enable dummy layout display code
LAYOUT = 0

def make_menubar (client):
    mBar = Pmw.MenuBar(client.root) 

    mBar.addmenu('File', 'File')
    mBar.addmenu('View', 'View')
    if LAYOUT:
        mBar.addmenu('Layout', 'Layout')
    mBar.addmenu('Help', 'Help', side=Tkinter.RIGHT)

    mBar.addmenuitem('File', 'command',
                      label='Quit', command = client.quit)
    
    mBar.addmenuitem('View', 'checkbutton',
                      label = 'Scale', command = client.toggle_scale)
    mBar.addmenuitem('View', 'checkbutton',
                      label = 'Crosshair', command = client.toggle_crosshair)

    if LAYOUT:
        mBar.addmenuitem('Layout', 'command',
                         label = 'Load CIF', command = client.load_cif)
        mBar.addmenuitem('Layout', 'command',
                         label = 'Modify Scale',
                         command = client.modify_scale)

    mBar.addmenuitem('Help', 'command',
                      label = 'About', command  = client.show_about)
    
    return mBar

class Client (Tkinter.Frame):
    def __init__ (self, root, userid, password, device, *args):
        apply(Tkinter.Frame.__init__,(self,root) + args)
        self.root = root

        self._login_dialog = None
        self.userid, self.password, self.device = userid, password, device
        self.hasControl = 0
        self.state = None
        self.connection_dialog = None

        menubar = self.menubar = make_menubar(self)
        menubar.pack(side=Tkinter.TOP, fill = Tkinter.X)
        
        f1 = Tkinter.Frame(self)
        f1.pack(side=Tkinter.TOP, fill = Tkinter.X)
	self.focus_display = FocusFrame(self,f1) 
	self.button_bar = ButtonFrame(self,f1)
        #self.focus_display.pack(side=Tkinter.LEFT)
        self.button_bar.pack(side=Tkinter.RIGHT)

        f2= Tkinter.Frame(self)
	self.setting_frame = SettingsFrame(self, f2) 
	self.image_display = ImageDisplay(self, f2)
        self.setting_frame.pack(side=Tkinter.LEFT)
        self.image_display.pack(side=Tkinter.RIGHT, fill=Tkinter.BOTH)
        f2.pack(side=Tkinter.TOP, fill = Tkinter.BOTH)

        self.f1 = f1 ; self.f2=f2
        
	self.chat_window = ChatFrame(self,self)
        self.chat_window.pack(side=Tkinter.BOTTOM, fill = Tkinter.X)

        # XXX delete later!
        ##self.load_cif('100.cif')


    def _show_login_dialog (self, args, message = None):
        root, = args
        self._login_dialog = LoginDialog(root, self,
            self.device, self.userid, self.password, message)

        
    def get_authtoken (self, args):
        """Get the authentication token by calling an XML-RPC handler on
        our Web server.
        """

        # Check if any of the connection information is missing; if so,
        # put up a dialog box to ask the user
        if (self.device is None or
            self.userid is None or
            self.password is None):
            self._show_login_dialog(args)
            return

        if hasattr(socket, 'ssl'): protocol = 'https'
        else: protocol = 'http'

        try:
            from mems.lib import MODE
        except ImportError:
            MODE = 'LIVE'

        if MODE in ('DEVEL', 'PORTAL_DEVEL'):
            server = xmlrpclib.Server('%s://ute.mems-exchange.org/xml/rpc'
                                      % protocol)
        else:
            server = xmlrpclib.Server('%s://www.mems-exchange.org/xml/rpc'
                                      % protocol)

        result = server.get_authtoken(self.userid, self.password,
                                      self.device)

        if isinstance(result, dict):
            msg = result['faultString']
            self._show_login_dialog(args, msg)
            return
        else:
            (self.host, self.port, self.authtoken) = result
            self.make_connections()


    def make_connections (self):
        try:
            self.conn = connection = ScopeConnection(self, self.host,
                                                     self.port)
        except socket.error:
            self.report_broken_connection()
            return
        
        self.conn.send_message('auth value=' + self.authtoken)
        image_port = self.conn.get_image_port()
        self.conn.send_message('image_socket port=%i' % image_port)

        self.state = state.MicroscopeState(self)
        self.state.set_cmd_connection(self.conn)
        
        self.button_bar.connection_line.message('state',
                                                'Connected to ' + self.host)
                                                
        self.root.title("Remote Microscope: " + self.host)

        
    def send_message (self, msg):
        if self.state is not None:
            self.state.send_message(msg)

    # Public API of the Client class
    def move (self, **kwargs):
        "Move the scope to a new position and setting"

	if self.state.in_control:
            apply(self.state.move, (), kwargs)
	else:
	    self.button_bar.status('error', "Not in control")

    def chat_arrived (self, user, message):
        self.chat_window.add_message(user, message)

    def userlist_changed (self, userlist, controller_id):
        self.chat_window.update_userlist(userlist, controller_id)

    def control_changed (self):
        if self.state.in_control:
            value = Tkinter.NORMAL
            opposite = Tkinter.DISABLED
        elif self.state.taken:
            value = Tkinter.DISABLED
            opposite = Tkinter.DISABLED
        else:
            value = Tkinter.DISABLED
            opposite = Tkinter.NORMAL
            
        self.button_bar.takeButton.configure(state=opposite)

        self.button_bar.cedeButton.configure(state=value)
#        self.button_bar.saveButton.configure(state=value)
        self.button_bar.emailButton.configure(state=value)
        self.button_bar.reloadButton.configure(state=value)
        self.button_bar.smallButton.configure(state=value)
        self.button_bar.mediumButton.configure(state=value)
        self.button_bar.largeButton.configure(state=value)


    def state_changed (self):
        self.setting_frame.update(self.state)
        self.focus_display.update(self.state)
        
    def image_changed (self):
        self.image_display.update()

    def toggle_scale (self):
        self.image_display.toggle_scale()

    def toggle_crosshair (self):
        self.image_display.toggle_crosshair()

    def show_about (self):
        about = Pmw.AboutDialog(self, applicationname = 'Remote Microscope')

    def report_broken_connection (self):
        "Show a 'Connection broken' dialog box, and then reconnect"
        if self.state is not None:
            self.state.set_cmd_connection(None)
        
        if self.connection_dialog is None:
            dialog = Pmw.MessageDialog(self,
                                       buttons=('Reconnect', 'Quit'),
                                       defaultbutton='Reconnect',
                                       title='Connection broken',
                                       command=self.execute_connection_dialog,
                                       message_text="""\
The connection to
%s:%i
has been broken.
Try to reconnect?
""" % (self.host, self.port))
            self.connection_dialog = dialog
            
    def execute_connection_dialog (self, result):
        if result == 'Reconnect':
            self.connection_dialog.withdraw()
            self.connection_dialog = None
            self.make_connections()
        elif result == 'Quit':
            sys.exit()            

    def load_cif (self, filename = None):
        if filename is None:
            dialog = PmwFileDialog(self)
            filename = dialog.askfilename(filter='*.cif')
            if filename is None:
                # Clear existing layout
                print 'cancelled'
                self.layout = self.layout_context = None
                self.image_display.redisplay_layout()
                return
        
        data = open(filename, 'r').read()
        layout = CIF.CIFLayout(); layout.parse(data)
        self.image_display.set_layout(layout)
        self.image_display.layout_context.set_translate(-self.state.x,
                                                        -self.state.y)
        self.image_display.redisplay_layout()
        
    def modify_scale (self):
        dialog = LayoutDialog(self.image_display, self)
                          
    def make_layer_menu (self, layout, context, layers_shown):
        "Dynamically change Layout menu to list the layers"

        # Ick, deletemenuitems doesn't work
        ##self.menubar.deletemenuitems(menuName='Layout', start=3, end=3)
        menu = self.menubar.component('Layout-menu')
        while 1:
            index = menu.index('end')
            if index<2: break
            menu.delete(index)

        # Add new entries
        if layout is not None:
            layers = context.list_layers()
            for layer, (color, pattern) in layers:
                self.menubar.addmenuitem('Layout', 'checkbutton',
                          label = layout.layer_names[layer],
                          command = self.image_display.change_layers,
                          variable = layers_shown[layer])
