#
# 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, GTK
import os
import string # For backwards compatibility with 1.5.x
import main

from intl import _

class BrowseWin:

	"""Directory browsing object base class

	Contains the member functions that make up the base class for
	displaying a browsing window that allows for both local and
	remote changing of directories. The display offers an entry
	both for direct entry, as well as a tree for selecting the
	directory.
	"""

	def __init__ (self):
		"""Creates the browsing window instance, draws the widgets,
		and sets itself as model. Not that the base class does not
		cause self.dialog to become visible but rather relies on the
		sub class to do this, as it is best to make the dialog
		visible at the very end of intialization."""

		self.dialog = gtk.GtkDialog ()
		self.dialog.set_title (_("ftpcube - Change Directory"))
		self.dialog.set_policy (gtk.FALSE, gtk.FALSE, gtk.TRUE)
		self.dialog.set_position (gtk.WIN_POS_CENTER)
		self.dialog.set_usize (400, 350)
		self.dialog.connect ("key_press_event", self.key_press)
		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 ()

		packer = gtk.GtkPacker ()
		packer.show ()
		self.dialog.vbox.add (packer)

		label = gtk.GtkLabel (_("Directory:"))
		label.show ()
		packer.add (label, anchor=GTK.ANCHOR_NW, i_pad_x=40, i_pad_y=10)

		self.entry_box = gtk.GtkEntry ()
		self.entry_box.show ()
		packer.add (self.entry_box, options=GTK.FILL_X, pad_x=35)

		frame = gtk.GtkFrame ()
		frame.show ()
		packer.add (frame, options=GTK.EXPAND+GTK.FILL_X+GTK.FILL_Y, pad_x=15, pad_y=15)
		scrolled = gtk.GtkScrolledWindow ()
		scrolled.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		scrolled.show ()
		frame.add (scrolled)
		self.tree = gtk.GtkCTree ()
		self.tree.connect ("select_row", self.tree_select)
		self.tree.show ()
		scrolled.add (self.tree)

		self.load_pixmaps ()

	def load_pixmaps (self):
		"""Loads pixmaps for the browse tree."""

		self.folder_pic, self.folder_mask = gtk.create_pixmap_from_xpm (self.tree, None, os.path.join (main.ICONS_PREFIX, "folder.xpm"))

	def key_press (self, win, event):
		"""Looks for carriage return key pressed and invokes the ok handler
		if found."""

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

	def ok (self, button):
		"""A basic handler for the ok action button."""

		self.cancel (button)

	def cancel (self, button):
		"""A basic handler for the cancel action button."""

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

	def tree_select (self, tree, row, col, event):
		"""Updates the text entry box when an entry in the tree is selected."""

		if hasattr (event, 'type'):
			node = self.tree.node_nth (row)
			path = self.node_to_path (node)
			self.entry_box.set_text (path)

	def node_to_path (self, node):
		"""To Be overridden in the subclass. Finds a node in the hierachy
		and returns its path."""

		pass

class LocalCWD (BrowseWin):

	"""Local Change Directory Window Object

	Displays a window allowing the user to change the local directory.
	The tree begins expanded to the user's current directory. If the
	user clicks on a directory that itself contains further
	sub-directories, these sub-directories are automatically expanded
	and added to the tree."""

	def __init__ (self, path):
		"""Create the window instance, load the contents of the tree
		and expand to our current path."""

		BrowseWin.__init__ (self)
		self.tree.show_stub = 1

		# Load in the list contents
		insert_node = self.tree.insert_node (None, None, [ os.sep ],
			pixmap_closed = self.folder_pic,
			mask_closed = self.folder_mask,
			pixmap_opened = self.folder_pic,
			mask_opened = self.folder_mask,
			is_leaf = gtk.FALSE)
		self.tree.toggle_expansion (insert_node)
		self.load_tree (path, insert_node)

		# Set the entry_box
		self.entry_box.set_text (path)

		self.dialog.show ()

	def ok (self, button):
		"""Performs the actual changing of local directory upon the
		ok action."""

		BrowseWin.ok (self, button)
		path = self.entry_box.get_text ()
		main.app.update_local_listing (path)

	def load_tree (self, path, parent):
		"""Loads the hierachy along 'path' using 'parent' as its
		root. Only directories are added as sub-directories during
		expansion."""

		dirs = [ ]
		full_path = os.sep
		for item in string.split (path, os.sep):
			if item:
				full_path = os.path.join (full_path, item)
				if os.path.isdir (full_path):
					dirs.append (item)
		dirs.insert (0, os.sep)

		cur_path = os.sep
		while dirs:
			dir = dirs.pop (0)
			cur_path = os.path.join (cur_path, dir)
			listing = self.get_dir_listing_only (cur_path)

			# Insert the new node and assign the parent to the appropriate node
			new_parent = None
			for item in listing:
				node = self.tree.insert_node (parent, None, [ item ],
					pixmap_closed = self.folder_pic,
					mask_closed = self.folder_mask,
					pixmap_opened = self.folder_pic,
					mask_opened = self.folder_mask,
					is_leaf = gtk.FALSE)
				self.tree.toggle_expansion (node)
				if dirs and item == dirs[0]:
					new_parent = node
			parent = new_parent

	def get_dir_listing_only (self, path):
		"""Converts the directory listing at 'path' to a listing of
		only sub-directories. A blank list is returned if an exception
		occurred while generating the listing."""

		try:
			listing = os.listdir (path)
		except OSError, strerror:
			return [ ]
		# Make sure we're only looking at directories
		listing = filter (lambda x, path=path: os.path.isdir (os.path.join (path, x)), listing)
		return listing

	def tree_select (self, tree, row, col, event):
		"""Upon selection of a directory, the entry box is updated with
		the path of the currently selected directory. If any sub-directories
		exist, they are expanded."""

		if hasattr (event, 'type'):
			
			node = self.tree.node_nth (row)
			path = self.node_to_path (node)
			self.entry_box.set_text (path)

			if not node.children:
				self.expand_node (node, path)
				self.tree.expand (node)

	def expand_node (self, parent, path):
		"""This expands a single node by adding 'parent's sub-directories
		into the tree with the given 'path'."""

		listing = self.get_dir_listing_only (path)
		self.tree.freeze ()
		for item in listing:
			node = self.tree.insert_node (parent, None, [ item ],
				pixmap_closed = self.folder_pic,
				mask_closed = self.folder_mask,
				pixmap_opened = self.folder_pic,
				mask_opened = self.folder_mask,
				is_leaf = gtk.FALSE)
		self.tree.thaw ()

	def node_to_path (self, node):
		"""Converts a single node to its hierachical path representation."""

		path = [ ]
		while node is not None:
			path.insert (0, self.tree.get_node_info (node)[0])
			node = node.parent
		path = path[0] + string.join (path[1:], os.sep)
		return path

