#
# FtpCube
# Copyright (C) 2001 Michael Gilfix
#
# This file is part of FtpCube.
#
# You should have received a file COPYING containing license terms
# along with this program; if not, write to Michael Gilfix
# (mgilfix@eecs.tufts.edu) for a copy.
#
# This version of FtpCube is open source; you can redistribute it and/or
# modify it under the terms listed in the file COPYING.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#

import gtk, GtkExtra
import sys, os
import string # For backwards compatibility with 1.5.x
import re
import main

from intl import _

# For compatibility with win32 pygtk
if sys.platform == "win32":
	import GTKconst
	GTK = GTKconst
else:
	import GTK

class ConnectWin:

	"""Connection window object

	Brings up a new window displaying connection information and "Ok"/"Cancel" options.
	The tabs refer to different connection parameters and the user inputs the desired
	host to connect to and whether to use login/password or anonymous access. Other
	connection parameters such as number of retry attempts and idle timeout periods can
	be set in the connection window.

	The connection window is created with reference to a callback function for
	actually initiating the connection provided the user clicks the "Ok" button. A hash
	of default options is also provided, indicating which values to use for seeding
	the various input boxes."""

	def __init__ (self, callback, default_opts):
		"""Creates and displays the connection window object. 'callback' is a reference
		to a callback function that is executed when the "Ok" button is clicked. The
		definition of 'callback' is:

			def callback (connectwin):
				# Where 'connectwin' is a reference to the calling ConnectWin instance

		'default_opts' is a hash containing the default options to seed the input
		boxes with."""

		self.callback = callback

		self.dialog = gtk.GtkDialog ()
		self.dialog.set_title (_("ftpcube - Connect"))
		self.dialog.set_policy (gtk.FALSE, gtk.FALSE, gtk.TRUE)
		self.dialog.set_position (gtk.WIN_POS_CENTER)
		icon_pic, icon_mask = gtk.create_pixmap_from_xpm (self.dialog, None, os.path.join (main.ICONS_PREFIX, "ftpcube.xpm"))
		self.dialog.set_icon (gtk.GtkPixmap (icon_pic, icon_mask))

		# Make ourselves modal
		gtk.grab_add (self.dialog)

		box = gtk.GtkHBox (spacing=75)
		box.show ()
		self.dialog.action_area.add (box, padding=95)

		ok_button = gtk.GtkButton (_("Ok"))
		ok_button.set_usize (50, 25)
		ok_button.connect ("clicked", self.ok)
		box.pack_start (ok_button, expand=gtk.FALSE)
		ok_button.show ()

		button = gtk.GtkButton (_("Cancel"))
		button.set_usize (50, 25)
		button.connect ("clicked", self.cancel)
		box.pack_start (button, expand=gtk.FALSE)
		button.show ()

		self.notebook = gtk.GtkNotebook ()
		self.notebook.set_tab_pos (gtk.POS_TOP)
		self.dialog.vbox.add (self.notebook)
		self.notebook.show ()

		# Create the tabs and add them and show them
		self.login_tab = LoginTab (self, opts=default_opts)
		label = gtk.GtkLabel (_("Login Info"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.login_tab.get_page (), label)

		self.advanced_tab = AdvancedTab (self, opts=default_opts)
		label = gtk.GtkLabel (_("Advanced"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.advanced_tab.get_page (), label)

		self.settings_tab = SettingsTab (self, opts=default_opts)
		label = gtk.GtkLabel (_("Settings"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.settings_tab.get_page (), label)

		self.dialog.connect ("key_press_event", self.key_press)
		self.dialog.show ()

	def key_press (self, win, event):

		if hasattr (event, 'string') and event.string == '\r':
			self.ok (None)

	def ok (self, button):
		"""Destroys the connect window and executes the callback."""

		self.cancel (button)
		self.callback (self)

	def cancel (self, button):
		"""Destroys the connect window by hiding it."""

		self.dialog.hide ()
		self.dialog.destroy ()

	def get_options (self):
		"""Returns a hash containing a list of all the values of the
		various input boxes, containing the default values and those
		the user modified."""

		options = { }
		options.update (self.login_tab.get_options ())
		options.update (self.advanced_tab.get_options ())
		options.update (self.settings_tab.get_options ())
		return options

class LoginTab:

	"""Connect window login tab

	Displays a tab containing information detailing which host/port to
	connect to and what login/password combination to use. A remote
	directory can also be specified. If the directory box is blank,
	'/' is used."""

	ftp_url_re = r'ftp://(([^:]+)(:([^@]+))?@)?([a-zA-Z\d\.-]+)(:(\d+))?(/.+)?'

	def __init__ (self, connectwin, ok_button=None, opts=None):
		"""Creates an instance of the tab object and displays the tab. 'connectiwin'
		is a reference to the parent connect window object. 'opts' is a reference to
		the defaults hash provided to the parent connect window object."""

		self.connectwin = connectwin
		self.ok_button = ok_button

		self.page = gtk.GtkFrame ()
		self.page.set_border_width (10)
		self.page.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		self.page.show ()

		table = gtk.GtkTable (6, 1)
		table.set_row_spacings (2)
		table.set_col_spacings (5)
		table.set_homogeneous (gtk.FALSE)
		table.show ()
		self.page.add (table)

		packer = gtk.GtkPacker ()
		label = gtk.GtkLabel (_("Host or URL"))
		label.show ()
		packer.show ()
		packer.add (label, anchor=GTK.ANCHOR_W)
		table.attach (packer, 0, 1, 0, 1, xpadding=12, ypadding=1)

		packer = gtk.GtkPacker ()
		label = gtk.GtkLabel (_("Port"))
		label.show ()
		packer.show ()
		packer.add (label, anchor=GTK.ANCHOR_W)
		table.attach (packer, 1, 2, 0, 1, xpadding=10, ypadding=1)

		self.host = gtk.GtkEntry ()
		if opts is not None and opts['host']:
			self.host.set_text (opts['host'])
		self.host.connect ("focus_out_event", self.convert_hostaddr)
		self.host.show ()
		table.attach (self.host, 0, 1, 1, 2, xoptions=GTK.FILL, xpadding=4, ypadding=1)

		packer = gtk.GtkPacker ()
		self.port = gtk.GtkEntry ()
		self.port.set_usize (50, -1)
		self.port.set_text ("%s" %main.app['port'])
		if opts is not None and opts['port']:
			self.port.set_text ("%s" %opts['port'])
		self.port.show ()
		packer.show ()
		packer.add (self.port, anchor=GTK.ANCHOR_W)
		table.attach (packer, 1, 2, 1, 2, xpadding=4, ypadding=1)

		packer = gtk.GtkPacker ()
		label = gtk.GtkLabel (_("Username"))
		label.show ()
		packer.show ()
		packer.add (label, anchor=GTK.ANCHOR_W)
		table.attach (packer, 0, 1, 2, 3, xpadding=12, ypadding=1)

		packer = gtk.GtkPacker ()
		label = gtk.GtkLabel (_("Password"))
		label.show ()
		packer.show ()
		packer.add (label, anchor=GTK.ANCHOR_W)
		table.attach (packer, 1, 2, 2, 3, xpadding=12, ypadding=1)

		self.username = gtk.GtkEntry ()
		if opts is not None and opts['username']:
			self.username.set_text (opts['username'])
		self.username.connect ("key_press_event", self.entry_update)
		self.username.show ()
		table.attach (self.username, 0, 1, 3, 4, xpadding=4, ypadding=1)

		self.password = gtk.GtkEntry ()
		self.password.set_visibility (gtk.FALSE)
		if opts is not None and opts['password']:
			self.password.set_text (opts['password'])
		self.password.connect ("key_press_event", self.entry_update)
		self.password.show ()
		table.attach (self.password, 1, 2, 3, 4, xpadding=4,  ypadding=1)

		packer = gtk.GtkPacker ()
		label = gtk.GtkLabel (_("Remote Directory"))
		label.show ()
		packer.show ()
		packer.add (label, anchor=GTK.ANCHOR_W)
		table.attach (packer, 0, 2, 4, 5, xpadding=12, ypadding=1)

		self.remote_dir = gtk.GtkEntry ()
		if opts is not None and opts['remotedir']:
			self.remote_dir.set_text (opts['remotedir'])
		self.remote_dir.show ()
		table.attach (self.remote_dir, 0, 2, 5, 6, xpadding=4, ypadding=1)

		box = gtk.GtkHBox ()
		box.show ()
		self.personal_button = gtk.GtkRadioButton (None, _("Personal Login"))
		self.personal_button.connect ("clicked", self.personal_clicked)
		self.personal_button.show ()
		self.anon_button = gtk.GtkRadioButton (self.personal_button, _("Anonymous Login"))
		self.anon_button.connect ("clicked", self.anon_clicked)
		self.anon_button.show ()
		if opts is not None and not opts['username'] == main.app['username']:
			self.personal_button.set_active (gtk.TRUE)
		else:
			self.anon_button.set_active (gtk.TRUE)
		box.pack_start (self.personal_button)
		box.pack_start (self.anon_button)
		table.attach (box, 0, 2, 6, 7, xpadding=4, ypadding=1)

	def convert_hostaddr (self, win, event):
		"""Convert a hostname of the form
		ftp://user@host:domain.net:1234/initial/path and put the data in
		the correct fields."""

		conn = re.match (self.ftp_url_re, self.host.get_text ())
		if conn:
			conninfo = conn.groups ()
			#('user:pass@','user',':pass','pass','host.com',':123','1231','/dir')
			if conninfo[1]:
				if conninfo[1] == main.app['host']:
					self.anon_button.set_active (gtk.TRUE)
				else:
					self.personal_button.set_active (gtk.TRUE)
				self.username.set_text (conninfo[1])
				if conninfo[3]:
					self.password.set_text (conninfo[3])
			else:
				self.anon_clicked (None)
			self.host.set_text (conninfo[4])
			if conninfo[6]:
				self.port.set_text (conninfo[6])
			else:
				self.port.set_text (main.app['port'])
			if conninfo[7]:
				self.remote_dir.set_text (conninfo[7])
			else:
				self.remote_dit.set_text ('')

	def entry_update (self, win, event):
		"""Automatically toggles between anonymous and personal radio buttons provided
		that the user begins to enter a new login/password combination."""

		self.personal_button.set_active (gtk.TRUE)

	def get_page (self):
		return self.page

	def anon_clicked (self, button):
		"""If the anonymous button is clicked, the login/password combination are set
		to the defaults of the application, which are usually 'anonymous' and some
		password 'guest@password.org'."""

		self.username.set_text ("%s" %main.app['username'])
		self.password.set_text ("%s" %main.app['password'])

	def personal_clicked (self, button):
		"""If personal is clicked, the login/password combination are blanked."""

		self.username.set_text ('')
		self.password.set_text ('')

	def get_options (self):
		"""Returns a hash containing the information from each of the input boxes."""

		# These are safe to strip and are probably expected to strip
		host = self.host.get_text ()
		host = string.strip (host)
		username = self.username.get_text ()
		username = string.strip (username)

		return {
			'host' : host,
			'port' : int (self.port.get_text ()),
			'username' : username,
			'password' : self.password.get_text (),
			'remotedir' : self.remote_dir.get_text ()
		}

class AdvancedTab:

	"""Connect window advanced tab

	Contains information about the local directory to default to when connecting to
	the host (useful for bookmarks) and possible limits for logins that can affect
	how threading works."""

	def __init__ (self, connectwin, ok_button=None, opts=None):
		"""Creates and displays the advanced tab. 'connectwin' is a reference to
		the calling connect window object. 'opts' is a hash containing the default
		option data passed to the connect window object."""

		self.connectwin = connectwin

		self.page = gtk.GtkFrame ()
		self.page.set_border_width (10)
		self.page.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		self.page.show ()

		outterpacker = gtk.GtkPacker ()
		outterpacker.show ()
		self.page.add (outterpacker)

		frame = gtk.GtkFrame ()
		frame.set_border_width (10)
		frame.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		frame.set_usize (350, 85)
		frame.show ()
		outterpacker.add (frame)

		packer = gtk.GtkPacker ()
		packer.show ()
		frame.add (packer)
		label = gtk.GtkLabel (_("Local Directory"))
		label.show ()
		packer.add (label, side=GTK.SIDE_TOP, anchor=GTK.ANCHOR_W, pad_x=75, pad_y=12)
		box = gtk.GtkHBox ()
		box.show ()
		packer.add (box)
		self.localdir = gtk.GtkEntry ()
		if opts is not None and opts['localdir']:
			self.localdir.set_text (opts['localdir'])
		self.localdir.set_usize (200, -1)
		self.localdir.show ()
		box.pack_start (self.localdir, expand=GTK.EXPAND)
		button = gtk.GtkButton ("...")
		button.connect ("clicked", self.browse)
		button.set_usize (25, 25)
		button.show ()
		box.pack_start (button, expand=GTK.EXPAND, padding=25)

		frame = gtk.GtkFrame ()
		frame.set_border_width (10)
		frame.set_usize (350, 85)
		frame.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		frame.show ()
		outterpacker.add (frame)

		packer = gtk.GtkPacker ()
		packer.show ()
		frame.add (packer)
		label = gtk.GtkLabel (_("Number of logins allowed:"))
		label.show ()
		packer.add (label, side=GTK.SIDE_TOP, anchor=GTK.ANCHOR_NW, pad_x=55, pad_y=10)
		box = gtk.GtkHBox ()
		box.show ()
		self.unlimited_button = gtk.GtkRadioButton (None, _("Unlimited"))
		self.unlimited_button.show ()
		box.pack_start (self.unlimited_button, padding=4)
		self.unlimited_button.set_active (gtk.TRUE)
		self.login_button = gtk.GtkRadioButton (self.unlimited_button, _("Login only once"))
		self.login_button.show ()
		box.pack_start (self.login_button, padding=4)
		self.limited_button = gtk.GtkRadioButton (self.unlimited_button, _("Limited"))
		self.limited_button.show ()
		box.pack_start (self.limited_button, padding=4)
		self.limited_box = gtk.GtkEntry ()
		if opts is not None and opts['limit']:
			limit = opts['limit']
			if limit == 0:
				self.unlimited_button.set_active (gtk.TRUE)
			elif limit == 1:
				self.login_button.set_active (gtk.TRUE)
			else:
				self.unlimited_button.set_active (gtk.TRUE)
				self.limited_box.set_text (limit)
		self.limited_box.set_usize (25, 25)
		self.limited_box.show ()
		box.pack_start (self.limited_box, expand=GTK.EXPAND, padding=4)
		packer.add (box, anchor=GTK.ANCHOR_W, pad_x=15)

	def get_page (self):
		return self.page

	def browse (self, button):
		"""Brings up a file selection window where the user can select the
		local path that will appear in the "Local Directory" input box."""

		# Release our modal grabbing of the window
		gtk.grab_remove (self.connectwin.dialog)

		file_win = gtk.GtkFileSelection (_("ftpcube - Browse"))
		file_win.show ()

		# Create the handlers for browse events
		def win_close (button, win=file_win, self=self):
			win.hide ()
			win.destroy ()
			gtk.grab_add (self.connectwin.dialog)
		def browse_selected (button, fs=file_win, self=self):
			self.localdir.set_text (fs.get_filename ())
			fs.hide ()
			fs.destroy ()
			gtk.grab_add (self.connectwin.dialog)
		file_win.ok_button.connect("clicked", browse_selected)
		file_win.cancel_button.connect("clicked", win_close)

	def get_options (self):
		"""Returns a hash containing the information in the tab's input boxes."""

		limit = None
		if self.login_button.get_active ():
			limit = 1
		elif self.limited_button.get_active ():
			text = self.limited_box.get_text ()
			if len (text) > 0:
				limit = int (text)

		return {
			'limit' : limit,
			'localdir' : self.localdir.get_text ()
		}

class SettingsTab:

	"""Connect window settings tab

	Displays a tab allowing the user to set various connection parameters, including
	number of retires, connection/idle timeouts, and delay between login retries. The
	user can also indicate whether a proxy server is being used."""

	def __init__ (self, connectwin, ok_button=None, opts=None):
		"""Creates and displays the Settings tab. 'connectwin' is a reference to the
		calling connect window object. 'opts' is a reference to the default hash passed
		to the connect window."""

		self.connectwin = connectwin

		self.page = gtk.GtkFrame ()
		self.page.set_border_width (10)
		self.page.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		self.page.show ()

		outterpacker = gtk.GtkPacker ()
		outterpacker.show ()
		self.page.add (outterpacker)

		frame = gtk.GtkFrame ()
		frame.set_border_width (10)
		frame.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		frame.set_usize (350, 130)
		frame.show ()
		outterpacker.add (frame)
 
		table = gtk.GtkTable (2, 4)
		table.set_col_spacing (0, 15)
		table.show ()
		frame.add (table)
		label = gtk.GtkLabel (_("Connection Retries"))
		label.show ()
		table.attach (label, 0, 1, 0, 1)
		label = gtk.GtkLabel (_("Idle Timeout (sec)"))
		label.show ()
		table.attach (label, 1, 2, 0, 1)
		self.retries_box = gtk.GtkEntry ()
		self.retries_box.set_usize (100, -1)
		if opts is not None and opts['retries']:
			self.retries_box.set_text ("%s" %opts['retries'])
		self.retries_box.show ()
		table.attach (self.retries_box, 0, 1, 1, 2, xoptions=GTK.EXPAND)
		self.idle_box = gtk.GtkEntry ()
		self.idle_box.set_usize (100, -1)
		if opts is not None and opts['main_idle']:
			self.idle_box.set_text ("%s" %opts['main_idle'])
		self.idle_box.show ()
		table.attach (self.idle_box, 1, 2, 1, 2, xoptions=GTK.EXPAND)
		label = gtk.GtkLabel (_("Connect timeout (sec)"))
		label.show ()
		table.attach (label, 0, 1, 2, 3)
		label = gtk.GtkLabel (_("Login retry delay (sec)"))
		label.show ()
		table.attach (label, 1, 2, 2, 3)
		self.timeout_box = gtk.GtkEntry ()
		self.timeout_box.set_usize (100, -1)
		if opts is not None and opts['timeout']:
			self.timeout_box.set_text ("%s" %opts['timeout'])
		self.timeout_box.show ()
		table.attach (self.timeout_box, 0, 1, 3, 4, xoptions=GTK.EXPAND)
		self.delay_box = gtk.GtkEntry ()
		self.delay_box.set_usize (100, -1)
		if opts is not None and opts['delay']:
			self.delay_box.set_text ("%s" %opts['delay'])
		self.delay_box.show ()
		table.attach (self.delay_box, 1, 2, 3, 4, xoptions=GTK.EXPAND)

		frame = gtk.GtkFrame ()
		frame.set_border_width (10)
		frame.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		frame.set_usize (350, 65)
		frame.show ()
		outterpacker.add (frame)

		box = gtk.GtkHBox ()
		box.show ()
		frame.add (box)
		self.proxy_check = gtk.GtkCheckButton (_("Use proxy server"))
		if opts is not None and opts['proxy']:
			self.proxy_check.set_active (gtk.TRUE)
		self.proxy_check.show ()
		box.pack_start (self.proxy_check, padding=100)

	def get_page (self):
		return self.page

	def get_options (self):
		"""Returns a hash containing the information in the tab's input boxes."""

		return {
			'retries' : int (self.retries_box.get_text ()),
			'main_idle' : int (self.idle_box.get_text ()),
			'timeout' : int (self.timeout_box.get_text ()),
			'delay' : int (self.delay_box.get_text ()),
			'proxy' : self.proxy_check.get_active ()
		}

class QuickConnectWin:

	"""Quick connection window

	This object brings up a pop-up window which only asks the user which host to
	connect to and does so assuming the rest of the application default parameters
	for connecting to an FTP server."""

	def __init__ (self, callback, opts):
		"""Create the quick connect object and display the pop-up. 'callback' is a reference
		to the function to execute provided the user has provided a host to connect to and
		clicked "Ok". 'opts' is a reference to the default options to assume when connecting
		(usually the application defaults)."""

		self.callback = callback

		self.opts = { }
		# Iterate through the keys since opts is not a real hash instance
		# but just an instance of Application
		if opts is not None:
			for key in opts.keys ():
				self.opts[key] = opts[key]

		self.host = GtkExtra.input_box (title=_("ftpcube - Quick Connect"), message=_("Remote Host:"))

		if not self.host:
			return
		else:
			self.callback (self)

	def get_options (self):
		"""Returns a hash containing all of the application defaults for connecting as well
		as the specified host."""

		opts = self.opts
		self.host = string.strip (self.host)
		opts['host'] = self.host

		# Perform all conversions on the main.app defaults
		# to integers that require conversion
		if main.app['port']:
			opts['port'] = int (main.app['port'])
		if main.app['retries']:
			opts['retries'] = int (main.app['retries'])
		if main.app['main_idle']:
			opts['main_idle'] = int (main.app['main_idle'])
		if main.app['timeout']:
			opts['timeout'] = int (main.app['timeout'])
		if main.app['delay']:
			opts['delay'] = int (main.app['delay'])

		return opts
