~yacinechaouche/+junk/BZR

« back to all changes in this revision

Viewing changes to CODE/TEST/PYTHON/URWID/browse.py

  • Committer: yacinechaouche at yahoo
  • Date: 2015-01-14 22:23:03 UTC
  • Revision ID: yacinechaouche@yahoo.com-20150114222303-6gbtqqxii717vyka
Ajout de CODE et PROD. Il faudra ensuite ajouter ce qu'il y avait dan TMP

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
#
 
3
# Urwid example lazy directory browser / tree view
 
4
#    Copyright (C) 2004-2009  Ian Ward
 
5
#
 
6
#    This library is free software; you can redistribute it and/or
 
7
#    modify it under the terms of the GNU Lesser General Public
 
8
#    License as published by the Free Software Foundation; either
 
9
#    version 2.1 of the License, or (at your option) any later version.
 
10
#
 
11
#    This library 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 GNU
 
14
#    Lesser General Public License for more details.
 
15
#
 
16
#    You should have received a copy of the GNU Lesser General Public
 
17
#    License along with this library; if not, write to the Free Software
 
18
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
19
#
 
20
# Urwid web site: http://excess.org/urwid/
 
21
 
 
22
"""
 
23
Urwid example lazy directory browser / tree view
 
24
 
 
25
Features:
 
26
- custom selectable widgets for files and directories
 
27
- custom message widgets to identify access errors and empty directories
 
28
- custom list walker for displaying widgets in a tree fashion
 
29
- outputs a quoted list of files and directories "selected" on exit
 
30
"""
 
31
 
 
32
import os
 
33
 
 
34
import urwid
 
35
 
 
36
 
 
37
class TreeWidget(urwid.WidgetWrap):
 
38
    """A widget representing something in the file tree."""
 
39
    def __init__(self, dir, name, index, display):
 
40
        self.dir = dir
 
41
        self.name = name
 
42
        self.index = index
 
43
 
 
44
        parent, _ign = os.path.split(dir)
 
45
        # we're at the top if parent is same as dir
 
46
        if dir == parent:
 
47
            self.depth = 0
 
48
        else:
 
49
            self.depth = dir.count(dir_sep())
 
50
 
 
51
        widget = urwid.Text(["  "*self.depth, display])
 
52
        self.widget = widget
 
53
        w = urwid.AttrWrap(widget, None)
 
54
        self.__super.__init__(w)
 
55
        self.selected = False
 
56
        self.update_w()
 
57
        
 
58
    
 
59
    def selectable(self):
 
60
        return True
 
61
    
 
62
    def keypress(self, size, key):
 
63
        """Toggle selected on space, ignore other keys."""
 
64
 
 
65
        if key == " ":
 
66
            self.selected = not self.selected
 
67
            self.update_w()
 
68
        else:
 
69
            return key
 
70
 
 
71
    def update_w(self):
 
72
        """
 
73
        Update the attributes of wrapped widget based on self.selected.
 
74
        """
 
75
        if self.selected:
 
76
            self._w.attr = 'selected'
 
77
            self._w.focus_attr = 'selected focus'
 
78
        else:
 
79
            self._w.attr = 'body'
 
80
            self._w.focus_attr = 'focus'
 
81
        
 
82
    def first_child(self):
 
83
        """Default to have no children."""
 
84
        return None
 
85
    
 
86
    def last_child(self):
 
87
        """Default to have no children."""
 
88
        return None
 
89
    
 
90
    def next_inorder(self):
 
91
        """Return the next TreeWidget depth first from this one."""
 
92
        #(yassine)
 
93
        # If I am a directory, the next element is always my first child
 
94
        # IF EXPANDED. Otherwise self.first_child() would return None.
 
95
        
 
96
        child = self.first_child()
 
97
        if child: 
 
98
            return child
 
99
        else:
 
100
            dir = get_directory(self.dir)
 
101
            return dir.next_inorder_from(self.index)
 
102
    
 
103
    def prev_inorder(self):
 