class RemoteCWD (BrowseWin):

	"""Remote Change Directory Window Object

	Displays a window allowing the user to change the remote directory.
	The tree begins expanded to the user's current directory. This window
	makes use of the remote file window caching object to load the
	remote listing. At any given time, the tree spans the entire contents
	of the directory cache and only shows the fringes of the directories
	that the user has visited."""

	def __init__ (self, cache, path):
		"""Creates the window instance and loads the cache into the tree.
		The tree is then expanded along path so that the user starts in
		his current remote directory."""

		BrowseWin.__init__ (self)
		self.cache = cache

		# Load in the list contents
		insert_node = self.tree.insert_node (None, None, [ self.cache.get_delim () ],
			pixmap_closed = self.folder_pic,
			mask_closed = self.folder_mask,
			pixmap_opened = self.folder_pic,
			mask_opened = self.folder_mask,
			is_leaf = gtk.FALSE)
		self.load_tree (self.cache.get_root (), insert_node)

		# Set ourselves to inital contents
		self.expand_path (path)
		self.entry_box.set_text (path)

		self.dialog.show ()

	def ok (self, button):
		"""Executes the remote change of directory upon clicking of the
		ok action button."""

		BrowseWin.ok (self, button)
		path = self.entry_box.get_text ()
		main.app.main_thread.cwd (path)

		# Messy but will have to rework these later
		main.app.remotewin.set_dir (path)
		main.app.remotewin.get_listing ()

	def load_tree (self, node, parent):
		"""Loads the directory structure from within the cache into the tree. Only
		directories are put into the tree."""

		directories = filter (lambda x: not x[0] == '..' and x[3][0] == 'd', node.data)
		for dir in directories:
			dir_name = dir[0]
			insert_node = self.tree.insert_node (parent, None, [ dir_name ],
				pixmap_closed = self.folder_pic,
				mask_closed = self.folder_mask,
				pixmap_opened = self.folder_pic,
				mask_opened = self.folder_mask,
				is_leaf = gtk.FALSE)
			child = self.cache.get_child_node (node, dir_name)
			if child is not None:
				self.load_tree (child, insert_node)

	def node_to_path (self, node):
		"""Converts a node to its hierachical path representation."""

		path = [ ]
		while node is not None:
			path.insert (0, self.tree.get_node_info (node)[0])
			node = node.parent
		path = path[0] + string.join (path[1:], self.cache.get_delim ())
		return path

	def expand_path (self, path):
		"""Expands the nodes along 'path' in the tree. This is used primary
		for expanded the tree to the user's current remote directory."""

		path = filter (lambda x: x, string.splitfields (path, self.cache.get_delim ()))
		node = self.tree.node_nth (0)
		self.tree.expand (node)
		if not path:
			return

		while path:
			node_name = path.pop (0)
			child_names = map (lambda x, tree=self.tree: tree.get_node_info (x)[0], node.children)
			try:
				child_index = child_names.index (node_name)
			except ValueError, strerror:
				return None
			node = node.children[child_index]
			self.tree.expand (node)
