# Client.py : Primary class for the GUI
#
# Distributed under the terms described in the LICENCE file.
#
# Developed by akuchlin@mems-exchange.org
#
# 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: PyClient.py,v 1.21.2.4 2002/03/15 19:12:16 akuchlin Exp $"

import string, os

from java import awt
import java.net

import gui_util, protocols


# The sub-panels of the main window
import ButtonBar 
import ImageCommentDialog
import ImageEmailDialog
import BrokenConnectionDialog
import SimpleBorder
from ChatWindow import *
from ImageDisplay import *
from ImageButton import ImageButton
from ScopeConnection import *
from SettingsPanel import *
from FocusDisplay import *

from ClientWindowListener import ClientWindowListener

class PyClient(awt.Frame):

    def __init__ (self, host, port, authtoken, user_id, applet=0):
        self.host, self.port = host, port
        self.user_id = user_id
        self.authtoken = authtoken
        
        self.conn = None
        self.applet = applet

        self.hasControl = 0
        self.last_event = None

        # This call should come last, because it ends up calling move()!
        awt.Frame.__init__(self)


    def init (self, show_chat=1):
        self.setTitle("Remote Microscope")
	self.setLayout(awt.GridBagLayout())

        if 1: 
            # Initialize menubar
            menubar = awt.MenuBar()
            self.setMenuBar( menubar ) 
            file_menu = awt.Menu("File")
            if not self.applet:
                file_menu.add( awt.MenuItem("Quit") )
                menubar.add(file_menu)

            view_menu = awt.Menu( "View" )
            view_menu.add( awt.MenuItem("Show scale") )
            view_menu.add( awt.MenuItem("Show crosshair") )
            self.view_menu = view_menu
            menubar.add(view_menu)

	# Create the basic sections of the interface
        self.button_bar = ButtonBar()
        self.status_line = self.button_bar.status_line
        self.message_line = self.button_bar.message_line
	self.settings = SettingsPanel( self )
        self.focus_disp = FocusDisplay( self )
	self.image_disp = ImageDisplay( self )
        self.image_disp.setSize(self.image_disp.image_width,
                                self.image_disp.image_height)

        if show_chat:
            self.chat_window = ChatWindow(self)
        else:
            self.chat_window = None
            
        # Assemble all the components into the interface
        gui_util.constrain(self, self.button_bar, 1,1, 1,1,
                           awt.GridBagConstraints.HORIZONTAL)
        sb = SimpleBorder() ; sb.setLayout( awt.GridBagLayout() )
        gui_util.constrain(sb, self.settings, 0,0,1,1)
        # XXX FocusDisplay temporarily disabled until our microscope is
        # working again and I can test the vertical movement code.
        if self.focus_disp is not None and 0:
            gui_util.constrain(self, self.focus_disp, 0,1,1,1)
        gui_util.constrain(self, sb, 0,2, 1,1)
        if self.chat_window is not None:
            gui_util.constrain(self, self.chat_window, 0,3, 2,1)
        
        self.im_panel = im_panel = awt.Panel()
        im_panel.setLayout( awt.GridBagLayout() )
        
        # Create the directional icons
        self.button_up = ImageButton(self, ImageButton.UP)
        self.button_down = ImageButton(self, ImageButton.DOWN)
        self.button_left = ImageButton(self, ImageButton.LEFT)
        self.button_right = ImageButton(self, ImageButton.RIGHT)

        gui_util.constrain(im_panel, self.button_up, 1,0, 1,1,
                  awt.GridBagConstraints.HORIZONTAL)
        gui_util.constrain(im_panel, self.button_down, 1,2, 1,1,
                  awt.GridBagConstraints.HORIZONTAL)
        gui_util.constrain(im_panel, self.button_left, 0,1, 1,1,
                  awt.GridBagConstraints.VERTICAL)
        gui_util.constrain(im_panel, self.button_right, 2,1, 1,1,
                  awt.GridBagConstraints.VERTICAL)

        # Add ImageDisplay to placeholder panel
        gui_util.constrain(im_panel, self.image_disp, 1,1, 1,1,
                  awt.GridBagConstraints.BOTH)
        # Add placeholder panel to the Client's frame.
        gui_util.constrain(self, im_panel, 1,2, 1,1,
                  awt.GridBagConstraints.BOTH)

	self.status("Ready.")
	self.update_coords()
	
        self.addWindowListener(ClientWindowListener(self))
        

    # Public API of the Client class
    def move (self, x = None, y = None, z = None,
              angle = None, mag = None,
              mode = None, aperture = None, light = None,
              display_mode = None):
        "Move the scope to a new position and setting"

        if self.conn is None:
            return
        
	if not self.hasControl: 
	    self.status("Not in control")
            return
        
        args = []
        if x is not None: args.append ( 'x=%i' %(x,) )
        if y is not None: args.append ( 'y=%i' %(y,) )
        if z is not None: args.append ( 'z=%i' %(z,) )