104
        """Return the previous TreeWidget depth first from this one."""
 
105
        
 
106
        dir = get_directory(self.dir)
 
107
        return dir.prev_inorder_from(self.index)
 
108
 
 
109
 
 
110
class EmptyWidget(TreeWidget):
 
111
    """A marker for expanded directories with no contents."""
 
112
 
 
113
    def __init__(self, dir, name, index):
 
114
        self.__super.__init__(dir, name, index, 
 
115
            ('flag',"(empty directory)"))
 
116
    
 
117
    def selectable(self):
 
118
        return False
 
119
    
 
120
 
 
121
class ErrorWidget(TreeWidget):
 
122
    """A marker for errors reading directories."""
 
123
 
 
124
    def __init__(self, dir, name, index):
 
125
        self.__super.__init__(dir, name, index, 
 
126
            ('error',"(error/permission denied)"))
 
127
    
 
128
    def selectable(self):
 
129
        return False
 
130
 
 
131
class FileWidget(TreeWidget):
 
132
    """Widget for a simple file (or link, device node, etc)."""
 
133
    
 
134
    def __init__(self, dir, name, index):
 
135
        self.__super.__init__(dir, name, index, name)
 
136
 
 
137
 
 
138
class DirectoryWidget(TreeWidget):
 
139
    """Widget for a directory."""
 
140
    
 
141
    def __init__(self, dir, name, index):
 
142
        self.__super.__init__(dir, name, index, "")
 
143
        
 
144
        # check if this directory starts expanded
 
145
        self.expanded = starts_expanded(os.path.join(dir,name))
 
146
        
 
147
        self.update_widget()
 
148
    
 
149
    def update_widget(self):
 
150
        """Update display widget text."""
 
151
        
 
152
        if self.expanded:
 
153
            mark = "+"
 
154
        else:
 
155
            mark = "-"
 
156
        self.widget.set_text(["  "*(self.depth),
 
157
            ('dirmark', mark), " ", self.name])
 
158
 
 
159
    def keypress(self, size, key):
 
160
        """Handle expand & collapse requests."""
 
161
        
 
162
        if key in ("+", "right"):
 
163
            self.expanded = True
 
164
            self.update_widget()
 
165
        elif key == "-":
 
166
            self.expanded = False
 
167
            self.update_widget()
 
168
        else:
 
169
            return self.__super.keypress(size, key)
 
170
    
 
171
    def mouse_event(self, size, event, button, col, row, focus):
 
172
        if event != 'mouse press' or button!=1:
 
173
            return False
 
174
 
 
175
        if row == 0 and col == 2*self.depth:
 
176
            self.expanded = not self.expanded
 
177
            self.update_widget()
 
178
            return True
 
179
        
 
180
        return False
 
181
    
 
182
    def first_child(self):
 
183
        """Return first child if expanded."""
 
184
        
 
185
        if not self.expanded: 
 
186
            return None
 
187
        full_dir = os.path.join(self.dir, self.name)
 
188
        dir = get_directory(full_dir)
 
189
        return dir.get_first()
 
190
    
 
191
    def last_child(self):
 
192
        """Return last child if expanded."""
 
193
        
 
194
        if not self.expanded:
 
195
            return None
 
196
        full_dir = os.path.join(self.dir, self.name)
 
197
        dir = get_directory(full_dir)
 
198
        widget = dir.get_last()
 
199
        sub = widget.last_child()
 
200
        if sub is not None:
 
201
            return sub
 
202
        return widget
 
203
        
 
204
        
 
205
 
 
206
class Directory:
 
207
    """Store sorted directory contents and cache TreeWidget objects."""
 
208
    def __init__(self, path):
 
209
        self.path = path
 
210
        self.widgets = {}
 
211
 
 
212
        dirs = []
 
213
        files = []
 
214
        try:
 
215
            # separate dirs and files
 
216
            for a in os.listdir(path):
 
217
                if os.path.isdir(os.path.join(path,a)):
 
218
                    dirs.append(a)
 
219
                else:
 
