#!/usr/bin/perl
#
# Copyright 2006, 2007 by Manuel Carrasco 
#
# This file is part of the LookXP project, and may only be used, modified,
# and distributed under the terms of the LookXP project license,
# LICENSE.TXT.  By continuing to use, modify, or distribute this file you
# indicate that you have read the license and understand and accept it
# fully.
#
#             http://lxp.sourceforge.net
#
# This program generate a icewm menu using:
# xml files: /etc/xdg/menus 
# mnu files: /usr/share/desktop-directories
# app files: /usr/share/applications/* and /usr/app-install/desktop
# ico files: /usr/share/pixmaps and /usr/share/icons
# if there aren't xml files this script has a hardcoded menu (from ubuntu)
#
# Usage lxp-menu-generator --help to view options availables
#
our $VERSION="0.2-1b";

use strict;

#####################################################################
package ICE::DVars;
use File::Find;
use File::Basename;
use Data::Dumper;

our @DIRS_ICONS=('/usr/share/pixmaps', '/usr/share/icons', '/usr/share/app-install/icons');
our $DIR_MENU = "/etc/xdg/menus";
our $DIR_DIRS = "/usr/share/desktop-directories/";
our @DIRS_APPS = ('/usr/share/applications', '/usr/app-install/desktop');
our @DIRS_APPS_KDE = ('/opt/kde3/share/apps', '/usr/share/apps'. '/usr/share/applications/kde');
our $APP_FILE = "$DIR_MENU/applications.menu";
our $VALID_TAGS = "(CATEGORY)|(FILENAME)|(DIRECTORY)|(NAME)|(TYPE)|(MERGEFILE)";
our $DBG = 0;

our $comments = 0;
our $iconsize=32;
our %filesdone;
our %appsdone;
our %appsgksu;
our %iconfiles;
our %execfiles;

sub indent {
   my ($cont) = @_;
   my $ret = '';
   for (my $i=0; $i<$cont; $i ++) {
      $ret .= " ";
   }
   return $ret;
}
## find the correct command line for the application:
#  remove %x parameters
#  also remember if the application needs gksu
sub getAppExec {
    my ($ap) = @_;
    return if (!$ap);
    return if ($ap->{NODISPLAY} && $ap->{NODISPLAY} =~ /true/i);
    my $exec = $ap->{EXEC};
    $exec =~ s/^\s+//g;
    #return if ($exec !~ /kmplayer/);
    return if (!$exec || $exec eq '');
    # %u , %f , %c caption,  --icon %i, --miniicon %m
    $exec =~ s/\"(\%[Uufim])\"/$1/g;
    $exec =~ s/[\s]*\%[Uufim][\s]*/ /g;
    #
    my ($gksu, $cmd, $args);
    if ($exec =~ /^[^\s]+gksu\s+([^\s]+)(.*)$/) {
	$gksu=1; $cmd=$1; $args=$2;
    } elsif ($exec =~ /^([^\s]+)(.*)$/){
	$gksu=0; $cmd=$1; $args=$2;
    }
    my $bexec = basename($cmd);
    return if ($bexec eq '');

    $execfiles{$bexec}=$cmd if (!$execfiles{$bexec} && -x $cmd );

    return if (!$execfiles{$bexec});

    if ($gksu || $appsgksu{$bexec} ) {
	    $exec = $execfiles{gksu} . " " . $execfiles{$bexec};
	    $appsgksu{$bexec} = 1;
    } else {
	    $exec = $execfiles{$bexec};
    }
    if ($args && $args ne '') {
            $args =~ s/\%c/$bexec/c;
	    $exec .= " " . $args;
    }
    return ($exec, $bexec);
}
## look for files that match $expr into all folders an subfolders of @dirs
sub findFiles {
    my ($expr, @dirs) = @_;
    my @files;
    find (
	    sub {
		    my $str = 'push @files, "$File::Find::dir/$_" if ('.$expr.');';
		    eval $str;
	    }, @dirs
    );
    return @files;
}
## find all images and cache it in a hash table %iconfiles
sub LoadIconFiles {
    if ($comments) {
        print STDERR "Looking for icons in: " . join(" ", @DIRS_ICONS) . "\n";
    }
    find(
	sub { 
	    if (/.png$|.xpm$/) {
		my $f = $_;
		my $d = $File::Find::dir;
		my $n = basename($f, ".png", ".xpm");
		# if the image is under a directory name 16x16, 32x32 etc.
		if ($d =~ /\/(\d\dx\d\d)\//) {
		   $iconfiles{$n}{"dir$1"}=$d;
		   $iconfiles{$n}{dir}=$d if (!defined $iconfiles{$n}{dir});
		} else {
		   $iconfiles{$n}{dir}=$d;
		}
		$iconfiles{$n}{name}=$f;
	    }
	 } , 
	 @DIRS_ICONS);
}
## look for a image in the hash table %iconfiles
sub findIcon {
    my ($name, $size) = @_;
    return if (!$name || $name eq '');
    return ($name) if (-f $name);

    my $n = basename($name, ".png", ".xpm");
    $size = "$iconsize" if (!$size);
    my $dir = "dir";
    $dir = "dir${size}x${size}" if (defined $iconfiles{$n}{"dir${size}x${size}"});

    my $file = $iconfiles{$n}{$dir} . "/" . $iconfiles{$n}{name};
    return (!-d $file && -f $file) ? $file : "-";
}
## find all executable files in PATH directories and chache them
sub LoadExecFiles {
    if ($comments) {
        print STDERR "Looking for binaries in: " . $ENV{PATH} . "\n";
    }
    my @path_dirs = split(":", $ENV{PATH});
    find (
	    sub {
		my $n = basename($_);
		my $f = "$File::Find::dir/$_";
		$execfiles{$n}="$f" if (-x $f);
	    }, split(":", $ENV{PATH})
    );
}
sub findExec {
    my ($n) = @_;
    return $execfiles{$n};
}

