#!/usr/bin/python3
from pathlib import Path
import subprocess
import random
import sys
import xml.etree.ElementTree as ET
import traceback
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gio, Gtk, GObject, Gdk, GdkPixbuf


# This would typically be its own file
MENU_XML = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menubar">
    <submenu>
        <attribute name="label">_File</attribute>
        <section>
            <item>
                <attribute name="label" translatable="yes">_New</attribute>
                <attribute name="action">app.new_menu</attribute>
            </item>
            <item>
                <attribute name="label">_Open</attribute>
                <attribute name="action">app.open_menu</attribute>
            </item>
            <item>
                <attribute name="label">_Save</attribute>
                <attribute name="action">app.save_menu</attribute>
            </item>
            <item>
                <attribute name="label">_Save As</attribute>
                <attribute name="action">app.saveas_menu</attribute>
            </item>
            <item>
                <attribute name="label">_Reconfigure Openbox</attribute>
                <attribute name="action">app.reconfigure</attribute>
            </item>
            <item>
                <attribute name="label">_Quit</attribute>
                <attribute name="action">app.quit</attribute>
            </item>
        </section>
    </submenu>
    <submenu>
        <attribute name="label">_Edit</attribute>
        <section>
            <item>
                <attribute name="label">_Move Up</attribute>
                <attribute name="action">app.move_item_up</attribute>
            </item>
            <item>
                <attribute name="label">_Move Down</attribute>
                <attribute name="action">app.move_item_down</attribute>
            </item>
            <item>
                <attribute name="label">_Delete</attribute>
                <attribute name="action">app.delete_item</attribute>
            </item>
        </section>
    </submenu>
    <submenu>
        <attribute name="label">_Import</attribute>
        <section>
            <item>
                <attribute name="label">_Directory View</attribute>
                <attribute name="action">app.add_dir_view</attribute>
            </item>
            <item>
                <attribute name="label">_File List</attribute>
                <attribute name="action">app.add_file_view</attribute>
            </item>
            <item>
                <attribute name="label">_Bookmarks</attribute>
                <attribute name="action">app.add_bm_view</attribute>
            </item>
        </section>
    </submenu>
    <submenu>
        <attribute name="label">_Add</attribute>
        <section>
            <item>
                <attribute name="label">_Menu</attribute>
                <attribute name="action">app.add_item_menu</attribute>
            </item>
            <item>
                <attribute name="label">_Item</attribute>
                <attribute name="action">app.add_item_item</attribute>
            </item>
            <item>
                <attribute name="label">_Execution</attribute>
                <attribute name="action">app.add_item_execute</attribute>
            </item>
            <item>
                <attribute name="label">_Separator</attribute>
                <attribute name="action">app.add_item_separator</attribute>
            </item>
            <item>
                <attribute name="label">_Pipemenu</attribute>
                <attribute name="action">app.add_item_pipemenu</attribute>
            </item>
            <item>
                <attribute name="label">_Link to Menu</attribute>
                <attribute name="action">app.add_item_link</attribute>
            </item>
            <item>
                <attribute name="label">_Directory Pipe Menu</attribute>
                <attribute name="action">app.add_directory_pipe</attribute>
            </item>
        </section>
    </submenu>
    <submenu>
      <attribute name="label">_Help</attribute>
      <section>
        <item>
              <attribute name="label">_About</attribute>
              <attribute name="action">app.about</attribute>
          </item>
      </section>
    </submenu>
  </menu>