220
                    files.append(a)
 
221
        except OSError, e:
 
222
            self.widgets[None] = ErrorWidget(self.path, None, 0)
 
223
 
 
224
        # sort dirs and files
 
225
        dirs.sort(sensible_cmp)
 
226
        files.sort(sensible_cmp)
 
227
        # store where the first file starts
 
228
        self.dir_count = len(dirs)
 
229
        # collect dirs and files together again
 
230
        self.items = dirs + files
 
231
 
 
232
        # if no items, put a dummy None item in the list
 
233
        if not self.items:
 
234
            self.items = [None]
 
235
 
 
236
    def get_widget(self, name):
 
237
        """Return the widget for a given file.  Create if necessary."""
 
238
        
 
239
        if self.widgets.has_key(name):
 
240
            return self.widgets[name]
 
241
        
 
242
        # determine the correct TreeWidget type (constructor)
 
243
        index = self.items.index(name)
 
244
        if name is None:
 
245
            constructor = EmptyWidget
 
246
        elif index < self.dir_count:
 
247
            constructor = DirectoryWidget
 
248
        else:
 
249
            constructor = FileWidget
 
250
 
 
251
        widget = constructor(self.path, name, index)
 
252
        
 
253
        self.widgets[name] = widget
 
254
        return widget
 
255
 
 
256
        
 
257
    def next_inorder_from(self, index):
 
258
        """Return the TreeWidget following index depth first."""
 
259
    
 
260
        index += 1
 
261
        # try to get the next item at same level
 
262
        if index < len(self.items):
 
263
            return self.get_widget(self.items[index])
 
264
            
 
265
        # need to go up a level
 
266
        parent, myname = os.path.split(self.path)
 
267
        # give up if we can't go higher
 
268
        if parent == self.path: return None
 
269
 
 
270
        # find my location in parent, and return next inorder
 
271
        pdir = get_directory(parent)
 
272
        mywidget = pdir.get_widget(myname)
 
273
        return pdir.next_inorder_from(mywidget.index)
 
274
        
 
275
    def prev_inorder_from(self, index):
 
276
        """Return the TreeWidget preceeding index depth first."""
 
277
        
 
278
        index -= 1
 
279
        if index >= 0:
 
280
            widget = self.get_widget(self.items[index])
 
281
            widget_child = widget.last_child()
 
282
            if widget_child: 
 
283
                return widget_child
 
284
            else:
 
285
                return widget
 
286
 
 
287
        # need to go up a level
 
288
        parent, myname = os.path.split(self.path)
 
289
        # give up if we can't go higher
 
290
        if parent == self.path: return None
 
291
 
 
292
        # find myself in parent, and return
 
293
        pdir = get_directory(parent)
 
294
        return pdir.get_widget(myname)
 
295
 
 
296
    def get_first(self):
 
297
        """Return the first TreeWidget in the directory."""
 
298
        
 
299
        return self.get_widget(self.items[0])
 
300
    
 
301
    def get_last(self):
 
302
        """Return the last TreeWIdget in the directory."""
 
303
        
 
304
        return self.get_widget(self.items[-1])
 
305
        
 
306
 
 
307
 
 
308
class DirectoryWalker(urwid.ListWalker):
 
309
    """ListWalker-compatible class for browsing directories.
 
310
    
 
311
    positions used are directory,filename tuples."""
 
312
    
 
313
    def __init__(self, start_from, new_focus_callback):
 
314
        parent = start_from
 
315
        # (yassine)        
 
316
        # uses _dir_cache global variable. It stores instances of Directory
 
317
        dir = get_directory(parent)
 
318
        # this simple method will start a chain reaction creating the whole
 
319
        # directory structure as a WidgetTree. Jump to Directory.get_first() for more.
 
320
        widget = dir.get_first()
 
321
        self.focus = parent, widget.name
 
322
        # (yassine)
 
323
        # this callback will be called each time focus changes
 
324
        # (in set_focus) so that DirectoryBrowser updates its
 
325
        # header via show_focus.
 
