#!/usr/bin/env python

#
# Revelation - a password manager for GNOME 2
# http://oss.codepoet.no/revelation/
# $Id: revelation-applet.in 636 2007-01-16 10:34:55Z erikg $
#
# Applet for account lookup
#
#
# Copyright (c) 2003-2006 Erik Grinaker
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

import gettext, gnome, gnomeapplet, gobject, gtk, os, sys

if "/usr/lib64/python2.6/site-packages" not in sys.path:
	sys.path.insert(0, "/usr/lib64/python2.6/site-packages")

from revelation import config, data, datahandler, dialog, entry, io, ui, util

_ = gettext.gettext


class RevelationApplet(object):
	"Revelation applet"

	def __init__(self, applet, iid):
		self.applet	= applet
		self.iid	= iid

		sys.excepthook	= self.__cb_exception

		gettext.bindtextdomain(config.PACKAGE, config.DIR_LOCALE)
		gettext.bind_textdomain_codeset(config.PACKAGE, "UTF-8")
		gettext.textdomain(config.PACKAGE)

		try:
			self.__init_config()
			self.__init_facilities()
			self.__init_ui()
			self.__init_states()

		except config.ConfigError:
			dialog.Error(None, _('Missing configuration data'), _('The applet could not find its configuration data, please reinstall Revelation.')).run()
			sys.exit(1)


	def __init_config(self):
		"Sets up configuration"

		self.applet.add_preferences("/schemas/apps/revelation-applet/prefs")
		self.config = config.Config(self.applet.get_preferences_key())

		# set up defaults
		# TODO this shouldn't really be necessary, the schema should
		# be used for defaults - is this even possible with the current
		# applet api?
		defaults = {
			"autolock"		: True,
			"autolock_timeout"	: 10,
			"chain_username"	: False,
			"file"			: "",
			"menuaction"		: "show",
			"show_passwords"	: True,
			"show_searchentry"	: True
		}

		for key, value in defaults.items():
			try:
				self.config.get(key)

			except config.ConfigError:
				self.config.set_force(key, value)

		# make sure the launchers have been set up, otherwise
		# install the Revelation schema
		def check_launchers():
			try:
				for entrytype in [ et() for et in entry.ENTRYLIST if et != entry.FolderEntry ]:
					self.config.get("/apps/revelation/launcher/%s" % entrytype.id)

			except config.ConfigError:
				return False

			else:
				return True


		if check_launchers() == False:
			if config.install_schema("%s/revelation.schemas" % config.DIR_GCONFSCHEMAS) == False:
				raise config.ConfigError

			if check_launchers() == False:
				raise config.ConfigError


	def __init_facilities(self):
		"Sets up facilities"

		self.clipboard		= data.Clipboard()
		self.datafile		= io.DataFile(datahandler.Revelation)
		self.entrystore		= data.EntryStore()
		self.entrysearch	= data.EntrySearch(self.entrystore)
		self.items		= ui.ItemFactory(self.applet)
		self.locktimer		= data.Timer()

		self.config.monitor("autolock_timeout", lambda k,v,d: self.locktimer.start(v * 60))
		self.config.monitor("file", self.__cb_config_file)

		self.datafile.connect("changed", self.__cb_file_changed)
		self.datafile.connect("content-changed", self.__cb_file_content_changed)
		self.locktimer.connect("ring", self.__cb_file_autolock)

		self.entrysearch.folders = False


	def __init_states(self):
		"Sets up the initial states"

		self.datafile.emit("changed", self.datafile.get_file())
		os.chdir(os.path.expanduser("~/"))

		self.config.monitor("show_searchentry", self.__cb_config_show_searchentry)


	def __init_ui(self):
		"Sets up the main ui"

		gtk.about_dialog_set_url_hook(lambda d,l: gnome.url_show(l))
		gtk.about_dialog_set_email_hook(lambda d,l: gnome.url_show("mailto:" + l))

		# set up applet
		self.applet.set_flags(gnomeapplet.EXPAND_MINOR)

		# set up window icons
		pixbufs = [ self.items.get_pixbuf("revelation", size) for size in ( 48, 32, 24, 16) ]
		pixbufs = [ pixbuf for pixbuf in pixbufs if pixbuf != None ]

		if len(pixbufs) > 0:
			gtk.window_set_default_icon_list(*pixbufs)

		# set up popup menu
		self.applet.setup_menu("""
			<popup name="button3">
				<menuitem name="file-unlock"	verb="file-unlock"	label=\"""" + _('Unlock File') + """\"		pixtype="stock" pixname="revelation-unlock" />
				<menuitem name="file-lock"	verb="file-lock"	label=\"""" + _('Lock File') + """\"		pixtype="stock" pixname="revelation-lock" />
				<menuitem name="file-reload"	verb="file-reload"	label=\"""" + _('Reload File') + """\"		pixtype="stock" pixname="revelation-reload" />
				<separator />
				<menuitem name="revelation"	verb="revelation"	label=\"""" + _('Start Revelation') + """\"	pixtype="stock" pixname="revelation-revelation" />
				<menuitem name="prefs"		verb="prefs"		label=\"""" + _('Preferences') + """\"		pixtype="stock"	pixname="gtk-properties" />
				<menuitem name="about"		verb="about"		label=\"""" + _('About') + """\"		pixtype="stock"	pixname="gnome-stock-about" />
			</popup>
		""", (
			( "about",		lambda w,d=None: self.about() ),
			( "file-lock",		lambda w,d=None: self.file_close() ),
			( "file-reload",	lambda w,d=None: self.file_reload() ),
			( "file-unlock",	lambda w,d=None: self.file_open(self.config.get("file")) ),
			( "prefs",		lambda w,d=None: self.prefs() ),
			( "revelation",		lambda w,d=None: util.execute_child("/usr/bin/revelation") ),
		), None)

		# set up ui items
		self.entry = ui.Entry()
		self.entry.set_width_chars(14)
		self.entry.connect("activate", self.__cb_entry_activate)
		self.entry.connect("button_press_event", self.__cb_entry_buttonpress)
		self.entry.connect("key_press_event", lambda w,d=None: self.locktimer.reset())

		self.icon = ui.Image()
		self.eventbox = ui.EventBox(self.icon)
		self.eventbox.connect("button_press_event", self.__cb_icon_buttonpress)

		self.hbox = ui.HBox(self.eventbox, self.entry)
		self.applet.add(self.hbox)

		self.applet.show_all()

		# set up various ui element holders
		self.popup_entryview	= None
		self.popup_entrylist	= None

		self.entrymenu		= None



	##### CALLBACKS #####

	def __cb_config_file(self, key, value, data):
		"Config callback for file key changes"

		self.file_close()
		self.applet.get_popup_component().set_prop("/commands/file-unlock", "sensitive", self.config.get("file") != "" and "1" or "0")


	def __cb_config_show_searchentry(self, key, value, data):
		"Config callback for show searchentry setting"

		if value == True:
				self.entry.show()

		else:
				self.entry.hide()


	def __cb_exception(self, type, value, trace):
		"Callback for unhandled exceptions"

		if type == KeyboardInterrupt:
			sys.exit(1)

		traceback = util.trace_exception(type, value, trace)
		sys.stderr.write(traceback)

		if dialog.Exception(None, traceback).run() == True:
			gtk.main()

		else:
			sys.exit(1)


	def __cb_entry_activate(self, widget, data = None):
		"Callback for entry activation (pressing enter etc)"

		self.entry_search(self.entry.get_text(), True)


	def __cb_entry_buttonpress(self, widget, data = None):
		"Callback for entry button presses"

		self.locktimer.reset()

		if data.button == 1:
			self.applet.request_focus(data.time)


	def __cb_file_autolock(self, widget, data = None):
		"Callback for autolocking the file"

		if self.config.get("autolock") == True:
			self.file_close()


	def __cb_file_content_changed(self, widget, data = None):
		"Callback for changed file content"

		try:
			self.__file_load(self.datafile.get_file(), self.datafile.get_password())

		except dialog.CancelError:
			pass

		except datahandler.PasswordError:
			self.file_close()

		except datahandler.Error:
			pass


	def __cb_file_changed(self, widget, data = None):
		"Callback for changed data file"

		popup = self.applet.get_popup_component()

		if self.datafile.get_file() == None:
			self.entry.set_text("")

			popup.set_prop("/commands/file-unlock", "sensitive", self.config.get("file") != "" and "1" or "0")
			popup.set_prop("/commands/file-lock", "sensitive", "0")
			popup.set_prop("/commands/file-reload", "sensitive", "0")

			self.icon.set_from_stock(ui.STOCK_REVELATION_LOCKED, ui.ICON_SIZE_APPLET)

		else:
			popup.set_prop("/commands/file-unlock", "sensitive", "0")
			popup.set_prop("/commands/file-lock", "sensitive", "1")
			popup.set_prop("/commands/file-reload", "sensitive", "1")

			self.icon.set_from_stock(ui.STOCK_REVELATION, ui.ICON_SIZE_APPLET)


	def __cb_icon_buttonpress(self, widget, data = None):
		"Callback for buttonpress on button"

		if data.button != 1:
			return False

		self.entry_menu(data.time)

		return True


	def __cb_popup_activate(self, widget, data = None):
		"Takes appropriate action when a menu item is activated"

		self.locktimer.reset()

		action = self.config.get("menuaction")

		if action == "show":
			self.entry_show(data)

		elif action == "copy":
			self.entry_copychain(data)

		elif self.__launcher_valid(data):
			self.entry_goto(data)

		else:
			self.entry_show(data)



	##### PRIVATE METHODS #####

	def __close_popups(self):
		"Closes any open popups"

		self.locktimer.reset()

		if hasattr(self, "popup_entryview") == True and self.popup_entryview != None:
			self.popup_entryview.destroy()

		if hasattr(self, "popup_entrylist") == True and self.popup_entrylist != None:
			self.popup_entrylist.destroy()

		if hasattr(self, "entrymenu") == True and self.entrymenu != None:
			self.entrymenu.hide()


	def __file_load(self, file, password = None):
		"Loads a data file"

		if file in ( "", None ):
			return False

		if dialog.present_unique(dialog.PasswordOpen) == True:
			return False

		entrystore = self.datafile.load(file, password, lambda: dialog.run_unique(dialog.PasswordOpen, None, os.path.basename(file)))

		self.entrystore.clear()
		self.entrystore.import_entry(entrystore, None)

		self.entrymenu = self.__generate_entrymenu(self.entrystore)
		self.locktimer.start(self.config.get("autolock_timeout") * 60)

		self.__close_popups()

		return True


	def __flash_entry(self, color = "#ffbaba", duration = 500):
		"Flashes the entry with a color"

		color_normal	= ui.Entry().rc_get_style().base[gtk.STATE_NORMAL]
		color_new	= gtk.gdk.color_parse(color)

		self.entry.modify_base(gtk.STATE_NORMAL, color_new)
		gobject.timeout_add(duration, lambda: self.entry.modify_base(gtk.STATE_NORMAL, color_normal))


	def __focus_entry(self):
		"Gives focus to the entry"

		self.applet.request_focus(long(0))


	def __generate_entrymenu(self, entrystore, parent = None):
		"Generates an entry menu tree"

		menu = gtk.Menu()

		for i in range(entrystore.iter_n_children(parent)):
			iter = entrystore.iter_nth_child(parent, i)

			e = entrystore.get_entry(iter)
			item = ui.ImageMenuItem(type(e) == entry.FolderEntry and ui.STOCK_FOLDER or e.icon, e.name)
			item.connect("select", lambda w,d=None: self.locktimer.reset())

			if type(e) == entry.FolderEntry:
				item.set_submenu(self.__generate_entrymenu(entrystore, iter))

			else:
				item.connect("activate", self.__cb_popup_activate, e)

			menu.append(item)

		return menu


	def __get_launcher(self, e):
		"Returns a launcher command for an entry, if possible"

		command = self.config.get("/apps/revelation/launcher/%s" % e.id)

		if command in ( "", None ):
			return None

		subst = {}
		for field in e.fields:
			subst[field.symbol] = field.value

		command = util.parse_subst(command, subst)

		return command


	def __get_popup_offset(self, popup):
		"Returns a tuple of x and y offset coords for popups"

		screen	= self.applet.get_screen()
		a	= self.applet.get_allocation()
		rw, rh	= popup.size_request()

		x, y	= self.applet.window.get_origin()
		x	+= a.x
		y	+= a.y


		# TODO use constants ORIENT_UP etc here, if available
		if self.applet.get_orient() in ( 0, 1 ):
			x = min(x, screen.get_width() - rw)

			if (y > screen.get_height() / 2):
				y -= rh

			else:
				y += a.height

		else:
			y = min(y, screen.get_height() - rh)

			if (x > screen.get_width() / 2):
				x -= rw

			else:
				x += a.width

		return x, y


	def __launcher_valid(self, e):
		"Checks if a launcher is valid"

		try:
			command = self.__get_launcher(e)

			return command != None

		except ( util.SubstFormatError ):
			return True

		except ( util.SubstValueError, config.ConfigError ):
			return False


	def __require_file(self):
		"Checks if a datafile is loaded, or alerts the user"

		if self.datafile.get_file() != None:
			return True

		if self.config.get("file") != "":
			return self.file_open(self.config.get("file"))

		d = dialog.Info(
			None, _('File not selected'),
			_('You must select a Revelation data file to use - this can be done in the applet preferences.'),
			( ( gtk.STOCK_PREFERENCES, gtk.RESPONSE_ACCEPT ), ( gtk.STOCK_OK, gtk.RESPONSE_OK ) )
		)


		if d.run() == gtk.RESPONSE_ACCEPT:
			self.prefs()

		return False


	##### PUBLIC METHODS #####

	def about(self):
		"Displays an about dialog"

		dialog.run_unique(About, self.applet)


	def entry_copychain(self, e, launcher = ""):
		"Copies all passwords from an entry as a chain"

		if e == None:
			return

		secrets = [ field.value for field in e.fields if field.datatype == entry.DATATYPE_PASSWORD and field.value != "" ]

		if self.config.get("chain_username") == True and len(secrets) > 0:
			if e.has_field(entry.UsernameField) and e[entry.UsernameField] != "":
				if "%" + entry.UsernameField.symbol not in launcher:
					secrets.insert(0, e[entry.UsernameField])

		self.clipboard.set(secrets)


	def entry_goto(self, e):
		"Goes to an entry"

		try:
			command = self.__get_launcher(e)

			if command == None:
				return

			self.entry_copychain(e)

			util.execute_child(command)

		except ( util.SubstFormatError, config.ConfigError ):
			dialog.Error(None, _('Invalid goto command format'), _('The goto command for '" + e.typename + "' entries is invalid, please correct this in the preferences.')).run()

		except util.SubstValueError:
			self.entry_show(e)


	def entry_menu(self, time = None):
		"Displays the entry menu"

		self.__close_popups()

		if self.__require_file() == False:
			return

		if self.entrymenu == None:
			return

		x, y = self.__get_popup_offset(self.entrymenu)

		self.entrymenu.show_all()
		self.entrymenu.popup(None, None, lambda d: (x, y, False), 1, time)


	def entry_search(self, term, focusafter = False):
		"Searches for an entry"

		self.__close_popups()

		if term.strip() == "":
			return

		if self.__require_file() == False:
			return


		matches = [ self.entrystore.get_entry(iter) for iter in self.entrysearch.find_all(term) ]

		if len(matches) == 0:
			self.__focus_entry()
			self.__flash_entry()
			self.entry.select_region(0, -1)

		elif len(matches) == 1:
			self.entry_show(matches[0], True)

		else:
			self.popup_entrylist = EntryListPopup(matches)
			self.popup_entrylist.connect("entry-chosen", lambda w,e: self.entry_show(e, focusafter))

			if focusafter == True:
				self.popup_entrylist.connect("closed", lambda w: self.__focus_entry())

			self.popup_entrylist.realize()
			x, y = self.__get_popup_offset(self.popup_entrylist)
			self.popup_entrylist.show(x, y)


	def entry_show(self, e, focusafter = False):
		"Shows an entry"

		self.__close_popups()

		self.popup_entryview = EntryViewPopup(e, self.config, self.clipboard)

		if focusafter == True:
			self.popup_entryview.connect("closed", lambda w: self.__focus_entry())

		def cb_goto(widget):
			if self.__launcher_valid(e):
				self.entry_goto(e)

			self.popup_entryview.close()

		self.popup_entryview.button_goto.connect("clicked", cb_goto)
		self.popup_entryview.button_goto.set_sensitive(self.__launcher_valid(e))

		self.popup_entryview.realize()
		x, y = self.__get_popup_offset(self.popup_entryview)
		self.popup_entryview.show(x, y)


	def file_close(self):
		"Closes the current data file"

		self.__close_popups()
		self.locktimer.stop()

		self.datafile.close()
		self.entrystore.clear()
		self.entrymenu = None


	def file_open(self, file, password = None):
		"Opens a data file"

		try:
			return self.__file_load(file, password)

		except dialog.CancelError:
			pass

		except datahandler.FormatError:
			dialog.Error(None, _('Invalid file format'), _('The file \'%s\' contains invalid data.') % file).run()

		except ( datahandler.DataError, entry.EntryTypeError, entry.EntryFieldError ):
			dialog.Error(None, _('Unknown data'), _('The file \'%s\' contains unknown data. It may have been created by a more recent version of Revelation.') % file).run()

		except datahandler.PasswordError:
			dialog.Error(None, _('Incorrect password'), _('You entered an incorrect password for the file \'%s\', please try again.') % file).run()
			self.file_open(file, None)

		except datahandler.VersionError:
			dialog.Error(None, _('Unknown data version'), _('The file \'%s\' has a future version number, please upgrade Revelation to open it.') % file).run()

		except IOError:
			dialog.Error(None, _('Unable to open file'), _('The file \'%s\' could not be opened. Make sure that the file exists, and that you have permissions to open it.') % file).run()

		return False


	def file_reload(self):
		"Reloads the current data file"

		if self.datafile.get_file() == None:
			return

		self.file_open(self.datafile.get_file(), self.datafile.get_password())


	def prefs(self):
		"Displays the preference dialog"

		dialog.run_unique(Preferences, None, self.config)



