#!/usr/local/bin/perl -w # -*- perl -*- ###################################################################### # dialogeditor.pl -- Edit dialogs # Copyright (c) 2005 Tero Kivinen # All Rights Reserved. ###################################################################### # Program: dialogeditor.pl # $Source: /u/samba/nwn/bin/RCS/dialogeditor.pl,v $ # Author : $Author: kivinen $ # # (C) Tero Kivinen 2005 # # Creation : 10:10 Oct 25 2005 kivinen # Last Modification : 04:06 May 24 2007 kivinen # Last check in : $Date: 2007/05/30 15:19:46 $ # Revision number : $Revision: 1.3 $ # State : $State: Exp $ # Version : 1.278 # Edit time : 164 min # # Description : Edit dialogs # # $Log: dialogeditor.pl,v $ # Revision 1.3 2007/05/30 15:19:46 kivinen # Fixed to work on windows. # # Revision 1.2 2007/05/23 22:23:00 kivinen # Fixed path splitting to accept windows paths. # # Revision 1.1 2005/10/27 17:08:04 kivinen # Created. # # $EndLog$ # # # ###################################################################### # initialization require 5.6.0; package DialogEditor; use strict; use Getopt::Long; use File::Glob ':glob'; use GffRead; use GffWrite; use Gff; use TlkRead; use Time::HiRes qw(time); use Pod::Usage; use Tk; use Tk::FileSelect; use Tk::ErrorDialog; use Tk::HList; use Tk::Photo; use Tk::Balloon; $Opt::verbose = 0; $Opt::dialog = undef; ###################################################################### # Get version information open(PROGRAM, "<$0") || die "Cannot open myself from $0 : $!"; undef $/; $Prog::program = ; $/ = "\n"; close(PROGRAM); if ($Prog::program =~ /\$revision:\s*([\d.]*)\s*\$/i) { $Prog::revision = $1; } else { $Prog::revision = "?.?"; } if ($Prog::program =~ /version\s*:\s*([\d.]*\.)*([\d]*)\s/mi) { $Prog::save_version = $2; } else { $Prog::save_version = "??"; } if ($Prog::program =~ /edit\s*time\s*:\s*([\d]*)\s*min\s*$/mi) { $Prog::edit_time = $1; } else { $Prog::edit_time = "??"; } $Prog::version = "$Prog::revision.$Prog::save_version.$Prog::edit_time"; $Prog::progname = $0; $Prog::progname =~ s/^.*[\/\\]//g; $| = 1; ###################################################################### # Read rc-file if (defined($ENV{'HOME'})) { read_rc_file("$ENV{'HOME'}/.dialogeditorrc"); } ###################################################################### # Option handling Getopt::Long::Configure("no_ignore_case"); if (!GetOptions("config=s" => \$Opt::config, "verbose|v+" => \$Opt::verbose, "help|h" => \$Opt::help, "display=s" => \$Opt::display, "geometry=s" => \$Opt::geometry, "bg|background=s" => \$Opt::bg, "fg|foreground=s" => \$Opt::fg, "fn|font=s" => \$Opt::fn, "iconic!" => \$Opt::iconic, "name=s" => \$Opt::name, "title=s" => \$Opt::title, "xrm=s@" => \@Opt::xrm, "dialog|d=s" => \$Opt::dialog, "version|V" => \$Opt::version) || defined($Opt::help)) { usage(); } if (defined($Opt::version)) { print("\u$Prog::progname version $Prog::version by Tero Kivinen.\n"); exit(0); } while (defined($Opt::config)) { my($tmp); $tmp = $Opt::config; undef $Opt::config; if (-f $tmp) { read_rc_file($tmp); } else { die "Config file $Opt::config not found: $!"; } } ###################################################################### # Window initialization if (defined($Opt::display)) { $ENV{'DISPLAY'} = $Opt::display; } $DialogEditor::top = MainWindow->new(); if (defined($Opt::fn)) { $DialogEditor::top->optionAdd('*font' => $Opt::fn); } if (defined(@Opt::xrm)) { my($i); foreach $i (@Opt::xrm) { if ($i =~ /^\s*([^:\s]+)\s*:\s*(.*)\s*$/) { $DialogEditor::top->optionAdd($1 => $2); } } } if (defined($Opt::name)) { $DialogEditor::top->iconname($Opt::name); if (defined($Opt::title)) { $DialogEditor::top->title($Opt::title); } else { $DialogEditor::top->title($Opt::name); } } else { $DialogEditor::top->iconname("DialogEditor"); if (defined($Opt::title)) { $DialogEditor::top->title($Opt::title); } else { $DialogEditor::top->title("DialogEditor"); } } if (defined($Opt::geometry)) { $DialogEditor::top->geometry($Opt::geometry); } else { $DialogEditor::top->geometry("700x600"); } if (!defined($Opt::bg)) { $Opt::bg = $DialogEditor::top->cget('background') } if (!defined($Opt::fg)) { $Opt::fg = $DialogEditor::top->cget('foreground') } if (!defined($Opt::bg)) { $Opt::bg = 'gray85'; } if (!defined($Opt::fg)) { $Opt::fg = 'black'; } $DialogEditor::top->setPalette(foreground => $Opt::fg, background => $Opt::bg); if (defined($Opt::iconic) && $Opt::iconic) { $DialogEditor::top->iconify; } ###################################################################### # Frames $DialogEditor::menu_frame = $DialogEditor::top->Frame(-relief => 'raised', -borderwidth => 2) ->pack(-side => 'top', -fill => 'x'); $DialogEditor::data_frame = $DialogEditor::top->Frame(-relief => 'groove', -borderwidth => 3) ->pack(-side => 'top', -fill => 'both', -expand => 1); $DialogEditor::status_frame = $DialogEditor::top->Frame(-relief => 'groove', -borderwidth => 3) ->pack(-side => 'top', -fill => 'x'); $DialogEditor::data_window = MainWindow->new(); $DialogEditor::data_window->geometry("400x400"); ###################################################################### # File open dialog $DialogEditor::fileselect = $DialogEditor::top->FileSelect(); ###################################################################### # Pictures my($dir, $prog, $found, $path_sep); $prog = $0; $prog =~ s/\/[^\/]*//g; $found = 0; if ($^O =~ /^MSWin32/i) { $path_sep = ';'; } else { $path_sep = ':'; } foreach $dir ('', $prog, split(';', $ENV{'PATH'})) { if (-f $dir . '/open.gif') { $DialogEditor::top->Photo('open', -file => $dir . '/open.gif'); $DialogEditor::top->Photo('close', -file => $dir . '/close.gif'); $found = 1; last; } } if ($found == 0) { warn "Could not find open.gif and close.gif"; } ###################################################################### # Menubar $DialogEditor::file_menu = $DialogEditor::menu_frame->Menubutton(-text => 'File', -underline => 0) ->pack(-side => 'left'); $DialogEditor::file_menu->command(-label => 'Open...', -underline => 0, -command => [ \&open_file, $DialogEditor::top]); $DialogEditor::file_menu->separator; $DialogEditor::file_menu->command(-label => 'Quit', -underline => 0, -command => [ \&quit_program, $DialogEditor::top ]); ###################################################################### # Status $DialogEditor::status_frame->Label(-textvariable => \$DialogEditor::status_txt) ->pack(-side => 'left'); $DialogEditor::status_txt = "Initializing..."; ###################################################################### # Start processing if (join(";", @ARGV) =~ /[*?]/) { my(@argv, $i); foreach $i (@ARGV) { push(@argv, bsd_glob($i)); } @ARGV = @argv; } my($i); foreach $i (@ARGV) { $DialogEditor::files{$i}{name} = $i; } if (defined($Opt::dialog) && $Opt::dialog ne "") { $DialogEditor::tlk = TlkRead::read(filename => $Opt::dialog); } $DialogEditor::top->after(0, \&open_files); ###################################################################### # Main loop MainLoop; exit(0); ###################################################################### # Open files sub open_files { my($i); foreach $i (keys %DialogEditor::files) { open_given_file($i); } $DialogEditor::status_txt = "All done"; return; } ###################################################################### # Fill in the hierarchy sub add_hierarchy { my($gff, $hlist, $path, $type, $index, %options) = @_; my($i, $key, $subkey, $next, $str, $subgff, $keys, $widget); if ($type eq 'E') { $key = 'EntryList'; $subkey = 'RepliesList'; $next = 'R'; } else { $key = 'ReplyList'; $subkey = 'EntriesList'; $next = 'E'; } $str = $$gff{$key}[$index]{Text}{0}; if (!defined($str)) { my($res); $res = $$gff{$key}[$index]{'Text. ____string_ref'}; if (defined($DialogEditor::tlk)) { $str = $DialogEditor::tlk->string($res); } if (!defined($str)) { $str = "String Ref: " . $res; } } if (defined($options{IsChild}) && $options{IsChild} == 1) { $str = " -> link to " . $type . $index . " (" . $str . ")"; $options{Text} = $str; $widget = $hlist->add($path, -text => $type . $index . ": " . $str); $DialogEditor::data{$path} = \%options; } else { $subgff = $gff->value("/" . $key . '[' . $index .']/'); $options{Text} = $str; $widget = $hlist->add($path, -text => $type . $index . ": " . $str); foreach $i (sort $subgff->struct_keys()) { next if ($i =~ /^Text./); if (ref($$gff{$key}[$index]{$i}) eq '') { $options{$i} = $$gff{$key}[$index]{$i}; if ($$gff{$key}[$index]{$i} ne '') { $str .= "\n" . $i . ": " . $$gff{$key}[$index]{$i}; } } } $DialogEditor::data{$path} = \%options; $keys = $#{$$gff{$key}[$index]{$subkey}}; if ($keys != -1) { $hlist->indicator('create', $path, -itemtype => 'imagetext', -image => 'close'); } for($i = 0; $i <= $keys; $i++) { add_hierarchy($gff, $hlist, $path . "." . $i, $next, $$gff{$key}[$index]{$subkey}[$i]{Index}, Active => $$gff{$key}[$index]{$subkey}[$i]{Active}, IsChild => $$gff{$key}[$index]{$subkey}[$i]{IsChild}); } } } ###################################################################### # Show info about current item sub show_info { my($filename, $path) = @_; my($i, $tmp, $hlist); $hlist = $DialogEditor::files{$filename}{hlist}; destroy $DialogEditor::info_frame; $DialogEditor::info_frame = $DialogEditor::data_window->Frame(-borderwidth => 0) ->pack(-side => 'top', -anchor => 'nw', -expand => 1, -fill => 'x'); foreach $i (sort keys %{$DialogEditor::data{$path}}) { $tmp = $DialogEditor::info_frame->Frame() ->pack(-side => 'top', -fill => 'both', -expand => 1); $tmp->Label(-text => $i . ": ", -width => 20, -anchor => 'e', -justify => 'right') ->pack(-side => 'left'); if ($i eq 'Text') { $tmp->Entry(-textvariable => $DialogEditor::data{$path}{$i}) ->pack(-side => 'left', -fill => 'both', -expand => 1); } else { $tmp->Entry(-textvariable => $DialogEditor::data{$path}{$i}) ->pack(-side => 'left'); } } if ($hlist->info('hidden', $path)) { $path =~ s/\.[^.]$//g; $hlist->indicator('create', $path, -itemtype => 'imagetext', -image => 'close'); foreach $i ($hlist->info('children', $path)) { $hlist->show('entry', $i); } } } ###################################################################### # Show or hide items sub show_or_hide { my($filename, $path, $type) = @_; my($i, $hlist); $hlist = $DialogEditor::files{$filename}{hlist}; return if ($type ne ''); if (!$hlist->info('hidden', $path . ".0")) { $hlist->indicator('create', $path, -itemtype => 'imagetext', -image => 'open'); foreach $i ($hlist->info('children', $path)) { $hlist->hide('entry', $i); } } else { $hlist->indicator('create', $path, -itemtype => 'imagetext', -image => 'close'); foreach $i ($hlist->info('children', $path)) { $hlist->show('entry', $i); } } } ###################################################################### # Open given file sub open_given_file { my($filename) = @_; my($q, $title, $i, $gff); my($w_frame, $h_list_frame, $h_list, $w_generic, $w_sc_y, $w_sc_x, $w_tmp); $gff = $DialogEditor::files{$filename}{gff} = GffRead::read('filename' => $filename); $w_frame = $DialogEditor::data_frame->Frame(-relief => 'groove', -borderwidth => 3) ->pack(-side => 'top', -fill => 'both', -expand => 1); $w_generic = $w_frame->Frame(-borderwidth => 0) ->pack(-side => 'top', -fill => 'x'); $title = $DialogEditor::files{$filename}{name}; $title =~ s-.*/--g; $w_tmp = $w_generic->Frame() ->pack(-side => 'top', -fill => 'both', -expand => 1); $w_tmp->Label(-text => "Filename: " . $title, -width => 20, -anchor => 'e', -justify => 'right') ->pack(-side => 'left'); foreach $i ('DelayEntry', 'DelayReply', 'EndConverAbort', 'EndConversation', 'NumWords', 'PreventZoomIn') { $w_tmp = $w_generic->Frame() ->pack(-side => 'top', -fill => 'both', -expand => 1); $w_tmp->Label(-text => $i . ": ", -width => 20, -anchor => 'e', -justify => 'right') ->pack(-side => 'left'); $w_tmp->Entry(-textvariable => \$$gff{$i}) ->pack(-side => 'left'); } $h_list_frame = $w_frame->Frame(-borderwidth => 0) ->pack(-side => 'left', -fill => 'both', -expand => 1); $h_list = $h_list_frame->HList(-separator => '.', -itemtype => 'text', -indicator => 1, -browsecmd => [ \&show_info, $filename], -indicatorcmd => [ \&show_or_hide, $filename ]) ->pack(-side => 'top', -fill => 'both', -expand => 1); $w_sc_y = $w_frame->Scrollbar(-command => [ $h_list => 'yview' ]) ->pack(-side => 'right', -fill => 'y'); $w_sc_x = $h_list_frame->Scrollbar(-orient => 'horiz', -command => [ $h_list => 'xview' ]) ->pack(-side => 'bottom', -fill => 'x'); $h_list->configure(-xscrollcommand => [ $w_sc_x => 'set' ]); $h_list->configure(-yscrollcommand => [ $w_sc_y => 'set' ]); $DialogEditor::files{$filename}{frame} = $w_frame; $DialogEditor::files{$filename}{hlist} = $h_list; $DialogEditor::info_frame = $DialogEditor::data_window->Frame(-borderwidth => 0) ->pack(-side => 'bottom', -expand => 1, -fill => 'both'); for($i = 0; $i <= $#{$$gff{StartingList}}; $i++) { add_hierarchy($gff, $h_list, $i, 'E', $$gff{StartingList}[$i]{Index}, Active => $$gff{StartingList}[$i]{Active}); } } ###################################################################### # open_file -- open file sub open_file { my($top) = @_; my($file); $file = $DialogEditor::fileselect->Show(); if (defined($file)) { if (!exists($DialogEditor::files{$file})) { $DialogEditor::files{$file}{name} = $file; open_given_file($file); } } } ###################################################################### # quit_program -- quit program sub quit_program { my($top) = @_; destroy $top; } ###################################################################### # Read rc file sub read_rc_file { my($file) = @_; my($next, $space); if (open(RCFILE, "<$file")) { while () { chomp; while (/\\$/) { $space = 0; if (/\s+\\$/) { $space = 1; } s/\s*\\$//g; $next = ; chomp $next; if ($next =~ s/^\s+//g) { $space = 1; } if ($space) { $_ .= " " . $next; } else { $_ .= $next; } } if (/^\s*([a-zA-Z0-9_]+)\s*$/) { eval('$Opt::' . lc($1) . ' = 1;'); } elsif (/^\s*([a-zA-Z0-9_]+)\s*=\s*\"([^\"]*)\"\s*$/) { my($key, $value) = ($1, $2); $value =~ s/\\n/\n/g; $value =~ s/\\t/\t/g; eval('$Opt::' . lc($key) . ' = $value;'); } elsif (/^\s*([a-zA-Z0-9_]+)\s*=\s*(.*)\s*$/) { my($key, $value) = ($1, $2); $value =~ s/\\n/\n/g; $value =~ s/\\t/\t/g; eval('$Opt::' . lc($key) . ' = $value;'); } } close(RCFILE); } } ###################################################################### # Usage sub usage { Pod::Usage::pod2usage(0); } =head1 NAME dialogeditor - Edit nwn dialogs =head1 SYNOPSIS dialogeditor [B<--help>|B<-h>] [B<--version>|B<-V>] [B<--verbose>|B<-v>] [B<--config> I] [B<--dialog>|B<-d> I] I ... dialogeditor B<--help> =head1 DESCRIPTION B is interactive dialog editor using Perl and TK. I is read in and then you can edit and modify it at will. This tool also allows you to save it back to file. =head1 OPTIONS =over 4 =item B<--help> B<-h> Prints out the usage information. =item B<--version> B<-V> Prints out the version information. =item B<--verbose> B<-v> Enables the verbose prints. This option can be given multiple times, and each time it enables more verbose prints. =item B<--config> I All options given by the command line can also be given in the configuration file. This option is used to read another configuration file in addition to the default configuration file. =item B<--dialog> B<-d> I Pointer to the tlk file. If given then it is used to convert string references to strings in case there is no strings in the item item itself. =back =head1 EXAMPLES dialogeditor --dialog ../nwn-server/dialog.tlk x2_skill_ctrap.dlg =head1 FILES =over 6 =item ~/.dialogeditorrc Default configuration file. =back =head1 SEE ALSO gffprint(1), gffencode(1), gffmodify(1), Gff(3), GffWrite(3), and GffRead(3). =head1 AUTHOR Tero Kivinen . =head1 HISTORY This program was written in the Vught meeting to easily read and modify the x2_skil_ctrap.dlg file.