rbcurse-extras 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/README.md +75 -0
  2. data/VERSION +1 -0
  3. data/examples/data/list.txt +300 -0
  4. data/examples/data/lotr.txt +12 -0
  5. data/examples/data/table.txt +36 -0
  6. data/examples/data/tasks.txt +27 -0
  7. data/examples/data/unix1.txt +21 -0
  8. data/examples/inc/qdfilechooser.rb +70 -0
  9. data/examples/inc/rfe_renderer.rb +121 -0
  10. data/examples/newtabbedwindow.rb +100 -0
  11. data/examples/rfe.rb +1236 -0
  12. data/examples/test2.rb +670 -0
  13. data/examples/testeditlist.rb +78 -0
  14. data/examples/testtable.rb +270 -0
  15. data/examples/testvimsplit.rb +141 -0
  16. data/lib/rbcurse/extras/include/celleditor.rb +112 -0
  17. data/lib/rbcurse/extras/include/checkboxcellrenderer.rb +57 -0
  18. data/lib/rbcurse/extras/include/comboboxcellrenderer.rb +30 -0
  19. data/lib/rbcurse/extras/include/defaultlistselectionmodel.rb +79 -0
  20. data/lib/rbcurse/extras/include/listkeys.rb +37 -0
  21. data/lib/rbcurse/extras/include/listselectable.rb +144 -0
  22. data/lib/rbcurse/extras/include/tableextended.rb +40 -0
  23. data/lib/rbcurse/extras/widgets/horizlist.rb +203 -0
  24. data/lib/rbcurse/extras/widgets/menutree.rb +63 -0
  25. data/lib/rbcurse/extras/widgets/multilinelabel.rb +142 -0
  26. data/lib/rbcurse/extras/widgets/rcomboedit.rb +256 -0
  27. data/lib/rbcurse/extras/widgets/rlink.rb.moved +27 -0
  28. data/lib/rbcurse/extras/widgets/rlistbox.rb +1247 -0
  29. data/lib/rbcurse/extras/widgets/rmenulink.rb.moved +21 -0
  30. data/lib/rbcurse/extras/widgets/rmulticontainer.rb +304 -0
  31. data/lib/rbcurse/extras/widgets/rmultisplit.rb +722 -0
  32. data/lib/rbcurse/extras/widgets/rmultitextview.rb +306 -0
  33. data/lib/rbcurse/extras/widgets/rpopupmenu.rb +755 -0
  34. data/lib/rbcurse/extras/widgets/rtable.rb +1758 -0
  35. data/lib/rbcurse/extras/widgets/rvimsplit.rb +800 -0
  36. data/lib/rbcurse/extras/widgets/table/tablecellrenderer.rb +86 -0
  37. data/lib/rbcurse/extras/widgets/table/tabledatecellrenderer.rb +98 -0
  38. metadata +94 -0
