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

class BookmarkWin:

	window = None

	tree = None
	list = None

	headings = [ 'Description', 'Host', 'Username', 'Directory' ]

	bookdir = None

	def __init__ (self, config_dir):

		self.window = gtk.GtkWindow ()
		self.window.set_title ("ftpcube - Bookmarks")
		self.window.set_usize (750, 500)
		self.window.show ()

		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.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)
		# 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
		if not os.path.exists (self.bookdir + os.sep + "bookmarks"):
			os.makedirs (self.bookdir + os.sep + "bookmarks")

		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 (self.bookdir + os.sep + "bookmarks", parent=node)
		self.tree.expand (node)
		self.tree.select (node)
		self.display_bookmarks ()

	def menu_init (self, container):

		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/_Delete Folder', None,  edit,        3,   ''),
			('/_Edit/<Separator>', None,     None,        0,   '<Separator>'),
			('/_Edit/_Edit Bookmark', None,  edit,        4,   ''),
			('/_Edit/Delete Bookmark', None, edit,        5,   ''),
			('/_Edit/Rename Bookmark', None, edit,        6,   ''),
			('/_Edit/<Separator>', None,     None,        0,   '<Separator>'),
			('/_Edit/Add _Bookmark', None,   edit,        7,   ''),
		])
		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):

		if action == 1:
			self.connect ()

	def menu_process_edit (self, action, widget):

		if action == 2:
			self.add_folder ()
		elif action == 3:
			self.remove_folder ()
		elif action == 4:
			self.edit_bookmark ()
		elif action == 5:
			self.remove_bookmark ()
		elif action == 6:
			self.rename_bookmark ()
		elif action == 7:
			config_opts = main.app.load_config (main.app.get_config_file ())
			connect_win = connectwin.ConnectWin (self.add_bookmark, config_opts)


	def toolbar_init (self, container):

		toolbox = gtk.GtkHandleBox ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)

		connect_pic, connect_mask = gtk.create_pixmap_from_xpm (self.window, None, main.ICONS_PREFIX + os.sep + "quick_connect.xpm")
		edit_pic, edit_mask = gtk.create_pixmap_from_xpm (self.window, None, main.ICONS_PREFIX + os.sep + "edit_bookmark.xpm")

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

		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_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):

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

	def connect (self):

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		bookfile = path + os.sep + 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:
			connect_win = connectwin.ConnectWin (main.app.initiate_connection, options)
		else:

			def callback (callback=main.app.initiate_connection, opts=options):

				main.app.main_thread.cancel_thread ()
				connect_win = connectwin.ConnectWin (callback, opts)

			disconnect_box = app.DisconnectBox (callback)

		self.destroy ()

	def select_items (self, list, r, c, event):

		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, main.ICONS_PREFIX + os.sep + "folder.xpm")

	def tree_select (self, tree, row, col, event):

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

		# Callback function for reloading the bookmark tree
		def add_new_folder (parent, name, self=self):
			node = self.tree.insert_node (parent, None, [ 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)

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

	def remove_folder (self):

		for node in self.tree.selection:
			path = self.get_path_to_node (node)
			try:
				os.rmdir (path)
			except OSError, (errno, strerror):
				# For now just print
				print "Couldn't remove directory: %s" %strerror
				return
			self.tree.remove_node (node)

	def get_path_to_current_node (self):

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

	def get_path_to_node (self, node):

		if node.parent is None:
			return self.bookdir + os.sep + (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 = (self.tree.node_get_pixtext (node, 0))[0] + os.sep + path
			return self.bookdir + os.sep + path

	def add_bookmark (self, connectwin):

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

	def edit_bookmark (self):

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

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

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

		connect_win = connectwin.ConnectWin (after_edit, options)

	def rename_bookmark (self):

		path = self.get_path_to_current_node ()
		name = self.list.get_text (self.list.selection.pop (0), 0)
		rename_win = RenameBookmarkWin (path, name, callback=self.display_bookmarks)

	def remove_bookmark (self):

		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 = path + os.sep + name
		try:
			os.unlink (path)
		except OSError, (errno, strerror):
			# For now just print
			print "Couldn't remove file: %s" %strerror
			return
		self.display_bookmarks ()

	def save_bookmark (self, bookmark, opts):

		file = open (bookmark, 'w')
		for key in opts.keys ():
			if opts[key] is not None:
				file.write ("%s=%s\n" %(key, opts[key]))
			else:
				file.write ("%s=\n" %key)
		file.close ()

	def load_bookmark (self, file):

		bookmark = { }
		file = open (file)
		line = file.readline ()
		while line:
			if line[-2] == '\r\n':
				line = line[:-2]
			elif line[-1] in '\r\n':
				line = line[:-1]
			index = string.find (line, '=')
			key = line[:index]
			value = line[index + 1:]
			bookmark[key] = value
			line = file.readline ()
		file.close ()
		return bookmark

	def load_bookmark_tree (self, path, parent=None):

		try:
			files = os.listdir (path)
		except OSError, (errno, strerror):
			# Couldn't read it, so do nothing right now
			# Will need to change this later
			print "Couldn't open directory: %s" %strerror
			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 = path + os.sep + 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):

		path = self.get_path_to_current_node ()

		try:
			files = os.listdir (path)
		except OSError, (errno, strerror):
			# Couldn't read it, so do nothing right now
			# Will need to change this later
			print "Couldn't open directory: %s" %strerror
			return

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

		for file in files:
			full_path = path + os.sep + 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 ()

class AddFolderWin:

	dialog = None
	entry = None

	def __init__ (self, path=None, parent=None, reload=None):

		self.path = path
		self.parent = parent
		self.reload_func = reload

		self.dialog = gtk.GtkDialog ()
		self.dialog.set_title ("ftpcube - Add Bookmark Folder")
		self.dialog.set_policy (gtk.FALSE, gtk.FALSE, gtk.TRUE)
		self.dialog.set_position (gtk.WIN_POS_CENTER)
		self.dialog.show ()

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

		button = gtk.GtkButton ("Ok")
		button.set_usize (50, 25)
		button.connect ("clicked", self.ok)
		box.pack_start (button, expand=gtk.FALSE)
		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, padding=15)

		label = gtk.GtkLabel ("Folder Name:")
		label.show ()
		packer.add (label, anchor=GTK.ANCHOR_NW, i_pad_x=45)
		self.entry = gtk.GtkEntry ()
		self.entry.set_usize (225, -1)
		self.entry.show ()
		packer.add (self.entry, options=GTK.EXPAND, i_pad_y=10)

	def ok (self, button):

		text = self.entry.get_text ()

		if text == '':
			return
		else:
			self.dialog.hide ()
			self.dialog.destroy ()
			os.mkdir (self.path + os.sep + text, 0700)
			self.reload_func (self.parent, text)

	def cancel (self, button):

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

