#!/usr/bin/perl
#
# This script is a counterpart of vdr2iso and is aimed to assist in 
#       creating jobfiles for processing.
#       this script is written by ...: geronimo a-t vdrportal d.o_t de
# 
# COPYRIGHT:   (c) 2005-2006 Creative Commons by-nc-sa
#               some rights reserved. For details on copyright see:
#               http://creativecommons.org/licenses/by-nc-sa
#
# DISCLAIMER:   NO WARANTY AT ALL. Use at your own risk.
#
# vi: ts=3
#      
# INSTALLATION:
# use the installer and follow the questions.
# 
# DESCRIPTION: see README on a detailled description
#
# HISTORY:
# 30.04.2006 - removed separate window to edit recordings summary
#            - added support for addons-menue creation
#            - improved layout 
#
# 28.04.2006 - added little wysiwyg for fonts and images. Rearranged layout
#
# 22.04.2006 - added support for changing the order of the recordings of a disk.
#
# 18.04.2006 - changed check of external tools. Now only vdrsync is mandatory.
#              other (non existant) tools will result in an error message.
#
# 13.04.2006 - removed Spinbox (seems to be too new for some distris)
#
# 27.03.2006 - changed architecture to pluggable themes for the menue-creation
#              AGAIN: changed format of the jobfile!
#
# 26.02.2006 - improved network integration
# 
# 15.02.2006 - added network-client for communication with vdrproxy and 
#              vdr2iso-Server.
#
# 25.01.2006 - complete rewrite of tk-part
#
# 15.01.2006 - bugfix on cutmark-handling on recordsplitting
# 
# 14.01.2006 - improved splitting of recordings
#              improved cache-handling
#              improved preview
#
# 08.01.2006 - improved handling of recordings
#
# 03.01.2006 - new compat-mode
# 
# 01.01.2006 - support for info.vdr
#
# 01.01.2006 - XMLSimple ersetzt
#
# 30.12.2005 - first try               
# 
use strict;
use Tk;
use Tk::HList;
use Tk::ItemStyle;
use Tk::Dialog;
use Tk::FileSelect;
use Tk::Listbox;
use Tk::NoteBook;
use Tk::RecordListbox;
use Data::Dumper;
use Storable qw(dclone);
use File::Basename;
use File::Copy;
use IO::Socket;
use Net::hostent;
use POSIX ":sys_wait_h";
use IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRWXU);

use V2I::Config;
use V2I::Job;
use V2I::utils;

our($mw, $debug, $appLang);

my(@chNames, %channels, @allRecordings, %status, %jeChilds);
my($menuFrame, $diskFrame, $dSetFrame, $recsFrame, $statFrame, $lbDisks, $lbRecs);
my($appCfg, $curJob, $shmID, $dlgHelp, $dlgAbout, $lblDisks, $nbMain);
my $mainMenu = [{ name=>'file'
                , menu=>[{ name=>'open'
                         , menu=>\&mnOpen }
                        ,{ name=>'save'
                         , menu=>\&mnSave }
                        ,{ name=>'saveAs'
                         , menu=>\&mnSaveAs }
                        ,{ name=>'transmit'
                         , menu=>\&mnTransmit }
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'new'
                         , menu=>[{ name=>'disk'
                                  , menu=>\&mnNewDisk}
                                 ,{ name=>'job'
                                  , menu=>\&mnNewJob }]}
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'exit'
                         , menu=>\&mnExit }]}
               ,{ name=>'edit'
                , menu=>[{ name=>'addRec'
                         , menu=>\&mnAddRecording }
                        ,{ name=>'special'
                         , menu=>\&mnEditSpecial }  
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'copy'
                         , menu=>[{ name=>'disk'
                                  , menu=>\&mnCpDisk }
                                 ,{ name=>'recording'
                                  , menu=>\&mnCpRecording }]}  
                        ,{ name=>'delete'
                         , menu=>[{ name=>'disk'
                                  , menu=>\&mnRmDisk }
                                 ,{ name=>'recording'
                                  , menu=>\&mnRmRecording }
                                 ,{ name=>'special'
                                  , menu=>\&mnRmSpecial }]}        
                        ,{ name=>'details'
                         , menu=>\&editRecording }
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'settings'
                         , menu=>\&mnSettings }]}
               ,{ name=>'view'
                , menu=>[{ name=>'preview'
                         , menu=>\&mnPreview }
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'vmain'
                         , type=>'R'
                         , value=>1        
                         , menu=>\$main::View }
                        ,{ name=>'vpage'
                         , type=>'R'
                         , value=>0        
                         , menu=>\$main::View }
                        ,{ name=>'special'
                         , type=>'C'
                         , menu=>\$main::vSpecial }
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'lang'
                         , menu=>[{name=>'de', menu=>undef}]}       
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'vdrclient'
                         , menu=>\&mnVdrClient }       
                         ]}
               ,{ name=>'help'
                , menu=>[{ name=>'help'
                         , menu=>\&mnHelp }
                        ,{ name=>'-', menu=>'-' }
                        ,{ name=>'about'
                         , menu=>\&mnAbout }]}];
   
sub createSubMenu {
   my $mDesc = shift;
   my $parent = shift;

   foreach my $elem (@{$mDesc->{menu}}){
      if(ref($elem->{menu}) eq 'ARRAY'){
         my $sMenu = $parent->menu->Menu(-tearoff=>0);
         my $text = $appCfg->getLangToken($elem->{name});

         $parent->cascade(-label=>$text);
         createSubMenu($elem, $sMenu);
         $parent->entryconfigure($text, -menu=>$sMenu);
         }
      else{
         my $text = $appCfg->getLangToken($elem->{name});

         if($elem->{name} eq '-'){ $parent->separator(); }
         elsif(exists($elem->{type}) and $elem->{type} eq 'R'){
            if(exists($elem->{value})) {
               if(exists($elem->{command})){
                  $parent->radiobutton(-label=>$text, -command=>$elem->{command}, -variable=>$elem->{menu}, -value=>$elem->{value});
                  }
               else{
                  $parent->radiobutton(-label=>$text, -variable=>$elem->{menu}, -value=>$elem->{value});
                  }
               }
            else{
               $parent->radiobutton(-label=>$text, -variable=>$elem->{menu});
               }
            }
         elsif(exists($elem->{type}) and $elem->{type} eq 'C'){
            $parent->checkbutton(-label=>$text, -variable=>$elem->{menu});
            }
         else{
            $parent->command(-label=>$text, -command=>$elem->{menu});
            }
         }     
      }
}
            
sub createMenu {
   my $mDesc = shift;
   my $parent = shift;
   my $mb;

   patchMenue();
   foreach my $elem (@$mDesc){
      my $text = $elem->{name};

      $text = $appCfg->getLangToken($elem->{name});
      $mb = $parent->Menubutton(-text=>$text, -tearoff=>0)->pack(-side=>'left') if not $elem->{name} eq 'help';     
      $mb = $parent->Menubutton(-text=>$text, -tearoff=>0)->pack(-side=>'right') if $elem->{name} eq 'help';     
      if(ref($elem->{menu}) eq 'ARRAY'){ createSubMenu($elem, $mb); }
      else{ $mb->command(-label=>$text, -command=>$elem->{menu}); }
      }
}

sub showJob {
   my $job = shift;

   createDiskSettings($job);
   $lbDisks->delete(0, 'end');
   foreach my $disk (@{$job->getDisks}){
      $lbDisks->insert('end', $disk->{'title'});
      }
   showRecordings($job);
   $lbDisks->selectionSet($job->getCurrentDisk);
   $lbRecs->selectionSet($job->getCurrentRecording);
}

sub showRecordings {
   my $job = shift;

   $lbRecs->delete(0, 'end');
   for my $rec (@{$job->getRecordings}){ $lbRecs->insert('end', $rec); }
}

sub cbNumber {
   my $w = shift;
   my $b = shift;
   my $ref = shift;
   my $min = shift;
   my $max = shift;
   
   if($$ref =~ /(\d+)/){
      my $num = $1;
      
      $num++ if $b eq 1 or $b eq 4;
      $num-- if $b eq 3 or $b eq 5;
      $num = $min if $num < $min;
      $num = $max if $num > $max;
      $$ref = $num;
      }
   else{
      $$ref = $min;
      }
}

sub guiElem {
   my $parent = shift;
   my $pwidth = shift;
   my $ewidth = shift;
   my $id = shift;
   my $type = shift;
   my @args = @_;
   my $frame = $parent->Frame()->pack(-anchor=>'w', -fill=>'x');
   
   $frame->Label(-text=>$appCfg->getLangToken($id).': ', 
                 -relief=>'flat', 
                 -width=>$pwidth, 
                 -anchor=>'e')->pack(-side=>'left', -anchor=>'ne');
   my $subFunc = 'guiElem'.$type;

   no strict 'refs';
   return &$subFunc($frame, $id, $ewidth, \@args);
}

sub guiElemEntry {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $elem = $parent->Entry(-textvariable=>$args->[0], -width=>$ewidth)->pack(-side=>'left');

   return $elem;
}

sub guiElemDir {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $elem = $parent->Label(-text=>${$args->[0]},
                             -width=>$ewidth,
                            -anchor=>'e',
                            -relief=>'groove')->pack(-side=>'left');

   $elem->bind('<Button-1>', [\&selectDir, $id, $args->[0]]);

   return $elem;
}

sub guiElemRO {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my @args = shift;
   my $elem = $parent->Label(-text=>${$args[0][0]},
                             -width=>$ewidth,
                            -anchor=>'w',
                            -relief=>'flat')->pack(-side=>'left');
   return $elem;
}

sub guiElemPath {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my @args = shift;
   my $elem = $parent->Label(-text=>${$args[0][0]},
                             -width=>$ewidth,
                            -anchor=>'e',
                            -relief=>'groove')->pack(-side=>'left');

   $elem->bind('<Button-1>', [\&selectFile, $id, $args[0][0], @args]);

   return $elem;
}

sub guiElemImage {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $dummy = $appCfg->getHexColor4($args->[0]{'color'});
   my $elem = $parent->Label(-text=>$args->[0]{'value'},
                             -width=>$ewidth,
                             -anchor=>'e',
                             -background=>$dummy,
                             -cursor=>'sb_h_double_arrow',
                             -relief=>'groove')->pack(-side=>'left');
   my $imgFN = $args->[0]{'value'};
   my $addonsDir = $appCfg->getAppAddonsDir;

   $addonsDir .= '/' if not $addonsDir =~ /\/$/;
   $imgFN = $addonsDir.'SS-Jobedit.gif' if not -s $imgFN;
   my $img = createPhotoFrom($imgFN, $parent, $ewidth);                

   $elem->configure(-image=>$img);
   $elem->bind('<Button-1>', [\&selectImage, $id, $args->[0]]);
   $elem->bind('<Button-3>', [\&selectImageColor, $id, $args->[0]]);

   return $elem;
}

sub guiElemFont {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $elem0 = $parent->Label(-textvariable=>\$args->[0]{'value'},
                              -anchor=>'w',
                              -cursor=>'sb_h_double_arrow',
                              -relief=>'groove')->pack(-side=>'left');
   my $elem1 = $parent->Entry(-textvariable=>\$args->[0]{'size'}
                            , -width=>3
                            , -relief=>'groove')->pack(-side=>'left');

   $elem0->configure(-image=>$appCfg->fontSample($parent, $id, $ewidth-4, $args->[0]));
   $elem0->bind('<Button-1>', [\&selectFont, $id, $args->[0]]);
   $elem0->bind('<Button-3>', [\&selectFontColor, $id, $args->[0]]);
   $elem1->bind('<Button-1>', [\&cbNumber, Ev('b'), \$args->[0]{'size'}, $args->[1][0], $args->[1][1]]);
   $elem1->bind('<Button-4>', [\&cbNumber, Ev('b'), \$args->[0]{'size'}, $args->[1][0], $args->[1][1]]);
   $elem1->bind('<Button-3>', [\&cbNumber, Ev('b'), \$args->[0]{'size'}, $args->[1][0], $args->[1][1]]);
   $elem1->bind('<Button-5>', [\&cbNumber, Ev('b'), \$args->[0]{'size'}, $args->[1][0], $args->[1][1]]);

   return $elem0;
}

sub guiElemColor {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $dummy = $appCfg->getHexColor4(${$args->[0]});
   my $elem = $parent->Label(-text=>$dummy,
                             -background=>$dummy,
                             -width=>$ewidth,
                             -relief=>'groove')->pack(-side=>'left');
   $elem->bind('<Button-1>', [\&selectColor, $id, $args->[0]]);

   return $elem;
}

sub guiElemOption {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $elem;

   if($#{$args} > 1){
      $elem = $parent->Optionmenu(-textvariable=>$args->[0]
                                , -activeforeground=>'red'
                                , -foreground=>'blue'
                                , -command=>$args->[2]
                                , -options=>$args->[1])->pack(-side=>'left');
      }
   else{
      $elem = $parent->Optionmenu(-textvariable=>$args->[0]
                                , -activeforeground=>'red'
                                , -foreground=>'blue'
                                , -options=>$args->[1])->pack(-side=>'left');
      }
   return $elem;
}

sub guiElemRadio {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   
   foreach (@{$args->[1]}){
      my $value = $_;
      my $text = $appCfg->getLangToken($value);
      my $elem   = $parent->Radiobutton(-variable=>$args->[0],
                                       -text=>$text,
                                      -value=>$value);
      
      if($args->[2] =~ /^H/i){ $elem->pack(-side=>'left'); }
      else { $elem->pack(-anchor=>'w'); }
      if(exists($args->[3])){
         $elem->bind('<Button-1>', [$args->[3], $id, $args->[0]]);     
         }
      }
}

sub guiElemNumber {
   my $parent = shift;
   my $id = shift;
   my $ewidth = shift;
   my $args = shift;
   my $elem = $parent->Entry(-textvariable=>$args->[0]
                           , -width=>$ewidth
                           , -relief=>'groove')->pack(-side=>'left');
   $elem->bind('<Button-1>', [\&cbNumber, Ev('b'), $args->[0], $args->[1][0], $args->[1][1]]);
   $elem->bind('<Button-4>', [\&cbNumber, Ev('b'), $args->[0], $args->[1][0], $args->[1][1]]);
   $elem->bind('<Button-3>', [\&cbNumber, Ev('b'), $args->[0], $args->[1][0], $args->[1][1]]);
   $elem->bind('<Button-5>', [\&cbNumber, Ev('b'), $args->[0], $args->[1][0], $args->[1][1]]);

   return $elem;
}

sub createDiskSettings {
   my $job = shift;     
   my @kids = $dSetFrame->children();
   my @themes = $appCfg->getThemeManager->getActiveThemes();
   my $theme = $job->getCurrentTheme;
   
   if(not $theme){
      $job->setCurrentTheme($job->defaultTheme);
      $theme = $job->getCurrentTheme;
      }
   foreach (@kids){ $_->destroy(); }

   my $t = $dSetFrame->Scrolled('Text', -width=>500, -scrollbars=>'os', -relief=>'flat', -height=>5, -bd=>0, -wrap=>'none')->pack(-expand=>1, -fill=>'both');
   my $frLeft  = $t->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'both');
   my $frRight = $t->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'both');

   guiElem $frRight, 15, 25, 'title',   'Entry', \$job->currentDisk->{'title'};
   $frRight->Button(-text=>$appCfg->getLangToken('propagate'), -activeforeground=>'blue', -foreground=>'red', -command=>\&propagateDisk)->pack(-anchor=>'w', -expand=>1, -fill=>'x');
   guiElem $frRight, 15, 25, 'header',  'Entry', \$job->currentDiskSettings->{'header'};
   guiElem $frRight, 15, 18, 'theme',  'Option', \$job->currentDisk->{'theme'}, \@themes, [\&onThemeChange, $job];
   guiElem $frRight, 15, 18, 'demux',   'Radio', \$job->currentDiskSettings->{'demux'}, ['vdrsync', 'projectX'], 'H';
   guiElem $frRight, 15, 25, 'cutmarks','Path',  \$job->currentDiskSettings->{'cutmarks'}, undef, 'emptyOk';
   guiElem $frRight, 15,  5, 'language','Number',\$job->currentDiskSettings->{'language'},[0,9],  1;
   guiElem $frRight, 15,  5, 'chapLen', 'Number',\$job->currentDiskSettings->{'chapLen'}, [1,20], 1;
   guiElem $frRight, 15, 18, 'showBt',  'Radio', \$main::testOnly, [qw(0 1)], 'H';
   
   $appCfg->getThemeManager->getTheme($theme)->createDiskSettings($dSetFrame, $frLeft, $frRight, $job->currentDiskSettings);
   $t->windowCreate('end', -window=>$frLeft);
   $t->windowCreate('end', -window=>$frRight);
   $t->configure(-state=>'disabled', -insertofftime=>0, -cursor=>'left_ptr');
   $t->menu(undef);
}