#        if angle is not None: args.append ( 'angle=%i' %(angle,) )
        if mag is not None: args.append ( 'magnification=%i' %(mag,) )
        if mode is not None: args.append ( 'mode=%s' %(mode,) )
        if aperture is not None: args.append ( 'aperture=%i' %(aperture,) )
        if light is not None: args.append ( 'light=%i' %(light,) )
        args = string.join(args, ' ')
        self.send_message('move ' + args )

        if display_mode == 'small':
            self.send_message('image x=270 y=201')
        elif display_mode == 'medium':
            self.send_message('image x=320 y=240')
        elif display_mode == 'large':
            self.send_message('image x=801 y=600')

    def take (self):
        "Attempt to take control of the microscope"
        self.send_message("take")
    
    def cede (self):
        "Give up control of the microscope"
        self.send_message("cede")

    def chatmsg (self, msg):
        msg = protocols.build_message_line('chatmsg', msg=msg)
        self.send_message(msg)

    def rectangle (self, x1, y1, x2, y2):
        "Draw a rectangle on the visible area"
        pass

    def status (self, message):
	"Change the frame's status line"
	self.status_line.setText( message )
    def message (self, message):
	"Change the frame's message line"
	self.message_line.setText( message )

    def quit (self):
        self.hide()
        self.dispose()
        try:
            if self.hasControl: self.cede()
        except: pass
        try:
            self.send_message('quit')
        except: pass
        if self.conn is not None:
            self.conn.close()
        self.conn = None
        if not self.applet:
            import java.lang.System
            java.lang.System.exit(0)
        
    # Internal and GUI-related methods; you probably don't need to
    # bother with these.  
    def action(self, e, o):
        # Suppress duplicated events
        if self.last_event == e:
            return
        self.last_event = e

