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