sub tbJobSettings {
   my $p = shift;     
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   my $frLeft  = $p->Frame(-relief=>'groove', -bd=>1)->pack(-side=>'left', -expand=>1, -fill=>'both');
   my $frRight = $p->Frame(-relief=>'groove', -bd=>1)->pack(-side=>'left', -expand=>1, -fill=>'both');

   $curJob->createJobSettings($frLeft, $frRight);
}

sub tbVdrSettings {
   my $p = shift;     
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   guiElem $p, 35, 15, 'vdrprxhost', 'Entry', \$appCfg->getProxy->{'host'};
   guiElem $p, 35,  5, 'vdrprxport', 'Entry', \$appCfg->getProxy->{'port'};
   guiElem $p, 35,  5, 'jobeditport','Entry', \$appCfg->getConfig->{'jobedit'}{'port'};
   guiElem $p, 35, 40, 'epgFN',      'Path',  \$appCfg->getConfig->{'epgCache'};
   guiElem $p, 35, 30, 'showStarted','Radio', \$appCfg->getConfig->{'showStarted'}, ['yes', 'no'], 'H';
   guiElem $p, 35, 30, 'useVPS',     'Radio', \$appCfg->getConfig->{'useVPS'}, ['yes', 'no'], 'H';
   guiElem $p, 35,  5, 'offStart',   'Number',\$appCfg->getConfig->{'offStart'}, [0,99], 1;
   guiElem $p, 35,  5, 'offEnd',     'Number',\$appCfg->getConfig->{'offEnd'}, [0,99], 1;
   guiElem $p, 35,  5, 'timerPrio',  'Number',\$appCfg->getConfig->{'timerPrio'}, [0,99], 1;
   guiElem $p, 35,  5, 'recLiveTime','Number',\$appCfg->getConfig->{'recLiveTime'}, [0,99], 1;

   $p->Button(-text=>$appCfg->getLangToken('rescan'), -command=>\&rescanRecording)->pack(-side=>'bottom');
}

sub tbEnvSettings {
   my $p = shift;     
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   guiElem $p, 35, 30, 'vdr2isohost','Entry', \$appCfg->getWorker->{'host'};
   guiElem $p, 35, 30, 'vdr2isoport','Entry', \$appCfg->getWorker->{'port'};

   $p->Label(-text=>' ')->pack(-anchor=>'w');

   guiElem $p, 35, 55, 'recBase',   'Dir' , \$appCfg->getConfig->{'recBase'};
   guiElem $p, 35, 55, 'tmpdir',      'Dir' , \$appCfg->getConfig->{'temp'};
   foreach (qw(vdrsync java projectX convert genindex)){
      guiElem $p, 35, 55, $_, 'Path', \$appCfg->getConfig->{'commands'}{$_};
      }
}

sub tbThemeSettings {
   my $p = shift;     
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   my $nb = $p->NoteBook();
   my @themes = $appCfg->getThemeManager->getActiveThemes;

   $nb->form(-top=>'%0', -left=>'%0', -right=>'%100', -bottom=>'%100');
   foreach(@themes){
      tbThemePage($nb->add($_, -label=>$_), $_);
      }
}

sub tbThemePage {
   my $p = shift;
   my $themeName = shift;
   my @kids = $p->children();
   
   foreach (@kids){ $_->destroy(); }
   $appCfg->getConfig->{'themes'}{$themeName} = {} if not exists($appCfg->getConfig->{'themes'}{$themeName});
   $appCfg->getThemeManager->getTheme($themeName)->createJobSettings($p, $appCfg->getConfig->{'themes'}{$themeName});
}
 
sub tbMiscSettings {
   my $p = shift;     
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   guiElem $p, 50,  5, 'niceLevel','Number',\$appCfg->getConfig->{'niceLevel'}, [1,19], 1;
   guiElem $p, 50, 15, 'dvdSize',  'Entry', \$appCfg->getConfig->{'dvdSize'};
   guiElem $p, 50, 15, 'showTVBox','Radio', \$appCfg->getConfig->{'showTVBox'}, [0, 1], 'H';
   guiElem $p, 50, 30, 'ttfSample','Entry', \$appCfg->getConfig->{'ttfSample'};

   $p->Button(-text=>$appCfg->getLangToken('loadTTF'), -command=>\&rescanTTF)->pack(-side=>'bottom');
}

sub tbSpecMenue {
   my $p = shift;
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   my $frLeft  = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left',  -expand=>1, -fill=>'both');
   my $frRight = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'right', -expand=>1, -fill=>'both');

   $frRight->Button(-text=>$appCfg->getLangToken('propagate'), -activeforeground=>'blue', -foreground=>'red', -command=>\&propagateSpecs)->pack(-anchor=>'w', -fill=>'x');
   $appCfg->getThemeManager->getTheme($curJob->getCurrentTheme)->createDiskSettings($p, $frLeft, $frRight, $curJob->specials->{'settings'});
}

sub tbSpecPageSettings {
   my $p = shift;
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   my $frLeft  = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'both');
   my $frRight = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'both');

   guiElem $frLeft, 18, 30, 'title', 'Entry', \$curJob->currentPage->{'title'};
   $appCfg->getThemeManager->getTheme($curJob->getCurrentTheme)->createDiskSettings($p, $frLeft, $frRight, $curJob->currentPage->{'settings'});
}
      
sub tbSpecPageData {
   my $p = shift;
   my @kids = $p->children();

   foreach (@kids){ $_->destroy(); }

   my $frLeft  = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'y');
   my $frRight = $p->Frame(-relief=>'flat', -bd=>0)->pack(-side=>'left', -expand=>1, -fill=>'both');

   $appCfg->getThemeManager->getTheme($curJob->getCurrentTheme)->createSpecialSettings($p, $frLeft, $frRight, $curJob->currentPage);
}

sub tbVdrEpgData {
   my $p = shift;     
   my @kids = $p->children();
   my $events = getFilteredEvents($main::channels, 'Channel', $appCfg->currentChannel);
   my $now = time();

   foreach (@kids){ $_->destroy(); }

   my $f = $p->Frame()->pack(-side=>'top');
   my $lbx = RecordListbox->new($p
                               , -columns=>[qw(channel day date time len aspect audio s title)]
                               , -headers=>[$appCfg->getLangToken('hchannel')
                                         , $appCfg->getLangToken('hday')
                                         , $appCfg->getLangToken('hdate')
                                         , $appCfg->getLangToken('htime')
                                         , $appCfg->getLangToken('hlen')
                                         , $appCfg->getLangToken('haspect')
                                         , $appCfg->getLangToken('haudio')
                                         , $appCfg->getLangToken('hs')
                                         , $appCfg->getLangToken('htitle')
                                          ]
                              , -height=>19
                              , -width=>0);
   $f->Button(-text=>$appCfg->getLangToken('movies'), -command=>[\&doMovieEvents, $lbx])->pack(-side=>'left');
   $f->Label(-text=>' ', -width=>4)->pack(-side=>'left');
   $f->Button(-text=>'<', -command=>[\&prevChannel, $lbx])->pack(-side=>'left');
   $f->Optionmenu(-textvariable=>\$appCfg->getConfig->{'channel'}, -command=>[\&chgChannel, $lbx], -options=>\@chNames)->pack(-side=>'left');
   $f->Button(-text=>'>', -command=>[\&nextChannel, $lbx])->pack(-side=>'left');
   $f->Label(-text=>' ', -width=>4)->pack(-side=>'left');
   $f->Button(-text=>'<', -command=>[\&doPrevDay, $lbx, \$now])->pack(-side=>'left');
   $f->Button(-text=>$appCfg->getLangToken('today'), -command=>[\&doDateEvents, $lbx, \$now])->pack(-side=>'left');
   $f->Button(-text=>'>', -command=>[\&doNextDay, $lbx, \$now])->pack(-side=>'left');
   $p->bind('<Configure>', [\&onEpgConfigure, $lbx]);
   showEpgData($events, $lbx);
}

sub tbVdrTimers {
   my $p = shift;     
   my @kids = $p->children();
   my $timers = getTimers();

   foreach (@kids){ $_->destroy(); }

   my $lbx = RecordListbox->new($p
                               , -columns=>[qw(channel sdate sstart send prio live path)]
                               , -headers=>[$appCfg->getLangToken('hchannel') 
                                         , $appCfg->getLangToken('hsdate')
                                         , $appCfg->getLangToken('hsstart') 
                                         , $appCfg->getLangToken('hsend') 
                                         , $appCfg->getLangToken('hprio') 
                                         , $appCfg->getLangToken('hlive') 
                                         , $appCfg->getLangToken('hpath')
                                          ]
                              , -height=>22
                              , -width=>0);

   my @sortedTimers = sort { $a->{date} cmp $b->{date} 
                                   or
                             $a->{start} cmp $b->{start}              
                           } @$timers;
   foreach (@sortedTimers){ $lbx->insert('end', $_); }
   $p->bind('<Configure>', [\&onTimerConfigure, $lbx]);
   $main::vdrTimerListbox = $lbx;
}

sub tbHelpOV         { $_[0]->Label(-text=>$appCfg->getLangToken('txtHOV'),   -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpGUI        { $_[0]->Label(-text=>$appCfg->getLangToken('txtHGUI'),  -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpSettings   { $_[0]->Label(-text=>$appCfg->getLangToken('txtHSet'),  -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpRecordings { $_[0]->Label(-text=>$appCfg->getLangToken('txtHRec'),  -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpSpecial    { $_[0]->Label(-text=>$appCfg->getLangToken('txtHSpec'), -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpPreview    { $_[0]->Label(-text=>$appCfg->getLangToken('txtHPrev'), -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpVDR        { $_[0]->Label(-text=>$appCfg->getLangToken('txtHVDR'),  -wraplength=>'8i', -justify=>'left')->pack; }
sub tbHelpMisc       { $_[0]->Label(-text=>$appCfg->getLangToken('txtHMisc'), -wraplength=>'8i', -justify=>'left')->pack; }

sub createVDRClient {   
   if($main::wVdrClient and Exists($main::wVdrClient)){
      $main::wVdrClient->deiconify();
      $main::wVdrClient->raise();
      }
   else{
      my $w = $mw->Toplevel();

      if($appCfg->getConfig->{'geoVdr'}){
         $w->geometry($appCfg->getConfig->{'geoVdr'});
         }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('750x457+%d+%d', $x - 375, $y - 228));
         }
      $w->title($appCfg->getLangToken('vdr'));
      $w->minsize(750,400);
      $main::wVdrClient = $w;

      my $nb = $w->NoteBook();
      my $buttonFrame = $w->Frame(-relief=>'flat');

      $nb->form(-top=>'%0', -left=>'%0', -right=>'%100', -bottom=>$buttonFrame);
      $buttonFrame->form(-left=>'%0', -right=>'%100', -bottom=>'%100');
      
      my $sf = $buttonFrame->Frame()->pack();

      $sf->Button(-text=>$appCfg->getLangToken('leave'), -command=>\&closeVdrClient)->pack(-side=>'left');
      tbVdrEpgData($nb->add('epginfo', -label=>$appCfg->getLangToken('epginfo')));
      tbVdrTimers($nb->add('timers', -raisecmd=>\&refreshVdrTimers, -label=>$appCfg->getLangToken('timers')));
      }
}

sub refreshSpecials {
   my $lb = shift;

   $lb->delete(0, 'end');
   foreach(@{$curJob->specials->{'pages'}}){ $lb->insert('end', $_->{'title'}); }
   $lb->selectionSet($curJob->getCurrentPage);
   $curJob->currentPage->{'summary'} = $main::specialEdit->get('1.0', 'end') if $main::specialEdit;
}

sub showSpecials {
   my $lb = shift;
   my $nb = shift;
   my @kids = $nb->children();

   foreach (@kids){ $_->destroy(); }

   tbSpecMenue($nb->add('specMenue', -raisecmd=>[\&refreshSpecials, $lb], -label=>$appCfg->getLangToken('specMenue')));
   tbSpecPageSettings($nb->add('specSettings', -raisecmd=>[\&refreshSpecials, $lb], -label=>$appCfg->getLangToken('specSettings')));
   tbSpecPageData($nb->add('specData', -raisecmd=>[\&refreshSpecials, $lb], -label=>$appCfg->getLangToken('specData')));
   refreshSpecials($lb);
}

sub selectPage {
   my $lb = shift;
   my $nb = shift;
   my @sel = $lb->curselection;

   $curJob->setCurrentPage($sel[0]);
   showSpecials($lb, $nb);
}

sub createSpecialEdit {
   if($main::wSpecial and Exists($main::wSpecial)){
      $main::wSpecial->deiconify();
      $main::wSpecial->raise();
      }
   else{
      my $w = $mw->Toplevel();
      
      if($appCfg->getConfig->{'geoSpecial'}){ $w->geometry($appCfg->getConfig->{'geoSpecial'}); }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('814x450+%d+%d', $x - 407, $y - 225));
         }
      $main::wSpecial = $w;
      $w->title($appCfg->getLangToken('special'));
      $w->minsize(813,450);

      guiElem $w, 40, 50, 'spectitle', 'Entry', \$curJob->specials->{'title'};
      my $bf = $w->Frame->pack(-side=>'bottom');
      my $sf = $w->Scrolled('Listbox', -scrollbars=>'ose')->pack(-side=>'left', -fill=>'both');
      my $lbPages = $sf->Subwidget('listbox');
      my $nb = $w->NoteBook->pack(-side=>'right', -expand=>1, -fill=>'both');
      
      $lbPages->bind('<Button-1>', [\&selectPage, $nb]);
      showSpecials($lbPages, $nb);   
      $bf->Button(-text=>$appCfg->getLangToken('new'), -command=>[\&addSpecialPage, $lbPages, $nb])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('delete'), -command=>[\&delSpecialPage, $lbPages, $nb])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('up'), -command=>[\&movePageDown, $lbPages, $nb])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('down'), -command=>[\&movePageUp, $lbPages, $nb])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('preview'), -command=>\&specPreview)->pack(-side=>'left', -padx=>20);
      $bf->Button(-text=>$appCfg->getLangToken('leave'), -command=>\&closeSpecial)->pack(-side=>'left');
      }
}

sub delSpecialPage {
   my $lb = shift;
   my $nb = shift;
   
   $curJob->removeCurrentPage;
   showSpecials($lb, $nb);
}

sub movePageUp {
   my $lb = shift;
   my $nb = shift;
   
   $curJob->movePageUp;
   showSpecials($lb, $nb);
}

sub movePageDown {
   my $lb = shift;
   my $nb = shift;
   
   $curJob->movePageDown;
   showSpecials($lb, $nb);
}

sub specPreview {
   $main::View = 0;
   $main::vSpecial = 1;
   $main::special = $curJob->getCurrentPage;
   mnPreview();
}

sub addSpecialPage {
   my $lb = shift;
   my $nb = shift;

   $curJob->addPage;
   showSpecials($lb, $nb);
}

sub createSettingsWindow {
   if($main::wSettings and Exists($main::wSettings)){
      $main::wSettings->deiconify();
      $main::wSettings->raise();
      }
   else{
      my $w = $mw->Toplevel();

      if($appCfg->getConfig->{'geoSettings'}){
         $w->geometry($appCfg->getConfig->{'geoSettings'});
         }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('700x390+%d+%d', $x - 350, $y - 185));
         }
      $w->title($appCfg->getLangToken('settings'));
      $w->minsize(700,390);
      $main::wSettings = $w;

      my $nb = $w->NoteBook();
      my $buttonFrame = $w->Frame(-relief=>'flat');

      $nb->form(-top=>'%0', -left=>'%0', -right=>'%100', -bottom=>$buttonFrame);
      $buttonFrame->form(-left=>'%0', -right=>'%100', -bottom=>'%100');
      
      my $sf = $buttonFrame->Frame()->pack();

      $sf->Button(-text=>$appCfg->getLangToken('ok'), -command=>\&saveSettings)->pack();
      tbJobSettings($nb->add('jobsettings', -label=>$appCfg->getLangToken('jobsettings')));
      tbEnvSettings($nb->add('envsettings', -label=>$appCfg->getLangToken('envsettings')));
      tbVdrSettings($nb->add('vdrsettings', -label=>$appCfg->getLangToken('vdrsettings')));
      tbMiscSettings($nb->add('miscsettings', -label=>$appCfg->getLangToken('miscsettings')));
      tbThemeSettings($nb->add('themesettings', -label=>$appCfg->getLangToken('themesettings')));
      }
}

sub createRecordingsList {   
   if($main::wRecordings and Exists($main::wRecordings)){
      $main::wRecordings->deiconify();
      $main::wRecordings->raise();
      }
   else{
      my $w = $mw->Toplevel();     

      if($appCfg->getConfig->{'geoRecList'}){ $w->geometry($appCfg->getConfig->{'geoRecList'}); }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('770x450+%d+%d', $x - 385, $y - 225));
         }
      $main::wRecordings = $w;
      $w->title($appCfg->getLangToken('addRecordings'));
      $w->minsize(770,450);

      if(not @allRecordings){
         my $recordings = readRecordings();

         @allRecordings = sort { $a->{ts} cmp $b->{ts} } values(%$recordings);
         }
      my $bf = $w->Frame()->pack(-side=>'bottom');
      my $totalSize = 0;
      $bf->Label(-textvariable=>\$totalSize, -width=>17, -relief=>'flat')->pack(-side=>'left');
      my $lbx = RecordListbox->new($w
                                 , -columns=>[qw(sts name aspect audio ssize scut_size sraw_size path)]
                                 , -headers=>[$appCfg->getLangToken('hsts')
                                            , $appCfg->getLangToken('hname')
                                            , $appCfg->getLangToken('haspect')
                                            , $appCfg->getLangToken('haudio')
                                            , $appCfg->getLangToken('hssize')
                                            , $appCfg->getLangToken('hscut_size')
                                            , $appCfg->getLangToken('hsraw_size')
                                            , $appCfg->getLangToken('hpath')
                                             ]
                                   , -selectmode=>'extended'
                                 , -height=>19
                                 , -width=>0);
      refreshRecordings($lbx, 0, \$totalSize);
      $lbx->bind('<ButtonRelease-1>', [\&onSelectRecording, $lbx, \$totalSize]);
      $w->bind('<Configure>', [\&onRecListConfigure, $lbx]);
      $bf->Button(-text=>$appCfg->getLangToken('ok'), -command=>[\&chgRelatedRecordings, $lbx])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('cancel'), -command=>\&closeRecList)->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('sProjectX'), -command=>[\&startProjectX, $lbx])->pack(-side=>'left', -padx=>10);
      $bf->Button(-text=>$appCfg->getLangToken('splitRec'), -command=>[\&doSplitRecording, $lbx])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('joinRec'), -command=>[\&doJoinRecordings, $lbx])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('moveRec'), -command=>[\&doMoveRecordings, $lbx])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('removeRec'), -command=>[\&doRemoveRecordings, $lbx])->pack(-side=>'left');
      }
}