#####################################################################
package ICE::DApp;
sub new {
    bless { NAME      => undef,
            CATEGORY  => undef,
            DESCRIPTION  => undef,
          }, shift;
}

#####################################################################
package ICE::DMenu;
#use Data::Dumper;
sub new {
    bless { NAME   => undef,
            TYPE   => undef,
            ELEMENTS => [],
            #PARENT => undef,
            #FILE   => {} 
          }, shift;
}

## return a string representing the $apps menu
sub renderMenu {

   my ($self, $apps, $cont, $showRoot, $uniq, $showicons, $hideSub, $maxitems) = @_;

   my $ret = '';
   return($ret) if (!$apps);

   $ret .= ICE::DVars::indent($cont)
        ."# renderMenu -> CONT:$cont ROOT:$showRoot UNIQ:$uniq ICONS:$showicons HIDESUBM:$hideSub MAXITEMS:$maxitems\n" if ($comments);

   my $kids = 0;
   my @elements = @{$self->{ELEMENTS}};
   $kids += $#elements + 1;

   my @apps     = $apps->getElements($self, $cont+1);
   my @files    = split(/ +/, $self->{FILENAME});
   foreach my $file (@files) {
       my $ap = $apps->getElement($file);
       ### next if (!$ap || !$ap->{EXEC});
       my ($exec, $bexec) = ICE::DVars::getAppExec($ap);
       next if (!$exec);
       push @apps, $ap;
   }
   $kids += $#apps + 1;
   return($ret) if ($kids < 1);

   my $dir = $self->{DIRECTORY};
   return($ret) if (!$dir);

   my $file = "$DIR_DIRS/$dir";
   $self->loadDesktopDir($file);
   my ($name, $comment, $icon, $type, $cats, $xmlfile, $filename) = 
      ($self->{NAME}, $self->{COMMENT}, $self->{ICON}, $self->{TYPE}, $self->{CATEGORY}, $self->{XMLFILE}, $self->{FILENAME});

   ## Submenus of this menu
   my ($mntContent, $mitems, $pitems) = ('', 0, 0);
   foreach my $submenu (sort { 
                         return lc $a->{NAME} cmp lc $b->{NAME}; 
                       } @elements) {  

       next if ($submenu->{DIRECTORY} =~ /more/i || $submenu->{DIRECTORY} =~ /other/i || $submenu->{DIRECTORY} =~ /unknown/i );
       my $txt = $submenu->renderMenu($apps, $cont+2, 1, $uniq, $showicons, $hideSub, $maxitems);
       if (length($txt)) {
	   $mntContent .= $txt;
	   $mitems ++;
       }
   }

   ## Applications of this menu
   my %menuappsdone;
   foreach my $ap (sort { lc $a->{NAME} cmp lc $b->{NAME} } @apps) {

       my ($name, $comment, $icon, $type, $cat, $afile) = 
          ($ap->{NAME}, $ap->{COMMENT}, $ap->{ICON}, $ap->{TYPE}, $ap->{CATEGORIES}, $ap->{FILE});
       my ($exec,$bexec) = ICE::DVars::getAppExec($ap);
       next if (!$exec);

       my $rep = "";

       next if ($uniq && $appsdone{$exec});
       $appsdone{$exec}=1;

       $rep = (defined $menuappsdone{$exec}) ? '### ' : '';
       $icon = ICE::DVars::findIcon($icon);
       $icon = '-' if (!$showicons || !$icon || $icon eq '');

       if ($comments) {
          $mntContent .=  ICE::DVars::indent($cont+1) . "#### $afile\n";
          $mntContent .=  ICE::DVars::indent($cont+1) . "# $comment\n";
          $mntContent .=  ICE::DVars::indent($cont+1) . "# $cat\n";
       }
       my $txt = ICE::DVars::indent($cont+1) . $rep . "prog \"$name\" \"$icon\" $exec\n";
       $mntContent .= $txt;
       $pitems ++;

       $menuappsdone{$exec}=1;
   }

   ## This Menu and its items
   if ($showRoot && $mntContent ne '') {
      if ($comments) {
         $ret .=  ICE::DVars::indent($cont) . "# NAME:     $name\n";
         $ret .=  ICE::DVars::indent($cont) . "# COMMENT:  $comment\n";
         $ret .=  ICE::DVars::indent($cont) . "# XML:      $xmlfile\n";
         $ret .=  ICE::DVars::indent($cont) . "# FILE:     $filename\n";
         $ret .=  ICE::DVars::indent($cont) . "# CATEG.:   $cats\n";
         $ret .=  ICE::DVars::indent($cont) . "# CHILDREN:  kids: $kids mitems: $mitems pitems: $pitems\n";
      }
      $icon = ICE::DVars::findIcon($icon);
      $icon = '-' if (!$icon || $icon eq '');
      if ( !$hideSub  || ($mitems + $pitems) > 1) {
         if ( $pitems > $maxitems) { ## split this huge menu into small submenus
            my $div   = int($pitems / $maxitems);
	    $div ++ if ($pitems % $maxitems);
            $maxitems = int($pitems / $div);
	    $maxitems ++ if ($pitems % $div);
            $ret .= ICE::DVars::indent($cont) . "menu \"$name\" \"$icon\" {\n";
	    $ret .= ICE::DVars::indent($cont) . "##### INI Splitted menu $name\n" if ($comments);
            my $acont=0; my $txt=''; my $let='';
            foreach my $line (split(/\n/, $mntContent)) {
		if ($let eq '' && $line =~ /^\s*prog \"([A-z])(.)/) {
		   $let = "(". uc ($1) . lc ($2) . "...)";
		}
		$txt .= "$line\n";
                $acont ++ if ($line !~ /^#/);
	        if ( !($acont % $maxitems) ) {
                   my $idx = int($acont / $maxitems);
		   #prog "KHexEdit"
                   $ret .=  ICE::DVars::indent($cont) . "menu \"$name $idx $let\" \"$icon\" {\n";
                   $ret .=  $txt;
                   $ret .=  ICE::DVars::indent($cont) . "}\n";
		   $txt  = ''; $let = '';
                }
            }
            if ($txt ne '') {
                my $idx = int($acont / $maxitems) + 1;
                $ret .=  ICE::DVars::indent($cont) . "menu \"$name $idx $let\" \"$icon\" {\n";
                $ret .=  $txt;
                $ret .=  ICE::DVars::indent($cont) . "}\n";
            }
	    $ret .= ICE::DVars::indent($cont) . "##### END Splitted menu $name\n" if ($comments);
            $ret .= ICE::DVars::indent($cont) . "}\n";
         } else {
            $ret .=  ICE::DVars::indent($cont) . "menu \"$name\" \"$icon\" {\n";
            $ret .=  $mntContent;
            $ret .=  ICE::DVars::indent($cont) . "}\n";
         }
      } else {
	 $ret .= $mntContent;
      }
   } elsif ($mntContent ne '') {
      $ret .= $mntContent;
   }

   return($ret);
}

## read a desktop folder filename
sub loadDesktopDir {
   my ($self, $file) = @_;
   my $lang = $ENV{LANG};
   if ($lang =~ /^(..)/) {
      $lang = lc $1;
   }
   open (F, $file);
   while(<F>) {
      chomp;
      if (/^([^=\[\]]+)=\"*(.*)\"*$/) {
         $self->{uc $1}=$2;
      } elsif ($lang && $lang ne '' && /^([^=\[\]]+)\[($lang)\]\s*=\"*(.*)\"*$/) {
         $self->{uc $1}=$3;
      }
   }
   close(F);
   $self->{FILENAME}=$file;
   return ($self);
}

#####################################################################
package ICE::DParser;
use XML::Parser;
#use Data::Dumper;
my ($ind, $file, $mfirst, $mparent, $mcurrent, $tcurrent, $inInclude, $inAnd, $inNot, $inOr, $preOp) = (0);

sub new {
    my ($self, $f) = @_;
    ($file, $ind) = ($f, 0);
    ($ind, $file, $mfirst, $mparent, $mcurrent, $tcurrent, $inInclude, $inAnd, $inNot, $inOr, $preOp) = (0, $f);
    return($self);
}
sub parse {
    my ($p) = @_;
    print STDERR "## Parsing: $file\n" if ($comments);
    ($mfirst, $mparent, $mcurrent, $tcurrent) = (undef, undef, undef, undef);
    my $xml = new XML::Parser( Style=>'Objects');
    $xml->setHandlers(Start=>\&start, End=>\&end, Char=>\&text);
    my @tree = @{ $xml->parsefile( $file ) };
    return($mfirst);
}
sub start { ## Start tags handler
   my ($p, $tag) = @_;
   $tag = uc $tag;
   $tcurrent = undef;
   if ($tag eq 'MENU') {
       my $parent = $mcurrent;
       $mfirst = $mcurrent if (!$mfirst);

       $mcurrent  = new ICE::DMenu();
       $mcurrent->{PARENT} = $parent;
       $mcurrent->{XMLFILE} = $file;

       push @{$parent->{ELEMENTS}}, $mcurrent if ($parent);

   } elsif ( $tag =~ /$VALID_TAGS/i ) {
       $tcurrent = $tag;
   } elsif ( $tag eq 'INCLUDE' ) {
       $preOp = '';
       $inInclude = 1;
   } elsif ( $tag eq 'AND' ) {
       $preOp .= "_AND_" ;
       $inAnd = 1;
   } elsif ( $tag eq 'NOT' ) {
       $preOp .= "_NOT_" ;
       $inNot = 1;
   } elsif ( $tag eq 'OR' ) {
       $preOp .= "_OR_";
       $inOr = 1;
   }
   $ind ++;
   print ICE::DVars::indent($ind) . "\\\\\\ TAG: $tag PRE: $preOp CUR: $tcurrent CAT: $mcurrent->{CATEGORY}\n" if ($DBG);
}
sub end { ## End tags handler
   my ($p, $tag) = @_;
   $tag = uc $tag;
   if ($tag eq 'MENU') {
       $mcurrent->{DONE}=1;
       $mcurrent = $mcurrent->{PARENT};
   } elsif ( $tag eq 'INCLUDE' ) {
       #$mcurrent->{$tcurrent} = _Include($mcurrent->{$tcurrent});
       $mcurrent->{CATEGORY} = _Include($mcurrent->{CATEGORY});
       $inInclude = 0;
   } elsif ( $tag eq 'AND' ) {
       $preOp =~ s/_AND_\s*$//;
       $mcurrent->{CATEGORY} = _And($mcurrent->{CATEGORY});
       $inAnd = 0;
   } elsif ( $tag eq 'NOT' ) {
       $preOp =~ s/_NOT_\s*$//;
       $mcurrent->{CATEGORY} = _Not($mcurrent->{CATEGORY});
       $inNot = 0;
   } elsif ( $tag eq 'OR' ) {
       $preOp =~ s/_OR_\s*$//;
       $mcurrent->{CATEGORY} = _Or($mcurrent->{CATEGORY});
       $inOr = 0;
   }
   print ICE::DVars::indent($ind) . "/// TAG: $tag PRE: $preOp CUR: $tcurrent CAT: $mcurrent->{CATEGORY}\n" if ($DBG);
   $ind --;
}
sub text { ## Text contenr handler
  my ($p, $data) = @_;
  if ($tcurrent && $mcurrent) {
     $data =~ s/[ \t\r\n]+$/ /g;
     my $tag = uc $tcurrent;
     if ($inInclude) {
	 $data =~ s/\s+//g;
         if ($data ne '' ) {
             $mcurrent->{$tag} .= $preOp;
	     # $mcurrent->{$tag} .= ($tag eq 'CATEGORY') ? " /$data/ " : " $data ";
	     $mcurrent->{$tag} .= ($tag eq 'CATEGORY') ? " /;$data;/ " : " $data ";
             $preOp = '';
         }
     } else {
         $mcurrent->{$tag} .= ' ' if ($mcurrent->{$tag});
         $mcurrent->{$tag} .= $data;
         $mcurrent->{$tag} =~ s/ +$//g;
     }
  }
} 

sub _Include {
   my ($txt) = @_;
   print ICE::DVars::indent($ind) . "OP _Include: $txt " if ($DBG);
   $txt =~ s/^\s+//g;
   $txt =~ s/\s+$//g;
   return $txt if ($txt !~ /./);
   my @a = split (/[\s]+/,$txt);
   my $ret = '(' . join(')||(', @a) . ')';
   print " -> $ret\n" if ($DBG);
   return($ret);
}
sub _And {
   my ($txt) = @_;
   print ICE::DVars::indent($ind) . "OP _And: $txt " if ($DBG);
   if ($txt =~ /^(.*)_AND_\s*(.*)$/) {
      my ($ini, $end) = ($1, $2);
      my @a = split (/[\s]+/,$end);
      $end = '(' . join(')&&(', @a) . ')';
      print " -> $ini$end\n" if ($DBG);
      return "$ini$end";
   }
   print "\n" if ($DBG);
   return($txt);
}
sub _Or {
   my ($txt) = @_;
   print ICE::DVars::indent($ind) . "OP _Or: $txt " if ($DBG);
   if ($txt =~ /^(.*)_OR_(.*)$/) {
      my ($ini, $end) = ($1, $2);
      my @a = split (/[\s]+/,$end);
      $end = '(' . join(')||(', @a) . ')';
      print " -> $ini$end\n" if ($DBG);
      return "$ini$end";
   }
   print "\n" if ($DBG);
   return($txt);
} 
sub _Not {
   my ($txt) = @_;
   print ICE::DVars::indent($ind) . "OP _Not: $txt " if ($DBG);
   if ($txt =~ /^(.*)_NOT_(.*)$/) {
      my ($ini, $end) = ($1, $2);
      $end =~ s/^\s+//g;
      $end =~ s/\s+$//g;
      my @a = split (/[\s]+/,$end);
      $end = ' !(' . join(')&&!(', @a) . ')';
      print " -> $ini$end\n" if ($DBG);
      return "$ini$end";
   }
   print "\n" if ($DBG);
   return($txt);
}

#####################################################################
package ICE::DApps;
#use Data::Dumper;
my @apps;
sub new() {
   my ($self, $kdea) =  @_;
   my @files;
   my $home = $ENV{HOME};
   my $regex;
   my @dirs;
   if ($kdea == 0) {
      $regex='/.desktop$/ && !/kde/ && $File::Find::dir !~ /\/kde/';
      push @dirs, @DIRS_APPS, "$home/.gnome/apps/";
   } elsif ($kdea == 1) {
      $regex='/.desktop$/ && (/kde/ || $File::Find::dir =~ /\/kde/)';
      push @dirs, @DIRS_APPS, @DIRS_APPS_KDE, "$home/.kde/share/applnk";
   } elsif ($kdea == 2) {
      $regex='/.desktop$/';
      push @dirs, @DIRS_APPS, @DIRS_APPS_KDE, "$home/.gnome/apps/", "$home/.kde/share/applnk";
   }
   if ($comments) {
      print STDERR "Looking for apps:\n  REGEX: $regex\n   DIRS: " . join(" ",@dirs) . "\n";
   }
   push @files, ICE::DVars::findFiles($regex, @dirs);

   foreach my $file (@files) {
       my $app = $self->loadDesktopFile($file);
       $app->{FILE} = $file;
       if ($file =~ /^(.*)\/+(.+)$/) {
           $app->{BASENAME}=$2;
       }
       push @apps, $app;
   }
   return($self);
}
sub getElements{
   my ($self, $menu) = @_;
   return @apps if (!$menu);
   my @ret;
   foreach my $a (@apps) {
       if (my $r = belongsToCategories($self, $a, $menu) ) {
           push @ret, $a;
           #print ICE::DVars::indent($cont) . "> " . $a->{NAME} . " $r \n";
       }
   }
   return(@ret);
}
sub getElement {
   my ($self, $file) = @_;
   foreach my $a (@apps) {
       my $basename = $file;
       if ($file =~ /^(.*)\/+(.+)$/) {
           $basename = $2;
       }
       if ($a->{BASENAME} eq $basename) {
           return $a;
       }
   }
}
sub loadDesktopFile {
   my ($self, $file) = @_;
   my $lang = $ENV{LANG};
   if ($lang =~ /^(..)/) {
      $lang = lc $1;
   }
   my $app = new ICE::DApp();
   $app->{FILE} = $file;
   open (F, $file) || return $app;
   while(<F>) {
      chomp;
      return ($app) if (defined $app->{EXEC} && /^\s*\[/);
      if (/^([^=\[\]]+)=(.*)$/) {
	 my $key = uc $1;
	 my $val = $2;
	 if ($val =~ /^\s*\"(.*)\"\s*$/ ) {
            $val = $1;
	 }
         $app->{$key}= ($key eq 'CATEGORIES') ? ";$val;" : "$val";
	 if ($key eq 'EXEC') {
	     my ($exec, $bexec) = ICE::DVars::getAppExec($app);
	     return if (!$exec);
	 }
      } elsif ($lang && $lang ne '' && /^([^=\[\]]+)\[($lang)\]\s*=\"*(.*)\"*$/) {
         $app->{uc $1}=$3;
      }
   }
   close(F);
   return ($app);
}

sub belongsToCategories {
   my ($self, $app, $menu) = @_;

   ## Regular expresion for categories of this menu (ie: ((/;Qt;/)||(/;KDE;/)))
   my $cat_regexp = $menu->{CATEGORY};
   return 0 if ($cat_regexp eq '');

   ## Categories for this app  (ie: Application;Core
   my $categories = $app->{CATEGORIES};
   my @ok;
   my $str = '@ok=(grep {' . $cat_regexp . '} (\'' . $categories . '\'));';
   eval $str;
   # OK
   return "$categories _ $cat_regexp " if ($#ok >= 0);
   
   ## Return NOT OK
   return 0;
}



#####################################################################
package ICE::DMain;
use Data::Dumper;

sub mergeFile {
    my ($ele) = @_;
    if ($ele->{MERGEFILE}) {
       my $file = "$ele->{MERGEFILE}";
       $file = "$DIR_MENU/$file" if (! -f $file);
       if ( -f "$file" ) {
           my $parser = new ICE::DParser($file);
           my $nele  = $parser->parse();
           $nele=expandElements ($nele);
           return ($nele);
       }
    }
    return($ele);
}

sub expandElements {
   my ($ele) = @_;
   return(undef) if (!$ele->{ELEMENTS});
   my @elements = @{$ele->{ELEMENTS}};
   my @nelements;
   $ele = mergeFile($ele);
   foreach my $nele (@elements) {
      $nele = mergeFile($nele);
      push @nelements, $nele if ($nele);
   }
   $ele->{ELEMENTS}=[@nelements];
   return($ele);
}

my ($root, $uniq, $collapse, $icons, $hideSub, $maxitems, $kde, $kdea)=(undef, 1, 1, 1, 1, 40, 0, 2);
sub usage {
    print <<EOF;

lxp-menu-gnerator v$VERSION

Usage: lxp-menu-generator [options] [files]

- Options
    --root=[0|1]      (default 1)  print the first root menu (parent menu element)
    --uniq=[0|1]      (default $uniq)  don't repeat applicaciones along menus
    --collapse=[0|1]  (default $collapse)  collapse the submenus that have only a submenu item
    --icons=[0|1]     (default $icons)  use icons for applications 
    --mitems=[nn]     (default $maxitems) Split submenus that has more items than mitems
    --comments        Show comments in menus
    --kde=[0|1|2]     (default $kde)  0 exclude kde menus, 1 show only kde menus, 2 show all menus
    --kdea=[0|1|2]    (default $kdea)  0 exclude kde applications, 1 show only kde apps, 2 show all apps
    --help            Show this message

- Files
    If file names are ommited, look for all files in $DIR_MENU folder
    If the file name is not absolute, search for the file in $DIR_MENU

- Icewm Configuration Example
#
# \$HOME/.icewm/menu
#
#
# gnome applications
menuprog "Programs"     folder lxp-menu-generator --kde=0 --kdea=0 --icons=1
#
#
# KDE applications
menuprog "KDE Programs" folder lxp-menu-generator --kde=1 --kdea=1 --icons=1 --root=0
#


EOF
}

###############################################################################
## Main
my (@files, $apps, @menus);
my $argfiles = 0;

foreach my $arg (@ARGV) {
        if ($arg =~ /^--root=(\d+)/ ) { ## print the root Menu
       $root = $1;
   } elsif ($arg =~ /^--uniq=(\d+)/ ) { ## Don't repeat applications in diferent menus
       $uniq = $1;
   } elsif ($arg =~ /^--collapse=(\d+)/ ) { ## Collapse submenus with only one item
       $collapse = $1;
   } elsif ($arg =~ /^--icons=(\d+)/ ) { ## When the menu has so much items, icewm expents lot of time reading icons
       $icons = $1;
   } elsif ($arg =~ /^--icons/ ) { ## When the menu has so much items, icewm expents lot of time reading icons
       $icons = 1;
   } elsif ($arg =~ /^--comments=(\d+)/ ) { ## When the menu has so much items, icewm expents lot of time reading icons
       $comments = $1;
   } elsif ($arg =~ /^--kde=(\d+)/ ) { ## 0 exclude kde menus, 1 only kde menus, 2 all menus
       $kde = $1;
       $kdea = $1;
   } elsif ($arg =~ /^--kdea=(\d+)/ ) { ## 0 exclude kde apps,  1 only kde apps,  2 all apps
       $kdea = $1;
   } elsif ($arg =~ /^--comments/ ) { ## When the menu has so much items, icewm expents lot of time reading icons
       $comments = 1;
   } elsif ($arg =~ /^--mitems=(\d+)/ ) { ## Maximal Items in each menu
       $icons = $1;
   } elsif ($arg =~ /^--help/ ) {
       usage();
       exit;
   } elsif (-f $arg) {
       $argfiles = 1;
       push @files, $arg;
   } elsif (-f "$DIR_MENU/$arg" ) {
       $argfiles = 1;
       push @files, "$DIR_MENU/$arg";
   } elsif (-f "$DIR_MENU/$arg.menu" ) {
       $argfiles = 1;
       push @files, "$DIR_MENU/$arg.menu";
   }
}

##
if (!$files[0]) {

    push @files, ICE::DVars::findFiles('/.menu/ || /.menu.kde$/', $DIR_MENU);
    #push @files, ICE::DVars::getSortedDirFiles("$DIR_MENU");
    $root = 1 if (!defined $root && $#files >=0);
}

$root = 0 if (!defined $root);

## Load all xml menus from /etc/xdf/menus
@menus = getMenusCfg($kde);
exit if ($#menus < 0 && defined($kde) && $kde == 1);

## Load all Icons
ICE::DVars::LoadIconFiles();
## Load all Exec
ICE::DVars::LoadExecFiles();
## Load all application.desktop files
$apps = new ICE::DApps($kdea);

## If there aren't xml files, load hardcoded menus in this file.
@menus = getMenusCfgDefault() if ($#menus < 0);

## Print menus
foreach my $m (@menus) {

   print "# >> Procesing XML: " . $m->{XMLFILE} . " DIR: " . $m->{DIRECTORY} . " NAM: " . $m->{NAME} . "\n" if ($comments);
   my $txt = $m->renderMenu($apps, 1, $root, $uniq , $icons, $hideSub, $maxitems);
   print $txt;
}

exit if ($argfiles);

my @core = getMenuCore();
foreach my $m (@core) {
   $uniq = 1; $root=0; $icons = 1;
   print "# >> Core Apps\n" if ($comments);
   my $txt = $m->renderMenu($apps, 1, $root, $uniq , $icons, $hideSub, $maxitems);
   print $txt;
}

my @orphand = getMenuOther();
foreach my $m (@orphand) {
   $uniq = 1; $root=1;
   print "# >> Orphand Apps\n" if ($comments);
   my $txt = $m->renderMenu($apps, 1, $root, $uniq , $icons, $hideSub, $maxitems);
   print $txt;
}

exit if ($kde == 1);

## Lxp utilities
#print "menuprogreload \"_Keyboard layout\" keyboard 300 lxp-icewm-keyboards\n" if (-x "/usr/bin/lxp-icewm-keyboards");
#print "menuprogreload \"D_esktop\"  desktop  30 lxp-switch-desktop --menu\n"   if (-x "/usr/bin/lxp-switch-desktop");
#print "menuprogreload \"Favoritos\" folder 30 olixmenu\n" if (-x "/usr/bin/olixmenu");
exit;

####################################################################################
sub getMenusCfg {
   my ($kde) = @_;
   my @ret;
   foreach my $file (@files) {
      my $nfile = $file;
      $nfile =~ s/^.*\///;
      next if ($filesdone{uc $nfile});
      next if (!$kde && $nfile =~ /kde/i);
      next if (($kde && $kde == 1) && $nfile !~ /kde/i);
      print STDERR "# Parsing xml file $file\n" if ($comments);
      my $r;
      eval {
         my $parser = new ICE::DParser($file);
         $r = $parser->parse();
         $r = expandElements($r);
      };
      push @ret, $r if ($r);
      $filesdone{uc $file}=1;
   }
   return @ret;
}
sub getMenuOther {
   my @ret = 
   (
        bless( {
                 'NAME' => 'Other',
                 'DONE' => 1,
                 'CATEGORY' => '/.*/',
                 'TYPE' => undef,
                 'ELEMENTS' => [],
                 'DIRECTORY' => 'Other.directory'
               }, 'ICE::DMenu' ),
   );
   return @ret;
}
sub getMenuCore {
   my @ret = 
   (
        bless( {
                 'NAME' => 'Core',
                 'DONE' => 1,
                 'CATEGORY' => '(/Application/)&&(/Core/)',
                 'TYPE' => undef,
                 'ELEMENTS' => [],
                 'DIRECTORY' => 'Core.directory'
               }, 'ICE::DMenu' ),
   );
   return @ret;
}
sub getMenusCfgDefault {
   print "## !! Hardcoded menu ##" if ($comments);
   my @ret = 
   (
        bless( {
                 'NAME' => 'Applications',
                 'DONE' => 1,
                 'CATEGORY' => '',
                 'TYPE' => undef,
                 'ELEMENTS' => [
                                 bless( {
                                          'NAME' => 'Accessories',
                                          'DONE' => 1,
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Accessories.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Accessibility',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Accessibility;/)&&(!(/;Settings;/)))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Accessibility.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Development',
                                          'DONE' => 1,
                                          'CATEGORY' => '(/;Development;/)',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Development.directory',
                                          'FILENAME' => '(emacs.desktop)'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Education',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Education;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Education.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Games',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Game;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Games.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Graphics',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Graphics;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Graphics.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Internet',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Network;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Internet.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Multimedia',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;AudioVideo;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Multimedia.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Office',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Office;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Office.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'System',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;System;/)&&(!(/;Settings;/)))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'System-Tools.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Other',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Application;/)&&(!(/;Core;/))&&(!(/;Settings;/)))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'DIRECTORY' => 'Other.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Debian',
                                          'DONE' => 1,
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/applications.menu',
                                          'MERGEFILE' => 'debian-menu.menu',
                                          'DIRECTORY' => 'Debian.directory'
                                        }, 'ICE::DMenu' )
                               ],
                 'XMLFILE' => '/etc/xdg/menus/applications.menu',
                 'DEFAULTDIRECTORYDIRS' => '',
                 'FILENAME' => '(gnome-app-install.desktop) gnome-app-install.desktop',
                 'DIRECTORY' => 'Applications.directory'
               }, 'ICE::DMenu' ),

        bless( {
                 'NAME' => 'Preferences',
                 'DONE' => 1,
                 'CATEGORY' => '((/;Settings;/)&&(!(()||(/;System;/)||(/;Accessibility;/))))',
                 'TYPE' => undef,
                 'ELEMENTS' => [
                                 bless( {
                                          'NAME' => 'Accessibility',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Settings;/)&&(/;Accessibility;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/preferences.menu',
                                          'DIRECTORY' => 'Settings-Accessibility.directory'
                                        }, 'ICE::DMenu' )
                               ],
                 'XMLFILE' => '/etc/xdg/menus/preferences.menu',
                 'DEFAULTDIRECTORYDIRS' => '',
                 'FILENAME' => 'gnomecc.desktop',
                 'DIRECTORY' => 'Preferences.directory'
               }, 'ICE::DMenu' ),
        bless( {
                 'NAME' => 'Desktop',
                 'DONE' => 1,
                 'CATEGORY' => '',
                 'TYPE' => undef,
                 'ELEMENTS' => [
                                 bless( {
                                          'NAME' => 'Preferences',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Settings;/)&&(!(()||(/;System;/)||(/;Accessibility;/))))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [
                                                          bless( {
                                                                   'NAME' => 'Accessibility',
                                                                   'DONE' => 1,
                                                                   'CATEGORY' => '((/;Settings;/)&&(/;Accessibility;/))',
                                                                   'TYPE' => undef,
                                                                   'ELEMENTS' => [],
                                                                   'XMLFILE' => 'preferences.menu',
                                                                   'DIRECTORY' => 'Settings-Accessibility.directory'
                                                                 }, 'ICE::DMenu' )
                                                        ],
                                          'XMLFILE' => 'preferences.menu',
                                          'DEFAULTDIRECTORYDIRS' => '',
                                          'FILENAME' => 'gnomecc.desktop',
                                          'DIRECTORY' => 'Preferences.directory'
                                        }, 'ICE::DMenu' ),
                                 bless( {
                                          'NAME' => 'Administration',
                                          'DONE' => 1,
                                          'CATEGORY' => '((/;Settings;/)&&(/;System;/))',
                                          'TYPE' => undef,
                                          'ELEMENTS' => [],
                                          'XMLFILE' => '/etc/xdg/menus/settings.menu',
                                          'DIRECTORY' => 'System-Settings.directory'
                                        }, 'ICE::DMenu' )
                               ],
                 'XMLFILE' => '/etc/xdg/menus/settings.menu',
                 'MERGEFILE' => '',
                 'DEFAULTDIRECTORYDIRS' => '',
                 'DIRECTORY' => 'Desktop.directory'
               }, 'ICE::DMenu' ),
   );
   return @ret;
}