</interface>
"""

EMPTY_OPENBOX_MENU = """<?xml version='1.0' encoding='utf-8'?>
<openbox_menu xmlns="http://openbox.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://openbox.org/                 file:///usr/share/openbox/menu.xsd">
<menu id="root-menu" label="Openbox Menu">
<item label="New Item">
<action name="Execute">
<execute>command</execute>
</action>
</item>
</menu>
</openbox_menu>"""

# Helper function for ET
# backported from python 3.9
# Contributed by Stefan Behnel


def indent(tree, space="  ", level=0):
    """Indent an XML document by inserting newlines and indentation space
    after elements.
    *tree* is the ElementTree or Element to modify.  The (root) element
    itself will not be changed, but the tail text of all elements in its
    subtree will be adapted.
    *space* is the whitespace to insert for each indentation level, two
    space characters by default.
    *level* is the initial indentation level. Setting this to a higher
    value than 0 can be used for indenting subtrees that are more deeply
    nested inside of a document.
    """
    if isinstance(tree, ET.ElementTree):
        tree = tree.getroot()
    if level < 0:
        raise ValueError(
            f"Initial indentation level must be >= 0, got {level}")
    if not len(tree):
        return

    # Reduce the memory consumption by reusing indentation strings.
    indentations = ["\n" + level * space]

    def _indent_children(elem, level):
        # Start a new indentation level for the first child.
        child_level = level + 1
        try:
            child_indentation = indentations[child_level]
        except IndexError:
            child_indentation = indentations[level] + space
            indentations.append(child_indentation)

        if not elem.text or not elem.text.strip():
            elem.text = child_indentation

        for child in elem:
            if len(child):
                _indent_children(child, child_level)
            if not child.tail or not child.tail.strip():
                child.tail = child_indentation

        # Dedent after the last child by overwriting the previous indentation.
        if not child.tail.strip():
            child.tail = indentations[level]

    _indent_children(tree, 0)


def reconfigure_openbox():
    subprocess.run(["openbox", "--reconfigure"])


def print_stack():
    for line in traceback.format_stack():
        print(line.strip())

class Obxml2:
    def __init__(self, filename=None, change_observer=None):
        ET.register_namespace('', "http://openbox.org/")
        if filename is not None:
            self.open(filename)
        self.dirt_observer = change_observer

    def strip_ns(self, tag):
        _, _, stag = tag.rpartition('}')
        return stag

    def add_ns(self, tag):
        if self.import_empty_ns == True:
            return tag
        else:
            return "{http://openbox.org/}" + tag

    def check_default_ns(self, elem):
        if "{http://openbox.org/" not in elem.tag:
            self.import_empty_ns = True

    def open(self, filename):
        self.import_empty_ns = False
        try:
            self.xml = ET.parse(filename)
        except ET.ParseError as err:
#            lineno, column = err.position
#            err.msg = '{}\n{}\n{}'.format(err, lineno, column)
#            raise
            self.xml = None

        if self.xml is not None:
            self.root = self.xml.getroot()
            self.check_default_ns(self.root)

            if self.strip_ns(self.root.tag) != "openbox_menu":
                print('need to clear root tag not matching')
                self.clear()
            else:
                self.fname = filename
                self.clear_dirty()
        else:
            print('need to clear xml is none')
            self.clear()

    def save(self):
        if self.fname is None or self.fname == "":
            return False
        else:
            self.write(self.fname)
            return True

    def write(self, filename):
        indent(self.xml)
        self.xml.write(filename, "utf-8", True, '')
        self.fname = filename
        self.clear_dirty()

    def clear(self):
        self.fname = "untitled.menu.xml"
        self.xml = ET.ElementTree(ET.fromstring(EMPTY_OPENBOX_MENU))
        self.root = self.xml.getroot()
        self.clear_dirty()

    def is_dirty(self):
        return self.dirty

    def change_dirty(self, state):
        self.dirty = state
        if self.dirt_observer is not None:
            self.dirt_observer(state)

    def clear_dirty(self):
        self.change_dirty(False)

    def set_dirty(self):
        self.change_dirty(True)

    def parse(self, menu_treestore):
        self.parse_submenu(menu_treestore, None, self.root)

    def parse_submenu(self, menu_treestore, parent_iter, submenu):
        for child in submenu:
            if self.strip_ns(child.tag) == "menu":
                if child.get('label') is None:
                    self.parse_link(menu_treestore, parent_iter, child)
                elif child.get('execute') is not None:
                    self.parse_pipemenu(menu_treestore, parent_iter, child)
                else:
                    piter = menu_treestore.append(
                        parent_iter, [ child.get('icon'),
                                       child.get('label'), 
                                       self.strip_ns(child.tag), 
                                       "", "", child])
                    self.parse_submenu(menu_treestore, piter, child)
            elif self.strip_ns(child.tag) == "item":
                self.parse_item(menu_treestore, parent_iter, child)
            else:
                menu_treestore.append(
                    parent_iter, [
                        "", child.get('label'), self.strip_ns(
                            child.tag), "", "", child])

    def parse_pipemenu(self, menu_treestore, parent_iter, pipe):
        execute_text = pipe.get('execute').rstrip().lstrip()
        menu_treestore.append(
            parent_iter, [
                pipe.get('icon'), pipe.get('label'), 'pipemenu', 'Execute', execute_text, pipe])

    def parse_link(self, menu_treestore, parent_iter, link):
        menu_treestore.append(
            parent_iter, [
                self.get_icon(link), self.get_label(link), self.strip_ns(
                    link.tag), "Link", "", link])

    def parse_item(self, menu_treestore, parent_iter, item):
        if (len(list(item)) == 1):
            if self.strip_ns(item[0].tag) == "action":
                self.parse_action(
                    menu_treestore,
                    parent_iter,
                    item.get('icon'),
                    item.get('label'),
                    self.strip_ns(
                        item.tag),
                    item[0])
        elif (len(list(item)) == 0):
            piter = menu_treestore.append(
                parent_iter, [
                    item.get('icon'), item.get('label'), self.strip_ns(
                        item.tag), "", "", item])
        else:
            piter = menu_treestore.append(
                parent_iter, [
                    item.get('icon'), item.get('label'), self.strip_ns(
                        item.tag), "Multiple Execute", "", item])
            for action in item:
                self.parse_action(menu_treestore, piter, "", "", "", action)

    def parse_action(self, menu_treestore, parent_iter,
                     item_icon, item_label, 
                     item_type, action):
        execute_text = ""
        if len(list(action)) == 1:
            if self.strip_ns(
                    action[0].tag) == "execute" and action[0].text is not None:
                action[0].text = action[0].text.rstrip().lstrip()
                execute_text = action[0].text
            # elif parse the rest of the possible types
        menu_treestore.append(
            parent_iter, [ item_icon, item_label, 
                           item_type, action.get('name'), 
                           execute_text, action])

    def get_id_string(self, item):
        if self.strip_ns(item.tag) == "menu":
            return item.get('id')
        else:
            return ""

    def set_id(self, item, id_string):
        if self.strip_ns(item.tag) == "menu":
            if item.get('id') != id_string:
                item.set('id', id_string)
                self.set_dirty()

    def resolve_link(self, link):
        for menu in self.root.iter():
            if (self.strip_ns(menu.tag) == "menu"):
                if (menu.get('id') == link.get('id')
                        and menu.get('label') is not None):
                    return menu
        return None

    def is_menu(self, item):
        return self.strip_ns(item.tag) == "menu"

    def is_link(self, item):
        return self.is_menu(item) and item.get('label') is None

    def get_label(self, link):
        if self.strip_ns(link.tag) == "menu":
            if link.get('label') is None:
                link_orig_label = "???"

                resolved_link = self.resolve_link(link)
                if resolved_link is not None:
                    link_orig_label = resolved_link.get('label')
                return link_orig_label
            else:
                return link.get('label')
        elif self.strip_ns(link.tag) == "action":
            parent_item = self.get_parent(link)
            if (len(list(parent_item)) == 1):
                return self.get_label( parent_item )
            else:
                return link.get('label')
        else:
            return link.get('label')

    def set_label(self, item, label):
        if self.strip_ns(item.tag) == "item":
            if item.get('label') != label:
                item.set('label', label)
                self.set_dirty()
        elif self.strip_ns(item.tag) == "action":
            if label != "":
                self.set_label(self.get_parent(item), label)
        elif self.strip_ns(item.tag) == "menu":
            if item.get('label') is not None:
                if item.get('label') != label:
                    item.set('label', label)
                    self.set_dirty()

    def set_execute(self, item, execute_text):
        if self.strip_ns(item.tag) == "action":
            if len(list(item)) == 1:
                if item[0].text != execute_text:
                    item[0].text = execute_text
                    self.set_dirty()
        elif self.strip_ns(item.tag) == "menu":
            if item.get('execute') is not None:
                if item.get('execute') != execute_text:
                    item.set('execute', execute_text)
                    self.set_dirty()

    def get_execute(self, item):
        if self.strip_ns(item.tag) == "action":
            if (len(list(item)) == 1):
                return item[0].text
        elif self.strip_ns(item.tag) == "menu":
            if item.get('execute') is not None:
                return item.get('execute')
        return None

    def set_action(self, item, action_text):
        if self.strip_ns(item.tag) == "action":
            if item.get('name') != action_text:
                old_action = item.get('name')
                item.set('name', action_text)
                self.set_dirty()
                if (action_text != "Execute") and (old_action == "Execute"):
                    if (len(list(item)) == 1):
                        item.remove(item[0])
                if (action_text == "Execute") and (old_action != "Execute"):
                    init_exe = ET.Element(self.add_ns("execute"))
                    init_exe.text = "command"
                    item.append(init_exe)
        elif self.strip_ns(item.tag) == "item":
            if len(list(item)) == 1:
                self.set_action(item[0], action_text)

    def get_action(self, item):
        if self.strip_ns(item.tag) == "action":
            return item.get('name')
        elif self.strip_ns(item.tag) == "item":
            if len(list(item)) == 1:
                return item[0].get('name')
        elif self.strip_ns(item.tag) == "menu":
            if item.get('execute') is not None:
                return "Execute"
            elif item.get('label') is None:
                return "Link"
        return None

    def get_icon(self, item):
        if self.strip_ns(item.tag) == "action":
            parent = self.get_parent(item)
            if (len(list(parent)) == 1):
                return self.get_icon(parent)
            else:
                return None
        elif self.strip_ns(item.tag) == "menu" and item.get('label') is None:
            resolved_link = self.resolve_link(item)
            if resolved_link is not None:
                return resolved_link.get('icon')
        else:
            return item.get('icon')

    def set_icon(self, item, icon_text):
        if self.strip_ns(item.tag) == "item" or self.strip_ns(item.tag) == "menu":
            item.set('icon', icon_text)
            self.set_dirty()
        elif self.strip_ns(item.tag) == "action":
            self.set_icon(self.get_parent(item), icon_text)
            

    def find_in_children(self, submenu, node):
        for child in submenu:
            if child == node:
                return submenu
            else:
                if len(list(child)) > 0:
                    ref = self.find_in_children(child, node)
                    if ref is not None:
                        return ref
        return None

    def get_parent(self, child):
        return self.find_in_children(self.root, child)

    def delete_node(self, item):
        p = self.get_parent(item)
        if self.strip_ns(item.tag) == "action" and len(list(p)) == 1:
            parent = self.get_parent(p)
            item = p
        else:
            parent = p

        if parent is not None:
            parent.remove(item)
            self.set_dirty()

    def insert_node_below(self, item, node_tag, allow_root=False):
        inserted_item = None
        if item is None:
            if allow_root:
                self.root.append(ET.Element(node_tag))
                inserted_item = list(self.root)[len(list(self.root)) - 1]
                self.set_dirty()
        else:
            parent = self.get_parent(item)
            if (self.strip_ns(node_tag) == "menu" and (len(list(item)) == 0 or parent == self.root)) and (
                    item.get('label') is not None) and (item.get('execute') is None) and (allow_root is False):
                item.append(ET.Element(node_tag))
                inserted_item = list(item)[len(list(item)) - 1]
                self.set_dirty()
            else:
                parent = self.get_parent(item)
                if self.strip_ns(
                        item.tag) == "action" and node_tag != item.tag:
                    item = parent
                    parent = self.get_parent(item)
                if (parent is not None) and (
                        parent != self.root or allow_root == True):
                    current_index = list(parent).index(item)
                    parent.insert(current_index + 1, ET.Element(node_tag))
                    inserted_item = list(parent)[current_index + 1]
                    self.set_dirty()
        return inserted_item

    def init_item(self, item):
        item.set('label', "New Item")
        init_action = ET.Element(self.add_ns("action"))
        init_action.set('name', "Execute")
        init_exe = ET.Element(self.add_ns("execute"))
        init_exe.text = "command"
        init_action.append(init_exe)
        item.append(init_action)

    def insert_item_below(self, item):
        inserted_item = self.insert_node_below(item, self.add_ns("item"))
        if inserted_item is not None:
            self.init_item(inserted_item)
        return inserted_item

    def insert_link_below(self, item):
        inserted_item = self.insert_node_below(item, self.add_ns("menu"))
        if inserted_item is not None:
            self.set_id(inserted_item, "None")
        return inserted_item

    def insert_pipe_below(self, item):
        inserted_item = self.insert_node_below(item, self.add_ns("menu"))
        if inserted_item is not None:
            self.set_id(inserted_item, "pipe-" +
                        str(random.randrange(33333, 9999999)))
            inserted_item.set('label', "New Pipemenu")
            inserted_item.set('execute', "command")
        return inserted_item

    def insert_menu_below(self, item):
        inserted_item = self.insert_node_below(item, self.add_ns("menu"), True)
        if inserted_item is not None:
            self.set_id(inserted_item, "menu-" +
                        str(random.randrange(33333, 9999999)))
            inserted_item.set('label', "New Menu")
            init_item = ET.Element(self.add_ns("item"))
            self.init_item(init_item)
            inserted_item.append(init_item)
        return inserted_item

    def insert_separator_below(self, item):
        inserted_item = self.insert_node_below(item, self.add_ns("separator"))
        return inserted_item

    def move_up(self, item):
        parent = self.get_parent(item)
        if self.strip_ns(item.tag) == "action":
            item = parent
            parent = self.get_parent(item)
        if parent is not None:
            current_index = list(parent).index(item)
            if current_index > 0:
                previous_item = list(parent)[current_index - 1]
                parent.remove(previous_item)
                parent.insert(current_index, previous_item)
                self.set_dirty()

    def move_down(self, item):
        parent = self.get_parent(item)
        if self.strip_ns(item.tag) == "action":
            item = parent
            parent = self.get_parent(item)
        if parent is not None:
            current_index = list(parent).index(item)
            if current_index < len(list(parent)) - 1:
                parent.remove(item)
                parent.insert(current_index + 1, item)
                self.set_dirty()

    def add_separator(self, item, menu_treestore, menu_treestore_iter):
        separator_node = self.insert_separator_below(item)
        if separator_node is not None:
            menu_treestore.insert_after(
                None, menu_treestore_iter, [
                    "", self.get_label(separator_node), self.strip_ns(
                        separator_node.tag), "", "", separator_node])

    def add_item(self, item, menu_treestore, menu_treestore_iter):
        item_node = self.insert_item_below(item)
        if item_node is not None:
            menu_treestore.insert_after(None,
                                        menu_treestore_iter,
                                        ["icon", self.get_label(item_node),
                                         self.strip_ns(item_node.tag),
                                            item_node[0].get('name'),
                                            item_node[0][0].text.rstrip(
                                        ).lstrip(),
                                            item_node[0]])

    def add_action(self, item, menu_treestore, menu_treestore_iter):
        if self.strip_ns(item.tag) == "item":
            init_action = ET.Element(self.add_ns("action"))
            init_action.set('name', "Execute")
            init_exe = ET.Element(self.add_ns("execute"))
            init_exe.text = "command"
            init_action.append(init_exe)
            if (len(list(item)) == 1):
                item.append(init_action)
                menu_treestore.set_row(
                    menu_treestore_iter, [
                        "icon", item.get('label'), self.strip_ns(
                            item.tag), "Multiple Execute", "", item])
                for action in item:
                    self.parse_action(
                        menu_treestore, menu_treestore_iter, "", "", "", action)
            elif (len(list(item)) == 0):
                item.append(init_action)
                menu_treestore.set_row(
                    menu_treestore_iter, [
                        "icon", item.get('label'), 
                        self.strip_ns(item.tag), "Execute", 
                        item[0][0].text.rstrip().lstrip(), item[0]])
            else:
                item.append(init_action)
                self.parse_action(menu_treestore, menu_treestore_iter, "", "", "", list(item)[
                                  len(list(item)) - 1])
        elif self.strip_ns(item.tag) == "action":
            parent = self.get_parent(item)
            if parent is not None:
                if len(list(parent)) > 1:
                    menu_treestore_iter = menu_treestore.iter_parent(
                        menu_treestore_iter)
                self.add_action(parent, menu_treestore, menu_treestore_iter)

    def add_link(self, item, menu_treestore, menu_treestore_iter):
        link_node = self.insert_link_below(item)
        if link_node is not None:
            menu_treestore.insert_after(
                None, menu_treestore_iter, [
                    "icon", self.get_label(link_node), 
                    self.strip_ns(link_node.tag), 
                    "Link", "", link_node])

    def add_pipemenu(self, item, menu_treestore, menu_treestore_iter):
        node = self.insert_pipe_below(item)
        if node is not None:
            menu_treestore.insert_after(
                None, menu_treestore_iter, [
                    "icon", self.get_label(node), 'pipemenu', "Execute", node.get('execute'), node])

    def add_menu(self, item, menu_treestore, menu_treestore_iter):
        node = self.insert_menu_below(item)
        if node is not None:
            piter = menu_treestore.insert_after(
                None, menu_treestore_iter, [
                    "icon", self.get_label(node), 
                    self.strip_ns(node.tag),
                    "", "", node])
            self.parse_item(menu_treestore, piter, node[0])

    def remove_item(self, item, menu_treestore, menu_treestore_iter):
        self.delete_node(item)
        menu_treestore.remove(menu_treestore_iter)

    def swap_up(self, item, menu_treestore, menu_treestore_iter):
        previous_iter = menu_treestore.iter_previous(menu_treestore_iter)
        if previous_iter is not None:
            self.move_up(item)
            menu_treestore.move_before(menu_treestore_iter, previous_iter)

    def swap_down(self, item, menu_treestore, menu_treestore_iter):
        next_iter = menu_treestore.iter_next(menu_treestore_iter)
        if next_iter is not None:
            self.move_down(item)
            menu_treestore.move_after(menu_treestore_iter, next_iter)


class Obmenu2Window(Gtk.ApplicationWindow):
    def __init__(self, passed_filename, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(1)
        self.set_default_size(640, 640)
        self.set_gravity(Gdk.Gravity.CENTER)
        self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)

        self.omenu = Obxml2(None, self.omenu_changed)

        home = str(Path.home())
        self.dotfile_menu_xml = home + '/.config/openbox/menu.xml'
        init_file = self.dotfile_menu_xml

        if passed_filename is not None and Path(passed_filename).is_file():
            init_file = passed_filename

        if Path(init_file).is_file():
            self.omenu.open(init_file)
        else:
            self.omenu.clear()

        self.set_title("obmenu2: " + self.omenu.fname)

        # Setting up the self.grid in which the elements are to be positionned
        self.grid = Gtk.Grid()
        self.add(self.grid)

        # Creating the ListStore model
        self.menu_treestore = Gtk.TreeStore(
            str, str, str, str, str, GObject.TYPE_PYOBJECT)
        self.omenu.parse(self.menu_treestore)

        self.current_filter_language = None

        # creating the treeview, making it use the filter as a model, and
        # adding the columns
        self.treeview = Gtk.TreeView.new_with_model(
            self.menu_treestore.filter_new())
        self.treeview.set_headers_visible(True)
        for i, column_title in enumerate(
            ["Label", "Type", "Action", "Execute"]
        ):
            if column_title == "Label":
                px_renderer = Gtk.CellRendererPixbuf()
                px_column = Gtk.TreeViewColumn(column_title)
                px_column.pack_start(px_renderer, False)
                txt_renderer = Gtk.CellRendererText()
                px_column.pack_start(txt_renderer, False)
                px_column.set_cell_data_func(px_renderer, self.get_tree_cell_pixbuf)
                px_column.set_cell_data_func(txt_renderer, self.get_tree_cell_text)
                self.treeview.append_column(px_column)
            else:
                renderer = Gtk.CellRendererText()
                column = Gtk.TreeViewColumn(column_title, renderer, text=i+1)
                column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
                self.treeview.append_column(column)

        self.treeview.get_selection().connect("changed", self.on_cursor_changed)
        self.treeview.connect("key-press-event", self.on_treeview_keypress)

        # creating menu buttons and connect it with the actions
        self.menu_toolbar = Gtk.Toolbar()
        self.menu_toolbar.set_halign(Gtk.Align.START)
        self.menu_toolbar.set_hexpand(False)

        for toolbar_item in [["document-save", "Save", self.on_save_menu],
                             [None, None, None],
                             [None, "Add Menu", self.on_add_menu],
                             [None, "Add Item", self.on_add_item],
                             [None, "Add Separator", self.on_add_separator],
                             [None, None, None],
                             ["go-up", "Up", self.on_move_item_up],
                             ["go-down", "Down", self.on_move_item_down],
                             [None, None, None],
                             ["edit-delete", "Delete", self.on_delete_item]]:
            if toolbar_item[2] is not None:
                if toolbar_item[0] is not None:
                    icon = Gtk.Image.new_from_icon_name(toolbar_item[0], 24)
                else:
                    icon = None
                button = Gtk.ToolButton.new(icon, toolbar_item[1])
                button.connect("clicked", toolbar_item[2])
                button.set_expand(False)
                button.set_homogeneous(False)
                self.menu_toolbar.insert(
                    button,
                    len(self.menu_toolbar.get_children()))
            else:
                separator = Gtk.SeparatorToolItem()
                separator.set_expand(False)
                separator.set_draw(True)
                self.menu_toolbar.insert(
                    separator,
                    len(self.menu_toolbar.get_children()))

        # creating the entry info fields
        self.icon_edit_img = Gtk.Image()

        self.icon_edit_button = Gtk.Button(xalign=0.5, yalign=1)
        self.icon_edit_button.set_label("No Icon")
        self.icon_edit_button.connect( "clicked", self.on_icon_search_clicked)
        self.icon_edit_button.set_sensitive(False)
            

        self.label_edit_label = Gtk.Label(label="Label/Icon", xalign=0)
        self.label_edit_label.set_margin_start(20)
        self.label_edit_label.set_margin_end(6)
        self.label_edit_label.set_hexpand(False)
        self.label_edit_label.set_halign(Gtk.Align.END)

        self.entry_edit_label = Gtk.Entry()
        self.entry_edit_label.set_hexpand(True)
        self.entry_edit_label.set_halign(Gtk.Align.FILL)
        self.entry_edit_label.set_margin_start(0)
        self.entry_edit_label.connect("changed", self.on_label_changed)
        self.entry_edit_label.set_sensitive(False)

        self.label_edit_id = Gtk.Label(label="id", xalign=0)
        self.label_edit_id.set_hexpand(False)
        self.label_edit_id.set_margin_start(20)
        self.label_edit_id.set_margin_end(6)
        self.label_edit_id.set_halign(Gtk.Align.END)
        self.entry_edit_id = Gtk.Entry()
        self.entry_edit_id.set_sensitive(False)
        self.entry_edit_id.connect("changed", self.on_id_changed)

        self.label_edit_action = Gtk.Label(label="Action", xalign=0)
        self.label_edit_action.set_margin_start(20)
        self.label_edit_action.set_margin_end(6)
        self.label_edit_action.set_hexpand(False)
        self.label_edit_action.set_halign(Gtk.Align.END)
        action_options = Gtk.ListStore(str)
        action_options.append(["Execute"])
        action_options.append(["Reconfigure"])
        action_options.append(["Restart"])
        action_options.append(["Exit"])
        self.combo_edit_action = Gtk.ComboBox.new_with_model_and_entry(
            action_options)
        self.combo_edit_action.set_entry_text_column(0)
        self.combo_edit_action.set_sensitive(False)
        self.combo_edit_action.connect("changed", self.on_action_changed)

        self.label_edit_execute = Gtk.Label(label="Execute", xalign=0)
        self.label_edit_execute.set_margin_start(20)
        self.label_edit_execute.set_margin_end(6)
        self.label_edit_execute.set_hexpand(False)
        self.label_edit_execute.set_halign(Gtk.Align.END)
        self.entry_edit_execute = Gtk.Entry(hexpand=True)
        self.entry_edit_execute.set_sensitive(False)
        self.entry_edit_execute.connect("changed", self.on_execution_changed)
        self.search_edit_execute = Gtk.Button(label="...")
        self.search_edit_execute.connect(
            "clicked", self.on_search_execute_clicked)
        self.search_edit_execute.set_sensitive(False)
        self.search_edit_execute.set_hexpand(False)
        self.search_edit_execute.set_halign(Gtk.Align.END)

        # setting up the layout, putting the treeview in a scrollwindow, and
        # the buttons in a row
        self.scrollable_treelist = Gtk.ScrolledWindow()
        self.scrollable_treelist.set_policy(
            Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
        self.scrollable_treelist.set_vexpand(True)
        self.scrollable_treelist.add(self.treeview)

        self.grid.set_column_homogeneous(False)
        self.grid.set_column_spacing(0)
        self.grid.set_row_homogeneous(False)

        self.grid.attach(self.scrollable_treelist, 0, 0, 3, 1)
        self.grid.attach_next_to(
            self.menu_toolbar,
            self.scrollable_treelist,
            Gtk.PositionType.TOP,
            3,
            1)

        self.grid.attach_next_to(
            self.label_edit_label,
            self.scrollable_treelist,
            Gtk.PositionType.BOTTOM,
            1,
            1)
        self.grid.attach_next_to(
            self.entry_edit_label,
            self.label_edit_label,
            Gtk.PositionType.RIGHT,
            1,
            1)
        self.grid.attach_next_to(
            self.icon_edit_button,
            self.entry_edit_label,
            Gtk.PositionType.RIGHT,
            1,
            1)

        self.grid.attach_next_to(
            self.label_edit_id,
            self.label_edit_label,
            Gtk.PositionType.BOTTOM,
            1,
            1)
        self.grid.attach_next_to(
            self.entry_edit_id,
            self.label_edit_id,
            Gtk.PositionType.RIGHT,
            2,
            1)

        self.grid.attach_next_to(
            self.label_edit_action,
            self.label_edit_id,
            Gtk.PositionType.BOTTOM,
            1,
            1)
        self.grid.attach_next_to(
            self.combo_edit_action,
            self.label_edit_action,
            Gtk.PositionType.RIGHT,
            2,
            1)

        self.grid.attach_next_to(
            self.label_edit_execute,
            self.label_edit_action,
            Gtk.PositionType.BOTTOM,
            1,
            1)
        self.grid.attach_next_to(
            self.entry_edit_execute,
            self.label_edit_execute,
            Gtk.PositionType.RIGHT,
            1,
            1)
        self.grid.attach_next_to(
            self.search_edit_execute,
            self.entry_edit_execute,
            Gtk.PositionType.RIGHT,
            1,
            1)

        self.show_all()

    def get_tree_cell_text(self, col, cell, model, iter, user_data):
        cell.set_property('text', model.get_value(iter, 1))

    def get_tree_cell_pixbuf(self, col, cell, model, iter, user_data):
        icon_filename = model.get_value(iter, 0)
        if icon_filename is not None and Path(icon_filename).is_file():
            cell.set_property('pixbuf', GdkPixbuf.Pixbuf.new_from_file_at_size(icon_filename, 24, 24))
        else:
            cell.set_property('pixbuf', None)

    # Override the default handler for the delete-event signal
    def do_delete_event(self, event):
        if self.omenu.is_dirty() == True:
            # Show our message dialog
            d = Gtk.MessageDialog(transient_for=self,
                                  modal=True,
                                  buttons=Gtk.ButtonsType.YES_NO)
            d.props.text = 'Are you sure you want to quit?'
            d.format_secondary_text(
                "Do you want to discard the changes?"
            )
            response = d.run()
            d.destroy()

            # We only terminate when the user presses the OK button
            if response == Gtk.ResponseType.YES:
                print('Terminating...')
                return False

            # Otherwise we keep the application open
            return True
        else:
            return False

    def get_selected_store_iter(self):
        selection = self.treeview.get_selection()
        _, paths = selection.get_selected_rows()
        iter = None
        for path in paths:
            iter = self.menu_treestore.get_iter(path)
        return iter

    def get_iter_object(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 5)

    def get_iter_icon_name(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 0)

    def get_iter_label(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 1)

    def get_iter_type(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 2)

    def get_iter_action(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 3)

    def get_iter_exe(self, store_iter):
        return self.menu_treestore.get_value(store_iter, 4)

    def on_search_execute_clicked(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Please choose a file",
            parent=self,
            action=Gtk.FileChooserAction.OPEN,
        )
        dialog.add_buttons(Gtk.STOCK_CANCEL,
                           Gtk.ResponseType.CANCEL,
                           Gtk.STOCK_OK,
                           Gtk.ResponseType.OK)
        self.add_filter_any(dialog)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.entry_edit_execute.set_text(dialog.get_filename())

        dialog.destroy()


    def on_icon_search_clicked(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Please choose a file",
            parent=self,
            action=Gtk.FileChooserAction.OPEN,
        )
        dialog.add_buttons("Clear Icon", 
                           23,
                           Gtk.STOCK_CANCEL,
                           Gtk.ResponseType.CANCEL,
                           Gtk.STOCK_OK,
                           Gtk.ResponseType.OK)
        self.add_filter_image(dialog)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.perform_on_selection(self.omenu.set_icon, dialog.get_filename())
            self.reload_icon_button()
        elif response == 23:
            store_iter = self.get_selected_store_iter()
            if self.get_iter_icon_name(store_iter) is not None:
                if self.get_iter_icon_name(store_iter) != "":
                    self.perform_on_selection(self.omenu.set_icon, "")
            self.reload_icon_button()
            
        dialog.destroy()

    def add_filter_image(self, dialog):
        filter_png = Gtk.FileFilter()
        filter_png.set_name("PNG Images")
        filter_png.add_mime_type("image/png")
        dialog.add_filter(filter_png)
        filter_jpg = Gtk.FileFilter()
        filter_jpg.set_name("JPEG Images")
        filter_jpg.add_mime_type("image/jpeg")
        dialog.add_filter(filter_jpg)
        filter_svg = Gtk.FileFilter()
        filter_svg.set_name("SVG Images")
        filter_svg.add_mime_type("image/svg+xml")
        filter_svg.add_pattern("*.svg*")
        dialog.add_filter(filter_svg)
        filter_xpm = Gtk.FileFilter()
        filter_xpm.set_name("XPM Images")
        filter_xpm.add_mime_type("image/x-xpixmap")
        dialog.add_filter(filter_xpm)
        filter_gif = Gtk.FileFilter()
        filter_gif.set_name("GIF Images")
        filter_gif.add_mime_type("image/gif")
        dialog.add_filter(filter_gif)

    def add_filter_any(self, dialog):
        filter_any = Gtk.FileFilter()
        filter_any.set_name("Any files")
        filter_any.add_pattern("*")
        dialog.add_filter(filter_any)

    def add_filter_xml(self, dialog):
        filter_xml = Gtk.FileFilter()
        filter_xml.set_name("XML files")
        filter_xml.add_mime_type("text/xml")
        dialog.add_filter(filter_xml)

    def set_combo_value(self, combo_text):
        if combo_text is not None:
            # this function should be obsolete if set_active_id() would work!
            if combo_text == "Execute":
                self.combo_edit_action.set_active(0)
            elif combo_text == "Reconfigure":
                self.combo_edit_action.set_active(1)
            elif combo_text == "Restart":
                self.combo_edit_action.set_active(2)
            elif combo_text == "Exit":
                self.combo_edit_action.set_active(3)
            else:
                self.combo_edit_action.set_active(0)
            self.combo_edit_action.set_sensitive(True)
        else:
            self.combo_edit_action.set_active_id(None)
            self.combo_edit_action.set_sensitive(False)

    def disable_entry(self, entry_edit, value=""):
        entry_edit.set_text(value)
        entry_edit.set_sensitive(False)

    def enable_entry(self, entry_edit, value):
        entry_edit.set_text(value)
        entry_edit.set_sensitive(True)

    def reload_icon_button(self):
        store_iter = self.get_selected_store_iter()
        if store_iter is not None:
            icon_name = self.get_iter_icon_name(store_iter)
            if icon_name != "" and icon_name is not None:
                if Path(icon_name).is_file():
                    pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_name, 24, 24)
                    if pixbuf is not None:
                        self.icon_edit_img.set_from_pixbuf(pixbuf)
                        self.icon_edit_button.set_image(self.icon_edit_img)
                        self.icon_edit_button.set_label("")
                        self.icon_edit_button.set_always_show_image(True)
                else:
                    self.icon_edit_button.set_image(None)
                    self.icon_edit_button.set_label("No Icon")
                    self.icon_edit_button.set_always_show_image(False)
            else:
                self.icon_edit_button.set_image(None)
                self.icon_edit_button.set_label("No Icon")
                self.icon_edit_button.set_always_show_image(False)


    def update_input_fields(self):
        store_iter = self.get_selected_store_iter()
        if store_iter is not None:
            if self.get_iter_type(store_iter) == "item":
                self.enable_entry(
                    self.entry_edit_label,
                    self.get_iter_label(store_iter))
                self.disable_entry(self.entry_edit_id)
                self.set_combo_value(self.get_iter_action(store_iter))
                if self.get_iter_action(store_iter) == "Execute":
                    self.enable_entry(
                        self.entry_edit_execute,
                        self.get_iter_exe(store_iter))
                else:
                    self.disable_entry(self.entry_edit_execute)
            elif self.get_iter_type(store_iter) == "menu":
                if self.get_iter_action(store_iter) == "Link":
                    self.disable_entry(
                        self.entry_edit_label,
                        self.get_iter_label(store_iter))
                else:
                    self.enable_entry(
                        self.entry_edit_label,
                        self.get_iter_label(store_iter))
                self.enable_entry(
                    self.entry_edit_id, self.omenu.get_id_string(
                        self.get_iter_object(store_iter)))
                self.disable_entry(self.entry_edit_execute)
                self.set_combo_value(None)
            elif self.get_iter_type(store_iter) == "pipemenu":
                self.enable_entry(
                    self.entry_edit_label,
                    self.get_iter_label(store_iter))
                self.enable_entry(
                    self.entry_edit_id, self.omenu.get_id_string(
                        self.get_iter_object(store_iter)))
                self.enable_entry(
                    self.entry_edit_execute,
                    self.get_iter_exe(store_iter))
                self.set_combo_value(None)
            elif self.get_iter_type(store_iter) == "":
                self.disable_entry(self.entry_edit_label)
                self.disable_entry(self.entry_edit_id)
                self.set_combo_value(self.get_iter_action(store_iter))
                if self.get_iter_action(store_iter) == "Execute":
                    self.enable_entry(
                        self.entry_edit_execute,
                        self.get_iter_exe(store_iter))
                else:
                    self.disable_entry(self.entry_edit_execute)
            else:  # "separator"
                self.disable_entry(self.entry_edit_label)
                self.disable_entry(self.entry_edit_id)
                self.disable_entry(self.entry_edit_execute)
                self.set_combo_value(None)

            self.reload_icon_button()
            
        else:
            self.disable_entry(self.entry_edit_label)
            self.disable_entry(self.entry_edit_id)
            self.disable_entry(self.entry_edit_execute)
            self.set_combo_value(None)
        self.search_edit_execute.set_sensitive(
            self.entry_edit_execute.get_sensitive())
        self.icon_edit_button.set_sensitive(
            self.entry_edit_label.get_sensitive())


    def on_cursor_changed(self, selection):
        self.update_input_fields()

    def on_treeview_keypress(self, widget, ev):
        if ev.keyval == Gdk.KEY_Delete and ev.type == Gdk.EventType.KEY_PRESS:
            self.on_delete_item()
        #elif ev.keyval == Gdk.KEY_S and ev.state == Gdk.ModifierType.CONTROL_MASK:
        #    self.on_save_menu()

    def request_discard(self):
        dialog = Gtk.MessageDialog(
            parent=self,
            flags=0,
            message_type=Gtk.MessageType.QUESTION,
            buttons=Gtk.ButtonsType.YES_NO,
            text="You have unsaved changes!",
        )
        dialog.format_secondary_text(
            "Do you want to discard the changes?"
        )

        shall_discard = False
        response = dialog.run()
        if response == Gtk.ResponseType.YES:
            shall_discard = True
        elif response == Gtk.ResponseType.NO:
            shall_discard = False

        dialog.destroy()
        return shall_discard

    def on_new_menu(self, widget=None):
        if self.omenu.is_dirty():
            if self.request_discard():
                self.omenu.clear()
                self.menu_treestore.clear()
                self.omenu.parse(self.menu_treestore)
        else:
            self.omenu.clear()
            self.menu_treestore.clear()
            self.omenu.parse(self.menu_treestore)

    def on_open_menu(self, widget=None):
        if self.omenu.is_dirty():
            if self.request_discard() == False:
                return

        # show file picker
        dialog = Gtk.FileChooserDialog(
            title="Please choose a file",
            parent=self,
            action=Gtk.FileChooserAction.OPEN,
        )
        dialog.add_buttons(Gtk.STOCK_CANCEL,
                           Gtk.ResponseType.CANCEL,
                           Gtk.STOCK_OPEN,
                           Gtk.ResponseType.OK)
        self.add_filter_xml(dialog)
        self.add_filter_any(dialog)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            # load selected
            self.treeview.get_selection().unselect_all()
            self.treeview.collapse_all()
            self.omenu.open(dialog.get_filename())
            self.menu_treestore.clear()
            self.omenu.parse(self.menu_treestore)
            self.set_title("obmenu2: " + self.omenu.fname)

        dialog.destroy()

    def on_save_menu(self, widget=None):
        if self.omenu.save() == False:
            self.on_save_as_menu()
        else:
            if self.omenu.fname == self.dotfile_menu_xml:
                reconfigure_openbox()

    def on_save_as_menu(self, widget=None):
        dialog = Gtk.FileChooserDialog(
            title="Please choose a file",
            parent=self,
            action=Gtk.FileChooserAction.SAVE,
        )
        dialog.add_buttons(Gtk.STOCK_CANCEL,
                           Gtk.ResponseType.CANCEL,
                           Gtk.STOCK_SAVE,
                           Gtk.ResponseType.OK)
        dialog.set_current_name("menu.xml")
        dialog.set_do_overwrite_confirmation(True)

        self.add_filter_xml(dialog)
        self.add_filter_any(dialog)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            # save current
            self.omenu.write(dialog.get_filename())
            if self.omenu.fname == self.dotfile_menu_xml:
                reconfigure_openbox()

        dialog.destroy()


    def find_links(self, search_id, store_iter):
        links_found = []
        while store_iter is not None:
            store_iter_obj = self.get_iter_object(store_iter)
            menu_id = self.omenu.get_id_string(store_iter_obj)
            if menu_id == search_id and self.omenu.is_link(store_iter_obj):
                links_found.append(store_iter)
            if self.menu_treestore.iter_has_child(store_iter):
                child_iter = self.menu_treestore.iter_children(store_iter)
                child_links = self.find_links(search_id, child_iter)
                if len(child_links) > 0:
                    links_found.extend(child_links)
            store_iter = self.menu_treestore.iter_next(store_iter)
                
        return links_found


    def perform_at_selection(self, operation, allow_root=False):
        store_iter = self.get_selected_store_iter()
        if store_iter is not None:
            #TODO: self.push_to_undo(UndoItem(self.menu_treestore, self.omenu))
            operation(
                self.get_iter_object(store_iter),
                self.menu_treestore,
                store_iter)
        else:
            if allow_root == True:
                operation(None, self.menu_treestore, None)
        self.update_input_fields()

    def perform_on_selection(self, operation, argument):
        store_iter = self.get_selected_store_iter()
        if store_iter is not None:
            store_iter_obj = self.get_iter_object(store_iter)
            if store_iter_obj is not None:
                operation(store_iter_obj, argument)
                label_text = ""
                execution_text = ""
                action_text = ""
                icon_text = ""
                if self.omenu.get_label(store_iter_obj) is not None:
                    label_text = self.omenu.get_label(store_iter_obj)
                if self.omenu.get_execute(store_iter_obj) is not None:
                    execution_text = self.omenu.get_execute(
                        store_iter_obj)
                if self.omenu.get_action(store_iter_obj) is not None:
                    action_text = self.omenu.get_action(store_iter_obj)
                if self.omenu.get_icon(store_iter_obj) is not None:
                    icon_text = self.omenu.get_icon(store_iter_obj)
                self.menu_treestore.set_row(store_iter,
                                            [icon_text,
                                             label_text,
                                             self.get_iter_type(store_iter),
                                             action_text,
                                             execution_text,
                                             self.get_iter_object(store_iter)])
                self.update_input_fields()
                if self.omenu.is_menu(store_iter_obj) == True and self.omenu.is_link(store_iter_obj) == False:
                    links = self.find_links(self.omenu.get_id_string(store_iter_obj), self.menu_treestore.get_iter_first())
                    for link_iter in links:
                        self.menu_treestore.set_row(link_iter,
                                                    [icon_text,
                                                     label_text,
                                                     self.get_iter_type(link_iter),
                                                     self.get_iter_action(link_iter),
                                                     self.get_iter_exe(link_iter),
                                                     self.get_iter_object(link_iter)])

    def on_move_item_up(self, widget=None):
        self.perform_at_selection(self.omenu.swap_up)

    def on_move_item_down(self, widget=None):
        self.perform_at_selection(self.omenu.swap_down)

    def on_delete_item(self, widget=None):
        self.perform_at_selection(self.omenu.remove_item)

    def on_add_menu(self, widget=None):
        self.perform_at_selection(self.omenu.add_menu, True)

    def on_add_item(self, widget=None):
        self.perform_at_selection(self.omenu.add_item)

    def on_add_execute(self, widget=None):
        self.perform_at_selection(self.omenu.add_action)

    def on_add_separator(self, widget=None):
        self.perform_at_selection(self.omenu.add_separator)

    def on_add_pipemenu(self, widget=None):
        self.perform_at_selection(self.omenu.add_pipemenu)

    def on_add_link(self, widget=None):
        self.perform_at_selection(self.omenu.add_link)

    def on_add_dir_view(self, widget=None):
        print('will add pipe menu with dir listening')

    def on_label_changed(self, widget):
        self.perform_on_selection(self.omenu.set_label, widget.get_text())

    def on_execution_changed(self, widget):
        self.perform_on_selection(self.omenu.set_execute, widget.get_text())

    def on_action_changed(self, widget):
        combo_iter = widget.get_active_iter()
        if combo_iter is not None:
            self.perform_on_selection(self.omenu.set_action, widget.get_model()[combo_iter][0])

    def on_id_changed(self, widget):
        self.perform_on_selection(self.omenu.set_id, widget.get_text())

    def omenu_changed(self, changed):
        if changed == True:
            title = self.get_title()
            if not title.endswith(" (*)"):
                self.set_title(title + " (*)")
        else:
            title = self.get_title()
            if title.endswith(" (*)"):
                self.set_title(title[:-(len(" (*)"))])


class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="org.openbox.obmenu2",
            flags=Gio.ApplicationFlags.NON_UNIQUE | Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
            **kwargs
        )
        self.window = None

        self.add_main_option(
            "file",
            ord("f"),
            GLib.OptionFlags.NONE,
            GLib.OptionArg.STRING,
            "Open given file",
            None,
        )
        self.passed_filename = None

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

        builder = Gtk.Builder.new_from_string(MENU_XML, -1)
        self.set_menubar(builder.get_object("menubar"))

        signals = [['quit', self.on_quit],
                   ['about', self.on_about],
                   ['new_menu', self.on_action_newmenu],
                   ['open_menu', self.on_action_openmenu],
                   ['save_menu', self.on_action_savemenu],
                   ['saveas_menu', self.on_action_saveasmenu],
                   ['reconfigure', self.on_action_reconfigure],
                   ['move_item_up', self.on_action_moveitemup],
                   ['move_item_down', self.on_action_moveitemdown],
                   ['delete_item', self.on_action_deleteitem],
                   ['add_item_menu', self.on_action_addmenu],
                   ['add_item_item', self.on_action_additem],
                   ['add_item_execute', self.on_action_addexecute],
                   ['add_item_separator', self.on_action_addseparator],
                   ['add_item_pipemenu', self.on_action_addpipemenu],
                   ['add_item_link', self.on_action_addlink]]
        for signal in signals:
            self.add_simple_action(signal[0], signal[1])

        builder.connect_signals(self)

    def on_action_newmenu(self, action, user_data):
        self.window.on_new_menu()

    def on_action_openmenu(self, action, user_data):
        self.window.on_open_menu()

    def on_action_savemenu(self, action, user_data):
        self.window.on_save_menu()

    def on_action_saveasmenu(self, action, user_data):
        self.window.on_save_as_menu()

    def on_action_reconfigure(self, action, user_data):
        reconfigure_openbox()

    def on_action_moveitemup(self, action, user_data):
        self.window.on_move_item_up()

    def on_action_moveitemdown(self, action, user_data):
        self.window.on_move_item_down()

    def on_action_deleteitem(self, action, user_data):
        self.window.on_delete_item()

    def on_action_addmenu(self, action, user_data):
        self.window.on_add_menu()

    def on_action_additem(self, action, user_data):
        self.window.on_add_item()

    def on_action_addexecute(self, action, user_data):
        self.window.on_add_execute()

    def on_action_addseparator(self, action, user_data):
        self.window.on_add_separator()

    def on_action_addpipemenu(self, action, user_data):
        self.window.on_add_pipemenu()

    def on_action_addlink(self, action, user_data):
        self.window.on_add_link()

    def on_action_adddir(self, action, user_data):
        self.window.on_add_dir_view()

    def add_simple_action(self, name, callback):
        action = Gio.SimpleAction.new(name, None)
        action.connect('activate', callback)
        self.add_action(action)

    def do_activate(self):
        # We only allow a single window and raise any existing ones
        if not self.window:
            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = Obmenu2Window(
                passed_filename=self.passed_filename,
                application=self,
                title="obmenu2")

        self.window.present()

    def do_command_line(self, command_line):
        options = command_line.get_options_dict()
        # convert GVariantDict -> GVariant -> dict
        options = options.end().unpack()

        self.passed_filename = None
        if "file" in options:
            # This is printed on the main instance
            if Path(options["file"]).is_file():
                self.passed_filename = options["file"]

        self.activate()
        return 0

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog(
            parent=self.window,
            transient_for=self.window,
            modal=True)
        about_dialog.set_program_name("obmenu2")
        about_dialog.set_version("1.1")
        about_dialog.set_copyright("Christian Kranz")
        about_dialog.set_authors(["Christian Kranz"])
        about_dialog.set_website("https://github.com/0x10/obmenu2")
        about_dialog.set_license_type(Gtk.License.MIT_X11)
        about_dialog.run()
        about_dialog.destroy()

    def on_quit(self, action, param):
        if self.window.do_delete_event(None) != True:
            self.quit()


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