#
# 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, pickle
import main, app, connectwin

from intl import _

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

class BookmarkWin:

	"""Bookmark window object

	Contains the member functions for displaying the bookmark window and
	the functionality for managing, creating, connecting, and editing
	site bookmarks. Bookmarks are stored in the ftpcube configuration
	directory under [CONFIG_DIR]/bookmarks and are stored in pickle
	format."""

	headings = [ _('Description'), _('Host'), _('Username'), _('Directory') ]

	def __init__ (self, config_dir):
		"""Creates the bookmark object and brings up the bookmark
		window. The "bookmarks" directory is created in 'config_dir'
		if it does not already exist. The bookmark tree is then loaded
		into memory for graphical display."""

		self.window = gtk.GtkWindow ()
		self.window.set_title (_("ftpcube - Bookmarks"))
		self.window.set_usize (750, 500)
		icon_pic, icon_mask = gtk.create_pixmap_from_xpm (self.window, None, os.path.join (main.ICONS_PREFIX, "ftpcube.xpm"))
		self.window.set_icon (gtk.GtkPixmap (icon_pic, icon_mask))

		packer = gtk.GtkPacker ()
		packer.show ()
		self.window.add (packer)

		self.menu_init (packer)
		self.toolbar_init (packer)

		hbox = gtk.GtkHBox ()
		hbox.show ()
		packer.add (hbox, options=GTK.EXPAND+GTK.FILL_X+GTK.FILL_Y)
		pane = gtk.GtkHPaned ()
		pane.show ()
		hbox.add (pane)

		frame = gtk.GtkFrame ()
		frame.show ()
		pane.pack1 (frame)
		self.tree = gtk.GtkCTree ()
		self.tree.connect ("select_row", self.tree_select)
		self.tree.connect ("button_press_event", self.tree_popup_menu)
		self.tree.show ()
		frame.add (self.tree)

		frame = gtk.GtkFrame ()
		frame.show ()
		pane.pack2 (frame)
		scrolled = gtk.GtkScrolledWindow ()
		scrolled.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		scrolled.show ()
		frame.add (scrolled)

		self.list = gtk.GtkCList (4, self.headings)
		self.list.connect ("select_row", self.select_items)
		self.list.connect ("button_press_event", self.list_popup_menu)
		# Set up list column defaults
		for i in range(len (self.headings)):
			self.list.set_column_width (i, 100)
			self.list.set_column_justification (i, gtk.JUSTIFY_LEFT)
		self.list.set_column_width (0, 200)
		self.list.show ()
		scrolled.add_with_viewport (self.list)

		# Compute the position of the pane divider
		pane.set_position (250)

		# Check if the bookmark directory in the config directory exists
		self.bookdir = config_dir
		path = os.path.join (self.bookdir, 'bookmarks')
		if not os.path.exists (path):
			try:
				os.mkdir (path)
				os.chmod (path, 0700)
			except OSError, strerror:
				GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))

		self.load_pixmaps ()
		# Now load the initial bookmarks directory into the tree
		node = self.tree.insert_node (None, None, [ "bookmarks" ],
						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_bookmark_tree (os.path.join (self.bookdir, "bookmarks"), parent=node)
		self.tree.expand (node)
		self.tree.select (node)
		self.display_bookmarks ()

		self.window.show ()

	def menu_init (self, container):
		"""Initializes and displays the main menu for the bookmark window."""

		ag = gtk.GtkAccelGroup ()
		item = gtk.GtkItemFactory (gtk.GtkMenuBar, "<main>", ag)
		self.window.add_accel_group (ag)

		site = self.menu_process_site
		edit = self.menu_process_edit

		item.create_items ([
			(_('/_Site'),                 None,  None,        0,   '<Branch>'),
			(_('/Site/_Connect'),         None,  site,        1,   ''),
			(_('/_Edit'),                 None,  None,        0,   '<Branch>'),
			(_('/_Edit/Add _Folder'),     None,  edit,        2,   ''),
			(_('/_Edit/Rename Folder'),   None,  edit,        3,   ''),
			(_('/_Edit/Delete Folder'),   None,  edit,        4,   ''),
			(_('/_Edit/<Separator>'),     None,  None,        0,   '<Separator>'),
			(_('/_Edit/Add _Bookmark'),   None,  edit,        5,   ''),
			(_('/_Edit/_Edit Bookmark'),  None,  edit,        6,   ''),
			(_('/_Edit/Rename Bookmark'), None,  edit,        7,   ''),
			(_('/_Edit/Delete Bookmark'), None,  edit,        8,   ''),
		])
		mainmenu = item.get_widget ('<main>')

		# Now add the menu bar into the canvas
		menubox = gtk.GtkVBox ()
		menubox.pack_start (mainmenu, expand=gtk.FALSE)
		mainmenu.show ()
		menubox.show ()
		container.add (menubox, side=gtk.SIDE_TOP, anchor=gtk.ANCHOR_NW, options=GTK.FILL_X)

	def menu_process_site (self, action, widget):
		"""Performs the appropriate 'action' for the main menu site option."""

		if action == 1:
			self.connect ()

	def menu_process_edit (self, action, widget):
		"""Performs the appropriate 'action' for the main menu edit option."""

		if action == 2:
			self.add_folder ()
		elif action == 3:
			self.rename_folder ()
		elif action == 4:
			self.remove_folder ()
		elif action == 5:
			self.add_bookmark ()
		elif action == 6:
			self.edit_bookmark ()
		elif action == 7:
			self.rename_bookmark ()
		elif action == 8:
			self.remove_bookmark ()

	def toolbar_init (self, container):
		"""Initializes and displays the bookmark toolbar."""

		toolbox = gtk.GtkHandleBox ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)
		toolbar.set_space_size (10)
		toolbar.set_space_style (GTK.TOOLBAR_SPACE_LINE)

		connect_pic, connect_mask = gtk.create_pixmap_from_xpm (self.window, None, os.path.join (main.ICONS_PREFIX, "quick_connect.xpm"))
		folder_pic, folder_mask = gtk.create_pixmap_from_xpm (self.window, None, os.path.join (main.ICONS_PREFIX, "add_folder.xpm"))
		add_pic, add_mask = gtk.create_pixmap_from_xpm (self.window, None, os.path.join (main.ICONS_PREFIX, "add_bookmark.xpm"))
		edit_pic, edit_mask = gtk.create_pixmap_from_xpm (self.window, None, os.path.join (main.ICONS_PREFIX, "edit_bookmark.xpm"))

		def connect_button (button, toolbar=toolbar, self=self):
			self.connect ()

		def folder_button (button, toolbar=toolbar, self=self):
			self.add_folder ()

		def add_button (button, toolbar=toolbar, self=self):
			self.add_bookmark ()

		def edit_button (button, toolbar=toolbar, self=self):
			self.edit_bookmark ()

		toolbar.append_item (_("Connect"), _("Connect"),
			_("Connect"),
			gtk.GtkPixmap (connect_pic, connect_mask),
			connect_button)
		toolbar.append_space ()
		toolbar.append_item (_("Add Folder"), _("Add Folder"),
			_("Add Folder"),
			gtk.GtkPixmap (folder_pic, folder_mask),
			folder_button)
		toolbar.append_item (_("Add Bookmark"), _("Add Bookmark"),
			_("Add Bookmark"),
			gtk.GtkPixmap (add_pic, add_mask),
			add_button)
		toolbar.append_item (_("Edit Bookmark"), _("Edit Bookmark"),
			_("Edit Bookmark"),
			gtk.GtkPixmap (edit_pic, edit_mask),
			edit_button)

		toolbar.show ()
		toolbox.add (toolbar)
		toolbox.show ()
		container.add (toolbox, side=gtk.SIDE_TOP, anchor=gtk.ANCHOR_NW, options=GTK.FILL_X)

	def destroy (self):
		"""Removes the bookmark window."""

		self.window.hide ()
		self.window.destroy ()

	def tree_popup_menu (self, tree, event):
		"""Displays the right-click pop-up menu when a bookmark subfolder is
		right-clicked."""

		if event.button == 3 and not tree.selection == []:
			opts = [
				(_('Rename Folder'), None, lambda b, self=self: self.rename_folder ()),
				(_('Delete Folder'), None, lambda b, self=self: self.remove_folder ()),
			]

			menu = GtkExtra.MenuFactory (GtkExtra.MENU_FACTORY_MENU)
			menu.add_entries (opts)
			menu.popup (None, None, None, event.button, event.time)
			menu.show ()

	def list_popup_menu (self, list, event):
		"""Displays the right-click pop-up menu when an individual bookmark
		is right-clicked."""

		if event.button == 3 and not self.list.selection == [ ]:
			opts = [
				(_('/Edit Bookmark'),   None, lambda b, self=self: self.edit_bookmark ()),
				(_('/Rename Bookmark'), None, lambda b, self=self: self.rename_bookmark ()),
				(_('/Delete Bookmark'), None, lambda b, self=self: self.remove_bookmark ()),
			]

			menu = GtkExtra.MenuFactory (GtkExtra.MENU_FACTORY_MENU)
			menu.add_entries (opts)
			menu.popup (None, None, None, event.button, event.time)
			menu.show ()

	def connect (self):
		"""Connect to the selected bookmark by bringing up the connect window with the
		appropriate options."""

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		bookfile = os.path.join (path, name)
		options = self.load_bookmark (bookfile)

		# Check if we're breaking a connection and display the disconnect box
		# if we are
		if main.app.main_thread is None:
			connectwin.ConnectWin (main.app.initiate_connection, options)
		else:
			main.app.display_disconnect_box (main.app.main_thread.get_host (), 'ConnectWin', opts=options)

		self.destroy ()

	def select_items (self, list, r, c, event):
		"""Look for double clicks and bring up the connect window for the selected bookmark."""

		if hasattr (event, 'type'):
			# Check for double clicks
			if event.type == 5:

				self.list.selection.append (r)
				self.connect ()

	def load_pixmaps (self):

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

	def tree_select (self, tree, row, col, event):
		"""Expands the selected double-clicked subfolder by loading all the bookmarks into the
		bookmark list."""

		if hasattr (event, 'type'):
			if event.type == 5:
				node = self.tree.node_nth (row)
				self.tree.expand (node)

		self.display_bookmarks ()

	def add_folder (self):
		"""A pop-up window is brough up asking for the name of the new bookmark sub-folder to
		create and the folder is then created with private permissions (if the OS supports
		permissions."""

		if not self.tree.selection:
			# Just use the top level
			node = None
			path = self.bookdir
		else:
			node = self.tree.selection.pop (0)
			path = self.get_path_to_node (node)

		folder = GtkExtra.input_box (title=_("ftpcube - Add Bookmark Folder"), message=_("Folder Name:"))

		if folder is not None:
			os.mkdir (os.path.join (path, folder), 0700)
			node = self.tree.insert_node (node, None, [ folder ],
					pixmap_closed = self.folder_pic,
					mask_closed = self.folder_mask,
					pixmap_opened = self.folder_pic,
					mask_opened = self.folder_mask,
					is_leaf = gtk.FALSE)

	def rename_folder (self):
		"""Brings up a pop-up window asking for the new folder name and then moves the
		older folder to the new name. The subfolder tree is then updated to reflect the
		new changes."""

		for node in self.tree.selection:

			# Skip if this is the root node
			if node == self.tree.base_nodes ().pop (0):
				continue

			path = self.get_path_to_node (node)
			new_folder = GtkExtra.input_box (title=_("ftpcube - Rename Folder"), message=_("Rename to:"))
			if new_folder is not None:
				new_path = os.path.join (os.path.dirname (path), new_folder)
				try:
					os.rename (path, new_path)
				except OSError, (errno, strerror):
					GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
				parent = node.parent
				self.tree.remove_node (node)
				node = self.tree.insert_node (parent, None, [ new_folder ],
					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_bookmark_tree (new_path, parent=node)

	def remove_folder (self):
		"""Removes the selected folder and updates the tree accordingly."""

		for node in self.tree.selection:

			# Skip if this is the root node
			if node == self.tree.base_nodes ().pop (0):
				continue

			path = self.get_path_to_node (node)
			try:
				os.rmdir (path)
			except OSError, (errno, strerror):
				GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
				return
			self.tree.remove_node (node)

	def get_path_to_current_node (self):
		"""Recurses through the tree and returns path to the current node (Assuming an implicit
		prefix of './')."""

		# Get the current folder
		if not self.tree.selection:
			return self.get_path_to_node (self.tree.node_nth (0))
		else:
			node = self.tree.selection.pop (0)
			return self.get_path_to_node (node)

	def get_path_to_node (self, node):
		"""The recursive function to traverse the path tree."""

		if node.parent is None:
			return os.path.join (self.bookdir, (self.tree.node_get_pixtext (node, 0))[0])
		else:
			# Figure out current path
			path = (self.tree.node_get_pixtext (node, 0))[0]
			while node.parent is not None:
				node = node.parent
				path = os.path.join ((self.tree.node_get_pixtext (node, 0))[0], path)
			return os.path.join (self.bookdir, path)

	def add_bookmark (self):
		"""Adds a new bookmark by bringing up the connection window with the application
		defaults. The new bookmark is then created when the "ok" button is clicked."""

		config_opts = main.app.load_config (main.app.get_config_file ())
		connectwin.ConnectWin (self.create_bookmark, config_opts)

	def create_bookmark (self, connectwin):
		"""The callback function provided during the creation of the connection window
		object. The callback gets executed when the "ok" button is clicked and is
		provided a reference to the called window 'connectwin' for retrieval of
		the set options. The new bookmark is then created in the current bookmark
		folder."""

		opts = connectwin.get_options ()
		if opts['host']:
			bookfile = os.path.join (self.get_path_to_current_node (), opts['host'])
			self.save_bookmark (bookfile, opts)
			self.display_bookmarks ()

	def edit_bookmark (self):
		"""Brings up the connect window with the options contained in the selected bookmark
		file. Options are saved upon a click of the "ok" button."""

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		bookfile = os.path.join (path, name)
		options = self.load_bookmark (bookfile)

		def after_edit (connectwin, bookfile=bookfile, self=self):

			opts = connectwin.get_options ()
			self.save_bookmark (bookfile, opts)

		connectwin.ConnectWin (after_edit, options)

	def rename_bookmark (self):
		"""Brings up a pop-up window asking for the new name of a bookmark. The selected
		bookmark is then renamed to the new name."""

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		new_name = GtkExtra.input_box (title=_("ftpcube - Rename Bookmark"), message=_("Rename to:"))
		if new_name is not None:
			try:
				os.rename (os.path.join (path, name), os.path.join (path, new_name))
			except OSError, (errno, strerror):
				GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
			self.display_bookmarks ()

	def remove_bookmark (self):
		"""Deletes the selected bookmark and updates the bookmark listing accordingly."""

		if not self.list.selection:
			# Nothing selected so do nothing
			return

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		path = os.path.join (path, name)
		try:
			os.unlink (path)
		except OSError, (errno, strerror):
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
			return
		self.display_bookmarks ()

	def save_bookmark (self, bookmark, opts):
		"""Writes out the bookmark configuration data in 'opts' to the 'bookmark' file."""

		try:
			file = open (bookmark, 'w')
		except IOError, strerror:
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ] ))
			return
		pickler = pickle.Pickler (file)
		pickler.dump (opts)
		file.close ()

		# Set the permission of the bookmark to be sure it's private
		try:
			os.chmod (bookmark, 0600)
		except OSError, strerror:
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ] ))

	def load_bookmark (self, file):
		"""Loads the configuration data from 'file' and returns a hash containing the data."""

		try:
			file = open (file)
		except IOError, strerror:
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ] ))
			return
		pickler = pickle.Unpickler (file)
		bookmark = pickler.load ()
		file.close ()
		return bookmark

	def load_bookmark_tree (self, path, parent=None):
		"""Loads the recursive directory structure within the bookmark directory 'path' into
		the subfolder tree. 'parent' is used as a reference to the parent node during
		recursion through the tree."""

		try:
			files = os.listdir (path)
		except OSError, (errno, strerror):
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
			return

		if not files:
			# Then our parent is really a leaf folder
			self.tree.toggle_expansion (parent)
		else:
			# Add our children into the tree
			for file in files:
				full_path = os.path.join (path, file)
				if os.path.isdir (full_path):
					node = self.tree.insert_node (parent, None, [ file ],
						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_bookmark_tree (full_path, parent=node)

	def display_bookmarks (self):
		"""Displays all of the bookmarks in the current selected bookmark folder in the
		bookmark listing window."""

		path = self.get_path_to_current_node ()

		try:
			files = os.listdir (path)
		except OSError, (errno, strerror):
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ]))
			return

		self.list.freeze ()
		self.list.clear ()

		for file in files:
			full_path = os.path.join (path, file)
			if os.path.isfile (full_path):
				bookmark = self.load_bookmark (full_path)
				self.list.append ((file, "%s:%s" %(bookmark['host'], bookmark['port']),
				                   bookmark['username'], bookmark['remotedir']))

		self.list.thaw ()
