2
# -*- coding: iso-8859-15 -*-
4
# Copyright (C) 2001-4 I�igo Serna
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
Copyright (C) 2001-4, I�igo Serna <inigoserna@telefonica.net>.
25
This software has been realised under the GPL License, see the COPYING
26
file that comes with this package. There is NO WARRANTY.
28
'Last File Manager' is (tries to be) a simple 'midnight commander'-type
29
application for UNIX console.
37
from __init__ import *
46
##################################################
48
##################################################
50
"""Main application class"""
55
def __init__(self, win, paths, npanels = 2):
56
self.win = win # root window, needed for resizing
57
self.npanels = npanels # no. of panels showed
58
self.panels = [] # list of panels
59
self.panel = 0 # active panel
62
self.prefs = preferences.Preferences(PREFSFILE, defaultprogs, filetypes)
63
self.modes = self.prefs.modes
64
# We need prefs now, but return code will be handled after
65
# curses initalization, because we might need windows.
66
ret = self.prefs.load()
67
# check for valid programs
68
self.prefs.check_progs(self.prefs.progs)
71
# Ok, now we can handle error messages.
73
messages.error('Load Preferences',
74
'\'%s\' does not exist\nusing default values' %
78
messages.error('Load Preferences',
79
'\'%s\' seems corrupted\nusing default values' %
82
# rest of the panels initialization.
84
self.panels.append(Panel(paths[0], 1, self)) # left panel
85
self.panels.append(Panel(paths[1], 2, self)) # right panel
87
self.panels.append(Panel(paths[0], 3, self)) # full panel
88
self.panels.append(Panel(paths[1], 0, self)) # not shown
92
def init_curses(self):
93
"""initialize curses stuff: windows, colors, ..."""
95
self.maxh, self.maxw = self.win.getmaxyx()
100
# create top and bottom windows
102
self.win_title = curses.newwin(1, 0, 0, 0)
103
self.win_status = curses.newwin(1, 0, self.maxh-1, 0)
105
print 'Can\'t create windows'
109
if curses.has_colors():
110
self.colors = self.prefs.colors
112
# Translation table: color name -> curses color name.
114
'black': curses.COLOR_BLACK,
115
'blue': curses.COLOR_BLUE,
116
'cyan': curses.COLOR_CYAN,
117
'green': curses.COLOR_GREEN,
118
'magenta': curses.COLOR_MAGENTA,
119
'red': curses.COLOR_RED,
120
'white': curses.COLOR_WHITE,
121
'yellow': curses.COLOR_YELLOW }
123
# Defaults of base objects. object, foregrounf, background
125
('title', 'yellow', 'blue'),
126
('files', 'white', 'black'),
127
('current_file', 'blue', 'cyan'),
128
('messages', 'magenta', 'cyan'),
129
('help', 'green', 'black'),
130
('file_info', 'red', 'black'),
131
('error_messages1', 'white', 'red'),
132
('error_messages2', 'black', 'red'),
133
('buttons', 'yellow', 'red'),
134
('selected_file', 'yellow', 'black'),
135
('current_selected_file', 'yellow', 'cyan') ]
137
# Initialize every color pair with user colors or with the defaults.
138
for i in range(len(colors)):
139
curses.init_pair(i+1,
140
self.__set_color(self.colors[colors[i][0]][0], self.coltbl[colors[i][1]]),
141
self.__set_color(self.colors[colors[i][0]][1], self.coltbl[colors[i][2]]))
143
self.win_title.attrset(curses.color_pair(1) | curses.A_BOLD)
144
self.win_title.bkgdset(curses.color_pair(1))
145
self.win_status.attrset(curses.color_pair(1))
146
self.win_status.bkgdset(curses.color_pair(1))
152
h, w = self.win.getmaxyx()
153
self.maxh, self.maxw = h, w
156
for p in self.panels:
158
p.win_files.refresh()
159
self.win.resize(h, w)
160
self.win_title.resize(1, w)
161
self.win_status.resize(1, w)
162
self.win_status.mvwin(h-1, 0)
163
for p in self.panels:
168
def __set_color(self, col, defcol):
169
"""return curses color value if exists, otherwise return default"""
170
if self.coltbl.has_key(col):
171
return self.coltbl[col]
176
def __show_bars(self):
177
"""show title and status bars"""
179
self.win_title.erase()
181
title = '%s v%s (c) %s, %s' % (LFM_NAME, VERSION, DATE, AUTHOR)
182
pos = (self.maxw-len(title)) /2
183
self.win_title.addstr(0, pos, title)
184
self.win_title.refresh()
186
self.win_status.erase()
187
panel = self.panels[self.panel]
188
if len(panel.selections):
191
for f in panel.selections:
192
size += panel.files[f][files.FT_SIZE]
193
self.win_status.addstr(' %s bytes in %d files' % \
194
(num2str(size), len(panel.selections)))
197
self.win_status.addstr('File: %4d of %-4d' % \
198
(panel.file_i + 1, panel.nfiles))
199
filename = panel.sorted[panel.file_i]
201
realpath = files.get_realpath(panel.path, filename,
202
panel.files[filename][files.FT_TYPE])
204
realpath = os.path.join(vfs.join(app, panel), filename)
205
if len(realpath) > self.maxw - 35:
206
path = '~' + realpath[-(self.maxw-37):]
209
path = files.fix_chars_in_filename(path)
210
self.win_status.addstr(0, 20, 'Path: ' + path)
213
self.win_status.addstr(0, self.maxw-8, 'F1=Help')
216
self.win_status.refresh()
220
"""show title, files panel(s) and status bar"""
223
for panel in self.panels:
228
"""run application"""
232
oldpanel = self.panels[self.panel].manage_keys()
234
if self.prefs.options['save_conf_at_exit']:
237
for panel in self.panels:
240
return self.panels[self.panel].path
242
for panel in self.panels:
246
# change panel active
253
def get_otherpanel(self):
254
"""return the panel not active"""
257
return self.panels[1]
259
return self.panels[0]
262
##################################################
264
##################################################
268
def __init__(self, path, pos, app):
269
self.maxh, self.maxw = app.maxh, app.maxw
270
self.__calculate_columns()
272
self.init_curses(pos)
273
self.init_dir(path, app)
276
def __calculate_columns(self):
277
self.pos_col2 = self.maxw / 2 - 14
278
self.pos_col1 = self.pos_col2 - 8
282
def __calculate_dims(self, pos=0):
283
if pos == 0: # not visible panel
284
return (self.maxh-2, self.maxw, 0, 0) # h, w, y, x
285
elif pos == 1: # left panel
286
return (self.maxh-2, int(self.maxw/2), 1, 0)
287
elif pos == 2: # right panel
288
return (self.maxh-2, int(self.maxw/2), 1, int(self.maxw/2))
289
elif pos == 3: # full panel
290
return (self.maxh-2, self.maxw, 1, 0) # h, w, y, x
292
messages.error('Initialize Panels',
293
'Incorrect panel number.\nLook for bugs if you can see this.')
294
return (self.maxh-2, int(self.maxw/2), 1, int(self.maxw/2))
297
def init_curses(self, pos=0):
298
self.dims = self.__calculate_dims(pos)
300
self.win_files = curses.newwin(self.dims[0], self.dims[1], self.dims[2], self.dims[3])
302
print 'Can\'t create panel window'
304
self.win_files.keypad(1)
305
if curses.has_colors():
306
self.win_files.attrset(curses.color_pair(2))
307
self.win_files.bkgdset(curses.color_pair(2))
310
def do_resize(self, h, w):
311
self.maxh, self.maxw = h, w
312
self.dims = self.__calculate_dims(self.pos)
313
self.win_files.resize(self.dims[0], self.dims[1])
314
self.win_files.mvwin(self.dims[2], self.dims[3])
315
self.__calculate_columns()
326
self.win_files.erase()
331
if self == app.panels[app.panel]:
332
self.win_files.attrset(curses.color_pair(5))
333
attr = curses.color_pair(6) | curses.A_BOLD
335
self.win_files.attrset(curses.color_pair(2))
336
attr = curses.color_pair(2)
340
path = vfs.join(app, self)
341
if len(path) > int(self.maxw/2) - 5:
342
title_path = '~' + path[-int(self.maxw/2)+5:]
347
self.win_files.addstr(0, 2, title_path, attr)
348
self.win_files.addstr(1, 1, center('Name', self.pos_col1-2),
349
curses.color_pair(2) | curses.A_BOLD)
350
self.win_files.addstr(1, self.pos_col1 + 2, 'Size',
351
curses.color_pair(2) | curses.A_BOLD)
352
self.win_files.addstr(1, self.pos_col2 + 5, 'Date',
353
curses.color_pair(2) | curses.A_BOLD)
356
for i in range(self.file_z - self.file_a + 1):
357
filename = self.sorted[i + self.file_a]
359
self.selections.index(filename)
361
attr = curses.color_pair(2)
363
attr = curses.color_pair(10) | curses.A_BOLD
365
res = files.get_fileinfo_dict(self.path, filename,
366
self.files[filename])
369
buf = self.__get_fileinfo_str_long(res)
370
self.win_files.addstr(i, 0, buf, attr)
371
# left or right panels
373
buf = self.__get_fileinfo_str_short(res)
374
self.win_files.addstr(i + 2, 1, buf, attr)
376
# vertical separators
378
self.win_files.vline(1, self.pos_col1,
379
curses.ACS_VLINE, self.dims[0]-2)
380
self.win_files.vline(1, self.pos_col2,
381
curses.ACS_VLINE, self.dims[0]-2)
383
# vertical scroll bar
389
y0, n = calculate_scrollbar_dims(h, self.nfiles, self.file_i)
392
self.win_files.vline(0, self.maxw - 1,
394
self.win_files.vline(y0, self.maxw - 1, curses.ACS_CKBOARD, n)
396
self.win_files.vline(0, self.maxw - 1, '^', 1)
397
if n == 1 and (y0 == 0):
398
self.win_files.vline(1, self.maxw - 1,
399
curses.ACS_CKBOARD, n)
400
if self.nfiles - 1 > self.file_a + h - 1:
401
self.win_files.vline(h - 1, self.maxw - 1, 'v', 1)
402
if n == 1 and (y0 == h - 1):
403
self.win_files.vline(h - 2, self.maxw - 1,
404
curses.ACS_CKBOARD, n)
406
self.win_files.vline(y0 + 2, int(self.maxw/2) - 1,
407
curses.ACS_CKBOARD, n)
409
self.win_files.vline(2, int(self.maxw/2) - 1, '^', 1)
410
if n == 1 and (y0 + 2 == 2):
411
self.win_files.vline(3, int(self.maxw/2) - 1,
412
curses.ACS_CKBOARD, n)
413
if self.nfiles - 1 > self.file_a + h - 1:
414
self.win_files.vline(h + 1, int(self.maxw/2) - 1, 'v', 1)
415
if n == 1 and (y0 + 2 == h + 1):
416
self.win_files.vline(h, int(self.maxw/2) - 1,
417
curses.ACS_CKBOARD, n)
419
self.win_files.refresh()
424
if self != app.panels[app.panel]: # panel not active
426
if self.pos == 3: # full panel
427
cursorbar = curses.newpad(1, self.maxw)
428
else: # left or right panel
429
cursorbar = curses.newpad(1, int(self.maxw/2) - 1)
431
cursorbar.attrset(curses.color_pair(3))
432
cursorbar.bkgdset(curses.color_pair(3))
433
filename = self.sorted[self.file_i]
434
# print "FILENAME: <%s>" % filename
437
self.selections.index(filename)
439
attr = curses.color_pair(3)
441
attr = curses.color_pair(11) | curses.A_BOLD
444
res = files.get_fileinfo_dict(self.path, filename,
445
self.files[filename])
447
buf = self.__get_fileinfo_str_long(res)
448
cursorbar.addstr(0, 0, buf, attr)
449
cursorbar.refresh(0, 0,
450
self.file_i % self.dims[0] + 1, 0,
451
self.file_i % self.dims[0] + 1, self.maxw - 2)
453
buf = self.__get_fileinfo_str_short(res)
454
cursorbar.addstr(0, 0, buf, attr)
455
cursorbar.addch(0, self.pos_col1-1, curses.ACS_VLINE)
456
cursorbar.addch(0, self.pos_col2-1, curses.ACS_VLINE)
458
cursorbar.refresh(0, 0,
459
self.file_i % (self.dims[0]-3) + 3, 1,
460
self.file_i % (self.dims[0]-3) + 3,
461
int(self.maxw/2) - 2)
463
cursorbar.refresh(0, 0,
464
self.file_i % (self.dims[0]-3) + 3,
465
int(self.maxw/2) + 1,
466
self.file_i % (self.dims[0]-3) + 3,
470
def __get_fileinfo_str_short(self, res):
471
filewidth = self.maxw/2-24
472
fname = res['filename']
473
if len(fname) > filewidth:
474
half = int(filewidth / 2)
475
fname = fname[:half+2] + '~' + fname[-half+3:]
476
fname = ljust(fname, self.pos_col1-2)
478
buf = '%c%s %3d,%3d %12s' % \
479
(res['type_chr'], fname,
480
res['maj_rdev'], res['min_rdev'],
483
buf = '%c%s %7s %12s' % \
484
(res['type_chr'], fname,
485
res['size'], res['mtime2'])
489
def __get_fileinfo_str_long(self, res):
490
fname = res['filename']
491
if len(fname) > self.maxw-57:
492
half = int((self.maxw-57) / 2)
493
fname = fname[:half+2] + '~' + fname[-half+2:]
495
buf = '%c%9s %-8s %-8s %3d,%3d %16s %s' % \
496
(res['type_chr'], res['perms'],
497
res['owner'][:8], res['group'][:8],
498
res['maj_rdev'], res['min_rdev'],
501
buf = '%c%9s %-8s %-8s %7s %16s %s' % \
502
(res['type_chr'], res['perms'],
503
res['owner'][:8], res['group'][:8],
510
def init_dir(self, path, application = None):
512
# HACK: hack to achieve to pass prefs to this function
513
# the first time it is executed
514
if application != None:
515
self.nfiles, self.files = files.get_dir(path, application.prefs.options['show_dotfiles'])
516
sortmode = application.modes['sort']
517
sort_mix_dirs = application.modes['sort_mix_dirs']
518
sort_mix_cases = application.modes['sort_mix_cases']
520
self.nfiles, self.files = files.get_dir(path, app.prefs.options['show_dotfiles'])
521
sortmode = app.modes['sort']
522
sort_mix_dirs = app.modes['sort_mix_dirs']
523
sort_mix_cases = app.modes['sort_mix_cases']
524
self.sorted = files.sort_dir(self.files, sortmode,
525
sort_mix_dirs, sort_mix_cases)
526
self.file_i = self.file_a = self.file_z = 0
528
self.path = os.path.abspath(path)
530
except (IOError, OSError), (errno, strerror):
531
messages.error('Enter In Directory', '%s (%d)' %
532
(strerror, errno), path)
533
# if not initialized, python can retrive app.initialized variable,
534
# so the try-except statement
536
if not app.initialized:
542
self.vfs = '' # vfs? if not -> blank string
543
self.base = '' # tempdir basename
544
self.vbase = self.path # virtual directory basename
548
def fix_limits(self):
551
if self.file_i > self.nfiles - 1:
552
self.file_i = self.nfiles - 1
553
if self.pos == 3 or self.pos == 0: # full or invisible panel
554
height = self.dims[0]
555
else: # left or right panel
556
height = self.dims[0] - 3
557
self.file_a = int(self.file_i / height) * height
558
self.file_z = self.file_a + height - 1
559
if self.file_z > self.nfiles - 1:
560
self.file_z = self.nfiles - 1
563
def refresh_panel(self, panel):
564
"""this is needed because panel could be changed"""
567
if path[-1] == os.sep:
569
while not os.path.exists(path):
570
path = os.path.dirname(path)
572
if path != panel.path:
575
pvfs, base, vbase = panel.vfs, panel.base, panel.vbase
576
panel.init_dir(panel.path)
577
panel.vfs, panel.base, panel.vbase = pvfs, base, vbase
579
panel.selections = []
581
filename_old = panel.sorted[panel.file_i]
582
selections_old = panel.selections[:]
583
pvfs, base, vbase = panel.vfs, panel.base, panel.vbase
584
panel.init_dir(panel.path)
585
panel.vfs, panel.base, panel.vbase = pvfs, base, vbase
587
panel.file_i = panel.sorted.index(filename_old)
591
panel.selections = selections_old[:]
592
for f in panel.selections:
593
if f not in panel.sorted:
594
panel.selections.remove(f)
598
def manage_keys(self):
601
ch = self.win_files.getch()
603
ch = self.win_files.getch() + 0x400
604
# print 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \
605
# (curses.keyname(ch), ch & 255, ch, ch)
606
# messages.win('Keyboard hitted:',
607
# 'key: \'%s\' <=> %c <=> 0x%X <=> %d' % \
608
# (curses.keyname(ch), ch & 255, ch, ch))
609
ret = actions.do(app, self, ch)
614
# elif ch in [0x12]: # Ctrl-r
616
# self.refresh_panel(self)
617
# self.refresh_panel(app.get_otherpanel())
620
##################################################
622
##################################################
637
##################################################
639
##################################################
640
def center(s, maxlen):
641
return s.center(maxlen)[:maxlen]
644
def ljust(s, maxlen):
645
return s.ljust(maxlen)[:maxlen]
648
def rjust(s, maxlen):
649
return s.rjust(maxlen)[maxlen:]
654
while num / 1000.0 >= 0.001:
655
num_list.append('%.3d' % (num % 1000))
659
if len(num_list) != 0:
661
num_str = ','.join(num_list)
662
while num_str[0] == '0':
663
num_str = num_str[1:]
667
def calculate_scrollbar_dims(h, nels, i):
668
"""calculate scrollbar initial position and size"""
671
n = int(h * h / nels)
675
y0 = int(a * h / nels)
686
##################################################
688
##################################################
689
def usage(prog, msg = ""):
690
prog = os.path.basename(prog)
692
print '%s:\t%s\n' % (prog, msg)
694
%s v%s - (C) %s, by %s
696
A program trying to recover command line good ol' times feelings...
697
Released under GNU Public License, read COPYING for more details.
699
Usage:\t%s\t[-h | --help]
701
\t\t[-1 | -2] [pathtodir1 | pathtofile [pathtodir2]]
703
-1\t\t\tstart in 1 panel mode
704
-2\t\t\tstart in 2 panels mode (default)
705
-d, --debug\t\tcreate debug file
706
-h, --help\t\tshow help
707
pathtodir1\t\tdirectory
708
pathtofile\t\tfile to view/edit
709
pathtodir2\t\tdirectory to show in panel 2\
710
""" % (LFM_NAME, VERSION, DATE, AUTHOR, prog)
713
def lfm_exit(ret_code, ret_path='.'):
714
f = open('/tmp/lfm-%s.path' % (os.getppid()), 'w')
720
def main(win, path, npanels):
723
app = Lfm(win, path, npanels)
740
opts, args = getopt.getopt(sysargs[1:], '12dh',
741
['', '', 'debug', 'help'])
742
except getopt.GetoptError:
743
usage(sysargs[0], 'Bad argument(s)')
750
if o in ('-d', '--debug'):
752
if o in ('-h', '--help'):
757
paths.append(os.path.abspath('.'))
758
paths.append(os.path.abspath('.'))
760
buf = os.path.abspath(args[0])
761
if not os.path.isdir(buf):
762
if os.path.isfile(buf):
766
usage(sysargs[0], '<%s> is not a file or directory' % args[0])
769
paths.append(os.path.abspath('.'))
771
buf = os.path.abspath(args[0])
772
if not os.path.isdir(buf):
773
usage(sysargs[0], '<%s> is not a file or directory' % args[0])
776
buf = os.path.abspath(args[1])
777
if not os.path.isdir(buf):
778
usage(sysargs[0], '<%s> is not a file or directory' % args[1])
782
usage(sysargs[0], 'Incorrect number of arguments')
785
DEBUGFILE = './lfm-log.debug'
787
debug = open(DEBUGFILE, 'w')
788
debug.write('********** Start: ')
789
debug.write(time.ctime(time.time()) + ' **********\n')
793
path = curses.wrapper(main, paths, npanels)
796
debug.write('********** End: ')
797
debug.write(time.ctime(time.time()) + ' **********\n')
800
sys.stdout = sys.__stdout__
801
sys.stdout = sys.__stderr__
809
if __name__ == '__main__':