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