326
        self._new_focus_callback = new_focus_callback
 
327
        new_focus_callback(self.focus)
 
328
 
 
329
    def get_focus(self):
 
330
        parent, name = self.focus
 
331
        dir = get_directory(parent)
 
332
        widget = dir.get_widget(name)
 
333
        return widget, self.focus
 
334
        
 
335
    def set_focus(self, focus):
 
336
        parent, name = focus
 
337
        self._new_focus_callback(focus)
 
338
        self.focus = parent, name
 
339
        self._modified()
 
340
    
 
341
    def get_next(self, start_from):
 
342
        parent, name = start_from
 
343
        dir = get_directory(parent)
 
344
        widget = dir.get_widget(name)
 
345
        target = widget.next_inorder()
 
346
        if target is None:
 
347
            return None, None
 
348
        return target, (target.dir, target.name)
 
349
 
 
350
    def get_prev(self, start_from):
 
351
        parent, name = start_from
 
352
        dir = get_directory(parent)
 
353
        widget = dir.get_widget(name)
 
354
        target = widget.prev_inorder()
 
355
        if target is None:
 
356
            return None, None
 
357
        return target, (target.dir, target.name)
 
358
                
 
359
 
 
360
        
 
361
class DirectoryBrowser:
 
362
    palette = [
 
363
        ('body', 'black', 'light gray'),
 
364
        ('selected', 'black', 'dark green', ('bold','underline')),
 
365
        ('focus', 'light gray', 'dark blue', 'standout'),
 
366
        ('selected focus', 'yellow', 'dark cyan', 
 
367
                ('bold','standout','underline')),
 
368
        ('head', 'yellow', 'black', 'standout'),
 
369
        ('foot', 'light gray', 'black'),
 
370
        ('key', 'light cyan', 'black','underline'),
 
371
        ('title', 'white', 'black', 'bold'),
 
372
        ('dirmark', 'black', 'dark cyan', 'bold'),
 
373
        ('flag', 'dark gray', 'light gray'),
 
374
        ('error', 'dark red', 'light gray'),
 
375
        ]
 
376
    
 
377
    footer_text = [
 
378
        ('title', "Directory Browser"), "    ",
 
379
        ('key', "UP"), ",", ('key', "DOWN"), ",",
 
380
        ('key', "PAGE UP"), ",", ('key', "PAGE DOWN"),
 
381
        "  ",
 
382
        ('key', "SPACE"), "  ",
 
383
        ('key', "+"), ",",
 
384
        ('key', "-"), "  ",
 
385
        ('key', "LEFT"), "  ",
 
386
        ('key', "HOME"), "  ", 
 
387
        ('key', "END"), "  ",
 
388
        ('key', "Q"),
 
389
        ]
 
390
    
 
391
    
 
392
    def __init__(self):
 
393
        cwd = os.getcwd()
 
394
        # (yassine)
 
395
        # initializes _init_cwd, which is used by starts_expanded.
 
396
        # starts_expanded is used by the DirectoryWidget constructor
 
397
        # a DirectoryWidget is created by the Directory.get_widget method
 
398
        # Directory.get_widget uses an internal cache and stores widgets
 
399
        # for every file or directory it is asked for
 
400
        store_initial_cwd(cwd)
 
401
        
 
402
        self.header = urwid.Text("")
 
403
        # DirectoryWalker is responsable for filling up our ListBox
 
404
        self.listbox = urwid.ListBox(DirectoryWalker(cwd, self.show_focus))
 
405
        self.listbox.offset_rows = 1
 
406
        self.footer = urwid.AttrWrap(urwid.Text(self.footer_text),
 
407
            'foot')
 
408
        self.view = urwid.Frame(
 
409
            urwid.AttrWrap(self.listbox, 'body'), 
 
410
            header=urwid.AttrWrap(self.header, 'head'), 
 
411
            footer=self.footer)
 
412
 
 
413
    def show_focus(self, focus):
 
414
        parent, ignore = focus
 
415
        self.header.set_text(parent)
 