class RenameBookmarkWin:

	dialog = None
	entry = None

	def __init__ (self, path, name, callback):

		self.path = path
		self.name = name
		self.callback = callback

		self.dialog = gtk.GtkDialog ()
		self.dialog.set_title ("ftpcube - Rename Bookmark")
		self.dialog.set_policy (gtk.FALSE, gtk.FALSE, gtk.TRUE)
		self.dialog.set_position (gtk.WIN_POS_CENTER)
		self.dialog.show ()

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

		button = gtk.GtkButton ("Ok")
		button.set_usize (50, 25)
		button.connect ("clicked", self.ok)
		box.pack_start (button, expand=gtk.FALSE)
		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, padding=15)

		label = gtk.GtkLabel ("Rename to:")
		label.show ()
		packer.add (label, anchor=GTK.ANCHOR_NW, i_pad_x=45)
		self.entry = gtk.GtkEntry ()
		self.entry.set_usize (225, -1)
		self.entry.set_text (self.name)
		self.entry.show ()
		packer.add (self.entry, options=GTK.EXPAND, i_pad_y=10)

	def ok (self, button):

		text = self.entry.get_text ()

		if text == '':
			return
		else:
			self.dialog.hide ()
			self.dialog.destroy ()
			old_path = self.path + os.sep + self.name
			new_path = self.path + os.sep + text
			try:
				os.rename (old_path, new_path)
			except OSError, (errno, strerror):
				print "Couldn't rename bookmark file %s: %s" %(new_path, strerror)
				return
			self.callback ()

	def cancel (self, button):

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