sub showEpgData {
   my $events = shift;
   my $lbx = shift;

   return if not $events;
   my @sortedEvents = sort { $a->{start} <=> $b->{start} ||
                             uc($a->{channel}) cmp uc($b->{channel}) } @$events;

   $lbx->delete(0,'end');
   foreach (@sortedEvents){
      $lbx->insert('end', $_);     
      }
   $lbx->bind('<Button-1>', [\&eventInfo, $lbx, \@sortedEvents]);
}

sub refreshVdrTimers {
   if(defined($main::vdrTimerListbox)){
      my $timers = getTimers();

      $main::vdrTimerListbox->delete(0,'end');

      my @sortedTimers = sort { $a->{date} cmp $b->{date} 
                                      or
                                $a->{start} cmp $b->{start}              
                              } @$timers;
      foreach (@sortedTimers){ $main::vdrTimerListbox->insert('end', $_); }
      }
}

sub mnOpen { 
   my $dir = '.';
   my $newFN = '';

   $dir = dirname($appCfg->getJobFilename);
   my $fs = $mw->FileSelect(-title=>$appCfg->getLangToken('topen'), -directory=>$dir);
   
   $newFN = $fs->Show; 
   return if not -s $newFN;
   $mw->title(basename($newFN).' - Job-Editor');
   $curJob = V2I::Job->load($newFN, $appCfg);
   $curJob->setFilename($newFN);
   showJob($curJob);
}

sub mnSave { 
   if(not $curJob->getFilename){ mnSaveAs(); }
   else{ $curJob->save; }
}

sub mnSaveAs { 
   my $dir = '.';
   
   $dir = dirname($appCfg->getJobFilename) if $appCfg->getJobFilename;
   $dir = dirname($curJob->getFilename) if $curJob->getFilename;
   my $fs = $mw->FileSelect(-title=>$appCfg->getLangToken('tsaveAs'), -directory=>$dir);
   my $newFN = $fs->Show;

   return if not $newFN;
   $curJob->setFilename($newFN);
   $mw->title(basename($curJob->getFilename).' - Job-Editor');
   mnSave();
}

sub mnNewDisk { 
   $curJob->addDisk;     
   showJob($curJob);
}

sub mnNewJob { 
   $curJob = V2I::Job->new($appCfg);     
   showJob($curJob);
}

sub mnExit {
   mnSave();
   stopInterfaces();
   exit; 
}

sub mnTransmit { 
   if($appCfg->getWorker->{'isOffline'}){
      $mw->messageBox(-icon=>'error'
                    , -message=>$appCfg->getLangToken('errOffline')
                    , -title=>$appCfg->getLangToken('error')
                    , -type=>'ok');
      print('worker may be offline?'."\n") if $debug;
      return;
   }
   my $proxy = IO::Socket::INET->new(Proto=>'tcp'
                                    , PeerAddr=>$appCfg->getWorker->{'host'}
                                   , PeerPort=>$appCfg->getWorker->{'port'});
   if($proxy){
      my $response = '';
      my $scratch;
      
      print($proxy 'accj jobedit'."\n");
      while(not ($scratch = <$proxy>)){}
      print('ok, we got a response...'."\n");
      if(not $scratch =~ /^jcca jobedit/i){
         $mw->messageBox(-icon=>'error'
                       , -message=>$appCfg->getLangToken('errAccept')
                       , -title=>$appCfg->getLangToken('error')
                       , -type=>'ok');
         return;
         }

      print('gonna convert job to xml ...'."\n");
      my $job = $curJob->toXml($appCfg);
      my $jobsize = length($job);

      print('job has size of #'.$jobsize."\n");
      print($proxy $job);
      while(not ($scratch = <$proxy>)){}
      print('another response from server...'."\n");
      if($scratch =~ /jobsize: (\d+)/){
         my $srvJobsize = $1;
         if($srvJobsize eq $jobsize){
            print('server got job with right size'."\n") if $debug;     
            print($proxy 'jcca ok');
            }
         else{
            $mw->messageBox(-icon=>'error'
                          , -message=>$appCfg->getLangToken('errTransmission')
                          , -title=>$appCfg->getLangToken('error')
                          , -type=>'ok');
            print($proxy 'jcaa nok');
            }
         }
      close($proxy);
      }
   else {
      $mw->messageBox(-icon=>'error'
                    , -message=>$appCfg->getLangToken('errOffline')
                    , -title=>$appCfg->getLangToken('error')
                    , -type=>'ok');
      print('worker may be offline?'."\n") if $debug;
      }
}

sub mnDoTest {
   print('mnDoTest() ... has these args:'."\n".Dumper(\@_)."\n");
   print('test: main::vSpecial is '.($main::vSpecial?'on':'off')."\n");
}

sub mnVdrClient {
   waitPanel($appCfg->getLangToken('waitVDRC'), \&createVDRClient);     
}
        
sub mnCpDisk {
   push(@{$curJob->getDisks}, dclone($curJob->currentDisk));
   showJob($curJob);
}

sub mnCpRecording {
   cloneRecEntry() if $#{$curJob->getRecordings} >= 0;
}

sub mnRmDisk { 
   $curJob->removeCurrentDisk;     
   showJob($curJob);
}

sub mnRmRecording { 
   $curJob->removeCurrentRecording;     
   showJob($curJob);
}

sub mnRmSpecial {
   print('job is before delete ...'."\n".$curJob->dump."\n\n");
   $curJob->addSpecials;
   $curJob->specials->{'title'} = undef;
   print('job is after delete ...'."\n".$curJob->dump."\n\n");
}

sub mnPreview { 
   waitPanel($appCfg->getLangToken('waitPreview'), \&startPreview);     
}

sub mnHelp { 
   $dlgHelp = $mw->Dialog(-title=>$appCfg->getLangToken('help'), -buttons=>['Ok']);
   my $nb = $dlgHelp->NoteBook()->pack;

   tbHelpOV(        $nb->add('helpoverview', -label=>$appCfg->getLangToken('helpoverview')));
   tbHelpGUI(       $nb->add('helpGUI',      -label=>$appCfg->getLangToken('helpGUI')));
   tbHelpSettings(  $nb->add('helpSettings', -label=>$appCfg->getLangToken('helpSettings')));
   tbHelpRecordings($nb->add('helpRecs',     -label=>$appCfg->getLangToken('helpRecordings')));
   tbHelpSpecial(   $nb->add('helpSpecial',  -label=>$appCfg->getLangToken('helpSpecial')));
   tbHelpPreview(   $nb->add('helpPreview',  -label=>$appCfg->getLangToken('helpPreview')));
   tbHelpVDR(       $nb->add('helpVDR',      -label=>$appCfg->getLangToken('helpVDR')));
   tbHelpMisc(      $nb->add('helpMisc',     -label=>$appCfg->getLangToken('helpMisc')));
   $dlgHelp->Show();
   undef($dlgHelp);
}

sub mnAbout { 
   $dlgAbout->Subwidget('message')->configure(-wraplength=>'7i', -text=>$appCfg->getLangToken('aboutText'));
   $dlgAbout->Show(); 
}

sub mnEditSpecial {
   $curJob->addSpecials if not exists($curJob->currentDisk->{'special'}{'title'});
   waitPanel($appCfg->getLangToken('waitSpecial'), \&createSpecialEdit);
}

sub mnAddRecording { 
   waitPanel($appCfg->getLangToken('waitRecList'), \&createRecordingsList);     
}
        
sub mnSettings {
   waitPanel($appCfg->getLangToken('waitSettings'), \&createSettingsWindow);     
}
        
sub chgLables {
   $appCfg->setupLanguage;     
   my @kids = $menuFrame->children();
   foreach my $w (@kids){
      $w->destroy();     
      }
   $appLang = $appCfg->getAppLang;
   createMenu($mainMenu, $menuFrame);
   $lblDisks->configure(-text=>$appCfg->getLangToken('tdisks'));
   $lbRecs->refresh(-headers=>[$appCfg->getLangToken('hdemux')
                             , $appCfg->getLangToken('hchapLen')
                             , $appCfg->getLangToken('hlang')
                             , $appCfg->getLangToken('htitle')
                             , $appCfg->getLangToken('hpath')]);
   foreach (@{$nbMain->info('pages')}){
      $nbMain->pageconfigure($_, -label=>$appCfg->getLangToken($_));
      }
   createDiskSettings($curJob);
}

sub saveSettings {
   $appCfg->getConfig->{'geoSettings'} = $main::wSettings->geometry;     
   $appCfg->save;
   stopInterfaces();
   startInterfaces();
   my $dlg = $mw->Dialog(-text => 'Sollen neue Werte auch in aktuellem Job gelten?'
                        , -bitmap => 'question'
                       , -title => 'weitere Aktion bestimmen'
                       , -default_button => 'Nein'
                       , -buttons => [ $appCfg->getLangToken('yes'), $appCfg->getLangToken('no')]);
   my $response = $dlg->Show;

   if($response eq $appCfg->getLangToken('yes')){
      $curJob->propagateJob;
      showJob($curJob);
      }
   $main::wSettings->withdraw;
   $main::wSettings = 0;
}

sub propagateSpecs { $curJob->propagateSpecs; }
sub propagateDisk  { $curJob->propagateDisk;  }

sub cloneRecEntry {
   my %newRec;
   
   foreach my $key (keys(%{$curJob->currentRecording})){
      print('cloneRecEntry: ['.$key.'] => { '.$curJob->currentRecording->{$key}." }\n") if $debug;
      $newRec{$key} = $curJob->currentRecording->{$key};
      }
   push(@{$curJob->currentDisk->{'recordings'}}, {%newRec});
   showJob($curJob);
}

sub startProjectX {
   my $lbx = shift;     
   my $path = '';

   if(not (-x $appCfg->editorCommand('java') and -s $appCfg->editorCommand('projectX'))){
      $lbx->parent->messageBox(-icon=>'error'
                             , -message=>$appCfg->getLangToken('errNoProjectX')
                             , -title=>$appCfg->getLangToken('error')
                             , -type=>'ok');
      return;
      }
   $path = $curJob->currentRecording->{path} if $curJob->currentRecording and exists($curJob->currentRecording->{path});
   if(defined($lbx) and $lbx->curselection()){
      my @selection = $lbx->curselection();
      my @rec = $lbx->get($selection[0]);
      
      $path = $rec[0]->{path};
      }
   $path .= '/' if not $path =~ /\/$/;
   my $cmd = $appCfg->nice.' '.$appCfg->editorCommand('java').' -jar '.$appCfg->editorCommand('projectX').' -out /tmp/.jobedit -gui ';
   my $mfn = $path.'marks.cnv.pjx';

   if(not -s $path.'marks.pjx' and -s $path.'marks.vdr'){
      print('gonna convert VDR-marks to ProjectX-Marks (see marks.cnv.pjx). Please be patient...'."\n");
      my $fp;     
      my $pcmRef = readIdx($path);
      my $marks  = readMarks($path, undef, $pcmRef);
      writeMarks($mfn, $marks, $pcmRef);
      }
   elsif(-s $path.'marks.pjx'){
      $mfn = $path.'marks.pjx';     
      }
   $cmd .= ' -cut \''.$mfn.'\'';

   for my $n (1 .. 999){
      my $file = $path.sprintf("%03d.vdr", $n);     

      print('gonna check file ['.$file."]\n") if $debug;
      last if(not -s $file);
      $cmd .= ' \''.$file.'\'';
      }
   $cmd .= ' &';

   print("\n$cmd\n\n") if $debug;
   system($cmd);
}

sub doPrevDay {
   my $lbx = shift;     
   my $tmRef = shift;
   my($sec,$min,$hour,undef,undef,undef,undef,undef,undef) = localtime($$tmRef);

   $$tmRef -= 86400 + $hour*3600 + $min*60 + $sec;
   my $events = getFilteredEvents($main::channels, 'Date', $$tmRef);

   showEpgData($events, $lbx);
}

sub doDateEvents {
   my $lbx = shift;     
   my $tmRef = shift;
   my $now = time;
   my $events = getFilteredEvents($main::channels, 'Date', $now);

   $$tmRef = $now;
   showEpgData($events, $lbx);
}

sub doNextDay {
   my $lbx = shift;     
   my $tmRef = shift;
   my($sec,$min,$hour,undef,undef,undef,undef,undef,undef) = localtime($$tmRef);

   $$tmRef += 86400 - $hour*3600 - $min*60 -$sec;
   my $events = getFilteredEvents($main::channels, 'Date', $$tmRef);

   showEpgData($events, $lbx);
}

sub doMovieEvents {
   my $lbx = shift;     
   my $events = getFilteredEvents($main::channels, 'Movie');

   showEpgData($events, $lbx);
}

