1#!/usr/bin/env perl 2# 3########################################################################## 4# @(#) App::PFM::Browser::Files 0.13 5# 6# Name: App::PFM::Browser::Files 7# Version: 0.13 8# Author: Rene Uittenbogaard 9# Created: 2010-11-29 10# Date: 2011-03-28 11# 12 13########################################################################## 14 15=pod 16 17=head1 NAME 18 19App::PFM::Browser::Files 20 21=head1 DESCRIPTION 22 23This class is responsible for the file-specific part of browsing through 24the filesystem. 25It provides the Browser class with the necessary file data. 26 27=head1 METHODS 28 29=over 30 31=cut 32 33########################################################################## 34# declarations 35 36package App::PFM::Browser::Files; 37 38use base qw(App::PFM::Browser App::PFM::Abstract); 39 40use strict; 41use locale; 42 43########################################################################## 44# private subs 45 46=item _init(App::PFM::Screen $screen, App::PFM::Config $config, 47App::PFM::State $state) 48 49Initializes new instances. Called from the constructor. 50Stores the application's current state internally. 51 52=cut 53 54sub _init { 55 my ($self, $screen, $config, $state) = @_; 56 $self->{_state} = $state; 57 $self->{_swap_mode} = 0; 58 $self->SUPER::_init($screen, $config); 59 return; 60} 61 62########################################################################## 63# constructor, getters and setters 64 65=item browselist() 66 67Getter for the listing that is to be shown. 68 69=cut 70 71sub browselist { 72 my ($self) = @_; 73 return $self->{_state}->directory->showncontents; 74} 75 76=item cursorcol() 77 78Getter for the cursor column to be used in this browser. 79 80=cut 81 82sub cursorcol { 83 my ($self) = @_; 84 return $self->{_screen}->listing->cursorcol; 85} 86 87=item currentitem() 88 89Getter for the file at the cursor position. 90 91=cut 92 93sub currentitem { 94 my ($self) = @_; 95 return $self->currentfile; 96} 97 98=item currentfile() 99 100Getter for the file at the cursor position. 101 102=cut 103 104sub currentfile { 105 my ($self) = @_; 106 my $index = $self->{_currentline} + $self->{_baseindex}; 107 return $self->browselist->[$index]; 108} 109 110=item swap_mode( [ bool $swap_mode ] ) 111 112Getter/setter for the swap_mode variable, which indicates if the browser 113considers its current directory as 'swap' directory. 114 115=cut 116 117sub swap_mode { 118 my ($self, $value) = @_; 119 my $screen = $self->{_screen}; 120 if (defined($value)) { 121 $self->{_swap_mode} = $value; 122 $screen->set_deferred_refresh($screen->R_FRAME); 123 } 124 return $self->{_swap_mode}; 125} 126 127=item main_state( [ App::PFM::State $state ] ) 128 129Getter/setter for the I<_state> member variable, indicating which state 130this browser is operating on. 131 132=cut 133 134sub main_state { 135 my ($self, $value) = @_; 136 if (defined($value)) { 137 $self->{_state} = $value; 138 } 139 return $self->{_state}; 140} 141 142########################################################################## 143# public subs 144 145=item position_cursor( [ string $filename ] ) 146 147Positions the cursor at a specific file. Specifying a filename here 148overrules the I<position_at> variable. 149 150=cut 151 152sub position_cursor { 153 my ($self, $target) = @_; 154 $self->{_position_at} = $target if (defined $target and $target ne ''); 155 return if $self->{_position_at} eq ''; 156 my @browselist = @{$self->browselist}; 157 $self->{_currentline} = 0; 158 $self->{_baseindex} = 0 if $self->{_position_at} eq '..'; # descending 159 POSITION_ENTRY: { 160 for (0..$#browselist) { 161 if ($self->{_position_at} eq $browselist[$_]{name}) { 162 $self->{_currentline} = $_ - $self->{_baseindex}; 163 last POSITION_ENTRY; 164 } 165 } 166 $self->{_baseindex} = 0; 167 } 168 $self->{_position_at} = ''; 169 $self->{_position_exact} = 0; 170 $self->validate_position(); 171 $self->{_screen}->set_deferred_refresh($self->{_screen}->R_LISTING); 172 return; 173} 174 175=item position_cursor_fuzzy( [ string $filename ] ) 176 177Positions the cursor at the file with the closest matching name. 178Used by incremental find. 179 180=cut 181 182sub position_cursor_fuzzy { 183 my ($self, $target) = @_; 184 $self->{_position_at} = $target if (defined $target and $target ne ''); 185 return if $self->{_position_at} eq ''; 186 187 my @browselist = @{$self->browselist}; 188 my ($criterion); 189 190 # don't position fuzzy if sort mode is not by name, 191 # or exact positioning was requested 192 if ($self->{_position_exact} or $self->{_state}->sort_mode !~ /^[nm]$/io) { 193 goto &position_cursor; 194 } 195 196 for ($self->{_state}->sort_mode) { 197 $_ eq 'n' and do { 198 $criterion = sub { 199 return ($self->{_position_at} le 200 substr($_[0], 0, length($self->{_position_at})) 201 ); 202 }; 203 }; 204 $_ eq 'N' and do { 205 $criterion = sub { 206 return ($self->{_position_at} ge 207 substr($_[0], 0, length($self->{_position_at})) 208 ); 209 }; 210 }; 211 $_ eq 'm' and do { 212 $criterion = sub { 213 return (uc($self->{_position_at}) le 214 substr(uc($_[0]), 0, length($self->{_position_at})) 215 ); 216 }; 217 }; 218 $_ eq 'M' and do { 219 $criterion = sub { 220 return (uc($self->{_position_at}) ge 221 substr(uc($_[0]), 0, length($self->{_position_at})) 222 ); 223 }; 224 }; 225 } 226 227 $self->{_currentline} = 0; 228 if ($#browselist > 1) { 229 POSITION_ENTRY_FUZZY: { 230 for my $i (1..$#browselist) { 231 if ($criterion->($browselist[$i]{name})) { 232 $self->{_currentline} = 233 $self->find_best_find_match( 234 $self->{_position_at}, 235 $browselist[$i-1]{name}, 236 $browselist[$i ]{name} 237 ) 238 + $i - 1 - $self->{_baseindex}; 239 last POSITION_ENTRY_FUZZY; 240 } 241 } 242 $self->{_currentline} = $#browselist - $self->{_baseindex}; 243 } 244 } 245 $self->{_position_at} = ''; 246 $self->{_position_exact} = 0; 247 $self->validate_position(); 248 $self->{_screen}->set_deferred_refresh($self->{_screen}->R_LISTING); 249 return; 250} 251 252=item find_best_find_match(string $seek, string $first, string $second ) 253 254Decides which file out of two is the best match, I<e.g.> if there are 255two files C<Contractor.php> and C<Dealer.php>, and 'Coz' is searched, 256this method decides that C<Contractor.php> is the better match. 257 258Returns 0 (first match is better) or 1 (second is better). 259 260=cut 261 262sub find_best_find_match { 263 my ($self, $seek, $first, $second) = @_; 264 my $char; 265 if (lc $self->{_state}->sort_mode eq 'm') { 266 # case-insensitive 267 $first = lc $first; 268 $second = lc $second; 269 $seek = lc $seek; 270 } 271 for ($char = length($seek); $char > 0; $char--) { 272 if (substr($first, 0, $char) eq substr($seek, 0, $char)) { 273 return 0; 274 } 275 if (substr($second, 0, $char) eq substr($seek, 0, $char)) { 276 return 1; 277 } 278 } 279 return 1; 280} 281 282=item handle_non_motion_input(App::PFM::Event $event) 283 284Attempts to handle the non-motion event (keyboard- or mouse-input). 285Returns a hash reference with a member 'handled' indicating if this 286was successful, and a member 'data' with additional data (like 287the string 'quit' in case the user requested an application quit). 288 289=cut 290 291sub handle_non_motion_input { 292 my ($self, $event) = @_; 293 my $screen = $self->{_screen}; 294 my $screenheight = $screen->screenheight; 295 my $BASELINE = $screen->BASELINE; 296 my $res = {}; 297 # should not be necessary to initialize _itemcol and _itemlen here: 298 # they are set right after screen->refresh() in browse() 299# $self->{_itemcol} = $screen->listing->filerecordcol; 300# $self->{_itemlen} = length $screen->listing->currentformatline; 301 if ($event->{type} eq 'mouse') { 302 if ($event->{mouserow} >= $BASELINE and 303 $event->{mouserow} <= $BASELINE + $screenheight and 304 $event->{mousecol} >= $self->{_itemcol} and 305 $event->{mousecol} < $self->{_itemcol} + $self->{_itemlen} 306 ) { 307 # potentially on a fileline (might be diskinfo column though) 308 $event->{mouseitem} = ${$self->browselist}[ 309 $self->{_baseindex} + $event->{mouserow} - $BASELINE 310 ]; 311 } 312 } 313 # pass it to the commandhandler 314 $event->{name} = 'after_receive_non_motion_input'; 315 $event->{currentfile} = $self->currentfile; 316 $event->{lunchbox}{baseindex} = $self->{_baseindex}; 317 $event->{lunchbox}{currentline} = $self->{_currentline}; 318 $res->{data} = $self->fire($event); 319 $res->{handled} = $res->{data} ? 1 : 0; 320 # a space needs to be handled by both the CommandHandler 321 # and the Browser 322 if ($event->{type} eq 'key' and 323 $event->{data} eq ' ') 324 { 325 $res->{handled} = $self->handlemove($event->{data}); 326 } 327 if ($event->{type} eq 'mouse' and 328 defined($event->{mouseitem}) and 329 $self->{_config}{mouse_moves_cursor}) 330 { 331 $self->{_currentline} = $event->{mouserow} - $BASELINE; 332 } 333 return $res; 334} 335 336########################################################################## 337 338=back 339 340=head1 EVENTS 341 342This package implements the following events: 343 344=over 2 345 346=item after_receive_non_motion_input 347 348Called when the input event is not a browsing event. Probably 349the CommandHandler knows how to handle it. 350 351=back 352 353=head1 SEE ALSO 354 355pfm(1), App::PFM::Browser(3pm). 356 357=cut 358 3591; 360 361# vim: set tabstop=4 shiftwidth=4: 362