#!/usr/bin/python
# -*- coding: utf-8 -*-

##
#  ADesk Menu
#   - v0.2 : add signal , get cursor position and show menu whith it
#   - v0.1 : check if 'Terminal=true' in .desktop file, always above other windows
#
#   by ADcomp <david.madbox@gmail.com>
#      http://www.ad-comp.be/
#
#   This program is distributed under the terms of the GNU General Public License
#   For more info see http://www.gnu.org/licenses/gpl.txt
#####################################################################################
#
#   7/25/11
#
#   This script was edited from the original by sliphot and MoD from silver irc.
#   I thhought it might come in handy with tint2s's new launcher feature.
#   We re-worked it so it will open the menu in a designated area of the screen when the script is executed. 
#   by default it opens in the top left corner of the screen. To adjust go to line 369 and adjust x= and y=
#   Make this script executable and move it to /usr/bin after that make a .desktop file pointing to it.
#   Point tint2 to the .desktop file and presto! tint2 desktop menu. It can be a little slow to load.
#   It helps to run it once in a terminal with the -s option. it saves a config file of all your .desktop files.
#   If you install a new app after you have ran the -s option you will have to run it again to update the config.
#
#####################################################################################


MENU_WIDTH = 380

import os
import sys
import gtk
import signal

cat_name = {'Стандартные':('Utility','Accessories'),
            'Программирование':('Development'),
            'Инженерные':(),
            'Игры':('Game'),
            'Графика':('Graphics'),
            'Интернет':('Internet', 'Network'),
            'Мультимедиа':('AudioVideo', 'Audio', 'Video', 'Multimedia'),
            'офис':('Office'),
            'прочее':('Other'),
            'научные':(),
            'Система':('System', 'Administration'),
            'Настройки':('Settings', 'X-XFCE'),
            'Утилиты':()
            }

def exec_cmd(cmd=None):
    if cmd and not cmd=='':
        os.system('%s &' % cmd)

# Find the Name / Command / Icon from .desktop file
def info_desktop(file):
    cmd, icon, name, category = None, None, None, None
    terminal = False
    try:
        cfile = open(file,"r")

        for line in cfile:
            if 'NoDisplay=true' in line or 'OnlyShowIn=KDE' in line:
                icon, category = None, None
                break
            elif 'Terminal=true' in line:
                terminal = True
            elif '=' in line:
                words = line.split('=')
                if words[0] == 'Name[ru]':
                    name = words[1].replace('\n','')
                if words[0] == 'Name':
                    name = words[1].replace('\n','')
                    # print 'Name =', name
                elif words[0] == 'Icon':
                    icon = words[1].replace('\n','')
                    # print 'Icon =', icon
                elif words[0] == 'Exec':
                    cmd = words[1].replace('\n','')
                    cmd = cmd.split('%')[0]
                    # print 'Exec :', cmd
                elif words[0] == 'Categories':
                    tab = words[1].replace('\n','')
                    tab = tab.split(';')
                    #~ category = '***************************'

                    #~ if not 'X-XFCE' in tab:

                    for cat in tab:
                        found = False
                        for c_name in cat_name:
                            if cat in cat_name[c_name]:
                                category = c_name
                                found = True
                                break
                        if found:
                            break

        # if command is 'console only', launch it with terminal ..
        if terminal:
            cmd = "x-terminal-emulator -e %s" % cmd

        cfile.close()

    except:
        #~ print("# Error : parsing %s" % file)
        pass

    return (cmd, icon, name, category)

# Find the best icon with highest resolution for the launcher
def find_icon(icon):
    foundiconfile=None

    if icon == '':
        return foundiconfile

    if icon[0] == '/':
        return icon

    iconbase=('','Madbox','gnome','hicolor','locolor')
    iconpath='/usr/share/icons'
    sizelist =('', 'scalable', '256x256', '128x128', '64x64', '48x48', '32x32', '24x24')
    categorylist=('actions', 'apps',"devices", 'categories','filesystems', 'places', 'status', '')
    extensionlist = ('png', 'svg', 'xpm')

    iconimagelist=[]

    for extension in extensionlist:
        if (icon.find('.'+extension) != -1):
            icon = icon.replace('.'+extension,'')

    for size in sizelist:
        for extension in extensionlist:
            for category in categorylist:
                for iconbasecat in iconbase:
                    iconfile = iconpath+"/"+iconbasecat+'/'+size+'/'+category+'/'+icon+'.'+extension
                    iconimagelist.append(iconfile)

    for extension in extensionlist:
        iconfile = '/usr/share/pixmaps/'+icon+'.'+extension
        iconimagelist.append(iconfile)

    for extension in extensionlist:
        iconfile = '/usr/share/app-install/icons/'+icon+'.'+extension
        iconimagelist.append(iconfile)

    # Seek if the files in pre-generated list exists.. first match is the best
    # return it
    for iconimage in iconimagelist:
        if os.path.exists(iconimage):
            return iconimage

    return foundiconfile

