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
44 - def __init__(self, xyz):
45 self.xyz = xyz 46 self.conf = self.xyz.conf[u"plugins"][u":sys:panel"] 47 48 self._keys = libxyz.ui.Keys() 49 50 _size = self.xyz.screen.get_cols_rows() 51 _blocksize = libxyz.ui.Size(rows=_size[1] - 1, cols=_size[0] / 2 - 2) 52 self._enc = xyzenc 53 self._stop = False 54 self._resize = False 55 56 self._set_plugins() 57 self._cmd = libxyz.ui.Cmd(xyz) 58 _cwd = os.getcwd() 59 60 self.filters = self._build_filters() 61 self.xyz.hm.register("event:conf_update", self._update_conf_hook) 62 63 self.block1 = Block(xyz, _blocksize, _cwd, self._enc, active=True) 64 self.block2 = Block(xyz, _blocksize, _cwd, self._enc) 65 self._compose() 66 67 super(Panel, self).__init__(self._widget)
68 69 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70
71 - def _update_conf_hook(self, var, val, sect):
72 """ 73 Hook for update conf event 74 """ 75 76 # Not ours 77 if sect != "plugins" or var != ":sys:panel": 78 return 79 80 if "filters" in val or "filters_enabled" in val: 81 self.filters = self._build_filters()
82 83 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 84
85 - def _compose(self):
86 """ 87 Compose widgets 88 """ 89 90 columns = lowui.Columns([self.block1, self.block2], 0) 91 self._widget = lowui.Pile([columns, self._cmd])
92 93 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 94
95 - def _build_filters(self):
96 """ 97 Compile filters 98 """ 99 100 filters = [] 101 102 # No need to compile 103 if not self.conf[u"filters_enabled"]: 104 return filters 105 106 for f in self.conf[u"filters"]: 107 try: 108 rule = libxyz.core.FSRule(ustring(f)) 109 except libxyz.exceptions.ParseError, e: 110 xyzlog.error(_(u"Error compiling filter: %s" % 111 ustring(str(e)))) 112 continue 113 else: 114 filters.append(rule) 115 116 return filters
117 118 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 120 @property
121 - def active(self):
122 if self.block1.active: 123 return self.block1 124 else: 125 return self.block2
126 127 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 128 129 @property
130 - def inactive(self):
131 if self.block1.active: 132 return self.block2 133 else: 134 return self.block1
135 136 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137
138 - def loop(self):
139 """ 140 Start working loop 141 """ 142 143 _dim = self.xyz.screen.get_cols_rows() 144 145 while True: 146 if self._stop: 147 break 148 149 canv = self.xyz.top.render(_dim, True) 150 self.xyz.screen.draw_screen(_dim, canv) 151 152 _input = self.xyz.input.get() 153 154 if _input: 155 try: 156 self._cmd.keypress(_dim, _input) 157 except Exception, e: 158 xyzlog.error(_(u"Error executing bind (%s): %s") % 159 (Shortcut(_input), ustring(str(e)))) 160 xyzlog.debug(ustring(traceback.format_exc(), 161 self._enc)) 162 163 if self.xyz.input.resized: 164 self._resize = True 165 166 if self._resize: 167 self._resize = False 168 _dim = self.xyz.screen.get_cols_rows() 169 _bsize = libxyz.ui.Size(rows=_dim[1] - 1, 170 cols=_dim[0] / 2 - 2) 171 172 self.block1.size = _bsize 173 self.block2.size = _bsize 174 self._cmd._invalidate() 175 self.block1._invalidate() 176 self.block2._invalidate()
177 178 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 179
180 - def _set_plugins(self):
181 """ 182 Set virtual plugins 183 """ 184 185 # :sys:run 186 _run_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"run") 187 _run_plugin.export(self.shutdown) 188 _run_plugin.export(self.repaint) 189 190 _run_plugin.VERSION = u"0.1" 191 _run_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>" 192 _run_plugin.BRIEF_DESCRIPTION = u"Run plugin" 193 _run_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name" 194 195 # :sys:panel 196 _panel_plugin = libxyz.core.plugins.VirtualPlugin(self.xyz, u"panel") 197 _panel_plugin.export(self.entry_next) 198 _panel_plugin.export(self.entry_prev) 199 _panel_plugin.export(self.entry_top) 200 _panel_plugin.export(self.entry_bottom) 201 _panel_plugin.export(self.block_next) 202 _panel_plugin.export(self.block_prev) 203 _panel_plugin.export(self.switch_active) 204 _panel_plugin.export(self.get_selected) 205 _panel_plugin.export(self.get_tagged) 206 _panel_plugin.export(self.get_untagged) 207 _panel_plugin.export(self.get_current) 208 _panel_plugin.export(self.get_active) 209 _panel_plugin.export(self.toggle_tag) 210 _panel_plugin.export(self.tag_all) 211 _panel_plugin.export(self.untag_all) 212 _panel_plugin.export(self.tag_invert) 213 _panel_plugin.export(self.tag_rule) 214 _panel_plugin.export(self.untag_rule) 215 _panel_plugin.export(self.swap_blocks) 216 _panel_plugin.export(self.reload) 217 _panel_plugin.export(self.reload_all) 218 _panel_plugin.export(self.action) 219 _panel_plugin.export(self.chdir) 220 _panel_plugin.export(self.search_forward) 221 _panel_plugin.export(self.search_backward) 222 _panel_plugin.export(self.search_cycle) 223 _panel_plugin.export(self.show_tagged) 224 _panel_plugin.export(self.select) 225 _panel_plugin.export(self.cwd) 226 _panel_plugin.export(self.vfs_driver) 227 _panel_plugin.export(self.filter) 228 _panel_plugin.export(self.sort) 229 230 _panel_plugin.VERSION = u"0.1" 231 _panel_plugin.AUTHOR = u"Max E. Kuznecov <syhpoon@syhpoon.name>" 232 _panel_plugin.BRIEF_DESCRIPTION = u"Panel plugin" 233 _panel_plugin.HOMEPAGE = u"xyzcmd.syhpoon.name" 234 _panel_plugin.DOC = u"""\ 235 Configuration variables: 236 filters_enabled - Enable permanent filters. Default - False 237 filters_policy - Filters policy. 238 If True - filter out objects which matching the rule. 239 If False - filter out objects which do not match the rule. Default - True 240 filters - List of permanent filters. 241 Filters applied in defined order sequentially. Default - [] 242 sorting_policy - Active sorting policy name or None. Default - None 243 sorting - Defined sorting policies. Each key corresponds to a policy name 244 and value is either a function with two arguments (VFSObject) behaving 245 like cmp() or a list of those functions. If value is a list, each function 246 applied sequentially. Default - []""" 247 248 self.xyz.pm.register(_run_plugin) 249 self.xyz.pm.register(_panel_plugin)
250 251 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 252
253 - def shutdown(self):
254 """ 255 Quit program 256 """ 257 258 _q = _(u"Really quit %s?") % libxyz.const.PROG 259 _title = libxyz.const.PROG 260 261 if libxyz.ui.YesNoBox(self.xyz, self.xyz.top, _q, _title).show(): 262 self._stop = True 263 self.xyz.hm.dispatch(self.EVENT_SHUTDOWN)
264 265 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 266
267 - def repaint(self):
268 """ 269 Reparint screen 270 """ 271 272 self._resize = True 273 self.xyz.screen.clear()
274 275 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 276
277 - def entry_next(self):
278 """ 279 Next entry 280 """ 281 282 return self.active.next()
283 284 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 285
286 - def entry_prev(self):
287 """ 288 Previous entry 289 """ 290 291 return self.active.prev()
292 293 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 294
295 - def entry_top(self):
296 """ 297 Top entry 298 """ 299 300 return self.active.top()
301 302 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 303
304 - def entry_bottom(self):
305 """ 306 Bottom entry 307 """ 308 309 return self.active.bottom()
310 311 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 312
313 - def switch_active(self):
314 """ 315 Switch active block 316 """ 317 318 if self.block1.active: 319 self.block1.deactivate() 320 self.block2.activate() 321 else: 322 self.block2.deactivate() 323 self.block1.activate()
324 325 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 326
327 - def block_next(self):
328 """ 329 Next block 330 """ 331 332 return self.active.block_next()
333 334 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 335
336 - def block_prev(self):
337 """ 338 Previous block 339 """ 340 341 return self.active.block_prev()
342 343 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 344
345 - def get_selected(self, active=True):
346 """ 347 Get selected VFSObject instance 348 """ 349 350 if active: 351 obj = self.active 352 else: 353 obj = self.inactive 354 355 return obj.get_selected()
356 357 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 358
359 - def get_tagged(self, active=True):
360 """ 361 Return list of tagged VFSObject instances 362 """ 363 364 if active: 365 obj = self.active 366 else: 367 obj = self.inactive 368 369 return obj.get_tagged()
370 371 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 372
373 - def get_untagged(self, active=True):
374 """ 375 Return list of not tagged VFSObject instances 376 """ 377 378 if active: 379 obj = self.active 380 else: 381 obj = self.inactive 382 383 return obj.get_untagged()
384 385 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 386
387 - def get_current(self, active=True):
388 """ 389 Return VFSObject instance of current VFSObject 390 """ 391 392 if active: 393 obj = self.active 394 else: 395 obj = self.inactive 396 397 return obj.get_current()
398 399 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 400
401 - def get_active(self):
402 """ 403 Return list of tagged VFSObject instances or list of single selected 404 object if none tagged 405 """ 406 407 return self.get_tagged() or [self.get_selected()]
408 409 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 410
411 - def toggle_tag(self, active=True):
412 """ 413 Tag selected file 414 """ 415 416 if active: 417 obj = self.active 418 else: 419 obj = self.inactive 420 421 return obj.toggle_tag()
422 423 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 424
425 - def tag_all(self, active=True):
426 """ 427 Tag every single object in current dir 428 """ 429 430 if active: 431 obj = self.active 432 else: 433 obj = self.inactive 434 435 return obj.tag_all()
436 437 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 438
439 - def untag_all(self, active=True):
440 """ 441 Untag every single object in current dir 442 """ 443 444 if active: 445 obj = self.active 446 else: 447 obj = self.inactive 448 449 return obj.untag_all()
450 451 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 452
453 - def tag_invert(self, active=True):
454 """ 455 Invert currently tagged files 456 """ 457 458 if active: 459 obj = self.active 460 else: 461 obj = self.inactive 462 463 return obj.tag_invert()
464 465 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 466
467 - def tag_rule(self, active=True):
468 """ 469 Tag files by combined rule 470 """ 471 472 if active: 473 obj = self.active 474 else: 475 obj = self.inactive 476 477 return obj.tag_rule()
478 479 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 480
481 - def untag_rule(self, active=True):
482 """ 483 Untag files by combined rules 484 """ 485 486 if active: 487 obj = self.active 488 else: 489 obj = self.inactive 490 491 return obj.untag_rule()
492 493 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 494
495 - def swap_blocks(self):
496 """ 497 Swap panel blocks 498 """ 499 500 self.block1, self.block2 = self.block2, self.block1 501 self._compose() 502 503 if hasattr(self, "set_w"): 504 self.set_w(self._widget) 505 else: 506 self._w = self._widget
507 508 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 509
510 - def reload(self, active=True):
511 """ 512 Reload contents 513 """ 514 515 if active: 516 obj = self.active 517 else: 518 obj = self.inactive 519 520 return obj.reload()
521 522 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 523
524 - def reload_all(self):
525 """ 526 Reload both panels 527 """ 528 529 self.active.reload() 530 self.inactive.reload()
531 532 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 533
534 - def action(self, active=True):
535 """ 536 Perfrom action on selected object 537 """ 538 539 if active: 540 obj = self.active 541 else: 542 obj = self.inactive 543 544 return obj.action()
545 546 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 547
548 - def chdir(self, path, active=True):
549 """ 550 Change directory 551 """ 552 553 if active: 554 obj = self.active 555 else: 556 obj = self.inactive 557 558 return obj.chdir(path, active=active)
559 560 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 561
562 - def search_forward(self):
563 """ 564 Enable forward search-when-you-type mode 565 """ 566 567 self.active.search_forward()
568 569 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 570
571 - def search_backward(self):
572 """ 573 Enable backward search-when-you-type mode 574 """ 575 576 self.active.search_backward()
577 578 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 579
580 - def search_cycle(self):
581 """ 582 Enable cyclic search-when-you-type mode 583 """ 584 585 self.active.search_cycle()
586 587 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 588
589 - def show_tagged(self, active=True):
590 """ 591 Show only tagged entries 592 """ 593 594 if active: 595 obj = self.active 596 else: 597 obj = self.inactive 598 599 return obj.show_tagged()
600 601 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 602
603 - def select(self, name, active=True):
604 """ 605 Select VFS object by given name in current directory 606 """ 607 608 if active: 609 obj = self.active 610 else: 611 obj = self.inactive 612 613 return obj.select(name)
614 615 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 616
617 - def cwd(self, active=True):
618 """ 619 Get current working directory 620 """ 621 622 if active: 623 obj = self.active 624 else: 625 obj = self.inactive 626 627 return obj.cwd
628 629 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 630
631 - def vfs_driver(self, active=True):
632 """ 633 Return vfs driver used by object. None stands for LocalVFS 634 """ 635 636 if active: 637 obj = self.active 638 else: 639 obj = self.inactive 640 641 return obj.vfs_driver
642 643 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 644
645 - def filter(self, objects):
646 """ 647 Filter objects 648 """ 649 650 if not self.conf["filters_enabled"]: 651 return objects 652 653 policy = self.conf["filters_policy"] 654 655 def policyf(res): 656 if policy == True: 657 result = not res 658 else: 659 result = res 660 661 return result
662 663 for f in self.filters: 664 objects = [x for x in objects if policyf(f.match(x))] 665 666 return objects
667 668 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 669
670 - def sort(self, objects):
671 """ 672 Sort objects 673 """ 674 675 policy = self.conf["sorting_policy"] 676 677 if policy is None: 678 return objects 679 680 if policy not in self.conf["sorting"]: 681 xyzlog.warning(_(u"Unable to find `%s` sorting policy" % 682 ustring(policy))) 683 return objects 684 685 policy_data = self.conf["sorting"][policy] 686 687 if is_func(policy_data): 688 objects.sort(cmp=policy_data) 689 elif isinstance(policy_data, list): 690 for f in policy_data: 691 objects.sort(cmp=f) 692 693 return objects
694
695 #++++++++++++++++++++++++++++++++++++++++++++++++ 696 697 -class Block(lowui.FlowWidget):
698 """ 699 Single panel block 700 """ 701
702 - def __init__(self, xyz, size, path, enc, active=False):
703 """ 704 @param xyz: XYZData instance 705 @param size: Block widget size 706 @type size: L{libxyz.ui.Size} 707 @param enc: Local encoding 708 @param active: Boolean flag, True if block is active 709 710 Required resources: cwdtitle, cwdtitleinact, panel, cursor, info 711 border, tagged 712 """ 713 714 self.xyz = xyz 715 self.size = size 716 self.attr = lambda x: self.xyz.skin.attr(Panel.resolution, x) 717 # Length of the string in terms of terminal columns 718 self.term_width = lambda x: lowui.util.calc_width(x, 0, len(x)) 719 720 self.active = active 721 self.selected = 0 722 self.cwd = path 723 724 self._display = [] 725 self._vindex = 0 726 self._from = 0 727 self._to = 0 728 self._force_reload = False 729 self.entries = [] 730 self._dir = None 731 self._len = 0 732 self._palettes = [] 733 self._vfsobj = None 734 self._title = u"" 735 self._tagged = [] 736 737 self._cursor_attr = None 738 self._custom_info = None 739 self._keys = libxyz.ui.Keys() 740 self._cmd = self.xyz.pm.load(":sys:cmd") 741 self._filter = self.xyz.pm.from_load(":sys:panel", "filter") 742 self._sort = self.xyz.pm.from_load(":sys:panel", "sort") 743 744 self._pending = libxyz.core.Queue(20) 745 self._re_raw = r".*" 746 self._rule_raw = "" 747 self._enc = enc 748 self.vfs_driver = None 749 750 self.chdir(path) 751 752 self._winfo = lowui.Text(u"") 753 self._sep = libxyz.ui.Separator() 754 755 _info = self._make_info() 756 _title_attr = self._get_title_attr() 757 758 self.frame = lowui.Frame(lowui.Filler(lowui.Text("")), footer=_info) 759 self.border = libxyz.ui.Border(self.frame, self._title, 760 _title_attr, self.attr(u"border")) 761 self.block = lowui.AttrWrap(self.border, self.attr(u"panel")) 762 763 super(Block, self).__init__()
764 765 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 766
767 - def rows(self, (maxcol,), focus=False):
768 # TODO: cache 769 w = self.display_widget((maxcol,), focus) 770 return w.rows((maxcol,), focus)
771 772 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 773
774 - def display_widget(self, (maxcol,), focus):
775 return lowui.BoxAdapter(self.block, self.size.rows)
776 777 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 778
779 - def _setup(self, vfsobj):
780 _parent, _dir, _dirs, _files = vfsobj.walk() 781 782 self._dir = _dir 783 784 _entries = [] 785 _entries.extend(_dirs) 786 _entries.extend(_files) 787 _entries = self._filter(_entries) 788 _entries = self._sort(_entries) 789 _entries.insert(0, _parent) 790 791 self._title = truncate(_dir.full_path, self.size.cols - 4, 792 self._enc, True) 793 794 if hasattr(self, "border"): 795 self.border.set_title(self._title) 796 797 self._tagged = [] 798 799 self.entries = _entries 800 self._len = len(self.entries) 801 self._palettes = self._process_skin_rulesets() 802 self._vfsobj = vfsobj 803 self.vfs_driver = vfsobj.driver 804 805 self._force_reload = True
806 807 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 808
809 - def selectable(self):
810 return True
811 812 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 813
814 - def render(self, (maxcol,), focus=False):
815 """ 816 Render block 817 """ 818 819 w = self.display_widget((maxcol,), focus) 820 maxrow = w.rows((maxcol,), focus) 821 822 # Reduce original sizes in order to fit into overlay 823 maxcol_orig, maxcol = maxcol, maxcol - 2 824 maxrow_orig, maxrow = maxrow, maxrow - 4 825 826 # Search for pending action 827 while True: 828 try: 829 _act = self._pending.pop() 830 except IndexError: 831 break 832 else: 833 _act(maxcol, maxrow) 834 835 if self._custom_info is not None: 836 self._set_custom_info(self._custom_info, maxcol) 837 else: 838 self._set_info(self.entries[self.selected], maxcol) 839 840 _tlen = len(self._tagged) 841 842 if _tlen > 0: 843 _text = _(u"%s bytes (%d)") % ( 844 self._make_number_readable( 845 reduce(lambda x, y: x + y, 846 [self.entries[x].size for x in self._tagged 847 if isinstance(self.entries[x].ftype, VFSTypeFile) 848 ], 0)), _tlen) 849 850 self._sep.set_text(bstring(_text, self._enc), 851 self.attr(u"tagged")) 852 else: 853 self._sep.clear_text() 854 855 self._display = self._get_visible(maxrow, maxcol, self._force_reload) 856 self._force_reload = False 857 858 _len = len(self._display) 859 860 canvases = [] 861 862 for i in xrange(0, _len): 863 _text = self._display[i] 864 _own_attr = None 865 _abs_i = self._from + i 866 867 if self._cursor_attr is not None and i == self._vindex: 868 _own_attr = self._cursor_attr 869 elif self.active and i == self._vindex: 870 _own_attr = self.attr(u"cursor") 871 elif _abs_i in self._tagged: 872 _own_attr = self.attr(u"tagged") 873 elif _abs_i in self._palettes: 874 _own_attr = self._palettes[_abs_i] 875 876 if _own_attr is not None: 877 x = lowui.AttrWrap(lowui.Text(bstring(_text, self._enc)), 878 _own_attr).render((maxcol,)) 879 canvases.append((x, i, False)) 880 else: 881 canvases.append((lowui.Text(_text).render((maxcol,)), 882 i, False)) 883 884 if _len < maxrow: 885 _pad = lowui.AttrWrap(lowui.Text(" "), self.attr(u"panel")) 886 canvases.append((_pad.render((maxcol,), focus), 0, False)) 887 888 _info = self._make_info() 889 self.frame.set_footer(_info) 890 891 combined = lowui.CanvasCombine(canvases) 892 border = self.block.render((maxcol_orig, maxrow_orig), focus) 893 894 if _len > maxrow: 895 combined.trim_end(_len - maxrow) 896 897 return lowui.CanvasOverlay(combined, border, 1, 1)
898 899 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 900
901 - def _make_info(self):
902 _info = lowui.Padding(self._winfo, align.LEFT, self.size.cols) 903 _info = lowui.AttrWrap(_info, self.attr(u"info")) 904 _info = lowui.Pile([self._sep, _info]) 905 return _info
906 907 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 908
909 - def _make_number_readable(self, num):
910 _res = [] 911 912 i = 0 913 _sep = False 914 915 for x in reversed(unicode(num)): 916 if _sep: 917 _res.append(u"_") 918 _sep = False 919 920 _res.append(x) 921 922 if i > 0 and (i + 1) % 3 == 0: 923 _sep = True 924 925 i += 1 926 927 _res.reverse() 928 929 return u"".join(_res)
930 931 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 932
933 - def _get_visible(self, rows, cols, reload=False):
934 """ 935 Get currently visible piece of entries 936 """ 937 938 _len = self._len 939 _from, _to, self._vindex = self._update_vindex(rows) 940 941 if reload or ((_from, _to) != (self._from, self._to)): 942 self._from, self._to = _from, _to 943 self._display = [] 944 945 for _obj in self.entries[self._from:self._to]: 946 _text = "%s%s "% (_obj.vtype, _obj.name) 947 _text = truncate(_text, cols, self._enc) 948 self._display.append(_text) 949 950 return self._display
951 952 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 953
954 - def _process_skin_rulesets(self):
955 """ 956 Process defined fs.* rulesets 957 """ 958 959 _result = {} 960 961 try: 962 _rules = self.xyz.skin[u"fs.rules"] 963 except KeyError: 964 return _result 965 966 for i in xrange(self._len): 967 for _exp, _attr in _rules.iteritems(): 968 if _exp.match(self.entries[i]): 969 _result[i] = _attr.name 970 break 971 972 return _result
973 974 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 975
976 - def _get_title_attr(self):
977 """ 978 Return title attr 979 """ 980 981 if self.active: 982 return self.attr(u"cwdtitle") 983 else: 984 return self.attr(u"cwdtitleinact")
985 986 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 987
988 - def _set_info(self, vfsobj, cols):
989 """ 990 Set info text 991 """ 992 993 _part2 = vfsobj.info 994 _part1 = truncate(vfsobj.visual, cols - len(_part2) - 2, self._enc) 995 996 _text = u"%s%s%s" % (_part1, 997 u" " * (cols - (self.term_width(_part1) + 998 self.term_width(_part2)) - 999 1), _part2) 1000 1001 self._winfo.set_text(bstring(_text, self._enc))
1002 1003 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1004
1005 - def _set_custom_info(self, custom_text, cols):
1006 """ 1007 Set custom info text 1008 """ 1009 1010 _text = truncate(custom_text, cols, self._enc, True) 1011 self._winfo.set_text(bstring(_text, self._enc))
1012 1013 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1014
1015 - def _update_vindex(self, rows):
1016 """ 1017 Calculate vindex according to selected position 1018 """ 1019 1020 pos = self.selected 1021 1022 _from = pos / rows * rows 1023 _to = _from + rows 1024 _vindex = pos - (rows * (pos / rows)) 1025 1026 return (_from, _to, _vindex)
1027 1028 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1029 1030 @refresh
1031 - def deactivate(self):
1032 """ 1033 Deactivate block 1034 """ 1035 1036 self.active = False 1037 self.border.set_title_attr(self._get_title_attr())
1038 1039 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1040 1041 @refresh
1042 - def activate(self):
1043 """ 1044 Activate block 1045 """ 1046 1047 self.active = True 1048 self.border.set_title_attr(self._get_title_attr()) 1049 self.chdir(self._dir.path, reload=False)
1050 1051 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1052 1053 @refresh
1054 - def next(self):
1055 """ 1056 Next entry 1057 """ 1058 1059 if self.selected < self._len - 1: 1060 self.selected += 1
1061 1062 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1063 1064 @refresh
1065 - def prev(self):
1066 """ 1067 Previous entry 1068 """ 1069 1070 if self.selected > 0: 1071 self.selected -= 1
1072 1073 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1074 1075 @refresh
1076 - def top(self):
1077 """ 1078 Top entry 1079 """ 1080 1081 self.selected = 0
1082 1083 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1084 1085 @refresh
1086 - def bottom(self):
1087 """ 1088 Bottom entry 1089 """ 1090 1091 self.selected = self._len - 1
1092 1093 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1094 1095 @refresh
1096 - def block_next(self):
1097 """ 1098 One block down 1099 """ 1100 1101 def _do_next_block(cols, rows): 1102 if self.selected + rows >= self._len: 1103 return self.bottom() 1104 else: 1105 self.selected += rows
1106 1107 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1108 1109 # As we aren't aware of how many rows are in a single 1110 # block at this moment, postpone jumping until render is called 1111 1112 self._pending.push(_do_next_block)
1113 1114 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1115 1116 @refresh
1117 - def block_prev(self):
1118 """ 1119 One block up 1120 """ 1121 1122 def _do_prev_block(cols, rows): 1123 if self.selected - rows < 0: 1124 return self.top() 1125 else: 1126 self.selected -= rows
1127 1128 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1129 1130 self._pending.push(_do_prev_block) 1131 1132 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1133
1134 - def get_selected(self):
1135 """ 1136 Get selected VFSObject instance 1137 """ 1138 1139 return self.entries[self.selected]
1140 1141 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1142
1143 - def get_current(self):
1144 """ 1145 Get current VFSObject instance 1146 """ 1147 1148 return self._vfsobj
1149 1150 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1151
1152 - def get_tagged(self):
1153 """ 1154 Return list of tagged VFSObject instances 1155 """ 1156 1157 return [self.entries[x] for x in self._tagged]
1158 1159 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1160
1161 - def get_untagged(self):
1162 """ 1163 Return list of not tagged VFSObject instances 1164 """ 1165 1166 return [self.entries[x] for x in xrange(self._len) 1167 if x not in self._tagged]
1168 1169 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1170
1171 - def toggle_tag(self):
1172 """ 1173 Toggle tagged selected file 1174 """ 1175 1176 if self.selected in self._tagged: 1177 self._tagged.remove(self.selected) 1178 else: 1179 self._tagged.append(self.selected) 1180 1181 self.next()
1182 1183 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1184 1185 @refresh
1186 - def tag_rule(self):
1187 """ 1188 Tag files by combined rule 1189 """ 1190 1191 self._tag_rule(tag=True)
1192 1193 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1194 1195 @refresh
1196 - def untag_rule(self):
1197 """ 1198 Untag files by combined rule 1199 """ 1200 1201 self._tag_rule(tag=False)
1202 1203 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1204
1205 - def _tag_rule(self, tag=True):
1206 """ 1207 Tag engine 1208 """ 1209 1210 if tag: 1211 _title = _(u"Tag group") 1212 else: 1213 _title = _(u"Untag group") 1214 1215 _input = libxyz.ui.InputBox(self.xyz, self.xyz.top, 1216 _("Type FS Rule"), 1217 title=_title, text=self._rule_raw) 1218 1219 _raw = _input.show() 1220 1221 if _raw is None: 1222 return 1223 else: 1224 self._rule_raw = _raw 1225 1226 try: 1227 _rule = libxyz.core.FSRule(ustring(_raw, self._enc)) 1228 except libxyz.exceptions.ParseError, e: 1229 xyzlog.error(ustring(str(e))) 1230 return 1231 1232 try: 1233 if tag: 1234 self._tagged = [i for i in xrange(self._len) if 1235 _rule.match(self.entries[i])] 1236 else: 1237 self._tagged = [i for i in self._tagged if not 1238 _rule.match(self.entries[i])] 1239 except libxyz.exceptions.FSRuleError, e: 1240 self._tagged = [] 1241 1242 xyzlog.error(ustring(str(e))) 1243 return
1244 1245 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1246 1247 @refresh
1248 - def tag_invert(self):
1249 """ 1250 Invert currently tagged files 1251 """ 1252 1253 self._tagged = [i for i in xrange(self._len) 1254 if i not in self._tagged]
1255 1256 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1257 1258 @refresh
1259 - def tag_all(self):
1260 """ 1261 Tag every single object in current dir 1262 """ 1263 1264 self._tagged = [i for i in xrange(self._len) if 1265 self.entries[i].name != ".."]
1266 1267 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1268 1269 @refresh
1270 - def untag_all(self):
1271 """ 1272 Untag every single object in current dir 1273 """ 1274 1275 self._tagged = []
1276 1277 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1278 1279 @refresh
1280 - def reload(self):
1281 """ 1282 Reload contents 1283 """ 1284 1285 _selected = self.entries[self.selected] 1286 1287 self._setup(self._vfsobj) 1288 1289 if self.selected >= self._len: 1290 self.selected = self._len - 1 1291 1292 # Try to find previously selected object 1293 if self.entries[self.selected].name != _selected.name: 1294 self.select(_selected.name)
1295 1296 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1297 1298 @refresh
1299 - def select(self, name):
1300 """ 1301 Select VFS object by given name in current directory 1302 """ 1303 1304 for i in xrange(self._len): 1305 if self.entries[i].name == name: 1306 self.selected = i 1307 break
1308 1309 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1310
1311 - def action(self):
1312 """ 1313 Perform action on selected file 1314 """ 1315 1316 _selected = self.entries[self.selected] 1317 1318 _action = self.xyz.am.match(_selected) 1319 1320 if _action is not None: 1321 try: 1322 _action(_selected) 1323 except Exception, e: 1324 xyzlog.error(_(u"Action error: %s") % (ustring(str(e))))
1325 1326 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1327 1328 @refresh
1329 - def chdir(self, path, reload=True, active=True):
1330 """ 1331 Change directory 1332 If reload is not True only execute os.chdir, without reloading 1333 directory contents 1334 If active is False do not call os.chdir 1335 """ 1336 1337 if reload: 1338 _path = os.path.normpath(path) 1339 _parent = None 1340 _old_vfs = None 1341 1342 if self.entries: 1343 _parent = os.path.normpath(self.entries[0].full_path) 1344 _old = self._dir.name 1345 _old_vfs = self._vfsobj 1346 1347 try: 1348 _vfsobj = self.xyz.vfs.dispatch(path, self._enc) 1349 except libxyz.exceptions.VFSError, e: 1350 xyzlog.error(_(u"Unable to chdir to %s: %s") % 1351 (ustring(path), ustring(e))) 1352 return 1353 1354 try: 1355 self._setup(_vfsobj) 1356 except libxyz.exceptions.XYZRuntimeError, e: 1357 xyzlog.info(_(u"Unable to chdir to %s: %s") % 1358 (ustring(path), ustring(e))) 1359 return 1360 1361 self.selected = 0 1362 1363 # We've just stepped out from dir, try to focus on it 1364 if _parent == _path: 1365 for x in xrange(self._len): 1366 if self.entries[x].name == _old: 1367 self.selected = x 1368 break 1369 1370 # TODO: 2-3 level cache 1371 if _old_vfs: 1372 del(_old_vfs) 1373 1374 self.cwd = path 1375 1376 # Call chdir only for local objects 1377 if isinstance(self._vfsobj, LocalVFSObject) and active: 1378 os.chdir(path)
1379 1380 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1381 1382 @refresh
1383 - def search_forward(self):
1384 """ 1385 Search forward for matching object while user types 1386 """ 1387 1388 return self._search_engine(lambda x: (xrange(x, self._len)))
1389 1390 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1391 1392 @refresh
1393 - def search_backward(self):
1394 """ 1395 Search backward for matching object while user types 1396 """ 1397 1398 return self._search_engine(lambda x: (xrange(x, 0, -1)))
1399 1400 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1401
1402 - def search_cycle(self):
1403 """ 1404 Search from current position downwards and then from top to 1405 currently selected 1406 """ 1407 1408 return self._search_engine(lambda x: range(x, self._len) + 1409 range(0, x))
1410 1411 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1412 1413 @refresh
1414 - def show_tagged(self):
1415 """ 1416 Show only tagged entries 1417 """ 1418 1419 if not self._tagged: 1420 return 1421 1422 self.entries = [self.entries[x] for x in self._tagged] 1423 self._len = len(self.entries) 1424 self.selected = 0 1425 self._tagged = [] 1426 self._palettes = self._process_skin_rulesets() 1427 1428 _tagged = _(u"TAGGED") 1429 1430 if not self._title.endswith(_tagged): 1431 self._title = truncate(u"%s:%s" % (self._title, _tagged), 1432 self.size.cols - 4, self._enc, True) 1433 1434 if hasattr(self, "border"): 1435 self.border.set_title(self._title) 1436 1437 self._force_reload = True
1438 1439 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1440
1441 - def _search_engine(self, order, pattern=None):
1442 """ 1443 Search for matching filenames while user types 1444 @param order: A function that returns generator for search order 1445 @param pattern: A search type pattern 1446 """ 1447 1448 self._cursor_attr = self.attr(u"search") 1449 1450 if pattern is None: 1451 # Search everywhere in object name 1452 pattern = lambda pat, obj: ustring(pat) in ustring(obj) 1453 # Search from the beginning of object name 1454 #pattern = lambda pat, obj: obj.startswith(pat) 1455 1456 _dim = self.xyz.screen.get_cols_rows() 1457 _collected = [] 1458 1459 _current_pos = self.selected 1460 _current_pos_orig = self.selected 1461 _skip = False 1462 1463 # Starting internal read loop 1464 while True: 1465 self._custom_info = u"".join(_collected) 1466 1467 self._invalidate() 1468 self.xyz.screen.draw_screen(_dim, self.xyz.top.render(_dim, True)) 1469 1470 try: 1471 _raw = self.xyz.input.get() 1472 1473 if self.xyz.input.WIN_RESIZE in _raw: 1474 _dim = self.xyz.screen.get_cols_rows() 1475 continue 1476 1477 if self._keys.ESCAPE in _raw or self._keys.ENTER in _raw: 1478 self._invalidate() 1479 break 1480 elif self._keys.BACKSPACE in _raw: 1481 _current_pos = _current_pos_orig 1482 if _collected: 1483 _collected.pop() 1484 # Continue search 1485 elif self._keys.DOWN in _raw: 1486 _skip = True 1487 1488 _tmp = _collected[:] 1489 _tmp.extend([ustring(x, self._enc) for x in _raw 1490 if len(x) == 1]) 1491 _pattern = u"".join(_tmp) 1492 except Exception: 1493 break 1494 1495 # Search 1496 for i in order(_current_pos): 1497 if pattern(_pattern, self.entries[i].name): 1498 if _skip: 1499 _skip = False 1500 _current_pos = i + 1 1501 continue 1502 1503 self.selected = i 1504 _collected = _tmp 1505 break 1506 1507 self._cursor_attr = None 1508 self._custom_info = None
1509