#!/usr/bin/python3
import gi, os, re, jwmkit_utils
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf
import xml.etree.ElementTree as ET

# JWM Kit - A set of Graphical Apps to simplify use of JWM (Joe's Window Manager) <https://codeberg.org/JWMKit/JWM_Kit>
# Copyright © 2020-2022 Calvin Kent McNabb <apps.jwmkit@gmail.com>
#
# This file is part of JWM Kit.
#
# JWM Kit is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# JWM Kit 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 JWM Kit.  If not, see <https://www.gnu.org/licenses/>.


class MainWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="JWM Kit Icons Paths")
        self.set_border_width(15)
        self.home = os.path.expanduser('~')
        self.path = ''
        self.status_message = Gtk.Label()
        self.icon_paths = self.get_icon_paths()
        if self.icon_paths == 'warning_settings':
            jwmkit_utils.warning_settings(self)
            return
        self.results = []
        try:
            self.set_icon_from_file('/usr/share/pixmaps/jwmkit/icons.svg')
        except gi.repository.GLib.Error:
            self.set_icon_name("applications-graphics")
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        header_box = Gtk.Box(spacing=5)
        main_box.add(header_box)
        sub_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        self.image_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        self.add(main_box)
        main_box.add(sub_box)
        scrolledwindow = Gtk.ScrolledWindow()
        scrolledwindow.set_min_content_width(340)
        scrolledwindow.set_min_content_height(220)
        scrolledwindow2 = Gtk.ScrolledWindow()
        scrolledwindow2.set_min_content_width(340)
        scrolledwindow2.set_min_content_height(220)
        self.image_scrolledwindow = Gtk.ScrolledWindow()
        self.image_scrolledwindow.set_min_content_width(680)
        self.image_scrolledwindow.set_min_content_height(220)

        top_box = Gtk.Box(spacing=5)
        center_box = Gtk.Box(spacing=5)
        bottom_box = Gtk.Box(spacing=5)
        sub_box.add(top_box)
        sub_box.pack_start(scrolledwindow, True, True, 0)
        sub_box.pack_start(scrolledwindow2, True, True, 0)
        self.image_box.add(self.image_scrolledwindow)
        header_label = Gtk.Label()
        header_label.set_markup("<big><b>   Icon Paths for JWM</b></big>")
        search_label = Gtk.Label(label='Icon Search:')
        self.search_entry = Gtk.Entry()
        self.search_entry.set_property("width-request", 250)
        search_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_FIND))
        search_button.set_property("width-request", 40)
        header_box.pack_start(header_label, False, False, 0)
        self.search_image = Gtk.Image()
        header_box.pack_start(self.search_image, True, True, 0)

        about_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_ABOUT))
        about_button.set_always_show_image(True)
        about_button.set_property("width-request", 40)
        about_button.connect('clicked', jwmkit_utils.get_about, self)
        header_box.pack_end(about_button, False, False, 0)
        header_box.pack_end(search_button, False, False, 0)
        header_box.pack_end(self.search_entry, False, False, 0)
        header_box.pack_end(search_label, False, False, 0)
        self.listbox = Gtk.ListBox()
        self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.listbox2 = Gtk.ListBox()
        self.listbox2.set_selection_mode(Gtk.SelectionMode.SINGLE)
        scrolledwindow.add(self.listbox)
        scrolledwindow2.add(self.listbox2)
        self.image_box.set_no_show_all(True)
        self.image_box.set_visible(False)
        main_box.add(center_box)
        main_box.add(bottom_box)
        main_box.add(self.image_box)

        for path in self.icon_paths:
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
            hbox.add(vbox)
            self.listbox.add(hbox)
            path_label = Gtk.Label(label=path)
            hbox.add(path_label)

        self.up_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_GO_UP))
        self.down_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_GO_DOWN))
        self.add_button = Gtk.Button(label="Add", image=Gtk.Image(stock=Gtk.STOCK_ADD))
        self.insert_button = Gtk.Button(label="", image=Gtk.Image(stock=Gtk.STOCK_GO_BACK))
        self.view_button = Gtk.Button(label="View")
        self.remove_button = Gtk.Button(label="Remove", image=Gtk.Image(stock=Gtk.STOCK_DELETE))
        self.save_button = Gtk.Button(label="Save", image=Gtk.Image(stock=Gtk.STOCK_SAVE))
        self.close_button = Gtk.Button(label="Close", image=Gtk.Image(stock=Gtk.STOCK_CANCEL))
        self.browse_button = Gtk.Button(label="Browse", image=Gtk.Image(stock=Gtk.STOCK_FIND))
        self.path_entry = Gtk.Entry()

        self.up_button.set_sensitive(False)
        self.down_button.set_sensitive(False)
        self.view_button.set_sensitive(False)
        self.insert_button.set_sensitive(False)
        self.path_entry.set_sensitive(False)
        self.browse_button.set_sensitive(False)
        self.remove_button.set_sensitive(False)

        search_button.set_always_show_image(True)
        self.up_button.set_always_show_image(True)
        self.down_button.set_always_show_image(True)
        self.add_button.set_always_show_image(True)
        self.insert_button.set_always_show_image(True)
        self.view_button.set_always_show_image(True)
        self.remove_button.set_always_show_image(True)
        self.save_button.set_always_show_image(True)
        self.close_button.set_always_show_image(True)
        self.browse_button.set_always_show_image(True)

        search_button.set_tooltip_text('Search & find an icon')
        self.search_entry.set_tooltip_text('Search & find an icon\n* wildcards supported')
        self.up_button.set_tooltip_text('Move selected icon path up (higher priority)')
        self.down_button.set_tooltip_text('Move selected icon path down (lower priority)')
        self.add_button.set_tooltip_text('Add a new icon path')
        self.insert_button.set_tooltip_text('Add path of the selected search result to the list')
        self.remove_button.set_tooltip_text('Remove the selected icon path\n2nd click to confirm')
        self.save_button.set_tooltip_text('Saving will overwrite the existing file')
        self.browse_button.set_tooltip_text('Browse the file system to choose an icon path')
        self.view_button.set_tooltip_text('View all icons from the path of the selected search result'
                                          '\nnote: may take a few seconds for paths with lots of icons')
        self.listbox.set_tooltip_text("JWM's Icon Paths")
        self.listbox2.set_tooltip_text('Search Results')
        self.up_button.set_property("width-request", 36)
        self.down_button.set_property("width-request", 36)
        self.add_button.set_property("width-request", 90)
        self.insert_button.set_property("width-request", 45)
        self.view_button.set_property("width-request", 65)
        self.remove_button.set_property("width-request", 90)
        self.save_button.set_property("width-request", 90)
        self.close_button.set_property("width-request", 115)
        self.browse_button.set_property("width-request", 90)
        self.path_entry.set_property("width-request", 300)

        center_box.add(self.up_button)
        center_box.add(self.add_button)
        center_box.pack_start(self.path_entry, True, True, 0)
        bottom_box.pack_start(self.down_button, False, False, 0)
        bottom_box.pack_start(self.remove_button, False, False, 0)
        bottom_box.pack_start(self.status_message, True, False, 0)
        bottom_box.pack_end(self.close_button, False, False, 0)
        bottom_box.pack_end(self.save_button, False, False, 0)
        center_box.pack_end(self.view_button, False, False, 0)
        center_box.pack_end(self.insert_button, False, False, 0)
        center_box.pack_end(self.browse_button, False, False, 0)

        self.listbox.connect("row-selected", self.on_listbox_change)
        self.listbox2.connect("row-selected", self.search_listbox_change)
        self.up_button.connect("clicked", self.move_up_action)
        self.down_button.connect("clicked", self.move_down_action)
        self.close_button.connect("clicked", Gtk.main_quit)
        self.add_button.connect("clicked", self.add_command, '')
        self.remove_button.connect("clicked", self.remove_command)
        self.save_button.connect("clicked", self.save_command)
        self.browse_button.connect("clicked", self.on_browse)
        self.insert_button.connect("clicked", self.add_command, '2')
        self.view_button.connect("clicked", self.image_preview)
        search_button.connect("clicked", self.on_search)
        self.search_entry.connect("activate", self.on_search)
        self.path_entry.connect("changed", self.on_entry_change)

    def image_preview(self, widget):
        self.confirm_cleanup()
        i = self.listbox2.get_selected_rows()[0].get_index()
        image_path = re.findall('(.+/)', self.results[i])[0]
        self.image_box.set_visible(True)
        folder_list = os.listdir(image_path)
        for item in self.image_scrolledwindow: self.image_scrolledwindow.remove(item)
        icon_list = []
        for icon in folder_list:
            image_types = ('jpg', 'png', 'gif', 'jpeg', 'xpm', 'svg')
            for im_type in image_types:
                if icon.lower().endswith(im_type):
                    icon_list.append(icon)
        image_hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        image_vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox3 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox4 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox5 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox6 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox7 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox8 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox9 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox10 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox11 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox12 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox13 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox14 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox15 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox16 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox17 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox18 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox19 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox20 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox21 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox22 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        image_vbox23 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)

        boxes = (image_vbox1, image_vbox2, image_vbox3, image_vbox4, image_vbox5, image_vbox6, image_vbox7,
                 image_vbox8, image_vbox9, image_vbox10, image_vbox11, image_vbox12, image_vbox13, image_vbox14,
                 image_vbox15, image_vbox16, image_vbox17, image_vbox18, image_vbox19, image_vbox20, image_vbox21,
                 image_vbox22, image_vbox23)

        count = 0
        for icon in icon_list:
            try:
                if count > 22:
                    count = 0
                image = Gtk.Image()
                pix = GdkPixbuf.Pixbuf.new_from_file_at_scale(image_path + icon, 24, 24, preserve_aspect_ratio=True)
                image.set_from_pixbuf(pix)
                boxes[count].add(image)
                count += 1
            except gi.repository.GLib.Error:
                print('unable to process image')
        for box in boxes:
            image_hbox.add(box)

        self.image_scrolledwindow.add(image_hbox)
        self.image_scrolledwindow.show_all()

    def on_search(self, widget):
        self.confirm_cleanup()
        self.results = self.find_icon_paths(self.search_entry.get_text())
        if len(self.results) > 0:
            self.insert_button.set_sensitive(True)
            self.view_button.set_sensitive(True)
        else:
            self.view_button.set_sensitive(False)
        # clear listbox2
        for result in self.listbox2: self.listbox2.remove(result)
        # populate listbox2 with search results
        for result in self.results:
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            hbox.add(vbox)
            self.listbox2.add(hbox)
            result_label = Gtk.Label(label=result, xalign=0)
            hbox.add(result_label)
            self.listbox2.show_all()

    def on_entry_change(self, widget):
        self.confirm_cleanup()
        i = self.listbox.get_selected_rows()[0].get_index()
        if self.icon_paths[i] != self.path_entry.get_text():
            self.icon_paths[i] = self.path_entry.get_text()
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            hbox.add(vbox)
            path_label = Gtk.Label(label=self.icon_paths[i], xalign=0)
            hbox.add(path_label)
            self.listbox.remove(self.listbox.get_selected_rows()[0])
            self.listbox.insert(hbox, i)
            self.listbox.select_row(self.listbox.get_row_at_index(i))
            self.listbox.show_all()

    def save_command(self, button):
        self.confirm_cleanup()
        jwm_data = '<?xml version="1.0"?>\n<JWM>\n'
        count = 0
        width = len(self.home)
        for data in self.icon_paths:
            if data not in [None, '']:
                if data.startswith('$HOME'):
                    data = '{}{}'.format(self.home, data[5:])
                if not os.path.isdir(data):
                    count += 1
                    self.status_message.set_markup\
                        ('<big>Save aborted: ' + str(count) + ' nonexistent path(s)</big>')
                else:
                    if data.startswith(self.home):
                        data = '$HOME' + data[width:]
                    jwm_data += '   <IconPath>' + data + '</IconPath>\n'

        if count == 0:
            jwm_data += '</JWM>\n'
            # write the the JWM XML Icon file. File will be created if it does not exist
            # path to write to is not defined!
            with open(self.path, "w+") as f:
                f.write(jwm_data)
            os.system('jwm -restart')
            self.status_message.set_markup('<big>Saved</big>')

    def move_up_action(self, widget):
        self.confirm_cleanup()
        i = self.listbox.get_selected_rows()[0].get_index()
        if i != 0:
            ii = i - 1
            selected_row = self.listbox.get_row_at_index(i)
            self.listbox.remove(self.listbox.get_row_at_index(i))
            self.listbox.insert(selected_row, ii)
            self.icon_paths[i], self.icon_paths[ii] = self.icon_paths[ii], self.icon_paths[i]

    def move_down_action(self, widget):
        self.confirm_cleanup()
        i = self.listbox.get_selected_rows()[0].get_index()
        if i is not len(self.icon_paths) - 1:
            ii = i + 1
            selected_row = self.listbox.get_row_at_index(i)
            self.listbox.remove(self.listbox.get_row_at_index(i))
            self.listbox.insert(selected_row, ii)
            self.icon_paths[i], self.icon_paths[ii] = self.icon_paths[ii], self.icon_paths[i]

    def on_browse(self, button):
        self.confirm_cleanup()
        file_dialog = Gtk.FileChooserDialog(title="Choose a directory",
                                            parent=self, action=Gtk.FileChooserAction.SELECT_FOLDER)
        file_dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
        file_dialog.set_show_hidden(False)
        file_dialog.set_current_folder('/usr/share/icons')
        file_selected = file_dialog.run()
        if file_selected == Gtk.ResponseType.OK:
            file_selected = file_dialog.get_filename()
            if file_selected not in [None, '']:
                self.path_entry.set_text(file_selected)
                self.on_entry_change(self.path_entry)
        file_dialog.destroy()

    def add_command(self, widget, listbox_id):
        # if listbox_id is 1 create an empty entry.
        # if listbox_id is 2 create entry using the selected search result
        if self.add_button.get_label() != 'Add':
            self.confirm_cleanup()
        else:
            if listbox_id == "2":
                result = self.results[self.listbox2.get_selected_rows()[0].get_index()]
                result = re.findall('(.+/)', result)[0]
            else:
                result = ''
            if result not in self.icon_paths:
                if result[:-1] not in self.icon_paths:
                    self.icon_paths.append(result)
                    hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
                    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
                    hbox.add(vbox)
                    self.listbox.add(hbox)
                    path_label = Gtk.Label(label=result, xalign=0)
                    hbox.add(path_label)
                    self.listbox.show_all()
                    self.listbox.select_row(self.listbox.get_row_at_index(len(self.icon_paths)-1))
                    self.on_entry_change(self.path_entry)
                else:
                    self.status_message.set_markup("<big>Path is already in the list</big>")
            else:
                self.status_message.set_markup("<big>Path is already in the list</big>")

    def remove_command(self, widget):
        i = self.listbox.get_selected_rows()[0].get_index()-1
        # 1st click gives status message
        # 2nd click confirms deletion.
        if self.remove_button.get_label() == "Remove":
            self.status_message.set_markup('<big>confirm delete?</big>')
            self.remove_button.set_label('OK')
            self.add_button.set_label(label="Cancel")
        else:
            if i < 0: i = 1
            i = self.listbox.get_row_at_index(i)
            del self.icon_paths[self.listbox.get_selected_rows()[0].get_index()]
            self.listbox.remove(self.listbox.get_selected_rows()[0])
            self.listbox.select_row(i)
            self.remove_button.set_label('Remove')
            self.add_button.set_label(label="Add")

    def on_listbox_change(self, listbox, row):
        self.browse_button.set_sensitive(True)
        self.path_entry.set_sensitive(True)
        self.remove_button.set_sensitive(True)
        self.up_button.set_sensitive(True)
        self.down_button.set_sensitive(True)
        self.confirm_cleanup()
        try:
            i = row.get_index()
            self.path_entry.set_text(self.icon_paths[i])
        except (IndexError, AttributeError):
            # data already updated
            pass

    def search_listbox_change(self, listbox, row):
        self.confirm_cleanup()
        try:
            pix = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.results[row.get_index()], 32, 32, preserve_aspect_ratio=True)
            self.search_image.set_from_pixbuf(pix)
        except AttributeError:
            self.search_image.clear()

    def find_icon_paths(self, icon_name):
        self.confirm_cleanup()
        # find and return the paths of an icon. creates a list of all results
        # wild cards are supported so a search for *fox* will return paths for:
        # firefox, mozilla-firefox, waterfox, foxitreader,
        # I'm using Linux's "find" command for this function.
        # it is way faster than anything I was able to do in python.
        if icon_name not in ['', '*']:
            icon_name = icon_name.replace(' ', '\ ')
            icons = os.popen('find -L /usr/share/icons -type f -iname ' + icon_name + '.png -o -iname ' + icon_name +
                             '.svg -o -iname ' + icon_name + '.xpm').read().split('\n')
            for i in os.popen('find -L /usr/share/pixmaps -type f -iname ' + icon_name + '.png -o -iname ' + icon_name +
                              '.svg -o -iname ' + icon_name + '.xpm').read().split('\n'): icons.append(i)
            while '' in icons: icons.remove('')
        else:
            icons = []
            self.view_button.set_sensitive(False)
            self.insert_button.set_sensitive(False)
        return icons

    def confirm_cleanup(self):
        self.status_message.set_markup('')
        self.remove_button.set_label('Remove')
        self.add_button.set_label(label="Add")

    def get_icon_paths(self):
        # read settings file to find user defined icons file
        home = os.path.expanduser('~')
        settings = home + '/.config/jwmkit/settings'
        path_ok = False
        if os.path.isfile(settings):
            with open(settings) as f:
                f = f.read()
            f = '\n{}'.format(f)
            try:
                icon_path = re.findall('\nicons.*=(.+)', f)[0]
                if icon_path.startswith('$HOME'):
                    icon_path = home + icon_path[5:]
                path_ok = True
            except IndexError:
                pass
        if not path_ok:
            return 'warning_settings'

        # make a list of icons paths defined in the JWM xml icon file
        self.path = icon_path
        try:
            tree = ET.parse(icon_path)
            root = tree.getroot()
            icon_path = []
            for node in root:
                node = node.text
                if node.startswith('$HOME'):
                    node = home + node[5:]
                # if the file exist add it to the list
                if os.path.isdir(node):
                    icon_path.append(node)
        except (ET.ParseError, FileNotFoundError):
            self.status_message.set_markup \
                ('<b>Icons config is missing or corrupt</b>\n Save function will create a new file')
            icon_path = []
        return icon_path


window = MainWindow()
window.connect("delete-event", Gtk.main_quit)
window.set_position(Gtk.WindowPosition.CENTER)
window.set_resizable(False)
window.show_all()
Gtk.main()