##        print 'Client action:', repr(e)

        # Movement 
        if o == self.button_up:
            i = self.image_disp
            self.move(y = i.ypos + i.y_scale/2.0 )
        elif o == self.button_down:
            i = self.image_disp
            self.move(y = i.ypos - i.y_scale/2.0)
        elif o == self.button_left:
            i = self.image_disp
            self.move(x = i.xpos + i.x_scale/2.0)
        elif o == self.button_right:
            i = self.image_disp
            self.move(x = i.xpos - i.x_scale/2.0)
            
        elif e.arg == 'Quit':
            self.quit()

        # View menu selections
        elif e.arg == 'Show scale':
            self.image_disp.show_scale = 1
            item = self.view_menu.getItem(0)
            item.setLabel('Hide scale')
            self.image_disp.repaint()
        elif e.arg == 'Hide scale':
            self.image_disp.show_scale = 0
            item = self.view_menu.getItem(0)
            item.setLabel('Show scale')
            self.image_disp.repaint()

        elif e.arg == 'Show crosshair':
            self.image_disp.show_crosshair = 1
            item = self.view_menu.getItem(1)
            item.setLabel('Hide crosshair')
            self.image_disp.repaint()
        elif e.arg == 'Hide crosshair':
            self.image_disp.show_crosshair = 0
            item = self.view_menu.getItem(1)
            item.setLabel('Show crosshair')
            self.image_disp.repaint()
            
        elif e.arg == 'Reload':
            self.move()

        elif e.arg == 'Small':
            self.move(display_mode = 'small')
        elif e.arg == 'Medium':
            self.move(display_mode = 'medium')
        elif e.arg == 'Large':
            self.move(display_mode = 'large')
            
        elif e.arg == 'Send Microscope Settings':
            x = y = z = angle = None
            try: x = int(self.settings.x_field.getText()) 
            except ValueError: pass
            try: y = int(self.settings.y_field.getText()) 
            except ValueError: pass
            mag_str = self.settings.mag_field.getSelectedItem()
            mag = int( mag_str[:-1] )
            mode = self.settings.mode_field.getSelectedItem()
            aperture_str = self.settings.aperture_field.getSelectedItem()
            aperture = int( aperture_str )
            light = self.settings.light_field.getValue()
            self.move(x, y, None, None,
                      mag, mode, aperture, light)
            
        elif e.arg == 'Take':
            self.take()
        elif e.arg == 'Cede':
            self.cede()

        elif e.arg == 'Save':
            icd = ImageCommentDialog(self)
            icd.pack() ; icd.show()
            if icd.comments is not None:
                # Convert newlines to spaces so everything will be on a
                # single line.
                msg = protocols.build_message_line('hires',
                                                   action='save',
                                                   comments=icd.comments)
                self.send_message(msg)

        elif e.arg == 'Email':
            icd = ImageEmailDialog(self)
            icd.pack() ; icd.show()
            if icd.email is not None:
                # Convert newlines to spaces so everything will be on a
                # single line.
                msg = protocols.build_message_line('email',
                                                   address=icd.email,
                                                   comments=icd.comments)
                self.send_message(msg)

        elif e.arg == 'New Wafer':
            # Do nothing at the moment
            pass
	else:
	    pass

    def send_message (self, message):
        if self.conn:
            self.conn.send_message( message )

    def update_coords (self):
        "Update the microscope coordinates"
        self.settings.update_status()
        self.focus_disp.update_status()
        
    def cmd_take (self, sock):
	self.hasControl = 1
	self.status('In control of microscope')
        if self.chat_window is not None:
            self.chat_window.set_controller(self.user_id)
        self.button_bar.take_button.setEnabled(0)
        self.button_bar.cede_button.setEnabled(1)
        self.button_bar.email_button.setEnabled(1)
        self.button_bar.small_button.setEnabled(1)
        self.button_bar.medium_button.setEnabled(1)
        self.button_bar.large_button.setEnabled(1)

    def cmd_taken (self, sock, id):
	self.status('Someone else controlling microscope')
        self.button_bar.take_button.setEnabled(0)
        self.button_bar.cede_button.setEnabled(0)
        if self.chat_window is not None:
            self.chat_window.set_controller( id )

    def cmd_cede (self, sock):
	if self.hasControl:
	    self.status('No longer in control of microscope')
        else:
            self.status('No one controlling microscope')

        if self.chat_window is not None:
            self.chat_window.set_controller( None )
            
	self.hasControl = 0
        self.button_bar.take_button.setEnabled(1)
        self.button_bar.cede_button.setEnabled(0)
        self.button_bar.save_button.setEnabled(0)
        self.button_bar.email_button.setEnabled(0)
        self.button_bar.small_button.setEnabled(0)
        self.button_bar.medium_button.setEnabled(0)
        self.button_bar.large_button.setEnabled(0)


    # Make a connection to the microscope server
    def make_connection (self):
        while 1:
            self.message("Connecting to server")
            import java.net
            try:
                socket = java.net.Socket(self.host, self.port)
            except java.net.UnknownHostException:
                self.message("Host " + self.host + " does not exist... exiting")
                time.sleep(5)
                import java.lang.System
                java.lang.System.exit(0)
            except java.net.ConnectException:
                dlg = BrokenConnectionDialog(self, self.host)
                dlg.pack() ; dlg.show()
                if not dlg.reconnect:
                    import java.lang.System
                    java.lang.System.exit(0)
            else:
                # Break out of the 'while 1' loop
                break
            self.chat_window.clear_users()
            
        # Try connecting to image port
        image_socket = java.net.Socket(self.host, self.port + 2)

        # Create scope connection object
        self.button_bar.connection_line.setText('Connected to ' + self.host)
        self.message("Sending authentication token")
        conn = ScopeConnection(socket, image_socket, self, self.authtoken, 1)
        self.conn = conn

	# Add the subwidgets as handlers for the scope connection
	conn.handlers.append(self.image_disp)
	conn.handlers.append(self)
	conn.handlers.append(self.focus_disp)
        if self.chat_window is not None:
            conn.handlers.append(self.chat_window)

	conn.start()
        self.message("")


    def reconnect (self):
        """reconnect()
        Inform the user that the connection has been broken,
        and try to reconnect to the microscope server.
        """
        self.message("Connection broken; re-connnecting")
        time.sleep(1)
        self.make_connection()

    def get_frame (self):
        return self
    