@@ -0,0 +1,21 @@
1
+ Eric S. Raymond, in his book The Art of Unix Programming,[2] summarizes the Unix philosophy as the widely-used KISS Principle of "Keep it Simple, Stupid."[3] He also provides a series of design rules:
2
+
3
+ * Rule of Modularity: Write simple parts connected by clean interfaces.
4
+ * Rule of Clarity: Clarity is better than cleverness.
5
+ * Rule of Composition: Design programs to be connected to other programs.
6
+ * Rule of Separation: Separate policy from mechanism; separate interfaces from engines.
7
+ * Rule of Simplicity: Design for simplicity; add complexity only where you must.
8
+ * Rule of Parsimony: Write a big program only when it is clear by demonstration that nothing else will do.
9
+ * Rule of Transparency: Design for visibility to make inspection and debugging easier.
10
+ * Rule of Robustness: Robustness is the child of transparency and simplicity.
11
+ * Rule of Representation: Fold knowledge into data so program logic can be stupid and robust.[4]
12
+ * Rule of Least Surprise: In interface design, always do the least surprising thing.
13
+ * Rule of Silence: When a program has nothing surprising to say, it should say nothing.
14
+ * Rule of Repair: When you must fail, fail noisily and as soon as possible.
15
+ * Rule of Economy: Programmer time is expensive; conserve it in preference to machine time.
16
+ * Rule of Generation: Avoid hand-hacking; write programs to write programs when you can.
17
+ * Rule of Optimization: Prototype before polishing. Get it working before you optimize it.
18
+ * Rule of Diversity: Distrust all claims for "one true way".
19
+ * Rule of Extensibility: Design for the future, because it will be here sooner than you think.
20
+
21
+
@@ -0,0 +1,70 @@
1
+ # NOTE this no longer works after the changes in Messagebox,
2
+ # needs to be redone.
3
+ # this is a test program, tests out messageboxes. type F1 to exit
4
+ # a quick dirty file chooser in 2 lines of code.
5
+ require 'logger'
6
+ require 'rbcurse/core/system/ncurses'
7
+ require 'rbcurse/core/system/window'
8
+ require 'rbcurse/core/widgets/rwidget'
9
+
10
+ ##
11
+ # a quick dirty file chooser - only temporary till we make something better.
12
+ class QDFileChooser
13
+ attr_accessor :show_folders # bool
14
+ attr_accessor :traverse_folders # bool
15
+ attr_accessor :default_pattern # e.g. "*.*"
16
+ attr_accessor :dialog_title # File Chooser
17
+ def initialize
18
+
19
+ end
20
+ def show_open_dialog
21
+ @form = RubyCurses::Form.new nil
22
+ label = RubyCurses::Label.new @form, {'text' => 'File', 'row'=>3, 'col'=>4, 'color'=>'black', 'bgcolor'=>'white', 'mnemonic'=>'F'}
23
+ field = RubyCurses::Field.new @form do
24
+ name "file"
25
+ row 3
26
+ col 10
27
+ display_length 40
28
+ set_label label
29
+ end
30
+ default_pattern ||= "*.*"
31
+ flist = Dir.glob(default_pattern)
32
+ @listb = RubyCurses::Listbox.new @form do
33
+ name "mylist"
34
+ row 5
35
+ col 10
36
+ width 40
37
+ height 10
38
+ list flist
39
+ title "File list"
40
+ title_attrib 'bold'
41
+ end
42
+ #@listb.list.bind(:ENTER_ROW) { field.set_buffer @listb.selected_item }
43
+ listb = @listb
44
+ field.bind(:CHANGE) do |f|
45
+ flist = Dir.glob(f.getvalue+"*")
46
+ l = listb.list
47
+ l.remove_all
48
+ l.insert 0, *flist
49
+ end
50
+ atitle = @dialog_title || "Quick Dirty(TM) File Chooser"
51
+ @mb = RubyCurses::MessageBox.new @form do
52
+ title atitle
53
+ type :override
54
+ height 20
55
+ width 60
56
+ top 5
57
+ left 20
58
+ default_button 0
59
+ button_type :ok_cancel
60
+ end
61
+ #$log.debug "MBOX :selected #{@listb.selected_item}, #{@listb[@listb.getvalue[0]]} "
62
+ return @mb.selected_index == 0 ? :OK : :CANCEL
63
+ end
64
+ def get_selected_file
65
+ #return @mb.selected_index == 0 ? @listb.selected_item : nil
66
+ #return @mb.selected_index == 0 ? @listb[@listb.getvalue[0]] : nil
67
+ # return either the selected_value or if user pressed okay, then focussed item
68
+ return @mb.selected_index == 0 ? @listb.selected_value || @listb.selected_item : nil
69
+ end
70
+ end
@@ -0,0 +1,121 @@
1
+ module RubyCurses
2
+
3
+ ##
4
+ # This is a basic list cell renderer that will render the to_s value of anything.
5
+ # Using alignment one can use for numbers too.
6
+ # However, for booleans it will print true and false. If editing, you may want checkboxes
7
+ class RfeRenderer < ListCellRenderer
8
+ def initialize text="", config={}, &block
9
+ super
10
+ @orig_bgcolor = @bgcolor
11
+ @orig_color = @color
12
+ @orig_attr = @attr
13
+ # we need to refer to file types, executable, dir, link, otherwise we'll go crazy
14
+ @color_hash = {
15
+ '.rb' => [get_color($datacolor, 'red', 'black'), 0],
16
+ '.txt' => [get_color($datacolor, 'white', 'black'), 0],
17
+ '.gemspec' => [get_color($datacolor, 'yellow', 'black'), 0],
18
+ '.gem' => [get_color($datacolor, 'yellow', 'black'), 0],
19
+ '.c' => [get_color($datacolor, 'green', 'black'), 0],
20
+ '.py' => [get_color($datacolor, 'green', 'black'), 0],
21
+ '.tgz' => [get_color($datacolor, 'red', 'black'), get_attrib('bold')],
22
+ '.gz' => [get_color($datacolor, 'red', 'black'), 0],
23
+ '.zip' => [get_color($datacolor, 'red', 'black'), 0],
24
+ '.jar' => [get_color($datacolor, 'red', 'black'), 0],
25
+ '.html' => [get_color($datacolor, 'green', 'black'), get_attrib('reverse')],
26
+ "" => [get_color($datacolor, 'yellow', 'black'), get_attrib('bold')],
27
+ '.jpg' => [get_color($datacolor, 'magenta', 'black'), 0],
28
+ '.png' => [get_color($datacolor, 'magenta', 'black'), 0],
29
+ '.sh' => [get_color($datacolor, 'red', 'black'), 0],
30
+ '.mp3' => [get_color($datacolor, 'cyan', 'blue'), 0],
31
+ '.bak' => [get_color($datacolor, 'magenta', 'black'), 0],
32
+ '.tmp' => [get_color($datacolor, 'black', 'blue'), 0],
33
+ '.pl' => [get_color($datacolor, 'green', 'black'), 0],
34
+ '.java' => [get_color($datacolor, 'cyan', 'blue'), 0],
35
+ '.class' => [get_color($datacolor, 'magenta', 'black'), 0],
36
+ '.pyc' => [get_color($datacolor, 'magenta', 'black'), 0],
37
+ '.o' => [get_color($datacolor, 'magenta', 'black'), 0],
38
+ '.a' => [get_color($datacolor, 'magenta', 'black'), 0],
39
+ '.lib' => [get_color($datacolor, 'magenta', 'black'), 0]
40
+ }
41
+ end
42
+
43
+ # override parent method to set color_pair and attr for different kind of files
44
+ def select_colors focussed, selected
45
+ ext = File.extname(@path)
46
+ c = @color_hash[ext]
47
+ c = [$datacolor, FFI::NCurses::A_NORMAL] unless c
48
+ if File.directory? @path
49
+ c = [get_color($datacolor, 'blue', 'black'), FFI::NCurses::A_BOLD]
50
+ end
51
+ @color_pair, @attr = *c
52
+ if selected
53
+ #@attr = FFI::NCurses::A_UNDERLINE | FFI::NCurses::A_BOLD # UL not avaible on screen
54
+ @attr = FFI::NCurses::A_REVERSE | FFI::NCurses::A_BOLD
55
+ @color_pair = $datacolor
56
+ end
57
+ if focussed
58
+ @attr = FFI::NCurses::A_REVERSE # | FFI::NCurses::A_BOLD
59
+ end
60
+ end
61
+ #
62
+ def repaint graphic, r=@row,c=@col, row_index=-1, value=@text, focussed=false, selected=false
63
+
64
+ @bgcolor = @orig_bgcolor
65
+ @color = @orig_color
66
+ @row_attr = @orig_attr
67
+ # XXX ouch, when we delete from list, must delete from here too.
68
+ value = @parent.entries[row_index]
69
+ if value[0,1]=="/"
70
+ path = value.dup
71
+ else
72
+ path = @parent.cur_dir()+"/"+value
73
+ end
74
+ @path = path # i need it in select_color
75
+ begin
76
+ stat = File.stat(path)
77
+ if File.directory? path
78
+ @row_attr = Ncurses::A_BOLD
79
+ #@color = 'yellow'
80
+ end
81
+ value = format_string(value, path, stat)
82
+ super
83
+
84
+ rescue => err
85
+ $log.debug " rfe_renderer: #{err}"
86
+ end
87
+
88
+ end
89
+ GIGA_SIZE = 1073741824.0
90
+ MEGA_SIZE = 1048576.0
91
+ KILO_SIZE = 1024.0
92
+
93
+ # Return the file size with a readable style.
94
+ def readable_file_size(size, precision)
95
+ case
96
+ #when size == 1 then "1 B"
97
+ when size < KILO_SIZE then "%d B" % size
98
+ when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
99
+ when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
100
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
101
+ end
102
+ end
103
+ def date_format t
104
+ t.strftime "%Y/%m/%d"
105
+ end
106
+ def format_string fn, path,stat
107
+ max_len = 30
108
+ f = fn.dup
109
+ if File.directory? path
110
+ #"%-*s\t(dir)" % [max_len,f]
111
+ #f = "/"+f # disallows search on keypress
112
+ f = "/"+f
113
+ end
114
+ if f.size > max_len
115
+ f = f[0..max_len-1]
116
+ end
117
+ "%-*s %10s %s" % [max_len,f, readable_file_size(stat.size,1), date_format(stat.mtime)]
118
+ end
119
+ # ADD HERE
120
+ end
121
+ end
@@ -0,0 +1,100 @@
1
+ # this is a test program, tests out tabbed panes. type F1 to exit
2
+ #
3
+ require 'logger'
4
+ require 'rbcurse'
5
+ require 'rbcurse/core/widgets/rtabbedpane'
6
+ require 'rbcurse/core/widgets/rcontainer'
7
+ require 'rbcurse/core/widgets/rcombo'
8
+ require 'rbcurse/core/widgets/rtabbedwindow'
9
+
10
+ include RubyCurses
11
+ class SetupTabbedPane
12
+ def run
13
+ $config_hash ||= Variable.new Hash.new
14
+ #configvar.update_command(){ |v| $config_hash[v.source()] = v.value }
15
+
16
+ r = Container.new nil, :suppress_borders => true
17
+ l1 = Label.new nil, :name => "profile", :attr => 'bold', :text => "Profile"
18
+ f1 = Field.new nil, :name => "name", :maxlen => 20, :display_length => 20, :bgcolor => :white,
19
+ :color => :black, :text => "abc", :label => ' Name: '
20
+ f2 = Field.new nil, :name => "email", :display_length => 20, :bgcolor => :white,
21
+ :color => :blue, :text => "me@google.com", :label => 'Email: '
22
+ f3 = RadioButton.new nil, :variable => $config_hash, :text => "red", :value => "RED", :color => :red
23
+ f4 = RadioButton.new nil, :variable => $config_hash, :text => "blue", :value => "BLUE", :color => :blue
24
+ f5 = RadioButton.new nil, :variable => $config_hash, :text => "green", :value => "GREEN", :color => :green
25
+ r.add(l1,f1)
26
+ r.add(f2)
27
+ r.add(f3,f4,f5)
28
+
29
+ tp = TabbedWindow.new :row => 3, :col => 7, :width => 60, :height => 20 do
30
+ title "User Setup"
31
+ button_type :ok_apply_cancel
32
+ tab "&Profile" do
33
+ item Field.new nil, :row => 2, :col => 2, :text => "enter your name", :label => ' Name: '
34
+ item Field.new nil, :row => 3, :col => 2, :text => "enter your email", :label => 'Email: '
35
+ end
36
+ tab "&Settings" do
37
+ item Label.new nil, :text => "Text", :row => 1, :col => 2, :attr => 'bold'
38
+ item CheckBox.new nil, :row => 2, :col => 2, :text => "Antialias text"
39
+ item CheckBox.new nil, :row => 3, :col => 2, :text => "Use bold fonts"
40
+ item CheckBox.new nil, :row => 4, :col => 2, :text => "Allow blinking text"
41
+ item CheckBox.new nil, :row => 5, :col => 2, :text => "Display ANSI Colors"
42
+ item Label.new nil, :text => "Cursor", :row => 7, :col => 2, :attr => 'bold'
43
+ $config_hash.set_value Variable.new, :cursor
44
+ item RadioButton.new nil, :row => 8, :col => 2, :text => "Block", :value => "block", :variable => $config_hash[:cursor]
45
+ item RadioButton.new nil, :row => 9, :col => 2, :text => "Blink", :value => "blink", :variable => $config_hash[:cursor]
46
+ item RadioButton.new nil, :row => 10, :col => 2, :text => "Underline", :value => "underline", :variable => $config_hash[:cursor]
47
+ end
48
+ tab "&Term" do
49
+
50
+ item Label.new nil, :text => "Arrow Key in Combos", :row => 2, :col => 2, :attr => 'bold'
51
+ x = Variable.new
52
+ $config_hash.set_value x, :term
53
+ item RadioButton.new nil, :row => 3, :col => 2, :text => "ignore", :value => "ignore", :variable => $config_hash[:term]
54
+ item RadioButton.new nil, :row => 4, :col => 2, :text => "popup", :value => "popup", :variable => $config_hash[:term]
55
+ item RadioButton.new nil, :row => 5, :col => 2, :text => "next", :value => "next", :variable => $config_hash[:term]
56
+ cb = ComboBox.new nil, :row => 7, :col => 2, :display_length => 15,
57
+ :list => %w[xterm xterm-color xterm-256color screen vt100 vt102],
58
+ :label => "Declare terminal as: "
59
+ #radio.update_command() {|rb| ENV['TERM']=rb.value }
60
+ item cb
61
+ x.update_command do |rb|
62
+ cb.arrow_key_policy=rb.value.to_sym
63
+ end
64
+
65
+ end
66
+ tab "Conta&iner" do
67
+ item r
68
+ end
69
+ # tell tabbedpane what to do if a button is pressed (ok/apply/cancel)
70
+ command do |eve|
71
+ alert "user pressed button index:#{eve.event} , Name: #{eve.action_command}, Tab: #{eve.source.current_tab} "
72
+ case eve.event
73
+ when 0,2 # ok cancel
74
+ throw :close, eve.event
75
+ when 1 # apply
76
+ end
77
+ end
78
+ end
79
+ tp.run
80
+ end
81
+
82
+ end
83
+ if $0 == __FILE__
84
+ # Initialize curses
85
+ begin
86
+ # XXX update with new color and kb
87
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
88
+ $log = Logger.new((File.join(ENV["LOGDIR"] || "./" ,"rbc13.log")))
89
+ $log.level = Logger::DEBUG
90
+ tp = SetupTabbedPane.new()
91
+ buttonindex = tp.run
92
+ rescue => ex
93
+ ensure
94
+ VER::stop_ncurses
95
+ p ex if ex
96
+ p(ex.backtrace.join("\n")) if ex
97
+ $log.debug( ex) if ex
98
+ $log.debug(ex.backtrace.join("\n")) if ex
99
+ end
100
+ end
@@ -0,0 +1,1236 @@
1
+ #####################################################
2
+ # This is a sample program demonstrating a 2 pane file explorer using
3
+ # rbcurse's widgets.
4
+ # I have used a listbox here, perhaps a Table would be more configurable
5
+ # than a listbox.
6
+ #
7
+ # FIXME many popups use the old Messagebox which will crash. i've updated
8
+ # grep not the others.
9
+ # Copyright rkumar 2009, 2010 under Ruby License.
10
+ #
11
+ ####################################################
12
+ require 'rbcurse'
13
+ require 'rbcurse/core/widgets/rcombo'
14
+ require 'rbcurse/extras/widgets/rlistbox'
15
+ require './inc/rfe_renderer'
16
+ require 'rbcurse/core/widgets/keylabelprinter'
17
+ require 'rbcurse/core/widgets/applicationheader'
18
+ require 'rbcurse/core/include/action'
19
+ require 'fileutils'
20
+ require 'yaml' ## added for 1.9
21
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
22
+
23
+ # TODO
24
+ # operations on selected files: move, delete, zip, copy
25
+ # - delete should move to Trash if exists - DONE
26
+ # global - don't ask confirm
27
+ # Select based on pattern.
28
+ # This class represents the finder pane. There are 2
29
+ # on this sample app
30
+ # NOTE: rfe_renderer uses entries so you may need to sync it with list_data_model
31
+ # actually, renderer should refer back to list data model!
32
+ class FileExplorer
33
+ include FileUtils
34
+ attr_reader :wdir
35
+ attr_reader :list # listbox
36
+ attr_reader :dir
37
+ attr_reader :prev_dirs
38
+ attr_reader :other_list # the opposite list
39
+ attr_reader :entries # avoid, can be outdated
40
+ attr_accessor :filter_pattern
41
+
42
+ def initialize form, rfe, row, col, height, width
43
+ @form = form
44
+ @rfe = rfe
45
+ @row, @col, @ht, @wid = row, col, height, width
46
+ @dir = Dir.new(Dir.getwd)
47
+ @wdir = @dir.path
48
+ @filter_pattern = '*'
49
+ @prev_dirs=[]
50
+ @inside_block = false
51
+ $entry_count = 0 # just for show... may not be accurate after deletes etc
52
+
53
+ end
54
+ def title str
55
+ @list.title = str
56
+ end
57
+ def selected_color
58
+ @list.selected_color
59
+ end
60
+ def selected_bgcolor
61
+ @list.selected_bgcolor
62
+ end
63
+
64
+ # changes to given dir
65
+ # ensure that path is provided since other list
66
+ # may have cd'd elsewhere
67
+ def change_dir adir
68
+ list = @list
69
+ begin
70
+ #dir = File.expand_path dir
71
+ #cd "#{@dir.path}/#{adir}"
72
+ cd adir
73
+ list.title = pwd()
74
+ @dir = Dir.new(Dir.getwd)
75
+ @wdir = @dir.path
76
+ @prev_dirs << @wdir
77
+ rescan
78
+ rescue => err
79
+ @rfe.status_row.text = err.to_s
80
+ end
81
+ end
82
+ def goto_previous_dir
83
+ #alert "Previous dirs contain #{@prev_dirs} "
84
+ d = @prev_dirs.pop
85
+ if !d.nil? && d == @wdir
86
+ d = @prev_dirs.pop
87
+ end
88
+ d = @wdir unless d # for those cases where we are showing some result and user doesn't know how t get back
89
+ change_dir d unless d.nil?
90
+ end
91
+ def filter list
92
+ list.delete_if { |f|
93
+ !File.directory? @wdir +"/"+ f and !File.fnmatch?(@filter_pattern, f)
94
+ }
95
+ #$log.debug " FILTER CALLED AFTER #{list.size}, #{list.entries}"
96
+ end
97
+ def rescan
98
+ flist = @dir.entries
99
+ $entry_count = flist.size # just for show... may not be accurate after deletes etc
100
+ flist.shift
101
+ #populate @entries
102
+ populate flist
103
+ end
104
+ def populate flist
105
+ #fl << format_string("..", nil)
106
+ #fl = []
107
+ #flist.each {|f| ff = "#{@wdir}/#{f}"; stat = File.stat(ff)
108
+ # fl << format_string(f, stat)
109
+ #}
110
+ filter(flist) if @filter_pattern != '*'
111
+ @entries = flist
112
+ list.list_data_model.remove_all
113
+ #list.list_data_model.insert 0, *fl
114
+ list.list_data_model.insert 0, *flist
115
+ list.set_form_row
116
+ #$entry_count = list.size # just for show... may not be accurate after deletes etc
117
+ end
118
+ def sort key, reverse=false
119
+ # remove parent before sorting, keep at top
120
+ first = @entries.delete_at(0) if @entries[0]==".."
121
+ key ||= @sort_key
122
+ cdir=cur_dir()+"/"
123
+ case key
124
+ when :size
125
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
126
+ if reverse
127
+ xs.size <=> ys.size
128
+ else
129
+ ys.size <=> xs.size
130
+ end
131
+ }
132
+ when :mtime
133
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
134
+ if reverse
135
+ xs.mtime <=> ys.mtime
136
+ else
137
+ ys.mtime <=> xs.mtime
138
+ end
139
+ }
140
+ when :atime
141
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
142
+ if reverse
143
+ xs.atime <=> ys.atime
144
+ else
145
+ ys.atime <=> xs.atime
146
+ end
147
+ }
148
+ when :name
149
+ @entries.sort! {|x,y| x <=> y
150
+ if reverse
151
+ x <=> y
152
+ else
153
+ y <=> x
154
+ end
155
+ }
156
+ when :ext
157
+ @entries.sort! {|x,y|
158
+ if reverse
159
+ File.extname(cdir+x) <=> File.extname(cdir+y)
160
+ else
161
+ File.extname(cdir+y) <=> File.extname(cdir+x)
162
+ end
163
+ }
164
+ end
165
+ @sort_key = key
166
+ @entries.insert 0, first unless first.nil? # keep parent on top
167
+ populate @entries
168
+ end
169
+ GIGA_SIZE = 1073741824.0
170
+ MEGA_SIZE = 1048576.0
171
+ KILO_SIZE = 1024.0
172
+
173
+ # Return the file size with a readable style.
174
+ def readable_file_size(size, precision)
175
+ case
176
+ #when size == 1 : "1 B"
177
+ when size < KILO_SIZE then "%d B" % size
178
+ when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
179
+ when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
180
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
181
+ end
182
+ end
183
+ def date_format t
184
+ t.strftime "%Y/%m/%d"
185
+ end
186
+ def oldformat_string fn, stat
187
+ max_len = 30
188
+ f = fn.dup
189
+ if File.directory? f
190
+ #"%-*s\t(dir)" % [max_len,f]
191
+ #f = "/"+f # disallows search on keypress
192
+ f = f + "/ "
193
+ end
194
+ if f.size > max_len
195
+ f = f[0..max_len-1]
196
+ end
197
+ "%-*s\t%10s\t%s" % [max_len,f, readable_file_size(stat.size,1), date_format(stat.mtime)]
198
+ end
199
+ def cur_dir
200
+ @dir.path
201
+ end
202
+ alias :current_dir :cur_dir
203
+ def draw_screen dir=nil
204
+ # I get an error here if dir in yaml file is non-existent, like deleted or copied from another machine
205
+ # 2010-08-22 19:25
206
+ if dir
207
+ if File.exists?(dir) && File.directory?(dir)
208
+ cd dir unless dir.nil?
209
+ else
210
+ dir = nil
211
+ end
212
+ end
213
+
214
+ wdir = FileUtils.pwd
215
+ @prev_dirs << wdir
216
+ @dir = Dir.new(Dir.getwd)
217
+ @wdir = @dir.path
218
+ r = @row
219
+ c = @col
220
+ #cola = 1
221
+ #colb = Ncurses.COLS/2
222
+ ht = @ht
223
+ wid = @wid
224
+ #fl = Dir.glob(default_pattern)
225
+ #flist << format_string("..", nil)
226
+ fl = @dir.entries
227
+ fl.shift
228
+ filter(fl)
229
+ #flist = []
230
+ #fl.each {|f| stat = File.stat(f)
231
+ # flist << format_string(f, stat)
232
+ #}
233
+ @entries = fl
234
+ $entry_count = @entries.size
235
+ title = pwd()
236
+ @wdir = title
237
+ rfe = self
238
+
239
+ lista = Listbox.new @form do
240
+ name "lista"
241
+ row r
242
+ col c
243
+ width wid
244
+ height ht
245
+ #list flist
246
+ list fl
247
+ title wdir
248
+ #title_attrib 'reverse'
249
+ cell_renderer RfeRenderer.new "", {"color"=>@color, "bgcolor"=>@bgcolor, "parent" => rfe, "display_length"=> wid-2}
250
+ KEY_ROW_SELECTOR 0 # C-space since space used for preview
251
+ end
252
+ @list = lista
253
+ lista.bind(:ENTER) {|l| @rfe.current_list(self); l.title_attrib 'reverse'; }
254
+ lista.bind(:LEAVE) {|l| l.title_attrib 'normal'; }
255
+
256
+
257
+ #row_cmd = lambda {|list| file = list.list_data_model[list.current_index].split(/\t/)[0].strip; @rfe.status_row.text = File.stat("#{cur_dir()}/#{file}").inspect }
258
+ row_cmd = lambda {|lb, list| file = list.entries[lb.current_index]; @rfe.status_row.text = file; # File.stat("#{cur_dir()}/#{file}").inspect
259
+ }
260
+ lista.bind(:ENTER_ROW, self) {|lb,list| row_cmd.call(lb,list) }
261
+
262
+ end
263
+ def list_data
264
+ @list.list_data_model
265
+ end
266
+ def current_index
267
+ @list.current_index
268
+ end
269
+ def filename ix=current_index()
270
+ #@entries[@list.current_index]
271
+ list_data()[ix]
272
+ end
273
+ def filepath ix=current_index()
274
+ f = filename(ix)
275
+ return unless f
276
+ if f[0,1]=='/'
277
+ f
278
+ else
279
+ cur_dir() + "/" + f
280
+ end
281
+ end
282
+ # delete the item at position i
283
+ def delete_at i=@list.current_index
284
+ ret = @list.list_data_model.delete_at i
285
+ ret = @entries.delete_at i
286
+ end
287
+ def delete obj
288
+ ret = @list.list_data_model.delete obj
289
+ ret = @entries.delete_at obj
290
+ end
291
+ def insert_at obj, ix = @list.current_index
292
+ @list.list_data_model.insert ix, f
293
+ @entries.insert ix, obj
294
+ end
295
+ def remove_selected_rows
296
+ rows = @list.selected_rows
297
+ rows=rows.sort! {|x,y| y <=> x }
298
+ rows.each do |i|
299
+ ret = @list.list_data_model.delete_at i
300
+ ret = @entries.delete_at i
301
+ end
302
+ end
303
+
304
+ # ADD
305
+ end
306
+ class RFe
307
+ attr_reader :status_row
308
+ def initialize
309
+ @window = VER::Window.root_window
310
+ @form = Form.new @window
311
+ # made in invisible since i am using chunks to print. however, we do set this variable
312
+ status_row = RubyCurses::Label.new @form, {'text' => "", :row => Ncurses.LINES-4, :col => 0, :display_length=>Ncurses.COLS-2, :visible => false}
313
+ @status_row = status_row
314
+ colb = Ncurses.COLS/2
315
+ ht = Ncurses.LINES - 4
316
+ wid = Ncurses.COLS/2 - 0
317
+ @trash_path = File.expand_path("~/.Trash")
318
+ @trash_exists = File.directory? @trash_path
319
+ $log.debug " trash_path #{@trash_path}, #{@trash_exists}"
320
+ @lista = FileExplorer.new @form, self, row=1, col=1, ht, wid
321
+ @listb = FileExplorer.new @form, self, row=1, col=colb, ht, wid
322
+
323
+ init_vars
324
+ end
325
+ def init_vars
326
+ @bookmarks=[]
327
+ @config_name = File.expand_path("~/.rfe.yml")
328
+ if File.exist? @config_name
329
+ @config = YAML::load( File.open(@config_name));
330
+ if !@config.nil?
331
+ @bookmarks = @config["bookmarks"]||[]
332
+ @last_dirs = @config["last_dirs"]
333
+ end
334
+ end
335
+ @config ||={}
336
+ @stopping = false
337
+ end
338
+ def save_config
339
+ @config["last_dirs"]=[@lista.current_dir(),@listb.current_dir()]
340
+ File.open(@config_name, "w") { | f | YAML.dump( @config, f )}
341
+ end
342
+ def move
343
+ fp = @current_list.filepath #.gsub(' ',"\ ")
344
+ fn = @current_list.filename
345
+ $log.debug " FP #{fp}"
346
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
347
+ other_dir = other_list.cur_dir
348
+ $log.debug " OL #{other_list.cur_dir}"
349
+ if @current_list.list.selected_row_count == 0
350
+ str= "#{fn}"
351
+ else
352
+ str= "#{@current_list.list.selected_row_count} files "
353
+ end
354
+
355
+ mb = RubyCurses::MessageBox.new :width => 80 do
356
+ title "Move"
357
+ message "Move #{str} to"
358
+ add Field.new :default => other_dir, :bgcolor => :cyan, :name => "name"
359
+ button_type :ok_cancel
360
+ default_button 0
361
+ end
362
+ index = mb.run
363
+ fld = mb.widget("name")
364
+
365
+ #mb = RubyCurses::MessageBox.new do
366
+ #title "Move"
367
+ #message "Move #{str} to"
368
+ #type :input
369
+ #width 80
370
+ #default_value other_dir
371
+ #button_type :ok_cancel
372
+ #default_button 0
373
+ #end
374
+ #confirm "selected :#{mb.input_value}, #{mb.selected_index}"
375
+ if index == 0
376
+ begin
377
+ if @current_list.list.selected_row_count == 0
378
+ FileUtils.move(fp, fld.text)
379
+ #ret = @current_list.list.list().delete_at @current_list.list.current_index # ???
380
+ # @current_list.entries.delete_at @current_list.current_index
381
+ @current_list.delete_at
382
+ else
383
+ each_selected_row do |f|
384
+ FileUtils.move(f, fld.text)
385
+ end
386
+ @current_list.remove_selected_rows
387
+ @current_list.list.clear_selection
388
+ end
389
+ rescue => err
390
+ textdialog err
391
+ $log.debug "XXX: Exception in rfe, move #{err.to_s} "
392
+ end
393
+ other_list.rescan
394
+ end
395
+ end
396
+ def each_selected_row #title, message, default_value
397
+ rows = @current_list.list.selected_rows
398
+ rows = rows.dup
399
+ rows.each do |i|
400
+ fp = @current_list.filepath i
401
+ #$log.debug " moving #{i}: #{fp}"
402
+ #FileUtils.move(fp, mb.input_value)
403
+ yield fp
404
+ end
405
+ end
406
+ def copy
407
+ fp = @current_list.filepath #.gsub(' ',"\ ")
408
+ fn = @current_list.filename
409
+ $log.debug " FP #{fp}"
410
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
411
+ other_dir = other_list.cur_dir
412
+ $log.debug " OL #{other_list.cur_dir}"
413
+ if @current_list.list.selected_row_count == 0
414
+ str= "#{fn}"
415
+ else
416
+ str= "#{@current_list.list.selected_row_count} files "
417
+ end
418
+ mb = RubyCurses::MessageBox.new do
419
+ title "Copy"
420
+ message "Copy #{str} to"
421
+ type :input
422
+ width 80
423
+ default_value other_dir
424
+ button_type :ok_cancel
425
+ default_button 0
426
+ end
427
+ if mb.selected_index == 0
428
+ if @current_list.list.selected_row_count == 0
429
+ FileUtils.copy(fp, mb.input_value)
430
+ else
431
+ each_selected_row do |f|
432
+ FileUtils.copy(f, mb.input_value)
433
+ end
434
+ @current_list.list.clear_selection
435
+ end
436
+ other_list.rescan
437
+ end
438
+ end
439
+ def delete
440
+ fp = @current_list.filepath #.gsub(' ',"\ ")
441
+ fn = @current_list.filename
442
+ if @current_list.list.selected_row_count == 0
443
+ str= "#{fn}"
444
+ else
445
+ str= "#{@current_list.list.selected_row_count} files "
446
+ end
447
+ if confirm("delete #{str}")
448
+ if @current_list.list.selected_row_count == 0
449
+ if @trash_exists
450
+ FileUtils.mv fp, @trash_path
451
+ else
452
+ FileUtils.rm fp
453
+ end
454
+ ret=@current_list.delete_at
455
+ else
456
+ each_selected_row do |f|
457
+ if @trash_exists
458
+ FileUtils.mv f, @trash_path
459
+ else
460
+ FileUtils.rm f
461
+ end
462
+ end
463
+ @current_list.remove_selected_rows
464
+ @current_list.list.clear_selection
465
+ end
466
+ end
467
+ end
468
+ def copy1
469
+ fp = @current_list.filepath
470
+ fn = @current_list.filename
471
+ $log.debug " FP #{fp}"
472
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
473
+ other_dir = other_list.cur_dir
474
+ $log.debug " OL #{other_list.cur_dir}"
475
+ str= "copy #{fn} to #{other_list.cur_dir}"
476
+ $log.debug " copy #{fp}"
477
+ #confirm "#{str}"
478
+ mb = RubyCurses::MessageBox.new do
479
+ title "Copy"
480
+ message "Copy #{fn} to"
481
+ type :input
482
+ width 60
483
+ default_value other_dir
484
+ button_type :ok_cancel
485
+ default_button 0
486
+ end
487
+ #confirm "selected :#{mb.input_value}, #{mb.selected_index}"
488
+ if mb.selected_index == 0
489
+ # need to redraw directories
490
+ FileUtils.copy(fp, mb.input_value)
491
+ other_list.rescan
492
+ end
493
+ end
494
+ ## TODO : make this separate and callable with its own keylabels
495
+ # Now you can just use viewer() instead of having to write this. See Utils in rwidget.rb
496
+ def view content=nil # can throw IO errors
497
+ require 'rbcurse/core/widgets/rtextview'
498
+ wt = 0
499
+ wl = 0
500
+ wh = Ncurses.LINES-wt
501
+ ww = Ncurses.COLS-wl
502
+ if content.nil?
503
+ begin
504
+ fp = @current_list.filepath
505
+ content = get_contents(fp)
506
+ rescue => err
507
+ $log.debug err.to_s
508
+ $log.debug(err.backtrace.join("\n"))
509
+ alert err.to_s
510
+ return
511
+ end
512
+ else
513
+ fp=""
514
+ end
515
+ @layout = { :height => wh, :width => ww, :top => wt, :left => wl }
516
+ @v_window = VER::Window.new(@layout)
517
+ @v_form = RubyCurses::Form.new @v_window
518
+ @textview = TextView.new @v_form do
519
+ name "myView"
520
+ row 0
521
+ col 0
522
+ width ww
523
+ height wh-0
524
+ title fp
525
+ title_attrib 'bold'
526
+ print_footer true
527
+ footer_attrib 'bold'
528
+ end
529
+ #content = File.open(fp,"r").readlines
530
+ begin
531
+ @textview.set_content content #, :WRAP_WORD
532
+ @v_form.repaint
533
+ @v_window.wrefresh
534
+ Ncurses::Panel.update_panels
535
+ while((ch = @v_window.getchar()) != ?\C-q.getbyte(0) )
536
+ break if [ FFI::NCurses::KEY_F3 , KEY_F10 , ?q.getbyte(0) , 27, 2727].include? ch
537
+ @v_form.handle_key ch
538
+ @v_form.repaint
539
+ ##@v_window.wrefresh
540
+ end
541
+ rescue => err
542
+ alert err.to_s
543
+ ensure
544
+ @v_window.destroy if !@v_window.nil?
545
+ end
546
+ end
547
+ def get_contents fp
548
+ return nil unless File.readable? fp
549
+ return Dir.new(fp).entries if File.directory? fp
550
+ case File.extname(fp)
551
+ when '.tgz','.gz'
552
+ cmd = "tar -ztvf #{fp}"
553
+ content = %x[#{cmd}]
554
+ when '.zip'
555
+ cmd = "unzip -l #{fp}"
556
+ content = %x[#{cmd}]
557
+ else
558
+ content = File.open(fp,"r").readlines
559
+ end
560
+ end
561
+ def opt_file c
562
+ fp = @current_list.filepath
563
+ fn = @current_list.filename
564
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
565
+ case c
566
+ when 'c'
567
+ copy
568
+ when 'm'
569
+ move
570
+ when 'd'
571
+ delete
572
+ when 'u'
573
+ str= "move #{fn} to #{other_list.cur_dir}"
574
+ if confirm("#{str}")
575
+ $log.debug " MOVE #{str}"
576
+ end
577
+ when 'v'
578
+ str= "view #{fp}"
579
+ #if confirm("#{str}")==:YES
580
+ $log.debug " VIEW #{fp}"
581
+ view
582
+ #end
583
+ when 'r'
584
+ str= "ruby #{fn}"
585
+ if confirm("#{str}")
586
+ $log.debug " #{str} "
587
+ end
588
+ when 'e'
589
+ str= "edit #{fp}"
590
+ #if confirm("#{str}")==:YES
591
+ edit fp
592
+ when 'x'
593
+ str= "exec #{fp}"
594
+ exec_popup fp
595
+ when 'p'
596
+ page fp
597
+ when 'q'
598
+ qlmanage fp
599
+ end
600
+ end
601
+ def opt_dir c
602
+ fp = @current_list.filepath
603
+ fn = @current_list.filename
604
+ case c
605
+ when 'O'
606
+ # str= "copy #{fn} to #{other_list.cur_dir}"
607
+ if File.directory? @current_list.filepath
608
+ @current_list.change_dir fp
609
+ end
610
+ when 'o'
611
+ if File.directory? @current_list.filepath
612
+ @other_list.change_dir fp
613
+ end
614
+ @open_in_other = true # ???basically keep opening in other
615
+ when 'd'
616
+ str= "delete #{fn} "
617
+ if confirm("#{str}")
618
+ $log.debug " delete #{fp}"
619
+ FileUtils.rm fp
620
+ ret=@current_list.list.list_data_model.delete_at @current_list.list.current_index # ???
621
+ $log.debug " DEL RET #{ret},#{@current_list.list.current_index}"
622
+ end
623
+ when 'u'
624
+ str= "move #{fn} to #{other_list.cur_dir}"
625
+ if confirm("#{str}")
626
+ $log.debug " MOVE #{str}"
627
+ end
628
+ when 'b'
629
+ dd = @current_list.wdir
630
+ @bookmarks << dd unless @bookmarks.include? dd
631
+ when 'u'
632
+ dd = @current_list.wdir
633
+ @bookmarks.delete dd
634
+ when 'l'
635
+ @current_list.populate @bookmarks
636
+ when 's'
637
+ @config["bookmarks"] = @bookmarks
638
+ save_config
639
+ when 'e'
640
+ str= "edit #{fp}"
641
+ #if confirm("#{str}")==:YES
642
+ edit fp
643
+ when 'm'
644
+ f = get_string("Enter a directory to create", 20 )
645
+ if f != ""
646
+ FileUtils.mkdir f
647
+ @current_list.list.list_data_model.insert @current_list.list.current_index, f # ???
648
+ @current_list.entries.insert @current_list.list.current_index, f # ???
649
+ end
650
+
651
+ when 'x'
652
+ str= "exec #{fp}"
653
+ exec_popup fp
654
+ end
655
+ end
656
+ def exec_popup fp
657
+ last_exec_def1 = @last_exec_def1 || ""
658
+ last_exec_def2 = @last_exec_def2 || false
659
+
660
+ sel, inp, hash = get_string_with_options("Enter a command to execute on #{fp}", 30, last_exec_def1, {"checkboxes" => ["view result"], "checkbox_defaults"=>[last_exec_def2]})
661
+ if sel == 0
662
+ @last_exec_def1 = inp
663
+ @last_exec_def2 = hash["view result"]
664
+ cmd = "#{inp} #{fp}"
665
+ filestr = %x[ #{cmd} 2>/dev/null ]
666
+ if hash["view result"]==true
667
+ view filestr
668
+ end
669
+ end
670
+ end
671
+ def edit fp=@current_list.filepath
672
+ $log.debug " edit #{fp}"
673
+ editor = ENV['EDITOR'] || 'vi'
674
+ vimp = %x[which #{editor}].chomp
675
+ shell_out "#{vimp} #{fp}"
676
+ end
677
+
678
+ # TODO we need to move these to some common file so differnt programs and demos
679
+ # can use them on pressing space or enter.
680
+ def page fp=@current_list.filepath
681
+ ft=%x[file #{fp}]
682
+ if ft.index("text")
683
+ pager = ENV['PAGER'] || 'less'
684
+ vimp = %x[which #{pager}].chomp
685
+ shell_out "#{vimp} #{fp}"
686
+ elsif ft.index(/zip/i)
687
+ shell_out "tar tvf #{fp} | less"
688
+ elsif ft.index(/directory/i)
689
+ shell_out "ls -lh #{fp} | less"
690
+ else
691
+ alert "#{fp} is not text, not paging "
692
+ #use_on_file "als", fp # only zip or archive
693
+ end
694
+ end
695
+ def qlmanage fp=@current_list.filepath
696
+ begin
697
+ a=%x[which qlmanage]
698
+ if a != ""
699
+ cmd="qlmanage -p #{@current_list.filepath} 2>/dev/null"
700
+ %x[#{cmd}]
701
+ else
702
+ alert "qlmanage not present on your system. Trying pager"
703
+ page fp
704
+ end
705
+ rescue Interrupt
706
+ rescue => err
707
+ alert err.to_s
708
+ end
709
+ end
710
+ # also if calling program gives output but does not stop
711
+ # or take control, then nothing will show up
712
+ def use_on_file prog, args, fp=@current_list.filepath
713
+ vimp = %x[which #{prog}].chomp
714
+ if vimp != ""
715
+ alert "using #{prog} "
716
+ shell_out "#{vimp} #{args } #{fp}"
717
+ else
718
+ alert "could not locate #{prog} "
719
+ end
720
+ end
721
+ def stopping?
722
+ @stopping
723
+ end
724
+ def draw_screens
725
+ lasta = lastb = nil
726
+ if !@config["last_dirs"].nil?
727
+ lasta = @config["last_dirs"][0]
728
+ lastb = @config["last_dirs"][1]
729
+ end
730
+ @lista.draw_screen lasta
731
+ @listb.draw_screen lastb
732
+
733
+ # bind dd to delete file
734
+ # actually this should be in listbox, and we should listen for row delete and then call opt_file
735
+ @form.bind_key([?d,?d], 'delete'){
736
+ opt_file 'd'
737
+ }
738
+ # list traps 'q' so this won't work
739
+ @form.bind_key([?q,?q], 'quit'){
740
+ @stopping = true
741
+ }
742
+ # this won't work since the listbox will consume the d first
743
+ @form.bind_key(?@, 'home'){
744
+ @current_list.change_dir File.expand_path("~/")
745
+ }
746
+ @form.bind_key(?^, 'previous dir'){
747
+ @current_list.change_dir @current_list.prev_dirs[0] unless @current_list.prev_dirs.empty?
748
+ }
749
+ @form.bind_key(?\C-f, 'file menu'){
750
+ @klp.mode :file
751
+ @klp.repaint
752
+ while((ch = @window.getchar()) != ?\C-c.getbyte(0) )
753
+ if ch < 33 || ch > 126
754
+ Ncurses.beep
755
+ elsif "cmdsuvrexpq".index(ch.chr) == nil
756
+ Ncurses.beep
757
+ else
758
+ opt_file ch.chr
759
+ break
760
+ end
761
+ end
762
+ @klp.mode :normal
763
+ }
764
+ # C-d has been bound to scroll-down
765
+ @form.bind_key(?\C-w, 'dir menu'){
766
+ @klp.mode :dir
767
+ @klp.repaint
768
+ keys = @klp.get_current_keys
769
+ while((ch = @window.getchar()) != ?\C-c.getbyte(0) )
770
+ if ch < 33 || ch > 126
771
+ Ncurses.beep
772
+ elsif !keys.include?(ch.chr)
773
+ Ncurses.beep
774
+ else
775
+ opt_dir ch.chr
776
+ break
777
+ end
778
+ end
779
+ @klp.mode :normal
780
+ }
781
+ # backspace KEY_BS
782
+ @form.bind_key(127, 'previous dir'){
783
+ @current_list.goto_previous_dir
784
+ }
785
+ # form gives this to list which consumes it for selection.
786
+ # need to bind to list's LIST_SELECTION_EVENT
787
+ @form.bind_key(32, 'pager'){
788
+ fp = @current_list.filepath
789
+ page fp
790
+ }
791
+ @form.bind_key(FFI::NCurses::KEY_F3, 'view'){
792
+ begin
793
+ view()
794
+ rescue => err
795
+ alert err.to_s
796
+ end
797
+ }
798
+ @form.bind_key(FFI::NCurses::KEY_F1, 'help'){
799
+ #Io.view(["this is some help text","hello there"])
800
+ color0 = get_color($promptcolor, 'black','green')
801
+ color1 = get_color($reversecolor, 'green','black')
802
+ color2 = get_color($reversecolor, 'magenta','black')
803
+ arr = []
804
+ #arr << " FILE BROWSER HELP "
805
+ arr << [color0," FILE BROWSER HELP ", FFI::NCurses::A_NORMAL]
806
+ arr << " "
807
+ arr << [[color1," <tab> ",nil],[color2, "- switch between windows", nil]]
808
+ arr << [[color1," <enter> ",nil],[color2, "- open dir", nil]]
809
+ arr << [[color1," F3 ",nil],[color2, "- view file/dir/zip contents (toggle) ", nil]]
810
+ arr << [[color1," F4 ",nil],[color2, "- edit file content", nil]]
811
+ arr << [[color1," <char> ", nil],[color2, "first file starting with <char>",nil]]
812
+ #arr << " VIM MODE "
813
+ arr << [color0," VIM MODE ", FFI::NCurses::A_NORMAL]
814
+ arr << [[color1," M",nil],[color2, "-v - Vim like bindings for navigation (toggle)", nil]]
815
+ arr << " jk gg G up down motion, C-n C-p, Alt-0, Alt-9 "
816
+ arr << [[color1," p or Space ",nil],[color2, "- use pager on current file", nil]]
817
+ arr << [[color1," q ",nil],[color2, "- use qlmanage on file ",nil],[$promptcolor, " (OSX)", nil]]
818
+ arr << [[color1," f<char> ",nil],[color2, "- first file starting with <char>", nil]]
819
+
820
+ arr << " "
821
+ arr << " < > ^ V - move help window "
822
+ arr << " "
823
+ # next line does not work due to chunks
824
+ w = arr.max_by(&:length).length
825
+ #arr = ["this is some help text","hello there"]
826
+
827
+ require 'rbcurse/core/util/viewer'
828
+ RubyCurses::Viewer.view(arr, :layout => [10,10, 4+arr.size, w+2],:close_key => KEY_RETURN, :title => "<Enter> to close", :print_footer => false) do |t|
829
+ # you may configure textview further here.
830
+ #t.suppress_borders true
831
+ #t.color = :black
832
+ #t.bgcolor = :white
833
+ # or
834
+ #t.attr = :reverse
835
+ t.attr = :normal
836
+ #t.bind_key(KEY_F1){ alert "closing up!"; throw :close }
837
+ # just for kicks move window around
838
+ t.bind_key('<'){ f = t.form.window; c = f.left - 1; f.hide; f.mvwin(f.top, c); f.show;
839
+ f.reset_layout([f.height, f.width, f.top, c]);
840
+ }
841
+ t.bind_key('>'){ f = t.form.window; c = f.left + 1; f.hide; f.mvwin(f.top, c);
842
+ f.reset_layout([f.height, f.width, f.top, c]); f.show;
843
+ }
844
+ t.bind_key('^'){ f = t.form.window; c = f.top - 1 ; f.hide; f.mvwin(c, f.left);
845
+ f.reset_layout([f.height, f.width, c, f.left]) ; f.show;
846
+ }
847
+ t.bind_key('V'){ f = t.form.window; c = f.top + 1 ; f.hide; f.mvwin(c, f.left);
848
+ f.reset_layout([f.height, f.width, c, f.left]); f.show;
849
+ }
850
+ end
851
+ }
852
+ @form.bind_key(FFI::NCurses::KEY_F4, 'edit'){
853
+ edit()
854
+ }
855
+ @form.bind_key(FFI::NCurses::KEY_F6, 'sort'){
856
+ selected_index, sort_key, reverse, case_sensitive = sort_popup
857
+ if selected_index == 0
858
+ @current_list.sort(sort_key, reverse)
859
+ end
860
+ }
861
+ @form.bind_key(FFI::NCurses::KEY_F5, 'filter'){
862
+ filter()
863
+ }
864
+ @form.bind_key(FFI::NCurses::KEY_F7, 'grep'){
865
+ grep_popup()
866
+ }
867
+ @form.bind_key(FFI::NCurses::KEY_F8, 'system'){
868
+ system_popup()
869
+ }
870
+ # will work if you are in vim mode
871
+ @form.bind_key(?p, 'pager'){
872
+ page
873
+ }
874
+ @form.bind_key(?z, 'qlmanage OSX'){
875
+ qlmanage
876
+ }
877
+ # will no longer come here. list event has to be used
878
+ #@form.bind_key(?\C-m){ # listbox has eaten it up
879
+ @lista.list.bind(:PRESS){
880
+ dir = @current_list.filepath
881
+ if File.directory? @current_list.filepath
882
+ @current_list.change_dir dir
883
+ end
884
+ }
885
+ @listb.list.bind(:PRESS){
886
+ dir = @current_list.filepath
887
+ if File.directory? @current_list.filepath
888
+ @current_list.change_dir dir
889
+ end
890
+ }
891
+ gw = get_color($reversecolor, 'green', 'black')
892
+ bw = get_color($datacolor, 'blue', 'black')
893
+ @klp = RubyCurses::KeyLabelPrinter.new @form, get_key_labels
894
+ @klp.footer_color_pair = bw
895
+ @klp.footer_mnemonic_color_pair = gw
896
+ @klp.set_key_labels get_key_labels(:file), :file
897
+ @klp.set_key_labels get_key_labels(:view), :view
898
+ @klp.set_key_labels get_key_labels(:dir), :dir
899
+ @form.repaint
900
+ @window.wrefresh
901
+ Ncurses::Panel.update_panels
902
+ begin
903
+ chunks=["File","Key"]
904
+ color0 = get_color($promptcolor, 'white','blue')
905
+ color1 = get_color($datacolor, 'black','green')
906
+
907
+ ## qq stops program, but only if M-v (vim mode)
908
+ while(!stopping? && (ch = @window.getchar()) != ?\C-q.getbyte(0) )
909
+ break if ch == KEY_F10
910
+ s = keycode_tos ch
911
+ #status_row.text = "| Pressed #{ch} , #{s}"
912
+ @form.handle_key(ch)
913
+
914
+ # the following is just a test of show_colored_chunks
915
+ # Please use status_line introduced in 1.4.x
916
+ #count = @current_list.list.size
917
+ count1 = @lista.list.size
918
+ count2 = @listb.list.size
919
+ x, y = FFI::NCurses::getyx @window.get_window
920
+ chunks[0]=[color0, "%-30s" % status_row.text, FFI::NCurses::A_BOLD]
921
+ chunks[1]=[color1, "%-18s" % " | #{ch}, #{s} |", FFI::NCurses::A_NORMAL]
922
+ chunks[2]=[color0, " Total: #{count1}, #{count2}"]
923
+ @window.wmove(Ncurses.LINES-3,0)
924
+ @window.color_set(color0, nil)
925
+ @window.print_empty_line
926
+ @window.wmove(Ncurses.LINES-3,0)
927
+ @window.show_colored_chunks chunks
928
+ @window.wmove(x,y)
929
+
930
+ #@form.repaint
931
+ @window.wrefresh
932
+ end
933
+ ensure
934
+ @window.destroy if !@window.nil?
935
+ save_config
936
+ end
937
+
938
+ end
939
+ # TODO
940
+ #
941
+ #
942
+ #
943
+
944
+ # current_list
945
+ ##
946
+ # getter and setter for current_list
947
+ def current_list(*val)
948
+ if val.empty?
949
+ @current_list
950
+ else
951
+ @current_list = val[0]
952
+ @other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
953
+ end
954
+ end
955
+ def get_key_labels categ=nil
956
+ if categ.nil?
957
+ key_labels = [
958
+ ['C-q', 'Exit'], ['C-v', 'View'],
959
+ ['C-f', 'File'], ['C-w', 'Dir'],
960
+ ['C-Sp','Select'], ['Spc', "Preview"],
961
+ ['F3', 'View'], ['F4', 'Edit'],
962
+ ['F5', 'Filter'], ['F6', 'Sort'],
963
+ ['F7', 'Grep'], ['F8', 'System'],
964
+ ['M-0', 'Top'], ['M-9', 'End'],
965
+ ['C-p', 'PgUp'], ['C-n', 'PgDn'],
966
+ ['M-v', 'Vim Mode'], ['BSpc', 'PrevDir']
967
+ ]
968
+ elsif categ == :file
969
+ key_labels = [
970
+ ['c', 'Copy'], ['m', 'Move'],
971
+ ['d', 'Delete'], ['v', 'View'],
972
+ ['s', 'Select'], ['u', 'Unselect'],
973
+ ['p', 'Page'], ['x', 'Exec Cmd'],
974
+ ['r', 'ruby'], ['e', "Edit"],
975
+ ['C-c', 'Cancel']
976
+ ]
977
+ elsif categ == :view
978
+ key_labels = [
979
+ ['c', 'Date'], ['m', 'Size'],
980
+ ['d', 'Delete'], ['v', 'View'],
981
+ ['C-c', 'Cancel']
982
+ ]
983
+ elsif categ == :dir
984
+ key_labels = [
985
+ ['o', 'open'], ['O', 'Open in right'],
986
+ ['d', 'Delete'], ['R', 'Del Recurse'],
987
+ ['t', 'tree'], ['p', 'Previous'],
988
+ ['b', 'Bookmark'], ['u', 'Unbookmark'],
989
+ ['l', 'List'], ['s', 'Save'],
990
+ ['m', 'mkdir'], nil,
991
+ ['C-c', 'Cancel']
992
+ ]
993
+ end
994
+ return key_labels
995
+ end
996
+ def get_key_labels_table
997
+ key_labels = [
998
+ ['M-n','NewRow'], ['M-d','DelRow'],
999
+ ['C-x','Select'], nil,
1000
+ ['M-0', 'Top'], ['M-9', 'End'],
1001
+ ['C-p', 'PgUp'], ['C-n', 'PgDn'],
1002
+ ['M-Tab','Nxt Fld'], ['Tab','Nxt Col'],
1003
+ ['+','Widen'], ['-','Narrow']
1004
+ ]
1005
+ return key_labels
1006
+ end
1007
+ def sort_popup
1008
+ #mform = RubyCurses::Form.new nil
1009
+ mform = nil
1010
+ field_list = []
1011
+ r = 4
1012
+ $radio = RubyCurses::Variable.new
1013
+ rtextvalue = [:name, :ext, :size, :mtime, :atime]
1014
+ ["Name", "Extension", "Size", "Modify Time", "Access Time" ].each_with_index do |rtext,ix|
1015
+ field = RubyCurses::RadioButton.new mform do
1016
+ variable $radio
1017
+ text rtext
1018
+ value rtextvalue[ix]
1019
+ color 'black'
1020
+ bgcolor 'white'
1021
+ #row r
1022
+ #col 5
1023
+ end
1024
+ field_list << field
1025
+ r += 1
1026
+ end
1027
+ r = 4
1028
+ ["Reverse", "case sensitive"].each do |cbtext|
1029
+ field = RubyCurses::CheckBox.new mform do
1030
+ text cbtext
1031
+ name cbtext
1032
+ color 'black'
1033
+ bgcolor 'white'
1034
+ #row r
1035
+ #col 30
1036
+ end
1037
+ field_list << field
1038
+ r += 1
1039
+ end
1040
+ mb = RubyCurses::MessageBox.new :width => 50 do
1041
+ title "Sort Options"
1042
+ button_type :ok_cancel
1043
+ default_button 0
1044
+ end
1045
+ field_list.each { |e| mb.add(e) }
1046
+ index = mb.run
1047
+ if index == 0
1048
+ $log.debug " SORT POPUP #{$radio.value}"
1049
+ #$log.debug " SORT POPUP #{mb.inspect}"
1050
+ $log.debug " SORT POPUP #{mb.widget("Reverse").value}"
1051
+ $log.debug " SORT POPUP #{mb.widget("case sensitive").value}"
1052
+ end
1053
+ return index, $radio.value, mb.widget("Reverse").value, mb.widget("case sensitive").value
1054
+ end
1055
+ def filter
1056
+ f = get_string("Enter a filter pattern", :maxlen => 20, :default => "*")
1057
+ f = "*" if f.nil? or f == ""
1058
+ @current_list.filter_pattern = f
1059
+ @current_list.rescan
1060
+ end
1061
+
1062
+ # ask user for pattern to grep through files
1063
+ # and display file names in same list
1064
+ # Please see testmessagebox.rb for cleaner ways of creating messageboxes.
1065
+
1066
+ def grep_popup
1067
+ last_regex = @last_regex || ""
1068
+ last_pattern = @last_pattern || "*"
1069
+ r = 4
1070
+ mform = nil
1071
+ field = RubyCurses::Field.new mform do
1072
+ name "regex"
1073
+ #row r
1074
+ #col 30
1075
+ set_buffer last_regex
1076
+ label 'Regex'
1077
+ #set_label Label.new @form, {'text' => 'Regex', 'col'=>5, 'mnemonic'=> 'R'}
1078
+ end
1079
+ r += 1
1080
+ field1 = RubyCurses::Field.new mform do
1081
+ name "filepattern"
1082
+ #row r
1083
+ #col 30
1084
+ set_buffer last_pattern
1085
+ label 'File Pattern'
1086
+ #set_label Label.new @form, {'text' => 'File Pattern','col'=>5, :color=>'black',:bgcolor=>'white','mnemonic'=> 'F'}
1087
+ end
1088
+ r += 1
1089
+ fields = []
1090
+ ["Recurse", "case insensitive"].each do |cbtext|
1091
+ fields.push(RubyCurses::CheckBox.new(mform) do
1092
+ text cbtext
1093
+ name cbtext
1094
+ #color 'black'
1095
+ #bgcolor 'white'
1096
+ #row r
1097
+ #col 5
1098
+ end
1099
+ )
1100
+ r += 1
1101
+ end
1102
+ mb = RubyCurses::MessageBox.new :width => 50 do
1103
+ title "Grep Options"
1104
+ button_type :ok_cancel
1105
+ default_button 0
1106
+ item field
1107
+ item field1
1108
+ item fields.first
1109
+ item fields.last
1110
+ end
1111
+ selected_index = mb.run
1112
+ if selected_index == 0
1113
+ return if mb.form.by_name["regex"].getvalue()==""
1114
+ @last_regex = mb.form.by_name["regex"].getvalue
1115
+ inp = mb.form.by_name["regex"].getvalue
1116
+ fp = mb.form.by_name["filepattern"].getvalue
1117
+ @last_pattern = fp
1118
+ flags=""
1119
+ #flags << " -i " if mb.form.by_name["case insensitive"].value==true
1120
+ #flags << " -R " if mb.form.by_name["Recurse"].value==true
1121
+ flags << " -i " if fields[0].value==true
1122
+ flags << " -R " if fields[1].value==true
1123
+ # sometimes grep pukes "No such file or directory if you specify some
1124
+ # filespec that is not present. I don't want it to come in filestr
1125
+ # so i am not piping stderr to stdout
1126
+ cmd = "cd #{@current_list.cur_dir()};grep -l #{flags} #{inp} #{fp}"
1127
+ filestr = %x[ #{cmd} ]
1128
+ files = nil
1129
+ files = filestr.split(/\n/) unless filestr.nil?
1130
+ #view filestr
1131
+ if !files || files.empty?
1132
+ alert "Sorry! No files for regex #{inp}"
1133
+ return
1134
+ end
1135
+ @current_list.title "grep #{inp}"
1136
+ @current_list.populate files
1137
+ # Use backspace to go back
1138
+ end
1139
+ return selected_index, mb.form.by_name["regex"].getvalue, mb.form.by_name["filepattern"].getvalue, fields[1].value, fields[0].value
1140
+ end
1141
+
1142
+ def system_popup
1143
+ deflt = @last_system || ""
1144
+ options=["run in shell","view output","file explorer"]
1145
+ #inp = get_string("Enter a system command", 30, deflt)
1146
+
1147
+ mb = MessageBox.new :title => "System Command" , :width => 80 do
1148
+ add Field.new :label => 'Enter command', :name => "cmd", :display_length => 50, :bgcolor => :cyan,
1149
+ :text => deflt
1150
+ $radio = RubyCurses::Variable.new
1151
+ add RadioButton.new :text => "run in shell", :color => :blue, :variable => $radio
1152
+ add RadioButton.new :text => "view output", :color => :red, :variable => $radio
1153
+ add RadioButton.new :text => "file explorer", :color => :red, :variable => $radio
1154
+ button_type :ok_cancel
1155
+ end
1156
+ sel = mb.run
1157
+ inp = mb.widget("cmd").text
1158
+ $log.debug "XXX: value of radio is #{$radio.value}, inp : #{inp} "
1159
+ #sel, inp, hash = get_string_with_options("Enter a system command", 40, deflt, {"radiobuttons" => options, "radio_default"=>@last_system_radio || options[0]})
1160
+ if sel == 0
1161
+ if !inp.nil?
1162
+ @last_system = inp
1163
+ @last_system_radio = $radio.value
1164
+ case $radio.value
1165
+ when options[0]
1166
+ shell_out inp
1167
+ when options[1]
1168
+ filestr = %x[ #{inp} ]
1169
+ view filestr
1170
+ when options[2]
1171
+ filestr = %x[ #{inp} ]
1172
+ files = nil
1173
+ files = filestr.split(/\n/) unless filestr.nil?
1174
+ @current_list.title inp
1175
+ @current_list.populate files
1176
+ $log.debug " SYSTEM got #{files.size}, #{files.inspect}"
1177
+ else
1178
+ shell_out inp
1179
+ end
1180
+ end
1181
+ end
1182
+ end
1183
+ # This will notwork, dunno if its being called
1184
+ def popup
1185
+ deflt = @last_regexp || ""
1186
+ #sel, inp, hash = get_string_with_options("Enter a filter pattern", 20, "*", {"checkboxes" => ["case sensitive","reverse"], "checkbox_defaults"=>[true, false]})
1187
+ sel, inp, hash = get_string_with_options("Enter a grep pattern", 20, deflt, {"checkboxes" => ["case insensitive","not-including"]})
1188
+ if sel == 0
1189
+ @last_regexp = inp
1190
+ flags=""
1191
+ flags << " -i " if hash["case insensitive"]==true
1192
+ flags << " -v " if hash["not-including"]==true
1193
+ cmd = "grep -l #{flags} #{inp} *"
1194
+ filestr = %x[ #{cmd} ]
1195
+ files = nil
1196
+ files = filestr.split(/\n/) unless filestr.nil?
1197
+ view filestr
1198
+ end
1199
+ $log.debug " POPUP: #{sel}: #{inp}, #{hash['case sensitive']}, #{hash['reverse']}"
1200
+ end
1201
+ # This won't work if you do something like 'ls -l', you will need to do 'ls -l | less'
1202
+ def shell_out command
1203
+ @window.hide
1204
+ Ncurses.endwin
1205
+ ret = system command
1206
+ Ncurses.refresh
1207
+ #Ncurses.curs_set 0 # why ?
1208
+ @window.show
1209
+ ret
1210
+ end
1211
+ end
1212
+ if $0 == __FILE__
1213
+ include RubyCurses
1214
+ include RubyCurses::Utils
1215
+
1216
+ begin
1217
+ # Initialize curses
1218
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
1219
+ #$log = Logger.new("rbc13.log")
1220
+ $log = Logger.new(ENV['LOGDIR'] || "" + "rbc13.log")
1221
+
1222
+ $log.level = Logger::DEBUG
1223
+
1224
+ catch(:close) do
1225
+ t = RFe.new
1226
+ t.draw_screens
1227
+ end
1228
+ rescue => ex
1229
+ ensure
1230
+ VER::stop_ncurses
1231
+ p ex if ex
1232
+ p(ex.backtrace.join("\n")) if ex
1233
+ $log.debug( ex) if ex
1234
+ $log.debug(ex.backtrace.join("\n")) if ex
1235
+ end
1236
+ end