1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import os
18 import traceback
19
20 import libxyz.ui
21 import libxyz.core
22 import libxyz.const
23 import libxyz.exceptions
24
25 from libxyz.core.utils import ustring, bstring, is_func
26 from libxyz.ui import lowui
27 from libxyz.ui import align
28 from libxyz.ui import Shortcut
29 from libxyz.ui.utils import refresh
30 from libxyz.ui.utils import truncate
31 from libxyz.vfs.types import VFSTypeFile
32 from libxyz.vfs.local import LocalVFSObject
33
34 -class Panel(lowui.WidgetWrap):
35 """
36 Panel is used to display filesystem hierarchy
37 """
38
39 resolution = (u"panel", u"widget")
40 context = u":sys:panel"
41
42 EVENT_SHUTDOWN = u"event:shutdown"
43 EVENT_BEFORE_SWITCH_TAB = u"event:sys:panel:before_switch_tab"
44 EVENT_SWITCH_TAB = u"event:sys:panel:switch_tab"
45 EVENT_NEW_TAB = u"event:sys:panel:new_tab"
46 EVENT_DEL_TAB = u"event:sys:panel:del_tab"
47
49 self.xyz = xyz
50 self.conf = self.xyz.conf[u"plugins"][u":sys:panel"]
51
52 self._keys = libxyz.ui.Keys()
53
54 _size = self.xyz.screen.get_cols_rows()
55 _blocksize = libxyz.ui.Size(rows=_size[1] - 1, cols=_size[0] / 2 - 2)
56 self._enc = xyzenc
57 self._stop = False
58 self._resize = False
59
60 self._set_plugins()
61 self._cmd = libxyz.ui.Cmd(xyz)
62 _cwd = os.getcwd()
63
64 self.filters = self._build_filters()
65 self.xyz.hm.register("event:conf_update", self._update_conf_hook)
66
67 self.block1 = Block(xyz, _blocksize, _cwd, self._enc, active=True)
68 self.block2 = Block(xyz, _blocksize, _cwd, self._enc)
69 self._compose()
70
71 super(Panel, self).__init__(self._widget)
72
73
74
76 """
77 Hook for update conf event
78 """
79
80
81 if sect != "plugins" or var != ":sys:panel":
82 return
83
84 if "filters" in val or "filters_enabled" in val:
85 self.filters = self._build_filters()
86
87
88
90 """
91 Compose widgets
92 """
93
94 columns = lowui.Columns([self.block1, self.block2], 0)
95 self._widget = lowui.Pile([columns, self._cmd])
96
97
98
100 """
101 Compile filters
102 """
103
104 filters = []
105
106
107 if not self.conf[u"filters_enabled"]:
108 return filters
109
110 for f in self.conf[u"filters"]:
111 try:
112 rule = libxyz.core.FSRule(ustring(f))
113 except libxyz.exceptions.ParseError, e:
114 xyzlog.error(_(u"Error compiling filter: %s" %
115 ustring(str(e))))
116 continue
117 else:
118 filters.append(rule)
119
120 return filters
121
122
123
124 @property
126 if self.block1.active:
127 return self.block1
128 else:
129 return self.block2
130
131
132
133 @property
135 if self.block1.active:
136 return self.block2
137 else:
138 return self.block1
139
140
141
143 """
144 Start working loop
145 """
146
147 _dim = self.xyz.screen.get_cols_rows()
148
149 while True:
150 if self._stop:
151 break
152
153 canv = self.xyz.top.render(_dim, True)
154 self.xyz.screen.draw_screen(_dim, canv)
155
156 _input = self.xyz.input.get()
157
158 if _input:
159 try:
160 self._cmd.keypress(_dim, _input)
161 except Exception, e:
162 xyzlog.error(_(u"Error executing bind (%s): %s") %
163 (Shortcut(_input), ustring(str(e))))
164 xyzlog.debug(ustring(traceback.format_exc(),
165 self._enc))
166
167 if self.xyz.input.resized:
168 self._resize = True
169
170 if self._resize:
171 self._resize = False
172 _dim = self.xyz.screen.get_cols_rows()
173 _bsize = libxyz.ui.Size(rows=_dim[1] - 1,
174 cols=_dim[0] / 2 - 2)
175
176 self.block1.size = _bsize
177 self.block2.size = _bsize
178 self._cmd._invalidate()
179 self.block1._invalidate()
180 self.block2._invalidate()
181
182
183
185 """
186 Set virtual plugins
187 """
188
189
190 _run_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"run")
191 _run_plugin.export(self.shutdown)
192 _run_plugin.export(self.repaint)
193
194 _run_plugin.VERSION = u"0.1"
195 _run_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>"
196 _run_plugin.BRIEF_DESCRIPTION = u"Run plugin"
197 _run_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name"
198
199
200 _panel_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"panel")
201 _panel_plugin.export(self.entry_next)
202 _panel_plugin.export(self.entry_prev)
203 _panel_plugin.export(self.entry_top)
204 _panel_plugin.export(self.entry_bottom)
205 _panel_plugin.export(self.block_next)
206 _panel_plugin.export(self.block_prev)
207 _panel_plugin.export(self.switch_active)
208 _panel_plugin.export(self.get_selected)
209 _panel_plugin.export(self.get_tagged)
210 _panel_plugin.export(self.get_untagged)
211 _panel_plugin.export(self.get_current)
212 _panel_plugin.export(self.get_active)
213 _panel_plugin.export(self.toggle_tag)
214 _panel_plugin.export(self.tag_all)
215 _panel_plugin.export(self.untag_all)
216 _panel_plugin.export(self.tag_invert)
217 _panel_plugin.export(self.tag_rule)
218 _panel_plugin.export(self.untag_rule)
219 _panel_plugin.export(self.swap_blocks)
220 _panel_plugin.export(self.reload)
221 _panel_plugin.export(self.reload_all)
222 _panel_plugin.export(self.action)
223 _panel_plugin.export(self.chdir)
224 _panel_plugin.export(self.search_forward)
225 _panel_plugin.export(self.search_backward)
226 _panel_plugin.export(self.search_cycle)
227 _panel_plugin.export(self.show_tagged)
228 _panel_plugin.export(self.select)
229 _panel_plugin.export(self.cwd)
230 _panel_plugin.export(self.vfs_driver)
231 _panel_plugin.export(self.filter)
232 _panel_plugin.export(self.sort)
233 _panel_plugin.export(self.new_tab)
234 _panel_plugin.export(self.del_tab)
235 _panel_plugin.export(self.switch_tab)
236 _panel_plugin.export(self.next_tab)
237 _panel_plugin.export(self.prev_tab)
238 _panel_plugin.export(self.get_tabs)
239 _panel_plugin.export(self.active_tab)
240
241 _panel_plugin.VERSION = u"0.1"
242 _panel_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>"
243 _panel_plugin.BRIEF_DESCRIPTION = u"Panel plugin"
244 _panel_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name"
245 _panel_plugin.DOC = u"""\
246 Configuration variables:
247 filters_enabled - Enable permanent filters. Default - False
248 filters_policy - Filters policy.
249 If True - filter out objects which matching the rule.
250 If False - filter out objects which do not match the rule. Default - True
251 filters - List of permanent filters.
252 Filters applied in defined order sequentially. Default - []
253 sorting_policy - Active sorting policy name or None. Default - None
254 sorting - Defined sorting policies. Each key corresponds to a policy name
255 and value is either a function with two arguments (VFSObject) behaving
256 like cmp() or a list of those functions. If value is a list, each function
257 applied sequentially. Default - []"""
258
259 _panel_plugin.EVENTS = [
260 ("before_switch_tab",
261 "Fires before switching to another tab. "\
262 "Arguments: Block instance and old tab index."),
263
264 ("switch_tab",
265 "Fires when switching to another tab. "\
266 "Arguments: Block instance and new tab index."),
267
268 ("new_tab",
269 "Fires when new tab is added. "\
270 "Arguments: Block instance and new tab index."),
271
272 ("del_tab",
273 "Fires when tab is delete. "\
274 "Arguments: Block instance and deleted tab index."),
275 ]
276
277 self.xyz.pm.register(_run_plugin)
278 self.xyz.pm.register(_panel_plugin)
279
280
281
293
294
295
297 """
298 Reparint screen
299 """
300
301 self._resize = True
302 self.xyz.screen.clear()
303
304
305
306 - def entry_next(self):
307 """
308 Next entry
309 """
310
311 return self.active.next()
312
313
314
315 - def entry_prev(self):
316 """
317 Previous entry
318 """
319
320 return self.active.prev()
321
322
323
324 - def entry_top(self):
325 """
326 Top entry
327 """
328
329 return self.active.top()
330
331
332
333 - def entry_bottom(self):
334 """
335 Bottom entry
336 """
337
338 return self.active.bottom()
339
340
341
353
354
355
362
363
364
371
372
373
375 """
376 Get selected VFSObject instance
377 """
378
379 if active:
380 obj = self.active
381 else:
382 obj = self.inactive
383
384 return obj.get_selected()
385
386
387
389 """
390 Return list of tagged VFSObject instances
391 """
392
393 if active:
394 obj = self.active
395 else:
396 obj = self.inactive
397
398 return obj.get_tagged()
399
400
401
403 """
404 Return list of not tagged VFSObject instances
405 """
406
407 if active:
408 obj = self.active
409 else:
410 obj = self.inactive
411
412 return obj.get_untagged()
413
414
415
417 """
418 Return VFSObject instance of current VFSObject
419 """
420
421 if active:
422 obj = self.active
423 else:
424 obj = self.inactive
425
426 return obj.get_current()
427
428
429
431 """
432 Return list of tagged VFSObject instances or list of single selected
433 object if none tagged
434 """
435
436 return self.get_tagged() or [self.get_selected()]
437
438
439
451
452
453
455 """
456 Tag every single object in current dir
457 """
458
459 if active:
460 obj = self.active
461 else:
462 obj = self.inactive
463
464 return obj.tag_all()
465
466
467
469 """
470 Untag every single object in current dir
471 """
472
473 if active:
474 obj = self.active
475 else:
476 obj = self.inactive
477
478 return obj.untag_all()
479
480
481
483 """
484 Invert currently tagged files
485 """
486
487 if active:
488 obj = self.active
489 else:
490 obj = self.inactive
491
492 return obj.tag_invert()
493
494
495
497 """
498 Tag files by combined rule
499 """
500
501 if active:
502 obj = self.active
503 else:
504 obj = self.inactive
505
506 return obj.tag_rule()
507
508
509
511 """
512 Untag files by combined rules
513 """
514
515 if active:
516 obj = self.active
517 else:
518 obj = self.inactive
519
520 return obj.untag_rule()
521
522
523
525 """
526 Swap panel blocks
527 """
528
529 self.block1, self.block2 = self.block2, self.block1
530 self._compose()
531
532 if hasattr(self, "set_w"):
533 self.set_w(self._widget)
534 else:
535 self._w = self._widget
536
537
538
539 - def reload(self, active=True):
540 """
541 Reload contents
542 """
543
544 if active:
545 obj = self.active
546 else:
547 obj = self.inactive
548
549 return obj.reload()
550
551
552
560
561
562
563 - def action(self, active=True):
564 """
565 Perfrom action on selected object
566 """
567
568 if active:
569 obj = self.active
570 else:
571 obj = self.inactive
572
573 return obj.action()
574
575
576
577 - def chdir(self, path, active=True):
588
589
590
597
598
599
606
607
608
610 """
611 Enable cyclic search-when-you-type mode
612 """
613
614 self.active.search_cycle()
615
616
617
629
630
631
632 - def select(self, name, active=True):
633 """
634 Select VFS object by given name in current directory
635 """
636
637 if active:
638 obj = self.active
639 else:
640 obj = self.inactive
641
642 return obj.select(name)
643
644
645
646 - def cwd(self, active=True):
647 """
648 Get current working directory
649 """
650
651 if active:
652 obj = self.active
653 else:
654 obj = self.inactive
655
656 return obj.cwd
657
658
659
661 """
662 Return vfs driver used by object. None stands for LocalVFS
663 """
664
665 if active:
666 obj = self.active
667 else:
668 obj = self.inactive
669
670 return obj.vfs_driver
671
672
673
675 """
676 Filter objects
677 """
678
679 if not self.conf["filters_enabled"]:
680 return objects
681
682 policy = self.conf["filters_policy"]
683
684 def policyf(res):
685 if policy == True:
686 result = not res
687 else:
688 result = res
689
690 return result
691
692 for f in self.filters:
693 objects = [x for x in objects if policyf(f.match(x))]
694
695 return objects
696
697
698
699 - def sort(self, objects):
700 """
701 Sort objects
702 """
703
704 policy = self.conf["sorting_policy"]
705
706 if policy is None:
707 return objects
708
709 if policy not in self.conf["sorting"]:
710 xyzlog.warning(_(u"Unable to find `%s` sorting policy" %
711 ustring(policy)))
712 return objects
713
714 policy_data = self.conf["sorting"][policy]
715
716 if is_func(policy_data):
717 objects.sort(cmp=policy_data)
718 elif isinstance(policy_data, list):
719 for f in policy_data:
720 objects.sort(cmp=f)
721
722 return objects
723
724
725
726 - def new_tab(self, tabname=None, active=True):
727 """
728 Create new tab
729 """
730
731 if active:
732 obj = self.active
733 else:
734 obj = self.inactive
735
736 return obj.tab_bar.new_tab(tabname)
737
738
739
740 - def del_tab(self, index=None, active=True):
741 """
742 Delete tab. If index is None - delete current tab
743 """
744
745 if active:
746 obj = self.active
747 else:
748 obj = self.inactive
749
750 return obj.tab_bar.del_tab(index)
751
752
753
765
766
767
769 """
770 Switch to the next tab
771 """
772
773 if active:
774 obj = self.active
775 else:
776 obj = self.inactive
777
778 return obj.tab_bar.next_tab()
779
780
781
783 """
784 Switch to the previous tab
785 """
786
787 if active:
788 obj = self.active
789 else:
790 obj = self.inactive
791
792 return obj.tab_bar.prev_tab()
793
794
795
797 """
798 Return list of tabs in format:
799 [(path, selected_name)]
800 """
801
802 if active:
803 obj = self.active
804 else:
805 obj = self.inactive
806
807 return obj.get_tabs()
808
809
810
812 """
813 Get active tab index
814 """
815
816 if active:
817 obj = self.active
818 else:
819 obj = self.inactive
820
821 return obj.tab_bar.active_tab
822
823
824
825 -class Block(lowui.FlowWidget):
826 """
827 Single panel block
828 """
829
830 - def __init__(self, xyz, size, path, enc, active=False):
831 """
832 @param xyz: XYZData instance
833 @param size: Block widget size
834 @type size: L{libxyz.ui.Size}
835 @param enc: Local encoding
836 @param active: Boolean flag, True if block is active
837
838 Required resources: cwdtitle, cwdtitleinact, panel, cursor, info
839 border, tagged
840 """
841
842 self.xyz = xyz
843 self.size = size
844 self.attr = lambda x: self.xyz.skin.attr(Panel.resolution, x)
845
846 self.term_width = lambda x: lowui.util.calc_width(x, 0, len(x))
847
848 self.active = active
849 self.selected = 0
850 self.cwd = path
851
852 self._display = []
853 self._vindex = 0
854 self._from = 0
855 self._to = 0
856 self._force_reload = False
857 self.entries = []
858 self._dir = None
859 self._len = 0
860 self._palettes = []
861 self._vfsobj = None
862 self._title = u""
863 self._tagged = []
864 self._tab_data = []
865
866 self._cursor_attr = None
867 self._custom_info = None
868 self._keys = libxyz.ui.Keys()
869 self._cmd = self.xyz.pm.load(":sys:cmd")
870 self._filter = self.xyz.pm.from_load(":sys:panel", "filter")
871 self._sort = self.xyz.pm.from_load(":sys:panel", "sort")
872
873 self._pending = libxyz.core.Queue(20)
874 self._re_raw = r".*"
875 self._rule_raw = ""
876 self._enc = enc
877 self.vfs_driver = None
878
879 self.tab_bar = TabBar(self.xyz, self.attr, self)
880
881 self._winfo = lowui.Text(u"")
882 self._sep = libxyz.ui.Separator()
883
884 _info = self._make_info()
885 _title_attr = self._get_title_attr()
886
887 self.frame = lowui.Frame(lowui.Filler(lowui.Text("")), footer=_info)
888
889 self.border = libxyz.ui.Border(self.frame, self._title,
890 _title_attr, self.attr(u"border"))
891 self.block = lowui.Frame(
892 lowui.AttrWrap(self.border, self.attr(u"panel")),
893 header=self.tab_bar)
894
895 self.xyz.hm.register(Panel.EVENT_BEFORE_SWITCH_TAB,
896 self._before_switch_tab_hook)
897 self.xyz.hm.register(Panel.EVENT_SWITCH_TAB, self._switch_tab_hook)
898 self.xyz.hm.register(Panel.EVENT_NEW_TAB, self._new_tab_hook)
899 self.xyz.hm.register(Panel.EVENT_DEL_TAB, self._del_tab_hook)
900
901 self.tab_bar.new_tab()
902
903 super(Block, self).__init__()
904
905
906
907 - def rows(self, (maxcol,), focus=False):
911
912
913
916
917
918
920 _parent, _dir, _dirs, _files = vfsobj.walk()
921
922 self._dir = _dir
923
924 _entries = []
925 _entries.extend(_dirs)
926 _entries.extend(_files)
927 _entries = self._filter(_entries)
928 _entries = self._sort(_entries)
929 _entries.insert(0, _parent)
930
931 self._title = truncate(_dir.full_path, self.size.cols - 4,
932 self._enc, True)
933
934 if hasattr(self, "border"):
935 self.border.set_title(self._title)
936
937 self._tagged = []
938
939 self.entries = _entries
940 self._len = len(self.entries)
941 self._palettes = self._process_skin_rulesets()
942 self._vfsobj = vfsobj
943 self.vfs_driver = vfsobj.driver
944
945 self._force_reload = True
946
947
948
951
952
953
954 - def render(self, (maxcol,), focus=False):
955 """
956 Render block
957 """
958
959 w = self.display_widget((maxcol,), focus)
960 maxrow = w.rows((maxcol,), focus)
961
962
963 maxcol_orig, maxcol = maxcol, maxcol - 2
964 maxrow_orig, maxrow = maxrow, maxrow - 5
965
966
967 while True:
968 try:
969 _act = self._pending.pop()
970 except IndexError:
971 break
972 else:
973 _act(maxcol, maxrow)
974
975 if self._custom_info is not None:
976 self._set_custom_info(self._custom_info, maxcol)
977 else:
978 self._set_info(self.entries[self.selected], maxcol)
979
980 _tlen = len(self._tagged)
981
982 if _tlen > 0:
983 _text = _(u"%s bytes (%d)") % (
984 self._make_number_readable(
985 reduce(lambda x, y: x + y,
986 [self.entries[x].size for x in self._tagged
987 if isinstance(self.entries[x].ftype, VFSTypeFile)
988 ], 0)), _tlen)
989
990 self._sep.set_text(bstring(_text, self._enc),
991 self.attr(u"tagged"))
992 else:
993 self._sep.clear_text()
994
995 self._display = self._get_visible(maxrow, maxcol, self._force_reload)
996 self._force_reload = False
997
998 _len = len(self._display)
999
1000 canvases = []
1001
1002 for i in xrange(0, _len):
1003 _text = self._display[i]
1004 _own_attr = None
1005 _abs_i = self._from + i
1006
1007 if self._cursor_attr is not None and i == self._vindex:
1008 _own_attr = self._cursor_attr
1009 elif self.active and i == self._vindex:
1010 _own_attr = self.attr(u"cursor")
1011 elif _abs_i in self._tagged:
1012 _own_attr = self.attr(u"tagged")
1013 elif _abs_i in self._palettes:
1014 _own_attr = self._palettes[_abs_i]
1015
1016 if _own_attr is not None:
1017 x = lowui.AttrWrap(lowui.Text(bstring(_text, self._enc)),
1018 _own_attr).render((maxcol,))
1019 canvases.append((x, i, False))
1020 else:
1021 canvases.append((lowui.Text(_text).render((maxcol,)),
1022 i, False))
1023
1024 if _len < maxrow:
1025 _pad = lowui.AttrWrap(lowui.Text(" "), self.attr(u"panel"))
1026 canvases.append((_pad.render((maxcol,), focus), 0, False))
1027
1028 _info = self._make_info()
1029 self.frame.set_footer(_info)
1030
1031 combined = lowui.CanvasCombine(canvases)
1032 border = self.block.render((maxcol_orig, maxrow_orig), focus)
1033
1034 if _len > maxrow:
1035 combined.trim_end(_len - maxrow)
1036
1037 return lowui.CanvasOverlay(combined, border, 1, 2)
1038
1039
1040
1046
1047
1048
1050 _res = []
1051
1052 i = 0
1053 _sep = False
1054
1055 for x in reversed(unicode(num)):
1056 if _sep:
1057 _res.append(u"_")
1058 _sep = False
1059
1060 _res.append(x)
1061
1062 if i > 0 and (i + 1) % 3 == 0:
1063 _sep = True
1064
1065 i += 1
1066
1067 _res.reverse()
1068
1069 return u"".join(_res)
1070
1071
1072
1074 """
1075 Get currently visible piece of entries
1076 """
1077
1078 _len = self._len
1079 _from, _to, self._vindex = self._update_vindex(rows)
1080
1081 if reload or ((_from, _to) != (self._from, self._to)):
1082 self._from, self._to = _from, _to
1083 self._display = []
1084
1085 for _obj in self.entries[self._from:self._to]:
1086 _text = "%s%s "% (_obj.vtype, _obj.name)
1087 _text = truncate(_text, cols, self._enc)
1088 self._display.append(_text)
1089
1090 return self._display
1091
1092
1093
1095 """
1096 Process defined fs.* rulesets
1097 """
1098
1099 _result = {}
1100
1101 try:
1102 _rules = self.xyz.skin[u"fs.rules"]
1103 except KeyError:
1104 return _result
1105
1106 for i in xrange(self._len):
1107 for _exp, _attr in _rules.iteritems():
1108 if _exp.match(self.entries[i]):
1109 _result[i] = _attr.name
1110 break
1111
1112 return _result
1113
1114
1115
1117 """
1118 Return title attr
1119 """
1120
1121 if self.active:
1122 return self.attr(u"cwdtitle")
1123 else:
1124 return self.attr(u"cwdtitleinact")
1125
1126
1127
1129 """
1130 Set info text
1131 """
1132
1133 _part2 = vfsobj.info
1134 _part1 = truncate(vfsobj.visual, cols - len(_part2) - 2, self._enc)
1135
1136 _text = u"%s%s%s" % (_part1,
1137 u" " * (cols - (self.term_width(_part1) +
1138 self.term_width(_part2)) -
1139 1), _part2)
1140
1141 self._winfo.set_text(bstring(_text, self._enc))
1142
1143
1144
1146 """
1147 Set custom info text
1148 """
1149
1150 _text = truncate(custom_text, cols, self._enc, True)
1151 self._winfo.set_text(bstring(_text, self._enc))
1152
1153
1154
1156 """
1157 Calculate vindex according to selected position
1158 """
1159
1160 pos = self.selected
1161
1162 _from = pos / rows * rows
1163 _to = _from + rows
1164 _vindex = pos - (rows * (pos / rows))
1165
1166 return (_from, _to, _vindex)
1167
1168
1169
1170 @refresh
1178
1179
1180
1181 @refresh
1190
1191
1192
1193 @refresh
1195 """
1196 Next entry
1197 """
1198
1199 if self.selected < self._len - 1:
1200 self.selected += 1
1201
1202
1203
1204 @refresh
1206 """
1207 Previous entry
1208 """
1209
1210 if self.selected > 0:
1211 self.selected -= 1
1212
1213
1214
1215 @refresh
1217 """
1218 Top entry
1219 """
1220
1221 self.selected = 0
1222
1223
1224
1225 @refresh
1227 """
1228 Bottom entry
1229 """
1230
1231 self.selected = self._len - 1
1232
1233
1234
1235 @refresh
1237 """
1238 One block down
1239 """
1240
1241 def _do_next_block(cols, rows):
1242 if self.selected + rows >= self._len:
1243 return self.bottom()
1244 else:
1245 self.selected += rows
1246
1247
1248
1249
1250
1251
1252 self._pending.push(_do_next_block)
1253
1254
1255
1256 @refresh
1258 """
1259 One block up
1260 """
1261
1262 def _do_prev_block(cols, rows):
1263 if self.selected - rows < 0:
1264 return self.top()
1265 else:
1266 self.selected -= rows
1267
1268
1269
1270 self._pending.push(_do_prev_block)
1271
1272
1273
1275 """
1276 Get selected VFSObject instance
1277 """
1278
1279 return self.entries[self.selected]
1280
1281
1282
1284 """
1285 Get current VFSObject instance
1286 """
1287
1288 return self._vfsobj
1289
1290
1291
1293 """
1294 Return list of tagged VFSObject instances
1295 """
1296
1297 return [self.entries[x] for x in self._tagged]
1298
1299
1300
1302 """
1303 Return list of not tagged VFSObject instances
1304 """
1305
1306 return [self.entries[x] for x in xrange(self._len)
1307 if x not in self._tagged]
1308
1309
1310
1312 """
1313 Toggle tagged selected file
1314 """
1315
1316 if self.selected in self._tagged:
1317 self._tagged.remove(self.selected)
1318 else:
1319 self._tagged.append(self.selected)
1320
1321 self.next()
1322
1323
1324
1325 @refresh
1327 """
1328 Tag files by combined rule
1329 """
1330
1331 self._tag_rule(tag=True)
1332
1333
1334
1335 @refresh
1337 """
1338 Untag files by combined rule
1339 """
1340
1341 self._tag_rule(tag=False)
1342
1343
1344
1346 """
1347 Tag engine
1348 """
1349
1350 if tag:
1351 _title = _(u"Tag group")
1352 else:
1353 _title = _(u"Untag group")
1354
1355 _input = libxyz.ui.InputBox(self.xyz, self.xyz.top,
1356 _("Type FS Rule"),
1357 title=_title, text=self._rule_raw)
1358
1359 _raw = _input.show()
1360
1361 if _raw is None:
1362 return
1363 else:
1364 self._rule_raw = _raw
1365
1366 try:
1367 _rule = libxyz.core.FSRule(ustring(_raw, self._enc))
1368 except libxyz.exceptions.ParseError, e:
1369 xyzlog.error(ustring(str(e)))
1370 return
1371
1372 try:
1373 if tag:
1374 self._tagged = [i for i in xrange(self._len) if
1375 _rule.match(self.entries[i])]
1376 else:
1377 self._tagged = [i for i in self._tagged if not
1378 _rule.match(self.entries[i])]
1379 except libxyz.exceptions.FSRuleError, e:
1380 self._tagged = []
1381
1382 xyzlog.error(ustring(str(e)))
1383 return
1384
1385
1386
1387 @refresh
1389 """
1390 Invert currently tagged files
1391 """
1392
1393 self._tagged = [i for i in xrange(self._len)
1394 if i not in self._tagged]
1395
1396
1397
1398 @refresh
1400 """
1401 Tag every single object in current dir
1402 """
1403
1404 self._tagged = [i for i in xrange(self._len) if
1405 self.entries[i].name != ".."]
1406
1407
1408
1409 @refresh
1411 """
1412 Untag every single object in current dir
1413 """
1414
1415 self._tagged = []
1416
1417
1418
1419 @refresh
1421 """
1422 Reload contents
1423 """
1424
1425 _selected = self.entries[self.selected]
1426
1427 self._setup(self._vfsobj)
1428
1429 if self.selected >= self._len:
1430 self.selected = self._len - 1
1431
1432
1433 if self.entries[self.selected].name != _selected.name:
1434 self.select(_selected.name)
1435
1436
1437
1438 @refresh
1440 """
1441 Select VFS object by given name in current directory
1442 """
1443
1444 for i in xrange(self._len):
1445 if self.entries[i].name == name:
1446 self.selected = i
1447 break
1448
1449
1450
1452 """
1453 Perform action on selected file
1454 """
1455
1456 _selected = self.entries[self.selected]
1457
1458 _action = self.xyz.am.match(_selected)
1459
1460 if _action is not None:
1461 try:
1462 _action(_selected)
1463 except Exception, e:
1464 xyzlog.error(_(u"Action error: %s") % (ustring(str(e))))
1465
1466
1467
1468 @refresh
1469 - def chdir(self, path, reload=True, active=True):
1470 """
1471 Change directory
1472 If reload is not True only execute os.chdir, without reloading
1473 directory contents
1474 If active is False do not call os.chdir
1475 """
1476
1477 try:
1478 old_selected = self.entries[self.selected].name
1479 except IndexError:
1480 old_selected = None
1481
1482 if reload:
1483 _path = os.path.normpath(path)
1484 _parent = None
1485 _old_vfs = None
1486
1487 if self.entries:
1488 _parent = os.path.normpath(self.entries[0].full_path)
1489 _old = self._dir.name
1490 _old_vfs = self._vfsobj
1491
1492 try:
1493 _vfsobj = self.xyz.vfs.dispatch(path, self._enc)
1494 except libxyz.exceptions.VFSError, e:
1495 xyzlog.error(_(u"Unable to chdir to %s: %s") %
1496 (ustring(path), ustring(e)))
1497 return
1498
1499 try:
1500 self._setup(_vfsobj)
1501 except libxyz.exceptions.XYZRuntimeError, e:
1502 xyzlog.info(_(u"Unable to chdir to %s: %s") %
1503 (ustring(path), ustring(e)))
1504 return
1505
1506 self.selected = 0
1507
1508
1509 if _parent == _path:
1510 for x in xrange(self._len):
1511 if self.entries[x].name == _old:
1512 self.selected = x
1513 break
1514
1515
1516 if _old_vfs:
1517 del(_old_vfs)
1518
1519 self.cwd = path
1520 self._tab_data[self.tab_bar.active_tab] = (path, old_selected)
1521
1522 if path == os.path.sep:
1523 new_tab_name = path
1524 else:
1525 new_tab_name = os.path.basename(path)
1526
1527 self.tab_bar.rename_tab(self.tab_bar.active_tab,
1528 truncate(new_tab_name, 15, self._enc))
1529
1530
1531 if isinstance(self._vfsobj, LocalVFSObject) and active:
1532 os.chdir(path)
1533
1534
1535
1536 @refresh
1538 """
1539 Search forward for matching object while user types
1540 """
1541
1542 return self._search_engine(lambda x: (xrange(x, self._len)))
1543
1544
1545
1546 @refresh
1548 """
1549 Search backward for matching object while user types
1550 """
1551
1552 return self._search_engine(lambda x: (xrange(x, 0, -1)))
1553
1554
1555
1557 """
1558 Search from current position downwards and then from top to
1559 currently selected
1560 """
1561
1562 return self._search_engine(lambda x: range(x, self._len) +
1563 range(0, x))
1564
1565
1566
1567 @refresh
1569 """
1570 Show only tagged entries
1571 """
1572
1573 if not self._tagged:
1574 return
1575
1576 self.entries = [self.entries[x] for x in self._tagged]
1577 self._len = len(self.entries)
1578 self.selected = 0
1579 self._tagged = []
1580 self._palettes = self._process_skin_rulesets()
1581
1582 _tagged = _(u"TAGGED")
1583
1584 if not self._title.endswith(_tagged):
1585 self._title = truncate(u"%s:%s" % (self._title, _tagged),
1586 self.size.cols - 4, self._enc, True)
1587
1588 if hasattr(self, "border"):
1589 self.border.set_title(self._title)
1590
1591 self._force_reload = True
1592
1593
1594
1596 """
1597 Search for matching filenames while user types
1598 @param order: A function that returns generator for search order
1599 @param pattern: A search type pattern
1600 """
1601
1602 self._cursor_attr = self.attr(u"search")
1603
1604 if pattern is None:
1605
1606 pattern = lambda pat, obj: ustring(pat) in ustring(obj)
1607
1608
1609
1610 _dim = self.xyz.screen.get_cols_rows()
1611 _collected = []
1612
1613 _current_pos = self.selected
1614 _current_pos_orig = self.selected
1615 _skip = False
1616
1617
1618 while True:
1619 self._custom_info = u"".join(_collected)
1620
1621 self._invalidate()
1622 self.xyz.screen.draw_screen(_dim, self.xyz.top.render(_dim, True))
1623
1624 try:
1625 _raw = self.xyz.input.get()
1626
1627 if self.xyz.input.WIN_RESIZE in _raw:
1628 _dim = self.xyz.screen.get_cols_rows()
1629 continue
1630
1631 if self._keys.ESCAPE in _raw or self._keys.ENTER in _raw:
1632 self._invalidate()
1633 break
1634 elif self._keys.BACKSPACE in _raw:
1635 _current_pos = _current_pos_orig
1636 if _collected:
1637 _collected.pop()
1638
1639 elif self._keys.DOWN in _raw:
1640 _skip = True
1641
1642 _tmp = _collected[:]
1643 _tmp.extend([ustring(x, self._enc) for x in _raw
1644 if len(x) == 1])
1645 _pattern = u"".join(_tmp)
1646 except Exception:
1647 break
1648
1649
1650 for i in order(_current_pos):
1651 if pattern(_pattern, self.entries[i].name):
1652 if _skip:
1653 _skip = False
1654 _current_pos = i + 1
1655 continue
1656
1657 self.selected = i
1658 _collected = _tmp
1659 break
1660
1661 self._cursor_attr = None
1662 self._custom_info = None
1663
1664
1665
1667 """
1668 Before switch tab hook
1669 """
1670
1671
1672 if block is not self:
1673 return
1674
1675 try:
1676
1677 path = self._tab_data[index][0]
1678 self._tab_data[index] = (path, self.entries[self.selected].name)
1679
1680 self.chdir(path)
1681 except IndexError:
1682 pass
1683
1684
1685
1687 """
1688 Switch tab hook
1689 """
1690
1691
1692 if block is not self:
1693 return
1694
1695 try:
1696 path, name = self._tab_data[index]
1697
1698 self.chdir(path)
1699
1700 if name is not None:
1701 self.select(name)
1702 except IndexError:
1703 pass
1704
1705
1706
1708 """
1709 New tab hook
1710 """
1711
1712
1713 if block is not self:
1714 return
1715
1716 try:
1717 selected = self.entries[self.selected].name
1718 except IndexError:
1719 selected = None
1720
1721 self._tab_data.append((self.cwd, selected))
1722
1723
1724
1726 """
1727 Delete tab hook
1728 """
1729
1730
1731 if block is not self:
1732 return
1733
1734 try:
1735 del(self._tab_data[index])
1736 except IndexError:
1737 pass
1738
1739
1740
1742 """
1743 Return list of open tabs
1744 """
1745
1746 return self._tab_data
1747
1748
1749
1750 -class TabBar(lowui.FlowWidget):
1751 """
1752 Tabs bar
1753 """
1754
1764
1765
1766
1767 active_tab = property(lambda self: self._active_tab)
1768
1769
1770
1771 @refresh
1773 """
1774 Add new tab
1775 """
1776
1777 if tabname is None:
1778 tabname = "Tab"
1779
1780 self._tabs.append(tabname)
1781 newidx = len(self._tabs) - 1
1782
1783 self.xyz.hm.dispatch(Panel.EVENT_NEW_TAB, self.block, newidx)
1784 self.switch_tab(newidx)
1785
1786
1787
1788 @refresh
1790 """
1791 Delete tab by index
1792 """
1793
1794 if index is None:
1795 index = self._active_tab
1796
1797 _len = len(self._tabs)
1798
1799 if _len > 1 and index < _len:
1800 del(self._tabs[index])
1801
1802 if self._active_tab >= len(self._tabs):
1803 self._active_tab -= 1
1804
1805 self.xyz.hm.dispatch(Panel.EVENT_DEL_TAB, self.block, index)
1806
1807 self.switch_tab(self._active_tab)
1808
1809
1810
1811 @refresh
1823
1824
1825
1826 @refresh
1828 """
1829 Switch to the next tab
1830 """
1831
1832 index = self._active_tab + 1
1833
1834 if index >= len(self._tabs):
1835 index = 0
1836
1837 self.switch_tab(index)
1838
1839
1840
1841 @refresh
1843 """
1844 Switch to the previous tab
1845 """
1846
1847 index = self._active_tab - 1
1848
1849 if index < 0:
1850 index = len(self._tabs) - 1
1851
1852 self.switch_tab(index)
1853
1854
1855
1856 @refresh
1858 """
1859 Rename tab at index
1860 """
1861
1862 if index >= len(self._tabs):
1863 return
1864 else:
1865 self._tabs[index] = new_name
1866
1867
1868
1869 - def render(self, (maxcol,), focus=False):
1870 """
1871 Render the tab bar
1872 """
1873
1874 make_c = lambda text, at: lowui.AttrWrap(
1875 lowui.Text(text), self._attr(at)).render((maxcol,))
1876
1877 canvases = []
1878
1879 length = 0
1880
1881 for idx in xrange(len(self._tabs)):
1882 tabname = self._gen_tab_name(self._tabs[idx], idx)
1883 length += len(tabname)
1884
1885 if idx == self._active_tab:
1886 canv = make_c(tabname, "tabact")
1887 else:
1888 canv = make_c(tabname, "tabbar")
1889
1890 canvases.append((canv, None, False, len(tabname)))
1891
1892
1893 if length < maxcol:
1894 canvases.append((make_c("", "tabbar"), None, False,
1895 maxcol - length))
1896
1897 combined = lowui.CanvasJoin(canvases)
1898
1899 if length > maxcol:
1900 more = lowui.AttrWrap(
1901 lowui.Text(" >>"),
1902 self._attr("tabbar")).render((3,))
1903
1904 combined.pad_trim_left_right(0, -(length - maxcol))
1905 combined.overlay(more, maxcol - 3, 0)
1906
1907 return combined
1908
1909
1910
1911 - def rows(self, (maxcol,), focus=False):
1912 """
1913 Return the number of lines that will be rendered
1914 """
1915
1916 return 1
1917
1918
1919
1921 return "{%d %s} " % (idx, tab)
1922