rkumar-rbcurse 0.1.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 (52) hide show
  1. data/CHANGELOG +1577 -0
  2. data/README.txt +310 -0
  3. data/examples/qdfilechooser.rb +68 -0
  4. data/examples/rfe.rb +853 -0
  5. data/examples/rfe_renderer.rb +69 -0
  6. data/examples/test1.rb +242 -0
  7. data/examples/test2.rb +498 -0
  8. data/examples/testcombo.rb +95 -0
  9. data/examples/testkeypress.rb +61 -0
  10. data/examples/testmenu.rb +105 -0
  11. data/examples/testtable.rb +266 -0
  12. data/examples/testtabp.rb +106 -0
  13. data/examples/testtodo.rb +532 -0
  14. data/examples/viewtodo.rb +512 -0
  15. data/lib/rbcurse.rb +7 -0
  16. data/lib/rbcurse/action.rb +31 -0
  17. data/lib/rbcurse/applicationheader.rb +57 -0
  18. data/lib/rbcurse/celleditor.rb +120 -0
  19. data/lib/rbcurse/checkboxcellrenderer.rb +69 -0
  20. data/lib/rbcurse/colormap.rb +133 -0
  21. data/lib/rbcurse/comboboxcellrenderer.rb +45 -0
  22. data/lib/rbcurse/defaultlistselectionmodel.rb +49 -0
  23. data/lib/rbcurse/keylabelprinter.rb +143 -0
  24. data/lib/rbcurse/listcellrenderer.rb +99 -0
  25. data/lib/rbcurse/listkeys.rb +33 -0
  26. data/lib/rbcurse/listscrollable.rb +216 -0
  27. data/lib/rbcurse/listselectable.rb +67 -0
  28. data/lib/rbcurse/mapper.rb +108 -0
  29. data/lib/rbcurse/orderedhash.rb +77 -0
  30. data/lib/rbcurse/rcombo.rb +243 -0
  31. data/lib/rbcurse/rdialogs.rb +183 -0
  32. data/lib/rbcurse/rform.rb +845 -0
  33. data/lib/rbcurse/rinputdataevent.rb +36 -0
  34. data/lib/rbcurse/rlistbox.rb +804 -0
  35. data/lib/rbcurse/rmenu.rb +666 -0
  36. data/lib/rbcurse/rmessagebox.rb +325 -0
  37. data/lib/rbcurse/rpopupmenu.rb +754 -0
  38. data/lib/rbcurse/rtabbedpane.rb +259 -0
  39. data/lib/rbcurse/rtable.rb +1296 -0
  40. data/lib/rbcurse/rtextarea.rb +673 -0
  41. data/lib/rbcurse/rtextview.rb +335 -0
  42. data/lib/rbcurse/rwidget.rb +1731 -0
  43. data/lib/rbcurse/scrollable.rb +301 -0
  44. data/lib/rbcurse/selectable.rb +94 -0
  45. data/lib/rbcurse/table/tablecellrenderer.rb +85 -0
  46. data/lib/rbcurse/table/tabledatecellrenderer.rb +102 -0
  47. data/lib/ver/keyboard.rb +150 -0
  48. data/lib/ver/keyboard2.rb +170 -0
  49. data/lib/ver/ncurses.rb +102 -0
  50. data/lib/ver/window.rb +369 -0
  51. data/test/test_rbcurse.rb +0 -0
  52. metadata +117 -0
