[Bps-public-commit] r9551 - in RT-Client-Console/trunk: . bin lib/RT lib/RT/Client lib/RT/Client/Console lib/RT/Client/Console/Session lib/RT/Client/Console/Session/Ticket t

dams at bestpractical.com dams at bestpractical.com
Mon Nov 5 09:11:10 EST 2007


Author: dams
Date: Mon Nov  5 09:11:08 2007
New Revision: 9551

Added:
   RT-Client-Console/trunk/Makefile.PL
   RT-Client-Console/trunk/README
   RT-Client-Console/trunk/bin/
   RT-Client-Console/trunk/bin/rtconsole   (contents, props changed)
   RT-Client-Console/trunk/lib/
   RT-Client-Console/trunk/lib/RT/
   RT-Client-Console/trunk/lib/RT/Client/
   RT-Client-Console/trunk/lib/RT/Client/Console/
   RT-Client-Console/trunk/lib/RT/Client/Console.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Cnx.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/
   RT-Client-Console/trunk/lib/RT/Client/Console/Session.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/KeyHandler.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Progress.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Root.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Status.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Attachments.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/CustFields.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Header.pm
   RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Links.pm
   RT-Client-Console/trunk/t/

Log:
initial import

Added: RT-Client-Console/trunk/Makefile.PL
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/Makefile.PL	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME                => 'RT::Client::Console',
+    AUTHOR              => 'Damien "dams" Krotkine <dams at zarb.org>',
+    VERSION_FROM        => 'lib/RT/Client/Console.pm',
+    ABSTRACT_FROM       => 'lib/RT/Client/Console.pm',
+    PL_FILES            => {},
+    PREREQ_PM => {
+        'Test::More' => 0,
+        'version'    => 0,
+    },
+    EXE_FILES           => [ 'bin/rtconsole' ],
+    dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+    clean               => { FILES => 'RT-Client-Console-*' },
+);

Added: RT-Client-Console/trunk/README
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/README	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,47 @@
+RT-Client-Console version 0.0.1
+
+[ REPLACE THIS...
+
+  The README is used to introduce the module and provide instructions on
+  how to install the module, any machine dependencies it may have (for
+  example C compilers and installed libraries) and any other information
+  that should be understood before the module is installed.
+
+  A README file is required for CPAN modules since CPAN extracts the
+  README file from a module distribution so that people browsing the
+  archive can use it get an idea of the modules uses. It is usually a
+  good idea to provide version information here so that people can
+  decide whether fixes for the module are worth downloading.
+]
+
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+    perl Makefile.PL
+    make
+    make test
+    make install
+
+
+Alternatively, to install with Module::Build, you can use the following commands:
+
+    perl Build.PL
+    ./Build
+    ./Build test
+    ./Build install
+
+
+
+DEPENDENCIES
+
+None.
+
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2007, Damien "dams" Krotkine
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.

Added: RT-Client-Console/trunk/bin/rtconsole
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/bin/rtconsole	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,181 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use Pod::Usage;
+
+use Config::Tiny;
+
+my %options;
+GetOptions(\%options,
+		   'server=s',
+		   'user=s',
+		   'pass=s',
+		   'queues=s',
+		   'config-file=s',
+		   'generate-config',
+		   'help',
+		  ) or usage();
+$options{help} and usage();
+
+sub usage {
+	pod2usage( {
+				-verbose => 1,
+			   }
+			 );
+}
+
+my $config = {};
+exists $options{$_} and $config->{connection}{$_} = $options{$_} foreach (qw(server user pass));
+exists $options{$_} and $config->{server}{$_} = $options{$_} foreach (qw(queues));
+
+my $config_filename = $ENV{HOME} . '/.rtconcolerc';
+exists $options{'config-file'} && length $options{'config-file'} and $config_filename = $options{'config-file'};
+
+my $config_ini;
+if ($options{'generate-config'}) {
+	if (! -e $config_filename) {
+		$config_ini = Config::Tiny->new() or
+		  die "Couldn't open $config_filename to save the configuration. The error was : $Config::Tiny::errstr \n";
+
+	} else {
+		$config_ini = Config::Tiny->read($config_filename) or
+		  die "Couldn't open $config_filename to save the configuration. The error was : $Config::Tiny::errstr \n";
+	}
+	$config_ini->{$_} = $config->{$_} foreach keys %$config;
+	$config_ini->write($config_filename) or
+		die "Couldn't open $config_filename to save the configuration. The error was : $Config::Tiny::errstr \n";
+	print "wrote configuration to file $config_filename\n";
+	exit;
+}
+
+if (-e $config_filename) {
+	$config_ini = Config::Tiny->read($config_filename) or
+	  die "Couldn't open $config_filename to save the configuration. The error was : $Config::Tiny::errstr \n";
+	$config->{$_} = $config_ini->{$_} foreach keys %$config_ini;
+}
+
+
+use Curses;
+my $curses_handler = new Curses;
+noecho();
+nodelay(1);
+$curses_handler->keypad(1);
+$curses_handler->syncok(1);
+curs_set(0);
+leaveok(1);
+
+# Erase the main window
+
+$curses_handler->erase();
+
+my $queues = $config->{server}{queues};
+my @queues = split(/\s+/, $queues);
+
+use RT::Client::Console;
+RT::Client::Console->run(curses_handler => $curses_handler,
+						 rt_servername => $config->{connection}{server},
+						 rt_username => $config->{connection}{user},
+						 rt_password => $config->{connection}{pass},
+						 queue_ids => \@queues,
+						);
+
+
+
+exit(0);
+
+__END__
+
+=head1 NAME
+
+rtconsole - RT text client console
+
+=head1 VERSION
+
+version 0.1
+
+=head1 USAGE
+
+    rtconsole [options]
+
+=head1 OPTIONS
+
+Options can be set --like=this, --like this, or -l this
+
+=over
+
+=item  --server server_name
+
+Specify the RT server
+
+=item  --user user_name
+
+Specify the user to connect to the server
+
+=item  --pass password
+
+Specify the password to connect to the server
+
+=item  --queues='1 12 54 23 84'
+
+Specify the ids of the queues on the server. That's because for now we can't
+get the list of queues automatically.
+
+=item  --config-file filename
+
+Specify the config file to read. Default $HOME/.rtconsolerc
+
+=item --generate-config
+
+Generates the config file
+
+=item --version
+
+=item --usage
+
+=item --help
+
+=back
+
+=head1 DESCRIPTION
+
+rtconsole is a text client to RT using ncurses.
+
+=head1 FILES
+
+The config file $HOME/.rtconsolerc (see the --config-file option) can be use to
+set options. The format is .ini file style. Here is an example with all
+possible keys/values for now :
+
+  [connection]
+  server=rt.cpan.org
+  user=dams
+  pass=my_password
+
+  [server]
+  queues=1 2 42 12 3
+
+For now there is only one section cannled Connection. More will be added in
+the next versions of this software.
+
+You can generate the config file by using --generate-config. It'll be saved at
+the location sp--config-file, or ad the default location
+
+=head1 AUTHOR
+
+Damien "dams" Krotkine (DAMS at CPAN.org)
+
+=head1 BUGS
+
+There are undoubtedly serious bugs lurking somewhere in this code.
+Bug reports and other feedback are most welcome.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2007, Damien Krotkine. All Rights Reserved.
+This module is free software. It may be used, redistributed
+and/or modified under the terms of Perl itself
+
+=cut