416
 
 
417
    def main(self):
 
418
        """Run the program."""
 
419
        
 
420
        self.loop = urwid.MainLoop(self.view, self.palette,
 
421
            unhandled_input=self.unhandled_input)
 
422
        self.loop.run()
 
423
    
 
424
        # on exit, write the selected filenames to the console
 
425
        # (yassine) this works with global variable _dir_cache.
 
426
        # it looks for each widget stored inside _dir_cache
 
427
        # that has selected flag raised.
 
428
        
 
429
        names = [escape_filename_sh(x) for x in get_selected_names()]
 
430
        print " ".join(names)
 
431
 
 
432
    def unhandled_input(self, k):
 
433
            # update display of focus directory
 
434
            if k in ('q','Q'):
 
435
                raise urwid.ExitMainLoop()
 
436
            elif k == 'left':
 
437
                self.move_focus_to_parent()
 
438
            elif k == '-':
 
439
                self.collapse_focus_parent()
 
440
            elif k == 'home':
 
441
                self.focus_home()
 
442
            elif k == 'end':
 
443
                self.focus_end()
 
444
                    
 
445
    def collapse_focus_parent(self):
 
446
        """Collapse parent directory."""
 
447
        
 
448
        widget, pos = self.listbox.body.get_focus()
 
449
        self.move_focus_to_parent()
 
450
        
 
451
        pwidget, ppos = self.listbox.body.get_focus()
 
452
        if widget.dir != pwidget.dir:
 
453
            self.loop.process_input(["-"])
 
454
 
 
455
    def move_focus_to_parent(self):
 
456
        """Move focus to parent of widget in focus."""
 
457
        focus_widget, position = self.listbox.get_focus()
 
458
        parent, name = os.path.split(focus_widget.dir)
 
459
        
 
460
        if parent == focus_widget.dir:
 
461
            # no root dir, choose first element instead
 
462
            self.focus_home()
 
463
            return
 
464
        
 
465
        self.listbox.set_focus((parent, name), 'below')
 
466
        return 
 
467
        
 
468
    def focus_home(self):
 
469
        """Move focus to very top."""
 
470
        
 
471
        dir = get_directory("/")
 
472
        widget = dir.get_first()
 
473
        parent, name = widget.dir, widget.name
 
474
        self.listbox.set_focus((parent, name), 'below')
 
475
 
 
476
    def focus_end(self):
 
477
        """Move focus to far bottom."""
 
478
        
 
479
        dir = get_directory("/")
 
480
        widget = dir.get_last()
 
481
        parent, name = widget.dir, widget.name
 
482
        self.listbox.set_focus((parent, name), 'above')
 
483
 
 
484
 
 
485
def main():
 
486
    DirectoryBrowser().main()
 
487
 
 
488
 
 
489
 
 
490
 
 
491
#######
 
492
# global cache of directory information
 
493
_dir_cache = {}
 
494
 
 
495
def get_directory(name):
 
496
    """Return the Directory object for a given path.  Create if necessary."""
 
497
    
 
498
    if not _dir_cache.has_key(name):
 
499
        # (yassine)
 
500
        # the creation of a Directory does nothing in particular
 
501
        # it only stores names. The widgets are created just in time.
 
502
        # For example, first  widgets are created when calling dir.get_first()
 
503
        # inside DirectoryWalker.__init__. The mere creation of Dictionary
 
504
        # doesn't create the widgets. Methods like Directory.get_first do
 
505
        # (via get_widget). This is why it is considered to be a 'lazy' browser.
 
506
        # get_widget creates the widget if necessary (uses internal cache).
 
507
        _dir_cache[name] = Directory(name)
 
508
    return _dir_cache[name]
 
509
 
 
510
def directory_cached(name):
 
511
    """Return whether the directory is in the cache."""
 
512
    
 
513
    return _dir_cache.has_key(name)
 
514
 
 
515
def get_selected_names():
 
516
    """Return a list of all filenames marked as selected."""
 
517
    
 
518
    l = []
 
