/usr/local/CPAN/X11-Motif/X11/Motif/URLChooser.pm
package X11::Motif::URLChooser;
use Cwd;
use Net::Domain qw(hostfqdn);
use X11::Motif;
use strict;
use vars qw($VERSION @ISA);
$VERSION = 1.0;
@ISA = qw();
my %StorageTypes = ();
sub add_storage_type {
my($type, $class) = @_;
my $impl = {};
bless $impl, $class;
$StorageTypes{$type} = $impl;
}
sub new {
my $self = shift;
my $class = ref($self) || $self;
my($location, $pattern) = @_;
$self = {
'hide_dots' => 1,
'hide_emacs' => 1,
'done' => 0,
'glob' => undef,
'filter' => undef,
'selection' => undef,
'host' => hostfqdn(),
'dir' => getcwd(),
'inactive_storage' => {},
'storage' => undef,
'active_pos' => 0,
'visible_history' => [],
'visible_file_list' => [],
'filtered_file_list' => [],
'visible_dir_list' => [],
'filtered_dir_list' => [],
'dialog_shell' => undef
};
bless $self, $class;
$self->switch_to($location);
$self->set_filter($pattern);
$self->reload();
$self;
}
sub popup {
my $self = shift;
my $shell = $self->{'dialog_shell'};
if (!defined $shell) {
my($toplevel, $dialog_title, $arg);
while (defined($arg = shift)) {
if (X::Toolkit::Widget::IsWidget($arg)) {
$toplevel = $arg;
}
else {
$dialog_title = $arg;
}
}
$toplevel = X::Toolkit::toplevel() if (!defined $toplevel);
$dialog_title = "Choose a File" if (!defined $dialog_title);
my $dialog = $self->create_dialog($toplevel, $dialog_title);
$dialog->ManageChild();
$shell = $dialog->Parent();
}
$shell->Popup(X::Toolkit::GrabNonexclusive);
$self->redisplay();
$self->{'done'} = 0;
$shell;
}
sub popdown {
my $self = shift;
my $shell = $self->{'dialog_shell'};
if (defined $shell) {
$shell->Popdown();
}
$self->{'done'} = -1;
}
sub destroy {
my $self = shift;
my $shell = $self->{'dialog_shell'};
if (defined $shell) {
$shell->DestroyWidget();
$self->{'dialog_shell'} = undef;
}
$self->{'done'} = -1;
}
sub choose {
my $self = shift;
my $shell = $self->popup(@_);
my $context = $shell->WidgetToApplicationContext();
my $event;
X::Motif::XmProcessTraversal($self->{'dialog_file'}, X::Motif::XmTRAVERSE_CURRENT);
while ($self->{'done'} == 0) {
$event = $context->AppNextEvent();
X::Toolkit::DispatchEvent($event);
}
my $selection;
if ($self->{'done'} > 0) {
if ($self->{'done'} == 1) {
$selection = $self->{'storage'}->format($self);
}
$self->popdown();
}
$self->_shutdown();
$selection;
}
sub _shutdown {
my $self = shift;
foreach my $storage (values %{$self->{'inactive_storage'}}) {
$storage->shutdown($self);
}
}
sub set_filter {
my $self = shift;
my($pattern) = @_;
my $dialog_filter = $self->{'dialog_filter'};
if (defined $dialog_filter) {
if (!defined $pattern) {
$pattern = X::Motif::XmTextFieldGetString($dialog_filter);
}
else {
X::Motif::XmTextFieldSetString($dialog_filter, $pattern);
X::Motif::XmTextFieldSetString($self->{'dialog_file'}, '');
}
}
$pattern = '*' if (!defined $pattern);
$self->{'glob'} = $pattern;
$self->{'filter'} = cvt_glob_to_regex($pattern);
$self->filter();
}
sub filter {
my $self = shift;
my $dialog_file_list = $self->{'dialog_file_list'};
if (defined $dialog_file_list) {
my $hide_emacs = $self->{'hide_emacs'};
my $hide_dots = $self->{'hide_dots'};
my $filter = $self->{'filter'};
my $row = 1;
my @filtered_file_list = ();
$dialog_file_list->Unmanage();
X::Motif::XmListDeleteAllItems($dialog_file_list);
my $i = 0;
foreach my $item (@{$self->{'visible_file_list'}}) {
next if ($hide_emacs && ($item =~ /^\#/ || $item =~ /~$/));
next if ($hide_dots && $item =~ /^\./);
next if (!&{$filter}($item));
X::Motif::XmListAddItemUnselected($dialog_file_list, $item, $row);
push @filtered_file_list, $i;
++$row;
}
continue {
++$i;
}
X::Motif::XmListSetPos($dialog_file_list, 1);
$dialog_file_list->Manage();
@{$self->{'filtered_file_list'}} = @filtered_file_list;
}
}
my %remembered_globs = ();
sub cvt_glob_to_regex {
my($glob) = @_;
$glob = '*' if (!defined $glob);
my $regex = $remembered_globs{$glob};
if (!defined $regex) {
$regex = $glob;
$regex =~ s|\\||g;
$regex =~ s|^\s+||;
$regex =~ s|\s+$||;
$regex =~ s|([^\w\s-])|\\$1|g;
$regex =~ s|\s+|\\s+|g;
$regex =~ s|\\\001|.|g;
$regex =~ s|\\\?|.|g;
$regex =~ s|\\\*|.*|g;
$regex .= '.*' if ($regex !~ m|\.\*|);
$regex = "^".$regex."\$";
$regex = eval qq(sub { \$_[0] =~ m\001$regex\001i });
$remembered_globs{$glob} = $regex;
}
return $regex;
}
sub complete_partial_name {
my $self = shift;
my($partial, $w) = @_;
if ($partial =~ m|(.*)/([^/]*)|) {
my $dir = ($1 eq '') ? '/' : $1;
$partial = $2;
$self->switch_to_dir($dir);
$self->reload();
}
if ($partial ne '') {
my $dialog_dir_list = $self->{'dialog_dir_list'};
my $dialog_file_list = $self->{'dialog_file_list'};
my @matches = ();
my $row;
my $dir_row;
my $file_row;
$row = scalar(@{$self->{'visible_history'}}) + 1;
foreach my $i (@{$self->{'filtered_dir_list'}}) {
my $item = $self->{'visible_dir_list'}[$i];
if ($item =~ /^\Q$partial\E/i) {
push @matches, $item;
if (!defined $dir_row) {
$dir_row = $row;
}
}
++$row;
}
$row = 1;
foreach my $i (@{$self->{'filtered_file_list'}}) {
my $item = $self->{'visible_file_list'}[$i];
if ($item =~ /^\Q$partial\E/i) {
push @matches, $item;
if (!defined $file_row) {
$file_row = $row;
}
}
++$row;
}
if (@matches == 0) {
X::Bell($w->Display(), 100);
}
elsif (@matches == 1) {
$partial = $matches[0];
if (defined $dir_row) {
$partial .= '/';
}
}
else {
my $start = length($partial);
my $test_start = $start;
my $test_match = pop @matches;
my $test_prefix;
undef $partial;
do {
$test_prefix = substr($test_match, $test_start, 1);
foreach (@matches) {
if (substr($_, $test_start, 1) ne $test_prefix) {
$partial = substr($test_match, 0, $test_start);
last;
}
}
++$test_start;
}
while (!defined $partial);
}
if (defined $dir_row) {
X::Motif::XmListSetPos($dialog_dir_list, $dir_row);
}
if (defined $file_row) {
X::Motif::XmListSetPos($dialog_file_list, $file_row);
}
}
X::Motif::XmTextFieldSetString($w, $partial);
X::Motif::XmTextFieldSetInsertionPosition($w, X::Motif::XmTextFieldGetLastPosition($w));
}
sub reload {
my $self = shift;
$self->{'storage'}->reload($self);
$self->redisplay();
}
sub redisplay {
my $self = shift;
my $dialog_dir_list = $self->{'dialog_dir_list'};
if (defined $dialog_dir_list) {
$dialog_dir_list->Unmanage();
X::Motif::XmListDeleteAllItems($dialog_dir_list);
my $row = 1;
foreach my $item (@{$self->{'visible_history'}}) {
my $visible_item = (' ' x ($row - 1)) . $item . " ";
X::Motif::XmListAddItemUnselected($dialog_dir_list, $visible_item, $row);
++$row;
}
my $selected_row = $row - 1 || $row;
my $hide_dots = $self->{'hide_dots'};
++$row;
my $pad = ' ' x $row;
my @filtered_dir_list = ();
my $i = 0;
foreach my $item (@{$self->{'visible_dir_list'}}) {
next if ($hide_dots && $item =~ /^\./);
X::Motif::XmListAddItemUnselected($dialog_dir_list, $pad . $item . " ", $row);
push @filtered_dir_list, $i;
++$row;
}
continue {
++$i;
}
$self->{'active_pos'} = $selected_row - 1;
@{$self->{'filtered_dir_list'}} = @filtered_dir_list;
my $last_row = $selected_row + query $dialog_dir_list -visibleItemCount;
while ($row <= $last_row) {
X::Motif::XmListAddItemUnselected($dialog_dir_list, '', $row);
++$row;
}
X::Motif::XmListSelectPos($dialog_dir_list, $selected_row, X::False);
X::Motif::XmListSetPos($dialog_dir_list, $selected_row - 1 || $selected_row);
$dialog_dir_list->Manage();
}
my $dialog_dir = $self->{'dialog_dir'};
if (defined $dialog_dir) {
change $dialog_dir -text => $self->{'dir'};
}
my $dialog_host = $self->{'dialog_host'};
if (defined $dialog_host) {
X::Motif::XmTextFieldSetString($dialog_host, $self->{'host'});
}
$self->filter();
}
sub switch_to {
my $self = shift;
my($location) = @_;
return if (!defined $location);
my($def_type, $def_host, $def_port);
if (!defined $self->{'storage'}) {
$def_type = 'file';
}
if ($location =~ s|^(\w+):||) {
if (defined $StorageTypes{$1}) {
$def_type = $1;
}
}
if ($location =~ s|^//([^/]+)||) {
$def_host = $1;
if ($def_host =~ s|:(\d+)$||) {
$def_port = $1;
}
}
$self->switch_to_storage($def_type) if (defined $def_type);
$self->switch_to_host($def_host, $def_port) if (defined $def_host);
$self->switch_to_dir($location);
}
sub switch_to_storage {
my $self = shift;
my($new_storage, $skip_update_display) = @_;
if (defined $new_storage && defined $StorageTypes{$new_storage}) {
my $storage = $self->{'storage'};
if (defined $storage) {
$storage->deactivate($self);
$storage = $self->{'inactive_storage'}{$new_storage};
if (!defined $storage) {
$StorageTypes{$new_storage}->new($self);
$self->{'storage'}->switch_to_host($self);
$self->{'storage'}->switch_to_dir($self);
}
else {
$self->{'storage'} = $storage;
$storage->activate($self);
}
}
else {
$StorageTypes{$new_storage}->new($self);
}
if (!defined $skip_update_display) {
my $dialog_storage = $self->{'dialog_storage'};
if (defined $dialog_storage) {
change $dialog_storage -menuHistory => $self->{'storage_options'}{$new_storage};
}
}
}
}
sub switch_to_host {
my $self = shift;
my($new_host, $new_port) = @_;
my $dialog_host = $self->{'dialog_host'};
if (defined $dialog_host) {
if (!defined $new_host) {
$new_host = X::Motif::XmTextFieldGetString($dialog_host);
}
else {
X::Motif::XmTextFieldSetString($dialog_host, $new_host);
}
}
if (defined $new_host) {
$self->{'storage'}->switch_to_host($self, $new_host, $new_port);
}
}
sub switch_to_dir {
my $self = shift;
my($new_dir) = @_;
if (defined $new_dir) {
$self->{'storage'}->switch_to_dir($self, $new_dir);
}
}
sub do_change_storage {
my($w, $user, $call) = @_;
$user->[0]->switch_to_storage($user->[1], 1);
$user->[0]->reload();
}
sub do_change_host {
my($w, $user, $call) = @_;
$user->switch_to_host();
$user->reload();
}
sub do_change_filter {
my($w, $user, $call) = @_;
$user->set_filter();
}
sub do_complete_partial_name {
my($w, $user, $call) = @_;
my $change = $call->text;
if (defined $change && ref($call->event) eq 'X::Event::KeyEvent') {
if ($change eq " ") {
$call->deny_change;
$user->complete_partial_name(X::Motif::XmTextFieldGetString($w), $w);
}
}
}
sub do_ok {
my($w, $user, $call) = @_;
$user->{'selection'} = X::Motif::XmTextFieldGetString($user->{'dialog_file'});
$user->{'done'} = 1;
}
sub do_cancel {
my($w, $user, $call) = @_;
$user->{'done'} = 2;
}
sub do_choose_dir {
my($w, $user, $call) = @_;
my $pos = $call->item_position() - 1;
my $active_pos = $user->{'active_pos'};
if ($pos != $active_pos) {
if ($pos < $active_pos) {
$user->{'storage'}->go_back($user, $pos);
}
else {
my $raw_pos = $user->{'filtered_dir_list'}[$pos - $active_pos - 1];
if (defined $raw_pos) {
$user->{'storage'}->go_forward($user, $raw_pos);
}
}
$user->reload();
}
}
sub do_choose_file {
my($w, $user, $call) = @_;
$user->{'selection'} = $call->item()->plain();
$user->{'done'} = 1;
}
sub do_maybe_choose_file {
my($w, $user, $call) = @_;
my $file = $call->item()->plain();
X::Motif::XmTextFieldSetString($user->{'dialog_file'}, $file);
}
sub create_dialog {
my $self = shift;
my($parent, $dialog_title) = @_;
my $shell = give $parent -Transient,
-resizable => X::True,
-title => $dialog_title;
my $form = give $shell -Form, -managed => X::False, -name => 'top_form',
-resizePolicy => X::Motif::XmRESIZE_GROW,
-horizontalSpacing => 5,
-verticalSpacing => 5;
my($storage, $menu) = give $form -OptionMenu,
-traversalOn => X::False,
-label => 'Storage System: ';
my $storage_options = {};
foreach (sort { $a->[1] cmp $b->[1] }
map { [$_->menu_name,
$_->menu_order,
$_->storage_name] } values %StorageTypes) {
$storage_options->{$_->[2]} = give $menu -Button, -text => $_->[0],
-command => [\&do_change_storage, [$self, $_->[2]]];
}
if (defined $self->{'storage'}) {
change $storage -menuHistory => $storage_options->{$self->{'storage'}->storage_name()};
}
my $spacer_1 = give $form -Spacer;
my $dir_form = give $form -Form, -name => 'dir_form',
-resizePolicy => X::Motif::XmRESIZE_GROW,
-verticalSpacing => 5;
my $host_label = give $dir_form -Label, -text => 'Computer:';
my $host = give $dir_form -Field, -text => $self->{'host'},
-sensitive => X::True,
-command => [\&do_change_host, $self];
my $dir_list_label = give $dir_form -Label, -text => 'Folders:';
my $dir_view = give $dir_form -ScrolledWindow;
my $dir_list = give $dir_view -List,
-traversalOn => X::False,
-visibleItemCount => 7,
-scrollBarDisplayPolicy => X::Motif::XmSTATIC,
-selectionPolicy => X::Motif::XmBROWSE_SELECT,
-listSizePolicy => X::Motif::XmVARIABLE;
$dir_list->AddCallback(X::Motif::XmNdefaultActionCallback, \&do_choose_dir, $self);
constrain $host_label -top => -form, -left => -form, -right => -form;
constrain $host -top => $host_label, -left => -form, -right => -form;
constrain $dir_list_label -top => $host, -left => -form, -right => -form;
constrain $dir_view -top => $dir_list_label, -left => -form, -right => -form, -bottom => -form;
my $file_form = give $form -Form, -name => 'file_form',
-resizePolicy => X::Motif::XmRESIZE_GROW,
-verticalSpacing => 5;
my $filter_label = give $file_form -Label, -text => 'Show Files Like:';
my $filter = give $file_form -Field, -text => $self->{'glob'};
$filter->AddCallback(X::Motif::XmNvalueChangedCallback, \&do_change_filter, $self);
my $file_list_label = give $file_form -Label, -text => 'Files:';
my $file_view = give $file_form -ScrolledWindow;
my $file_list = give $file_view -List,
-visibleItemCount => 7,
-scrollBarDisplayPolicy => X::Motif::XmSTATIC,
-selectionPolicy => X::Motif::XmBROWSE_SELECT,
-listSizePolicy => X::Motif::XmVARIABLE;
$file_list->AddCallback(X::Motif::XmNdefaultActionCallback, \&do_choose_file, $self);
$file_list->AddCallback(X::Motif::XmNbrowseSelectionCallback, \&do_maybe_choose_file, $self);
constrain $filter_label -top => -form, -left => -form, -right => -form;
constrain $filter -top => $filter_label, -left => -form, -right => -form;
constrain $file_list_label -top => $filter, -left => -form, -right => -form;
constrain $file_view -top => $file_list_label, -left => -form, -right => -form, -bottom => -form;
my $dir_label = give $form -Label, -text => 'Folder: ',
-alignment => X::Motif::XmALIGNMENT_END;
my $dir = give $form -Label, -text => $self->{'dir'},
-resizable => X::False;
my $file_label = give $form -Label, -text => 'File: ',
-alignment => X::Motif::XmALIGNMENT_END,
-width => (query $dir_label -width);
my $file = give $form -Field, -verifyBell => X::False;
$file->AddCallback(X::Motif::XmNmodifyVerifyCallback, \&do_complete_partial_name, $self);
$file->AddCallback(X::Motif::XmNactivateCallback, \&do_ok, $self);
my $spacer_3 = give $form -Spacer;
my $ok = give $form -Button, -text => 'OK', -command => [\&do_ok, $self];
my $cancel = give $form -Button, -text => 'Cancel', -command => [\&do_cancel, $self];
constrain $storage -top => -form, -left => -form;
constrain $spacer_1 -left => $storage, -right => -form;
constrain $dir_form -top => $storage, -left => -form, -bottom => $dir;
constrain $file_form -top => $storage, -left => $dir_form, -right => -form, -bottom => $dir;
constrain $dir_label -left => -form, -bottom => $file;
constrain $dir -left => $dir_label, -right => -form, -bottom => $file;
constrain $file_label -left => -form, -bottom => $cancel;
constrain $file -left => $file_label, -right => -form, -bottom => $cancel;
constrain $cancel -right => -form, -bottom => -form;
constrain $ok -right => $cancel, -bottom => -form;
constrain $spacer_3 -left => -form, -right => $ok;
$self->{'dialog_shell'} = $shell;
$self->{'dialog_form'} = $form;
$self->{'dialog_dir'} = $dir;
$self->{'dialog_file'} = $file;
$self->{'dialog_host'} = $host;
$self->{'dialog_storage'} = $storage;
$self->{'dialog_filter'} = $filter;
$self->{'dialog_dir_list'} = $dir_list;
$self->{'dialog_file_list'} = $file_list;
$self->{'storage_options'} = $storage_options;
foreach my $w ($file, $filter, $file_list) {
X::Motif::XmAddTabGroup($w);
}
return $form;
}
1;