## Print applications that no match any menu categories
### my $otherApps = '';
### my $coreApps  = '';
### foreach my $ap ($apps->getElements()) {
###        next if (!$ap || !$ap->{EXEC});
### 
###        my ($name, $comment, $icon, $type, $cat) = 
###           ($ap->{NAME}, $ap->{COMMENT}, $ap->{ICON}, $ap->{TYPE}, $ap->{CATEGORIES});
### 
###        my ($exec,$bexec) = ICE::DVars::getAppExec($ap);
###        next if (!$exec);
### 
###        my $cat_regexp = '(/Application/)&&(/Core/)';
###        my @ok;
###        my $str = '@ok=(grep {' . $cat_regexp . '} (\'' . $cat . '\'));';
###        eval $str;
### 
###        $icon = '' if (!$icons && $#ok < 0 );
### 
###        my $txt = '';
###        if ($comments) {
###           $txt .= ICE::DVars::indent(1) . "# $comment\n";
###           $txt .= ICE::DVars::indent(1) . "# $cat\n";
###        }
###        $txt .= ICE::DVars::indent(1) . "prog \"$name\" \"$icon\" $exec\n";
### 
###        if ($#ok >= 0) {
### 	   $coreApps .= $txt;
###        } else {
### 	   $otherApps .= $txt;
###        }
### }
## Orphand applications
### if ($otherApps ne '') {
###     print "menu \"_Other\" \"applications-other\" {\n$otherApps\n}\n";
### }


## Application;Core
### print $coreApps;