sub prevChannel {
   my $lbx = shift;

   for my $i (0 .. $#chNames){
      if($chNames[$i] eq $appCfg->currentChannel){
         if($i > 0){ $appCfg->setCurrentChannel($chNames[$i-1]); }
         else      { $appCfg->setCurrentChannel($#chNames); }
         last;
         }
      }
   chgChannel($lbx);
}

sub chgChannel {
   my $lbx = shift;     
   my $events = getFilteredEvents($main::channels, 'Channel', $appCfg->currentChannel);

   showEpgData($events, $lbx);
}

sub nextChannel {
   my $lbx = shift;

   for my $i (0 .. $#chNames){
      if($chNames[$i] eq $appCfg->currentChannel){
         if($i < $#chNames){ $appCfg->setCurrentChannel($chNames[$i+1]); }
         else              { $appCfg->setCurrentChannel($chNames[0]); }
         last;
         }
      }
   chgChannel($lbx);
}

sub loadTTF { $appCfg->loadTTF; }

sub rescanTTF {
   unlink($appCfg->getFontCacheFN);
   waitPanel($appCfg->getLangToken('waitTTF'), \&loadTTF);
}

sub rescanRecording {
   unlink($appCfg->getConfig->{'recCache'});
   undef(@allRecordings);
}

sub doRemoveRecordings {
   my $lbx = shift;        
   my @sel = $lbx->curselection();
   my $msg = $appCfg->getLangToken('removeThis');

   foreach (@sel){
      my @recs = $lbx->get($_);

      $msg .= $recs[0]->{path}."\n";
      }
   my $dlg = $lbx->parent->Dialog(-title=>$appCfg->getLangToken('question')
                                 , -buttons=>[$appCfg->getLangToken('ok')
                                             , $appCfg->getLangToken('cancel')]);

   $dlg->Subwidget('message')->configure(-wraplength=>'8i', -text=>$msg);
   my $rv = $dlg->Show();

   return if not $rv eq $appCfg->getLangToken('ok');
   foreach (@sel){
      fakeRemoveRec($_);     
      }
   my $i = 0;
   
   foreach (@sel){ splice(@allRecordings, $_-$i++, 1); }
   refreshRecordings($lbx, 1);
}

sub doSplitRecording {
   my $lbx = shift;

   if($main::wSplitRecording and Exists($main::wSplitRecording)){
      $main::wSplitRecording->deiconify();
      $main::wSplitRecording->raise();
      }
   else{
      my @selection = $lbx->curselection();     

      return if not @selection;

      if(not -x $appCfg->editorCommand('genindex')){
         $lbx->parent->messageBox(-icon=>'error'
                                , -message=>$appCfg->getLangToken('errNoGenindex')
                                , -title=>$appCfg->getLangToken('error')
                                , -type=>'ok');
         return;
         }
      my @rec = $lbx->get($selection[0]);
      my $path = $rec[0]->{path};
      my $size = $rec[0]->{ssize};
      my $w = $mw->Toplevel();
      my $f = $w->Frame()->pack(-side=>'right', -expand=>1, -fill=>'both');

      $w->title($appCfg->getLangToken('splitRec'));
      $w->geometry('+250+100');
      $main::wSplitRecording = $w;
      $w->Label(-text=>$path)->pack(-side=>'top');

      my $hint = $w->Scrolled('Text', -relief=>'flat', -scrollbars=>'osoe', -width=>70, -height=>6)->pack(-side=>'top', -expand=>1, -fill=>'x');
      $hint->insert('end', $appCfg->getLangToken('hintsplit'));
      $hint->configure(-wrap=>'word', -state=>'disabled');

      my $sf = $w->Frame(-relief=>'groove')->pack(-side=>'bottom');
      my $lf = $w->Frame()->pack(-side=>'left', -expand=>1, -fill=>'both');

      my $totalSize = recLength($path);
      my $nMarks = readMarks($path);
      my $dvdSize = $appCfg->getConfig->{dvdSize};
      my $splitAt = $dvdSize;
      $splitAt = $nMarks->{base}[0] + $dvdSize if(($nMarks->{base}[0] + $dvdSize) < $nMarks->{base}[$#{$nMarks->{base}}]);
      $splitAt = ($nMarks->{base}[0] + $nMarks->{base}[$#{$nMarks->{base}}]) /2 if(($nMarks->{base}[0] + $dvdSize) >= $nMarks->{base}[$#{$nMarks->{base}}]);

      guiElem $lf, 34, 20, 'totalsize', 'RO', \localizeNumber($totalSize);
      guiElem $lf, 34, 20, 'estimated', 'RO', \localizeNumber($size);

      foreach my $mark (@{$nMarks->{base}}){
         guiElem $lf, 34, 20, 'cutmarkat', 'RO', \localizeNumber($mark);
         }
      my $lblSplit = guiElem $lf, 34, 20, 'splitAt', 'RO', \localizeNumber($splitAt);

      $f->Scale(-from=>0, -to=>$totalSize, -command=>[\&showSplitValue, \$lblSplit], -showvalue=>0, -length=>300, -variable=>\$splitAt)->pack(-side=>'right', -expand=>1, -fill=>'y');
      $sf->Button(-text=>$appCfg->getLangToken('ok'), -command=>[\&splitRecording, \$splitAt, $path, $nMarks])->pack(-side=>'left');
      $sf->Button(-text=>$appCfg->getLangToken('cancel'), -command=>sub {$main::wSplitRecording->withdraw; $main::wSplitRecording=0;})->pack(-side=>'right');
      }
}

sub doMoveRecordings {
   my $lbx = shift;        
   my @sel = $lbx->curselection();
   my $changed = 0;
   my $dlg = $lbx->parent->Dialog(-title=>$appCfg->getLangToken('moveRec')
                                 , -buttons=>[$appCfg->getLangToken('ok')
                                             , $appCfg->getLangToken('cancel')]);
   $dlg->Label(-text=>$appCfg->getLangToken('newpath'))->pack();
   my $ed = $dlg->Entry(-width=>99)->pack();

   foreach (@sel){
      my $rec = $allRecordings[$_];
      my $srcPath = $rec->{path};
      my $dstPath = $srcPath;

      $ed->configure(-textvariable=>\$dstPath);
      my $rv = $dlg->Show();
      
      next if not $rv eq $appCfg->getLangToken('ok');
      moveRecording($srcPath, $dstPath);
      my($pref,$name,$ts) = parseRecording($dstPath);

      $rec->{path}   = $dstPath;
      $rec->{name}   = $name;
      $rec->{prefix} = $pref;
      $rec->{ts}     = $ts;
      $changed = 1;
      }
   refreshRecordings($lbx, 1) if $changed;
   foreach (@sel){
      $lbx->selectionSet($_);     
      $lbx->see($_);
      }
}

sub doJoinRecordings {
   my $lbx = shift;        
   my @sel = $lbx->curselection();
   
   if(not -x $appCfg->editorCommand('genindex')){
      $lbx->parent->messageBox(-icon=>'error'
                     , -message=>$appCfg->getLangToken('errNoGenindex')
                     , -title=>$appCfg->getLangToken('error')
                     , -type=>'ok');
      return;
      }
   if($#sel < 1){
      $lbx->parent->messageBox(-icon=>'error'
                     , -message=>$appCfg->getLangToken('errJoinWhat')
                     , -title=>$appCfg->getLangToken('error')
                     , -type=>'ok');
      return;
      }
   elsif($#sel > 4){
      my $rv = $lbx->parent->messageBox(-icon=>'question'
                                        , -message=>$appCfg->getLangToken('errJoinMany')
                                      , -title=>$appCfg->getLangToken('error')
                                      , -type=>'yesno');
      return if not $rv =~ /^YES$/i;
      }
   my $dstIdx = shift(@sel);
   
   foreach (@sel){
      joinRecordings($lbx, $dstIdx, $_);
      }
   my $dstPath = $allRecordings[$dstIdx]->{path};

   unlink($dstPath.'index.vdr');
   my $cmd = 'cd \''.$dstPath.'\' && '.$appCfg->editorCommand('genindex');
   print('doJoinRecordings() - command:'."\n  ".$cmd."\n") if $debug;
   system($cmd);
   my $i = 0;
   
   foreach (@sel){ splice(@allRecordings, $_-$i++, 1); }
   refreshRecordings($lbx, 1);
}

sub closeEditRecording {
   my $edit = shift;

   $curJob->currentRecording->{summary} = $edit->get('1.0', 'end');
   $main::wEdRecordingWindow->withdraw;
   undef($main::wEdRecordingWindow);
   showJob($curJob);
}

sub chgRelatedRecordings {
   my $lbx = shift;
   my @selection = $lbx->curselection();

   $curJob->currentDisk->{'recordings'} = [];

   print('chgRelatedRecordings() ... '.V2I::utils::toXml($curJob->currentDiskSettings, 'diskSettings')."\n") if $debug;
   foreach my $sel (@selection){
      my $rec = dclone($allRecordings[$sel]);

      $rec->{'settings'} = dclone($curJob->currentDiskSettings);
      $rec->{'title'} = $rec->{'name'};
      push(@{$curJob->getRecordings}, $rec);
      }
   $appCfg->getConfig->{'geoRecList'} = $main::wRecordings->geometry;     
   $main::wRecordings->withdraw();
   $main::wRecordings = 0;
   undef($main::wRecordings);
   showJob($curJob);
}

sub closeSpecial {
   $curJob->currentPage->{'summary'} = $main::specialEdit->get('1.0', 'end') if $main::specialEdit;
   $appCfg->getConfig->{'geoSpecial'} = $main::wSpecial->geometry;     
   $main::wSpecial->withdraw;
   undef($main::wSpecial);
}

sub closePreview {
   $appCfg->getConfig->{'geoDvdMenuPreview'} = $main::wDVDPreview->geometry;     
   $main::wDVDPreview->withdraw;
   undef($main::wDVDPreview);
}

sub closeRecList {
   $appCfg->getConfig->{'geoRecList'} = $main::wRecordings->geometry;     
   $main::wRecordings->withdraw;
   $main::wRecordings = 0;
   undef($main::wRecordings);
}

sub closeVdrClient {
   $appCfg->getConfig->{'geoVdr'} = $main::wVdrClient->geometry;     
   $main::wVdrClient->withdraw;
   $main::wVdrClient = 0;
   undef($main::vdrTimerListbox);
}

sub selectFile {
   my $w = shift;
   my $name = shift;
   my $vn = shift;
   my @args = shift;
   my $dir = $args[0][1];
   my $option = $args[0][2];

   if($main::fileSelection{win} and Exists($main::fileSelection{win})){
      $main::fileSelection{win}->deiconify();
      $main::fileSelection{win}->raise();
      }
   else{
      ${$vn} = '.' if not "${$vn}";     
      my $startDir = dirname(${$vn}) if not "$dir";

      $startDir = $$dir if "$dir";

      $main::fileSelection{win} = $mw->FileSelect(-directory=>$startDir);
      $main::fileSelection{var}{org} = ${$vn};
      $main::fileSelection{win}->title($appCfg->getLangToken($name));

      my $path = $main::fileSelection{win}->Show;
      $main::fileSelection{win} = 0;
      ${$vn} = $path if "$path" or $option eq 'emptyOk';
      print('result of selectFile: <'.$path.">\n") if $debug;
      $w->configure(-text=>${$vn});
      }
}

sub selectImage {
   my $w = shift;
   my $name = shift;
   my $vn = shift;
   my $width = 200;

   selectFile($w, $name, \$vn->{'value'});
   if($w->geometry =~ /^(\d+)x(\d+)/){ $width = $1; }
   $w->configure(-image=>createPhotoFrom($vn->{'value'}, $w->parent, $width));
}

sub selectDir {
   my $w = shift;
   my $name = shift;
   my $vn = shift;

   if($main::fileSelection{win} and Exists($main::fileSelection{win})){
      $main::fileSelection{win}->deiconify();
      $main::fileSelection{win}->raise();
      }
   else{
      ${$vn} = '.' if not "${$vn}";     

      $main::fileSelection{win} = $mw->FileSelect(-initialdir=>${$vn}, -verify=>['-d']);
      $main::fileSelection{var}{org} = ${$vn};
      $main::fileSelection{win}->title($appCfg->getLangToken($name));

      my $path = $main::fileSelection{win}->Show;
      $main::fileSelection{win} = 0;
      
      $path =~ s%/\*$%%;
      ${$vn} = $path if "$path";
      print('result of selectDir: <'.$path.">\n") if $debug;
      $w->configure(-text=>${$vn});
      }
}

sub chgColor {
   my $cp = shift;
   my $lbl = shift;
   my $red = shift;
   my $green = shift;
   my $blue = shift;
   my $color = V2I::Config::hexColor([$$red, $$green, $$blue]);
   my $tred = 255 - $$red;
   my $tgreen = 255 - $$green;
   my $tblue = 255 - $$blue;
   my $tcolor = V2I::Config::hexColor([$tred, $tgreen, $tblue]);
   my $cn = $color;
   
   $tcolor = V2I::Config::hexColor([255, 255, 255]) if($tred < 135 and $tred > 110 and $tgreen < 135 and $tgreen > 110 and $tblue < 150 and $tblue > 5);   
   $cn = $appCfg->getColorTable->{'byValue'}{$color} if exists($appCfg->getColorTable->{'byValue'}{$color});
   $$lbl->configure(-background=>$color, -foreground=>$tcolor, -text=>$cn);
}

sub setFontColor {
   my $ec = shift;

   if($ec =~ /^Ok$/){
      my $lbl   = shift;     
      my $name  = shift;
      my $var   = shift;
      my $red   = shift;
      my $green = shift;
      my $blue  = shift;
      my $width = 186;

      $var->{'color'} = V2I::Config::hexColor([$$red, $$green, $$blue]);
      if($lbl->geometry =~ /^(\d+)x(\d+)/){ $width = $1 - 4; }
      $lbl->configure(-image=>$appCfg->fontSample($lbl->parent, $name, $width, $var));
      }
   $main::colorSelectionWindow->withdraw();
   undef $main::colorSelectionWindow;
}

sub setImageColor {
   my $ec = shift;

   if($ec =~ /^Ok$/){
      my $lbl   = shift;     
      my $name  = shift;
      my $var   = shift;
      my $red   = shift;
      my $green = shift;
      my $blue  = shift;
      my $width = 186;

      $var->{'color'} = V2I::Config::hexColor([$$red, $$green, $$blue]);
      }
   $main::colorSelectionWindow->withdraw();
   undef $main::colorSelectionWindow;
}

sub csExit {
   my $ec = shift;

   if($ec =~ /^Ok$/){
      my $lbl   = shift;     
      my $name  = shift;
      my $var   = shift;
      my $red   = shift;
      my $green = shift;
      my $blue  = shift;
      my $width = 186;
      my $colVal = V2I::Config::hexColor([$$red, $$green, $$blue]);

      if(ref($var) eq 'HASH'){ $var->{'color'} = $colVal; }
      else{ $$var = $colVal; }

      $lbl->configure(-background=>$colVal);
      }
   $main::colorSelectionWindow->withdraw();
   undef $main::colorSelectionWindow;
}

sub scrollFL {
   my $self = shift;
   my $wTarget = shift;
   my $dir = shift;
   my $max = shift;
   my $delta = 0.00383141762452107;
   my($top,$bottom) = $wTarget->yview();
   my $mxBottom = $delta * $max;

   if(   $dir eq 4 and $top > 0)           { $top -= $delta; }
   elsif($dir eq 5 and $bottom < $mxBottom){ $top += $delta; }
   $wTarget->yviewMoveto($top);
}

sub chooseFont {
   my $self = shift;
   my $lbl = shift;
   my $id = shift;
   my $font = shift;
   my $which = shift;
   my $width = 186;

   $appCfg->getConfig->{'geoFonts'} = $main::fontSelectionWindow->geometry;
   $font->{'value'} = $appCfg->getTTFList->[$which]{'value'};
   if($lbl->geometry =~ /^(\d+)x(\d+)/){ $width = $1 - 4; }
   $lbl->configure(-image=>$appCfg->fontSample($lbl->parent, $id, $width, $font));
   $main::fontSelectionWindow->withdraw;
   $main::fontSelectionWindow = 0;
}

sub selectFont {
   my $xLabel = shift;
   my $name = shift;
   my $font = shift;
   my $delta = 0.00383141762452107;

   if($main::fontSelectionWindow and Exists($main::fontSelectionWindow)){
      $main::fontSelectionWindow->deiconify();
      $main::fontSelectionWindow->raise();
      }
   else{
      my $w = $main::mw->Toplevel();
      my $matched = -1;

      $w->minsize(440,600);
      if($appCfg->getConfig->{'geoFonts'}){ $w->geometry($appCfg->getConfig->{'geoFonts'}); }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('440x600+%d+%d', $x - 220, $y - 300));
         }
      $w->title($appCfg->getLangToken($name));
      $main::fontSelectionWindow = $w;
      my $t = $w->Scrolled('Text', -width=>50, -scrollbars=>'ose', -wrap=>'none')->pack(-side=>'left', -expand=>1, -fill=>'both');
      my $i = 0;

      print('old font: '.$font->{'value'}."\n") if $debug;
      foreach my $newfont (@{$appCfg->getTTFList}){        
              next if not -s $newfont->{'bmp'};
              my $img = $w->Photo(-file=>$newfont->{'bmp'});
              my $fbt = $t->Button(-text=>' ', -image=>$img);

              $t->windowCreate('end', -window=>$fbt);
              $t->insert('end', "\n");
              $matched = $i if $newfont->{'value'} eq $font->{'value'};
              $fbt->bind('<Button-1>', [\&chooseFont, $xLabel, $name, $font, $i]);
              $fbt->bind('<Button-4>', [\&scrollFL, $t, Ev('b'), $#{$appCfg->getTTFList}]);
              $fbt->bind('<Button-5>', [\&scrollFL, $t, Ev('b'), $#{$appCfg->getTTFList}]);
              $i++;
              }
      print('found match at #'.$matched."\n") if $debug;
      $t->yviewMoveto($matched * $delta) if $matched >= 0;
      $t->configure(-state=>'disabled');
      }
}

sub scrollColor {
   my $self = shift;
   my $wTarget = shift;
   my $dir = shift;
   my $max = shift;
   my $delta = 0.0021978021978022;
   my($top,$bottom) = $wTarget->yview();
   my $mxBottom = $delta * $max;

   if(   $dir eq 4 and $top > 0)           { $top -= $delta; }
   elsif($dir eq 5 and $bottom < $mxBottom){ $top += $delta; }
   $wTarget->yviewMoveto($top);
}

sub selectColor {
   my $xLabel = shift;
   my $property = shift;
   my $var = shift;
   my $cb = shift;

   if($main::colorSelectionWindow and Exists($main::colorSelectionWindow)){
      $main::colorSelectionWindow->deiconify();
      $main::colorSelectionWindow->raise();
      }
   else{
      my $colVal = ref($var) eq 'HASH' ? $var->{'color'} : $$var;
      my $colors = $appCfg->getColor4($colVal);
      my $delta = 0.0021978021978022;
      my $redPart   = $$colors[0];
      my $greenPart = $$colors[1];
      my $bluePart  = $$colors[2];
      my $d = $main::mw->Toplevel();
      $d->title($appCfg->getLangToken('colorSelection'));
      $d->minsize(300,600);

      print('selectColor('.$property.'): ('.$redPart.','.$greenPart.','.$bluePart.")\n") if $debug;
      $cb = \&csExit if not ref($cb) eq 'CODE';
      $main::colorSelectionWindow = $d;
      my $f = $d->Frame()->pack(-side=>'bottom');
      $f->Button(-text=>$appCfg->getLangToken('ok'), -command=>[$cb, 'Ok', $xLabel, $property, $var, \$redPart, \$greenPart, \$bluePart])->pack(-side=>'left');
      $f->Button(-text=>$appCfg->getLangToken('cancel'), -command=>[$cb, 'Cancel'])->pack(-side=>'left');

      my $t  = $d->Scrolled('Text', -width=>30, -scrollbars=>'ose', -wrap=>'none')->pack(-side=>'left', -expand=>1, -fill=>'both');
      my $xf = $d->Frame->pack(-side=>'right', -expand=>1, -fill=>'both');
      my $lColor = $xf->Label(-relief=>'groove')->pack(-expand=>1, -anchor=>'n', -fill=>'both');
      my $scales = [ $xf->Scale(-from=>0, -to=>255, -command=>[\&chgColor, 'r', \$lColor, \$redPart, \$greenPart, \$bluePart], -variable=>\$redPart, -length=>200, -sliderlength=>6, -label=>'red')->pack(-side=>'left', -anchor=>'s'),
                     $xf->Scale(-from=>0, -to=>255, -command=>[\&chgColor, 'g', \$lColor, \$redPart, \$greenPart, \$bluePart], -variable=>\$greenPart, -length=>200, -sliderlength=>6, -label=>'green')->pack(-side=>'left', -anchor=>'s'),
                     $xf->Scale(-from=>0, -to=>255, -command=>[\&chgColor, 'b', \$lColor, \$redPart, \$greenPart, \$bluePart], -variable=>\$bluePart, -length=>200, -sliderlength=>6, -label=>'blue')->pack(-side=>'left', -anchor=>'s') ];
      my $matched = -1;               
      my $i = 0;               

      foreach my $color (@{$appCfg->getColorList}){        
         my $hexColor = $appCfg->getHexColor4($color);     
         my $w = $t->Label(-text=>$color, -background=>$hexColor,-relief=>'groove', -width=>30);

         $t->windowCreate('end', -window=>$w);
         $t->insert('end', "\n");
         $matched = $i if $var eq $color;
         $w->bind('<Button-1>', [\&setColor, $color, \$lColor, $scales]);
         $w->bind('<Button-4>', [\&scrollColor, $t, Ev('b'), $#{$appCfg->getColorList}]);
         $w->bind('<Button-5>', [\&scrollColor, $t, Ev('b'), $#{$appCfg->getColorList}]);
         $i++;
         }
      $t->yviewMoveto($matched * $delta) if $matched >= 0;
      $t->configure(-state=>'disabled');
      }
}

sub selectFontColor {
   my $xLabel = shift;
   my $name = shift;
   my $font = shift;

   selectColor($xLabel, $name, $font, \&setFontColor);
}

sub selectImageColor {
   my $xLabel = shift;
   my $name = shift;
   my $image = shift;

   selectColor($xLabel, $name, $image, \&setImageColor);
   $xLabel->configure(-background=>$appCfg->getHexColor4($image->{'color'}));
}

sub setColor {
   my $lbl = shift;
   my $cn = shift;
   my $lblColor = shift;
   my $scales = shift;
   my $colors = $appCfg->getColor4($cn);

   $$scales[0]->set($colors->[0]);
   $$scales[1]->set($colors->[1]);
   $$scales[2]->set($colors->[2]);
}

sub chgRecDone {
   my $recs = shift;

   $appCfg->getConfig->{'geoChgRecSO'} = $main::wChgRecSOWindow->geometry;
   $curJob->currentDisk->{'recordings'} = $recs;
   $main::wChgRecSOWindow->withdraw;
   $main::wChgRecSOWindow = 0;
}

sub chgRecSOUp {
   my $lb = shift;
   my $recs = shift;
   my @sel = $lb->curselection;
   my $cur = $sel[0];
   my $max = $#{$recs};
   
   return if $cur < 1;

   my $tmp = $recs->[$cur];
   
   $recs->[$cur]   = $recs->[$cur-1];   
   $recs->[$cur-1] = $tmp;
   $lb->delete(0,'end');   
   foreach (@$recs){ $lb->insert('end', $_->{'title'}); }
   $lb->selectionSet($cur-1);        
}

sub chgRecSODown {
   my $lb = shift;
   my $recs = shift;
   my @sel = $lb->curselection;
   my $cur = $sel[0];
   my $max = $#{$recs};
   
   return if $cur > ($max -1);

   my $tmp = $recs->[$cur];
   
   $recs->[$cur]   = $recs->[$cur+1];   
   $recs->[$cur+1] = $tmp;
   $lb->delete(0,'end');   
   foreach (@$recs){ $lb->insert('end', $_->{'title'}); }
   $lb->selectionSet($cur+1);        
}

sub chgRecSO {
   if($main::wChgRecSOWindow and Exists($main::wChgRecSOWindow)){
      $main::wChgRecSOWindow->deiconify();
      $main::wChgRecSOWindow->raise();
      }
   elsif($#{$curJob->getRecordings} > 0){
      my $w = $mw->Toplevel();     

      if($appCfg->getConfig->{'geoChgRecSO'}){
         $w->geometry($appCfg->getConfig->{'geoChgRecSO'});
         }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('300x200+%d+%d', $x - 150, $y - 100));
         }
      $main::wChgRecSOWindow = $w;
      $w->title($appCfg->getLangToken($appCfg->getLangToken('chgRecSO')));
      $w->minsize(300,200);

      my $fb = $w->Frame->pack(-side=>'bottom');
      my $lb = $w->Listbox->pack(-side=>'left', -fill=>'both', -expand=>1);
      my $fr = $w->Frame->pack(-side=>'right');
      my $recs = [@{$curJob->getRecordings}];
      my $bUp = $fr->Button(-text=>$appCfg->getLangToken('up'), -command=>[\&chgRecSOUp, $lb, $recs])->pack(-side=>'top');
      my $bDn = $fr->Button(-text=>$appCfg->getLangToken('down'), -command=>[\&chgRecSODown, $lb, $recs])->pack(-side=>'bottom');
      my $bOk = $fb->Button(-text=>$appCfg->getLangToken('ok'), -command=>[\&chgRecDone, $recs])->pack(-side=>'left');
      my $bC  = $fb->Button(-text=>$appCfg->getLangToken('cancel'), -command=>sub{$main::wChgRecSOWindow->withdraw; $main::wChgRecSOWindow=0;})->pack(-side=>'right');

      foreach (@{$curJob->getRecordings}){ $lb->insert('end', $_->{'title'}); }
      $lb->selectionSet($curJob->getCurrentRecording);
      }
   else{ print('Sorry, nothing found to change order.'."\n"); }
}

sub editRecording {
   if($main::wEdRecordingWindow and Exists($main::wEdRecordingWindow)){
      $main::wEdRecordingWindow->deiconify();
      $main::wEdRecordingWindow->raise();
      }
   else{
      my $recording = $curJob->currentRecording;

      return if not $recording;
      my $theme   = $curJob->getCurrentTheme;
      my $w       = $mw->Toplevel();
      my $bf      = $w->Frame(-relief=>'flat')->pack(-side=>'bottom');
      my $frTop   = $w->Frame(-relief=>'groove', -bd=>1)->pack(-side=>'top', -fill=>'x');
      my $frLeft  = $w->Frame(-relief=>'groove', -bd=>1)->pack(-side=>'left', -fill=>'both');
      my $frRight = $w->Frame(-relief=>'groove', -bd=>1)->pack(-side=>'left', -fill=>'both', -expand=>1);
      my $mp       = $mw->geometry;
      my $x       = $mw->screenwidth/2;
      my $y         = $mw->screenheight/2;

      if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
         $x = int($1/2) + $3;
         $y = int($2/2) + $4;
         }
      $w->title($appCfg->getLangToken('recording'));
      $w->geometry(sprintf('+%d+%d', $x - 350, $y - 175));

      guiElem $frTop,  18, 80, 'hpath',    'RO',    \$recording->{'path'};
      guiElem $frLeft, 18, 30, 'header',    'Entry', \$recording->{'settings'}{'header'};
      guiElem $frLeft, 18, 30, 'title',    'Entry', \$recording->{'title'};
      guiElem $frLeft, 18, 30, 'demux',     'Radio', \$recording->{'settings'}{'demux'}, ['vdrsync', 'projectX'];
      guiElem $frLeft, 18,  5, 'language','Number', \$recording->{'settings'}{'language'},[0,9], 1;
      guiElem $frLeft, 18,  5, 'chapLen', 'Number', \$recording->{'settings'}{'chapLen'}, [1,20], 1;
      guiElem $frLeft, 18, 30, 'cutmarks', 'Path',  \$recording->{'settings'}{'cutmarks'}, undef, 'emptyOk';

      my $edit = $frRight->Scrolled('Text', -relief=>'groove', -scrollbars=>'osoe', -width=>40, -height=>9)->pack(-anchor=>'w', -expand=>1, -fill=>'both');

      $main::wEdRecordingWindow = $w;
      $edit->configure(-wrap=>'word', -setgrid=>1);
      $edit->insert('end', $recording->{summary});
      $appCfg->getThemeManager->getTheme($theme)->createDiskSettings($w
                                                                   , $frLeft
                                                                   , $frRight
                                                                   , $recording->{'settings'});
      $bf->Button(-text=>$appCfg->getLangToken('done'), -command=>[\&closeEditRecording, $edit])->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('order'), -command=>\&chgRecSO)->pack(-side=>'left');
      $bf->Button(-text=>$appCfg->getLangToken('clone'), -command=>\&cloneRecEntry)->pack(-side=>'left', -padx=>10);
      $bf->Button(-text=>$appCfg->getLangToken('sProjectX'), -command=>\&startProjectX)->pack(-side=>'left');
      }
}

sub showSplitValue {
   my $lbl = shift;
   my $value = shift;

   $$lbl->configure(-text=>sprintf("%13s", localizeNumber($value)));
}

sub splitRecording {
   my $splitAt = shift;        
   my $path = shift;
   my $marks = shift;

   $path =~ s/\/$//;
   my $pcmRef = readIdx($path);
   my($total, $off, $delta, $filno) = bp2offset($path, $$splitAt);
   my($mIdx, $mOff, $mFilno, $startFrame, $mType) = findMark($pcmRef, $off, $filno);
   my $dIdx = $startFrame;
   my $pref = dirname($path);
   my $ts   = basename($path);
   my $offset = $mOff+$delta;
   my $nts;

   print('found Splitpoint at ('.$mIdx.', '.($offset).")\n") if $debug;
   if($ts =~ /^(\d+-\d+-\d+\.\d+)\.(\d+)\.(.+)/) {
      $nts = $1.'.'.($2+5).'.'.$3;     
      }
   else {
      die('invalid timestamp format! ?!?'."\n");     
      }
   my $dest = $pref.'/'.$nts;
   my $i = 1;
   my $oMarks = {};
   my $nMarks = {};
   my $cMarks = $#{$marks->{base}};   

   $oMarks->{name} = $marks->{name};
   $oMarks->{base} = ();
   $oMarks->{idx}  = ();
   $nMarks->{name} = $dest.'/'.basename($marks->{name});
   $nMarks->{base} = ();
   $nMarks->{idx}  = ();
   for my $i (0 .. $cMarks){
      push(@{$oMarks->{idx}} , $marks->{idx}[$i])  if $marks->{base}[$i] <= $offset;     
      push(@{$oMarks->{base}}, $marks->{base}[$i]) if $marks->{base}[$i] <= $offset;     
      push(@{$nMarks->{idx}} , $marks->{idx}[$i])  if $marks->{base}[$i] >  $offset;     
      push(@{$nMarks->{base}}, $marks->{base}[$i]) if $marks->{base}[$i] >  $offset;     
      }
   push(@{$oMarks->{idx}} , $mIdx);
   push(@{$oMarks->{base}}, $mOff+$delta); 
   unshift(@{$nMarks->{idx}}, $mIdx);
   unshift(@{$nMarks->{base}}, $mOff+$delta);

   mkdir($dest);
   my $cmd = '';

   $cmd = 'cp \''.$path.'/summary.vdr\' \''.$dest.'/summary.vdr\'' if(-s $path.'/summary.vdr');
   $cmd = 'cp \''.$path.'/info.vdr\' \''.$dest.'/info.vdr\'' if(-s $path.'/info.vdr');
   print('command: <|'.$cmd."|>\n") if("$cmd");
   copy($path.'/summary.vdr', $dest.'/summary.vdr') if(-s $path.'/summary.vdr');
   copy($path.'/info.vdr', $dest.'/info.vdr') if(-s $path.'/info.vdr');

   my $fn = sprintf('%03d.vdr', $mFilno);     
   $cmd = 'cp \''.$path.'/'.$fn.'\' \''.$dest.sprintf('/%03d.vdr', $i++).'\'';
   print('command: <|'.$cmd."|>\n");
   copy($path.'/'.$fn, $dest.sprintf('/%03d.vdr', $i++));

   my $j = $mFilno +1;
   for($fn = sprintf('%03d.vdr', $j); -s $path.'/'.$fn; $j++, $i++, $fn = sprintf('%03d.vdr', $j)){
      $cmd = 'mv \''.$path.'/'.$fn.'\' \''.$dest.sprintf('/%03d.vdr', $i).'\'';
      print('command: <|'.$cmd."|>\n");
      move($path.'/'.$fn, $dest.sprintf('/%03d.vdr', $i));
      }
   $cmd = 'rm -f \''.$path.'/index.vdr\'';
   print('command: <|'.$cmd."|>\n");
    unlink($path.'/index.vdr');

   $cmd = 'cd \''.$path.'\' && '.$appCfg->editorCommand('genindex');
   print('command: <|'.$cmd."|>\n");
   system($cmd);

   $cmd = 'cd \''.$dest.'\' && '.$appCfg->editorCommand('genindex');
   print('command: <|'.$cmd."|>\n");
   system($cmd);

   corrMarks($nMarks, $dIdx, $dest);
   writeMarks($oMarks->{name}, $oMarks, $pcmRef);
   writeMarks($nMarks->{name}, $nMarks, $pcmRef);
   $main::splitRecordingWindow->withdraw;
}

sub joinRecordings {
   my $lbx = shift;     
   my $dstIdx = shift;
   my $srcIdx = shift;
   my $dstPath = $allRecordings[$dstIdx]->{path};
   my $srcPath = $allRecordings[$srcIdx]->{path};
   my $lastDst = 0;
   my $lastSrc = 0;
   my $skipFirst = 1;
   my($cmd, $i, $rv);
   
   for $i (1 .. 999){ 
      if(not -s $dstPath.sprintf('%03d', $i).'.vdr'){
         $lastDst = $i - 1;
         last;
         }
      }
   for $i (1 .. 999){ 
      if(not -s $srcPath.sprintf('%03d', $i).'.vdr'){
         $lastSrc = $i - 1;
         last;
         }
      }
   if(-s $dstPath.sprintf('%03d',$lastDst).'.vdr' eq -s $srcPath.'001.vdr'){
      $rv = $lbx->parent->messageBox( -icon=>'question'
                                    , -message=>$appCfg->getLangToke('supposeSplitted')
                                    , -title=>$appCfg->getLangToken('question')
                                    , -type=>'yesno');
      print('joinRecordings(): user said <'.$rv.">\n") if $debug;
      $skipFirst = 2 if($rv eq 'Yes');
      }
   for $i ($skipFirst .. 999){
      last if not -s $srcPath.sprintf('%03d', $i).'.vdr';     
      $cmd = 'mv \''.$srcPath.sprintf('%03d', $i).'.vdr\' \''.$dstPath.sprintf('%03d', ++$lastDst).'.vdr\'';
      print('joinRecordings() - command:'."\n  ".$cmd."\n") if $debug;
      move($srcPath.sprintf('%03d', $i).'.vdr', $dstPath.sprintf('%03d', ++$lastDst).'.vdr');
      }
   $cmd = 'rm \''.$srcPath.'index.vdr\'';
   print('joinRecordings() - command:'."\n  ".$cmd."\n") if $debug;
   unlink($srcPath.'index.vdr');
}

sub createPhotoFrom {
   my $src = shift;
   my $parent = shift;
   my $size = shift;
   my $name = basename($src);

   if($name =~ /^(.+?)\.(.+)$/){
      print('createPhotoFrom('.$src.'): name=['.$1.'], xt=<'.$2.">\n") if $debug;
      $name = $1;
      }
   my $dst = $appCfg->getWorkDir.'/'.$name.'.gif';

   if(not $src eq $dst){
      print("\t".'have to create intermediate file ...'."\n") if $debug;
      my $cmd = $appCfg->workerCommand('convert');
      
      $cmd .= ' -resize '.$size.'x'.$size if $size;
      $cmd .= ' \''.$src.'\' \''.$dst.'\'';

      system($cmd);
      }
   return $parent->Photo(-format=>'gif', -file=>$dst);             
}

sub startPreview {
   my $mode = shift;     
   my $work = '/tmp/.jobedit';
   my $fn = $work.'/menu-main.png';
   my $width = 720;
   my $height = 576;
   my $cmd = 'rm -rf '.$work.'/*';
   
   $main::vSpecial = 0 if not $curJob->specials->{'title'};
   $fn = $work.'/VDRSYNC.'.($#{$curJob->getRecordings}+1).'/menu-main.png' if $main::vSpecial;
   $mode = 'new' if not $mode;
   system($cmd) if $mode eq 'new' and -d $work;
   mkdir($work) if not -d $work;
   print('startPreview('.($mode eq 'new'?'build':'switch').') main::View=['.$main::View.'], main::vSpecial=['.$main::vSpecial."]\n") if $debug;
   $curJob->createDVDMenu($work) if $mode eq 'new';
   if($main::View eq 0){
      print('startPreview() should open subpage view ...'."\n") if $debug;     
      my $chkFn = $work.'/VDRSYNC.'.$curJob->getCurrentRecording.'/menu-bg-'.$main::page.'.png';
      
      $chkFn = $work.'/VDRSYNC.'.($#{$curJob->getRecordings}+1).'/menu'.$main::special.'-bg-'.$main::page.'.png' if $main::vSpecial;
      print('check file <'.$chkFn.">\n") if $debug;
      if(-s  $chkFn){
         print('ok, file exists.'."\n") if $debug;     
         $fn = $chkFn;
         $width = 1024 if not $main::vSpecial and $curJob->currentRecording->{'settings'}{'aspect'} eq '16:9';
         $width = 1024 if $main::vSpecial and $curJob->currentPage->{'settings'}{'aspect'} eq '16:9';
         }
      else{ print('Oups, file does NOT exists?!?'."\n") if $debug;
         my $chkFn = $work.'/VDRSYNC.'.$curJob->getCurrentRecording.'/menu-bg.png';

         $chkFn = $work.'/VDRSYNC.'.($#{$curJob->getRecordings}+1).'/menu'.$main::special.'-bg.png' if $main::vSpecial;
         print('check file <'.$chkFn.">\n") if $debug;
         if(-s  $chkFn){
            print('ok, file exists.'."\n") if $debug;     
            $fn = $chkFn;
            $width = 1024 if not $main::vSpecial and $curJob->getCurrentRecording->{'settings'}{'aspect'} eq '16:9';
            $width = 1024 if $main::vSpecial and $curJob->currentPage->{'settings'}{'aspect'} eq '16:9';
            }
         else{ print('Oups, file ['.$chkFn.'] does NOT exists?!?'."\n"); }
         }
      }
   else{
      $width = 1024 if not $main::vSpecial and $curJob->currentDisk->{'settings'}{'aspect'} eq '16:9';
      $width = 1024 if $main::vSpecial and $curJob->specials->{'settings'}{'aspect'} eq '16:9';
      }
   if(not -s $fn){
      print('Oups, no dvd-preview-image found ['.$fn.'] !?!'."\n");
      return;
      }
   if($main::wDVDPreview and Exists($main::wDVDPreview)){
      $main::wDVDPreview->deiconify();
      $main::wDVDPreview->raise();
      foreach (@{$main::wDVDPreview->children}){ $_->destroy(); }
      }
   else{
      my $w = $mw->Toplevel();     

      if($appCfg->getConfig->{'geoDvdMenuPreview'}){
         $w->geometry($appCfg->getConfig->{'geoDvdMenuPreview'});
         }
      else{
         my $mp = $mw->geometry;
         my $x = $mw->screenwidth/2;
         my $y = $mw->screenheight/2;

         if($mp =~ /^(\d+)x(\d+)\+(\d+)\+(\d+)$/){
            $x = int($1/2) + $3;
            $y = int($2/2) + $4;
            }
         $w->geometry(sprintf('726x620+%d+%d', $x - 363, $y - 310));
         }
      $main::wDVDPreview = $w;
      $w->title($appCfg->getLangToken('dvdMenuPreview'));
      $w->minsize(726,620);
      }
   print('preview: width='.$width.', height='.$height."\n") if $debug;
   my $f = $main::wDVDPreview->Frame()->pack(-side=>'bottom');
   my $c = $main::wDVDPreview->Canvas(-height=>$height, -width=>$width
                                    , -scrollregion=>[0,0,$width,$height]
                                    , -cursor=>'left_ptr'
                                     )->pack(-side=>'left', -fill=>'both', -expand=>1);
   print('startPreview('.($mode eq 'new'?'build':'switch').') of file: <'.$fn.">\n") if $debug;
   my $img = createPhotoFrom($fn, $main::wDVDPreview);

   $c->createImage($width, $height, -image=>$img, -anchor=>'se');
   $c->createRectangle(27,19,$width-29,$height-23, -outline=>'#69B73D', -width=>25) if $appCfg->getConfig->{'showTVBox'};
   $f->Button(-text=>$appCfg->getLangToken('refresh'), -command=>\&mnPreview)->pack(-side=>'left');
   $f->Button(-text=>$appCfg->getLangToken('vmain'), -command=>\&switch2Menue)->pack(-side=>'left', -padx=>10);
   $f->Button(-text=>'<', -command=>[\&switch2Page, 'prev', $work])->pack(-side=>'left');
   $f->Button(-text=>$appCfg->getLangToken('vpage'), -command=>[\&switch2Page, 'cur', $work])->pack(-side=>'left');
   $f->Button(-text=>'>', -command=>[\&switch2Page, 'next', $work])->pack(-side=>'left');
   if($#{$curJob->specials->{'pages'}} >= 0){
      my @pages;
      my $i = 0;
      
      push(@pages, [$appCfg->getLangToken('off'), -1]);
      foreach(@{$curJob->specials->{'pages'}}){ print('collect addon #'.$i.' <['.$_->{'title'}."]>\n") if $debug; 
              $main::txtspecial = $_->{'title'} if $main::vSpecial and $main::special eq $i;
              push(@pages, [$_->{'title'}, $i++]); 
              }
      $main::buildPreview = 1;
      $f->Optionmenu(-variable=>\$main::special
                   , -textvariable=>\$main::txtspecial
                   , -activeforeground=>'black'
                   , -foreground=>'black'
                   , -command=>\&switch2Special
                   , -options=>\@pages)->pack(-side=>'left', -padx=>20);
      $main::buildPreview = 0;
      }
   $f->Button(-text=>$appCfg->getLangToken('leave'), -command=>\&closePreview)->pack(-side=>'left');
}

sub switch2Menue {
   $main::View = 1;     
   startPreview('switchonly');
}

sub switch2Special {
   return if $main::buildPreview;
   $main::View = 0;
   $main::vSpecial = $main::special < 0?0:1;
   $main::page = 0;
   startPreview('switchonly');
}

sub switch2Page {
   print('switch2Page() ... '."\n".Dumper(\@_)."\n");
   my $dir = shift;
   my $work = shift;
   
   $main::View = 0;
   $main::page-- if($dir eq 'prev');
   $main::page++ if($dir eq 'next');
   $main::page = 0 if $main::page < 0;
   $main::page = 0 if(not $main::vSpecial and not -s $work.'/VDRSYNC.'.$curJob->getCurrentRecording.'/menu-bg-'.$main::page.'.png');
   $main::page = 0 if($main::vSpecial and not -s  $work.'/VDRSYNC.'.($#{$curJob->getRecordings}+1).'/menu'.$main::special.'-bg-'.$main::page.'.png');
   startPreview('switchonly');
}

sub eventInfo {
   my $notUsed = shift;     
   my $lbx = shift;
   my @selection = $lbx->curselection();
   my $events = shift;
   my $ev = $events->[$selection[0]];
   my $dlg = $main::wVdrClient->Dialog(-title=>$ev->{title}, -buttons=>['Ok', 'Timer']);
   my $msg;

   foreach my $id (keys(%{$ev->{'tvideo'}})){
      foreach my $lang (keys(%{$ev->{'tvideo'}{$id}})){
         $msg .= $appCfg->getLangToken('video').' ('.$lang.'): '.$ev->{'tvideo'}{$id}{$lang}."\n";     
         }
      }
   foreach my $id (keys(%{$ev->{'taudio'}})){
      foreach my $lang (keys(%{$ev->{'taudio'}{$id}})){
         $msg .= $appCfg->getLangToken('audio').' ('.$lang.'): '.$ev->{'taudio'}{$id}{$lang}."\n";     
         }
      }
   if(defined($ev->{vps})){
      my @vps = localtime($ev->{vps});

      $msg .= '   VPS: '.localizeTS((1900+$vps[5]).'-'.sprintf('%02d',$vps[4]+1).'-'.sprintf('%02d',$vps[3]).'-'.$vps[2].':'.$vps[1].':'.$vps[0])."\n";
      }
   $msg .= "\n";
   $msg .= 'S: '.$ev->{subtitle}."\n" if $ev->{subtitle};
   $msg .= 'D: '.$ev->{summary} if $ev->{summary};
   $msg .= '>> '.$appCfg->getLangToken('noinfo').' <<' if not $ev->{summary};
   $msg =~ s/\|/\n/g;
   $dlg->Subwidget('message')->configure(-wraplength=>'9i', -text=>$msg);
   print('eventInfo() ev:'."\n".Dumper($ev)."\n") if $debug;
   my $rv = $dlg->Show();

   return if $rv eq 'Ok';
   return if $appCfg->getProxy->{'isOffline'};
   my $startTime = ($appCfg->getConfig->{'useVPS'} eq 'yes' and defined($ev->{vps})) ? $ev->{vps} : $ev->{start};
   
   $startTime -= $appCfg->getConfig->{'offStart'} * 60;                

   my @start = localtime($startTime);
   my @end   = localtime($startTime + $ev->{duration} + ($appCfg->getConfig->{'offStart'} + $appCfg->getConfig->{'offEnd'}) * 60);
   my $title = $ev->{title};
   my $summary = $ev->{summary};

   $title = $ev->{title}.'~'.$ev->{subtitle} if("$ev->{subtitle}");
   $title =~ s/\:/|/g;
   $summary =~ s/\n/|/g;
   my $nTimer = sprintf('%d:%s:%4d-%02d-%02d:%02d%02d:%02d%02d:%d:%d:%s:%s'
                        , 5
                        , $ev->{source}{id}
                        , 1900+$start[5], $start[4]+1, $start[3]
                        , $start[2], $start[1]
                        , $end[2], $end[1]
                        , $appCfg->getConfig->{'timerPrio'}
                        , $appCfg->getConfig->{'recLiveTime'}
                        , $title
                        , $summary);
   my $proxy = IO::Socket::INET->new(Proto=>'tcp'
                                    , PeerAddr=>$appCfg->getProxy->{'host'}
                                   , PeerPort=>$appCfg->getProxy->{'port'});
   if($proxy){
      print($proxy 'newt '.$nTimer."\n");
      while(my $line = <$proxy>){
         print('vdr: '.$line."\n");
         last if $line =~ /^$/;
         }
      close($proxy);   
      }
}

sub onExit {
   $appCfg->getConfig->{'geomain'} = $mw->geometry;
   $appCfg->save;
   stopInterfaces();
}

sub onSelectRecording {
   my $w = shift;
   my $lbx = shift;
   my $varRef = shift;
   my @sel = $w->curselection();
   my $size = 0;

   foreach (@sel) { $size += $allRecordings[$_]->{size}; }
   $$varRef = localizeNumber($size).' Gb ';
}

sub onThemeChange {
   my $job = shift;
   my $nTheme = shift;

   print('main::onThemeChange( '.$nTheme.' ) ... job-Theme: '.$job->currentDisk->{'theme'}."\n") if $debug;
   $job->themeChanged;
   showJob($job);
}

sub onDiskChange {
   my $list = shift;
   my @sel = $list->curselection();
   
   return if not @sel or $sel[0] eq $curJob->getCurrentDisk;
   $curJob->setCurrentDisk($sel[0]) if(@sel);
   showJob($curJob);
}

sub onRecordingChange {
   my $w = shift;
   my $lbx = shift;
   my @sel = $lbx->curselection();
   
   $curJob->setCurrentRecording($sel[0]) if @sel;
}
   
sub onMainConfigure {
   my $w = shift;
   my $info = $mw->geometry;
   my($x,$y,$w,$h) = (0, 0, 810, 410);

   if($info =~ /^(\d{1,5})x(\d{1,5})\+(\d{1,5})\+(\d{1,5})$/){
      $w = $1;
      $h = $2;
      $x = $3;
      $y = $4;
      }
   $lbRecs->refresh(-height=>int(($h-110)/17.2));
}
   
sub onRecListConfigure {
   my $w = shift;
   my $lbx = shift;

   if($main::wRecordings){
      my $info = $main::wRecordings->geometry;
      my($x,$y,$w,$h) = (0, 0, 800, 500);
   
      if($info =~ /^(\d{1,5})x(\d{1,5})\+(\d{1,5})\+(\d{1,5})$/){
         $w = $1;
         $h = $2;
         $x = $3;
         $y = $4;
         }
      $lbx->refresh(-height=>int(($h-30)/17.25)-2);
      }
}

sub onEpgConfigure {
   my $w = shift;
   my $lbx = shift;
   my $info = $main::wVdrClient->geometry;
   my($x,$y,$w,$h) = (0, 0, 800, 500);

   if($info =~ /^(\d{1,5})x(\d{1,5})\+(\d{1,5})\+(\d{1,5})$/){
      $w = $1;
      $h = $2;
      $x = $3;
      $y = $4;
      }
   $lbx->refresh(-height=>int(($h-93)/17.25)-2);
}

sub onTimerConfigure {
   my $w = shift;
   my $lbx = shift;
   my $info = $main::wVdrClient->geometry;
   my($x,$y,$w,$h) = (0, 0, 800, 500);

   if($info =~ /^(\d{1,5})x(\d{1,5})\+(\d{1,5})\+(\d{1,5})$/){
      $w = $1;
      $h = $2;
      $x = $3;
      $y = $4;
      }
   $lbx->refresh(-height=>int(($h-63)/17.25)-1);
}

sub moveRecording {
   my $srcDir = shift;
   my $dstDir = shift;
   my $dp;

   $srcDir .= '/' if not $srcDir =~ /\/$/;
   $dstDir .= '/' if not $dstDir =~ /\/$/;
   return if $srcDir eq $dstDir;
   print('moveRecFiles(): source=<'.$srcDir.">\n") if $debug;
   print('moveRecFiles(): dest..=<'.$dstDir.">\n") if $debug;
   system('mkdir -p \''.$dstDir.'\'');
   if(-d $dstDir){
      opendir($dp, $srcDir) or die 'can\'t open soure-recording!'."\n";
      while(my $entry = readdir($dp)){
         next if $entry =~ /^..?$/;
         print('move \''.$srcDir.$entry.'\' \''.$dstDir.$entry.'\''."\n");
         move($srcDir.$entry, $dstDir.$entry);
         }
      system('rmdir \''.$srcDir.'\'');
      system('rmdir \''.dirname($srcDir).'\' 2> /dev/null');
      print('done.'."\n");
      }
   else{ print('Oups, can NOT create target directory! '.$!."\n"); }
}

sub fakeRemoveRec {
   my $idx  = shift;
   my $rec  = $allRecordings[$idx];
   my $path = $rec->{path};
   my $cmd  = 'mv \''.$path.'index.vdr\' \''.$path.'index.del\'';
   
   print('command: '.$cmd."\n");
   system($cmd);
   
   $path =~ s/\/$//;
   my $delPath = $path;

   $delPath =~ s/\.rec$/.del/;
   $cmd = 'mv \''.$path.'\' \''.$delPath.'\'';

   print('command: '.$cmd."\n");
   system($cmd);
}

sub find {
   my $path = shift;
   my $what = shift;
   my $when = shift;
   my $dp;
   my @entries = ();

   opendir($dp, $path) or die "can't opendir $path: $!";
   while(my $entry = readdir($dp)){
      next if($entry =~ /^\.\.?$/);     
      if(-d $path.'/'.$entry) {
         my $subEntries = find($path.'/'.$entry, $what, $when);     
         foreach (@$subEntries){
            push(@entries, $_);     
            }
         }
      if($entry eq $what){
         my @st = stat($path.'/'.$entry);     
         next if($when and $st[9] < $when);
         push(@entries, $path.'/'.$entry);        
         }
      }
   closedir($dp);
   return \@entries;
}

sub parseRecording {
   my $path = shift;
   my @parts = split(/\//, $path);
   my $ts = pop(@parts);

   $ts = pop(@parts) if not "$ts";
   my $name = pop(@parts);
   my $pref = pop(@parts);

   $name =~ s/^%//;
   $pref =~ s/^%//;
   if($name =~ /\#(.{2})/){
      $name =~ s/\#5F/ /ig;
      $name =~ s/\#2F/\//ig;
      $name =~ s/\#3A/:/ig;
      $name =~ s/\#E8/é/ig;
      }
   $name =~ s/_/ /g;
   if($pref =~ /\#(.{2})/){
      $pref =~ s/\#5F/ /ig;
      $pref =~ s/\#2F/\//ig;
      $pref =~ s/\#3A/:/ig;
      $pref =~ s/\#E8/é/ig;
      }
   $pref =~ s/_/ /g;

   return ($pref,$name,$ts);
}

sub scanRecording {
   my $record = shift;
   my $cmd = $appCfg->editorCommand('vdrsync').' -i \''.$record->{path}.'\'';
   my $fp;
   my (@streams, $streamType, $streamID, $codec, $sampleFreq, $bitRate, $eBitRate, $streamType);
   my ($aspect, $width, $height, $mode, $speed, $frameBytes, $frameTicks, $size);
   my $movTotal = 1;
   my   $movCSize = 1;

   print('scanRecording(): cmd['.$cmd."]\n");
   if(open($fp, "$cmd |")){
      my $hasAC3 = 0;     
      my $i = 0;
      my $startSeen = 0;

      print(stderr '.');
      print('scan: '.$record->{path}."\n") if $debug;
      while(my $line = <$fp>){
         $startSeen = 1 if($line =~ /^READING INDEX FILE, please be patient$/);
         next if not $startSeen;
         if($line =~ /^movie_size\s+:\s+(\d+)/)     { $movTotal = $1; }
         if($line =~ /^movie_size_cut\s+:\s+(\d+)/) { $movCSize = $1; }
         if($line =~ /(audio|video)\s+stream\s+([^\s]+)\s+info(\s+\(([^)]+)\))?:/){
            print("found $1-stream with ID: #$2 of type $3\n") if $debug;  
            $streamType = $1;
            $streamID = $2;
            $codec = $3;
            $hasAC3 = 1 if($codec =~ /AC3/);
            if($streamType =~ /audio/){
               while(my $line = <$fp>){
                  $sampleFreq = $1 if($line =~ /Sample frequency:\s+(\d+)/);
                  $bitRate    = $1 if($line =~ /Bitrate:\s+(\d+)/);
                  $mode       = $1 if($line =~ /Mode:\s+([^\s]+)/);
                  $frameBytes = $1 if($line =~ /Frame length \(bytes\)\s+(\d+)/);
                  $frameTicks = $1 if($line =~ /Frame length \(ticks\)\s+(\d+)/);
                  $size       = $1 if($line =~ /App. Size \(bytes\)\s+(\d+)/);
                  last if($line =~ /^\s*$/);
                  }
               $streams[$i]{type}     = $streamType;
               $streams[$i]{id}       = $streamID;
               $streams[$i]{codec}    = $codec;
               $streams[$i]{sample}   = $sampleFreq;
               $streams[$i]{bitrate}  = $bitRate;
               $streams[$i]{eBitrate} = $bitRate;
               $streams[$i]{mode}     = $mode;
               $streams[$i]{aspect}   = 0;
               $streams[$i]{width}    = 0;
               $streams[$i]{height}   = 0;
               $streams[$i]{speed}    = 0;
               $streams[$i]{bFrLen}   = $frameBytes;
               $streams[$i]{tFrLen}   = $frameTicks;
               $streams[$i]{size}     = $size;
               }
            elsif($streamType =~ /video/){
               while(my $line = <$fp>){
                  $frameTicks = $1 if($line =~ /Frame length \(ticks\)\s+(\d+)/);
                  $aspect     = $1 if($line =~ /Aspect ratio\s+([\d\:]+)/);
                  $width      = $1 if($line =~ /Horizontal size\s+(\d+)/);
                  $height     = $1 if($line =~ /Vertical size\s+(\d+)/);
                  $speed      = $1 if($line =~ /Frames per Second\s+(\d+)/);
                  $bitRate    = $1 if($line =~ /Bitrate\s+(\d+)/);
                  $eBitRate   = $1 if($line =~ /Bitrate \(estm\)\s+(\d+)/);
                  $size       = $1 if($line =~ /App. Size \(bytes\)\s+(\d+)/);
                  last if($line =~ /^\s*$/);
                  }
               $streams[$i]{type}     = $streamType;
               $streams[$i]{id}       = $streamID;
               $streams[$i]{codec}    = $codec;
               $streams[$i]{sample}   = 0;
               $streams[$i]{bitrate}  = $bitRate;
               $streams[$i]{eBitrate} = $eBitRate;
               $streams[$i]{mode}     = $streamType;
               $streams[$i]{aspect}   = $aspect;
               $streams[$i]{width}    = $width;
               $streams[$i]{height}   = $height;
               $streams[$i]{speed}    = $speed;
               $streams[$i]{bFrLen}   = 0;
               $streams[$i]{tFrLen}   = $frameTicks;
               $streams[$i]{size}     = $size;
               }
            $i++;
            }
         }
      close($fp);
      $record->{raw_size} = $movTotal;
      $record->{cut_size} = $movCSize;
      $record->{info}     = [@streams];
      $record->{hasAC3}   = $hasAC3;
      if(not $record->{summary}){
         my $summary = $record->{name};

         if(-s $record->{'path'}.'summary.vdr'){
            if(open($fp, $record->{'path'}.'summary.vdr')){
               $summary = '';     
               while(my $line = <$fp>){ $summary .= $line; }
               close($fp);
               }
            }
         elsif(-s $record->{'path'}.'info.vdr'){
            if(open($fp, $record->{'path'}.'info.vdr')){
               $summary = '';
               while(my $line = <$fp>){
                  if($line =~ /^T (.+)$/){ $record->{'title'} = $1; }
                  elsif($line =~ /^D (.+)$/){ $summary = $1; }
                  }
               close($fp);
               } 
            } 
         $summary =~ s/\n/|/g;
         $summary =~ s/;/./g;
         $record->{'summary'} = $summary;
         }
      }
   else{
      print(stderr 'Oups, could not read from command ['.$cmd."]\n");     
      exit;
      }
}

sub refreshRecordings {
   my $lbx = shift;     
   my $updateCache = shift;
   my $rSize = shift;
   my $i = 0;
   my $matched = 0;
   my $totalSize;

   $lbx->delete(0, 'end');
   foreach my $rec (@allRecordings){
      $lbx->insert('end', $rec);     

      foreach my $elem (@{$curJob->getRecordings}){
         if($elem->{'path'} eq $rec->{'path'}){
            print('recording matched at #'.$i.' for <'.$elem->{'path'}.">\n") if $debug;     
            $lbx->selectionSet($i);
            $matched = $i if not $matched;
            }
         }
      $i++
      }
   onSelectRecording($lbx, $lbx, $rSize) if $rSize;
   $lbx->see($matched) if $matched;
   if($updateCache){
      my $fp;

      open($fp, '> '.$appCfg->getRecCache) or die('could not create cache!'."\n");
      foreach my $rec (@allRecordings){
         printf($fp "%s;%s;%s;%s;%s;%s;%s;%s;%s;%s\n"
               , $rec->{prefix}
               , $rec->{name}
               , $rec->{aspect}
               , $rec->{audio}
               , $rec->{size}
               , $rec->{cut_size}
               , $rec->{raw_size}
               , $rec->{ts}
               , $rec->{path}
               , $rec->{summary});
         }
      close($fp);
      }
}

sub readRecordings {
   my $fp;     
   my %recordings;
   my $tm = 0;

   if(-s $appCfg->getRecCache){
      my @st = stat($appCfg->getRecCache);
      my $scratch;

      $tm = $st[9];
      if(open($fp, $appCfg->getRecCache)){
         while(my $line = <$fp>){
            $line =~ s/\n$//;
                 
            my $recInfo = {};
            my @parts = split(/;/, $line);

            $recInfo->{prefix}     = $parts[0];
            $recInfo->{name}       = $parts[1];
            $recInfo->{aspect}     = $parts[2];
            $recInfo->{audio}      = $parts[3];
            $recInfo->{size}       = $parts[4];
            $recInfo->{cut_size}   = $parts[5];
            $recInfo->{raw_size}   = $parts[6];
            $recInfo->{ts}         = $parts[7];
            $recInfo->{ssize}      = sprintf('%14s', localizeNumber($parts[4]));
            $recInfo->{scut_size}  = sprintf('%14s', localizeNumber($parts[5]));
            $recInfo->{sraw_size}  = sprintf('%14s', localizeNumber($parts[6]));
            $recInfo->{sts}        = substr(localizeTS($parts[7]),0,16);
            $recInfo->{path}       = $parts[8];
            $scratch               = $parts[9];
            $scratch =~ s/\|/\n/g;
            $recInfo->{summary}    = $scratch;
            $recordings{$parts[8]} = $recInfo;
            }
         close($fp);     
         }
      }
   else{
      print("\n".$appCfg->getLangToken('nocache')."\n\t".$appCfg->getLangToken('gonnascan')."\n\n");     
      print('recordings-cache is ['.$appCfg->getRecCache."]\n") if $debug;
      }
   my $newRecordings = find($appCfg->getRecBase, 'index.vdr', $tm);

   foreach (@$newRecordings){ print('new recording: '.$_."\n"); }
   if(@{$newRecordings}){
      foreach my $recPath (@$newRecordings){
         $recPath =~ s/index.vdr$//;

         my $elem = {path=>$recPath};
         my($pref,$rec,$ts) = parseRecording($recPath);

         $elem->{prefix} = $pref;
         $elem->{name} = $rec;
         $elem->{ts} = $ts;

         scanRecording($elem);

         my $size = 0;
         my $aspect = 'unknown';
         foreach my $i (0 .. $#{$elem->{info}}){
            if($elem->{info}[$i]{type} =~ /video/){
               print("Vidio-stream has size ".$elem->{info}[$i]{size}."\n") if $debug;     
               $size += $elem->{info}[$i]{size}; 
               $aspect = $elem->{info}[$i]{aspect};
               }     
            if($elem->{hasAC3}){
                if($elem->{info}[$i]{codec} =~ /AC3/){
                  print("found AC3-stream with size ".$elem->{info}[$i]{size}."\n") if $debug;     
                  $size += $elem->{info}[$i]{size};
                  }
               }
            elsif($elem->{info}[$i]{mode} =~ /stereo/) {
               print("found standard-stream with size ".$elem->{info}[$i]{size}."\n") if $debug;     
               $size += $elem->{info}[$i]{size};
               }
            }
         my $factor = $elem->{cut_size} / $elem->{raw_size};
         $size *= $factor * 1.0285 if $elem->{hasAC3};
         $size *= $factor * 1.026  if not $elem->{hasAC3};
         $elem->{aspect} = $aspect;
         $elem->{size}   = int($size);
         $elem->{audio}  = $elem->{hasAC3}?"ac3":"st";
         $elem->{ssize}      = sprintf('%14s', localizeNumber($elem->{size}));
         $elem->{scut_size}  = sprintf('%14s', localizeNumber($elem->{cut_size}));
         $elem->{sraw_size}  = sprintf('%14s', localizeNumber($elem->{raw_size}));
         $elem->{sts}        = substr(localizeTS($elem->{ts}), 0, 16);
         $recordings{$elem->{path}} = $elem;
         }
      open($fp, '> '.$appCfg->getRecCache) or die('could not create cache! ['.$appCfg->getRecCache."]\n");
      foreach my $recPath (keys(%recordings)){
         my $entry = $recordings{$recPath};
         printf($fp "%s;%s;%s;%s;%s;%s;%s;%s;%s;%s\n"
               , $entry->{prefix}
               , $entry->{name}
               , $entry->{aspect}
               , $entry->{audio}
               , $entry->{size}
               , $entry->{cut_size}
               , $entry->{raw_size}
               , $entry->{ts}
               , $entry->{path}
               , $entry->{summary});
         }
      close($fp);
      }
   return \%recordings;
}

sub logmsg { 
   my $msg = "@_"; 
   
   $msg =~ s/\n$//; 
   system('logger \''.$0.' '.$$.': '.$msg.'\''); 
}

sub chId2Name {
   my $id = shift;
   my $isNum = 1;
   my $rv;

   $isNum = 0 if($id =~ /-/);
   foreach my $name (keys(%{$main::channels})){
      if($isNum and $main::channels->{$name}{pos} eq $id){
         die('no support for channel places (yet)!');
         $rv = $name;
         last;
         }
      elsif(not $isNum and $main::channels->{$name}{id} =~ /$id/){
         $rv = $name;
         last;
         }
      }
   return $rv;
}

sub getEpgData {
   my $line;     
   my $fp;
   my @st = stat($appCfg->getConfig->{'epgCache'});
   my $proxy = IO::Socket::INET->new(Proto=>'tcp'
                                    , PeerAddr=>$appCfg->getProxy->{'host'}
                                   , PeerPort=>$appCfg->getProxy->{'port'});
   if($proxy){
      logmsg('connection established ...');     
      my $tm = defined($st[9])?$st[9]:0;
      my $epgData = '';

      logmsg('last transfer was ['.$tm.']');
      print($proxy 'gepg '.$tm."\n");
      while($line = <$proxy>){
         $epgData .= $line;     
         last if $line =~ /^$/;
         }
      if($epgData =~ /^$/){
         logmsg('getEpgData(): no data this time ...');     
         close($proxy);
         return;
         }
      print($proxy (length($epgData)-1)."\n");
      my $check;
      
      while($line =~ /^$/){ $line = <$proxy>; }
      logmsg('getEpgData(): '.$line);     
      if($line =~ /^OK/){
         if(open($fp, '> '.$appCfg->getConfig->{'epgCache'})){
            print($fp $epgData);     
            close($fp);     
            logmsg('getEpgData(): saved proxy-data to ['.$appCfg->getConfig->{'epgCache'}."]\n");
            }
         }
      else{ logmsg('getEpgData(): proxy said transfer was NOT ok!'."\n"); }
      close($proxy);
      }
   else{ logmsg('Oups, no connection to ['.$appCfg->getProxy->{'host'}.':'.$appCfg->getProxy->{'port'}.']! '.$!); }
}

sub getTimers {
   my @timers = ();

   if($appCfg->getProxy->{'isOffline'}){
      print('Sorry, proxy is offline - so no timers!'."\n");
      return \@timers;
   }
   my $proxy = IO::Socket::INET->new(Proto=>'tcp'
                                    , PeerAddr=>$appCfg->getProxy->{'host'}
                                   , PeerPort=>$appCfg->getProxy->{'port'});
   if($proxy){
      print($proxy 'lstt '."\n");
      my $i = 1;

      while(my $line = <$proxy>){
         if($line =~ /\:/){
            my($status,$channel,$date,$start,$end,$prio,$live,$file,$info) = split(/:/, $line);

            push(@timers, {status=>$status
                            ,id=>$i++
                            ,'channel-id'=>$channel
                            ,channel=>chId2Name($channel)
                          ,date=>$date
                          ,sdate=>localizeDate($date)
                          ,start=>$start
                          ,end=>$end
                          ,sstart=>sprintf('%2s:%02s', substr($start, 0, 2), substr($start, 2, 2))
                          ,send=>sprintf('%2s:%02s', substr($end, 0, 2), substr($end, 2, 2))
                          ,prio=>$prio
                          ,live=>$live
                          ,path=>$file
                          ,info=>$info});
            }
         last if $line =~ /^$/;
         }
      close($proxy);
      print('getTimers() ...'."\n".Dumper(\@timers)."\n") if $debug;
      }
   return \@timers;
}

sub parseEPG {
   my $fpEPG = shift;
   my %currentChannel;
   my %currentEvent;
   my $i = 1;
   my @rv;
   
   while(my $line = <$fpEPG>){
      if($line =~ /^C ([^\s]+) (.+)$/){
         $currentChannel{id}   = $1;
         $currentChannel{name} = $2;
         }
      elsif($line =~ /^E (\d+) (\d+) (\d+) (.+)$/){
         $currentEvent{source}   = {%currentChannel};
         $currentEvent{channel}  = $currentChannel{name};
         $currentEvent{id}       = $1;
         $currentEvent{start}    = $2;
         $currentEvent{duration} = $3;
         $currentEvent{xinfo}    = $4;
         }
      elsif($line =~ /^T (.+)$/){
         $currentEvent{title} = $1;
         $currentEvent{title} =~ s/\|/\n/g;
         }
      elsif($line =~ /^S (.+)$/){
         $currentEvent{subtitle} = $1;
         $currentEvent{subtitle} =~ s/\|/\n/g;
         }
      elsif($line =~ /^D (.+)$/){
         $currentEvent{summary} = $1;
         $currentEvent{summary} =~ s/\|/\n/g;
         $currentEvent{ssize} = length($currentEvent{summary});
         }
      elsif($line =~ /^V (\d+)$/){
         $currentEvent{vps} = $1;
         }
      elsif($line =~ /^X 1 ([^\s]+) ([^\s]+) (.+)/){
         $currentEvent{tvideo}{$1}{$2} = $3;     
         }
      elsif($line =~ /^X 2 ([^\s]+) ([^\s]+) (.+)/){
         $currentEvent{taudio}{$1}{$2} = $3;     
         }      
      elsif($line =~ /^e$/){
         my(undef,$min,$hour,$mday,$mon,undef,$wday,undef,undef) = localtime($currentEvent{start});     
         my @scratch = sort {$b cmp $a} keys(%{$currentEvent{'taudio'}});
         my($audio, $video);

         $audio = $currentEvent{'taudio'}{$scratch[0]}{$appCfg->getLangToken('epgLang')};
         @scratch = sort {$b cmp $a} keys(%{$currentEvent{'tvideo'}});
         $video = $currentEvent{'tvideo'}{$scratch[0]}{$appCfg->getLangToken('epgLang')};
         $audio = '--' if not $audio;
         $video = '--' if not $video;
         $currentEvent{day}    = $appCfg->getLangToken('days')->[$wday];     
         $currentEvent{date}   = sprintf('%2d. %3s', $mday, $appCfg->getLangToken('months')->[$mon]);     
         $currentEvent{time}   = sprintf('%2d:%02d', $hour, $min);
         $currentEvent{len}    = sprintf('%3d min', $currentEvent{duration}/60);
         $currentEvent{aspect} = substr($video, 0, 5);
         $currentEvent{audio}  = substr($audio, 0, 6);
         $currentEvent{s}      = $currentEvent{ssize}?'*':' ';
         push(@rv, {%currentEvent});
         undef(%currentEvent);
         }
      elsif($line =~ /^c$/){}
      elsif($line =~ /^$/){ last; }
      else{
         die('unknown line format at #'.$i.': ['.$line."]\n");      
         }
      $i++;
      }
   return \@rv;
}

sub getChannels {
   my $epgData = shift;
   my $curChannel = 'none';
   my $oldChannel;
   my $i = -1;
   my %rv;

   foreach my $event (@$epgData){
      $i++;
      $curChannel = $event->{channel};
      next if $curChannel eq $oldChannel;     
      $rv{$oldChannel}{last}  = $i-1 if $oldChannel;
      $rv{$curChannel}{first} = $i;
      $rv{$curChannel}{id}    = $event->{source}{id};
      $oldChannel = $curChannel;
      }
   $rv{$oldChannel}{last} = $i if $oldChannel;
   @chNames = sort { uc($a) cmp uc($b) } keys(%rv);

   return \%rv;
}

sub chkEpg {
   my $epgFN = $appCfg->getConfig->{'epgCache'};
   my @st = stat($epgFN);
         
   print('main::chkEpg() --- cache=<'.$epgFN.'>, last check: '.$st[9]."\n") if $debug;
   if(not ref($main::epgData)   eq 'ARRAY'
   or $main::epgAge < $st[9]) {
      my $fp;

      if(open($fp, $epgFN)){
         print('chkEpg() ... gonna (re)parse epg-data ...'."\n");
         $main::epgData  = parseEPG($fp);     
         $main::channels = getChannels($main::epgData);
         close($fp);     
         $main::epgAge   = $st[9];
         }
      }
}

sub getFilteredEvents {
   my $channels = shift;
   my $filter = shift;
   my $xarg = shift;
   my $fnFilter = 'getFiltered'.$filter;
   
   chkEpg();
   print('getFilteredEvents() ... gonna call <'.$fnFilter.">\n") if $debug;
   no strict 'refs';

   return &$fnFilter($main::epgData, $channels, $xarg);
}

sub getFilteredChannel {
   my $epgData = shift;
   my $channels = shift;
   my $xarg = shift;
   my $now = time;
   my @rv;

   return $epgData if not defined($channels->{$xarg});

   for my $i ($channels->{$xarg}{first} .. $channels->{$xarg}{last}){
      push(@rv, $epgData->[$i]) if $appCfg->getConfig->{'showStarted'} eq 'yes' or $epgData->[$i]{start} > $now;     
      }
   return \@rv;
}

sub getFilteredMovie {
   my $epgData = shift;
   my $channels = shift;
   my $xarg = shift;
   my $now = time;
   my @rv;

   foreach my $event (@$epgData){
      if($event->{duration} > 4500){
         push(@rv, $event) if $appCfg->getConfig->{'showStarted'} eq 'yes' or $event->{start} > $now;     
         }
      }
   return \@rv;
}

sub getFilteredDate {
   my $epgData = shift;
   my $channels = shift;
   my $xarg = shift;
   my @rv;

   $xarg = $epgData->[0]{start} if not $xarg;
   my(undef,undef,undef,$chkDay,$chkMon,$chkYear,undef,undef,undef) = localtime($xarg);

   foreach my $event (@$epgData){
      my(undef,undef,undef,$day,$mon,$year,undef,undef,undef) = localtime($event->{start});
           
      push(@rv, $event) if(($appCfg->getConfig->{'showStarted'} eq 'yes' or $event->{start} >= $xarg) 
                          and $day eq $chkDay and $mon eq $chkMon and $year eq $chkYear);
      }
   return \@rv;
}

sub REAPER {
   my $child;
   
   while((my $waitedpid = waitpid(-1,WNOHANG)) > 0){
      print("reaped $waitedpid".($? ? " with exit $?" : '')."\n");
      }
   $SIG{CHLD} = \&REAPER;
}

sub waitPanel {
   my $msg = shift;
   my $cbr = shift;
   my $panel = $mw->Toplevel();
   my $addonsDir = $appCfg->getAppAddonsDir;

   $addonsDir .= '/' if not $addonsDir =~ /\/$/;
   $panel->geometry(sprintf('320x200+%d+%d', $mw->screenwidth()/2 - 160, $mw->screenheight()/2 - 100));
   $panel->overrideredirect(1);
   $panel->Label(-relief=>'groove', -bd=>2, -text=>$msg, -background=>'green')->pack(-expand=>1, -fill=>'x', -side=>'bottom');
   if(-s $addonsDir.'SS-Jobedit.gif'){
      my $img = $panel->Photo(-file=>$addonsDir.'SS-Jobedit.gif');
   
      $panel->Button(-text=>' ', -image=>$img)->pack(-expand=>1, -fill=>'both');
      }
   else{
      $panel->Button(-text=>' ', -height=>11)->pack(-expand=>1, -fill=>'both');
      }
   $mw->after(1000, [\&waitClose, $panel, $cbr]) if $cbr;
}

sub waitClose {
   my $wp = shift;
   my $cbr = shift;
   
   &$cbr();
   $wp->destroy();
}

sub checkConnections {
   my $lastCheck = 0;     
   my $o0 = 0;
   my $o1 = 0;
   
   while(1){     
      $o0 = checkConnection('vdrproxy');
      $o1 = checkConnection('vdr2iso');
      shmwrite($shmID, pack('aa', $o0, $o1), 0, 2) or die("$!");
      if(time() - $lastCheck > 180){
         getEpgData();
         $lastCheck = time();
         }
      sleep(60);
      }
}

sub checkConnection {
   my $target = shift;     
   my $online = 0;
   my $proxy = IO::Socket::INET->new(Proto=>'tcp'
                                    , PeerAddr=>$appCfg->getConfig()->{$target}{'host'}
                                   , PeerPort=>$appCfg->getConfig()->{$target}{'port'}
                                   , Timeout=>1);
   if($proxy){
      my $response = '';
      
      print($proxy 'helo jobedit'."\n");
      while(my $line = <$proxy>){
         $response .= $line;     
         }
      close($proxy);
      if($response =~ /^OLEH (.+)$/){
         logmsg($target.' replied greeting with: '.$1."\n") if $debug;     
         $online = 1;
         }
      }
   return $online;
}

sub updateStatusline {
   my $scratch;
   my @os;
   
   shmread($shmID, $scratch, 0, 2) or die("$!");
   @os = unpack('aa', $scratch);
   if($os[0]){ $appCfg->getProxy->{'isOffline'} = 0; }
   else      { $appCfg->getProxy->{'isOffline'} = 1; }
   if($os[1]){ $appCfg->getWorker->{'isOffline'} = 0; }
   else      { $appCfg->getWorker->{'isOffline'} = 1; }

   $status{'status'}->configure(-text=>' ');
   if($appCfg->getProxy->{'isOffline'}){
      $status{'vdr'}->configure(-text=>$appCfg->getLangToken('vdrIsOffline'));
      }
   else {
      $status{'vdr'}->configure(-text=>$appCfg->getLangToken('vdrIsOnline'));
      }   
   if($appCfg->getWorker->{'isOffline'}){
      $status{'worker'}->configure(-text=>$appCfg->getLangToken('workerIsOffline'));
      }
   else {
      $status{'worker'}->configure(-text=>$appCfg->getLangToken('workerIsOnline'));
      }   
}

sub patchMenue {
   my @rv;
   my $menuLang;
   
   foreach my $menu (@$mainMenu){
      next if not $menu->{name} eq 'view';
      foreach my $submenu (@{$menu->{menu}}){
         if($submenu->{name} eq 'lang'){
            $menuLang = $submenu;
            last;
            }
         }
      }
   foreach ($appCfg->supportedLanguages){
      push(@rv, { name=>$_
                , type=>'R'
                , command=>\&chgLables
                , value=>$_
                , menu=>\$appCfg->getConfig->{'appLang'}});     
      }
   $menuLang->{'menu'} = \@rv;
}

sub initialize {
   $appCfg->loadColors;     
   $appCfg->loadTTF;     

   if(-s $ARGV[0]){
      $mw->title(basename($ARGV[0]).' - Job-Editor');
      $curJob = V2I::Job->load($ARGV[0], $appCfg);
      $curJob->setFilename($ARGV[0]);
      }
   else{ $curJob = V2I::Job->new($appCfg); }
   $main::testOnly = 1;
   $curJob->dump if $debug;
}

sub setupMain {
   initialize();
   $nbMain = $mw->NoteBook;

   $menuFrame = $mw->Frame(-relief=>'raised', -bd=>1);
   $statFrame = $mw->Frame(-relief=>'flat', -bd=>0);
   $diskFrame = $mw->Frame(-relief=>'raised', -bd=>1);
   $menuFrame->form(-top=>'%0',       -left=>'%0',       -right=>'%100');
   $diskFrame->form(-top=>$menuFrame, -left=>'%0',       -right=>'150',  -bottom=>$statFrame);
   $nbMain->form( -top=>$menuFrame, -left=>$diskFrame, -right=>'%100', -bottom=>$statFrame);
   $statFrame->form(-left=>'%0',      -right=>'%100',    -bottom=>'%100');
   $dSetFrame = $nbMain->add('settings', -label=>$appCfg->getLangToken('settings'));
   $recsFrame = $nbMain->add('attRecs', -label=>$appCfg->getLangToken('attRecs'));
   createMenu($mainMenu, $menuFrame);
   $lblDisks = $diskFrame->Label(-text=>$appCfg->getLangToken('tdisks'))->pack(-side=>'top', -fill=>'x');
   $lbDisks = $diskFrame->Scrolled('Listbox', -scrollbars=>'ose')->pack(-fill=>'both', -expand=>1);
   $lbDisks->bind('<Button-1>', \&onDiskChange);
   $lbRecs = RecordListbox->new($recsFrame
                               , -columns=>[qw(settings/demux settings/chapLen settings/language title path)]
                               , -headers=>[$appCfg->getLangToken('hdemux')
                                          , $appCfg->getLangToken('hchapLen')
                                          , $appCfg->getLangToken('hlang')
                                          , $appCfg->getLangToken('htitle')
                                          , $appCfg->getLangToken('hpath')]
                               , -height=>8
                               , -width=>0);
   $lbRecs->bind('<Button-1>', [\&onRecordingChange, $lbRecs]);
   $lbRecs->bind('<Double-Button-1>', [\&editRecording, $lbRecs]);
   $dlgAbout = $mw->Dialog(-title=>$appCfg->getLangToken('about'), -buttons=>['Ok']);
   $main::View = 0;
   $main::vSpecial = 0;
   $main::special = 0;
   $main::page = 0;
   $status{'status'} = $statFrame->Label(-text=>$appCfg->getLangToken('checkStatus'), -relief=>'sunken', -bd=>2)->pack(-side=>'left', -expand=>1, -fill=>'x');
   $status{'vdr'}    = $statFrame->Label(-text=>' ', -relief=>'sunken', -bd=>2)->pack(-side=>'left', -fill=>'x');
   $status{'worker'} = $statFrame->Label(-text=>' ', -relief=>'sunken', -bd=>2)->pack(-side=>'right', -fill=>'x');
   $statFrame->bind('<Destroy>', \&onExit);
   $diskFrame->bind('<Configure>', \&onMainConfigure);
   startInterfaces();
   updateStatusline();
   showJob($curJob);
   $statFrame->repeat(20000, \&updateStatusline);
}

sub stopInterfaces {
   foreach (keys(%jeChilds)){ kill("TERM", $jeChilds{$_}); }
   undef(%jeChilds);
   shmctl($shmID, IPC_RMID, 0) or die("$!");
}

sub startInterfaces {
   my $pid;
   my $size = 8;

   $SIG{CHLD} = \&REAPER;
   $shmID = shmget(IPC_PRIVATE, $size, S_IRWXU) or die("$!");
   shmwrite($shmID, pack('aa', 0, 0), 0, 2) or die("$!");
   if(!defined($pid = fork)){ print('cannot start checker: '.$!); }
   elsif($pid){ 
      $jeChilds{'checker'} = $pid;     
      }
   else{ checkConnections(); }
}

#
# Main
#
$appCfg = V2I::Config->new;
$appLang = $appCfg->getAppLang;
$mw = MainWindow->new;
$mw->title(basename($appCfg->getJobFilename).' - Job-Editor');

if(not -x $appCfg->editorCommand('vdrsync')){
   print('Sorry, but no Job-editor without vdrsync!'."\n");
   exit;
   }
if($appCfg->getConfig->{'geomain'}){ $mw->geometry($appCfg->getConfig->{'geomain'}); }
else{ $mw->geometry(sprintf('810x426+%d+%d', $mw->screenwidth()/2 - 405, $mw->screenheight()/2 - 213)); }
$mw->minsize(810,426);
waitPanel($appCfg->getLangToken('waitMain'), \&setupMain);
MainLoop;