class About(dialog.About):
	"About dialog"

	def __init__(self, parent):
		dialog.About.__init__(self, parent)

		self.set_name(_('Revelation Account Search'))
		self.set_comments(_('"%s"\n\nAn applet for searching and browsing a Revelation account database') % config.RELNAME)



class EntryListPopup(dialog.Popup):
	"A popup for displaying a list of entries"

	def __init__(self, entries):
		dialog.Popup.__init__(self)
		self.set_default_size(225, 200)

		self.entrystore = data.EntryStore()
		self.entrystore.set_sort_column_id(0, gtk.SORT_ASCENDING)

		for e in entries:
			self.entrystore.add_entry(e)

		self.treeview = ui.EntryTree(self.entrystore)
		self.treeview.set_cursor((0,))
		self.treeview.connect("row-activated", self.__cb_row_activated)

		self.scrolledwindow = ui.ScrolledWindow(self.treeview)
		self.scrolledwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
		self.add(self.scrolledwindow)


	def __cb_row_activated(self, widget, path, data = None):
		"Callback for tree row activation"

		iter = self.entrystore.get_iter(path)
		e = self.entrystore.get_entry(iter)

		self.emit("entry-chosen", e)
		self.close()


gobject.signal_new("entry-chosen", EntryListPopup, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, ( gobject.TYPE_PYOBJECT, ))