Added: RT-Client-Console/trunk/lib/RT/Client/Console.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,381 @@
+package RT::Client::Console;
+
+use warnings;
+use strict;
+use Carp;
+use version;
+our $VERSION = '0.0.1';
+
+use Params::Validate qw(:all);
+
+# global heap to keep an application-level state
+my %GLOBAL_HEAP = ( curses => { 
+								handler => undef, 
+							   },
+					rt => { cnx => {
+									handler => undef,
+									servername => undef,
+									username => undef,
+									password => undef,
+								   },
+							tickets => {
+										current => undef,
+										attachments => {
+													   current => undef,
+													   total => 0,
+													  },
+										total => 0,
+									   },
+						  },
+					server => {
+							   id_to_queue => {},
+							   name_to_queue => {},
+							  },
+					ui => {
+						   tab => {
+								   current => undef,
+								   total => 0,
+								  },
+						   modal_sessions => [],
+						  },
+#					current_atta => undef,
+#					total_tab => 0,
+#					current_tab => undef,
+					sessions => [],
+				  );
+sub GLOBAL_HEAP { \%GLOBAL_HEAP }
+
+sub run {
+	my ($class, @args) = @_;
+	my %params = validate( @args, { curses_handler => { isa => 'Curses' },
+									rt_servername => 0,
+									rt_username => 0,
+									rt_password => 0,
+									queue_ids => 0,
+								  }
+						 );
+
+
+	$class->GLOBAL_HEAP->{curses}{handler} = delete $params{curses_handler};
+
+	use RT::Client::Console::Session::Root;
+	RT::Client::Console::Session::Root->create();
+
+	use RT::Client::Console::Session::KeyHandler;
+	RT::Client::Console::Session::KeyHandler->create();
+
+	if ( exists $params{rt_servername}) {
+		use RT::Client::Console::Cnx;
+		RT::Client::Console::Cnx->connect(%params);
+	}
+
+	use RT::Client::Console::Session;
+	RT::Client::Console::Session->run();
+	
+}
+
+
+
+use Curses::Forms::Dialog;
+sub error {
+    my ($class, $message) = @_;
+    dialog('Error', BTN_OK, $message, 
+           qw(white red yellow));
+    return;
+}
+
+use Curses::Forms::Dialog::Input;
+sub input_ok_cancel {
+    my ($class, $title, $message, $length) = @_;
+    my ($rv, $value) = input($title, BTN_OK | BTN_CANCEL, $message, $length || 256,
+                             qw(white blue yellow));
+    # XXX bug, if one doesn't enter anything, or an empty string or 0 or '0'...
+    if ( $rv == 0) {
+        return $value
+    }
+}
+
+use Curses::Widgets::ListBox;
+sub input_list {
+    my ($class, %args) = @_;
+	my @items = @{$args{items}};
+
+	my $list_style = 1; #simple
+	ref $items[0] eq 'HASH' and $list_style = 0; #complex
+
+	my @display_items = $list_style ? @items : map { $_->{text} } @items;
+	my @value_items = $list_style ? @items : map { $_->{value} } @items;
+
+	my $i; 
+	my %index_of = map { $_ => $i++ } @value_items;
+	my %name_of = reverse %index_of;
+	my $value_idx = $index_of{$args{value}};
+	my $title = $args{title};
+
+	my ($screen_w, $screen_h);
+	my $curses_handler = $class->GLOBAL_HEAP->{curses}{handler};
+	$curses_handler->getmaxyx($screen_h, $screen_w);
+
+	use List::Util qw(min max);
+	my $height = min(@display_items + 2, $screen_h - 20);
+	my $width = min( max( map { length } (@display_items, $title) ) + 2, $screen_w - 20 );
+
+	my $list_box = Curses::Widgets::ListBox->new({
+												  LINES       => $height,
+												  COLUMNS     => $width,
+												  Y           => $screen_h/2-($height+2)/2,
+												  X           => $screen_w/2-($width+2)/2,,
+												  LISTITEMS   => \@display_items,
+												  MULTISEL    => 0,
+												  VALUE       => $value_idx,
+												  FOCUSSWITCH => "\n",
+												  SELECTEDCOL => 'red',
+												  CAPTION     => $title,
+												  CAPTIONCOL  => 'yellow',
+												  CURSORPOS   => $value_idx,
+												 });
+	$class->my_execute($list_box, $curses_handler);
+	my $new_value = $name_of{$list_box->getField('VALUE')};
+	return $new_value;
+}
+
+
+{
+
+my %label_widgets;
+my $widget_name_index = 0;
+
+sub struct_to_widgets {
+    my ($class, $header_labels, $max_lines, $max_columns) = @_;
+    my @header_labels = @$header_labels;
+
+    my $x = 0;
+    my %label_widgets;
+    foreach my $group (@header_labels) {
+        my $y = 0;
+		use List::Util qw(max);
+        my $key_width = max( map { length } map { $_->[0] } @$group );
+        $x + $key_width > $max_columns
+          and $key_width = $max_columns - $x;
+        $key_width > 0 or last;
+
+        my $value_width = max( map { length } map { $_->[1] || '' } @$group );
+        $x + $key_width + 1 + $value_width > $max_columns
+          and $value_width = $max_columns - ($x + $key_width + 1);
+        
+        foreach my $element (@$group) {
+            
+            $y > $max_lines and last;
+
+            $label_widgets{"label$widget_name_index"} =
+              {
+               TYPE        => 'Label',
+               X           => $x,
+               Y           => $y,
+               COLUMNS     => $key_width,
+               LINES       => 1,
+               FOREGROUND  => 'yellow',
+               BACKGROUND  => 'blue',
+               VALUE       => $element->[0],
+               ALIGNMENT   => 'R',
+              };
+            $widget_name_index++;
+
+            if ($value_width) {
+
+                $label_widgets{"label$widget_name_index"} =
+                  {
+                   TYPE        => 'Label',
+                   X           => $x + $key_width + 1,
+                   Y           => $y,
+                   COLUMNS     => $value_width,
+                   LINES       => 1,
+                   FOREGROUND  => 'white',
+                   BACKGROUND  => 'blue',
+                   VALUE       => $element->[1],
+                   ALIGNMENT   => 'L',
+                  };
+                $widget_name_index++;
+            }
+            $y++;
+        }
+        $x += $key_width + 1 + $value_width +2;
+    }
+    return %label_widgets;
+}
+
+}
+
+sub my_execute {
+  my $class = shift;
+  my $self = shift;
+  my $mwh = shift;
+  my $conf = $self->{CONF};
+  my $func = $$conf{'INPUTFUNC'} || \&Curses::Widgets::scankey;
+  my $regex = $$conf{'FOCUSSWITCH'};
+  my $key;
+
+  $self->draw($mwh, 1);
+
+  while (1) {
+    $key = &$func($mwh);
+    if (defined $key) {
+      $self->input_key($key);
+      if (defined $regex) {
+        return $key if ($key =~ /^[$regex]/);
+      }
+    }
+    $self->draw($mwh, 1);
+  }
+}
+
+1; # Magic true value required at end of module
+__END__
+
+=head1 NAME
+
+RT::Client::Console - [One line description of module's purpose here]
+
+
+=head1 VERSION
+
+This document describes RT::Client::Console version 0.0.1
+
+
+=head1 SYNOPSIS
+
+    use RT::Client::Console;
+
+=for author to fill in:
+    Brief code example(s) here showing commonest usage(s).
+    This section will be as far as many users bother reading
+    so make it as educational and exeplary as possible.
+  
+  
+=head1 DESCRIPTION
+
+=for author to fill in:
+    Write a full description of the module and its features here.
+    Use subsections (=head2, =head3) as appropriate.
+
+
+=head1 INTERFACE 
+
+=for author to fill in:
+    Write a separate section listing the public components of the modules
+    interface. These normally consist of either subroutines that may be
+    exported, or methods that may be called on objects belonging to the
+    classes provided by the module.
+
+
+=head1 DIAGNOSTICS
+
+=for author to fill in:
+    List every single error and warning message that the module can
+    generate (even the ones that will "never happen"), with a full
+    explanation of each problem, one or more likely causes, and any
+    suggested remedies.
+
+=over
+
+=item C<< Error message here, perhaps with %s placeholders >>
+
+[Description of error here]
+
+=item C<< Another error message here >>
+
+[Description of error here]
+
+[Et cetera, et cetera]
+
+=back
+
+
+=head1 CONFIGURATION AND ENVIRONMENT
+
+=for author to fill in:
+    A full explanation of any configuration system(s) used by the
+    module, including the names and locations of any configuration
+    files, and the meaning of any environment variables or properties
+    that can be set. These descriptions must also include details of any
+    configuration language used.
+  
+RT::Client::Console requires no configuration files or environment variables.
+
+
+=head1 DEPENDENCIES
+
+=for author to fill in:
+    A list of all the other modules that this module relies upon,
+    including any restrictions on versions, and an indication whether
+    the module is part of the standard Perl distribution, part of the
+    module's distribution, or must be installed separately. ]
+
+None.
+
+
+=head1 INCOMPATIBILITIES
+
+=for author to fill in:
+    A list of any modules that this module cannot be used in conjunction
+    with. This may be due to name conflicts in the interface, or
+    competition for system or program resources, or due to internal
+    limitations of Perl (for example, many modules that use source code
+    filters are mutually incompatible).
+
+None reported.
+
+
+=head1 BUGS AND LIMITATIONS
+
+=for author to fill in:
+    A list of known problems with the module, together with some
+    indication Whether they are likely to be fixed in an upcoming
+    release. Also a list of restrictions on the features the module
+    does provide: data types that cannot be handled, performance issues
+    and the circumstances in which they may arise, practical
+    limitations on the size of data sets, special cases that are not
+    (yet) handled, etc.
+
+No bugs have been reported.
+
+Please report any bugs or feature requests to
+C<bug-rt-client-console at rt.cpan.org>, or through the web interface at
+L<http://rt.cpan.org>.
+
+
+=head1 AUTHOR
+
+Damien "dams" Krotkine  C<< <dams at zarb.org> >>
+
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Damien "dams" Krotkine C<< <dams at zarb.org> >>. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+
+=head1 DISCLAIMER OF WARRANTY
+
+BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
+YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR, OR CORRECTION.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
+LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
+OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
+THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Cnx.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Cnx.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,87 @@
+package RT::Client::Console::Cnx;
+
+use base qw(RT::Client::Console);
+
+use Params::Validate qw(:all);
+
+sub connect {
+	my ($class, @args) = @_;
+	my %params = validate( @args, { rt_servername => 0,
+									rt_username => 0,
+									rt_password => 0,
+									queue_ids => 0,
+								  }
+						 );
+
+	$params{queue_ids} ||= [];
+
+	if (!$params{rt_servername}) {
+		$params{rt_servername} = $class->input_ok_cancel('Connexion', 'RT server name');
+	}
+	$params{rt_servername} or return;
+
+	use RT::Client::REST;
+	use Error qw(:try);
+ 	try {
+ 		my $rt_handler = RT::Client::REST->new(
+ 											   server  => $params{rt_servername},
+ 											  );
+		if (!(defined $params{rt_username} && defined $params{rt_password})) {
+			use Curses::Forms::Dialog::Logon;
+			use Curses::Forms::Dialog::Input;
+			(my $rv, $params{rt_username}, $params{rt_password}) = logon('connect to RT server', BTN_OK | BTN_CANCEL, 50, qw(white red yellow) );
+		}
+		$rt_handler->login(username => $params{rt_username}, password => $params{rt_password});
+		$class->GLOBAL_HEAP->{rt}{cnx}{handler} = $rt_handler;
+		$class->GLOBAL_HEAP->{rt}{cnx}{servername} = $params{rt_servername};
+		$class->GLOBAL_HEAP->{rt}{cnx}{username} = $params{rt_username};
+		$class->GLOBAL_HEAP->{rt}{cnx}{password} = $params{rt_password};
+
+		if (@{$params{queue_ids}}) {
+
+
+			my $idx = 0;
+			my $rt_handler = $class->GLOBAL_HEAP->{rt}{cnx}{handler};
+
+			use RT::Client::Console::Session::Progress;
+			RT::Client::Console::Session::Progress->add_progress(
+				steps_nb => sub { scalar(@{$params{queue_ids}}) },
+				caption => sub { 'generating queues' },
+				initially => sub { },
+				code => sub { 
+					my $id = $params{queue_ids}->[$idx++];
+					defined $id or return;
+					use RT::Client::REST::Queue;
+					my $queue;
+					try {
+						$queue = RT::Client::REST::Queue->new( rt  => $rt_handler,
+															   id  => $id, )->retrieve();
+					} catch Exception::Class::Base with { my $dummy = 0; };
+					if (defined $queue) {
+						$class->GLOBAL_HEAP->{server}{id_to_queue}{$id} = $queue;
+						$class->GLOBAL_HEAP->{server}{name_to_queue}{$queue->name()} = $queue;
+					}
+					return 1;
+				},
+				finally => sub { },
+			);
+		}
+ 	} catch Exception::Class::Base with {
+ 		$class->error("problem logging in: $@" . shift->message());
+#		print STDERR Dumper(shift); use Data::Dumper;
+#		print STDERR $@ . "\n";
+ 	};
+	return;
+}
+
+sub disconnect {
+	my ($class) = @_;
+	undef $class->GLOBAL_HEAP->{rt}{cnx}{handler};
+	undef $class->GLOBAL_HEAP->{rt}{cnx}{servername};
+	undef $class->GLOBAL_HEAP->{rt}{cnx}{username};
+	undef $class->GLOBAL_HEAP->{rt}{cnx}{password};
+	return;
+}
+
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,99 @@
+package RT::Client::Console::Session;
+
+use base qw(RT::Client::Console);
+
+use POE;
+
+sub create {
+	my ($class, $name, %args) = @_;
+	$args{inline_states}{_start} = sub { 
+		my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+		$kernel->alias_set($name);
+		$kernel->call($name, 'init');
+	};
+	POE::Session->create(%args);
+	push @{$class->GLOBAL_HEAP->{sessions}}, $name;
+}
+
+sub run {
+	my ($class) = @_;
+	$poe_kernel->run();
+}
+
+
+
+
+
+{
+
+my $modal_index = 0;
+
+sub create_modal {
+	my ($class, %args) = @_;
+	
+	my $text = $args{text} . "\n";
+	$args{keys}{c} ||= { text => 'cancel',
+						 code => sub { return 1 },
+					   };
+											
+	while (my ($k, $v) = each %{$args{keys}} ) {
+		$text .= "$k : " . $v->{text} . "\n";
+	}
+	my $height = scalar( () = $text =~ /(\n)/g) + 1;
+	use List::Util qw(max);
+	my $width = max (map { length } (split(/\n/, $text), $args{title}));
+
+	my ($screen_w, $screen_h);
+	my $curses_handler = $class->GLOBAL_HEAP->{curses}{handler};
+	$curses_handler->getmaxyx($screen_h, $screen_w);
+	
+	use Curses::Widgets::Label;
+	my $label = Curses::Widgets::Label->new({
+											 CAPTION     => $args{title},
+											 CAPTIONCOL  => 'yellow',
+											 BORDER      => 1,
+											 LINES       => $height,
+											 COLUMNS     => $width,
+											 Y           => $screen_h/2-($height+2)/2,
+											 X           => $screen_w/2-($width+2)/2,,
+											 VALUE       => $text,
+											 FOREGROUND  => 'white',
+											 BACKGROUND  => 'blue',
+											 BORDERCOL   => 'white',
+											});
+
+
+	my $modal_session_name = 'modal_' . ++$modal_index;
+	POE::Session->create(
+		inline_states => {
+			_start => sub {
+				my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+				$heap->{label} = $label;
+				$kernel->alias_set($modal_session_name);
+			},
+			key_handler => sub {
+				my ( $kernel, $heap, $keystroke ) = @_[ KERNEL, HEAP, ARG0 ];
+				exists $args{keys}->{$keystroke} or return;
+				if ($args{keys}{$keystroke}{code}->()) {
+					# stop modal mode
+					pop @{$class->GLOBAL_HEAP->{modal_sessions}};
+					$kernel->post('key_handler', 'draw_all');
+				} else {
+					$kernel->yield('draw');
+				}
+			},
+			draw => sub {
+				my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+				my $curses_handler = $class->GLOBAL_HEAP->{curses}{handler};
+				$heap->{label}->draw($curses_handler);
+			},
+		},
+	);
+	push @{$class->GLOBAL_HEAP->{modal_sessions}}, $modal_session_name;
+
+}
+
+}
+
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/KeyHandler.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/KeyHandler.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,358 @@
+package RT::Client::Console::Session::KeyHandler;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+
+use Curses;
+
+# class method
+sub create {
+	my ($class) = @_;
+
+	$class->SUPER::create(
+    'key_handler',
+	inline_states => {
+        init => sub {
+ 			my ($kernel, $heap) = @_[ KERNEL, HEAP];
+			print STDERR "key_handler : init\n";
+ 			$kernel->yield('compute_keys');
+ 			$kernel->yield('draw_all');
+
+			# Generate events from console input.  Sets up Curses, too.
+			# use POE::Wheel::MyCurses;
+			$heap->{console} = POE::Wheel::MyCurses->new(
+														 InputEvent => 'handler',
+														);
+		},
+ 		handler => sub {
+ 			my ($kernel, $heap, $keystroke) = @_[ KERNEL, HEAP, ARG0];
+			if ($keystroke ne -1) {
+ 				if ($keystroke lt ' ') {
+ 					$keystroke = '<' . uc(unctrl($keystroke)) . '>';
+ 				} elsif ($keystroke =~ /^\d{2,}$/) {
+ 					$keystroke = '<' . uc(keyname($keystroke)) . '>';
+ 				}
+ 				print STDERR "handler got $keystroke\n";
+				if (@{$class->GLOBAL_HEAP->{modal_sessions}}) {
+ 					print STDERR "modal handler : " . $class->GLOBAL_HEAP->{modal_sessions}->[-1] . "\n";
+					$kernel->call($class->GLOBAL_HEAP->{modal_sessions}->[-1], 'key_handler', $keystroke);
+# 					$kernel->yield('compute_keys');
+ 					$kernel->yield('draw_all');
+				} elsif (exists $heap->{key_to_action}->{$keystroke}) {
+ 					my $action = $heap->{key_to_action}->{$keystroke};
+ 					print STDERR "action : $action, event : $action->{event}\n";
+ 					$kernel->call($action->{session}, $action->{event});
+ 					$kernel->call('key_handler', 'compute_keys');
+ 					$kernel->call('key_handler', 'draw_all');
+ 				}
+			}
+ 		},
+ 		compute_keys => sub {
+ 			my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+ 			my $status_message = '';
+ 			$heap->{key_to_action} = {};
+ 			foreach my $session (@{$class->GLOBAL_HEAP->{sessions}}) {
+ 				my @list = $kernel->call($session, 'available_keys');
+				foreach (@list) {
+					defined && ref or next;
+					my ($key, $message, $event) = @$_;
+					defined $key or next;
+					$status_message .= " | $key: $message";
+					$heap->{key_to_action}->{$key} = { session => $session, event => $event };
+				}
+ 			}
+			$kernel->call('status', 'set_message', $status_message);
+ 			return;
+ 		},
+ 		draw_all => sub {
+ 			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+			noutrefresh();
+			foreach my $session (@{$class->GLOBAL_HEAP->{sessions}}) {
+				$kernel->call($session, 'draw');
+			}
+			foreach my $modal_session (@{$class->GLOBAL_HEAP->{modal_sessions}}) {
+				$kernel->call($modal_session, 'draw');
+			}
+			doupdate();
+ 		}
+	},
+	heap => { 
+			  console => undef,
+			},
+	);
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+package POE::Wheel::MyCurses;
+
+use strict;
+
+use vars qw($VERSION);
+$VERSION = do {my($r)=(q$Revision: 2102 $=~/(\d+)/);sprintf"1.%04d",$r};
+
+use Carp qw(croak);
+#use Curses qw(
+#  initscr start_color cbreak raw noecho nonl nodelay timeout keypad
+#  intrflush meta typeahead mousemask ALL_MOUSE_EVENTS clear refresh
+#  endwin COLS
+#);
+
+use Curses;
+
+use POSIX qw(:fcntl_h);
+use POE qw( Wheel );
+
+
+sub SELF_STATE_READ  () { 0 }
+sub SELF_STATE_WRITE () { 1 }
+sub SELF_EVENT_INPUT () { 2 }
+sub SELF_ID          () { 3 }
+
+sub new {
+  my $type = shift;
+  my %params = @_;
+
+  croak "$type needs a working Kernel" unless defined $poe_kernel;
+
+  my $input_event = delete $params{InputEvent};
+  croak "$type requires an InputEvent parameter" unless defined $input_event;
+
+  if (scalar keys %params) {
+    carp( "unknown parameters in $type constructor call: ",
+          join(', ', keys %params)
+        );
+  }
+
+  # Create the object.
+  my $self = bless
+    [ undef,                            # SELF_STATE_READ
+      undef,                            # SELF_STATE_WRITE
+      $input_event,                     # SELF_EVENT_INPUT
+      &POE::Wheel::allocate_wheel_id(), # SELF_ID
+    ];
+
+  # Set up the screen, and enable color, mangle the terminal and
+  # keyboard.
+
+  #initscr();
+  #start_color();
+
+  #cbreak();
+  #raw();
+  #noecho();
+  #nonl();
+
+  # Both of these achieve nonblocking input.
+  #nodelay(1);
+  #timeout(0);
+
+  keypad(1);
+  intrflush(0);
+  meta(1);
+  typeahead(-1);
+
+  my $old_mouse_events = 0;
+  mousemask(ALL_MOUSE_EVENTS, $old_mouse_events);
+
+#  clear();
+#  refresh();
+
+  # Define the input event.
+  $self->_define_input_state();
+
+  # Oop! Return ourself.  I forgot to do this.
+  $self;
+}
+
+sub _define_input_state {
+  my $self = shift;
+
+  # Register the select-read handler.
+  if (defined $self->[SELF_EVENT_INPUT]) {
+    # Stupid closure tricks.
+    my $event_input = \$self->[SELF_EVENT_INPUT];
+    my $unique_id   = $self->[SELF_ID];
+
+    $poe_kernel->state
+      ( $self->[SELF_STATE_READ] = ref($self) . "($unique_id) -> select read",
+        sub {
+
+          # Prevents SEGV in older Perls.
+          0 && CRIMSON_SCOPE_HACK('<');
+
+          my ($k, $me) = @_[KERNEL, SESSION];
+
+          # Curses' getch() normally blocks, but we've already
+          # determined that STDIN has something for us.  Be explicit
+          # about which getch() to use.
+          while ((my $keystroke = Curses::getch) ne '-1') {
+            $k->call( $me, $$event_input, $keystroke, $unique_id );
+          }
+        }
+      );
+
+    # Now start reading from it.
+    $poe_kernel->select_read( \*STDIN, $self->[SELF_STATE_READ] );
+
+    # Turn blocking back on for STDIN.  Some Curses implementations
+    # don't deal well with non-blocking STDIN.
+    my $flags = fcntl(STDIN, F_GETFL, 0) or die $!;
+    fcntl(STDIN, F_SETFL, $flags & ~O_NONBLOCK) or die $!;
+  }
+  else {
+    $poe_kernel->select_read( \*STDIN );
+  }
+}
+
+sub DESTROY {
+  my $self = shift;
+
+  # Turn off the select.
+  $poe_kernel->select( \*STDIN );
+
+  # Remove states.
+  if ($self->[SELF_STATE_READ]) {
+    $poe_kernel->state($self->[SELF_STATE_READ]);
+    $self->[SELF_STATE_READ] = undef;
+  }
+
+  # Restore the terminal.
+  endwin if COLS;
+
+  &POE::Wheel::free_wheel_id($self->[SELF_ID]);
+}
+
+###############################################################################
+1;
+
+__END__
+
+=head1 NAME
+
+POE::Wheel::Curses - non-blocking Curses.pm input for full-screen console apps
+
+=head1 SYNOPSIS
+
+  use POE;
+  use Curses;  # for unctrl, etc
+  use POE::Wheel::Curses;
+
+  # Generate events from console input.  Sets up Curses, too.
+  $heap->{console} = POE::Wheel::Curses->new(
+    InputEvent => 'got_keystroke',
+  );
+
+  # A keystroke handler.  This is the body of the program's main input
+  # loop.
+  sub keystroke_handler {
+    my ($keystroke, $wheel_id) = @_[ARG0, ARG1];
+
+    # Control characters.  Change them into something printable via
+    # Curses' unctrl function.
+
+    if ($keystroke lt ' ') {
+      $keystroke = '<' . uc(unctrl($keystroke)) . '>';
+    }
+
+    # Extended keys get translated into their names via Curses'
+    # keyname function.
+
+    elsif ($keystroke =~ /^\d{2,}$/) {
+      $keystroke = '<' . uc(keyname($keystroke)) . '>';
+    }
+
+    # Just display it.
+    addstr( $heap->{some_window}, $keystroke );
+    noutrefresh( $heap->{some_window} );
+    doupdate;
+  }
+
+=head1 DESCRIPTION
+
+Many console programs work best with full-screen input: top, systat,
+nethack, and various text editors.  POE::Wheel::Curses provides a
+simple way to add full-screen interfaces to POE programs.
+
+Whenever something occurs on a recognized input device-- usually just
+the keyboard, but also sometimes the mouse, as in the case of
+ncurses-- the Curses wheel will emit a predetermined event to tell the
+program about it.  This lets the program do other non-blocking things
+in between keystrokes, like interact on sockets or watch log files or
+move monsters or highlight text or something.
+
+=head1 PUBLIC METHODS
+
+=over 2
+
+=item new NOT_SO_MANY_THINGS
+
+new() creates a new Curses wheel.  Note, though, that there can be
+only one Curses wheel in any given program, since they glom onto
+*STDIN real hard.  Maybe this will change.
+
+new() always returns a Curses wheel reference, even if there is a
+problem glomming onto *STDIN or otherwise initializing curses.
+
+new() accepts only one parameter so far: InputEvent.  InputEvent
+contains the name of the event that the Curses wheel will emit
+whenever there is input on the console or terminal.
+
+=back
+
+=head1 EVENTS AND PARAMETERS
+
+=over 2
+
+=item InputEvent
+
+InputEvent defines the event that will be emitted when the Curses
+wheel detects and receives input.
+
+InputEvent is accompanied by two parameters:
+
+C<ARG0> contains the raw keystroke as received by Curses' getch()
+function.  It may be passed to Curses' unctrl() and keyname()
+functions for further processing.
+
+C<ARG1> contains the ID of the Curses wheel.
+
+=back
+
+=head1 SEE ALSO
+
+curses, Curses, POE::Wheel.
+
+The SEE ALSO section in L<POE> contains a table of contents covering
+the entire POE distribution.
+
+=head1 BUGS
+
+Curses implementations vary widely, and Wheel::Curses was written on a
+system sporting ncurses.  The functions used may not be the same as
+those used on systems with other curses implementations, and Bad
+Things might happen.  Please send patches.
+
+=head1 AUTHORS & COPYRIGHTS
+
+Please see L<POE> for more information about authors and contributors.
+
+=cut

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Progress.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Progress.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,106 @@
+package RT::Client::Console::Session::Progress;
+
+use base qw(RT::Client::Console::Session);
+
+use POE;
+
+my @progress_texts = ();
+
+# class method
+sub create {
+	my ($class) = @_;
+
+	$class->SUPER::create(
+		'progress_draw',
+		inline_states => {
+						  draw => sub {
+							  my ( $kernel, $heap) = @_[ KERNEL, HEAP ];
+							  my $draw_x = 0;
+							  my @toremove = ();
+							  my ($screen_w, $screen_h);
+							  my $curses_handler = RT::Client::Console->GLOBAL_HEAP->{curses}{handler};
+							  $curses_handler->getmaxyx($screen_h, $screen_w);
+
+							  my $label = Curses::Widgets::Label->new({
+										BORDER      => 0,
+										X           => 0,
+										Y           => $screen_h - 1,
+										COLUMNS     => $screen_w,
+										LINES       => 1,
+										VALUE       => '',
+										FOREGROUND  => 'black',
+										BACKGROUND  => 'black',
+									});
+							  $label->draw($curses_handler);
+							  
+							  foreach my $pos (0.. at progress_texts-1) {
+								  my ($text, $erase) = @{$progress_texts[$pos]};
+								  length $text or next;
+								  
+								  if ($erase) {
+									  push @toremove, $pos;
+								  } else {
+									  $label = Curses::Widgets::Label->new({
+										BORDER      => 0,
+										LINES       => 1,
+										COLUMNS     => length($text),
+										Y           => $screen_h - 1,
+										X           => $draw_x,
+										VALUE       => $text,
+										FOREGROUND  => 'white',
+										BACKGROUND  => 'red',
+										});
+									  $label->draw($curses_handler);
+									  $draw_x += length($text) + 1;
+								  }
+							  }
+							  foreach(@toremove) {
+								  splice(@progress_texts, $_, 1);
+							  }
+						  },
+						 },
+						 );
+}
+
+sub add_progress {
+	my ($class, %args) = @_;
+
+	my $progress_text = ['', 0];
+	push @progress_texts, $progress_text;
+
+	my $progress_session = POE::Session->create(
+		inline_states => {
+			_start => sub {
+				my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+				$heap->{value} = 0;
+				$args{initially}->();
+				$kernel->yield('draw');
+				$kernel->yield('code');
+			},
+			code => sub {
+				my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+				if ($args{code}->()) {
+					$heap->{value}++;
+					$kernel->yield('draw');
+					$kernel->yield('code');
+				} else {
+					$args{finally}->();
+					$kernel->yield('draw', 1);
+				}
+			},
+			draw => sub {
+				my ( $kernel, $heap, $erase ) = @_[ KERNEL, HEAP, ARG0 ];
+
+				my $value = $args{caption}->() . ':' . int($heap->{value}*100/($args{steps_nb}->()||1)) . '%';
+				$erase and $value = ' ' x length $value;
+
+				$progress_text->[0] = $value;
+				$progress_text->[1] = $erase;
+
+				$kernel->post('key_handler', 'draw_all');
+			},
+		},
+	);
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Root.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Root.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,62 @@
+package RT::Client::Console::Session::Root;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+
+# class method
+sub create {
+	my ($class) = @_;
+
+	$class->SUPER::create(
+    'root',
+	inline_states => {
+		init => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+			print STDERR "root : init\n";
+			$class->GLOBAL_HEAP->{curses}{handler}->clear();
+			$kernel->yield('create_status_session');
+			$kernel->yield('create_progress_session');
+		},
+		available_keys => sub {
+			my @available_list = ();
+			if (!$class->GLOBAL_HEAP->{rt}{cnx}{handler}) {
+				push @available_list, ['s', 'connect to RT server', 'connect_server'];
+			}
+			if ($class->GLOBAL_HEAP->{rt}{cnx}{handler}) {
+				push @available_list, ['d', 'disconnect from RT server', 'disconnect_server'];
+				if ($class->GLOBAL_HEAP->{rt}{tickets}{current}) {
+					push @available_list, ['c', 'close current ticket', 'close_ticket'];
+				} else {
+					push @available_list, ['o', 'open a ticket', 'open_ticket'];
+				}
+			}
+			return @available_list;
+		},
+		create_status_session => sub {
+			use RT::Client::Console::Session::Status;
+			RT::Client::Console::Session::Status->create();
+		},
+		create_progress_session => sub {
+			use RT::Client::Console::Session::Progress;
+			RT::Client::Console::Session::Progress->create();
+		},
+		connect_server => sub {
+			use RT::Client::Console::Cnx;
+			RT::Client::Console::Cnx->connect();
+		},
+		disconnect_server => sub {
+			use RT::Client::Console::Cnx;
+			RT::Client::Console::Cnx->disconnect();
+		},
+		open_ticket => sub {
+			use RT::Client::Console::Session::Ticket;
+			RT::Client::Console::Session::Ticket->create();
+		},
+	}
+	);
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Status.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Status.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,61 @@
+package RT::Client::Console::Session::Status;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+# class method
+sub create {
+	my ($class) = @_;
+
+	$class->SUPER::create(
+        'status',
+		inline_states => {
+			init => sub {
+				my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+				# Get the main screen max y & X
+				my ($screen_w, $screen_h);
+				$class->GLOBAL_HEAP->{curses}{handler}->getmaxyx($screen_h, $screen_w);
+	
+				$heap->{'pos_x'} = 0;
+				$heap->{'pos_y'} = $screen_h - 4;
+				$heap->{width} = $screen_w-2;
+				$heap->{height} = 1;
+			},
+			set_message => sub {
+				my ($kernel, $heap, $message) = @_[ KERNEL, HEAP, ARG0 ];
+				$heap->{message} = $message;
+			},
+			draw => sub { 
+				my ($kernel,$heap) = @_[ KERNEL, HEAP ];
+				my $label;
+	
+				# Render the comment box
+				use Curses::Widgets::Label;
+				$label = Curses::Widgets::Label->new({
+													  CAPTION     => ' Keys ',
+													  BORDER      => 1,
+													  LINES       => $heap->{height},
+													  COLUMNS     => $heap->{width},
+													  Y           => $heap->{'pos_y'},
+													  X           => $heap->{'pos_x'},
+													  VALUE       => $heap->{message},
+													  FOREGROUND  => 'white',
+													  BACKGROUND  => 'blue',
+													  BORDERCOL   => 'black',
+													 });
+				#refresh;
+				$label->draw($class->GLOBAL_HEAP->{curses}{handler});
+			},
+		},
+		heap => { 'pos_x' => 0,
+				  'pos_y' => 0,
+				  'width' => 0,
+				  'height' => 0,
+				  'message' => 'default',
+				},
+	);
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,55 @@
+package RT::Client::Console::Session::Ticket;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use RT::Client::REST;
+
+use POE;
+
+sub load_ticket {
+	my ($class, $rt_handler, $id) = @_;
+
+	use RT::Client::REST::Ticket;
+	my $t = RT::Client::REST::Ticket->new(
+										  rt  => $rt_handler,
+										  id  => $id,
+										 );
+	$t->retrieve();
+	return $t;
+}
+
+# class method
+sub create {
+	my ($class) = @_;
+
+	if (my $id = $class->input_ok_cancel('Open a ticket', 'Ticket number')) {
+
+		use Error qw(:try);
+		try {
+
+			my $ticket = $class->load_ticket($class->GLOBAL_HEAP->{rt}{cnx}{handler}, $id);
+			$class->GLOBAL_HEAP->{rt}{tickets}{current} = $ticket;
+
+			use RT::Client::Console::Session::Ticket::Header;
+			RT::Client::Console::Session::Ticket::Header->create();
+
+			use RT::Client::Console::Session::Ticket::CustFields;
+			RT::Client::Console::Session::Ticket::CustFields->create();
+
+			use RT::Client::Console::Session::Ticket::Links;
+			RT::Client::Console::Session::Ticket::Links->create();
+
+			use RT::Client::Console::Session::Ticket::Attachments;
+			RT::Client::Console::Session::Ticket::Attachments->create();
+
+		} catch Exception::Class::Base with {
+			$class->error("problem opening/retrieving rt $rt_num : " . shift->message());
+			return;
+		};
+
+	}
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Attachments.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Attachments.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,193 @@
+package RT::Client::Console::Session::Ticket::Attachments;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+use Error qw(:try);
+
+use POE;
+
+# class method
+sub create {
+	my ($class) = @_;
+	$class->SUPER::create(
+	'ticket_attachments',
+	inline_states => {
+		init => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+
+
+			my ($screen_w, $screen_h);
+			$class->GLOBAL_HEAP->{curses}{handler}->getmaxyx($screen_h, $screen_w);
+
+			$heap->{'pos_x'} = 0;
+			$heap->{'pos_y'} = 1+5+5;
+			$heap->{width} = $screen_w * 2 / 3 - 2;
+			$heap->{height} = $screen_h - 5 - 7 - 5;
+
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+
+		},
+		available_keys => sub {
+			return (['<KEY_NPAGE>', 'next attachment', 'next_attachment'],
+					['<KEY_PPAGE>', 'previous attachment', 'prev_attachment']
+				   );
+		},
+		next_attachment => sub {
+			my ( $kernel, $heap) = @_[ KERNEL, HEAP ];
+			$class->GLOBAL_HEAP->{rt}{attachments}{current}++;
+			$class->GLOBAL_HEAP->{rt}{attachments}{current} > $class->GLOBAL_HEAP->{rt}{attachments}{total} - 1
+			  and $class->GLOBAL_HEAP->{rt}{attachments}{current} = $class->GLOBAL_HEAP->{rt}{attachments}{total} - 1;
+			$kernel->call('key_handler', 'draw_all');
+		},
+		prev_attachment => sub {
+			my ( $kernel, $heap) = @_[ KERNEL, HEAP ];
+			$class->GLOBAL_HEAP->{rt}{attachments}{current}--;
+			$class->GLOBAL_HEAP->{rt}{attachments}{current} < 0
+			  and $class->GLOBAL_HEAP->{rt}{attachments}{current} = 0;
+			$kernel->call('key_handler', 'draw_all');
+		},
+		draw => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+			my $label;
+
+			if (!defined($heap->{attachments})) {
+				$class->_generate_job($kernel, $heap);
+			}
+			defined($heap->{attachments}) or return;
+			my $total = $class->GLOBAL_HEAP->{rt}{attachments}{total};
+			$total > 0 or return;
+			$class->GLOBAL_HEAP->{rt}{attachments}{current} ||= 0;
+
+			my $idx = $class->GLOBAL_HEAP->{rt}{attachments}{current};
+
+			my $attachment = $heap->{attachments}->[$idx];
+
+			my $text = '...loading...';
+			my $user_details = '';
+
+			if (defined $attachment) {
+				try {
+					my $user_id = $attachment->creator_id();
+					use RT::Client::REST::User;
+
+					my ($user, $user_name, $user_email, $user_real_name, $user_gecos, $user_comments)
+					  = _get_user_details( rt  => $class->GLOBAL_HEAP->{rt}{cnx}{handler},
+										   id  => $user_id,
+										 );
+					$user_details = "By : $user_real_name ($user_name) <$user_email>";
+					
+				} catch Exception::Class::Base with {
+					my $e = shift;
+					warn ref($e), ": ", $e->message || $e->description, "\n";
+				};
+				
+				$text = $class->as_text($attachment);
+			}
+			my $title = 'Attachment ' . ($idx + 1) . " / $total - $user_details";
+
+			use Curses::Widgets::ListBox;
+#my $widget = Curses::Widgets::ListBox->new({
+#  Y           => 2,
+#  X           => 38,
+#  COLUMNS     => 20,
+#  LISTITEMS   => ['Ham', 'Eggs', 'Cheese', 'Hash Browns', 'Toast'],
+#  MULTISEL    => 1,
+#  VALUE       => [0, 2],
+#  SELECTEDCOL => 'green',
+#  CAPTION     => 'List Box',
+#  CAPTIONCOL  => 'yellow',
+#  });
+
+			use Curses::Widgets::TextMemo;
+ 			my $widget = Curses::Widgets::TextMemo->new(
+ 					{
+ 					 X           => $heap->{'pos_x'},
+ 					 Y           => $heap->{'pos_y'},
+ 					 COLUMNS     => $heap->{width},
+ 					 LINES       => $heap->{height},
+ 					 MAXLENGTH   => undef,
+ 					 FOREGROUND  => 'white',
+ 					 BACKGROUND  => 'black',
+ 					 VALUE       => $text,
+ 					 BORDERCOL   => 'blue',
+ 					 BORDER      => 1,
+ 					 CAPTION     => $title,
+ 					 CAPTIONCOL  => 'yellow',
+ 					 READONLY    => 1,
+ 					}
+ 			);
+#			$widget->execute($class->GLOBAL_HEAP->{curses}{handler});
+			$widget->draw($class->GLOBAL_HEAP->{curses}{handler});
+		},
+	},
+	heap => { 'pos_x' => 0,
+			  'pos_y' => 0,
+			  'width' => 0,
+			  'height' => 0,
+			},
+	);
+}
+
+
+use Memoize;
+memoize('_get_user_details');
+
+sub _get_user_details {
+	my (%args) = @_;
+	my $user = RT::Client::REST::User->new( %args )->retrieve;
+	my $user_name = $user->name();
+	my $user_email = $user->email_address();
+	my $user_real_name = $user->real_name();
+	my $user_gecos = $user->gecos();
+	my $user_comments = $user->comments();
+	return ($user, $user_name, $user_email, $user_real_name, $user_gecos, $user_comments);
+}
+
+sub as_text {
+    my ($class, $attachment) = @_;
+	my $s = 'content :(' . $attachment->content_type() . ')' . "\n"
+	      . 'subject :{' . $attachment->subject() . '}' . "\n"
+	      . 'filename:{' . $attachment->file_name() . '}';
+    if ($attachment->content_type eq 'text/plain') {
+        return $s . "\n\n" . $attachment->content();
+    } elsif ($attachment->content_type eq 'multipart/mixed') {
+        return $s . "\n\n[" . $attachment->content() . "]\n";
+	}
+	else {
+        return $s;
+    }
+}
+
+sub _generate_job {
+	my ($class, $kernel, $heap) = @_;
+	$heap->{attachments} = [];
+
+	my @ids;
+	my $idx = 0;
+	my $rt_handler = $class->GLOBAL_HEAP->{rt}{cnx}{handler};
+	my $iterator;
+	use RT::Client::Console::Session::Progress;
+	RT::Client::Console::Session::Progress->add_progress(
+			steps_nb => sub { $class->GLOBAL_HEAP->{rt}{attachments}{total} },
+			caption => sub { 'attachments' },
+			initially => sub {
+				my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+				my $attachments_obj = $ticket->attachments();
+				my $count = $attachments_obj->count();
+				$class->GLOBAL_HEAP->{rt}{attachments}{total} = $count;
+				$iterator = $attachments_obj->get_iterator();
+			},
+			code => sub {
+				my $attachment = $iterator->();
+				defined $attachment or return;
+				push @{$heap->{attachments}}, $attachment;
+				$idx++ or $kernel->call('key_handler', 'draw_all');
+				return 1;
+			},
+			finally => sub { },
+	);
+}
+
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/CustFields.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/CustFields.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,134 @@
+package RT::Client::Console::Session::Ticket::CustFields;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+
+# class method
+sub create {
+	my ($class) = @_;
+	$class->SUPER::create(
+    'ticket_custfields',
+	inline_states => {
+		init => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+			print STDERR "ticket_custfields : init\n";
+
+			my ($screen_w, $screen_h);
+			$class->GLOBAL_HEAP->{curses}{handler}->getmaxyx($screen_h, $screen_w);
+
+			$heap->{'pos_x'} = 0;
+			$heap->{'pos_y'} = 8;
+			$heap->{width} = $screen_w * 2 / 3;
+			$heap->{height} = 3;
+
+		},
+		available_keys => sub {
+			return (['u', 'change custom fields', 'change_custfields']);
+		},
+		change_custfields => sub {
+			my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+			$heap->{change_custfields_mode} = 1;
+			my $text = qq(
+
+ c : cancel
+);
+			my $height = scalar( () = $text =~ /(\n)/g) + 1;
+			use List::Util qw(max);
+			my $title = ' Change ticket custom fields ';
+			my $width = max (map { length } (split(/\n/, $text), $title));
+			my ($screen_w, $screen_h);
+			my $curses_handler = $class->GLOBAL_HEAP->{curses}{handler};
+			$curses_handler->getmaxyx($screen_h, $screen_w);
+
+			use Curses::Widgets::Label;
+			my $label = Curses::Widgets::Label->new({
+													 CAPTION     => $title,
+													 CAPTIONCOL  => 'yellow',
+													 BORDER      => 1,
+													 LINES       => $height,
+													 COLUMNS     => $width,
+													 Y           => $screen_h/2-($height+2)/2,
+													 X           => $screen_w/2-($width+2)/2,,
+													 VALUE       => $text,
+													 FOREGROUND  => 'white',
+													 BACKGROUND  => 'blue',
+													 BORDERCOL   => 'white',
+													});
+			$label->draw($curses_handler);
+			$class->GLOBAL_HEAP->{modal_session} = 'ticket_custfields';
+		},
+		modal_handler => sub {
+			my ( $kernel, $heap, $keystroke) = @_[ KERNEL, HEAP, ARG0 ];
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+			if ($keystroke eq 'c' || $keystroke eq '<^[>') {
+				delete $class->GLOBAL_HEAP->{modal_session};
+			} else {
+				$kernel->yield('change_custfields');
+			}
+			return;
+		},
+		draw => sub { 
+			my ( $kernel, $heap) = @_[ KERNEL, HEAP ];
+			my $label;
+
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+			my @custom_fields = sort $ticket->cf();
+			use POSIX qw(floor);
+			my $per_col = POSIX::floor(@custom_fields/3);
+			my @custom_fields_labels;
+
+			# first 2 column
+			foreach (1..2) {
+				push @custom_fields_labels, 
+				  [
+				   map {
+					   [ "$_:", (defined $ticket->cf($_) ? $ticket->cf($_) : '') ],
+				   } splice @custom_fields, 0, $per_col
+				  ];
+				
+			}
+			# third column
+			push @custom_fields_labels, 
+			  [
+			   map {
+				   [ "$_:", $ticket->cf($_) ],
+			   } @custom_fields
+			  ];
+			
+			my %custom_fields_widgets = $class->struct_to_widgets(\@custom_fields_labels, $heap->{height}, $heap->{width});
+			
+			use Curses::Forms;
+			my $form = Curses::Forms->new({
+										   X           => $heap->{'pos_x'},
+										   Y           => $heap->{'pos_y'},
+										   COLUMNS     => $heap->{width},
+										   LINES       => $heap->{height},
+										   
+										   BORDER      => 0,
+										   BORDERCOL   => 'blue',
+										   FOREGROUND  => 'white',
+										   BACKGROUND  => 'blue',
+										   DERIVED     => 1,
+										   #        AUTOCENTER  => 1,
+										   TABORDER    => [],
+										   FOCUSED     => 'label1',
+										   WIDGETS     => \%custom_fields_widgets,
+										  },
+										 );
+			$form->draw($class->GLOBAL_HEAP->{curses}{handler});
+			#						refresh($mwh);
+		},
+	},
+	heap => { 'pos_x' => 0,
+			  'pos_y' => 0,
+			  'width' => 0,
+			  'height' => 0,
+			  change_custfields_mode => 0,
+			},
+	);	  
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Header.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Header.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,159 @@
+package RT::Client::Console::Session::Ticket::Header;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+
+# class method
+sub create {
+	my ($class) = @_;
+
+	$class->SUPER::create(
+    'ticket_header',
+	inline_states => {
+		init => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+
+			my ($screen_w, $screen_h);
+			$class->GLOBAL_HEAP->{curses}{handler}->getmaxyx($screen_h, $screen_w);
+
+			$heap->{'pos_x'} = 0;
+			$heap->{'pos_y'} = 1;
+			$heap->{width} = $screen_w * 2 / 3 - 2;
+			$heap->{height} = 5;
+
+		},
+		available_keys => sub {
+			return (['h', 'change ticket header', 'change_header']);
+		},
+		change_header => sub {
+			my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+			$class->create_modal( title => ' Change ticket headers ',
+								  text => '',
+								  keys => {
+										   s => { text => 'change subject',
+												  code => sub {
+													  if (my $new_subject = $class->input_ok_cancel(' Change subject ', $ticket->subject(), 500)) {
+														  $ticket->subject($new_subject);
+														  return 1; # stop modal mode
+													  }
+												  }
+												},
+										   t => { text => 'change status',
+												  code => sub {
+													  if (my $new_status = $class->input_list(title => ' Change status ',
+																							  items => [ qw(new open resolved stalled rejected deleted) ],
+																							  value => $ticket->status(),
+																							 )) {
+														  $ticket->status($new_status);
+														  return 1; # stop modal mode
+													  }
+												  }
+												},
+										   q => { text => 'change queue',
+												  code => sub {
+
+													  my $queues = $class->GLOBAL_HEAP->{server}{id_to_queue};
+
+													  my @queues_list_items;
+													  while (my ($id, $queue) = each %$queues) {
+														  push @queues_list_items, { text => $queue->name() . ' - ' . $queue->description(),
+																					 value => $id,
+																				   };
+													  };
+													  @queues_list_items = sort { $a->{text} cmp $b->{text} } @queues_list_items;
+
+													  if (my $new_queue_id = $class->input_list(title => ' Change queue ',
+																								items => [ @queues_list_items ],
+																								value => $class->GLOBAL_HEAP->{server}{name_to_queue}{$ticket->queue()}->id(),
+																							   )) {
+														  
+														  my $new_queue_name = $class->GLOBAL_HEAP->{server}{id_to_queue}{$new_queue_id}->name();
+														  $ticket->queue($new_queue_name);
+														  return 1; # stop modal mode
+													  }
+												  } 
+												},
+										   p => { text => 'change priority',
+												  code => sub {
+													  if (my $new_priority = $class->input_ok_cancel(' Change priority ', $ticket->priority(), 20)) {
+														  $ticket->priority($new_priority);
+														  return 1; # stop modal mode
+													  }
+												  }
+												},
+										   o => { text => 'change owner',
+												  code => sub {},
+												},
+										  },
+								);
+		},
+		draw => sub { 
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+			my $label;
+
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+			my @requestors = $ticket->requestors();
+			my @requestor_text_list = map {
+				[ 'Requestor ' . $_ . ':' => $requestors[$_-1] ]
+			} (1.. at requestors);
+						
+			my @header_labels = (
+								 # first column
+								 [ [ 'Id:'       => $ticket->id() ],
+								   [ 'Status:'   => $ticket->status() ],
+								   [ 'Queue:'    => $ticket->queue() ],
+								   [ 'Priority:' => $ticket->priority() ],
+								 ],
+								 
+								 # second column
+								 [ [ 'Owner:' => $ticket->owner() ],
+								   @requestor_text_list,
+								   [ 'Cc:' => $ticket->cc() ],
+								 ],
+								 
+								 # third column
+								 [ [ 'Created:' => $ticket->created() ],
+								   [ 'Updated:' => $ticket->last_updated() ],
+								 ],
+								 
+								);
+			
+			my %label_widgets = $class->struct_to_widgets(\@header_labels, $heap->{height}-2, $heap->{width}-2);
+			
+			use Curses::Forms;
+			my $form = Curses::Forms->new({
+										   X           => $heap->{'pos_x'},
+										   Y           => $heap->{'pos_y'},
+										   COLUMNS     => $heap->{width},
+										   LINES       => $heap->{height},
+										   
+										   BORDER      => 1,
+										   BORDERCOL   => 'yellow',
+										   CAPTION     => $ticket->subject(),
+										   CAPTIONCOL  => 'yellow',
+										   FOREGROUND  => 'white',
+										   BACKGROUND  => 'blue',
+										   DERIVED     => 1,
+										   #        AUTOCENTER  => 1,
+										   TABORDER    => [],
+										   FOCUSED     => 'label1',
+										   WIDGETS     => \%label_widgets,
+										  },
+										 );
+			$form->draw($class->GLOBAL_HEAP->{curses}{handler});
+			#						refresh($mwh);
+		},
+	},
+	heap => { 'pos_x' => 0,
+			  'pos_y' => 0,
+			  'width' => 0,
+			  'height' => 0,
+			},
+	);
+}
+
+1;

Added: RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Links.pm
==============================================================================
--- (empty file)
+++ RT-Client-Console/trunk/lib/RT/Client/Console/Session/Ticket/Links.pm	Mon Nov  5 09:11:08 2007
@@ -0,0 +1,211 @@
+package RT::Client::Console::Session::Ticket::Links;
+
+use base qw(RT::Client::Console::Session);
+
+use Params::Validate qw(:all);
+
+use POE;
+
+# class method
+sub create {
+	my ($class) = @_;
+	$class->SUPER::create(
+    'ticket_links',
+	inline_states => {
+		init => sub {
+			my ($kernel, $heap) = @_[ KERNEL, HEAP ];
+
+			my ($screen_w, $screen_h);
+			$class->GLOBAL_HEAP->{curses}{handler}->getmaxyx($screen_h, $screen_w);
+	
+			$heap->{'pos_x'} = $screen_w * 2 / 3 + 1;
+			$heap->{'pos_y'} = 1;
+			$heap->{width} = $screen_w - ($screen_w * 2 / 3);
+			$heap->{height} = $screen_h - 5 - 1;
+		},
+		available_keys => sub {
+			return (['l', 'change links', 'change_links']);
+		},
+		change_links => sub {
+			my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+			$heap->{change_links_mode} = 1;
+			my $text = qq(
+
+ c : cancel
+);
+			my $height = scalar( () = $text =~ /(\n)/g) + 1;
+			use List::Util qw(max);
+			my $title = ' Change ticket links ';
+			my $width = max (map { length } (split(/\n/, $text), $title));
+			my ($screen_w, $screen_h);
+			my $curses_handler = $class->GLOBAL_HEAP->{curses}{handler};
+			$curses_handler->getmaxyx($screen_h, $screen_w);
+
+			use Curses::Widgets::Label;
+			my $label = Curses::Widgets::Label->new({
+													 CAPTION     => $title,
+													 CAPTIONCOL  => 'yellow',
+													 BORDER      => 1,
+													 LINES       => $height,
+													 COLUMNS     => $width,
+													 Y           => $screen_h/2-($height+2)/2,
+													 X           => $screen_w/2-($width+2)/2,,
+													 VALUE       => $text,
+													 FOREGROUND  => 'white',
+													 BACKGROUND  => 'blue',
+													 BORDERCOL   => 'white',
+													});
+			$label->draw($curses_handler);
+			$class->GLOBAL_HEAP->{modal_session} = 'ticket_links';
+		},
+		modal_handler => sub {
+			my ( $kernel, $heap, $keystroke) = @_[ KERNEL, HEAP, ARG0 ];
+
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+			if ($keystroke eq 'c' || $keystroke eq '<^[>') {
+				delete $class->GLOBAL_HEAP->{modal_session};
+			} else {
+				$kernel->yield('change_links');
+			}
+			return;
+		},
+		draw => sub { 
+			my ( $kernel, $heap) = @_[ KERNEL, HEAP ];
+			my $label;
+
+			my $ticket = $class->GLOBAL_HEAP->{rt}{tickets}{current};
+
+			if (!defined($heap->{parents})) {
+				$class->_generate_job($kernel, $heap, 'parents', q(HasMember=') . $ticket->id() . q('))
+			}
+			if (!defined($heap->{children})) {
+				$class->_generate_job($kernel, $heap, 'children', q(MemberOf=') . $ticket->id() . q('))
+			}
+			if (!defined($heap->{depends})) {
+				$class->_generate_job($kernel, $heap, 'depends', q(DependedOnBy=') . $ticket->id() . q('))
+			}
+			if (!defined($heap->{depended})) {
+				$class->_generate_job($kernel, $heap, 'depended', q(DependsOn=') . $ticket->id() . q('))
+			}
+			if (!defined($heap->{refers})) {
+				$class->_generate_job($kernel, $heap, 'refers', q(ReferredToBy=') . $ticket->id() . q('))
+			}
+			if (!defined($heap->{refered})) {
+				$class->_generate_job($kernel, $heap, 'refered', q(RefersTo=') . $ticket->id() . q('))
+			}
+
+			
+			my $_ticket_to_label = sub {
+				my ($t) = @_;
+				defined $t && ref($t) or return '';
+				return $t->id() . ' ' . $t->subject()
+			};
+
+
+			my @d = @{$heap->{depends}};
+			my @depends_on = (
+							  [ 'Depends on:' => $_ticket_to_label->($d[0]) ],
+							  map { [ '' => $_ticket_to_label->($_) ] } @d[1..$#d]
+							 );
+			my @d2 = @{$heap->{depended}};
+			my @depended_on_by = (
+								  [ 'Depended on by:' => $_ticket_to_label->($d2[0]) ],
+								  map { [ '' => $_ticket_to_label->($_) ] } @d2[1..$#d2]
+								 );
+			my @p = @{$heap->{parents}};
+			my @parents = (
+						   [ 'Parents:' => $_ticket_to_label->($p[0]) ],
+						   map { [ '' => $_ticket_to_label->($_) ] } @p[1..$#p]
+						  );
+			my @c = @{$heap->{children}};
+			my @children = (
+							[ 'Children:' => $_ticket_to_label->($c[0]) ],
+							map { [ '' => $_ticket_to_label->($_) ] } @c[1..$#c]
+						   );
+			my @r = @{$heap->{refers}};
+			my @refers_to = (
+							 [ 'Refers to:' => $_ticket_to_label->($r[0]) ],
+							 map { [ '' => $_ticket_to_label->($_) ] } @r[1..$#r]
+							);
+			my @r2 = @{$heap->{refered}};
+			my @referred_to_by = (
+								  [ 'Refered to by:' => $_ticket_to_label->($r2[0]) ],
+								  map { [ '' => $_ticket_to_label->($_) ] } @r2[1..$#r2]
+								 );
+
+
+			my @links_labels = (
+								# first column
+								[ @depends_on,
+								  @depended_on_by,
+								  @parents,
+								  @children,
+								  @refers_to,
+								  @referred_to_by,
+								],
+							   );
+
+
+			my %links_widgets = $class->struct_to_widgets(\@links_labels, $heap->{height}-2, $heap->{width}-2);
+
+			use Curses::Forms;
+			my $form = Curses::Forms->new({
+													   X           => $heap->{'pos_x'},
+													   Y           => $heap->{'pos_y'},
+													   COLUMNS     => $heap->{width},
+													   LINES       => $heap->{height},
+
+													   BORDER      => 0,
+													   BORDERCOL   => 'blue',
+													   FOREGROUND  => 'white',
+													   BACKGROUND  => 'blue',
+													   DERIVED     => 1,
+													   #        AUTOCENTER  => 1,
+													   TABORDER    => [],
+													   FOCUSED     => 'label1',
+													   WIDGETS     => \%links_widgets,
+													  },
+													 );
+			use Data::Dumper;
+			$form->draw($class->GLOBAL_HEAP->{curses}{handler});
+			#						refresh($mwh);
+		},
+	},
+	heap => { 'pos_x' => 0,
+			  'pos_y' => 0,
+			  'width' => 0,
+			  'height' => 0,
+			  change_custfields_mode => 0,
+			},
+	);	  
+}
+
+sub _generate_job {
+	my ($class, $kernel, $heap, $element, $query) = @_;
+	$heap->{$element} = [];
+	my @ids;
+	my $idx = 0;
+	my $rt_handler = $class->GLOBAL_HEAP->{rt}{cnx}{handler};
+	use RT::Client::Console::Session::Progress;
+	RT::Client::Console::Session::Progress->add_progress(
+			steps_nb => sub { scalar(@ids) },
+			caption => sub { $element },
+			initially => sub {
+				@ids = $rt_handler->search( type => 'ticket',
+											query => $query,
+										  );
+			},
+			code => sub { my $id = $ids[$idx++];
+						  defined $id or return;
+						  use RT::Client::Console::Session::Ticket;
+						  push @{$heap->{$element}},
+							RT::Client::Console::Session::Ticket->load_ticket($rt_handler, $id);
+						  #										   $kernel->post('ticket_links', 'draw');
+						  return 1;
+					  },
+			finally => sub { 
+				$kernel->post('ticket_links', 'draw'),
+			},
+	);
+}
+1;



More information about the Bps-public-commit mailing list