#!/usr/local/bin/python3.14
#
#  Openbox Menu Editor
#
#  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 gi, obxml, random, time, os, sys

try:
	import locale
	from locale import textdomain, bindtextdomain, dgettext, gettext as _
	locale.setlocale(locale.LC_ALL, "")
except:
	import gettext
	from gettext import textdomain, bindtextdomain, dgettext, gettext as _

if gi.version_info < (3, 40, 0):
	print(_("PyGObject 3.40.0 or higher is required"), file=sys.stderr)
	exit(1)

gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gio, GLib, GObject

class App(Gtk.Application):
	# Recursively creates the treeview model
	def createTree(self, model, menuid):
		if model is None:
			model = self.treemodel.get_model()
		for it in self.menu.getMenu(menuid):
			if it["type"] == "item":
				hijo = GObject.Object()
				hijo.data = [it["label"], "item", it["action"], it["execute"], it["parent"], ""]
				model.append(hijo)
			elif it["type"] == "separator":
				hijo = GObject.Object()
				hijo.data = ["", "separator", "", "", it["parent"], ""]
				model.append(hijo)
			elif it["type"] == "menu":
				hijo = GObject.Object()
				hijo.data = [it["label"], "menu", it["action"], it["execute"], it["parent"], it["id"]]
				model.append(hijo)
			hijo.model = model

	def create_list_model(self, item, user_data):
		(label, tipe, action, execute, menu, mid) = item.data
		if tipe == "menu" and self.menu.getMenu(mid) and action == "":
			model = Gio.ListStore()
			self.createTree(model, mid)
			return model
		return None

	def initTree(self):
		# treemodel.iter(label, type, [action], [execute], parent, [menu-id])
		root = Gio.ListStore()
		self.treemodel = Gtk.TreeListModel.new(root, False, False, self.create_list_model, None)
		selection_model = self.builder.get_object("treeview_model")
		selection_model.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 = ""
		if self.menu_path:
			self.appwindow.set_title("Obmenu: %s %s" % (self.menu_path, s))
		else:
			self.appwindow.set_title("Obmenu")

	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_combo.set_sensitive(False)
		self.auto_change = False

	def clear_view(self):
		self.initTree()
		self.menu.newMenu()
		self.unsaved_menu = True
		self.menu_path = _("Untitled")
		self._sth_changed(True)
		self.clear_fields()

	def on_confirm(self, dialog, result, data):
		button = dialog.choose_finish(result)
		if button != dialog.get_cancel_button():
			if dialog.confirm_action == "exit":
				app.quit()
			elif dialog.confirm_action == "new":
				self.clear_view()
			elif dialog.confirm_action == "open":
				self.ask_for_path(_("Open"), Gtk.FileChooserAction.OPEN)
				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.initTree()
				self.createTree(None, None)
				self.clear_fields()

	def confirm(self, message, action):
		dlg = Gtk.AlertDialog()
		dlg.confirm_action = action
		dlg.set_message(message)
		dlg.set_buttons([dgettext(self.gtk_domain, '_No'), dgettext(self.gtk_domain, '_Yes')])
		dlg.set_cancel_button(0)
		dlg.choose(self.appwindow, None, self.on_confirm, None)

	def on_file_chosen(self, dialog, action):
		if self.menu_path is None:
			return

		if action == Gtk.FileChooserAction.OPEN:
			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.initTree()
			self.createTree(None, None)
			self.clear_fields()
		else:
			self.unsaved_menu = False
			self.menu.saveMenu(self.menu_path)
			self._sth_changed(False)

	def on_search_clicked(self, dialog, res):
		try:
			file = dialog.open_finish(res)
			self.execute_entry.set_text(file.peek_path())
		except:
			pass

	def get_selected_filename(self, dlg, res, data):
		(action, cb) = data
		if not cb is None:
			cb(dlg, res)
		elif action == Gtk.FileChooserAction.OPEN:
			try:
				self.menu_path = dlg.open_finish(res).get_path()
			except:
				self.menu_path = None
		elif action == Gtk.FileChooserAction.SAVE:
			try:
				self.menu_path = dlg.save_finish(res).get_path()
			except:
				self.menu_path = None
		if cb is None:
			self.on_file_chosen(dlg, action)

	def ask_for_path(self, title, action, cb=None):
			dlg = Gtk.FileDialog()
			if action == Gtk.FileChooserAction.OPEN:
				dlg.open(self.appwindow, None, self.get_selected_filename, (action, cb))
			elif action == Gtk.FileChooserAction.SAVE:
				dlg.save(self.appwindow, None, self.get_selected_filename, (action, cb))
			dlg.set_title(title)

	#  New file clicked
	def new(self, params, data):
		if self.sth_changed:
			self.confirm(_("Changes in %s will be lost. Continue?") % (self.menu_path), "new")
		else:
			self.clear_view()

	def open(self, params, data=None):
		if self.sth_changed:
			self.confirm(_("Changes in %s will be lost. Continue?") % (self.menu_path), "open")
		else:
			self.ask_for_path(_("Open"), Gtk.FileChooserAction.OPEN)

	# save the menu
	# It takes three parameters from the menu, but only two from the
	# toolbar, so make the third argument default to None
	def save(self, params, data=None):
		if self.unsaved_menu:
			self.ask_for_path(_("Save as..."), Gtk.FileChooserAction.SAVE)
		else:
			self.menu.saveMenu(self.menu_path)
		self._sth_changed(False)

	# save as
	def save_as(self, params, data):
		self.ask_for_path(_("Save as..."), Gtk.FileChooserAction.SAVE)

	def reconfigure_openbox(self, param, data):
		import signal
		lines = os.popen("pgrep -x openbox").read().splitlines()
		if lines:
			os.kill(int(lines[0]), signal.SIGUSR2)

	# quit signal
	def exit (self, bt):
		if self.settings:
			self.settings.set_value("last-opened-menu", GLib.Variant.new_string(self.menu_path))
		if self.sth_changed:
			self.confirm(_("There are unsaved changes. Exit anyway?"), "exit")
		else:
			self.quit()

	# File->Quit menu signal
	def mnu_quit (self, params, data):
		self.exit(None)

	# id_entry changed signal
	def change_id(self, pa):
		if self.auto_change: return
		self._sth_changed(True)

		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if tipe.lower() != "menu": return
		store_model = ite.model

		old_id = mid
		new_id = self.id_entry.get_text()
		ite.data = [label, tipe, action, exe, menu, new_id]

		if action.lower() == "link":
			self.auto_change = True
			if self.menu.getMenuLabel(new_id):
				self.label_entry.set_text(self.menu.getMenuLabel(new_id))
			else:
				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)

	# label_entry changed signal
	def change_label(self, pa):
		if self.auto_change: return
		self._sth_changed(True)

		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		ite = row.get_item()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = ite.data
		lb = self.label_entry.get_text()
		store_model = ite.model

		(found, found_pos) = store_model.find(ite)
		store_model.remove(found_pos)
		ite.data = [lb, tipe, aaa, eee, menu, mid]
		store_model.insert(found_pos, ite)
		model.set_selected(position)

		n = position
		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)

	# action_combo_box changed signal
	def change_action(self, pa, data=None):
		if self.auto_change: return
		self._sth_changed(True)

		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		ite = row.get_item()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = ite.data
		store_model = ite.model

		n = position - 1
		if tipe == "item":
			ac = self.action_combo.get_selected()
			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)

			(found, found_pos) = store_model.find(ite)
			store_model.remove(found_pos)
			ite.data = [label, tipe, action, eee, menu, mid]
			store_model.insert(found_pos, ite)
			model.set_selected(position)

	# execute_entry changed signal
	def change_execute(self, pa):
		if self.auto_change: return
		self._sth_changed(True)

		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		ite = row.get_item()
		if not ite: return
		(label, tipe, aaa, eee, menu, mid) = ite.data
		store_model = ite.model

		n = position - 1
		ex = self.execute_entry.get_text()
		if tipe == "item":
			self.menu.setItemProps(menu, n, label, aaa, ex)
		elif tipe == "menu":
			self.menu.setMenuExecute(menu, mid, ex)

		(found, found_pos) = store_model.find(ite)
		store_model.remove(found_pos)
		ite.data = [label, tipe, aaa, ex, menu, mid]
		store_model.insert(found_pos, ite)
		model.set_selected(position)

	# button [...] clicked signal
	def search_clicked(self, arg):
		dlg = self.ask_for_path(_("Select File"), Gtk.FileChooserAction.OPEN, self.on_search_clicked)

	# treeview clicked signal
	def treeview_changed(self, model, position, n_items, user_data=None):
		# The position argument can give the wrong position
		position = model.get_selected()
		row = model.get_item(position)
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data

		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_combo.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_combo.set_selected(0)
				self.execute_entry.set_sensitive(True)
				self.execute_srch.set_sensitive(True)
			elif action.lower() == "reconfigure":
				self.action_combo.set_selected(1)
			elif action.lower() == "restart":
				self.action_combo.set_selected(2)
			elif action.lower() == "exit":
				self.action_combo.set_selected(3)
			else:
				self.action_combo.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 new_menu(self, param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if row:
			ite = row.get_item()
			(label, tipe, action, exe, menu, mid) = ite.data
			store_model = ite.model
			(found, row_position) = store_model.find(ite)
			n = position
		else:
			found = False
			menu = None
			n = 0

		nmid="%s-%d%d%d" % (menu, random.randint(0,99), time.gmtime()[4], time.gmtime()[5])

		self.menu.createMenu(menu, _("New Menu"), nmid, n)
		self.menu.createItem(nmid, _("New Item"), "Execute", _("command"))
		if not found:
			self.createTree(None, None)
			self.treeview_changed(model, 0, 0, None)
		if found:
			hijo = GObject.Object()
			hijo.data = [_("New Menu"), "menu", "", "", menu, nmid]
			hijo.model = store_model
			store_model.insert(row_position + 1 , hijo)
			model.set_selected(position + 1)

		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()

		self._sth_changed(True)

	# new item button clicked
	def new_item(self, param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if menu == None: return
		store_model = ite.model

		n = position

		self.menu.createItem(menu, _("New Item"), "Execute", _("command"), n)

		new = GObject.Object()
		new.data = [_("New Item"), "item", "Execute", _("command"), menu, ""]
		new.model = store_model
		(found, row_position) = store_model.find(ite)
		store_model.insert(row_position + 1, new)
		model.set_selected(position + 1)

		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()

		self._sth_changed(True)

	# new separator button clicked
	def new_separator(self, param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if menu == None: return
		store_model = ite.model

		n = position

		self.menu.createSep(menu, n)

		new = GObject.Object()
		new.data =  ["", "separator", "", "", "", ""]
		new.model = store_model
		(found, row_position) = store_model.find(ite)
		store_model.insert(row_position + 1, new)
		model.set_selected(position + 1)

		self.treeview.grab_focus()

		self._sth_changed(True)

	# new link button clicked
	def new_link(self,param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if menu == None: return
		store_model = ite.model

		n = position
		nmid="link-%d%d%d" % (random.randint(0,99), time.gmtime()[4], time.gmtime()[5])

		self.menu.createLink(menu, nmid, n)

		new = GObject.Object()
		new.data = [nmid, "menu", "Link", "", menu, nmid]
		new.model = store_model

		(found, row_position) = store_model.find(ite)
		store_model.insert(row_position + 1, new)

		model.set_selected(position + 1)
		self.id_entry.select_region(0, -1)
		self.id_entry.grab_focus()

		self._sth_changed(True)

	# new pipe button clicked
	def new_pipe(self,param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if menu == None: return
		store_model = ite.model

		n = position
		nmid="pipe-%d%d%d" % (random.randint(0,99), time.gmtime()[4], time.gmtime()[5])

		self.menu.createPipe(menu, nmid, _("New Pipe"), _("command"), n)
		new = GObject.Object()
		new.data = [_("New Pipe"),"menu", "Pipemenu", _("command"), menu, nmid]
		new.model = store_model

		(found, row_position) = store_model.find(ite)
		store_model.insert(row_position + 1, new)

		model.set_selected(position + 1)
		self.label_entry.select_region(0, -1)
		self.label_entry.grab_focus()

		self._sth_changed(True)

	# up button clicked
	def up(self, param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = ite.data
		if menu == "None": menu = None
		store_model = ite.model

		n = position - 1
		if n > 0:
			self._sth_changed(True)
			self.menu.interchange(menu, n, n-1)
			(found, row_position) = store_model.find(ite)
			store_model.remove(row_position)
			store_model.insert(row_position - 1, ite)
			model.set_selected(position - 1)


	# down button clicked
	def down(self, param, data=None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		(label, tipe, action, exe, menu, mid) = list(ite.data)
		store_model = ite.model

		p = position
		if p < self.menu._get_menu_len(menu) - 1:
			self._sth_changed(True)
			self.menu.interchange(menu, p-1, p)

			(found, row_position) = store_model.find(ite)
			store_model.remove(row_position)
			store_model.insert(row_position + 1, ite)
			model.set_selected(p + 1)

	# remove button clicked
	def remove(self, button, user_data = None):
		model = self.treeview.get_model()
		position = model.get_selected()
		row = model.get_item(position)
		if not row: return
		ite = row.get_item()
		if not ite: return
		self._sth_changed(True)
		(label, tipe, action, exe, menu, mid) = list(ite.data)
		store_model = ite.model

		(found, found_pos) = store_model.find(ite)
		store_model.remove(found_pos)

		if tipe == "menu":
			if store_model.get_n_items() > 0:
				self.menu.removeMenu(mid)
			else:
				self.menu.removeItem(menu, found_pos)
		else:
			self.menu.removeItem(menu, found_pos)
		self.clear_fields()

	def hide_about(self, widget):
		widget.set_visible(False)
		return True

	def show_about(self, param, data):
		aboutdialog = self.builder.get_object("aboutdialog")
		aboutdialog.set_version(self.version)
		aboutdialog.set_visible(True)

	def do_startup(self):
		Gtk.Application.do_startup(self)

		# Look for the data files!
		if os.path.isfile("obmenu.ui"):
			# They're here.. looks like a pre-installation execution
			self.datapath = os.getcwd()
		else:
			self.datapath = ""
			data_dirs = [os.path.dirname(__file__) + "/../share", GLib.get_user_data_dir()]
			data_dirs += GLib.get_system_data_dirs()
			for data_dir in data_dirs:
				if os.path.isfile(os.path.abspath("%s/obmenu/obmenu.ui" % data_dir)):
					self.datapath = os.path.abspath("%s/obmenu" % data_dir)
					break
			if not self.datapath:
				# These will be untranslated, as things weren't installed correctly
				print("ERROR: the data files were not found!", file=sys.stderr)
				print("       check that everything was installed all right", file=sys.stderr)
				sys.exit(1)

		if not (textdomaindir := os.getenv("TEXTDOMAINDIR")):
			textdomaindir = os.path.abspath(os.path.dirname(__file__) + "/../share/locale")
		textdomain("obmenu")
		bindtextdomain("obmenu", textdomaindir)
		self.gtk_domain = "gtk%d0" % Gtk.get_major_version()

		app_signals = {
			"new": self.new,
			"reconfigure": self.reconfigure_openbox,
			"quit": self.mnu_quit,
			"about": self.show_about,
			"hide_about": self.hide_about,
			"exit": self.exit }
		win_signals = {
			"open": self.open,
			"save": self.save,
			"save.as": self.save_as,
			"label_changed": self.change_label,
			"id_changed": self.change_id,
			"action_changed": self.change_action,
			"execute_changed": self.change_execute,
			"search_clicked": self.search_clicked,
			"treeview_changed": self.treeview_changed,
			"add.menu": self.new_menu,
			"add.item": self.new_item,
			"add.separator": self.new_separator,
			"add.pipe": self.new_pipe,
			"add.link": self.new_link,
			"remove": self.remove,
			"up": self.up,
			"down": self.down }

		self.builder = Gtk.Builder({**app_signals, **win_signals})
		self.builder.add_from_file(self.datapath + "/obmenu.ui")

		# Set the basics for GTK
		self.appwindow = self.builder.get_object("appwindow")
		self.treeview = self.builder.get_object("treeview")
		self.label_entry = self.builder.get_object("label_entry")
		self.action_combo = self.builder.get_object("action_combo")
		self.execute_entry = self.builder.get_object("execute_entry")
		self.execute_srch = self.builder.get_object("execute_search_button")
		self.id_entry = self.builder.get_object("id_entry")

		for signal in app_signals:
			action = Gio.SimpleAction.new(signal, None)
			action.connect("activate", app_signals[signal])
			self.add_action(action)
		for signal in win_signals:
			action = Gio.SimpleAction.new(signal, None)
			action.connect("activate", win_signals[signal])
			self.appwindow.add_action(action)

		accels = {
			"app.new": ["<Control>n"],
			"win.open": ["<Control>o"],
			"win.save": ["<Control>s"],
			"win.save.as": ["<Control><Shift>s"],
			"app.reconfigure": ["<Control>c"],
			"app.quit": ["<Control>q"],
			"win.up": ["<Control>Up"],
			"win.down": ["<Control>Down"],
			"win.add.menu": ["<Control>m"],
			"win.add.item": ["<Control>i"],
			"win.add.separator": ["<Control>r"],
			"win.add.pipe": ["<Control>p"],
			"win.add.link": ["<Control>l"],
			"win.remove": ["Delete"],
			"app.about": ["F1"]
		}
		for accel in accels:
			app.set_accels_for_action(accel, accels[accel])

	def setup_listitem_cb(self, factory, item, data):
		label = Gtk.Label(halign=Gtk.Align.START)
		if data == 0:
			expander = Gtk.TreeExpander.new()
			expander.set_child(label)
			item.set_child(expander)
		else:
			item.set_child(label)

	def bind_listitem_cb(self, factory, item, data):
		row = item.get_item()
		row.set_expanded(False)

		if data == 0:
			expander = item.get_child()
			label = expander.get_child()
			expander.set_list_row(row)
			expander.set_child(label)
		else:
			label = item.get_child()

		text = row.get_item().data[data]
		if text != "":
			label.set_text(_(text))

	# Application activation
	def do_activate(self):
		self.menu = obxml.Obmenu()
		self.menu_path = ""
		self.settings = None
		try:
			if self.menu_file.query_exists():
				self.menu_path = self.menu_file.get_path()
		except:
			# No files specified on the command line
			#
			# If the GSettings schema isn't installed, don't go kaboom
			if Gio.SettingsSchemaSource.lookup(Gio.SettingsSchemaSource.get_default(), self.appid, True):
				self.settings = Gio.Settings.new(self.appid)
				self.menu_path = GLib.Variant.get_string(self.settings.get_value("last-opened-menu"))

		if self.menu_path:
			# Load in memory the real xml menu
			self.menu.loadMenu(self.menu_path)
			self.unsaved_menu = False
		else:
			self.unsaved_menu = True

		self.initTree()
		self.treeview.grab_focus()

		# Set the columns
		factory = Gtk.SignalListItemFactory()
		factory.connect("setup", self.setup_listitem_cb, 0)
		factory.connect("bind", self.bind_listitem_cb, 0)
		column = self.builder.get_object("label_column")
		column.set_title(dgettext(self.gtk_domain, "Label"))
		column.set_factory(factory)

		factory = Gtk.SignalListItemFactory()
		factory.connect("setup", self.setup_listitem_cb, 1)
		factory.connect("bind", self.bind_listitem_cb, 1)
		column = self.builder.get_object("type_column")
		column.set_title(dgettext(self.gtk_domain, "Type"))
		column.set_factory(factory)

		factory = Gtk.SignalListItemFactory()
		factory.connect("setup", self.setup_listitem_cb, 2)
		factory.connect("bind", self.bind_listitem_cb, 2)
		column = self.builder.get_object("action_column")
		column.set_title(dgettext(self.gtk_domain, "Action"))
		column.set_factory(factory)

		factory = Gtk.SignalListItemFactory()
		factory.connect("setup", self.setup_listitem_cb, 3)
		factory.connect("bind", self.bind_listitem_cb, 3)
		column = self.builder.get_object("execute_column")
		column.set_factory(factory)

		# Some states of the app
		self.auto_change = False
		self._sth_changed(False)

		# Create the menu tree
		self.createTree(None, None)

		model = self.treeview.get_model()
		if model.get_n_items() > 0:
			self.treeview_changed(model, 0, 0, None)

		# Let's roll!
		self.add_window(self.appwindow)
		self.appwindow.set_visible(True)

	# Callback called when command-line arguments are provided
	def do_open(self, files, n_files, hint):
		if n_files > 1:
			print(_("Error: Obmenu can presently open only one file at a time."), file=sys.stderr)
			print(_("       Only the first specified will be opened."), file=sys.stderr)
		self.menu_file = files[0]
		self.do_activate()

	# Application init
	def __init__(self):
		# Types and actions that should be translated in the frontend but not the backend:
		self.actions = [_("Link"), _("Pipemenu"), _("Execute"), _("Reconfigure"), _("Restart"), _("Exit")]
		self.types = [_("item"), _("menu"), _("separator")]
		import obmenu_version
		self.appid = "io.sourceforge.obmenu"
		self.version = obmenu_version.VERSION
		super().__init__(application_id=self.appid,
				flags=Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.NON_UNIQUE)

if __name__ == "__main__":
	app = App()
	app.run(sys.argv)