def parse_desktop_dir(check_config=True):
    config = {}
    cfg_file = home = os.getenv('HOME') + '/.config/adesk-menu/config'

    if os.access(cfg_file, os.F_OK|os.R_OK) and check_config:
        print('found user config ..')
        f = open(cfg_file,'r')

        for line in f:
            if line == '\n' or line.startswith('#'):
                continue
            elif line.startswith('@'):
                cat = line[1:]
                cat = cat.strip('\n')
                cat = cat.strip(' ')
                config[cat] = []
                cat_index = cat
            else:
                line = line.strip('\n')
                line = line.strip(' ')
                config[cat_index].append(line.split('##'))
        f.close()

    else:
        print('scan /usr/share/applications and parse .desktop file ..')

        APP_PATH = '/usr/share/applications/'
        listdir = os.listdir(APP_PATH)

        for i in listdir:
            if '.desktop' in i:
                #~ print i
                (cmd, icon, name, category) = info_desktop(APP_PATH + i)
                #~ print cmd, icon, name, category
                if category:
                    if not config.has_key(category):
                        #~ print "@ add :", category
                        config[category] = []
                    if icon:
                        icon_path = find_icon(icon)
                        config[category].append((cmd, icon_path, name))
                    else:
                        pass
                        #~ print "File =", i
                        #~ print '=> NO ICON ...'
                else:
                    pass
                    #~ print "File =", i
                    #~ print '=> NO CATEGORY ...'
    #~ print config
    return config

def write_config():
    home = os.environ['HOME']
    cfg_file = "%s/.config/adesk-menu/config" % home

    if not os.path.exists("%s/.config/adesk-menu" % home):
        os.makedirs("%s/.config/adesk-menu" % home)

    f = open(cfg_file,'w')
    m = parse_desktop_dir(False)
    m_tmp = []

    for cat in m:
        m_tmp.append(cat)
    m_tmp.sort()

    for cat in m_tmp:
        f.write('@%s\n' % cat)
        for item in m[cat]:
            (cmd, icon, text) = item
            f.write('%s##%s##%s\n' % item)
    f.close()
    print "menu saved to %s" % cfg_file

