canis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/CHANGES +52 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +24 -0
  7. data/Rakefile +2 -0
  8. data/canis.gemspec +25 -0
  9. data/examples/alpmenu.rb +46 -0
  10. data/examples/app.sample +19 -0
  11. data/examples/appemail.rb +191 -0
  12. data/examples/atree.rb +105 -0
  13. data/examples/bline.rb +181 -0
  14. data/examples/common/devel.rb +319 -0
  15. data/examples/common/file.rb +93 -0
  16. data/examples/data/README.markdown +9 -0
  17. data/examples/data/brew.txt +38 -0
  18. data/examples/data/color.2 +37 -0
  19. data/examples/data/gemlist.txt +59 -0
  20. data/examples/data/lotr.txt +12 -0
  21. data/examples/data/ports.txt +136 -0
  22. data/examples/data/table.txt +37 -0
  23. data/examples/data/tasks.csv +88 -0
  24. data/examples/data/tasks.txt +27 -0
  25. data/examples/data/todo.txt +16 -0
  26. data/examples/data/todocsv.csv +28 -0
  27. data/examples/data/unix1.txt +21 -0
  28. data/examples/data/unix2.txt +11 -0
  29. data/examples/dbdemo.rb +506 -0
  30. data/examples/dirtree.rb +177 -0
  31. data/examples/newtabbedwindow.rb +100 -0
  32. data/examples/newtesttabp.rb +92 -0
  33. data/examples/tabular.rb +212 -0
  34. data/examples/tasks.rb +179 -0
  35. data/examples/term2.rb +88 -0
  36. data/examples/testbuttons.rb +307 -0
  37. data/examples/testcombo.rb +102 -0
  38. data/examples/testdb.rb +182 -0
  39. data/examples/testfields.rb +208 -0
  40. data/examples/testflowlayout.rb +43 -0
  41. data/examples/testkeypress.rb +98 -0
  42. data/examples/testlistbox.rb +187 -0
  43. data/examples/testlistbox1.rb +199 -0
  44. data/examples/testmessagebox.rb +144 -0
  45. data/examples/testprogress.rb +116 -0
  46. data/examples/testree.rb +107 -0
  47. data/examples/testsplitlayout.rb +53 -0
  48. data/examples/testsplitlayout1.rb +49 -0
  49. data/examples/teststacklayout.rb +48 -0
  50. data/examples/testwsshortcuts.rb +68 -0
  51. data/examples/testwsshortcuts2.rb +129 -0
  52. data/lib/canis.rb +16 -0
  53. data/lib/canis/core/docs/index.txt +104 -0
  54. data/lib/canis/core/docs/list.txt +16 -0
  55. data/lib/canis/core/docs/style_help.yml +34 -0
  56. data/lib/canis/core/docs/tabbedpane.txt +15 -0
  57. data/lib/canis/core/docs/table.txt +31 -0
  58. data/lib/canis/core/docs/textpad.txt +48 -0
  59. data/lib/canis/core/docs/tree.txt +23 -0
  60. data/lib/canis/core/include/.DS_Store +0 -0
  61. data/lib/canis/core/include/action.rb +83 -0
  62. data/lib/canis/core/include/actionmanager.rb +49 -0
  63. data/lib/canis/core/include/appmethods.rb +179 -0
  64. data/lib/canis/core/include/bordertitle.rb +49 -0
  65. data/lib/canis/core/include/canisparser.rb +100 -0
  66. data/lib/canis/core/include/colorparser.rb +437 -0
  67. data/lib/canis/core/include/defaultfilerenderer.rb +64 -0
  68. data/lib/canis/core/include/io.rb +320 -0
  69. data/lib/canis/core/include/layouts/SplitLayout.rb +161 -0
  70. data/lib/canis/core/include/layouts/abstractlayout.rb +213 -0
  71. data/lib/canis/core/include/layouts/flowlayout.rb +104 -0
  72. data/lib/canis/core/include/layouts/stacklayout.rb +109 -0
  73. data/lib/canis/core/include/listbindings.rb +89 -0
  74. data/lib/canis/core/include/listeditable.rb +319 -0
  75. data/lib/canis/core/include/listoperations.rb +61 -0
  76. data/lib/canis/core/include/listselectionmodel.rb +388 -0
  77. data/lib/canis/core/include/multibuffer.rb +173 -0
  78. data/lib/canis/core/include/ractionevent.rb +73 -0
  79. data/lib/canis/core/include/rchangeevent.rb +27 -0
  80. data/lib/canis/core/include/rhistory.rb +95 -0
  81. data/lib/canis/core/include/rinputdataevent.rb +47 -0
  82. data/lib/canis/core/include/textdocument.rb +111 -0
  83. data/lib/canis/core/include/vieditable.rb +175 -0
  84. data/lib/canis/core/include/widgetmenu.rb +66 -0
  85. data/lib/canis/core/system/colormap.rb +165 -0
  86. data/lib/canis/core/system/keydefs.rb +32 -0
  87. data/lib/canis/core/system/ncurses.rb +237 -0
  88. data/lib/canis/core/system/panel.rb +129 -0
  89. data/lib/canis/core/system/window.rb +1081 -0
  90. data/lib/canis/core/util/ansiparser.rb +119 -0
  91. data/lib/canis/core/util/app.rb +696 -0
  92. data/lib/canis/core/util/basestack.rb +412 -0
  93. data/lib/canis/core/util/defaultcolorparser.rb +84 -0
  94. data/lib/canis/core/util/extras/README +5 -0
  95. data/lib/canis/core/util/extras/bottomline.rb +1815 -0
  96. data/lib/canis/core/util/extras/padreader.rb +192 -0
  97. data/lib/canis/core/util/focusmanager.rb +31 -0
  98. data/lib/canis/core/util/helpmanager.rb +160 -0
  99. data/lib/canis/core/util/oldwidgetshortcuts.rb +304 -0
  100. data/lib/canis/core/util/promptmenu.rb +235 -0
  101. data/lib/canis/core/util/rcommandwindow.rb +933 -0
  102. data/lib/canis/core/util/rdialogs.rb +520 -0
  103. data/lib/canis/core/util/textutils.rb +74 -0
  104. data/lib/canis/core/util/viewer.rb +238 -0
  105. data/lib/canis/core/util/widgetshortcuts.rb +508 -0
  106. data/lib/canis/core/widgets/applicationheader.rb +103 -0
  107. data/lib/canis/core/widgets/box.rb +58 -0
  108. data/lib/canis/core/widgets/divider.rb +310 -0
  109. data/lib/canis/core/widgets/extras/README.md +12 -0
  110. data/lib/canis/core/widgets/extras/rtextarea.rb +960 -0
  111. data/lib/canis/core/widgets/extras/stackflow.rb +474 -0
  112. data/lib/canis/core/widgets/keylabelprinter.rb +194 -0
  113. data/lib/canis/core/widgets/listbox.rb +326 -0
  114. data/lib/canis/core/widgets/listfooter.rb +86 -0
  115. data/lib/canis/core/widgets/rcombo.rb +210 -0
  116. data/lib/canis/core/widgets/rcontainer.rb +415 -0
  117. data/lib/canis/core/widgets/rlink.rb +30 -0
  118. data/lib/canis/core/widgets/rmenu.rb +970 -0
  119. data/lib/canis/core/widgets/rmenulink.rb +30 -0
  120. data/lib/canis/core/widgets/rmessagebox.rb +400 -0
  121. data/lib/canis/core/widgets/rprogress.rb +118 -0
  122. data/lib/canis/core/widgets/rtabbedpane.rb +631 -0
  123. data/lib/canis/core/widgets/rtabbedwindow.rb +70 -0
  124. data/lib/canis/core/widgets/rwidget.rb +3634 -0
  125. data/lib/canis/core/widgets/scrollbar.rb +147 -0
  126. data/lib/canis/core/widgets/statusline.rb +113 -0
  127. data/lib/canis/core/widgets/table.rb +1072 -0
  128. data/lib/canis/core/widgets/tabular.rb +264 -0
  129. data/lib/canis/core/widgets/textpad.rb +1674 -0
  130. data/lib/canis/core/widgets/tree.rb +690 -0
  131. data/lib/canis/core/widgets/tree/treecellrenderer.rb +150 -0
  132. data/lib/canis/core/widgets/tree/treemodel.rb +432 -0
  133. data/lib/canis/version.rb +3 -0
  134. metadata +229 -0