class EntryViewPopup(dialog.Popup):
	"A popup for displaying an entry"

	def __init__(self, e, cfg = None, clipboard = None):
		dialog.Popup.__init__(self)
		self.set_title(e.name)

		self.entryview = ui.EntryView(cfg, clipboard)
		self.entryview.set_border_width(0)
		self.entryview.display_entry(e)

		self.button_close = ui.Button(gtk.STOCK_CLOSE, lambda w: self.close())
		self.button_goto = ui.Button(ui.STOCK_GOTO)
		self.buttonbox = ui.HButtonBox(self.button_goto, self.button_close)

		self.vbox = ui.VBox(self.entryview, self.buttonbox)
		self.vbox.set_border_width(12)
		self.vbox.set_spacing(15)

		self.add(self.vbox)

		self.connect("show", lambda w: self.button_close.grab_focus())



class Preferences(dialog.Utility):
	"A preference dialog"

	def __init__(self, parent, cfg):
		dialog.Utility.__init__(self, parent, "Preferences")
		self.config = cfg
		self.set_modal(False)

		self.notebook = ui.Notebook()
		self.vbox.pack_start(self.notebook)

		self.page_general = self.notebook.create_page(_('General'))
		self.__init_section_file(self.page_general)
		self.__init_section_menuaction(self.page_general)
		self.__init_section_misc(self.page_general)

		self.page_goto = self.notebook.create_page(_('Goto Commands'))
		self.__init_section_gotocmd(self.page_goto)

		self.connect("response", lambda w,d: self.destroy())


	def __init_section_file(self, page):
		"Sets up a file section in a page"

		self.section_file = page.add_section(_('File Handling'))

		# entry for file
		self.button_file = ui.FileButton(_('Select File to Use'))
		ui.config_bind(self.config, "file", self.button_file)

		eventbox = ui.EventBox(self.button_file)
		self.tooltips.set_tip(eventbox, _('The data file to search for accounts in'))
		self.section_file.append_widget(_('File to use'), eventbox)

		# check-button for autolock
		self.check_autolock = ui.CheckButton(_('Lock file when inactive for'))
		ui.config_bind(self.config, "autolock", self.check_autolock)
		self.check_autolock.connect("toggled", lambda w: self.spin_autolock_timeout.set_sensitive(w.get_active()))
		self.tooltips.set_tip(self.check_autolock, _('Automatically lock the file after a period of inactivity'))

		# spin-entry for autolock-timeout
		self.spin_autolock_timeout = ui.SpinEntry()
		self.spin_autolock_timeout.set_range(1, 120)
		self.spin_autolock_timeout.set_sensitive(self.check_autolock.get_active())
		ui.config_bind(self.config, "autolock_timeout", self.spin_autolock_timeout)
		self.tooltips.set_tip(self.spin_autolock_timeout, _('The period of inactivity before locking the file, in minutes'))

		# container for autolock-widgets
		hbox = ui.HBox()
		hbox.set_spacing(3)
		hbox.pack_start(self.check_autolock)
		hbox.pack_start(self.spin_autolock_timeout)
		hbox.pack_start(ui.Label(_('minutes')))
		self.section_file.append_widget(None, hbox)


	def __init_section_gotocmd(self, page):
		"Sets up the goto command section"

		self.section_goto = page.add_section(_('Goto Commands'))

		for entrytype in entry.ENTRYLIST:
			if entrytype == entry.FolderEntry:
				continue

			e = entrytype()

			widget = ui.Entry()
			ui.config_bind(self.config, "/apps/revelation/launcher/%s" % e.id, widget)

			tooltip = _('Goto command for %s accounts. The following expansion variables can be used:\n\n') % e.typename

			for field in e.fields:
				tooltip += "%%%s: %s\n" % ( field.symbol, field.name )

			tooltip += "\n"
			tooltip += _('%%: a % sign') + "\n"
			tooltip += _('%?x: optional expansion variable') + "\n"
			tooltip += _('%(...%): optional substring expansion')

			self.tooltips.set_tip(widget, tooltip)
			self.section_goto.append_widget(e.typename, widget)


	def __init_section_menuaction(self, page):
		"Sets up a menuaction section in a page"

		self.section_menuaction = page.add_section(_('Menu Action'))

		# radio-button for show
		self.radio_show = ui.RadioButton(None, _('Display account info'))
		ui.config_bind(self.config, "menuaction", self.radio_show, "show")

		self.tooltips.set_tip(self.radio_show, _('Display the account information'))
		self.section_menuaction.append_widget(None, self.radio_show)

		# radio-button for goto
		self.radio_goto = ui.RadioButton(self.radio_show, _('Go to account, if possible'))
		ui.config_bind(self.config, "menuaction", self.radio_goto, "goto")

		self.tooltips.set_tip(self.radio_goto, _('Open the account in an external application if possible, otherwise display it'))
		self.section_menuaction.append_widget(None, self.radio_goto)

		# radio-button for copy username/password
		self.radio_copy = ui.RadioButton(self.radio_show, _('Copy password to clipboard'))
		ui.config_bind(self.config, "menuaction", self.radio_copy, "copy")

		self.tooltips.set_tip(self.radio_copy, _('Copy the account password to the clipboard'))
		self.section_menuaction.append_widget(None, self.radio_copy)


	def __init_section_misc(self, page):
		"Sets up the misc section"

		self.section_misc = page.add_section(_('Miscellaneous'))

		# show searchentry checkbutton
		self.check_show_searchentry = ui.CheckButton(_('Show search entry'))
		ui.config_bind(self.config, "show_searchentry", self.check_show_searchentry)

		self.tooltips.set_tip(self.check_show_searchentry, _('Display an entry box in the applet for searching'))
		self.section_misc.append_widget(None, self.check_show_searchentry)

		# show passwords checkbutton
		self.check_show_passwords = ui.CheckButton(_('Show passwords and other secrets'))
		ui.config_bind(self.config, "show_passwords", self.check_show_passwords)

		self.tooltips.set_tip(self.check_show_passwords, _('Display passwords and other secrets, such as PIN codes (otherwise, hide with ******)'))
		self.section_misc.append_widget(None, self.check_show_passwords)

		# check-button for username
		self.check_chain_username = ui.CheckButton(_('Also copy username when copying password'))
		ui.config_bind(self.config, "chain_username", self.check_chain_username)

		self.tooltips.set_tip(self.check_chain_username, _('When the password is copied to clipboard, put the username before the password as a clipboard "chain"'))
		self.section_misc.append_widget(None, self.check_chain_username)


	def run(self):
		"Runs the dialog"

		self.show_all()



def factory(applet, iid):
	"Applet factory function"

	RevelationApplet(applet, iid)

	return True



if __name__ == "__main__":
	gnome.init(config.APPNAME, config.VERSION)

	gnomeapplet.bonobo_factory(
		"OAFIID:GNOME_RevelationApplet_Factory",
		gnomeapplet.Applet.__gtype__,
		config.APPNAME, config.VERSION, factory
	)