class Menu():
    def __init__(self):
        self.hide_me = False
        self.focus_check = False
        self.mode = None

        self.create_window()

        # Menu
        self.popupMenu = gtk.Menu()
        menuPopup = gtk.ImageMenuItem (gtk.STOCK_QUIT)
        menuPopup.connect("activate", self.doquit)
        self.popupMenu.add(menuPopup)
        self.popupMenu.show_all()

    def doquit(self, widget=None, data=None):
        gtk.main_quit()

    def run(self):
        gtk.main()

    def create_window(self):
        self.window = gtk.Window() #gtk.WINDOW_POPUP) ## gtk.WINDOW_TOPLEVEL)
        self.window.connect("destroy", self.doquit)
        #~ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_UTILITY)
        self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
        self.window.add_events(gtk.gdk.FOCUS_CHANGE_MASK)
        self.window.connect("focus-out-event", self.lost_focus)
        self.window.connect("key-press-event", self.onkeypress)
        self.window.stick()
        self.window.set_decorated(False)
        self.window.set_keep_below(False)
        self.window.set_keep_above(True)
        self.window.set_resizable(True)
        self.window.set_border_width(2)
        self.window.set_app_paintable(True)

        self.frame = gtk.Frame()
        self.frame.show()
        self.frame.set_size_request(MENU_WIDTH,-1)

        self.nbook = gtk.Notebook()
        self.nbook.show()
        self.nbook.set_tab_pos(gtk.POS_LEFT)
        self.nbook.set_border_width(2)

        self.frame.add(self.nbook)
        self.window.add(self.frame)

        m = parse_desktop_dir()
        m_tmp = []

        for cat in m:
            m_tmp.append(cat)
        m_tmp.sort()

        self.cat_but = []
        self.cur_pos = 0
        self.cat_visible = True

        for cat in m_tmp:
            bBox = gtk.VBox()
            bBox.show()
            bBox.set_spacing(4)
            bBox.set_border_width(2)

            scrolled = gtk.ScrolledWindow()
            scrolled.show()
            scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
            scrolled.add_with_viewport(bBox)

            label = gtk.Label(cat[0].upper() + cat[1:])
            label.show()
            label.set_alignment(0, 1)
            self.nbook.append_page(scrolled, label)

            for item in m[cat]:
                (cmd, icon, text) = item

                widget = gtk.Button() # item.label)
                widget.set_relief(gtk.RELIEF_NONE)
                widget.set_border_width(0)
                widget.set_focus_on_click(False)
                #~ widget.set_property('can-focus', False)

                image = gtk.Image()
                image.show()
                image.set_size_request(24,24)
                label = gtk.Label(text)
                label.show()
                label.set_alignment(0.2, 1)
                align = gtk.Alignment(1, 0, 0.5, 0.5)
                align.show()
                align.add(label)
                box = gtk.HBox(False, 4)
                box.show()
                box.pack_start(image, False, False)
                box.pack_start(align, False, False)
                widget.add(box)

                try:
                    pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon, 24, 24)
                except:
                    pixbuf = gtk.gdk.pixbuf_new_from_file_at_size('/usr/share/pixmaps/debian-logo.png', 24, 24)
                image.set_from_pixbuf(pixbuf)
                image.show()

                widget.connect("button-release-event", self.ExecuteAction, cmd)
                bBox.pack_start(widget,False,False)
                widget.show()

    def ExecuteAction(self, widget, event, cmd):
        self.focus_check = False
        exec_cmd(cmd)
        self.doquit()

    def toggle_hide(self, widget=None, event=None):
        if self.hide_me:
            self.hide_me = False
            self.show_menu(self.mode)
            self.window.show()
            self.focus_check = True
        else:
            self.hide_me = True
            self.focus_check = False
            self.window.hide()

    def show_menu(self, mode=None):
        
        screen_width, screen_height = gtk.gdk.screen_width(), gtk.gdk.screen_height()
        rootwin = self.window.get_screen().get_root_window()
        w_width , w_height = self.window.get_size()
        x, y, mods = rootwin.get_pointer()
        if x + MENU_WIDTH > screen_width:
            x = screen_width - MENU_WIDTH
        if y + w_height > screen_height:
            y = screen_height - w_height
        #to move the menu adjust the x= and y=    
        #this should not be commented out
#        self.window.move(x=10, y=40)
        self.window.move(x=10, y=screen_height)

    def lost_focus(self, widget, event):
        if self.focus_check:
            self.doquit()

    def onkeypress(self, widget, event):
        if event.keyval == gtk.keysyms.Escape:
            self.doquit()

    def status_icon_popup(self, widget, button, active_time):
        self.popupMenu.popup(None, None, None, 1, 0)

    def callback_signal(self):
        #~ print "callback_signal .."
        self.mode = "mouse"
        self.toggle_hide()
        self.mode = None

##--

global g_menu

def SigUSR1Handler(signum, frame):
    """ grab signal to reload config """
    # kill -10 <adesk-menu pid>
    global g_menu
    g_menu.callback_signal()
    return

def save_pid():
    home = os.environ['HOME']
    pid_file = "%s/.config/adesk-menu/pid" % home
    pid = os.getpid()
    print "PID = %s" % pid

    if not os.path.exists("%s/.config/adesk-menu" % home):
        os.makedirs("%s/.config/adesk-menu" % home)

    f = open(pid_file,'w')
    f.write('%s' % pid)
    f.close()
    print "PID saved to %s" % pid_file

if __name__ == "__main__":

    if len(sys.argv) == 2:
        if sys.argv[1] == '-s':
            write_config()
        elif sys.argv[1] == '-t':
            cmd = "kill -10 `cat ~/.config/adesk-menu/pid`"
            os.system(cmd)
        else:
            print "Unknow option .."
            print "Usage : adesk-menu [-s|-t]"
            print " -s : save menu list ( ~/.config/adesk-menu/config )"
            print " -t : show menu ( cursor position )"
    else:
        global g_menu
        signal.signal(signal.SIGUSR1, SigUSR1Handler)
        save_pid()

        ## need to change directory
        SRC_PATH = os.path.dirname(os.path.realpath( __file__ ))
        os.chdir(SRC_PATH)

        g_menu = Menu()
        # change directory to Home
        os.chdir(os.getenv('HOME'))
        g_menu.hide_me = False
        g_menu.show_menu(g_menu.mode)
        g_menu.window.show()
        g_menu.focus_check = True
        g_menu.run()