@@ -0,0 +1,74 @@
1
+ # ----------------------------------------------------------------------------- #
2
+ # File: textutils.rb
3
+ # Description: contains some common string or Array<String> utilities
4
+ # that may be required by various parts of application.
5
+ # Author: j kepler http://github.com/mare-imbrium/canis/
6
+ # Date: 2014-05-22 - 11:11
7
+ # License: MIT
8
+ # Last update: 2014-05-26 19:40
9
+ # ----------------------------------------------------------------------------- #
10
+ # textutils.rb Copyright (C) 2012-2014 j kepler
11
+
12
+ module Canis
13
+ module TextUtils
14
+ # Convert an array of Strings that has help markup into tmux style
15
+ # which can then by parsed into native format by the tmux parser
16
+ # 'help' markup is very much like markdown, but a very restricted
17
+ # subset.
18
+ # Currently called only by help_manager in rwidgets.rb
19
+ # Some of these need to be fixed since they may not allow some
20
+ # characters, maybe too restrictive, or may match within a word. FIXME
21
+ def self.help2tmux arr
22
+ arr.each do |e|
23
+ # double sq brackets are like wiki links, to internal documents in same location
24
+ e.gsub! /\[\[(\S+)\]\]/, '#[style=link][\1]#[/end]'
25
+ # double asterisk needs to be more permissive and take a space FIXME
26
+ e.gsub! /\*\*(\S.*?\S)\*\*/, '#[style=strong]\1#[/end]'
27
+ # the next is wrong and could match two asteriks also
28
+ #e.gsub! /\*(\S[^\*]+\S)\*/, '#[style=em]\1#[/end]'
29
+ e.gsub! /\*(?!\s)([^\*]+)(?<!\s)\*/, '#[style=em]\1#[/end]'
30
+ e.gsub! /\|([^\|]+)\|/, '#[style=ul]\1#[/end]'
31
+ #e.gsub! /__(\w+)__/, '#[style=em]\1#[/end]'
32
+ #e.gsub! /_(\w+)_/, '#[style=em]\1#[/end]'
33
+ # next one is a bit too restrictive, but did not want a line
34
+ # full of underlines to get selected.
35
+ # __(?!_)(.+?)(?<!_)__/
36
+ #e.gsub! /__([a-zA-Z]+)__/, '#[style=strong]\1#[/end]'
37
+ # also avoid if a space or _ is after starting __ and before
38
+ # ending __
39
+ e.gsub! /__(?![_\s])(.+?)(?<![_\s])__/, '#[style=strong]\1#[/end]'
40
+ # make sure this does not match inside a word or code
41
+ # will not accept an underscore inside
42
+ e.gsub! /\b_([^_]+)_\b/, '#[style=em]\1#[/end]'
43
+ e.gsub! /`([^`]+)`/, '#[style=code]\1#[/end]'
44
+ # keys are mentioned with "<" and ">" surrounding
45
+ e.gsub! /(\<\S+\>)/, '#[style=key]\1#[/end]'
46
+ # headers start with "#"
47
+ e.sub! /^###\s*(.*)$/, '#[style=h3]\1#[/end]'
48
+ e.sub! /^## (.*)$/, '#[style=h2]\1#[/end]'
49
+ e.sub! /^# (.*)$/, '#[style=h1]\1#[/end]'
50
+ # line starting with "">" starts a white bold block as in vim's help. "<" ends block.
51
+ e.sub! /^\>$/, '#[style=wb]'
52
+ e.sub! /^\<$/, '#[/end]'
53
+ end
54
+ return arr
55
+ end
56
+ ##
57
+ # wraps text given max length, puts newlines in it.
58
+ # it does not take into account existing newlines
59
+ # Some classes have @maxlen or display_length which may be passed as the second parameter
60
+ def self.wrap_text(txt, max )
61
+ txt.gsub(/(.{1,#{max}})( +|$\n?)|(.{1,#{max}})/,
62
+ "\\1\\3\n")
63
+ end
64
+
65
+ # remove tabs, newlines and non-print chars from a string since these
66
+ # can mess display
67
+ def self.clean_string! content
68
+ content.chomp! # don't display newline
69
+ content.gsub!(/[\t\n]/, ' ') # don't display tab
70
+ content.gsub!(/[^[:print:]]/, '') # don't display non print characters
71
+ content
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,238 @@
1
+ require 'canis/core/widgets/textpad'
2
+ require 'canis/core/widgets/applicationheader'
3
+ require 'fileutils'
4
+
5
+ # A file or array viewer.
6
+ #
7
+ # CHANGES
8
+ # - 2014-04-09 - 00:58 changed textview to textpad
9
+ # Can be used for print_help_page
10
+ # SUGGESTIONS WELCOME.
11
+ # NOTE: since this is not a proper class / object, it is being hacked to pieces
12
+ # We need to either make this a proper class, or else make another one with a class,
13
+ # and use this for simple purposes only.
14
+
15
+ module Canis
16
+ # a data viewer for viewing some text or filecontents
17
+ # view filename, :close_key => KEY_ENTER
18
+ # send data in an array
19
+ # view Array, :close_key => KEY_ENTER, :layout => [23,80,0,0] (ht, wid, top, left)
20
+ # when passing layout reserve 4 rows for window and border. So for 2 lines of text
21
+ # give 6 rows.
22
+ class Viewer
23
+ # @param filename as string or content as array
24
+ # @yield textview object for further configuration before display
25
+ def self.view what, config={}, &block #:yield: textview
26
+ case what
27
+ when String # we have a path
28
+ content = _get_contents(what)
29
+ when Array
30
+ content = what
31
+ when TextDocument
32
+ $log.debug " setting content to textdocument #{what.options.keys} "
33
+ content = what
34
+ #content = what.text
35
+ #config[:content_type] = what.content_type
36
+ else
37
+ raise ArgumentError, "Viewer: Expecting Filename or Contents (array), but got #{what.class} "
38
+ end
39
+ wt = 0 # top margin
40
+ wl = 0 # left margin
41
+ wh = Ncurses.LINES-wt # height, goes to bottom of screen
42
+ ww = Ncurses.COLS-wl # width, goes to right end
43
+ layout = { :height => wh, :width => ww, :top => wt, :left => wl }
44
+ if config.has_key? :layout
45
+ layout = config[:layout]
46
+ case layout
47
+ when Array
48
+ #wt, wl, wh, ww = layout
49
+ # 2014-04-27 - 11:22 changed to the same order as window, otherwise confusion and errors
50
+ wh, ww, wt, wl = layout
51
+ layout = { :height => wh, :width => ww, :top => wt, :left => wl }
52
+ when Hash
53
+ # okay
54
+ end
55
+ end
56
+
57
+ fp = config[:title] || ""
58
+ pf = config.fetch(:print_footer, true)
59
+ ta = config.fetch(:title_attrib, 'bold')
60
+ fa = config.fetch(:footer_attrib, 'bold')
61
+ wbg = config.fetch(:window_bgcolor, nil)
62
+ b_ah = config[:app_header]
63
+ type = config[:content_type]
64
+
65
+ v_window = Canis::Window.new(layout)
66
+ v_form = Canis::Form.new v_window
67
+ v_window.name = "Viewer"
68
+ if wbg
69
+ v_window.wbkgd(Ncurses.COLOR_PAIR(wbg)); # does not work on xterm-256color
70
+ end
71
+ # I am placing this in globals since an alert on top will refresh the lower windows and this is quite large.
72
+ $global_windows << v_window
73
+ colors = Ncurses.COLORS
74
+ back = :blue
75
+ back = 235 if colors >= 256
76
+ blue_white = get_color($datacolor, :white, back)
77
+
78
+ tprow = 0
79
+ ah = nil
80
+ if b_ah
81
+ ah = ApplicationHeader.new v_form, "", :text_center => fp
82
+ tprow += 1
83
+ end
84
+
85
+ #blue_white = Canis::Utils.get_color($datacolor, :white, 235)
86
+ textview = TextPad.new v_form do
87
+ name "Viewer"
88
+ row tprow
89
+ col 0
90
+ width ww
91
+ height wh-tprow # earlier 2 but seems to be leaving space.
92
+ title fp
93
+ title_attrib ta
94
+ print_footer pf
95
+ footer_attrib fa
96
+ #border_attrib :reverse
97
+ border_color blue_white
98
+ end
99
+ # why multibuffers -- since used in help
100
+ require 'canis/core/include/multibuffer'
101
+ textview.extend(Canis::MultiBuffers)
102
+
103
+ t = textview
104
+ t.bind_key(Ncurses::KEY_F5, 'maximize window '){ f = t.form.window;
105
+ f.resize_with([FFI::NCurses.LINES-0, Ncurses.COLS, 0,0]);
106
+ #f.resize_with([0,0, 0,0]);
107
+ t.height = Ncurses.LINES - t.row - 0
108
+ }
109
+ t.bind_key(Ncurses::KEY_F6, 'restore window ', layout){ |l,m, n|
110
+ # l was DefaultKeyHandler, m was string, n was Hash
111
+ f = t.form.window;
112
+ #$log.debug " F6 ARG is #{m}, #{n}"
113
+ f.hide; # need to hide since earlier window was larger.
114
+ f.resize_with(n);
115
+ #f.resize_with([0,0, 0,0]);
116
+ t.height = f.height - t.row - 0
117
+ f.show
118
+ }
119
+ t.bind_key(?\C-\], "open file under cursor") {
120
+ eve = t.text_action_event
121
+ file = eve.word_under_cursor.strip
122
+ if File.exists? file
123
+ t.add_content file
124
+ t.buffer_last
125
+ end
126
+ }
127
+
128
+ =begin
129
+ # just for fun -- seeing how we can move window around
130
+ # these are working, but can cause a padrefresh error. we should check for bounds or something.
131
+ #
132
+ t.bind_key('<', 'move window left'){ f = t.form.window; c = f.left - 1; f.hide; f.mvwin(f.top, c); f.show;
133
+ f.set_layout([f.height, f.width, f.top, c]);
134
+ }
135
+ t.bind_key('>', 'move window right'){ f = t.form.window; c = f.left + 1; f.hide; f.mvwin(f.top, c);
136
+ f.set_layout([f.height, f.width, f.top, c]); f.show;
137
+ }
138
+ t.bind_key('^', 'move window up'){ f = t.form.window; c = f.top - 1 ; f.hide; f.mvwin(c, f.left);
139
+ f.set_layout([f.height, f.width, c, f.left]) ; f.show;
140
+ }
141
+ t.bind_key('V', 'move window down'){ f = t.form.window; c = f.top + 1 ; f.hide; f.mvwin(c, f.left);
142
+ f.set_layout([f.height, f.width, c, f.left]); f.show;
143
+ }
144
+ =end
145
+ items = {:header => ah}
146
+ close_keys = [ config[:close_key] , 3 , ?q.getbyte(0), 27 , 2727 ]
147
+ begin
148
+ # the next can also be used to use formatted_text(text, :ansi)
149
+ # yielding textview so you may further configure or bind keys or events
150
+ if block_given?
151
+ if block.arity > 0
152
+ yield textview, items
153
+ else
154
+ textview.instance_eval(&block)
155
+ end
156
+ end
157
+ # multibuffer requires add_co after set_co
158
+ # We are using in help, therefore we need multibuffers.
159
+ #textview.set_content content, :content_type => type #, :stylesheet => t.stylesheet
160
+ # i need to do this so it is available when moving around
161
+ # buffers
162
+ # but this means that pressing next will again show the same
163
+ # buffer.
164
+ textview.add_content content, :content_type => type #, :stylesheet => t.stylesheet
165
+ textview.buffer_last
166
+ #yield textview if block_given?
167
+ v_form.repaint
168
+ v_window.wrefresh
169
+ Ncurses::Panel.update_panels
170
+ retval = ""
171
+ # allow closing using q and Ctrl-q in addition to any key specified
172
+ # user should not need to specify key, since that becomes inconsistent across usages
173
+ # NOTE: 2727 is no longer operational, so putting just ESC
174
+ while((ch = v_window.getchar()) != ?\C-q.getbyte(0) )
175
+ $log.debug " VIEWER got key #{ch} , close key is #{config[:close_key]} "
176
+ retval = textview.current_value() if ch == config[:close_key]
177
+ break if close_keys.include? ch
178
+ # if you've asked for ENTER then i also check for 10 and 13
179
+ retval = textview.current_value() if (ch == 10 || ch == 13) && config[:close_key] == KEY_ENTER
180
+ break if (ch == 10 || ch == 13) && config[:close_key] == KEY_ENTER
181
+ $log.debug " 1 VIEWER got key #{ch} "
182
+ v_form.handle_key ch
183
+ v_form.repaint
184
+ end
185
+ rescue => err
186
+ $log.error " VIEWER ERROR #{err} "
187
+ $log.debug(err.backtrace.join("\n"))
188
+ alert "#{err}"
189
+ #textdialog ["Error in viewer: #{err} ", *err.backtrace], :title => "Exception"
190
+ ensure
191
+ v_window.destroy if !v_window.nil?
192
+ end
193
+ return retval
194
+ end
195
+ private
196
+ def self._get_contents fp
197
+ raise "File #{fp} not readable" unless File.readable? fp
198
+ return Dir.new(fp).entries if File.directory? fp
199
+ case File.extname(fp)
200
+ when '.tgz','.gz'
201
+ cmd = "tar -ztvf #{fp}"
202
+ content = %x[#{cmd}]
203
+ when '.zip'
204
+ cmd = "unzip -l #{fp}"
205
+ content = %x[#{cmd}]
206
+ when '.jar', '.gem'
207
+ cmd = "tar -tvf #{fp}"
208
+ content = %x[#{cmd}]
209
+ when '.png', '.out','.jpg', '.gif','.pdf'
210
+ content = "File #{fp} not displayable"
211
+ when '.sqlite'
212
+ cmd = "sqlite3 #{fp} 'select name from sqlite_master;'"
213
+ content = %x[#{cmd}]
214
+ else
215
+ content = File.open(fp,"r").readlines
216
+ end
217
+ end
218
+ end # class
219
+
220
+ end # module
221
+ if __FILE__ == $PROGRAM_NAME
222
+ require 'canis/core/util/app'
223
+
224
+ App.new do
225
+ header = app_header "canis 1.2.0", :text_center => "Viewer Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
226
+ message "Press F1 to exit from here"
227
+
228
+ Canis::Viewer.view(ARGV[0] || $0, :close_key => KEY_ENTER, :title => "Enter to close") do |t|
229
+ # you may configure textview further here.
230
+ #t.suppress_borders true
231
+ #t.color = :black
232
+ #t.bgcolor = :white
233
+ # or
234
+ #t.attr = :reverse
235
+ end
236
+
237
+ end # app
238
+ end
@@ -0,0 +1,508 @@
1
+ # ------------------------------------------------------------ #
2
+ # File: widgetshortcuts.rb
3
+ # Description: A common module for shortcuts to create widgets
4
+ # Also, stacks and flows objects
5
+ # Author: jkepler http://github.com/mare-imbrium/canis/
6
+ # Date: 05.11.11 - 15:13
7
+ # Last update: 2014-07-10 00:40
8
+ #
9
+ # I hope this slowly does not become an unmaintainable maze like vimsplit
10
+ #
11
+ # "Simplicity hinges as much on cutting nonessential features as on adding helpful ones."
12
+ # - Walter Bender
13
+ #
14
+ # == TODO
15
+ # add blocks that make sense like in app
16
+ # - what if user does not want form attached - app uses useform ot
17
+ # to check for this, if current_object don't add form
18
+ #
19
+ # - usage of _position inside means these shortcuts cannot be reused
20
+ # with other positioning systems, we'll be cut-pasting forever
21
+ #
22
+ # == CHANGES
23
+ # ------------------------------------------------------------ #
24
+ #
25
+
26
+ # what is the real purpose of the shortcuts, is it to avoid putting nil
27
+ # for form there if not required.
28
+ # Or is it positioning, such as in a stack. or just a method ?
29
+ #require 'canis/core/widgets/rlist'
30
+ ## trying out new list based on textpad 2014-04-07 - 00:02 CANIS
31
+ module Canis
32
+ module WidgetShortcuts
33
+ class Ws
34
+ attr_reader :config
35
+ def initialize config={}
36
+ @config = config
37
+ end
38
+ def [](sym)
39
+ @config[sym]
40
+ end
41
+ def []=(sym, val)
42
+ @config[sym] = val
43
+ end
44
+ end
45
+ class WsStack < Ws; end
46
+ class WsFlow < Ws; end
47
+ def widget_shortcuts_init
48
+ @_ws_app_row = @_ws_app_col = 0
49
+ #@_ws_active = []
50
+ @_ws_active = nil # so we can use shortcuts if no stack used
51
+ @_ws_components = []
52
+ @variables = {}
53
+ end
54
+ # --- shortcuts {{{
55
+ def blank
56
+ label :text => ""
57
+ end
58
+ def line config={}
59
+ #horizontal line TODO
60
+ #row = config[:row] || @app_row
61
+ #width = config[:width] || 20
62
+ #_position config
63
+ #col = config[:col] || 1
64
+ #@color_pair = config[:color_pair] || $datacolor
65
+ #@attrib = config[:attrib] || Ncurses::A_NORMAL
66
+ #@window.attron(Ncurses.COLOR_PAIR(@color_pair) | @attrib)
67
+ #@window.mvwhline( row, col, FFI::NCurses::ACS_HLINE, width)
68
+ #@window.attron(Ncurses.COLOR_PAIR(@color_pair) | @attrib)
69
+ end
70
+ def radio config={}, &block
71
+ a = config[:group]
72
+ # should we not check for a nil
73
+ if @variables.has_key? a
74
+ v = @variables[a]
75
+ else
76
+ v = Variable.new
77
+ @variables[a] = v
78
+ end
79
+ config[:variable] = v
80
+ config.delete(:group)
81
+ w = RadioButton.new nil, config #, &block
82
+ _position w
83
+ if block
84
+ w.bind(:PRESS, &block)
85
+ end
86
+ return w
87
+ end
88
+ # create a shortcut for a class
89
+ # path is path of file to use in require starting with canis
90
+ # klass is name of class to instantiate
91
+ def self.def_widget(path, klass, short=nil)
92
+ p=""
93
+ if path
94
+ p="require \"#{path}\""
95
+ end
96
+ short ||= klass.to_s.downcase
97
+ eval %{
98
+ def #{short}(config={}, &block)
99
+ if config.is_a? String
100
+ _s = config
101
+ config = {}
102
+ config[:text] = _s
103
+ end
104
+ #{p}
105
+ w = #{klass}.new nil, config
106
+ _position w
107
+ w.command &block if block_given?
108
+ return w
109
+ end
110
+ }
111
+ end
112
+ def_widget "canis/core/widgets/rprogress", "Progress"
113
+ def_widget "canis/core/widgets/scrollbar", "Scrollbar"
114
+ def_widget nil, "Label"
115
+ #def_widget nil, "Field"
116
+ def_widget nil, "LabeledField", 'field'
117
+ def_widget nil, :CheckBox, 'check'
118
+ def_widget nil, :Button
119
+ def_widget nil, :ToggleButton, 'toggle'
120
+ def menubar &block
121
+ require 'canis/core/widgets/rmenu'
122
+ Canis::MenuBar.new &block
123
+ end
124
+ # add a standard application header
125
+ # == Example
126
+ # header = app_header "canis ", :text_center => "Browser Demo", :text_right =>"New Improved!",
127
+ # :color => :black, :bgcolor => :white, :attr => :bold
128
+ def app_header title, config={}, &block
129
+ require 'canis/core/widgets/applicationheader'
130
+ header = ApplicationHeader.new @form, title, config, &block
131
+ end
132
+ # editable text area
133
+ # use only for simple cases, since this is not fully tested
134
+ def textarea config={}, &block
135
+ require 'canis/core/widgets/extras/rtextarea'
136
+ # TODO confirm events many more
137
+ events = [ :CHANGE, :LEAVE, :ENTER ]
138
+ block_event = events[0]
139
+ #_process_args args, config, block_event, events
140
+ #config[:width] = config[:display_length] unless config.has_key? :width
141
+ # if no width given, expand to flows width
142
+ #config[:width] ||= @stack.last.width if @stack.last
143
+ useform = nil
144
+ #useform = @form if @current_object.empty?
145
+ w = TextArea.new useform, config
146
+ w.width = :expand unless w.width
147
+ w.height ||= :expand # TODO This has to come before other in stack next one will overwrite.
148
+ _position(w)
149
+ w.height ||= 8 # TODO
150
+ # need to expand to stack's width or flows itemwidth if given
151
+ if block
152
+ w.bind(block_event, &block)
153
+ end
154
+ return w
155
+ end
156
+ def textpad config={}, &block
157
+ events = [ :LEAVE, :ENTER ]
158
+ block_event = events[0]
159
+ #_process_args args, config, block_event, events
160
+ #config[:width] = config[:display_length] unless config.has_key? :width
161
+ # if no width given, expand to flows width
162
+ #config[:width] ||= @stack.last.width if @stack.last
163
+ useform = nil
164
+ #useform = @form if @current_object.empty?
165
+ #w = TextView.new useform, config
166
+ w = TextPad.new useform, config
167
+ w.width = :expand unless w.width
168
+ w.height ||= :expand # TODO This has to come before other in stack next one will overwrite.
169
+ _position(w)
170
+ # need to expand to stack's width or flows itemwidth if given
171
+ if block
172
+ w.bind(block_event, &block)
173
+ end
174
+ return w
175
+ end
176
+ # deprecate and move textview soon TODO
177
+ alias :textview :textpad
178
+ def listbox config={}, &block
179
+ require 'canis/core/widgets/listbox'
180
+ events = [ :PRESS, :ENTER_ROW, :LEAVE, :ENTER ]
181
+ block_event = events[0]
182
+ #_process_args args, config, block_event, events
183
+ #config[:width] = config[:display_length] unless config.has_key? :width
184
+ # if no width given, expand to flows width
185
+ #config[:width] ||= @stack.last.width if @stack.last
186
+ useform = nil
187
+ #useform = @form if @current_object.empty?
188
+ #w = List.new useform, config
189
+ w = Listbox.new useform, config
190
+ w.width = :expand unless w.width
191
+ w.height ||= :expand # TODO We may need to push this before _position so it can be accounted for in stack
192
+ _position(w)
193
+ # need to expand to stack's width or flows itemwidth if given
194
+ if block
195
+ w.bind(block_event, &block)
196
+ end
197
+ return w
198
+ end
199
+ # prints pine-like key labels
200
+ def dock labels, config={}, &block
201
+ require 'canis/core/widgets/keylabelprinter'
202
+ klp = Canis::KeyLabelPrinter.new @form, labels, config, &block
203
+ end
204
+
205
+ #
206
+ # prints a status line at bottom where mode's statuses et can be reflected
207
+ def status_line config={}, &block
208
+ require 'canis/core/widgets/statusline'
209
+ sl = Canis::StatusLine.new @form, config, &block
210
+ end
211
+
212
+ def link config={}, &block
213
+ if config.is_a? String
214
+ _s = config
215
+ config = {}
216
+ config[:text] = _s
217
+ end
218
+ require 'canis/core/widgets/rlink'
219
+ events = [ :PRESS, :LEAVE, :ENTER ]
220
+ block_event = :PRESS
221
+ config[:highlight_color] = "yellow"
222
+ config[:highlight_bgcolor] = "red"
223
+ toggle = Link.new nil, config
224
+ _position(toggle)
225
+ if block
226
+ toggle.bind(block_event, toggle, &block)
227
+ end
228
+ return toggle
229
+ end
230
+ def menulink config={}, &block
231
+ if config.is_a? String
232
+ _s = config
233
+ config = {}
234
+ config[:text] = _s
235
+ end
236
+ require 'canis/core/widgets/rmenulink'
237
+ events = [ :PRESS, :LEAVE, :ENTER ]
238
+ block_event = :PRESS
239
+ config[:highlight_color] = "yellow"
240
+ config[:highlight_bgcolor] = "red"
241
+ #config[:hotkey] = true
242
+ w = MenuLink.new nil, config
243
+ _position(w)
244
+ if block
245
+ w.bind(block_event, w, &block)
246
+ end
247
+ return w
248
+ end
249
+ def tree config={}, &block
250
+ #require 'canis/core/widgets/rtree'
251
+ require 'canis/core/widgets/tree'
252
+ events = [:TREE_WILL_EXPAND_EVENT, :TREE_EXPANDED_EVENT, :TREE_SELECTION_EVENT, :PROPERTY_CHANGE, :LEAVE, :ENTER , :ENTER_ROW, :TREE_COLLAPSED_EVENT, :TREE_WILL_EXPAND_EVENT]
253
+ block_event = :TREE_WILL_EXPAND_EVENT
254
+ #config[:height] ||= 10
255
+ # if no width given, expand to flows width
256
+ useform = nil
257
+ #useform = @form if @current_object.empty?
258
+ w = Tree.new useform, config, &block
259
+ w.width ||= :expand
260
+ w.height ||= :expand # TODO This has to come before other in stack next one will overwrite.
261
+ _position w
262
+ # calling the block here was causing a problem since a tree may define root etc in the block
263
+ # containers like to define elements in a block and not have an event called by default
264
+ return w
265
+ end
266
+ # creates a simple readonly table, that allows users to click on rows
267
+ # and also on the header. Header clicking is for column-sorting.
268
+ def table config={}, &block
269
+ #def tabular_widget config={}, &block
270
+ require 'canis/core/widgets/table'
271
+ events = [:PROPERTY_CHANGE, :LEAVE, :ENTER, :CHANGE, :ENTER_ROW, :PRESS ]
272
+ block_event = nil
273
+ # if no width given, expand to stack width
274
+ #config.delete :title
275
+ useform = nil
276
+
277
+ w = Table.new useform, config # NO BLOCK GIVEN
278
+ w.width ||= :expand
279
+ w.height ||= :expand # TODO This has to come before other in stack next one will overwrite.
280
+ _position(w)
281
+ if block_given?
282
+ #@current_object << w
283
+ yield_or_eval &block
284
+ #@current_object.pop
285
+ end
286
+ return w
287
+ end
288
+
289
+ # --- }}}
290
+ def _position w
291
+ if @_ws_active.nil? || @_ws_active.empty?
292
+ # no stack or flow, this is independent usage, or else we are outside stacks and flows
293
+ #
294
+ # this is outside any stack or flow, so we do the minimal
295
+ # user should specify row and col
296
+ w.row ||= 0
297
+ w.col ||= 0
298
+ #$log.debug "XXX: LABEL #{w.row} , #{w.col} "
299
+ w.set_form @form if @form # temporary,, only set if not inside an object FIXME
300
+ if w.width == :expand # calculate from current col, not 0 FIXME
301
+ w.width = FFI::NCurses.COLS-w.col # or take windows width since this could be in a message box
302
+ end
303
+ if w.height == :expand
304
+ # take from current row, and not zero FIXME
305
+ w.height = FFI::NCurses.LINES-w.row # or take windows width since this could be in a message box
306
+ end
307
+ return
308
+
309
+ end
310
+ # -------------------------- there is a stack or flow -------------------- #
311
+ #
312
+ cur = @_ws_active.last
313
+ unless cur
314
+ raise "This should have been handled previously.Somethings wrong, check/untested"
315
+ end
316
+ r = cur[:row] || 0
317
+ c = cur[:col] || 0
318
+ w.row = r
319
+ w.col = c
320
+ # if flow then take flows height, else use dummy value
321
+ if w.height_pc
322
+ w.height = ( (cur[:height] * w.height_pc.to_i)/100).floor
323
+ end
324
+ if w.height == :expand
325
+ if cur.is_a? WsFlow
326
+ w.height = cur[:height] || 8 #or raise "height not known for flow"
327
+ else
328
+ w.height = cur[:item_height] || 8 #or raise "height not known for flow"
329
+ end
330
+ #alert "setting ht to #{w.height}, #{cur[:height]} , for #{cur} "
331
+ end
332
+ if w.width == :expand
333
+ if cur.is_a? WsFlow
334
+ if cur[:item_width]
335
+ w.width = cur[:item_width] #or raise "item_Width not known for flow #{cur.class}, #{cur[:item_width]}, #{cur[:width]} , #{w.width_pc} "
336
+ elsif w.width_pc
337
+ #w.width = w.width_pc * cur[:width]
338
+ w.width = (cur[:width] * (w.width_pc.to_i * 0.01)).floor
339
+ else
340
+ w.width = cur[:width]
341
+ end
342
+ raise "width could not be calculated. i need flow width and item width_pc" if w.width == :expand
343
+ else
344
+ w.width = cur[:width] or raise "Width not known for stack #{cur.class}, #{cur[:width]} "
345
+ end
346
+ end
347
+ if cur.is_a? WsStack
348
+ r += w.height || 1 # NOTE, we need to have height for this purpose defined BEFORE calling for list/text
349
+ cur[:row] = r
350
+ else
351
+ wid = cur[:item_width] || w.width || 10
352
+ c += wid + 1
353
+ cur[:col] = c
354
+ end
355
+ #alert "set width to #{w.width} ,cur: #{cur[:width]} ,iw: #{cur[:item_width]} "
356
+ if cur.is_a? WsFlow
357
+ unless w.height
358
+ w.height = cur[:height] #or raise "Height not known for flow"
359
+ end
360
+ end
361
+ w.color ||= cur[:color]
362
+ w.bgcolor ||= cur[:bgcolor]
363
+ w.set_form @form if @form # temporary
364
+ @_ws_components << w
365
+ cur[:components] << w
366
+ end
367
+ # make it as simple as possible, don't try to be intelligent or
368
+ # clever, put as much on the user
369
+ def stack config={}, &block
370
+ s = WsStack.new config
371
+ @_ws_active ||= []
372
+ _configure s
373
+ @_ws_active << s
374
+ yield_or_eval &block if block_given?
375
+ @_ws_active.pop
376
+
377
+ # ---- stack is finished now
378
+ last = @_ws_active.last
379
+ if last
380
+ case last
381
+ when WsStack
382
+ when WsFlow
383
+ last[:col] += last[:item_width] || 0
384
+ # this tries to set height of outer flow based on highest row
385
+ # printed, however that does not account for height of object,
386
+ # so user should give a height to the flow.
387
+ last[:height] = s[:row] if s[:row] > (last[:height]||0)
388
+ $log.debug "XXX: STACK setting col to #{s[:col]} "
389
+ end
390
+ end
391
+
392
+ end
393
+ #
394
+ # item_width - width to use per item
395
+ # but the item width may apply to stacks inside not to items
396
+ def flow config={}, &block
397
+ s = WsFlow.new config
398
+ @_ws_active ||= []
399
+ _configure s
400
+ @_ws_active << s
401
+ yield_or_eval &block if block_given?
402
+ @_ws_active.pop
403
+ last = @_ws_active.last
404
+ if last
405
+ case last
406
+ when WsStack
407
+ if s[:height]
408
+ last[:row] += s[:height]
409
+ else
410
+ #last[:row] += last[:highest_row]
411
+ last[:row] += 1
412
+ end
413
+ when WsFlow
414
+ last[:col] += last[:item_width] || 0
415
+ end
416
+ end
417
+ end
418
+ # flow and stack could have a border option
419
+ # NOTE: box takes one row below too, so :expand overwrites that line
420
+ def box config={}, &block
421
+ require 'canis/core/widgets/box'
422
+ # take current stacks row and col
423
+ # advance row by one and col by one
424
+ # at end note row and advance by one
425
+ # draw a box around using these coordinates. width should be
426
+ # provided unless we have item width or something.
427
+ @_ws_active ||= []
428
+ last = @_ws_active.last
429
+ if last
430
+ r = last[:row]
431
+ c = last[:col]
432
+ config[:row] = r
433
+ config[:col] = c
434
+ last[:row] += config[:margin_top] || 1
435
+ last[:col] += config[:margin_left] || 1
436
+ _box = Box.new @form, config # needs to be created first or will overwrite area after others painted
437
+ yield_or_eval &block if block_given?
438
+ # FIXME last[height] needs to account for row
439
+ h = config[:height] || last[:height] || (last[:row] - r)
440
+ h = 2 if h < 2
441
+ w = config[:width] || last[:width] || 15 # tmp
442
+ case last
443
+ when WsFlow
444
+ w = last[:col]
445
+ when WsStack
446
+ #h += 1
447
+ end
448
+ config[:row] = r
449
+ config[:col] = c
450
+ config[:height] = h
451
+ config[:width] = w
452
+ _box.row r
453
+ _box.col c
454
+ _box.height h
455
+ _box.width w
456
+ last[:row] += 1
457
+ last[:col] += 1 # ??? XXX if flow we need to increment properly or not ?
458
+ end
459
+ end
460
+
461
+ # This configures a stack or flow not the objects inside
462
+ def _configure s
463
+ s[:row] ||= 0
464
+ s[:col] ||= 0
465
+ s[:row] += (s[:margin_top] || 0)
466
+ s[:col] += (s[:margin_left] || 0)
467
+ s[:width] = FFI::NCurses.COLS-s[:col] if s[:width] == :expand
468
+ s[:height] = FFI::NCurses.LINES-s[:row] if s[:height] == :expand # 2011-11-30
469
+ last = @_ws_active.last
470
+ if last
471
+ if s[:width_pc]
472
+ if last.is_a? WsStack
473
+ s[:width] = (last[:width] * (s[:width_pc].to_i * 0.01)).floor
474
+ else
475
+ # i think this width is picked up by next stack in this flow
476
+ last[:item_width] = (last[:width] * (s[:width_pc].to_i* 0.01)).floor
477
+ end
478
+ end
479
+ if s[:height_pc]
480
+ if last.is_a? WsFlow
481
+ s[:height] = ( (last[:height] * s[:height_pc].to_i)/100).floor
482
+ else
483
+ # this works only for flows within stacks not for an object unless
484
+ # you put a single object in a flow
485
+ s[:item_height] = ( (last[:height] * s[:height_pc].to_i)/100).floor
486
+ end
487
+ #alert "item height set as #{s[:height]} for #{s} "
488
+ end
489
+ if last.is_a? WsStack
490
+ s[:row] += (last[:row] || 0)
491
+ s[:col] += (last[:col] || 0)
492
+ else
493
+ s[:row] += (last[:row] || 0)
494
+ s[:col] += (last[:col] || 0) # we are updating with item_width as each st finishes
495
+ s[:width] ||= last[:item_width] #
496
+ end
497
+ else
498
+ # this should be outer most flow or stack, if nothing mentioned
499
+ # trying this out
500
+ s[:width] ||= :expand
501
+ s[:height] ||= :expand
502
+ s[:width] = FFI::NCurses.COLS-s[:col] if s[:width] == :expand
503
+ s[:height] = FFI::NCurses.LINES-s[:row] if s[:height] == :expand # 2011-11-30
504
+ end
505
+ s[:components] = []
506
+ end
507
+ end
508
+ end