Chris Lansdown gtk talk 13 June 2000 example3.perl
From LILUG
Note: The following attempts to use much of the original formatting that Chris Lansdown used in his handout while still working in the wikitext format!
#! /usr/bin/perl # The GPL notice would go here # This was written by Christopher Lansdown and is in the public domain
# We use the Gtk module for Gtk functionality. This corresponds to
#include <gtk/gtk.h
# in C.
use Gtk;
#When developing in perl it is always a good idea to turn on strict
# parsing. It encourages you to write good code as well as helps
# catch stupid mistakes like misspelling a variable name.
use strict;
#############################
#delcare our global variables
use vars qw(@path $cur_sel);
# Unlike C, execution of a perl program starts with the first executable
# statement. There is no special way to indicate it like with C.
###############
#initialize Gtk
init Gtk;
##############################
# Create and set up our window
my $window = new Gtk::Window('toplevel');
$window->set_title('PathOLogical - The Logical Path Editor');
$window->signal_connect('destroy', sub { Gtk::main_quit('Gtk'); });
###############
#Create our box
# We will use a veritical box this time because the list will be much taller
# than it is wide. Really tall buttons look much worse than really wide ones.
my $box = new Gtk::VBox(0, 0);
$window->add($box);
#################
#Create the Clist
my $clist = new_with_titles Gtk::CList('Directories in Path');
# Here we hook into the signals generated when a row is selected or unselected
# instead of defining an entire subroutine, we make use of perl's ability to
# generate anonymous subroutines. All that we want to do when a row is selected
# or unselected is to keep track of what the currently slected row is (gtk provides
# another way to do this, but it would be more cumbersome that to just do it
# ourselves). You will notice that we assign $cur_sel to the value of the second
# argument for a selected row (in the beginning of subroutines in perl the
# arguments are passed by an array called @_). This corresponds to the function
# prototype in C of this sort of callback as
# void cd(GtkWidget *widget, int row, gpointer data)
# For the second callback, we simply assign the arbitrary value -1 as meaning
# out of bounds since it is not a valid row number.
$clist->signal_connect('select_row', sub { $cur_sel = $_[1]; });
$clist->signal_connect('unselect_row', sub { $cur_sel = -1; });
# To compute the path that the user currently has, we simply split up the
# environment variable PATH into components separated by colons and store
# it in the array @path.
@path = split(':', $ENV{PATH});
# We now use the path to populate our list with the directories comprising
# our path.
foreach my $tmp (@path) {
$clist->append($tmp);
}
# as a quick hack, we esitamate that every row in the clist will be about 20
# pixels high and thus we set the height of the list to 20 times the number
# of elements in the path. This is a really bad way to do it but it will
# work for most people. The -1 for the first argument means that the width
# of the list should be left at it's default. A number greater than 0 would
# have set the width to that value in pixels.
$clist->set_usize(-1, 20 * scalar(@path)); #bad assupmtion
# We now set up a scrolled window and stick the list in it so that we don't
# have to do scrolling manually. This isn't a wonderful idea if the list had
# 100,000 elements, but for most applications it is fine.
my $scroll = new Gtk::ScrolledWindow(undef, undef);
$scroll->add($clist);
$box->pack_start($scroll, 1, 1, 0);
# We now set up our buttons.
# The add button is like our previous callbacks. We simply pass a function
# (subroutine) to gtk to execute when it gets the signal 'clicked'. We also
# need the subroutine to be able to manipulate the list, so we give it the
# clist widget as our private data.
my $button = new Gtk::Button('Add Directory');
$button->signal_connect('clicked', \&add_dir, $clist);
$box->pack_start($button, 0, 0, 0);
# Adding the current directory must be a special action because the current
# directory is a concept rather than a particular directory. It is normally
# represented with the character '.', so we add it to our internal path list
# as well as to the one being displayed.
$button = new Gtk::Button('Add Current Directory');
$button->signal_connect('clicked',
sub { push(@path, '.'); $clist->append('.'); });
$box->pack_start($button, 0, 0, 0);
# Removing a directory is fairly simple. Again we use an anonymous subroutine
# though we do make use of a convenience routine that we wrote to remove an
# arbitrary. element from the array
$button = new Gtk::Button('Remove Directory');
$button->signal_connect('clicked',
sub{my $sel=$cur_sel;
$clist->remove($sel);
remove(\@path,$sel)}
);
$box->pack_start($button, 0, 0, 0);
#Saving is done with a full fledged function since it is fairly complicated.
$button = new Gtk::Button('Save Path to ~/.bash_profile');
$button->signal_connect('clicked', \&write_profile);
$box->pack_start($button, 0, 0, 0);
# Like in C, we cause the window and everything in it to be displayed $window->show_all();
# And then give control to Gtk
Gtk::main('Gtk');
# This is the end of the main part of the program. Everything after this
# are simply functions made reference to above.
# This is a convenience routine to remove an arbitrary element from the array.
# It doesn't use Gtk or do anything that you would want to change so I'm not
# going to document it. If you can't figure it out email me and I will
# document it.
sub remove($$) {
my ($array, $element) = @_;
my ($i, $j);
$array->[$element] = undef;
for($i=0; $i<scalar(@$array); $i++){
if($array->[$i]){
$array->[$j]=$array->[$i]; $j++;
}
}
for($i=scalar(@$array); $i > $j; $i--) { pop @$array; }
}
# Add_dir Creates a gtk file selection widget. The file selection widget
# is a complicated widget which provides the user with a powerful way to
# select files and directories.
# We attach a function to the clicked signal of the ok button in the
# file selection dialog which adds the name of the file or directory
# selected (the second argument in the callback) to the path and the
# display. Note: we don't do anything to ensure that the user has
# actually chosen a directory. We probably should.
sub add_dir($$) {
my ($window, $clist) = @_;
my $fs = new Gtk::FileSelection("Choose a Directory");
$fs->position(-mouse);
$fs->signal_connect('delete_event', sub { destroy $fs });
$fs->cancel_button->signal_connect("clicked", sub { destroy $fs });
$fs->ok_button->signal_connect('clicked',
sub {my $fs=$_[1];
push(@path, $fs->get_filename);
$clist->append($fs->get_filename);},
$fs);
$fs->show; # once the dialog is set up, we have to tell gtk to display it.
}
# This function also doesn't use any gtk components. If you can't figure
# it out, email me.
sub write_profile ($$) {
my ($found_it, $i, $j);
# make a backup copy of the user's .bash_profile
system('cp -f ~/.bash_profile ~/.bash_profile_backup');
# This is a dumb trick to read a file into memory, I know, but it works.
my @profile = split("\n", `cat ~/.bash_profile`);
foreach my $line (@profile) {
if ($line =~ m|PATH|) {
# This is a search-and-replace regular expression. Pipes
# '|' are being used as the delimeter. The e on the end
# means treat the second part (the part to replace the first
# with) as an perl expression rather than a regular expression.
# The $1 is the string which matched everything before the word
# PATH in the line
$line =~ s|(.*)PATH(\s*)=.*|$1.'PATH="'.join(':',@path)."\"\n"|e;
$found_it=1;
}
}
if(!$found_it) {
push(@profile, "export PATH=\"" . join(':', @path) . "\"\n");
}
open(HANDLE,">$ENV{HOME}/.bash_profile") or
die "Could not open ~/.bash_profile\n";
foreach my $line (@profile) { print HANDLE "$line\n"; }
close(HANDLE);
}
