"""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.70 2002/03/18 17:01:38 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 import protocols
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
            print self.host, self.port
            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.state = state.MicroscopeState(self)
        self.state.set_cmd_connection(self.conn)
        
        self.send_message('auth', value=self.authtoken)
        image_port = self.conn.get_image_port()
        self.send_message('image_socket',  port=image_port)

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

        
    def send_message (self, cmd, **kwargs):
        if self.state is not None:
            msg = protocols.build_message_line(cmd, **kwargs)
            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)
        self.button_bar.feedbackButton.configure(state=Tkinter.NORMAL)


    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])
