Package libxyz :: Package ui :: Module panel
[hide private]
[frames] | no frames]

Source Code for Module libxyz.ui.panel

   1  #-*- coding: utf8 -* 
   2  # 
   3  # Max E. Kuznecov ~syhpoon <syhpoon@syhpoon.name> 2008 
   4  # 
   5  # This file is part of XYZCommander. 
   6  # XYZCommander is free software: you can redistribute it and/or modify 
   7  # it under the terms of the GNU Lesser Public License as published by 
   8  # the Free Software Foundation, either version 3 of the License, or 
   9  # (at your option) any later version. 
  10  # XYZCommander is distributed in the hope that it will be useful, 
  11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  13  # GNU Lesser Public License for more details. 
  14  # You should have received a copy of the GNU Lesser Public License 
  15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>. 
  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
48 - def __init__(self, xyz):
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
75 - def _update_conf_hook(self, var, val, sect):
76 """ 77 Hook for update conf event 78 """ 79 80 # Not ours 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
89 - def _compose(self):
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
99 - def _build_filters(self):
100 """ 101 Compile filters 102 """ 103 104 filters = [] 105 106 # No need to compile 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
125 - def active(self):
126 if self.block1.active: 127 return self.block1 128 else: 129 return self.block2
130 131 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 132 133 @property
134 - def inactive(self):
135 if self.block1.active: 136 return self.block2 137 else: 138 return self.block1
139 140 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 141
142 - def loop(self):
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
184 - def _set_plugins(self):
185 """ 186 Set virtual plugins 187 """ 188 189 # :sys:run 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 # :sys:panel 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
282 - def shutdown(self):
283 """ 284 Quit program 285 """ 286 287 _q = _(u"Really quit %s?") % libxyz.const.PROG 288 _title = libxyz.const.PROG 289 290 if libxyz.ui.YesNoBox(self.xyz, self.xyz.top, _q, _title).show(): 291 self._stop = True 292 self.xyz.hm.dispatch(self.EVENT_SHUTDOWN)
293 294 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 295
296 - def repaint(self):
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
342 - def switch_active(self):
343 """ 344 Switch active block 345 """ 346 347 if self.block1.active: 348 self.block1.deactivate() 349 self.block2.activate() 350 else: 351 self.block2.deactivate() 352 self.block1.activate()
353 354 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 355
356 - def block_next(self):
357 """ 358 Next block 359 """ 360 361 return self.active.block_next()
362 363 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 364
365 - def block_prev(self):
366 """ 367 Previous block 368 """ 369 370 return self.active.block_prev()
371 372 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 373
374 - def get_selected(self, active=True):
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
388 - def get_tagged(self, active=True):
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
402 - def get_untagged(self, active=True):
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
416 - def get_current(self, active=True):
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
430 - def get_active(self):
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
440 - def toggle_tag(self, active=True):
441 """ 442 Tag selected file 443 """ 444 445 if active: 446 obj = self.active 447 else: 448 obj = self.inactive 449 450 return obj.toggle_tag()
451 452 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 453
454 - def tag_all(self, active=True):
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
468 - def untag_all(self, active=True):
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
482 - def tag_invert(self, active=True):
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
496 - def tag_rule(self, active=True):
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
510 - def untag_rule(self, active=True):
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
524 - def swap_blocks(self):
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
553 - def reload_all(self):
554 """ 555 Reload both panels 556 """ 557 558 self.active.reload() 559 self.inactive.reload()
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):
578 """ 579 Change directory 580 """ 581 582 if active: 583 obj = self.active 584 else: 585 obj = self.inactive 586 587 return obj.chdir(path, active=active)
588 589 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 590
591 - def search_forward(self):
592 """ 593 Enable forward search-when-you-type mode 594 """ 595 596 self.active.search_forward()
597 598 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 599
600 - def search_backward(self):
601 """ 602 Enable backward search-when-you-type mode 603 """ 604 605 self.active.search_backward()
606 607 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 608
609 - def search_cycle(self):
610 """ 611 Enable cyclic search-when-you-type mode 612 """ 613 614 self.active.search_cycle()
615 616 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 617
618 - def show_tagged(self, active=True):
619 """ 620 Show only tagged entries 621 """ 622 623 if active: 624 obj = self.active 625 else: 626 obj = self.inactive 627 628 return obj.show_tagged()
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
660 - def vfs_driver(self, active=True):
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
674 - def filter(self, objects):
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
754 - def switch_tab(self, index, active=True):
755 """ 756 Switch to tab 757 """ 758 759 if active: 760 obj = self.active 761 else: 762 obj = self.inactive 763 764 return obj.tab_bar.switch_tab(index)
765 766 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 767
768 - def next_tab(self, active=True):
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
782 - def prev_tab(self, active=True):
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
796 - def get_tabs(self, active=True):
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
811 - def active_tab(self, active=True):
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 # Length of the string in terms of terminal columns 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):
908 # TODO: cache 909 w = self.display_widget((maxcol,), focus) 910 return w.rows((maxcol,), focus)
911 912 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 913
914 - def display_widget(self, (maxcol,), focus):
915 return lowui.BoxAdapter(self.block, self.size.rows)
916 917 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 918
919 - def _setup(self, vfsobj):
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
949 - def selectable(self):
950 return True
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 # Reduce original sizes in order to fit into overlay 963 maxcol_orig, maxcol = maxcol, maxcol - 2 964 maxrow_orig, maxrow = maxrow, maxrow - 5 965 966 # Search for pending action 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
1041 - def _make_info(self):
1042 _info = lowui.Padding(self._winfo, align.LEFT, self.size.cols) 1043 _info = lowui.AttrWrap(_info, self.attr(u"info")) 1044 _info = lowui.Pile([self._sep, _info]) 1045 return _info
1046 1047 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1048
1049 - def _make_number_readable(self, num):
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
1073 - def _get_visible(self, rows, cols, reload=False):
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
1094 - def _process_skin_rulesets(self):
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
1116 - def _get_title_attr(self):
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
1128 - def _set_info(self, vfsobj, cols):
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
1145 - def _set_custom_info(self, custom_text, cols):
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
1155 - def _update_vindex(self, rows):
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
1171 - def deactivate(self):
1172 """ 1173 Deactivate block 1174 """ 1175 1176 self.active = False 1177 self.border.set_title_attr(self._get_title_attr())
1178 1179 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1180 1181 @refresh
1182 - def activate(self):
1183 """ 1184 Activate block 1185 """ 1186 1187 self.active = True 1188 self.border.set_title_attr(self._get_title_attr()) 1189 self.chdir(self._dir.path, reload=False)
1190 1191 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1192 1193 @refresh
1194 - def next(self):
1195 """ 1196 Next entry 1197 """ 1198 1199 if self.selected < self._len - 1: 1200 self.selected += 1
1201 1202 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1203 1204 @refresh
1205 - def prev(self):
1206 """ 1207 Previous entry 1208 """ 1209 1210 if self.selected > 0: 1211 self.selected -= 1
1212 1213 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1214 1215 @refresh
1216 - def top(self):
1217 """ 1218 Top entry 1219 """ 1220 1221 self.selected = 0
1222 1223 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1224 1225 @refresh
1226 - def bottom(self):
1227 """ 1228 Bottom entry 1229 """ 1230 1231 self.selected = self._len - 1
1232 1233 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1234 1235 @refresh
1236 - def block_next(self):
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 # As we aren't aware of how many rows are in a single 1250 # block at this moment, postpone jumping until render is called 1251 1252 self._pending.push(_do_next_block)
1253 1254 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1255 1256 @refresh
1257 - def block_prev(self):
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
1274 - def get_selected(self):
1275 """ 1276 Get selected VFSObject instance 1277 """ 1278 1279 return self.entries[self.selected]
1280 1281 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1282
1283 - def get_current(self):
1284 """ 1285 Get current VFSObject instance 1286 """ 1287 1288 return self._vfsobj
1289 1290 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1291
1292 - def get_tagged(self):
1293 """ 1294 Return list of tagged VFSObject instances 1295 """ 1296 1297 return [self.entries[x] for x in self._tagged]
1298 1299 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1300
1301 - def get_untagged(self):
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
1311 - def toggle_tag(self):
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
1326 - def tag_rule(self):
1327 """ 1328 Tag files by combined rule 1329 """ 1330 1331 self._tag_rule(tag=True)
1332 1333 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1334 1335 @refresh
1336 - def untag_rule(self):
1337 """ 1338 Untag files by combined rule 1339 """ 1340 1341 self._tag_rule(tag=False)
1342 1343 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1344
1345 - def _tag_rule(self, tag=True):
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
1388 - def tag_invert(self):
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
1399 - def tag_all(self):
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
1410 - def untag_all(self):
1411 """ 1412 Untag every single object in current dir 1413 """ 1414 1415 self._tagged = []
1416 1417 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1418 1419 @refresh
1420 - def reload(self):
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 # Try to find previously selected object 1433 if self.entries[self.selected].name != _selected.name: 1434 self.select(_selected.name)
1435 1436 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1437 1438 @refresh
1439 - def select(self, name):
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
1451 - def action(self):
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 # We've just stepped out from dir, try to focus on it 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 # TODO: 2-3 level cache 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 # Call chdir only for local objects 1531 if isinstance(self._vfsobj, LocalVFSObject) and active: 1532 os.chdir(path)
1533 1534 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1535 1536 @refresh
1537 - def search_forward(self):
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
1547 - def search_backward(self):
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
1556 - def search_cycle(self):
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
1568 - def show_tagged(self):
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
1595 - def _search_engine(self, order, pattern=None):
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 # Search everywhere in object name 1606 pattern = lambda pat, obj: ustring(pat) in ustring(obj) 1607 # Search from the beginning of object name 1608 #pattern = lambda pat, obj: obj.startswith(pat) 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 # Starting internal read loop 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 # Continue search 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 # Search 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
1666 - def _before_switch_tab_hook(self, block, index):
1667 """ 1668 Before switch tab hook 1669 """ 1670 1671 # It's for other block 1672 if block is not self: 1673 return 1674 1675 try: 1676 # Save position 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
1686 - def _switch_tab_hook(self, block, index):
1687 """ 1688 Switch tab hook 1689 """ 1690 1691 # It's for other block 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
1707 - def _new_tab_hook(self, block, _index):
1708 """ 1709 New tab hook 1710 """ 1711 1712 # It's for other block 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
1725 - def _del_tab_hook(self, block, index):
1726 """ 1727 Delete tab hook 1728 """ 1729 1730 # It's for other block 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
1741 - def get_tabs(self):
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
1755 - def __init__(self, xyz, attr, block):
1756 self.xyz = xyz 1757 self.block = block 1758 self._attr = attr 1759 1760 self._active_tab = 0 1761 self._tabs = [] 1762 1763 super(TabBar, self).__init__()
1764 1765 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1766 1767 active_tab = property(lambda self: self._active_tab) 1768 1769 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1770 1771 @refresh
1772 - def new_tab(self, tabname=None):
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
1789 - def del_tab(self, index=None):
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
1812 - def switch_tab(self, index):
1813 """ 1814 Switch to tab by index 1815 """ 1816 1817 if index < len(self._tabs): 1818 self.xyz.hm.dispatch(Panel.EVENT_BEFORE_SWITCH_TAB, self.block, 1819 self._active_tab) 1820 self._active_tab = index 1821 self.xyz.hm.dispatch(Panel.EVENT_SWITCH_TAB, self.block, 1822 self._active_tab)
1823 1824 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1825 1826 @refresh
1827 - def next_tab(self):
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
1842 - def prev_tab(self):
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
1857 - def rename_tab(self, index, new_name):
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
1920 - def _gen_tab_name(self, tab, idx):
1921 return "{%d %s} " % (idx, tab)
1922