data/README.txt ADDED
@@ -0,0 +1,310 @@
1
+ = rbcurse
2
+
3
+ * http://totalrecall.wordpress.com << CORRECTED !!!
4
+
5
+ * rbcurse on rubyforge: http://rbcurse.rubyforge.org/
6
+
7
+ * See latest changes on http://github.com/rkumar/rbcurse/tree/master/CHANGELOG
8
+
9
+ * Many working demos in examples folder, such as:
10
+
11
+ * test2.rb (exit with F1, Or Cancel button/Alt-C) contains various
12
+ widgets. F2 for menubar toggling
13
+
14
+ * rfe.rb is a ruby file explorer
15
+
16
+ * testtodo.rb is a test TODO application
17
+
18
+ * Screenshots on http://github.com/rkumar/rbcurse/wikis/screenshots
19
+ (OLD)
20
+ and http://www.benegal.org/files/screen/?M=D (new)
21
+ and on blog, http://totalrecall.wordpress.com
22
+
23
+
24
+ == DESCRIPTION:
25
+
26
+ A small but comprehensive widget library written in ruby for creating ncurses
27
+ applications.
28
+
29
+ == FEATURES
30
+
31
+ * entry fields in ruby
32
+ * scrollable list box (new, editable with Field, checkbox and combos)
33
+ * multi-line editable area
34
+ * togglebutton, radio and check buttons (with mnemonics)
35
+ * message box
36
+ * menubar - with submenu and CheckBoxMenuItem
37
+ * popup list
38
+ * tabbedpane (multiple forms using tabbed metaphor)
39
+ * combobox
40
+ * labels with mnemonics (hotkeys)
41
+ * multi-column table - with cell selection and editing
42
+
43
+ Above may be created using DSL like syntax, or hashes.
44
+
45
+ == Sample programs:
46
+
47
+ * rfe : file explorer or Finder like app
48
+ * testtodo.rb : a todo app based on a yaml file
49
+ * test2.rb most widgets (including menus)
50
+ * test1.rb various kinds of messageboxes (input, list, custom)
51
+ * testtabp.rb tabbed pane
52
+ * testcombo.rb combos with various insert policies and vertical
53
+ alignments
54
+
55
+ == PROBLEMS, ISSUES
56
+ TextArea not bug free. Some situations wrapping errors could exist.
57
+
58
+ == Terminal related issues.
59
+
60
+ * Some terminals may not show underlines (e.g screen).
61
+
62
+ * Some terminals (xterm-color) do not process Function keys, avoid declaring F1 etc if
63
+ unsure of client terminals. I have put in fixes for xterm-color F1 and
64
+ backtab.
65
+
66
+ * To use ALT/META keys on a Mac OS X, in Terminal preferences, under
67
+ Keyboard, select
68
+ "use Option as Meta key". All hotkeys are automatically, ALT combinations.
69
+
70
+ * Some screens do not display window background color under spaces.
71
+ This is okay under "screen" and "xterm-color" but not under "xterm". You will notice
72
+ this in the message box samples.
73
+
74
+ I am developing and testing under "screen" under OS X Leopard.
75
+
76
+ == SYNOPSIS:
77
+
78
+ See lib/rbcurse/rwidgets.rb.
79
+ For test programs, see test1, test2, testcombo etc in examples folder.
80
+
81
+ This depends only on "window" provided by ncurses. Does not use forms
82
+ and fields. Minor changes and improvements may have happened to sample
83
+ code below. See test programs for latest, working code.
84
+
85
+ === create a window and a form based on window
86
+
87
+ @layout = { :height => 0, :width => 0, :top => 0, :left => 0 }
88
+ @win = VER::Window.new(@layout)
89
+
90
+ @form = Form.new @win
91
+
92
+
93
+ === create a bunch of fields with dependent labels
94
+
95
+ r = 1; c = 22;
96
+ %w[ name age company].each do |w|
97
+ field = Field.new @form do
98
+ name w
99
+ row r
100
+ col c
101
+ display_length 30
102
+ set_buffer "abcd #{w}"
103
+ set_label Label.new @form, {'text' => w}
104
+ end
105
+ r += 1
106
+ end
107
+
108
+ === create a variable (like TkVariable) and tie a label to it.
109
+
110
+ $results = Variable.new
111
+ $results.value = "A variable"
112
+ var = RubyCurses::Label.new @form, {'text_variable' => $results, "row" => r, "col" => 22}
113
+ r += 1
114
+
115
+ === create a list and a list box based on the list.
116
+
117
+ mylist = []
118
+ 0.upto(100) { |v| mylist << "#{v} scrollable data" }
119
+
120
+ field = Listbox.new @form do
121
+ name "mylist"
122
+ row r
123
+ col 1
124
+ width 40
125
+ height 10
126
+ list mylist
127
+ end
128
+ field.insert 5, "hello ruby", "so long python", "farewell java", "RIP .Net"
129
+
130
+ === create a textarea for entry (this can be buggy at present)
131
+
132
+ texta = TextArea.new @form do
133
+ name "mytext"
134
+ row 1
135
+ col 52
136
+ width 40
137
+ height 20
138
+ end
139
+ texta << "hello there" << "we are testing deletes in this application"
140
+ texta << "HELLO there" << "WE ARE testing deletes in this application"
141
+
142
+ === create a check box, updates a Variable
143
+
144
+ checkbutton = CheckBox.new @form do
145
+ text_variable $results
146
+ #value = true
147
+ onvalue "selected cb"
148
+ offvalue "UNselected cb"
149
+ text "Please click me"
150
+ row 17
151
+ col 22
152
+ end
153
+
154
+ === change field properties at any time by referring to them by name
155
+
156
+ @form.by_name["age"].display_length = 3
157
+ @form.by_name["age"].maxlen = 3
158
+ @form.by_name["age"].set_buffer "24"
159
+ @form.by_name["name"].set_buffer "Not focusable"
160
+ @form.by_name["age"].chars_allowed = /\d/
161
+ @form.by_name["company"].type(:ALPHA)
162
+ @form.by_name["name"].set_focusable(false)
163
+
164
+ @form.by_name["password"].color 'red'
165
+ @form.by_name["password"].bgcolor 'blue'
166
+
167
+ # restrict entry to some values
168
+ password.values(%w[ scotty tiger secret qwerty])
169
+
170
+ # validation using ruby's regular expressions
171
+
172
+ field.valid_regex(/^[A-Z]\d+/)
173
+
174
+ === bind events to forms, and fields
175
+
176
+ @form.bind(:ENTER) { |f| f.label.bgcolor = $promptcolor if f.instance_of? RubyCurses::Field}
177
+ @form.bind(:LEAVE) { |f| f.label.bgcolor = $datacolor if f.instance_of? RubyCurses::Field}
178
+
179
+ === create buttons
180
+
181
+ ok_button = Button.new @form do
182
+ text "OK"
183
+ name "OK"
184
+ row 18
185
+ col 22
186
+ end
187
+ ok_button.command { |form| $results.value = "OK PRESS:";form.printstr(@window, 23,45, "OK CALLED") }
188
+ #text "Cancel"
189
+ cancel_button = Button.new @form do
190
+ text_variable $results
191
+ row 18
192
+ col 28
193
+ end
194
+ cancel_button.command { |form| form.printstr(@window, 23,45, "Cancel CALLED"); throw(:close); }
195
+
196
+ === create radio buttons
197
+
198
+ colorlabel = Label.new @form, {'text' => "Select a color:", "row" => 20,
199
+ "col" => 22, "color"=> "cyan"}
200
+ $radio = Variable.new
201
+ radio1 = RadioButton.new @form do
202
+ text_variable $radio
203
+ text "red"
204
+ value "red"
205
+ color "red"
206
+ row 21
207
+ col 22
208
+ end
209
+ radio2 = RadioButton.new @form do
210
+ text_variable $radio
211
+ text "green"
212
+ value "green"
213
+ color "green"
214
+ row 22
215
+ col 22
216
+ end
217
+
218
+ === create a messagebox
219
+
220
+ @mb = RubyCurses::MessageBox.new do
221
+ title "Enter your name"
222
+ message "Enter your name"
223
+ type :input
224
+ default_value "rahul"
225
+ default_button 0
226
+ end
227
+ #title "Color selector"
228
+ #type :custom
229
+ #buttons %w[red green blue yellow]
230
+ #underlines [0,0,0,0]
231
+
232
+ $log.debug "MBOX : #{@mb.selected_index} "
233
+ $log.debug "MBOX : #{@mb.input_value} "
234
+
235
+ === create a read-only scrollable view of data
236
+
237
+ @textview = TextView.new @form do
238
+ name "myView"
239
+ row 16
240
+ col 52
241
+ width 40
242
+ height 7
243
+ end
244
+ content = File.open("../../README.txt","r").readlines
245
+ @textview.set_content content
246
+
247
+ ## set it to point to row 21
248
+ @textview.top_row 21
249
+
250
+
251
+ # lets scroll the text view as we scroll the listbox
252
+
253
+ listb.bind(:ENTER_ROW, @textview) { |arow, tview| tview.top_row arow }
254
+
255
+ # lets scroll the text view to the line you enter in the numeric
256
+ # field
257
+ @form.by_name["line"].bind(:LEAVE, @textview) { |fld, tv| tv.top_row(fld.getvalue.to_i) }
258
+
259
+ # lets scroll the text view to the first match of the regex you
260
+ # enter
261
+ @form.by_name["regex"].bind(:LEAVE, @textview) { |fld, tv| tv.top_row(tv.find_first_match(fld.getvalue)) }
262
+
263
+ # change the value of colorlabel to the selected radiobutton
264
+ # (red or green)
265
+
266
+ $radio.update_command(colorlabel) {|tv, label| label.color tv.value}
267
+
268
+ # change the attribute of colorlabel to bold or normal
269
+
270
+ $results.update_command(colorlabel,checkbutton) {|tv, label, cb|
271
+ attrs = cb.value ? 'bold' : nil; label.attrs(attrs)}
272
+
273
+ # during menu creation, create a checkboxmenuitem
274
+
275
+ item = RubyCurses::CheckBoxMenuItem.new "CheckMe"
276
+
277
+ # when selected, make colorlabel attribute reverse.
278
+
279
+ item.command(colorlabel){|it, label| att = it.getvalue ? 'reverse' :
280
+ nil; label.attrs(att); label.repaint}
281
+
282
+ == REQUIREMENTS:
283
+
284
+ * ruby 1.8.7 (not compatible with 1.9)
285
+
286
+ * ncurses-ruby
287
+
288
+ (following is provided with source)
289
+
290
+ * uses the window class created by "manveru" (michael) - this can be
291
+ removed if not needed. (lib/ver/window)
292
+ It is provided with this package, and has some alterations from the
293
+ original. I have added a method getchar() which traps and returns
294
+ ALT/META, META+CTRL, META+SHIFT+CONTROL, ALT+Fn etc.
295
+
296
+ So basically do not remove it unless you have very different
297
+ requirements.
298
+
299
+ == INSTALL:
300
+
301
+ * currently, just unzip/untar in a folder. Please change the path in
302
+ the top line of the test programs.
303
+
304
+ (Please advice me how i can improve installation procedure)
305
+
306
+ == LICENSE:
307
+
308
+ Copyright (c) 2008 rkumar
309
+
310
+ Same as ruby license.
@@ -0,0 +1,68 @@
1
+ # this is a test program, tests out messageboxes. type F1 to exit
2
+ # a quick dirty file chooser in 2 lines of code.
3
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
4
+ require 'rubygems'
5
+ require 'ncurses'
6
+ require 'logger'
7
+ require 'ver/ncurses'
8
+ require 'ver/window'
9
+ require 'rbcurse/rwidget'
10
+
11
+ ##
12
+ # a quick dirty file chooser - only temporary till we make something better.
13
+ class QDFileChooser
14
+ attr_accessor :show_folders # bool
15
+ attr_accessor :traverse_folders # bool
16
+ attr_accessor :default_pattern # e.g. "*.*"
17
+ attr_accessor :dialog_title # File Chooser
18
+ def initialize
19
+
20
+ end
21
+ def show_open_dialog
22
+ @form = RubyCurses::Form.new nil
23
+ label = RubyCurses::Label.new @form, {'text' => 'File', 'row'=>3, 'col'=>4, 'color'=>'black', 'bgcolor'=>'white', 'mnemonic'=>'F'}
24
+ field = RubyCurses::Field.new @form do
25
+ name "file"
26
+ row 3
27
+ col 10
28
+ display_length 40
29
+ set_label label
30
+ end
31
+ default_pattern ||= "*.*"
32
+ flist = Dir.glob(default_pattern)
33
+ @listb = RubyCurses::Listbox.new @form do
34
+ name "mylist"
35
+ row 5
36
+ col 10
37
+ width 40
38
+ height 10
39
+ list flist
40
+ title "File list"
41
+ title_attrib 'bold'
42
+ end
43
+ #@listb.list.bind(:ENTER_ROW) { field.set_buffer @listb.selected_item }
44
+ listb = @listb
45
+ field.bind(:CHANGE) do |f|
46
+ flist = Dir.glob(f.getvalue+"*")
47
+ l = listb.list
48
+ l.remove_all
49
+ l.insert 0, *flist
50
+ end
51
+ atitle = @dialog_title || "Quick Dirty(TM) File Chooser"
52
+ @mb = RubyCurses::MessageBox.new @form do
53
+ title atitle
54
+ type :override
55
+ height 20
56
+ width 60
57
+ top 5
58
+ left 20
59
+ default_button 0
60
+ button_type :ok_cancel
61
+ end
62
+ return @mb.selected_index == 0 ? :OK : :CANCEL
63
+ $log.debug "MBOX :selected #{@listb.selected_item}"
64
+ end
65
+ def get_selected_file
66
+ return @mb.selected_index == 0 ? @listb.selected_item : nil
67
+ end
68
+ end
data/examples/rfe.rb ADDED
@@ -0,0 +1,853 @@
1
+ require 'rubygems'
2
+ require 'ncurses'
3
+ require 'logger'
4
+ require 'rbcurse'
5
+ require 'rbcurse/rcombo'
6
+ require 'rbcurse/rlistbox'
7
+ require 'rfe_renderer'
8
+ #require 'lib/rbcurse/table/tablecellrenderer'
9
+ require 'rbcurse/keylabelprinter'
10
+ require 'rbcurse/applicationheader'
11
+ require 'rbcurse/action'
12
+ require 'fileutils'
13
+ #$LOAD_PATH << "/Users/rahul/work/projects/rbcurse/"
14
+
15
+ # TODO
16
+ # operations on selected files: move, delete, zip
17
+ # This class represents the finder pane. There are 2
18
+ # on this sample app
19
+ class FileExplorer
20
+ include FileUtils
21
+ attr_reader :wdir
22
+ attr_reader :list # listbox
23
+ attr_reader :dir
24
+ attr_reader :prev_dirs
25
+ attr_reader :other_list # the opposite list
26
+ attr_reader :entries # avoid, can be outdated
27
+ attr_accessor :filter_pattern
28
+
29
+ def initialize form, rfe, row, col, height, width
30
+ @form = form
31
+ @rfe = rfe
32
+ @row, @col, @ht, @wid = row, col, height, width
33
+ @dir = Dir.new(Dir.getwd)
34
+ @wdir = @dir.path
35
+ @filter_pattern = '*'
36
+ @prev_dirs=[]
37
+ end
38
+ def title str
39
+ @list.title = str
40
+ end
41
+ def selected_color
42
+ @list.selected_color
43
+ end
44
+ def selected_bgcolor
45
+ @list.selected_bgcolor
46
+ end
47
+
48
+ # changes to given dir
49
+ # ensure that path is provided since other list
50
+ # may have cd'd elsewhere
51
+ def change_dir adir
52
+ list = @list
53
+ begin
54
+ #dir = File.expand_path dir
55
+ #cd "#{@dir.path}/#{adir}"
56
+ cd adir
57
+ list.title = pwd()
58
+ @dir = Dir.new(Dir.getwd)
59
+ @wdir = @dir.path
60
+ @prev_dirs << @wdir
61
+ rescan
62
+ rescue => err
63
+ @rfe.status_row.text = err.to_s
64
+ end
65
+ end
66
+ def goto_previous_dir
67
+ d = @prev_dirs.pop
68
+ if !d.nil? and d == @wdir
69
+ d = @prev_dirs.pop
70
+ end
71
+ change_dir d unless d.nil?
72
+ end
73
+ def filter list
74
+ list.delete_if { |f|
75
+ !File.directory? @wdir +"/"+ f and !File.fnmatch?(@filter_pattern, f)
76
+ }
77
+ #$log.debug " FILTER CALLED AFTER #{list.size}, #{list.entries}"
78
+ end
79
+ def rescan
80
+ flist = @dir.entries
81
+ flist.shift
82
+ #populate @entries
83
+ populate flist
84
+ end
85
+ def populate flist
86
+ #fl << format_string("..", nil)
87
+ #fl = []
88
+ #flist.each {|f| ff = "#{@wdir}/#{f}"; stat = File.stat(ff)
89
+ # fl << format_string(f, stat)
90
+ #}
91
+ filter(flist) if @filter_pattern != '*'
92
+ @entries = flist
93
+ list.list_data_model.remove_all
94
+ #list.list_data_model.insert 0, *fl
95
+ list.list_data_model.insert 0, *flist
96
+ end
97
+ def sort key, reverse=false
98
+ # remove parent before sorting, keep at top
99
+ first = @entries.delete_at(0) if @entries[0]==".."
100
+ key ||= @sort_key
101
+ cdir=cur_dir()+"/"
102
+ case key
103
+ when :size
104
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
105
+ if reverse
106
+ xs.size <=> ys.size
107
+ else
108
+ ys.size <=> xs.size
109
+ end
110
+ }
111
+ when :mtime
112
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
113
+ if reverse
114
+ xs.mtime <=> ys.mtime
115
+ else
116
+ ys.mtime <=> xs.mtime
117
+ end
118
+ }
119
+ when :atime
120
+ @entries.sort! {|x,y| xs = File.stat(cdir+x); ys = File.stat(cdir+y);
121
+ if reverse
122
+ xs.atime <=> ys.atime
123
+ else
124
+ ys.atime <=> xs.atime
125
+ end
126
+ }
127
+ when :name
128
+ @entries.sort! {|x,y| x <=> y
129
+ if reverse
130
+ x <=> y
131
+ else
132
+ y <=> x
133
+ end
134
+ }
135
+ when :ext
136
+ @entries.sort! {|x,y|
137
+ if reverse
138
+ File.extname(cdir+x) <=> File.extname(cdir+y)
139
+ else
140
+ File.extname(cdir+y) <=> File.extname(cdir+x)
141
+ end
142
+ }
143
+ end
144
+ @sort_key = key
145
+ @entries.insert 0, first unless first.nil? # keep parent on top
146
+ populate @entries
147
+ end
148
+ GIGA_SIZE = 1073741824.0
149
+ MEGA_SIZE = 1048576.0
150
+ KILO_SIZE = 1024.0
151
+
152
+ # Return the file size with a readable style.
153
+ def readable_file_size(size, precision)
154
+ case
155
+ #when size == 1 : "1 B"
156
+ when size < KILO_SIZE : "%d B" % size
157
+ when size < MEGA_SIZE : "%.#{precision}f K" % (size / KILO_SIZE)
158
+ when size < GIGA_SIZE : "%.#{precision}f M" % (size / MEGA_SIZE)
159
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
160
+ end
161
+ end
162
+ def date_format t
163
+ t.strftime "%Y/%m/%d"
164
+ end
165
+ def oldformat_string fn, stat
166
+ max_len = 30
167
+ f = fn.dup
168
+ if File.directory? f
169
+ #"%-*s\t(dir)" % [max_len,f]
170
+ #f = "/"+f # disallows search on keypress
171
+ f = f + "/ "
172
+ end
173
+ if f.size > max_len
174
+ f = f[0..max_len-1]
175
+ end
176
+ "%-*s\t%10s\t%s" % [max_len,f, readable_file_size(stat.size,1), date_format(stat.mtime)]
177
+ end
178
+ def cur_dir
179
+ @dir.path
180
+ end
181
+ alias :current_dir :cur_dir
182
+ def draw_screen dir=nil
183
+ cd dir unless dir.nil?
184
+ wdir = FileUtils.pwd
185
+ @prev_dirs << wdir
186
+ @dir = Dir.new(Dir.getwd)
187
+ @wdir = @dir.path
188
+ r = @row
189
+ c = @col
190
+ #cola = 1
191
+ #colb = Ncurses.COLS/2
192
+ ht = @ht
193
+ wid = @wid
194
+ #fl = Dir.glob(default_pattern)
195
+ #flist << format_string("..", nil)
196
+ fl = @dir.entries
197
+ fl.shift
198
+ filter(fl)
199
+ #flist = []
200
+ #fl.each {|f| stat = File.stat(f)
201
+ # flist << format_string(f, stat)
202
+ #}
203
+ @entries = fl
204
+ title = pwd()
205
+ @wdir = title
206
+ rfe = self
207
+
208
+ lista = Listbox.new @form do
209
+ name "lista"
210
+ row r
211
+ col c
212
+ width wid
213
+ height ht
214
+ #list flist
215
+ list fl
216
+ title wdir
217
+ #title_attrib 'reverse'
218
+ cell_renderer RfeRenderer.new "", {"color"=>@color, "bgcolor"=>@bgcolor, "parent" => rfe, "display_length"=> wid-2}
219
+ end
220
+ @list = lista
221
+ lista.bind(:ENTER) {|l| @rfe.current_list(self); l.title_attrib 'reverse'; }
222
+ lista.bind(:LEAVE) {|l| l.title_attrib 'normal'; }
223
+
224
+
225
+ #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 }
226
+ row_cmd = lambda {|lb, list| file = list.entries[lb.current_index]; @rfe.status_row.text = file; # File.stat("#{cur_dir()}/#{file}").inspect
227
+ }
228
+ lista.bind(:ENTER_ROW, self) {|lb,list| row_cmd.call(lb,list) }
229
+
230
+ end
231
+ def list_data
232
+ @list.list_data_model
233
+ end
234
+ def current_index
235
+ @list.current_index
236
+ end
237
+ def filename
238
+ #@entries[@list.current_index]
239
+ list_data()[current_index()]
240
+ end
241
+ def filepath
242
+ f = filename()
243
+ if f[0,1]=='/'
244
+ f
245
+ else
246
+ cur_dir() + "/" + filename()
247
+ end
248
+ end
249
+
250
+ end
251
+ class RFe
252
+ attr_reader :status_row
253
+ def initialize
254
+ @window = VER::Window.root_window
255
+ @form = Form.new @window
256
+ status_row = RubyCurses::Label.new @form, {'text' => "", :row => Ncurses.LINES-4, :col => 0, :display_length=>Ncurses.COLS-2}
257
+ @status_row = status_row
258
+ colb = Ncurses.COLS/2
259
+ ht = Ncurses.LINES - 7
260
+ wid = Ncurses.COLS/2 - 0
261
+ @lista = FileExplorer.new @form, self, row=2, col=1, ht, wid
262
+ @listb = FileExplorer.new @form, self, row=2, col=colb, ht, wid
263
+
264
+ init_vars
265
+ end
266
+ def init_vars
267
+ @bookmarks=[]
268
+ @config_name = File.expand_path("~/.rfe.yml")
269
+ if File.exist? @config_name
270
+ @config = YAML::load( File.open(@config_name));
271
+ if !@config.nil?
272
+ @bookmarks = @config["bookmarks"]||[]
273
+ @last_dirs = @config["last_dirs"]
274
+ end
275
+ end
276
+ @config ||={}
277
+ end
278
+ def save_config
279
+ @config["last_dirs"]=[@lista.current_dir(),@listb.current_dir()]
280
+ File.open(@config_name, "w") { | f | YAML.dump( @config, f )}
281
+ end
282
+ def move
283
+ fp = @current_list.filepath
284
+ fn = @current_list.filename
285
+ $log.debug " FP #{fp}"
286
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
287
+ other_dir = other_list.cur_dir
288
+ $log.debug " OL #{other_list.cur_dir}"
289
+ str= "move #{fn} to #{other_list.cur_dir}"
290
+ $log.debug " MOVE #{fp}"
291
+ #confirm "#{str}"
292
+ mb = RubyCurses::MessageBox.new do
293
+ title "Move"
294
+ message "Move #{fn} to"
295
+ type :input
296
+ width 60
297
+ default_value other_dir
298
+ button_type :ok_cancel
299
+ default_button 0
300
+ end
301
+ #confirm "selected :#{mb.input_value}, #{mb.selected_index}"
302
+ if mb.selected_index == 0
303
+ # need to redraw directories
304
+ FileUtils.move(fp, mb.input_value)
305
+ @current_list.list.list_data_model.delete_at @current_list.list.current_index # ???
306
+ other_list.rescan
307
+ end
308
+ end
309
+ def copy
310
+ fp = @current_list.filepath
311
+ fn = @current_list.filename
312
+ $log.debug " FP #{fp}"
313
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
314
+ other_dir = other_list.cur_dir
315
+ $log.debug " OL #{other_list.cur_dir}"
316
+ str= "copy #{fn} to #{other_list.cur_dir}"
317
+ $log.debug " copy #{fp}"
318
+ #confirm "#{str}"
319
+ mb = RubyCurses::MessageBox.new do
320
+ title "Copy"
321
+ message "Copy #{fn} to"
322
+ type :input
323
+ width 60
324
+ default_value other_dir
325
+ button_type :ok_cancel
326
+ default_button 0
327
+ end
328
+ #confirm "selected :#{mb.input_value}, #{mb.selected_index}"
329
+ if mb.selected_index == 0
330
+ # need to redraw directories
331
+ FileUtils.copy(fp, mb.input_value)
332
+ other_list.rescan
333
+ end
334
+ end
335
+ ## TODO : make this separate and callable with its own keylabels
336
+ def view content=nil
337
+ require 'rbcurse/rtextview'
338
+ wt = 0
339
+ wl = 0
340
+ wh = Ncurses.LINES-wt
341
+ ww = Ncurses.COLS-wl
342
+ if content.nil?
343
+ fp = @current_list.filepath
344
+ content = get_contents(fp)
345
+ else
346
+ fp=""
347
+ end
348
+ @layout = { :height => wh, :width => ww, :top => wt, :left => wl }
349
+ @v_window = VER::Window.new(@layout)
350
+ @v_form = RubyCurses::Form.new @v_window
351
+ @textview = TextView.new @v_form do
352
+ name "myView"
353
+ row 0
354
+ col 0
355
+ width ww
356
+ height wh-2
357
+ title fp
358
+ title_attrib 'bold'
359
+ print_footer true
360
+ footer_attrib 'bold'
361
+ end
362
+ #content = File.open(fp,"r").readlines
363
+ @textview.set_content content #, :WRAP_WORD
364
+ @v_form.repaint
365
+ @v_window.wrefresh
366
+ Ncurses::Panel.update_panels
367
+ begin
368
+ while((ch = @v_window.getchar()) != ?\C-q )
369
+ break if ch == KEY_F3
370
+ @v_form.handle_key ch
371
+ @v_form.repaint
372
+ ##@v_window.wrefresh
373
+ end
374
+ ensure
375
+ @v_window.destroy if !@v_window.nil?
376
+ end
377
+ end
378
+ def get_contents fp
379
+ return nil unless File.readable? fp
380
+ return Dir.new(fp).entries if File.directory? fp
381
+ case File.extname(fp)
382
+ when '.tgz','.gz'
383
+ cmd = "tar -ztvf #{fp}"
384
+ content = %x[#{cmd}]
385
+ else
386
+ content = File.open(fp,"r").readlines
387
+ end
388
+ end
389
+ def opt_file c
390
+ fp = @current_list.filepath
391
+ fn = @current_list.filename
392
+ other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
393
+ case c
394
+ when 'c'
395
+ # str= "copy #{fn} to #{other_list.cur_dir}"
396
+ copy
397
+ when 'm'
398
+ str= "move #{fn} to #{other_list.cur_dir}"
399
+ move
400
+ #if confirm("#{str}")==:YES
401
+ #$log.debug " MOVE #{str}"
402
+ #end
403
+ when 'd'
404
+ str= "delete #{fn} "
405
+ if confirm("#{str}")==:YES
406
+ $log.debug " delete #{fp}"
407
+ FileUtils.rm fp
408
+ ret=@current_list.list.list_data_model.delete_at @current_list.list.current_index # ???
409
+ $log.debug " DEL RET #{ret},#{@current_list.list.current_index}"
410
+ end
411
+ when 'u'
412
+ str= "move #{fn} to #{other_list.cur_dir}"
413
+ if confirm("#{str}")==:YES
414
+ $log.debug " MOVE #{str}"
415
+ end
416
+ when 'v'
417
+ str= "view #{fp}"
418
+ #if confirm("#{str}")==:YES
419
+ $log.debug " VIEW #{fp}"
420
+ view
421
+ #end
422
+ when 'r'
423
+ str= "ruby #{fn}"
424
+ if confirm("#{str}")=='y'
425
+ $log.debug " #{str} "
426
+ end
427
+ when 'e'
428
+ str= "edit #{fp}"
429
+ #if confirm("#{str}")==:YES
430
+ edit fp
431
+ when 'x'
432
+ str= "exec #{fp}"
433
+ exec_popup fp
434
+ end
435
+ end
436
+ def opt_dir c
437
+ fp = @current_list.filepath
438
+ fn = @current_list.filename
439
+ case c
440
+ when 'O'
441
+ # str= "copy #{fn} to #{other_list.cur_dir}"
442
+ if File.directory? @current_list.filepath
443
+ @current_list.change_dir fp
444
+ end
445
+ when 'o'
446
+ if File.directory? @current_list.filepath
447
+ @other_list.change_dir fp
448
+ end
449
+ @open_in_other = true # ???basically keep opening in other
450
+ when 'd'
451
+ str= "delete #{fn} "
452
+ if confirm("#{str}")==:YES
453
+ $log.debug " delete #{fp}"
454
+ FileUtils.rm fp
455
+ ret=@current_list.list.list_data_model.delete_at @current_list.list.current_index # ???
456
+ $log.debug " DEL RET #{ret},#{@current_list.list.current_index}"
457
+ end
458
+ when 'u'
459
+ str= "move #{fn} to #{other_list.cur_dir}"
460
+ if confirm("#{str}")==:YES
461
+ $log.debug " MOVE #{str}"
462
+ end
463
+ when 'b'
464
+ dd = @current_list.wdir
465
+ @bookmarks << dd unless @bookmarks.include? dd
466
+ when 'u'
467
+ dd = @current_list.wdir
468
+ @bookmarks.delete dd
469
+ when 'l'
470
+ @current_list.populate @bookmarks
471
+ when 's'
472
+ @config["bookmarks"] = @bookmarks
473
+ save_config
474
+ when 'e'
475
+ str= "edit #{fp}"
476
+ #if confirm("#{str}")==:YES
477
+ edit fp
478
+ when 'x'
479
+ str= "exec #{fp}"
480
+ exec_popup fp
481
+ end
482
+ end
483
+ def exec_popup fp
484
+ last_exec_def1 = @last_exec_def1 || ""
485
+ last_exec_def2 = @last_exec_def2 || false
486
+
487
+ 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]})
488
+ if sel == 0
489
+ @last_exec_def1 = inp
490
+ @last_exec_def2 = hash["view result"]
491
+ cmd = "#{inp} #{fp}"
492
+ filestr = %x[ #{cmd} 2>/dev/null ]
493
+ if hash["view result"]==true
494
+ view filestr
495
+ end
496
+ end
497
+ end
498
+ def edit fp=@current_list.filepath
499
+ $log.debug " edit #{fp}"
500
+ shell_out "/opt/local/bin/vim #{fp}"
501
+ end
502
+ def draw_screens
503
+ lasta = lastb = nil
504
+ if !@config["last_dirs"].nil?
505
+ lasta = @config["last_dirs"][0]
506
+ lastb = @config["last_dirs"][1]
507
+ end
508
+ @lista.draw_screen lasta
509
+ @listb.draw_screen lastb
510
+
511
+ @form.bind_key(?@){
512
+ @current_list.change_dir File.expand_path("~/")
513
+ }
514
+ @form.bind_key(?^){
515
+ @current_list.change_dir @current_list.prev_dirs[0] unless @current_list.prev_dirs.empty?
516
+ }
517
+ @form.bind_key(?\C-f){
518
+ @klp.mode :file
519
+ @klp.repaint
520
+ ## FIXME chr could fail !!
521
+ while((ch = @window.getchar()) != ?\C-c )
522
+ if "cmdsuvrex".index(ch.chr) == nil
523
+ Ncurses.beep
524
+ else
525
+ opt_file ch.chr
526
+ break
527
+ end
528
+ end
529
+ @klp.mode :normal
530
+ }
531
+ @form.bind_key(?\C-d){
532
+ @klp.mode :dir
533
+ @klp.repaint
534
+ keys = @klp.get_current_keys
535
+ ## FIXME chr could fail !!
536
+ while((ch = @window.getchar()) != ?\C-c )
537
+ if !keys.include?(ch.chr)
538
+ Ncurses.beep
539
+ else
540
+ opt_dir ch.chr
541
+ break
542
+ end
543
+ end
544
+ @klp.mode :normal
545
+ }
546
+ # backspace
547
+ @form.bind_key(127){
548
+ @current_list.goto_previous_dir
549
+ }
550
+ @form.bind_key(32){
551
+ begin
552
+ cmd="qlmanage -p #{@current_list.filepath} 2>/dev/null"
553
+ %x[#{cmd}]
554
+ rescue Interrupt
555
+ end
556
+ }
557
+ @form.bind_key(KEY_F3){
558
+ view()
559
+ }
560
+ @form.bind_key(KEY_F4){
561
+ edit()
562
+ }
563
+ @form.bind_key(KEY_F6){
564
+ selected_index, sort_key, reverse, case_sensitive = sort_popup
565
+ if selected_index == 0
566
+ @current_list.sort(sort_key, reverse)
567
+ end
568
+ }
569
+ @form.bind_key(KEY_F5){
570
+ filter()
571
+ }
572
+ @form.bind_key(KEY_F7){
573
+ grep_popup()
574
+ }
575
+ @form.bind_key(KEY_F8){
576
+ system_popup()
577
+ }
578
+ @form.bind_key(?\C-m){
579
+ dir = @current_list.filepath
580
+ if File.directory? @current_list.filepath
581
+ @current_list.change_dir dir
582
+ end
583
+ }
584
+ @klp = RubyCurses::KeyLabelPrinter.new @form, get_key_labels
585
+ @klp.set_key_labels get_key_labels(:file), :file
586
+ @klp.set_key_labels get_key_labels(:view), :view
587
+ @klp.set_key_labels get_key_labels(:dir), :dir
588
+ @form.repaint
589
+ @window.wrefresh
590
+ Ncurses::Panel.update_panels
591
+ begin
592
+ while((ch = @window.getchar()) != ?\C-q )
593
+ s = keycode_tos ch
594
+ status_row.text = "Pressed #{ch} , #{s}"
595
+ @form.handle_key(ch)
596
+
597
+ @form.repaint
598
+ @window.wrefresh
599
+ end
600
+ ensure
601
+ @window.destroy if !@window.nil?
602
+ save_config
603
+ end
604
+
605
+ end
606
+ # TODO
607
+ #
608
+ #
609
+ #
610
+
611
+ # current_list
612
+ ##
613
+ # getter and setter for current_list
614
+ def current_list(*val)
615
+ if val.empty?
616
+ @current_list
617
+ else
618
+ @current_list = val[0]
619
+ @other_list = [@lista, @listb].index(@current_list)==0 ? @listb : @lista
620
+ end
621
+ end
622
+ def get_key_labels categ=nil
623
+ if categ.nil?
624
+ key_labels = [
625
+ ['C-q', 'Exit'], ['C-v', 'View'],
626
+ ['C-f', 'File'], ['C-d', 'Dir'],
627
+ ['C-x','Select'], nil,
628
+ ['F3', 'View'], ['F4', 'Edit'],
629
+ ['F5', 'Filter'], ['F6', 'Sort'],
630
+ ['F7', 'Grep'], ['F8', 'System'],
631
+ ['M-0', 'Top'], ['M-9', 'End'],
632
+ ['C-p', 'PgUp'], ['C-n', 'PgDn']
633
+ ]
634
+ elsif categ == :file
635
+ key_labels = [
636
+ ['c', 'Copy'], ['m', 'Move'],
637
+ ['d', 'Delete'], ['v', 'View'],
638
+ ['s', 'Select'], ['u', 'Unselect'],
639
+ ['p', 'Page'], ['x', 'Exec Cmd'],
640
+ ['r', 'ruby'], ['e', "Edit"],
641
+ ['C-c', 'Cancel']
642
+ ]
643
+ elsif categ == :view
644
+ key_labels = [
645
+ ['c', 'Date'], ['m', 'Size'],
646
+ ['d', 'Delete'], ['v', 'View'],
647
+ ['C-c', 'Cancel']
648
+ ]
649
+ elsif categ == :dir
650
+ key_labels = [
651
+ ['o', 'open'], ['O', 'Open in right'],
652
+ ['d', 'Delete'], ['R', 'Del Recurse'],
653
+ ['t', 'tree'], ['p', 'Previous'],
654
+ ['b', 'Bookmark'], ['u', 'Unbookmark'],
655
+ ['l', 'List'], ['s', 'Save'],
656
+ ['C-c', 'Cancel']
657
+ ]
658
+ end
659
+ return key_labels
660
+ end
661
+ def get_key_labels_table
662
+ key_labels = [
663
+ ['M-n','NewRow'], ['M-d','DelRow'],
664
+ ['C-x','Select'], nil,
665
+ ['M-0', 'Top'], ['M-9', 'End'],
666
+ ['C-p', 'PgUp'], ['C-n', 'PgDn'],
667
+ ['M-Tab','Nxt Fld'], ['Tab','Nxt Col'],
668
+ ['+','Widen'], ['-','Narrow']
669
+ ]
670
+ return key_labels
671
+ end
672
+ def sort_popup
673
+ mform = RubyCurses::Form.new nil
674
+ field_list = []
675
+ r = 4
676
+ $radio = RubyCurses::Variable.new
677
+ rtextvalue = [:name, :ext, :size, :mtime, :atime]
678
+ ["Name", "Extension", "Size", "Modify Time", "Access Time" ].each_with_index do |rtext,ix|
679
+ field = RubyCurses::RadioButton.new mform do
680
+ variable $radio
681
+ text rtext
682
+ value rtextvalue[ix]
683
+ color 'black'
684
+ bgcolor 'white'
685
+ row r
686
+ col 5
687
+ end
688
+ field_list << field
689
+ r += 1
690
+ end
691
+ r = 4
692
+ ["Reverse", "case sensitive"].each do |cbtext|
693
+ field = RubyCurses::CheckBox.new mform do
694
+ text cbtext
695
+ name cbtext
696
+ color 'black'
697
+ bgcolor 'white'
698
+ row r
699
+ col 30
700
+ end
701
+ field_list << field
702
+ r += 1
703
+ end
704
+ mb = RubyCurses::MessageBox.new mform do
705
+ title "Sort Options"
706
+ button_type :ok_cancel
707
+ default_button 0
708
+ end
709
+ if mb.selected_index == 0
710
+ $log.debug " SORT POPUP #{$radio.value}"
711
+ #$log.debug " SORT POPUP #{mb.inspect}"
712
+ $log.debug " SORT POPUP #{mform.by_name["Reverse"].value}"
713
+ $log.debug " SORT POPUP #{mform.by_name["case sensitive"].value}"
714
+ end
715
+ return mb.selected_index, $radio.value, mform.by_name["Reverse"].value, mform.by_name["case sensitive"].value
716
+ end
717
+ def filter
718
+ f = get_string("Enter a filter pattern", 20, "*")
719
+ f = "*" if f.nil? or f == ""
720
+ @current_list.filter_pattern = f
721
+ @current_list.rescan
722
+ end
723
+ def grep_popup
724
+ last_regex = @last_regex || ""
725
+ last_pattern = @last_pattern || "*"
726
+ mform = RubyCurses::Form.new nil
727
+ r = 4
728
+ field = RubyCurses::Field.new mform do
729
+ name "regex"
730
+ row r
731
+ col 30
732
+ set_buffer last_regex
733
+ set_label Label.new @form, {'text' => 'Regex', 'col'=>5, :color=>'black',:bgcolor=>'white','mnemonic'=> 'R'}
734
+ end
735
+ r += 1
736
+ field = RubyCurses::Field.new mform do
737
+ name "filepattern"
738
+ row r
739
+ col 30
740
+ set_buffer last_pattern
741
+ set_label Label.new @form, {'text' => 'File Pattern','col'=>5, :color=>'black',:bgcolor=>'white','mnemonic'=> 'F'}
742
+ end
743
+ r += 1
744
+ ["Recurse", "case insensitive"].each do |cbtext|
745
+ field = RubyCurses::CheckBox.new mform do
746
+ text cbtext
747
+ name cbtext
748
+ color 'black'
749
+ bgcolor 'white'
750
+ row r
751
+ col 5
752
+ end
753
+ r += 1
754
+ end
755
+ mb = RubyCurses::MessageBox.new mform do
756
+ title "Grep Options"
757
+ button_type :ok_cancel
758
+ default_button 0
759
+ end
760
+ if mb.selected_index == 0
761
+ return if mform.by_name["regex"].getvalue()==""
762
+ @last_regex = mform.by_name["regex"].getvalue
763
+ inp = mform.by_name["regex"].getvalue
764
+ fp = mform.by_name["filepattern"].getvalue
765
+ @last_pattern = fp
766
+ flags=""
767
+ flags << " -i " if mform.by_name["case insensitive"].value==true
768
+ flags << " -R " if mform.by_name["Recurse"].value==true
769
+ cmd = "cd #{@current_list.cur_dir()};grep -l #{flags} #{inp} #{fp}"
770
+ filestr = %x[ #{cmd} ]
771
+ files = nil
772
+ files = filestr.split(/\n/) unless filestr.nil?
773
+ #view filestr
774
+ @current_list.title "grep #{inp}"
775
+ @current_list.populate files
776
+ end
777
+ return mb.selected_index, mform.by_name["regex"].getvalue, mform.by_name["filepattern"].getvalue, mform.by_name["Recurse"].value, mform.by_name["case insensitive"].value
778
+ end
779
+ def system_popup
780
+ deflt = @last_system || ""
781
+ options=["run in shell","view output","file explorer"]
782
+ #inp = get_string("Enter a system command", 30, deflt)
783
+ sel, inp, hash = get_string_with_options("Enter a system command", 40, deflt, {"radiobuttons" => options, "radio_default"=>@last_system_radio || options[0]})
784
+ if sel == 0
785
+ if !inp.nil?
786
+ @last_system = inp
787
+ @last_system_radio = hash["radio"]
788
+ case hash["radio"]
789
+ when options[0]
790
+ shell_out inp
791
+ when options[1]
792
+ filestr = %x[ #{inp} ]
793
+ view filestr
794
+ when options[2]
795
+ filestr = %x[ #{inp} ]
796
+ files = nil
797
+ files = filestr.split(/\n/) unless filestr.nil?
798
+ @current_list.title inp
799
+ @current_list.populate files
800
+ $log.debug " SYSTEM got #{files.size}, #{files.inspect}"
801
+ end
802
+ end
803
+ end
804
+ end
805
+ def popup
806
+ deflt = @last_regexp || ""
807
+ #sel, inp, hash = get_string_with_options("Enter a filter pattern", 20, "*", {"checkboxes" => ["case sensitive","reverse"], "checkbox_defaults"=>[true, false]})
808
+ sel, inp, hash = get_string_with_options("Enter a grep pattern", 20, deflt, {"checkboxes" => ["case insensitive","not-including"]})
809
+ if sel == 0
810
+ @last_regexp = inp
811
+ flags=""
812
+ flags << " -i " if hash["case insensitive"]==true
813
+ flags << " -v " if hash["not-including"]==true
814
+ cmd = "grep -l #{flags} #{inp} *"
815
+ filestr = %x[ #{cmd} ]
816
+ files = nil
817
+ files = filestr.split(/\n/) unless filestr.nil?
818
+ view filestr
819
+ end
820
+ $log.debug " POPUP: #{sel}: #{inp}, #{hash['case sensitive']}, #{hash['reverse']}"
821
+ end
822
+ def shell_out command
823
+ @window.hide
824
+ Ncurses.endwin
825
+ system command
826
+ Ncurses.refresh
827
+ #Ncurses.curs_set 0 # why ?
828
+ @window.show
829
+ end
830
+ end
831
+ if $0 == __FILE__
832
+ include RubyCurses
833
+ include RubyCurses::Utils
834
+
835
+ begin
836
+ # Initialize curses
837
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
838
+ $log = Logger.new("view.log")
839
+ $log.level = Logger::DEBUG
840
+
841
+ catch(:close) do
842
+ t = RFe.new
843
+ t.draw_screens
844
+ end
845
+ rescue => ex
846
+ ensure
847
+ VER::stop_ncurses
848
+ p ex if ex
849
+ p(ex.backtrace.join("\n")) if ex
850
+ $log.debug( ex) if ex
851
+ $log.debug(ex.backtrace.join("\n")) if ex
852
+ end
853
+ end