canis 0.0.4

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 (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