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