#!/usr/bin/python3
#
#  Openbox Menu Editor 1.0 beta
# 
#  Copyright 2005 Manuel Colmenero 
#
#    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 obxml, random, time, os, sys
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

class App:
	def reconfigureOpenbox(self):
		lines = os.popen("ps aux").read().splitlines()
		ob = os.popen("which openbox").read().strip()
		for line in lines:
			if ob in " ".join(line.split()[10:]):
				os.kill(int(line.split()[1]), 12)
				break
	
	# Recursively creates the treeview model
	def createTree(self, padre, menuid):
		for it in self.menu.getMenu(menuid):
			if it["type"] == "item":
				self.treemodel.append(padre, (it["label"], 'item',it["action"],it["execute"],it["parent"],""))
			elif it["type"] == "separator":
				self.treemodel.append(padre, ("", "separator", "", "", it["parent"], ""))
			elif it["type"] == "menu":
				hijo=self.treemodel.append(padre, (it["label"], 'menu',it["action"],it["execute"], it["parent"], it["id"]))
				if self.menu.getMenu(it["id"]) and it["action"] == "":
					self.createTree(hijo, it["id"])
	
	def deleteTree(self):
		# treemodel.iter(label, type, [action], [execute], parent, [menu-id])
		self.treemodel=Gtk.TreeStore(str, str, str, str, str, str)
		self.treeview.set_model(self.treemodel)
		
	# Sets the state of "menu modified"
	# Refreshes the window's title
	def _sth_changed(self, op):
		self.sth_changed = op
		if op: s = "(*)"
		else: s = ""
		self.glade.get_object("window1").set_title("Obmenu: %s %s" % (self.menu_path, s))
	
	# Auxiliary function for model.foreach()
	def _change_id(self, model, path, it, old_id, new_id ):
		mid = model.get_value(it,5)
		if mid == old_id:
			model.set(it, 5, new_id)
		parent = model.get_value(it,4)
		if parent == old_id:
			model.set(it, 4, new_id)
	
	def clear_fields(self):
		self.auto_change = True
		self.label_entry
		for each in (self.label_entry, self.id_entry, self.execute_entry):
			each.set_sensitive(False)
			each.set_text("")
		self.action_entry.set_sensitive(False)
		self.auto_change = False
	
	def confirm(self, message):
		dlg = Gtk.MessageDialog(None,0,Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO,message)
		res = dlg.run()
		dlg.destroy()
		if res == -8: return True
		else: return False
	
	def ask_for_path(self, title, op):
		if op == 0:
			action = Gtk.FileChooserAction.OPEN
		else:
			action = Gtk.FileChooserAction.SAVE
		dlg = Gtk.FileChooserDialog(title,None,action,(Gtk.STOCK_OK, 1, Gtk.STOCK_CANCEL, 3))
		dlg.set_default_response(1)
		res = dlg.run()
		flnm = dlg.get_filename()
		dlg.destroy()
		if res == 1: return flnm
	
	#  New file clicked
	def new_clicked(self, bt):
		if self.sth_changed:
			res = self.confirm("Changes in %s will be lost. Continue?" % (self.menu_path))
			if not res: return
		self.deleteTree()
		self.menu.newMenu()
		self.unsaved_menu = True
		self.menu_path = "Untitled"
		self._sth_changed(True)
		self.clear_fields()
		
	def open_clicked(self, bt):
		if self.sth_changed:
			res = self.confirm("Changes in %s will be lost. Continue?" % (self.menu_path))
			if not res: return
		path = self.ask_for_path("Open", 0)
		if not path: return
		self.menu_path = path
		self.unsaved_menu = False
		self._sth_changed(False)
		
		# Load in memory the real xml menu
		self.menu = obxml.ObMenu()
		self.menu.loadMenu(self.menu_path)
		
		self.deleteTree() #Not really deleting this time, just initialiting
		self.createTree(None, None)
		self.clear_fields()
		
	# save the menu
	def save_clicked(self, bt):
		if self.unsaved_menu:
			path = self.ask_for_path("Save as...", 1)
			if not path: return
			self.menu_path = path
			self.unsaved_menu = False
		self.menu.saveMenu(self.menu_path)
		self.reconfigureOpenbox()
		self._sth_changed(False)
	
	# save as...
	def save_as_clicked(self, bt):
		path = self.ask_for_path("Save as...", 1)
		if not path: return
		self.menu_path = path
		self.unsaved_menu = False
		self._sth_changed(False)
		self.menu.saveMenu(self.menu_path)
		self.reconfigureOpenbox()
	
	# quit signal
	def exit (self, bt, arg2):
		if self.sth_changed:
			res = self.confirm("There are unsaved changes. Exit anyway?")
			if not res: return True
		Gtk.main_quit()
	
	# file->quit menu signal
	def mnu_quit (self, bt):
		self.exit(None,None)
	
	# id_entry changed signal
	def id_changed(self, pa):
		if self.auto_change: return
		self._sth_changed(True)
		
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		if model.get_value(ite, 1).lower() != "menu": return
		
		old_id = model.get_value(ite, 5)
		new_id = self.id_entry.get_text()
		
		if model.get_value(ite, 2).lower() == "link":
			self.auto_change = True
			if self.menu.getMenuLabel(new_id):
				model.set(ite, 0, self.menu.getMenuLabel(new_id), 5, new_id)
				self.label_entry.set_text(self.menu.getMenuLabel(new_id))
			else:
				model.set(ite, 0, new_id, 5, new_id)
				self.label_entry.set_text(new_id)
			self.auto_change = False			
			self.menu.setRefId( model.get_value(ite, 4), old_id, new_id)
		else:
			if old_id != new_id and not self.menu.isMenu(new_id):				
				self.menu.replaceId(old_id, new_id)
				model.foreach(self._change_id,  old_id, new_id )
	
	# label_entry changed signal
	def label_changed(self, pa):
		if self.auto_change: return
		self._sth_changed(True)
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = model.get(ite,0,1,2,3,4,5)
		lb = self.label_entry.get_text()
		p = model.get_path(ite)
		n = p[len(p)-1]
		if tipe == "item":
			self.menu.setItemProps(menu, n, lb, aaa, eee)
		elif tipe == "menu":
			if aaa == "":
				self.menu.setMenuLabel(mid, lb)
			else:
				self.menu.setRefLabel(menu, mid, lb)
		model.set(ite, 0, lb)
	
	# action_combo_box changed signal
	def action_changed(self, pa):
		if self.auto_change: return
		self._sth_changed(True)
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = model.get(ite,0,1,2,3,4,5)	
		p = model.get_path(ite)
		n = p[len(p)-1]
		if tipe == "item":
			ac = self.action_entry.get_active()
			self.execute_entry.set_sensitive(False)
			self.execute_srch.set_sensitive(False)
			if ac == 0:
				action = "Execute"
				self.execute_entry.set_sensitive(True)
				self.execute_srch.set_sensitive(True)
			elif ac == 1:
				action = "Reconfigure"
				eee = ""
			elif ac == 2:
				action = "Restart"
				eee = ""				
			elif ac == 3:
				action = "Exit"
				eee = ""
			self.menu.setItemProps(menu, n, label, action, eee)
			model.set(ite, 2, action, 3, eee)	
	
	# execute_entry changed signal
	def execute_changed(self, pa):
		if self.auto_change: return
		self._sth_changed(True)
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = model.get(ite,0,1,2,3,4,5)	
		p = model.get_path(ite)
		n = p[len(p)-1]
		ex = self.execute_entry.get_text()
		if tipe == "item":
			p = model.get_path(ite)
			n = p[len(p)-1]
			self.menu.setItemProps(menu, n, label, aaa, ex)
		elif tipe == "menu":
			self.menu.setMenuExecute(menu, mid, ex)	
		model.set(ite, 3, ex)
	
	# buton [...] clicked signal
	def search_clicked(self, arg):
		path = self.ask_for_path("Select file", 0)
		if path:
			self.execute_entry.set_text(path)
	
	# treeview key pressed signal
	def tree_key_pressed(self, treeview, ev):
		# if Delete key has been pressed
		if ev.keyval == 65535:
			self.remove(None)
	
	# treeview clicked signal
	def on_treeview1(self, param):
		(model, ite) = param.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		
		self.auto_change = True
		
		self.label_entry.set_text(label)
		self.execute_entry.set_text(exe)
		
		self.label_entry.set_sensitive(tipe == "item" or (tipe == "menu" and action != "Link"))
		self.action_entry.set_sensitive(tipe == "item")
		self.id_entry.set_sensitive(tipe == "menu")
		self.id_entry.set_text(mid)
		
		if tipe == "item":
			self.execute_entry.set_sensitive(False)
			self.execute_srch.set_sensitive(False)
			if action.lower() == "execute":
				self.action_entry.set_active(0)
				self.execute_entry.set_sensitive(True)
				self.execute_srch.set_sensitive(True)
			elif action.lower() == "reconfigure":
				self.action_entry.set_active(1)
			elif action.lower() == "restart":
				self.action_entry.set_active(2)
			elif action.lower() == "exit":
				self.action_entry.set_active(3)
			else:
				self.action_entry.set_sensitive(False)
		elif tipe == "menu":
			self.execute_entry.set_sensitive(action == "Pipemenu")
			self.execute_srch.set_sensitive(action == "Pipemenu")
		else:
			self.execute_entry.set_sensitive(False)
			self.execute_srch.set_sensitive(False)
		self.auto_change = False
	
	# new menu button clicked
	def menu_clicked(self, param):
		(model, ite) = self.treeview.get_selection().get_selected()
		
		if ite:
			(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
			parent = model.iter_parent(ite)
			n = model.get_path(ite)[-1]
		else:
			menu = None
			parent = model.get_iter_first()
			n = 0
		
		nmid="%s-%d%d%d" % (menu, random.randint(0,99), time.gmtime()[4], time.gmtime()[5])
		
		n_menu = self.treemodel.insert_before(parent, ite,("New Menu", "menu", "", "", menu, nmid))
		itm = self.treemodel.append(n_menu, ("New Item", "item", "Execute", "command", nmid, ""))
		self.menu.createMenu(menu, "New Menu", nmid, n)
		self.menu.createItem(nmid, "New Item", "Execute", "command")
		
		path = model.get_path(n_menu)
		self.treeview.set_cursor(path, None, False)
		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()
		
		self._sth_changed(True)
	
	# new item button clicked
	def item_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		if menu == None: return
		
		n = model.get_path(ite)[-1]
		parent = model.iter_parent(ite)
		
		self.menu.createItem(menu, "New Item", "Execute", "command", n)
		itm = self.treemodel.insert_before(parent, ite, ("New Item", 'item','Execute','command', menu, ""))
		
		path = model.get_path(itm)
		self.treeview.set_cursor(path, None, False)
		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()
		
		self._sth_changed(True)
	
	# new separator button clicked
	def separator_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		if menu == None: return
		
		n = model.get_path(ite)[-1]
		parent = model.iter_parent(ite)
		
		self.menu.createSep(menu, n)
		itm = self.treemodel.insert_before(parent, ite, ("", 'separator', '','', menu, ""))
		
		path = model.get_path(itm)
		self.treeview.set_cursor(path, None, False)
		self.treeview.grab_focus()
		
		self._sth_changed(True)

	# new link button clicked
	def link_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		if menu == None: return
		
		n = model.get_path(ite)[-1]
		parent = model.iter_parent(ite)
		nmid="link-%d%d%d" % (random.randint(0,99), time.gmtime()[4], time.gmtime()[5])
		
		self.menu.createLink(menu, nmid, n)
		itm = self.treemodel.insert_before(parent, ite, (nmid, 'menu', 'Link','', menu, nmid))
		
		path = model.get_path(itm)
		self.treeview.set_cursor(path, None, False)
		self.id_entry.select_region(0, -1)
		self.id_entry.grab_focus()
		
		self._sth_changed(True)

	# new pipe button clicked
	def pipe_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		if menu == None: return
		
		n = model.get_path(ite)[-1]
		parent = model.iter_parent(ite)
		nmid="pipe-%d%d%d" % (random.randint(0,99), time.gmtime()[4], time.gmtime()[5])
		
		self.menu.createPipe(menu, nmid, "Newpipe", "command", n)
		itm = self.treemodel.insert_before(parent, ite, ("Newpipe", 'menu', 'Pipemenu','command', menu, nmid))
		
		path = model.get_path(itm)
		self.treeview.set_cursor(path, None, False)
		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()
		
		self._sth_changed(True)

	# up button clicked
	def up_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		if menu == "None": menu = None
		p = model.get_path(ite)
		n = p[len(p)-1]
		if n > 0:
			self._sth_changed(True)
			self.menu.interchange(menu, n, n-1)
			l = list(p)
			l[len(l)-1] -= 1
			p = tuple(l)
			upper = model.get_iter(p)
			model.move_before(ite,upper)
	
	# down button clicked
	def down_clicked(self,param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		p = model.get_path(ite)
		n = p[len(p)-1]
		if n < self.menu._get_menu_len(menu) -1:
			self._sth_changed(True)
			self.menu.interchange(menu, n, n+1)
			l = list(p)
			l[len(l)-1] += 1
			p = tuple(l)
			upper = model.get_iter(p)
			model.move_after(ite,upper)
	
	# remove button clicked
	def remove_clicked(self, param):
		(model, ite) = self.treeview.get_selection().get_selected()
		if not ite: return
		self._sth_changed(True)
		(label, tipe, action, exe, menu, mid) = model.get(ite,0,1,2,3,4,5)
		path = model.get_path(ite)
		n = path[len(path)-1]
		if tipe == "menu":
			if model.iter_has_child(ite):
				self.menu.removeMenu(mid)
			else:
				self.menu.removeItem(menu, n)
		else:
			self.menu.removeItem(menu, n)
		model.remove(ite)
		self.clear_fields()
	
	def mnu_remove(self, param):
		if self.treeview.is_focus():
			self.remove(None)

	def show_about(self, args):
		#gtk.glade.XML("/usr/local/share/obmenu/obmenu.glade", "aboutdialog1")
		self.glade.add_from_file(os.path.dirname(self.gladefile)+"obmenu_about.glade")
		self.glade.connect_signals (self)
		self.about = self.glade.get_object("aboutdialog1")

	def about_hide(self, arg1, arg2):
		self.about.destroy()

	# application init
	def init(self):
		if len(sys.argv) == 2:
			# must be a path to a menu
			self.menu_path = sys.argv[1]
		elif len(sys.argv) == 1:
			self.menu_path = os.getenv("HOME") + "/.config/openbox/menu.xml"
		else:
			print("Error: Wrong number of arguments")
			print("Usage: obmenu /path/to/menu.xml")
			print("\tOr just obmenu, to edit the default file")
			return
		
		self.unsaved_menu = False
		
		# Load in memory the real xml menu
		self.menu = obxml.ObMenu()
		if os.path.isfile(self.menu_path):
			self.menu.loadMenu(self.menu_path)
		else:
			print("Error: \"%s\" not found" % self.menu_path)
			self.menu.newMenu()
		
		# Look for my glade file!
		if os.path.isfile("obmenu.glade"):
			# It's here.. looks like a pre-installation execution
			self.gladefile = "obmenu.glade"
		elif os.path.isfile("/usr/local/share/obmenu/obmenu.glade"):
			self.gladefile = "/usr/local/share/obmenu/obmenu.glade"
		elif os.path.isfile("/usr/share/obmenu/obmenu.glade"):
			self.gladefile = "/usr/share/obmenu/obmenu.glade"
		else:
			print("ERROR: obmenu.glade not found!")
			print("       check that everything was installed all right")
			sys.exit(1)
		
		# Set the basics for GTK
		self.glade = Gtk.Builder()
		self.glade.add_from_file(self.gladefile)
		self.arbol = self.glade.get_object("window1")
		
		self.treeview=self.glade.get_object("treeview1")
		self.label_entry = self.glade.get_object("entry1")
		self.action_entry = self.glade.get_object("combobox1")
		self.execute_entry = self.glade.get_object("entry2")
		self.execute_srch = self.glade.get_object("button1")
		self.id_entry = self.glade.get_object("entry3")
		
		# treemodel.iter(label, type, [action], [execute], parent, [menu-id])
		self.treemodel=Gtk.TreeStore(str, str, str, str, str, str)
		self.treeview.set_model(self.treemodel)
		
		# Signals
		self.glade.connect_signals (self)
		#self.about.connect("delete-event", self.about_hide)
		
		# Set the columns
		self.treeview.set_headers_visible(True)
		
		renderer=Gtk.CellRendererText()
		column=Gtk.TreeViewColumn("Label",renderer, text=0)
		column.set_resizable(True)
		self.treeview.append_column(column)
		
		renderer=Gtk.CellRendererText()
		column=Gtk.TreeViewColumn("Type",renderer,text=1)
		column.set_resizable(True)
		self.treeview.append_column(column)
		
		renderer=Gtk.CellRendererText()
		column=Gtk.TreeViewColumn("Action",renderer,text=2)
		column.set_resizable(True)
		self.treeview.append_column(column)
		
		renderer=Gtk.CellRendererText()
		column=Gtk.TreeViewColumn("Execute",renderer,text=3)
		column.set_resizable(True)
		self.treeview.append_column(column)
		
		# Some states of the app
		self.auto_change = False
		self._sth_changed(False)
		
		# Create the menu tree
		self.createTree(None, None)
		
		# Let's roll!
		Gtk.main()

if __name__ == "__main__":
	app = App()
	app.init()
