package PACMain;
###################################################################
# This file is part of PAC( Perl Auto Connector)
#
# Copyright (C) 2010 David Torrejon Vaquerizas
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###################################################################
$|++;
###################################################################
# Import Modules
BEGIN
{
use FindBin qw ( $RealBin $Bin $Script );
push( @INC, $RealBin . '/lib' );
}
# Standard
use strict;
use warnings;
use YAML qw ( LoadFile DumpFile );
use Storable;
use Encode;
use File::Copy;
use Net::Ping;
#use Data::Dumper;
# GTK2
use Gtk2 '-init -threads-init';
use Gtk2::Ex::Simple::List;
use Gtk2::GladeXML;
# PAC modules
use PACTray;
use PACTerminal;
use PACShell;
use PACUtils;
use PACEdit;
use PACConfig;
use PACCluster;
# END: Import Modules
###################################################################
###################################################################
# Define GLOBAL CLASS variables
my $APPNAME = $PACUtils::APPNAME;
my $APPVERSION = $PACUtils::APPVERSION;
my $RES_DIR = $RealBin . '/res';
my $APPICON = $RealBin . '/res/pac64x64.png';
my $TRAYICON = $RealBin . '/res/pac_tray.png';
my $TABICON = $RealBin . '/res/pac_tab.png';
my $AUTOSTART_FILE = $RealBin . '/res/pac_start.desktop';
my $INIT_CFG_FILE = $RealBin . '/res/pac.yml';
my $CFG_DIR = $ENV{'HOME'} . '/.pac';
my $CFG_FILE = $CFG_DIR . '/pac.yml';
our %RUNNING;
our %FUNCS;
# END: Define GLOBAL CLASS variables
###################################################################
###################################################################
# START: Define PUBLIC CLASS methods
sub new
{
my $class = shift;
my $self = {};
# Setup some signal handling
$SIG{'TERM'} = sub{ $self -> _quitProgram( 'force' ) };
$SIG{'STOP'} = sub{ $self -> _quitProgram( 'force' ) };
$SIG{'QUIT'} = sub{ $self -> _quitProgram( 'force' ) };
$SIG{'INT'} = sub{ $self -> _quitProgram( 'force' ) };
$SIG{'HUP'} = sub{ $self -> _quitProgram( 'force' ) };
$self -> {_CFG} = undef;
$self -> {_ENVIRONMENT} = '';
$self -> {_CONNECTION} = '';
$self -> {_PREVTAB} = 0;
$self -> {_GLADE} = undef;
$self -> {_GUI} = undef;
$self -> {_PACTABS} = undef;
$self -> {_COPY}{'name'} = '';
$self -> {_COPY}{'cfg'} = '';
$self -> {_COPY}{'cut'} = '';
@{ $self -> {_UNDO} } = ();
$$self{_UPDATE_STATUS} = 1;
$$self{_RUNNING_COUNT} = 0;
$self -> {_PING} = Net::Ping -> new( 'tcp' );
$self -> {_PING} -> tcp_service_check( 1 );
# Read the config/connections file...
$$self{_CFG} = LoadFile( $CFG_FILE ) or die "ERROR: Could not load config file '$CFG_FILE': $!";
( $$self{_CFG}{'defaults'}{'version'} gt '2.4.1.1' ) and _decipherCFG( $$self{_CFG} );
#... and make some sanity checks
_cfgSanityCheck( $$self{_CFG} );
# Setup known connection methods
%{ $$self{_METHODS} } = _getMethods( $self );
# Check if only one instance of PAC is allowed
if ( $self -> {_CFG}{'defaults'}{'only one instance'} && _checkPIDFile )
{
kill( 1, _checkPIDFile );
Gtk2::Gdk -> notify_startup_complete;
return 0;
}
elsif ( ! _buildPIDFile )
{
Gtk2::Gdk -> notify_startup_complete;
return 0;
}
bless( $self, $class );
return $self;
}
# DESTRUCTOR
sub DESTROY
{
my $self = shift;
undef $self;
return 1;
}
# Start GUI and launch connection
sub start
{
my $self = shift;
my $opt = shift;
$SIG{'HUP'} = sub { $self -> _showConnectionsList; };
# Build the GUI
$self -> _initGUI( $opt ) or return 0;
# Setup callbacks
$self -> _setupCallbacks;
# Set a timer to control trayicon's tooltip
Glib::Timeout -> add( 750, sub { $self -> _updateStatus; return 1; } );
if ( $APPVERSION gt $$self{_CFG}{defaults}{version} )
{
$$self{_CFG}{defaults}{version} = $APPVERSION;
_saveConfiguration( $self );
}
Gtk2::Gdk -> notify_startup_complete;
# Goto GTK's event loop
Gtk2 -> main;
_deletePIDFile;
return 1;
}
# END: Define PUBLIC CLASS methods
###################################################################
###################################################################
# START: Define PRIVATE CLASS functions
sub _initGUI
{
my $self = shift;
my $opt = shift;
##############################################
# Create main window
##############################################
$$self{_GUI}{main} = Gtk2::Window -> new();
# Create a vbox1: main, status
$$self{_GUI}{vbox1} = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{main} -> add( $$self{_GUI}{vbox1} );
# Create an hbox1: connections and information frames
$$self{_GUI}{hbox1} = Gtk2::HBox -> new( 0, 0 );
$$self{_GUI}{vbox1} -> pack_start( $$self{_GUI}{hbox1}, 1, 1, 0 );
# Create a vbox2: environments and connections
$$self{_GUI}{vbox2} = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{hbox1} -> pack_start( $$self{_GUI}{vbox2}, 0, 1, 5 );
# Create a frame1 for environments
$$self{_GUI}{frame1} = Gtk2::Frame -> new( ' GROUP: ' );
$$self{_GUI}{vbox2} -> pack_start( $$self{_GUI}{frame1}, 0, 1, 0 );
$$self{_GUI}{frame1lbl} = Gtk2::Label -> new();
$$self{_GUI}{frame1lbl} -> set_markup( ' <b>GROUP:</b> ' );
$$self{_GUI}{frame1} -> set_label_widget( $$self{_GUI}{frame1lbl} );
# Create a vbox3: combo, separator and buttons
$$self{_GUI}{vbox3} = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{frame1} -> add( $$self{_GUI}{vbox3} );
$$self{_GUI}{vbox3} -> set_border_width( 5 );
# Create a comboEnvironment combo-box for groups
$$self{_GUI}{comboEnvironment} = Gtk2::ComboBox -> new_text();
$$self{_GUI}{vbox3} -> pack_start( $$self{_GUI}{comboEnvironment}, 0, 1, 0 );
# Create a sep1 separator
$$self{_GUI}{sep1} = Gtk2::HSeparator -> new();
$$self{_GUI}{vbox3} -> pack_start( $$self{_GUI}{sep1}, 0, 1, 5 );
# Create a hbuttonbox1: add, rename and delete
$$self{_GUI}{hbuttonbox1} = Gtk2::HButtonBox -> new();
$$self{_GUI}{vbox3} -> pack_start( $$self{_GUI}{hbuttonbox1}, 0, 1, 0 );
$$self{_GUI}{hbuttonbox1} -> set_layout( 'GTK_BUTTONBOX_EDGE' );
$$self{_GUI}{hbuttonbox1} -> set_homogeneous( 1 );
# Create groupAdd button
$$self{_GUI}{groupAddBtn} = Gtk2::Button -> new_from_stock( 'gtk-add' );
$$self{_GUI}{hbuttonbox1} -> add( $$self{_GUI}{groupAddBtn} );
$$self{_GUI}{groupAddBtn} -> set( 'can-focus' => 0 );
# Create groupRen button
$$self{_GUI}{groupRenBtn} = Gtk2::Button -> new_with_label( 'Rename' );
$$self{_GUI}{hbuttonbox1} -> add( $$self{_GUI}{groupRenBtn} );
$$self{_GUI}{groupRenBtn} -> set( 'can-focus' => 0 );
$$self{_GUI}{groupRenBtn} -> set_sensitive( 0 );
# Create groupDel button
$$self{_GUI}{groupDelBtn} = Gtk2::Button -> new_from_stock( 'gtk-delete' );
$$self{_GUI}{hbuttonbox1} -> add( $$self{_GUI}{groupDelBtn} );
$$self{_GUI}{groupDelBtn} -> set( 'can-focus' => 0 );
$$self{_GUI}{groupDelBtn} -> set_sensitive( 0 );
# Create a frame2 for connections
$$self{_GUI}{frame2} = Gtk2::Frame -> new( ' CONNECTIONS: ' );
$$self{_GUI}{vbox2} -> pack_start( $$self{_GUI}{frame2}, 1, 1, 0 );
$$self{_GUI}{frame2lbl} = Gtk2::Label -> new();
$$self{_GUI}{frame2lbl} -> set_markup( ' <b>CONNECTIONS:</b> ' );
$$self{_GUI}{frame2} -> set_label_widget( $$self{_GUI}{frame2lbl} );
# Create a vbox4: tree, separator and buttons
$$self{_GUI}{vbox4} = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{frame2} -> add( $$self{_GUI}{vbox4} );
# Create a scrolled1 scrolled window to contain the connections tree
$$self{_GUI}{scroll1} = Gtk2::ScrolledWindow -> new();
$$self{_GUI}{vbox4} -> pack_start( $$self{_GUI}{scroll1}, 1, 1, 0 );
$$self{_GUI}{scroll1} -> set_policy( 'automatic', 'automatic' );
$$self{_GUI}{vbox4} -> set_border_width( 5 );
# Create a treeConnections treeview for connections
$$self{_GUI}{treeConnections} = Gtk2::Ex::Simple::List -> new_from_treeview (
Gtk2::TreeView -> new(),
'Connection(s) icon:' => 'pixbuf',
'Connection(s) list:' => 'text'
);
$$self{_GUI}{scroll1} -> add( $$self{_GUI}{treeConnections} );
$$self{_GUI}{treeConnections} -> set_headers_visible( 0 );
$$self{_GUI}{treeConnections} -> get_selection -> set_mode( 'GTK_SELECTION_MULTIPLE' );
# Create a frameScreenshot for screenshot
$$self{_GUI}{frameScreenshot} = Gtk2::Frame -> new( ' SCREENSHOT: ' );
$$self{_GUI}{frameScreenshot} -> set_size_request( 150, 150 );
$$self{_GUI}{vbox4} -> pack_start( $$self{_GUI}{frameScreenshot}, 0, 1, 0 );
$$self{_GUI}{frameScreenshotlbl} = Gtk2::Label -> new();
$$self{_GUI}{frameScreenshotlbl} -> set_markup( ' <b>SCREENSHOT:</b> ' );
$$self{_GUI}{frameScreenshot} -> set_label_widget( $$self{_GUI}{frameScreenshotlbl} );
# Create an eventbox for the image
$$self{_GUI}{ebScreenshot} = Gtk2::EventBox -> new;
$$self{_GUI}{frameScreenshot} -> add( $$self{_GUI}{ebScreenshot} );
$$self{_GUI}{ebScreenshot} -> set_border_width( 5 );
# Create a gtkImage to contain the screenshot
$$self{_GUI}{imageScreenshot} = Gtk2::Image -> new();
$$self{_GUI}{ebScreenshot} -> add( $$self{_GUI}{imageScreenshot} );
# Create a sep2 separator
$$self{_GUI}{sep2} = Gtk2::HSeparator -> new();
$$self{_GUI}{vbox4} -> pack_start( $$self{_GUI}{sep2}, 0, 1, 5 );
# Create a hbox0: cut, copy and paste
$$self{_GUI}{hbox0} = Gtk2::HBox -> new( 1, 0 );
$$self{_GUI}{vbox4} -> pack_start( $$self{_GUI}{hbox0}, 0, 1, 0 );
# Create connExecBtn button
$$self{_GUI}{connExecBtn} = Gtk2::Button -> new_from_stock( 'gtk-connect' );
$$self{_GUI}{hbox0} -> pack_start( $$self{_GUI}{connExecBtn}, 0, 1, 0 );
$$self{_GUI}{connExecBtn} -> set( 'can-focus' => 0 );
# Create connEditBtn button
$$self{_GUI}{connEditBtn} = Gtk2::Button -> new_from_stock( 'gtk-edit' );
$$self{_GUI}{hbox0} -> pack_start( $$self{_GUI}{connEditBtn}, 0, 1, 0 );
$$self{_GUI}{connEditBtn} -> set( 'can-focus' => 0 );
# Create a hbuttonbox3: add, ren and del
$$self{_GUI}{hbuttonbox3} = Gtk2::HButtonBox -> new();
$$self{_GUI}{vbox4} -> pack_start( $$self{_GUI}{hbuttonbox3}, 0, 1, 0 );
$$self{_GUI}{hbuttonbox3} -> set_layout( 'GTK_BUTTONBOX_EDGE' );
$$self{_GUI}{hbuttonbox3} -> set_homogeneous( 1 );
# Create groupAdd button
$$self{_GUI}{connAddBtn} = Gtk2::Button -> new_from_stock( 'gtk-add' );
$$self{_GUI}{hbuttonbox3} -> add( $$self{_GUI}{connAddBtn} );
$$self{_GUI}{connAddBtn} -> set( 'can-focus' => 0 );
# Create groupRen button
$$self{_GUI}{connRenBtn} = Gtk2::Button -> new_with_label( 'Rename' );
$$self{_GUI}{hbuttonbox3} -> add( $$self{_GUI}{connRenBtn} );
$$self{_GUI}{connRenBtn} -> set( 'can-focus' => 0 );
# Create groupDel button
$$self{_GUI}{connDelBtn} = Gtk2::Button -> new_from_stock( 'gtk-delete' );
$$self{_GUI}{hbuttonbox3} -> add( $$self{_GUI}{connDelBtn} );
$$self{_GUI}{connDelBtn} -> set( 'can-focus' => 0 );
# Create a vbox5: description
$$self{_GUI}{vbox5} = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{vbox5} -> set_border_width( 5 ) if $$self{_CFG}{defaults}{'tabs in main window'};
$$self{_GUI}{hbox1} -> pack_start( $$self{_GUI}{vbox5}, 1, 1, 5 );
# Create a notebook widget
my $nb = Gtk2::Notebook -> new();
my $tablbl = Gtk2::HBox -> new( 0, 0 );
my $eblbl = Gtk2::EventBox -> new();
$eblbl -> add( Gtk2::Label -> new( 'Info ' ) );
$tablbl -> pack_start( $eblbl, 0, 1, 0 );
$$self{_GUI}{_TABIMG} = Gtk2::Image -> new_from_stock( 'gtk-info', 'menu' );
$tablbl -> pack_start($$self{_GUI}{_TABIMG}, 0, 1, 0 );
$tablbl -> show_all;
$$self{_GUI}{vbox5} -> pack_start( $nb, 1, 1, 0 );
$nb -> set_scrollable( 1 );
$nb -> set_tab_pos( $$self{_CFG}{'defaults'}{'tabs position'} );
# Create a vboxInfo: description
$$self{_GUI}{vboxInfo} = Gtk2::VBox -> new( 0, 0 );
$nb -> append_page( $$self{_GUI}{vboxInfo}, $tablbl );
# Create a frameDescription for description
$$self{_GUI}{frameDescription} = Gtk2::Frame -> new( ' DESCRIPTION: ' );
$$self{_GUI}{vboxInfo} -> pack_start( $$self{_GUI}{frameDescription}, 1, 1, 0 );
$$self{_GUI}{frameDescriptionlbl} = Gtk2::Label -> new();
$$self{_GUI}{frameDescriptionlbl} -> set_markup( ' <b>DESCRIPTION:</b> ' );
$$self{_GUI}{frameDescription} -> set_label_widget( $$self{_GUI}{frameDescriptionlbl} );
# Create a scrolled2 scrolled window to contain the description textview
$$self{_GUI}{scroll2} = Gtk2::ScrolledWindow -> new();
$$self{_GUI}{frameDescription} -> add( $$self{_GUI}{scroll2} );
$$self{_GUI}{scroll2} -> set_policy( 'automatic', 'automatic' );
# Create descView as a gtktextview with descBuffer
$$self{_GUI}{descBuffer} = Gtk2::TextBuffer -> new();
$$self{_GUI}{descView} = Gtk2::TextView -> new_with_buffer( $$self{_GUI}{descBuffer} );
$$self{_GUI}{descView} -> set_border_width( 5 );
$$self{_GUI}{scroll2} -> add( $$self{_GUI}{descView} );
$$self{_GUI}{descView} -> set_wrap_mode( 'GTK_WRAP_WORD' );
$$self{_GUI}{descView} -> set_sensitive( 1 );
# Create a sep2 separator
$$self{_GUI}{sep2} = Gtk2::HSeparator -> new();
$$self{_GUI}{vbox5} -> pack_start( $$self{_GUI}{sep2}, 0, 1, 5 );
# Create a hbox3 for some buttons at the bottom of the window
$$self{_GUI}{hbox3} = Gtk2::HBox -> new( 1, 0 );
$$self{_GUI}{vbox5} -> pack_end( $$self{_GUI}{hbox3}, 0, 1, 0 );
# Create aboutBtn button
$$self{_GUI}{aboutBtn} = Gtk2::Button -> new_from_stock( 'gtk-about' );
$$self{_GUI}{hbox3} -> pack_start( $$self{_GUI}{aboutBtn}, 0, 1, 0 );
$$self{_GUI}{aboutBtn} -> set( 'can-focus' => 0 );
# Create saveBtn button
$$self{_GUI}{saveBtn} = Gtk2::Button -> new_from_stock( 'gtk-save' );
$$self{_GUI}{hbox3} -> pack_start( $$self{_GUI}{saveBtn}, 0, 1, 0 );
$$self{_GUI}{saveBtn} -> set( 'can-focus' => 0 );
$$self{_GUI}{saveBtn} -> set_sensitive( 0 );
# Create quitBtn button
$$self{_GUI}{quitBtn} = Gtk2::Button -> new_from_stock( 'gtk-quit' );
$$self{_GUI}{hbox3} -> pack_start( $$self{_GUI}{quitBtn}, 0, 1, 0 );
$$self{_GUI}{quitBtn} -> set( 'can-focus' => 0 );
# Create a hbox2 for some buttons at the bottom of the window
$$self{_GUI}{hbox2} = Gtk2::HBox -> new( 1, 0 );
$$self{_GUI}{vbox5} -> pack_end( $$self{_GUI}{hbox2}, 0, 1, 0 );
# Create shellBtn button
$$self{_GUI}{shellBtn} = Gtk2::Button -> new_with_label( "Local Shell" );
$$self{_GUI}{hbox2} -> pack_start( $$self{_GUI}{shellBtn}, 0, 1, 0 );
$$self{_GUI}{shellBtn} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-home', 'GTK_ICON_SIZE_BUTTON' ) );
$$self{_GUI}{shellBtn} -> set( 'can-focus' => 0 );
# Create clusterBtn button
$$self{_GUI}{clusterBtn} = Gtk2::Button -> new_with_label( 'Clusters...' );
$$self{_GUI}{hbox2} -> pack_start( $$self{_GUI}{clusterBtn}, 0, 1, 0 );
$$self{_GUI}{clusterBtn} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-justify-fill', 'GTK_ICON_SIZE_BUTTON' ) );
$$self{_GUI}{clusterBtn} -> set( 'can-focus' => 0 );
# Create configBtn button
$$self{_GUI}{configBtn} = Gtk2::Button -> new_from_stock( 'gtk-preferences' );
$$self{_GUI}{hbox2} -> pack_start( $$self{_GUI}{configBtn}, 0, 1, 0 );
$$self{_GUI}{configBtn} -> set( 'can-focus' => 0 );
# Create gtkstatusbar
$$self{_GUI}{status} = Gtk2::Statusbar -> new;
$$self{_GUI}{vbox1} -> pack_start( $$self{_GUI}{status}, 0, 1, 0 );
# Setup some window properties.
$$self{_GUI}{main} -> set_title( "$APPNAME (v$APPVERSION)" );
$$self{_GUI}{main} -> set_position( 'center' );
$$self{_GUI}{main} -> set_icon_from_file( $APPICON );
$$self{_GUI}{main} -> set_default_size( 600, 300 );
$$self{_GUI}{main} -> set_resizable( 1 );
$$self{_CFG}{defaults}{'start main maximized'} and $$self{_GUI}{main} -> maximize;
##############################################
# Build TABBED TERMINAL WINDOW
##############################################
if ( $$self{_CFG}{defaults}{'tabs in main window'} )
{
$$self{_GUI}{nb} = $nb;
$$self{_GUI}{_PACTABS} = $$self{_GUI}{main};
}
else
{
# Create window
$$self{_GUI}{_PACTABS} = Gtk2::Window -> new();
# Setup some window properties.
$$self{_GUI}{_PACTABS} -> set_title( "Terminals Tabbed Window : $APPNAME (v$APPVERSION)" );
$$self{_GUI}{_PACTABS} -> set_position( 'center' );
$$self{_GUI}{_PACTABS} -> set_icon_from_file( $APPICON );
$$self{_GUI}{_PACTABS} -> set_size_request( 200, 100 );
$$self{_GUI}{_PACTABS} -> set_default_size( 600, 400 );
$$self{_GUI}{_PACTABS} -> set_resizable( 1 );
$$self{_GUI}{_PACTABS} -> maximize if $$self{_CFG}{'defaults'}{'start maximized'};
# Create a notebook widget
$$self{_GUI}{nb} = Gtk2::Notebook -> new();
$$self{_GUI}{_PACTABS} -> add( $$self{_GUI}{nb} );
$$self{_GUI}{nb} -> set_scrollable( 1 );
$$self{_GUI}{nb} -> set_tab_pos( $$self{_CFG}{'defaults'}{'tabs position'} );
$nb -> set_show_tabs( 0 );
$nb -> set_property( 'show_border', 0 );
}
# Populate the environment combobox
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } )
{
$$self{_GUI}{comboEnvironment} -> append_text( $env );
}
# Select first environment
$$self{_GUI}{comboEnvironment} -> set_active( 0 );
$self -> _comboEnvironment_changed;
# Select first connection
$$self{_GUI}{treeConnections} -> select( 0 );
$self -> _treeConnections_cursor_changed;
# Make a first GUI update
$self -> _updateGUIPreferences;
$$self{_GUI}{treeConnections} -> grab_focus();
# Build Config window
$$self{_CONFIG} = PACConfig -> new( $$self{_CFG} );
$FUNCS{_CONFIG} = $$self{_CONFIG};
# Build Edit window
$$self{_EDIT} = PACEdit -> new( $$self{_CFG} );
$FUNCS{_EDIT} = $$self{_EDIT};
# Build Cluster window
$$self{_CLUSTER} = PACCluster -> new( \%RUNNING );
$FUNCS{_CLUSTER} = $$self{_CLUSTER};
# Build Tray icon
$$self{_TRAY} = PACTray -> new( $self );
$FUNCS{_MAIN} = $self;
# To show_all, or not to show_all... that's the question!! :)
$$self{_GUI}{main} -> show_all;
if ( ! $$self{_CFG}{defaults}{'start iconified'} )
{
$$self{_GUI}{main} -> present;
}
else
{
$$self{_GUI}{main} -> hide;
}
return 1;
}
sub _setupCallbacks
{
my $self = shift;
###############################
# ENVIRONMENT RELATED CALLBACKS
###############################
# Capture 'comboEnvironment' 'F2' keypress
$$self{_GUI}{comboEnvironment} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
#print "KEY MASK:" . ( $event -> state ) . "\n";
#print "KEY PRESSED:" . $event -> keyval . ":" . ( chr( $event -> keyval ) ) . "\n";
# Capture 'F2' keypress to rename selected environment
if ( $event -> keyval == 65471 )
{
$$self{_GUI}{groupRenBtn} -> clicked();
return 1;
}
else
{
return 0;
}
} );
# Capture 'comboEnvironment' change
$$self{_GUI}{comboEnvironment} -> signal_connect( 'changed' => sub { $self -> _comboEnvironment_changed; } );
# Capture 'add environment' button clicked
$$self{_GUI}{groupAddBtn} -> signal_connect( 'clicked' => sub
{
my $new_env = _wEnterValue( $$self{_GUI}{main}, "<b>Creating new environment</b>" , "Enter a non-existing name for the new environment" );
if ( ( ! defined $new_env ) || ( $new_env =~ /^\s*$/go ) )
{
return 1;
}
elsif ( defined $$self{_CFG}{'environments'}{$new_env} )
{
my $dialog = Gtk2::MessageDialog -> new(
$$self{_GUI}{main},
'GTK_DIALOG_DESTROY_WITH_PARENT', 'GTK_MESSAGE_ERROR', 'GTK_BUTTONS_CLOSE',
"Environment '$new_env' already exists!!"
);
$dialog -> run(); $dialog -> destroy(); return 1;
}
# Empty the environments combobox
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } ) { $$self{_GUI}{comboEnvironment} -> remove_text( 0 ); }
# Create the new environment in configuration (auto vivifying $new_env in hash!)
defined $$self{_CFG}{'environments'}{$new_env}{1} and 1;
# Re-populate the environment combobox
my $i = 0;
my $j = 0;
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } )
{
$j = $i;
$$self{_GUI}{comboEnvironment} -> append_text( $env );
$env eq $new_env and $$self{_GUI}{comboEnvironment} -> set_active( $j );
++$i;
}
$self -> _updateGUIPreferences;
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
# Capture 'rename environment' button clicked
$$self{_GUI}{groupRenBtn} -> signal_connect( 'clicked' => sub
{
# Get the string of the active environment
my $environment = $$self{_GUI}{comboEnvironment} -> get_active_text();
return 1 unless $environment ne '';
my $new_env = _wEnterValue(
$$self{_GUI}{main},
"<b>Renaming environment</b>",
"Enter a NEW non-existing name for environment '" . $$self{_ENVIRONMENT} . "'",
$environment
);
if ( ( ! defined $new_env ) || ( $new_env =~ /^\s*$/go ) )
{
return 1;
}
elsif ( defined $$self{_CFG}{'environments'}{$new_env} )
{
my $dialog = Gtk2::MessageDialog -> new(
$$self{_GUI}{main},
'GTK_DIALOG_DESTROY_WITH_PARENT', 'GTK_MESSAGE_ERROR', 'GTK_BUTTONS_CLOSE',
"Environment '$new_env' already exists!!"
);
$dialog -> run();
$dialog -> destroy();
return 1;
}
# Empty the environments combobox
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } ) { $$self{_GUI}{comboEnvironment} -> remove_text( 0 ); }
# Rename this environment in configuration
$$self{_CFG}{'environments'}{$new_env} = $$self{_CFG}{'environments'}{$environment};
delete $$self{_CFG}{'environments'}{$environment};
$$self{_ENVIRONMENT} = $new_env;
# Re-populate the environment combobox
my $i = 0;
my $j = 0;
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } )
{
$$self{_GUI}{comboEnvironment} -> append_text( $env );
$env eq $new_env and $$self{_GUI}{comboEnvironment} -> set_active( $j );
++$i;
}
$self -> _updateGUIPreferences;
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
# Capture 'delete environment' button clicked
$$self{_GUI}{groupDelBtn} -> signal_connect( 'clicked' => sub
{
# Get the string of the active environment
my $environment = $$self{_GUI}{comboEnvironment} -> get_active_text();
_wConfirm( $$self{_GUI}{main}, "Delete environment <b>'$environment'</b> and ALL of its connections ?" ) or return 1;
# Empty the environments combobox
foreach ( keys %{ $$self{_CFG}{'environments'} } ) { $$self{_GUI}{comboEnvironment} -> remove_text( 0 ); }
# Delete selected environment from configuration
delete $$self{_CFG}{'environments'}{$environment};
# Re-populate the environment combobox
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } ) { $$self{_GUI}{comboEnvironment} -> append_text( $env ); }
# Now, empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
$self -> _treeConnections_cursor_changed;
$$self{_ENVIRONMENT} = '';
$$self{_CONNECTION} = '';
$self -> _updateGUIPreferences;
$$self{_GUI}{comboEnvironment} -> set_active( -1 );
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
###############################
# CONNECTION RELATED CALLBACKS
###############################
# Capture 'treeConnections' row activated
$$self{_GUI}{treeConnections} -> signal_connect( 'row_activated' => sub
{
my ( $index ) = $$self{_GUI}{treeConnections} -> get_selected_indices;
return unless defined $index;
$self -> _executeTerminal( $$self{_ENVIRONMENT}, $$self{_CONNECTION} );
return 1;
} );
# Capture 'treeconnections' keypress
$$self{_GUI}{treeConnections} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
my $state = '' . ( $event -> state );
#print "KEY MASK:" . ( $event -> state ) . "\n";
#print "KEY PRESSED:" . $event -> keyval . ":" . ( chr( $event -> keyval ) ) . "\n";
# <Ctrl>
if ( $event -> state == 'control-mask' )
{
return 0 unless scalar( $$self{_GUI}{treeConnections} -> get_selected_indices ) == 1;
# <Ctrl>c --> COPY current CONNECTION
if ( $keyval == 99 ) { $self -> _copyConn; return 1; }
# <Ctrl>x --> CUT current CONNECTION
if ( $keyval == 120 ) { $self -> _cutConn; return 1; }
# <Ctrl>v --> PASTE cut/copied CONNECTION
if ( $keyval == 118 ) { $self -> _pasteConn; return 1; }
}
# Capture 'F2' keypress to rename connection
elsif ( $event -> keyval == 65471 )
{
$$self{_GUI}{connRenBtn} -> clicked() if scalar $$self{_GUI}{treeConnections} -> get_selected_indices == 1;
return 1;
}
# Capture 'Del' keypress to delete connection
elsif ( $event -> keyval == 65535 )
{
$$self{_GUI}{connDelBtn} -> clicked();
return 1;
}
else
{
return 0;
}
} );
# Capture 'treeconnections' selected element changed
$$self{_GUI}{treeConnections} -> signal_connect( 'cursor_changed' => sub { $self -> _treeConnections_cursor_changed; } );
$$self{_GUI}{treeConnections} -> get_selection -> signal_connect( 'changed' => sub { $self -> _treeConnections_cursor_changed; } );
# Capture 'treeconnections' right click
$$self{_GUI}{treeConnections} -> signal_connect( 'button_release_event' => sub
{
my ( $widget, $event ) = @_;
$event -> button ne 3 and return 0;
$$self{_ENVIRONMENT} eq '' and return 1;
$self -> _treeConnections_menu;
return 0;
} );
# Capture 'add connection' button clicked
$$self{_GUI}{connAddBtn} -> signal_connect( 'clicked' => sub
{
# Prepare the input window
my $temp_env = $$self{_ENVIRONMENT}; $temp_env =~ s/(<|>)//go;
my $new_conn = _wEnterValue( $$self{_GUI}{main}, "<b>Creating new connection in '$temp_env'</b>" , "Enter a non-existing name for the new connection" );
if ( ( ! defined $new_conn ) || ( $new_conn =~ /^\s*$/go ) )
{
return 1;
}
elsif ( defined $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn} )
{
my $dialog = Gtk2::MessageDialog -> new(
$$self{_GUI}{main},
'GTK_DIALOG_DESTROY_WITH_PARENT', 'GTK_MESSAGE_ERROR', 'GTK_BUTTONS_CLOSE',
"Connection '$new_conn' in environment '" . $$self{_ENVIRONMENT} . "' already exists!!"
);
$dialog -> run(); $dialog -> destroy(); return 1;
}
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
my $screenshot_file = '';
if ( ! defined $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'screenshot'} )
{
$screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $$self{_ENVIRONMENT} . '-' . $$self{_CONNECTION} ) ). '.jpg';
while( -f $screenshot_file ) { $screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $$self{_ENVIRONMENT} . '-' . $$self{_CONNECTION} ) ). '.jpg'; }
}
# Create and initialize the new connection in configuration
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'description'} = "Connection with '" . $$self{_ENVIRONMENT} . "' -> '$new_conn'";
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'screenshot'} = $screenshot_file;
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'title'} = $$self{_ENVIRONMENT} . ' - ' . $new_conn . ' ';
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'method'} = 'ssh';
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'ip'} = '<ip/hostname>';
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'port'} = 22;
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'user'} = '<user>';
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'pass'} = '<password>';
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'use proxy'} = 0;
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'options'} = '';
@{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'local before'} } = ();
@{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'local connected'} } = ();
@{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'local after'} } = ();
@{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'macros'} } = ();
@{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn}{'expect'} } = ();
# Re-populate the connections tree
my $i = 0;
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
$$self{_GUI}{connDelBtn} -> set_sensitive( 1 );
$conn eq $new_conn and $$self{_GUI}{treeConnections} -> select( $i );
++$i;
}
$self -> _updateGUIPreferences;
$self -> _treeConnections_cursor_changed;
$$self{_EDIT} -> show( $$self{_ENVIRONMENT}, $new_conn );
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
# Capture 'rename connection' button clicked
$$self{_GUI}{connRenBtn} -> signal_connect( 'clicked' => sub
{
return 1 unless $$self{_CONNECTION} ne '';
# Prepare the input window
my $new_conn = _wEnterValue(
$$self{_GUI}{main},
"<b>Renaming connection in '" . __( $$self{_ENVIRONMENT} ) . "'</b>",
"Enter a NEW non-existing name for connection '" . $$self{_CONNECTION} . "'",
$$self{_CONNECTION}
);
if ( ( ! defined $new_conn ) || ( $new_conn =~ /^\s*$/go ) )
{
return 1;
}
elsif ( defined $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn} )
{
my $dialog = Gtk2::MessageDialog -> new(
$$self{_GUI}{main},
'GTK_DIALOG_DESTROY_WITH_PARENT', 'GTK_MESSAGE_ERROR', 'GTK_BUTTONS_CLOSE',
"Connection '$new_conn' in environment '" . $$self{_ENVIRONMENT} . "' already exists!!"
);
$dialog -> run(); $dialog -> destroy(); return 1;
}
# Rename this connection
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$new_conn} = $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}};
delete $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}};
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
# Re-populate the connections tree
my $i = 0;
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
$$self{_GUI}{connDelBtn} -> set_sensitive( 1 );
$conn eq $new_conn and $$self{_GUI}{treeConnections} -> select( $i );
++$i;
}
$self -> _updateGUIPreferences;
$$self{_CONNECTION} = $new_conn;
$self -> _treeConnections_cursor_changed;
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
# Capture 'delete connection' button clicked
$$self{_GUI}{connDelBtn} -> signal_connect( 'clicked' => sub
{
my $total = scalar( $$self{_GUI}{treeConnections} -> get_selected_indices );
_wConfirm( $$self{_GUI}{main}, "Are you sure you want to delete <b>$total</b> connection(s) from <b>'" . $$self{_ENVIRONMENT} . "'</b> ?" ) or return 1;
foreach my $sel ( $$self{_GUI}{treeConnections} -> get_selected_indices )
{
delete $$self{_CFG}{'environments'}{ $$self{_ENVIRONMENT} }{ $$self{_GUI}{treeConnections} -> {data}[$sel][1] };
}
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
$$self{_GUI}{connDelBtn} -> set_sensitive( 0 );
# Re-populate the connections tree
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
$$self{_GUI}{connDelBtn} -> set_sensitive( 1 );
}
$$self{_CONNECTION} = '';
$$self{_GUI}{treeConnections} -> select();
$self -> _treeConnections_cursor_changed;
$self -> _updateGUIPreferences;
$$self{_CFG}{tmp}{changed} = 1;
return 1;
} );
###############################
# OTHER CALLBACKS
###############################
# Catch buttons' keypresses
$$self{_GUI}{connExecBtn} -> signal_connect( 'clicked' => sub
{
foreach my $sel ( $$self{_GUI}{treeConnections} -> get_selected_indices )
{
$self -> _executeTerminal( $$self{_ENVIRONMENT}, $$self{_GUI}{treeConnections} -> {data}[$sel][1] );
}
} );
$$self{_GUI}{configBtn} -> signal_connect( 'clicked' => sub { $$self{_CONFIG} -> show; } );
$$self{_GUI}{connEditBtn} -> signal_connect( 'clicked' => sub { $$self{_EDIT} -> show( $$self{_ENVIRONMENT}, $$self{_CONNECTION} ); } );
$$self{_GUI}{shellBtn} -> signal_connect( 'clicked' => sub
{
# Check if we are forced to launch this conn in a new tab/window
my $where = $$self{_CFG}{'defaults'}{'open connections in tabs'} ? 'tab' : 'window';
my $pre = $$self{_CFG}{'defaults'}{'open connections in tabs'};
$$self{_CFG}{'defaults'}{'open connections in tabs'} = $where eq 'tab';
$$self{_UPDATE_STATUS} = 0;
$$self{_GUI}{main} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'watch' ) );
$$self{_GUI}{main} -> set_sensitive( 0 );
$$self{_GUI}{status} -> push( 0, "Please, wait. Starting local shell in new $where ..." );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
# Create a new PACShell...
my $terminal = PACShell -> new( $$self{_CFG}, $$self{_GUI}{nb}, $$self{_GUI}{_PACTABS} ) or die "ERROR: Could not create object($!)";
# ..., start it's connection...
$terminal -> start or die "ERROR: Could not start terminal for local shell: " . $$terminal{ERROR};
# Check if user wants main window to be close when a terminal comes up
( $$self{_CFG}{'defaults'}{'hide on connect'} && ! $$self{_CFG}{'defaults'}{'tabs in main window'} ) and $$self{_GUI}{main} -> hide;
$$self{_CLUSTER} -> delFromCluster( $$terminal{'_UUID'}, '' );
# Reset "open in tab/window" to its previuos state
$$self{_CFG}{'defaults'}{'open connections in tabs'} = $pre;
$$self{_UPDATE_STATUS} = 1;
$self -> {_GUI}{main} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'left-ptr' ) );
$self -> {_GUI}{main} -> set_sensitive( 1 );
$$terminal{_GUI}{_VTE} -> grab_focus;
foreach my $uuid ( keys %RUNNING ) { $RUNNING{$uuid}{terminal} -> _updateCFG unless $RUNNING{$uuid}{is_shell}; }
return 1;
} );
$$self{_GUI}{clusterBtn} -> signal_connect( 'clicked' => sub { $$self{_CLUSTER} -> show; } );
$$self{_GUI}{quitBtn} -> signal_connect( 'clicked' => sub { $self -> _quitProgram; } );
$$self{_GUI}{saveBtn} -> signal_connect( 'clicked' => sub { $self -> _saveConfiguration; } );
$$self{_GUI}{aboutBtn} -> signal_connect( 'clicked' => sub { $self -> _showAboutWindow; } );
# Show right-click menu for the screenshots image viewer
$$self{_GUI}{ebScreenshot} -> signal_connect( 'button_press_event' => sub
{
my ( $widget, $event ) = @_;
$event -> button ne 3 and return 0;
my @screenshot_menu_items;
push( @screenshot_menu_items,
{
label => 'Choose Screenshot file...',
stockicon => 'gtk-edit',
code => sub { $self -> _chooseScreenshot; }
} );
push( @screenshot_menu_items,
{
label => 'Remove Screenshot',
sensitive => $$self{_GUI}{imageScreenshot} -> get_storage_type ne 'stock',
stockicon => 'gtk-delete',
code => sub
{
unlink $$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot} if _wConfirm( undef, "Remove Screenshot file '$$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot}' for <b>$$self{_ENVIRONMENT} -> $$self{_CONNECTION}</b> ?" );
$self -> _updateGUIPreferences;
}
} );
_wPopUpMenu( \@screenshot_menu_items, $event );
return 1,
} );
# Capture tabs events on pactabs window
$$self{_GUI}{nb} -> signal_connect( 'page_removed' => sub { if ( defined $$self{_GUI}{_PACTABS} ) { $$self{_GUI}{nb} -> get_n_pages or $$self{_GUI}{_PACTABS} -> hide }; return 1; } );
$$self{_GUI}{nb} -> signal_connect( 'page_added' => sub { if ( defined $$self{_GUI}{_PACTABS} ) { $$self{_GUI}{nb} -> get_n_pages and $$self{_GUI}{_PACTABS} -> show }; return 1; } );
# Capture TABs window closing
! $$self{_CFG}{defaults}{'tabs in main window'} and $$self{_GUI}{_PACTABS} -> signal_connect( 'delete_event' => sub
{
_wConfirm( undef, "Close <b>TABBED TERMINALS</b> Window ?" ) or return 1;
foreach my $uuid ( keys %RUNNING )
{
next unless defined $RUNNING{$uuid}{'terminal'};
next unless $RUNNING{$uuid}{'terminal'}{_TABBED} || $RUNNING{$uuid}{'terminal'}{_RETABBED};
$RUNNING{$uuid}{'terminal'} -> stop( 'force' );
}
return 1;
} );
# Capture some keypress on TABBED window
$$self{_GUI}{_PACTABS} -> signal_connect( 'key_press_event' => sub
#$$self{_GUI}{nb} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
my $state = '' . ( $event -> state );
#print "TABBED WINDOW KEYPRESS:*$state*$keyval*" . chr($keyval) . "*\n";
# Get current page's tab number
my $curr_page = $$self{_GUI}{nb} -> get_current_page();
# Continue checking keypress only if <Ctrl> is pushed
if ( $event -> state >= 'control-mask' )
{
# Capture <Ctrl>PgUp --> select previous tab
if ( $keyval eq 65365 )
{
$$self{_PREVTAB} = $curr_page;
if ( $curr_page == 0 )
{
$$self{_GUI}{nb} -> set_current_page( -1 );
}
else
{
$$self{_GUI}{nb} -> prev_page();
}
}
# Capture <Ctrl>PgDwn --> select next tab
elsif ( $keyval eq 65366 )
{
$$self{_PREVTAB} = $curr_page;
if ( $curr_page == $$self{_GUI}{nb} -> get_n_pages() - 1 )
{
$$self{_GUI}{nb} -> set_current_page( 0 );
}
else
{
$$self{_GUI}{nb} -> next_page();
}
}
# Capture <Ctrl>number --> select number tab
elsif ( ( $keyval >= 48 ) && ( $keyval <= 57 ) )
{
$$self{_PREVTAB} = $curr_page;
$$self{_GUI}{nb} -> set_current_page( chr( $keyval ) );
}
# Capture <Ctrl>TAB --> switch between tabs
elsif ( $keyval == 65289 )
{
my $now = $$self{_GUI}{nb} -> get_current_page;
$$self{_GUI}{nb} -> set_current_page( $$self{_PREVTAB} );
$$self{_PREVTAB} = $now;
}
else
{
return 0;
}
return 1;
}
return 0;
} );
# Capture some keypress on Description widget
$$self{_GUI}{descView} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
my $state = '' . ( $event -> state );
# Check if <Ctrl>z is pushed
if ( ( $event -> state >= 'control-mask' ) && ( chr( $keyval ) eq 'z' ) && ( scalar @{ $$self{_UNDO} } ) )
{
$$self{_GUI}{descBuffer} -> set_text( encode( 'utf8', pop( @{ $$self{_UNDO} } ) ) );
return 1;
}
return 0;
} );
# Capture text changes on Description widget
$$self{_GUI}{descBuffer} -> signal_connect( 'begin_user_action' => sub
{
$$self{_CFG}{tmp}{changed} = 1;
push( @{ $$self{_UNDO} }, $$self{_GUI}{descBuffer} -> get_property( 'text' ) );
return 0;
} );
$$self{_GUI}{descBuffer} -> signal_connect( 'changed' => sub
{
$$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{description} = $$self{_GUI}{descBuffer} -> get_property( 'text' );
return 0;
} );
# Capture TAB page switching
$$self{_GUI}{nb} -> signal_connect( 'switch_page' => sub
{
my ( $nb, $p, $pnum ) = @_;
my $tab_page = $nb -> get_nth_page( $pnum );
foreach my $uuid ( keys %RUNNING )
{
my $check_gui = $RUNNING{$uuid}{terminal}{_SPLIT} ? $RUNNING{$uuid}{terminal}{_SPLIT_VPANE} : $RUNNING{$uuid}{terminal}{_GUI}{_VBOX};
next unless $check_gui eq $tab_page;
my $sel_env = $RUNNING{$uuid}{terminal}{_ENVIRONMENT};
my $sel_conn = $RUNNING{$uuid}{terminal}{_CONNECTION};
my $env_n = -1;
foreach my $env ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'} } )
{
++$env_n;
next unless $env eq $sel_env;
$$self{_GUI}{comboEnvironment} -> set_active( $env_n );
my $conn_n = -1;
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$env} } )
{
++$conn_n;
next unless $conn eq $sel_conn;
$$self{_GUI}{treeConnections} -> get_selection -> unselect_all;
$$self{_GUI}{treeConnections} -> select( $conn_n );
my $model = $$self{_GUI}{treeConnections} -> get_property( 'model' );
$$self{_GUI}{treeConnections} -> scroll_to_cell( $model -> get_path( $model -> get_iter_from_string( $conn_n ) ), undef, 0, 0, 0 );
last;
}
last;
}
$RUNNING{$uuid}{terminal}{_GUI}{_VTE} -> grab_focus;
}
return 1;
} );
# Capture window closing
$$self{_GUI}{main} -> signal_connect( 'delete_event' => sub
{
if ( $$self{_CFG}{defaults}{'close to tray'} )
{
$$self{_GUI}{main} -> hide();
return 1;
}
else
{
$self -> _quitProgram;
return 1;
}
} );
$$self{_GUI}{main} -> signal_connect( 'destroy' => sub { Gtk2 -> main_quit; exit 0; } );
return 1;
}
sub _comboEnvironment_changed
{
my $self = shift;
# Get the string of the new active environment
$$self{_ENVIRONMENT} = $$self{_GUI}{comboEnvironment} -> get_active_text();
if ( ( ! defined $$self{_ENVIRONMENT} ) || ( $$self{_ENVIRONMENT} eq '' ) ) { $$self{_ENVIRONMENT} = ''; return 0; }
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
$$self{_CONNECTION} = '';
return 1 unless $$self{_ENVIRONMENT} ne '';
$self -> _updateGUIPreferences;
# Populate the connections tree
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
}
return 1;
}
sub _treeConnections_cursor_changed
{
my $self = shift;
$$self{_CONNECTION} = '';
$self -> _updateGUIPreferences;
# ... and return if no connection is selected
my $total = $$self{_GUI}{treeConnections} -> get_selected_indices;
return unless $total;
foreach my $sel ( $$self{_GUI}{treeConnections} -> get_selected_indices )
{
$$self{_CONNECTION} = $$self{_GUI}{treeConnections} -> {data}[$sel][1];
}
# Update GUI with connection's properties
$self -> _updateGUIPreferences;
return 1;
}
sub _treeConnections_menu
{
my $self = shift;
my @tree_menu_items;
# Edit
push( @tree_menu_items,
{
label => 'Edit connection',
stockicon => 'gtk-edit',
sensitive => ( scalar( $$self{_GUI}{treeConnections} -> get_selected_indices ) == 1 ),
code => sub{ $$self{_GUI}{connEditBtn} -> clicked(); }
} );
# Quick Edit variables
my @var_submenu;
my $i = 0;
if ( $$self{_ENVIRONMENT} && $$self{_CONNECTION} )
{
foreach my $var ( @{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'variables'} } )
{
my $j = $i;
push( @var_submenu,
{
label => '<V:' . $j . '> = ' . $var,
code => sub
{
my $new_var = _wEnterValue(
$$self{_GUI}{main},
"Change variable <b>" . __( "<V:$j>" ) . "</b>",
'Enter a NEW value or close to keep this value...',
$var
);
! defined $new_var and return 1;
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'variables'}[$j] = $new_var;
}
} );
++$i;
}
}
push( @tree_menu_items,
{
label => 'Edit Local Variables',
stockicon => 'gtk-dialog-question',
sensitive => $$self{_CONNECTION} && scalar( @var_submenu ) && ( scalar( $$self{_GUI}{treeConnections} -> get_selected_indices ) == 1 ),
submenu => \@var_submenu
} );
push( @tree_menu_items, { separator => 1 } );
# Add
push( @tree_menu_items,
{
label => 'Add connection',
stockicon => 'gtk-add',
code => sub{ $$self{_GUI}{connAddBtn} -> clicked(); }
} );
# Rename
push( @tree_menu_items,
{
label => 'Rename connection',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices == 1 ),
code => sub{ $$self{_GUI}{connRenBtn} -> clicked(); }
} );
# Delete
push( @tree_menu_items,
{
label => 'Delete connection',
stockicon => 'gtk-delete',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices ),
code => sub{ $$self{_GUI}{connDelBtn} -> clicked(); }
} );
push( @tree_menu_items, { separator => 1 } );
# Copy
push( @tree_menu_items,
{
label => 'Copy connection',
stockicon => 'gtk-copy',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices == 1 ),
code => sub{ $self -> _copyConn; }
} );
# Cut
push( @tree_menu_items,
{
label => 'Cut connection',
stockicon => 'gtk-cut',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices == 1 ),
code => sub{ $self -> _cutConn; }
} );
# Paste ?
push( @tree_menu_items,
{
label => 'Paste connection',
stockicon => 'gtk-paste',
sensitive => ( $$self{_COPY}{'name'} ne '' ),
code => sub{ $self -> _pasteConn; }
} );
push( @tree_menu_items, { separator => 1 } );
# Send Wake On LAN magic packet
push( @tree_menu_items,
{
label => 'Wake On LAN...' . ( ( $self -> {_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'use proxy'} ) || ( $self -> {_CFG}{'defaults'}{'use proxy'} ) ? '(can\'t, uses PROXY!!)' : '' ),
#stockicon => 'gtk-execute',
sensitive => ( ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices == 1 ) && ( ! $self -> {_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'use proxy'} ) && ( ! $self -> {_CFG}{'defaults'}{'use proxy'} ) ),
code => sub { _wakeOnLan( $self -> {_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'ip'}, undef, undef, $self -> {_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{'port'} // undef ); }
} );
# Execute in window
push( @tree_menu_items,
{
label => 'Execute in new Window',
stockicon => 'gtk-execute',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices ),
code => sub
{
foreach my $sel ( $$self{_GUI}{treeConnections} -> get_selected_indices )
{
$self -> _executeTerminal( $$self{_ENVIRONMENT}, $$self{_GUI}{treeConnections} -> {data}[$sel][1], 'window' );
}
}
} );
# Execute in tab
push( @tree_menu_items,
{
label => 'Execute in new Tab',
stockicon => 'gtk-execute',
sensitive => ( scalar $$self{_GUI}{treeConnections} -> get_selected_indices ),
code => sub
{
foreach my $sel ( $$self{_GUI}{treeConnections} -> get_selected_indices )
{
$self -> _executeTerminal( $$self{_ENVIRONMENT}, $$self{_GUI}{treeConnections} -> {data}[$sel][1], 'tab' );
}
}
} );
_wPopUpMenu( \@tree_menu_items );
return 1;
}
sub _showAboutWindow
{
my $self = shift;
my $dialog = Gtk2::AboutDialog -> new;
$dialog -> set_program_name( $APPNAME );
$dialog -> set_version( $APPVERSION );
$dialog -> set_copyright( 'Copyright 2010 David Torrejon Vaquerizas' );
$dialog -> set_license( "Released under the GNU General Public Licence version 3\n(http://www.gnu.org/copyleft/gpl.html)" );
$dialog -> set_website( 'http://pacmanager.sourceforge.net/' );
$dialog -> set_authors( 'David Torrejon Vaquerizas <david.tv@gmail.com>' );
$dialog -> signal_connect( 'response' => sub { $_[0] -> destroy; } );
$dialog -> run;
$dialog -> destroy;
return 1;
}
sub _executeTerminal
{
my $self = shift;
my ( $environment, $connection, $where ) = @_;
# Check if we are forced to launch this conn in a new tab/window
$where //= $$self{_CFG}{'defaults'}{'open connections in tabs'} ? 'tab' : 'window';
my $pre = $$self{_CFG}{'defaults'}{'open connections in tabs'};
$$self{_CFG}{'defaults'}{'open connections in tabs'} = $where eq 'tab';
$$self{_UPDATE_STATUS} = 0;
$self -> {_GUI}{main} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'watch' ) );
$self -> {_GUI}{main} -> set_sensitive( 0 );
$$self{_GUI}{status} -> push( 0, "Please, wait. Starting terminal '$environment' -> '$connection' in new $where ..." );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
my $cancel = 0;
if ( ( $$self{_CFG}{'environments'}{$environment}{$connection}{'port'} ) && ( $$self{_CFG}{'defaults'}{'ping port before connect'} ) )
{
my $ip = $$self{_CFG}{'environments'}{$environment}{$connection}{'ip'};
my $port = $$self{_CFG}{'environments'}{$environment}{$connection}{'port'};
my $timeout = $$self{_CFG}{'defaults'}{'ping port timeout'};
$$self{_GUI}{status} -> push( 0, "Please, wait. Pinging '$ip' port $port..." );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
$self -> {_PING} -> port_number( $port );
if ( ! $self -> {_PING} -> ping( $ip, '1' ) )
{
_wConfirm( $$self{_GUI}{main}, "Server '$connection' ($ip)\nseems NOT reachable at port $port\nKeep on trying to connect?" ) or $cancel = 1;
}
}
my $terminal;
if ( ! $cancel )
{
$$self{_GUI}{status} -> push( 0, "Please, wait. Starting terminal '$environment' -> '$connection' in new $where ..." );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
# Create a new PACTerminal...
$terminal = PACTerminal -> new( $$self{_CFG}, $environment, $connection, $$self{_GUI}{nb}, $$self{_GUI}{_PACTABS} ) or die "ERROR: Could not create object($!)";
# ..., start it's connection...
$terminal -> start or die "ERROR: Could not start terminal for '$environment' -> '$connection': " . $terminal -> {ERROR};
# Check if user wants main window to be close when a terminal comes up
( $$self{_CFG}{'defaults'}{'hide on connect'} && ! $$self{_CFG}{'defaults'}{'tabs in main window'} ) and $self -> _hideConnectionsList;
$$self{_CLUSTER} -> delFromCluster( $$terminal{'_UUID'}, '' );
# Reset "open in tab/window" to its previuos state
$$self{_CFG}{'defaults'}{'open connections in tabs'} = $pre;
}
$$self{_UPDATE_STATUS} = 1;
$self -> {_GUI}{main} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'left-ptr' ) );
$self -> {_GUI}{main} -> set_sensitive( 1 );
$$terminal{_GUI}{_VTE} -> grab_focus if defined $terminal;
foreach my $uuid ( keys %RUNNING ) { $RUNNING{$uuid}{terminal} -> _updateCFG unless $RUNNING{$uuid}{is_shell}; }
return 1;
}
sub _quitProgram
{
my $self = shift;
my $force = shift // '0';
my $changed = $$self{_CFG}{tmp}{changed} // '0';
if ( ! $force )
{
my $string = "Are you sure you want to <b>EXIT $APPNAME</b> ?";
$$self{_RUNNING_COUNT} and $string .= "\n\n( there are " . $$self{_RUNNING_COUNT} . ' open Terminals)';
_wConfirm( $$self{_GUI}{main}, $string ) or return 1;
if ( $changed && ( ! $$self{_CFG}{defaults}{'save on exit'} ) )
{
my $opt = _wYesNoCancel( $$self{_GUI}{main}, "<b>Configuration has changed.</b>\n\nSave changes?" );
if ( $opt eq 'yes' )
{
delete $$self{_CFG}{tmp};
$self -> _saveConfiguration;
}
elsif ( $opt eq 'cancel' )
{
return 1;
}
}
elsif ( $changed && ( $$self{_CFG}{defaults}{'save on exit'} ) )
{
delete $$self{_CFG}{tmp};
$self -> _saveConfiguration;
}
}
# And finish every GUI
Gtk2 -> main_quit;
return 1;
}
sub _saveConfiguration
{
my $self = shift;
_cipherCFG( $$self{_CFG} );
DumpFile( $CFG_FILE, $$self{_CFG} );
_decipherCFG( $$self{_CFG} );
$$self{_GUI}{saveBtn} -> set_sensitive( 0 );
$$self{_CFG}{tmp}{changed} = 0;
return 1;
}
sub _updateStatus
{
my $self = shift;
my $running = scalar( keys %RUNNING );
my $string = '';
return 1 unless $$self{_UPDATE_STATUS};
$$self{_RUNNING_COUNT} = 0;
foreach my $uuid ( keys %RUNNING )
{
if ( ! defined $RUNNING{$uuid}{'terminal'}{CONNECTED} )
{
delete $RUNNING{$uuid};
next;
}
$$self{_RUNNING_COUNT} += $RUNNING{$uuid}{'terminal'}{CONNECTED};
}
## Modify main window's status bar
$$self{_GUI}{status} -> push( 0, "$running terminals opened ( $$self{_RUNNING_COUNT} connected )" );
$$self{_GUI}{saveBtn} -> set_sensitive( $$self{_CFG}{tmp}{changed} // 0 );
return 1;
}
sub _chooseScreenshot
{
my $self = shift;
my $filter_images = Gtk2::FileFilter -> new;
$filter_images -> set_name( 'Images' );
$filter_images -> add_pixbuf_formats;
my $dialog = Gtk2::FileChooserDialog -> new(
'Choose Screenshot',
$$self{_GUI}{main},
'GTK_FILE_CHOOSER_ACTION_OPEN',
'gtk-cancel', 'GTK_RESPONSE_CANCEL',
'gtk-open', 'GTK_RESPONSE_ACCEPT'
);
$dialog -> add_filter( $filter_images );
if ( -f $$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot} )
{
$dialog -> set_filename( $$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot} );
}
else
{
$dialog -> set_current_folder( $ENV{'HOME'} );
}
$dialog -> signal_connect( 'update-preview' => sub { _preview( $dialog ); } );
my $answer = $dialog -> run;
if ( $answer eq 'accept' )
{
copy( $dialog -> get_filename, $$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot} );
_scale( $dialog -> get_filename, 400, 300, 'keep aspect ratio' ) -> save( $$self{_CFG}{environments}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}}{screenshot}, 'jpeg' );
}
$dialog -> destroy;
$self -> _updateGUIPreferences;
return 1;
}
sub _updateGUIPreferences
{
my $self = shift;
my $env = $$self{_ENVIRONMENT} // '';
my $conn = $$self{_CONNECTION} // '';
my $total = $$self{_GUI}{treeConnections} -> get_selected_indices;
$$self{_GUI}{groupAddBtn} -> set_sensitive( 1 );
$$self{_GUI}{groupRenBtn} -> set_sensitive( $env ne '' );
$$self{_GUI}{groupDelBtn} -> set_sensitive( $env ne '' );
$$self{_GUI}{connAddBtn} -> set_sensitive( $env ne '' );
$$self{_GUI}{connRenBtn} -> set_sensitive( ( $conn ne '' ) && ( $total eq '1' ) );
$$self{_GUI}{connDelBtn} -> set_sensitive( ( $conn ne '' ) );
$$self{_GUI}{connExecBtn} -> set_sensitive( ( $conn ne '' ) );
$$self{_GUI}{connEditBtn} -> set_sensitive( ( $conn ne '' ) && ( $total eq '1' ) );
$$self{_GUI}{frameDescription} -> set_sensitive( ( $conn ne '' ) && ( $total eq '1' ) );
$$self{_GUI}{frameScreenshot} -> set_sensitive( ( $conn ne '' ) && ( $total eq '1' ) );
$$self{_GUI}{nb} -> set_tab_pos( $$self{_CFG}{'defaults'}{'tabs position'} );
return 1 unless $conn ne '';
@{ $$self{_UNDO} } = ();
$$self{_GUI}{descBuffer} -> set_text( encode( 'utf8', $$self{_CFG}{'environments'}{$env}{$conn}{'description'} // '' ) );
if ( $$self{_CFG}{'defaults'}{'show screenshots'} && ( -f $$self{_CFG}{environments}{$env}{$conn}{screenshot} ) )
{
#$$self{_GUI}{imageScreenshot} -> set_from_file( $$self{_CFG}{environments}{$env}{$conn}{screenshot} );
$$self{_GUI}{imageScreenshot} -> set_from_pixbuf( _scale( $$self{_CFG}{environments}{$env}{$conn}{screenshot}, 200, 200, 1 ) );
$$self{_GUI}{frameScreenshot} -> show_all;
}
elsif ( $$self{_CFG}{'defaults'}{'show screenshots'} )
{
$$self{_GUI}{imageScreenshot} -> set_from_file( '' ); # Force a 'broken' image
$$self{_GUI}{frameScreenshot} -> show_all;
}
else
{
$$self{_GUI}{frameScreenshot} -> hide_all;
}
return 1;
}
sub _showConnectionsList
{
my $self = shift;
#if ( $$self{_CFG}{defaults}{'tabs in main window'} )
#{
# $self -> {_GUI} -> {vbox2} -> show_all;
#}
#else
#{
$self -> {_GUI} -> {main} -> present;
#}
}
sub _hideConnectionsList
{
my $self = shift;
#if ( $$self{_CFG}{defaults}{'tabs in main window'} )
#{
# $self -> {_GUI} -> {vbox2} -> hide_all;
#}
#else
#{
$self -> {_GUI} -> {main} -> hide;
#}
}
sub _copyConn
{
my $self = shift;
return 1 unless ( ( $$self{_ENVIRONMENT} ne '' ) && ( $$self{_CONNECTION} ne '' ) );
$$self{_COPY}{'name'} = $$self{_CONNECTION};
$$self{_COPY}{'cfg'} = Storable::dclone( $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}} );
$$self{_COPY}{'cut'} = 0;
my $screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $$self{_ENVIRONMENT} . '-' . $$self{_CONNECTION} ) ). '.jpg';
while( -f $screenshot_file ) { $screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $$self{_ENVIRONMENT} . '-' . $$self{_CONNECTION} ) ). '.jpg'; }
$$self{_COPY}{'cfg'}{'screenshot'} = $screenshot_file;
return 1;
}
sub _cutConn
{
my $self = shift;
return 1 unless ( ( $$self{_ENVIRONMENT} ne '' ) && ( $$self{_CONNECTION} ne '' ) );
$$self{_COPY}{'name' } = $$self{_CONNECTION};
$$self{_COPY}{'cfg' } = $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_CONNECTION}};
$$self{_COPY}{'cut'} = 1;
# Delete this connection from CFG
delete $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_COPY}{'name'}};
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
# Re-populate the connections tree
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
next if $conn eq $$self{_COPY}{'name'};
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
}
$$self{_CONNECTION} = '';
$$self{_GUI}{treeConnections} -> select();
$self -> _treeConnections_cursor_changed;
$$self{_CFG}{tmp}{changed} = 1;
return 1;
};
sub _pasteConn
{
my $self = shift;
return 1 unless $$self{_ENVIRONMENT} ne '';
if ( ! $$self{_COPY}{'cut'} )
{
$$self{_COPY}{'name'} = $$self{_COPY}{'name'} . '_Copy';
while ( defined $$self{_CFG}{'environments'}{ $$self{_ENVIRONMENT} }{ $$self{_COPY}{'name'} } )
{
$$self{_COPY}{'name'} .= '_';
}
}
# Add this connection to CFG
$$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$$self{_COPY}{'name'}} = $$self{_COPY}{'cfg'};
# Empty the connections tree
@{ $$self{_GUI}{treeConnections} -> {data} } = ();
# Re-populate the connections tree
my $i = 0;
foreach my $conn ( sort { uc($a) cmp uc($b) } keys %{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}} } )
{
push( @{ $$self{_GUI}{treeConnections} -> {data} }, [ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$$self{_ENVIRONMENT}}{$conn}{'method'} }{'icon'}, $conn ] );
$conn eq $$self{_COPY}{'name'} and $$self{_GUI}{treeConnections} -> select( $i );
++$i;
}
$self -> _treeConnections_cursor_changed;
# Once pasted, remove connection's data from copy buffer (if 'cut')
if ( $$self{_COPY}{'cut'} )
{
$$self{_COPY}{'name'} = '';
$$self{_COPY}{'cfg'} = '';
$$self{_COPY}{'cut'} = '';
}
$$self{_CFG}{tmp}{changed} = 1;
return 1;
};
# END: Define PRIVATE CLASS functions
###################################################################
1;