519
    for d in _dir_cache.values():
 
520
        for w in d.widgets.values():
 
521
            if w.selected:
 
522
                l.append(os.path.join(w.dir, w.name))
 
523
    return l
 
524
            
 
525
 
 
526
 
 
527
######
 
528
# store path components of initial current working directory
 
529
_initial_cwd = []
 
530
 
 
531
def store_initial_cwd(name):
 
532
    """Store the initial current working directory path components."""
 
533
    
 
534
    global _initial_cwd
 
535
    _initial_cwd = name.split(dir_sep())
 
536
 
 
537
def starts_expanded(name):
 
538
    """Return True if directory is a parent of initial cwd."""
 
539
    
 
540
    l = name.split(dir_sep())
 
541
    if len(l) > len(_initial_cwd):
 
542
        return False
 
543
    
 
544
    if l != _initial_cwd[:len(l)]:
 
545
        return False
 
546
    
 
547
    return True
 
548
 
 
549
 
 
550
def escape_filename_sh(name):
 
551
    """Return a hopefully safe shell-escaped version of a filename."""
 
552
 
 
553
    # check whether we have unprintable characters
 
554
    for ch in name: 
 
555
        if ord(ch) < 32: 
 
556
            # found one so use the ansi-c escaping
 
557
            return escape_filename_sh_ansic(name)
 
558
            
 
559
    # all printable characters, so return a double-quoted version
 
560
    name.replace('\\','\\\\')
 
561
    name.replace('"','\\"')
 
562
    name.replace('`','\\`')
 
563
    name.replace('$','\\$')
 
564
    return '"'+name+'"'
 
565
 
 
566
 
 
567
def escape_filename_sh_ansic(name):
 
568
    """Return an ansi-c shell-escaped version of a filename."""
 
569
    
 
570
    out =[]
 
571
    # gather the escaped characters into a list
 
572
    for ch in name:
 
573
        if ord(ch) < 32:
 
574
            out.append("\\x%02x"% ord(ch))
 
575
        elif ch == '\\':
 
576
            out.append('\\\\')
 
577
        else:
 
578
            out.append(ch)
 
579
            
 
580
    # slap them back together in an ansi-c quote  $'...'
 
581
    return "$'" + "".join(out) + "'"
 
582
 
 
583
 
 
584
def sensible_cmp(name_a, name_b):
 
585
    """Case insensitive compare with sensible numeric ordering.
 
586
    
 
587
    "blah7" < "BLAH08" < "blah9" < "blah10" """
 
588
    
 
589
    # ai, bi are indexes into name_a, name_b
 
590
    ai = bi = 0
 
591
    
 
592
    def next_atom(name, i):
 
593
        """Return the next 'atom' and the next index.
 
594
        
 
595
        An 'atom' is either a nonnegative integer or an uppercased 
 
596
        character used for defining sort order."""
 
597
        
 
598
        a = name[i].upper()
 
599
        i += 1
 
600
        if a.isdigit():
 
601
            while i < len(name) and name[i].isdigit():
 
602
                a += name[i]
 
603
                i += 1
 
604
            a = long(a)
 
605
        return a, i
 
606
        
 
607
    # compare one atom at a time
 
608
    while ai < len(name_a) and bi < len(name_b):
 
609
        a, ai = next_atom(name_a, ai)
 
610
        b, bi = next_atom(name_b, bi)
 
611
        if a < b: return -1
 
612
        if a > b: return 1
 
613
    
 
614
    # if all out of atoms to compare, do a regular cmp
 
615
    if ai == len(name_a) and bi == len(name_b): 
 
616
        return cmp(name_a,name_b)
 
617
    
 
618
    # the shorter one comes first
 
619
    if ai == len(name_a): return -1
 
620
    return 1
 
621
 
 
622
 
 
623
def dir_sep():
 
624
    """Return the separator used in this os."""
 
625
    return getattr(os.path,'sep','/')
 
626
 
 
627
 
 
628
if __name__=="__main__": 
 
629
    main()
 
630