viewworkbook 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ #encoding: UTF-8
2
+
3
+ =begin
4
+ /***************************************************************************
5
+ * Copyright © 2014-2017, Michael Uplawski <michael.uplawski@uplawski.eu>*
6
+ * *
7
+ * This program is free software; you can redistribute it and/or modify *
8
+ * it under the terms of the GNU General Public License as published by *
9
+ * the Free Software Foundation; either version 3 of the License, or *
10
+ * (at your option) any later version. *
11
+ * *
12
+ * This program is distributed in the hope that it will be useful, *
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
+ * GNU General Public License for more details. *
16
+ * *
17
+ * You should have received a copy of the GNU General Public License *
18
+ * along with this program; if not, write to the *
19
+ * Free Software Foundation, Inc., *
20
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21
+ ***************************************************************************/
22
+ =end
23
+
24
+ require 'io/wait'
25
+ require 'io/console'
26
+ require_relative 'action'
27
+ require_relative 'logging'
28
+ require_relative 'user_input'
29
+
30
+ # Shows options that the user can choose from.
31
+ # Options may be associated with sub-menus.
32
+
33
+ class Menu
34
+ # include Logging
35
+ self.extend(Logging)
36
+ @@log = self.init_logger(STDOUT)
37
+
38
+ attr_accessor :key, :name
39
+
40
+ class MenuError < StandardError
41
+ end
42
+
43
+ @@global_elements = []
44
+ @@hidden_elements = []
45
+
46
+ def initialize(options = {})
47
+ @log = @@log
48
+ @elements = []
49
+ if(options && !options.respond_to?(:to_hash) )
50
+ raise MenuError.new('Arguments to menu.new must be hash-pairs')
51
+ end
52
+ @name = options[:name] if options[:name]
53
+ @key = options[:key] if options[:key]
54
+ end
55
+
56
+ def call(*args)
57
+ show()
58
+ end
59
+
60
+ def add(element, pos = nil)
61
+ if(element.respond_to?(:call))
62
+ if(pos)
63
+ @elements.insert(pos, element)
64
+ else
65
+ @elements << element
66
+ end
67
+ # make available the same element in any sub-menus.
68
+ if(element.respond_to?(:global) && element.global)
69
+ @@global_elements << element
70
+ end
71
+ # hidden elements
72
+ if(element.respond_to?(:hidden) && element.hidden)
73
+ @@hidden_elements << element
74
+ end
75
+ else
76
+ raise MenuError("{#element.to_s} cannot be used as action or sub-menu: missing method 'call()'}")
77
+ end
78
+ end
79
+
80
+
81
+ private
82
+ def show()
83
+ @elements.each do |ele|
84
+ name = ele.name.downcase
85
+ key = ele.key.downcase
86
+ print name.dup << " (#{key}) * " if !@@hidden_elements.include?(ele)
87
+ end
88
+ puts
89
+ ele = nil
90
+ until ele
91
+ key = "%c" %wait_for_user
92
+ =begin
93
+ if "\e" == key
94
+ break
95
+ end
96
+ =end
97
+ ele = @elements.detect{ |e| key == e.key }
98
+ ele ||= @@global_elements.detect{ |e| key == e.key }
99
+ end
100
+ ele.call if ele
101
+ end
102
+
103
+ public
104
+ alias :add_action :add
105
+ alias :add_sub_menu :add
106
+ alias :add_menu :add
107
+ end
@@ -0,0 +1,82 @@
1
+ #encoding: UTF-8
2
+
3
+ =begin
4
+ /***************************************************************************
5
+ * Copyright ©2016-2016, Michael Uplawski <michael.uplawski@uplawski.eu> *
6
+ * *
7
+ * This program is free software; you can redistribute it and/or modify *
8
+ * it under the terms of the GNU General Public License as published by *
9
+ * the Free Software Foundation; either version 3 of the License, or *
10
+ * (at your option) any later version. *
11
+ * *
12
+ * This program is distributed in the hope that it will be useful, *
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
+ * GNU General Public License for more details. *
16
+ * *
17
+ * You should have received a copy of the GNU General Public License *
18
+ * along with this program; if not, write to the *
19
+ * Free Software Foundation, Inc., *
20
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21
+ ***************************************************************************/
22
+ =end
23
+
24
+ require_relative 'cell'
25
+ require_relative 'logging'
26
+
27
+ # Objects of this class represent rows in a spreadsheet table
28
+ class Row
29
+ @@DEF_HEIGHT=2
30
+ self.extend(Logging)
31
+ @@log = self.init_logger(STDOUT)
32
+ def initialize(number = nil)
33
+ @log = @@log
34
+ @number = number
35
+ @cells = Array.new
36
+ @height = 1
37
+ end
38
+
39
+ def add(cell)
40
+ n_cell = nil
41
+ if cell.respond_to?(:to_cell)
42
+ n_cell = cell
43
+ elsif cell.respond_to?(:to_i)
44
+ n_cell = Cell.new(@number, cell)
45
+ else
46
+ msg = 'For a new cell, a colum-number must be specified'
47
+ @log.error(red(msg))
48
+ raise StandardError(msg)
49
+ end
50
+ @cells.insert(n_cell.col, n_cell)
51
+ @cells.compact!
52
+ resize()
53
+ end
54
+
55
+ def each(&b)
56
+ @cells.each do |c|
57
+ yield(c)
58
+ end
59
+ end
60
+
61
+ def resize()
62
+ if(@cells && ! @cells.empty? )
63
+ if(@cells.length == 1 )
64
+ @ideal_height = @cells[0].ideal_height
65
+ else
66
+ @ideal_height = @cells.max{|c1, c2| c1.ideal_height <=> c2.ideal_height}.ideal_height
67
+ end
68
+ @height = @ideal_height
69
+ end
70
+ @height ||= @@DEF_HEIGHT
71
+ end
72
+
73
+ def to_s
74
+ '#<' << self.class.name << ':' << object_id.to_s << "{number=%s height=%s cells.length=%s}" %[@number.to_s, @height.to_s, @cells.length.to_s]
75
+ end
76
+
77
+ attr_reader :number, :height, :cells
78
+
79
+ alias :<< :add
80
+
81
+ end
82
+
@@ -0,0 +1,392 @@
1
+ #encoding: UTF-8
2
+
3
+ =begin
4
+ /***************************************************************************
5
+ * Copyright ©2017-2017, Michael Uplawski <michael.uplawski@uplawski.eu> *
6
+ * *
7
+ * This program is free software; you can redistribute it and/or modify *
8
+ * it under the terms of the GNU General Public License as published by *
9
+ * the Free Software Foundation; either version 3 of the License, or *
10
+ * (at your option) any later version. *
11
+ * *
12
+ * This program is distributed in the hope that it will be useful, *
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
+ * GNU General Public License for more details. *
16
+ * *
17
+ * You should have received a copy of the GNU General Public License *
18
+ * along with this program; if not, write to the *
19
+ * Free Software Foundation, Inc., *
20
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21
+ ***************************************************************************/
22
+ =end
23
+ if __FILE__ == $0
24
+ require_relative 'user_input'
25
+ end
26
+
27
+ require_relative 'logging'
28
+ require_relative 'color_output'
29
+
30
+ class ScrollException < StandardError
31
+ end
32
+
33
+ # Objects which include this module are supposed to be multi-line text as they
34
+ # have to respond to :to_s. The new functionality will allow this text to
35
+ # become scrollable in a terminal-window. See the scroll-functions up(),
36
+ # down(), left() and right().
37
+ #
38
+ # You can have a box around the scrollable "window" by setting the attribute
39
+ # 'box' to true and, -as I do not care to be asked for my opinion-, you should.
40
+ module Scrollable
41
+ self.extend(Logging)
42
+ # may not be needed at all, but when it is..,
43
+ # you want it on class level.
44
+ @@box = %w~┌ ┐ └ ┘ ─ │~
45
+
46
+ # dto. and how so!
47
+ @@log = self.init_logger(STDOUT)
48
+
49
+ # pfffft. As you please.
50
+ @@def_step_x = 1
51
+ @@def_step_y = 1
52
+
53
+ # does it
54
+ def show
55
+ defaults() if !@have_defaults
56
+ system('clear')
57
+ new_view = limit_lines
58
+ if @box
59
+ box(new_view)
60
+ else
61
+ puts new_view
62
+ end
63
+ end
64
+
65
+ # guess
66
+ def scroll_x(offset)
67
+ defaults(self) if !@have_defaults
68
+ if(offset != 0)
69
+ xt = @xoff + offset
70
+ width_x = num_columns - xt
71
+ if(width_x > 0)
72
+ @xoff = xt
73
+ end
74
+ end
75
+ show()
76
+ end
77
+
78
+ # scroll up or down by offset lines.
79
+ def scroll_y(offset)
80
+ defaults() if !@have_defaults
81
+ # any offset is okay
82
+ if(offset != 0 )
83
+ yt = @yoff + offset
84
+ height_y = @lines.length + yt
85
+ if(height_y > 0)
86
+ @yoff = yt
87
+ end
88
+ end
89
+ show()
90
+ end
91
+
92
+ # scrolls
93
+ def left()
94
+ defaults() if !@have_defaults
95
+ scroll_x(@step_x * -1)
96
+ end
97
+
98
+ # dto. To the right. You guessed that.
99
+ def right()
100
+ defaults() if !@have_defaults
101
+ scroll_x(@step_x)
102
+ end
103
+
104
+ # scrolls
105
+ def up()
106
+ defaults() if !@have_defaults
107
+ scroll_y(@step_y * -1 )
108
+ end
109
+
110
+ # scrolls
111
+ def down
112
+ defaults() if !@have_defaults
113
+ # increase number of visible lines by step_y
114
+ scroll_y(@step_y)
115
+ end
116
+
117
+ # scrolls
118
+ def scroll(xoffset, yoffset)
119
+ defaults() if !@have_defaults
120
+ scroll_y(yoffset)
121
+ scroll_x(xoffset)
122
+ end
123
+
124
+ # read the code
125
+ def home()
126
+ first_line
127
+ first_column
128
+ end
129
+
130
+ # dto.
131
+ def to_end()
132
+ last_column
133
+ last_line
134
+ end
135
+
136
+ def first_line()
137
+ defaults() if !@have_defaults
138
+ @yoff = 0
139
+ end
140
+
141
+ def last_line()
142
+ defaults() if !@have_defaults
143
+ @yoff = @lines.length
144
+ end
145
+
146
+ def first_column()
147
+ defaults() if !@have_defaults
148
+ @xoff = 0
149
+ end
150
+
151
+ def last_column()
152
+ defaults() if !@have_defaults
153
+ @xoff = num_columns - 1
154
+ end
155
+ # set the size of a vertical scroll step
156
+ def step_y=(step)
157
+ if step > 0
158
+ @step_y = step
159
+ else
160
+ raise ScrollException.new('ERROR: scroll-steps must be positive numbers')
161
+ end
162
+ end
163
+
164
+ # set the size of a horizontal scroll step
165
+ def step_x=(step)
166
+ if step > 0
167
+ @step_x = step
168
+ else
169
+ raise ScrollException.new('ERROR: scroll-steps must be positive numbers')
170
+ end
171
+ end
172
+
173
+ def width=(w)
174
+ @width = w if w >= 1
175
+ end
176
+
177
+ def height=(h)
178
+ @height = h if h >= 1
179
+ end
180
+
181
+ attr_accessor :fixed_cols, :fixed_rows
182
+ attr_writer :box
183
+ attr_reader :width, :height, :xoff, :yoff, :clipped_bottom, :clipped_top, :clipped_left, :clipped_right
184
+
185
+ private
186
+
187
+ # Clip off all that cannot fit into the defined width and height. Removes
188
+ # probably more than needed, but in its current state, I tend to *understand*
189
+ # this method. In addition, it somehow appears to work for me...
190
+ def limit_lines()
191
+ # limit lines laterally
192
+ if(@xoff < 0)
193
+ @xoff = 0
194
+ end
195
+ new_view = @lines.collect do |l|
196
+ short = l[0...@fixed_cols].to_s + l[(@xoff + @fixed_cols)..(@width + @xoff)].to_s
197
+ end
198
+
199
+
200
+ # limit number of lines
201
+ if(@yoff >= 0)
202
+ if(@yoff > (@lines.length - @height + @fixed_rows))
203
+ @yoff = @lines.length - @height + @fixed_rows
204
+ end
205
+ end
206
+
207
+ @yoff = 0 if @yoff < 0
208
+ new_view = new_view[0...@fixed_rows] + new_view[(@yoff + @fixed_rows)...(@height + @yoff - @fixed_rows) ]
209
+
210
+ # describe the situation in case that someone must know..,
211
+ if @box
212
+ @clipped_left = @xoff > 0
213
+ @clipped_top = @yoff > 0
214
+ @clipped_right = num_columns - @xoff > @width
215
+ @clipped_bottom = @lines.length - @yoff > @height
216
+ end
217
+ #... not, if not
218
+
219
+ return new_view.join("\n")
220
+ end
221
+
222
+ # calculates the number of columns (characters), needed.
223
+ def num_columns
224
+ defaults if !@have_defaults
225
+ @lines.max {|a, b| a.length <=> b.length}.length
226
+ end
227
+
228
+ # sets a few values, needed for the rest of the module to function.
229
+ def defaults()
230
+ @log = @@log
231
+ if(self.respond_to?(:to_s) )
232
+ # @view = self
233
+ @view = self.gsub(/\e\[\d+m/, '')
234
+
235
+ @lines = @view.split("\n")
236
+
237
+ @xoff = 0
238
+ @yoff = 0
239
+
240
+ @step_x ||= @@def_step_x
241
+ @step_y ||= @@def_step_y
242
+
243
+ @width ||= 60
244
+ @height ||= 30
245
+
246
+ @fixed_rows ||= 0
247
+ @fixed_cols ||= 0
248
+
249
+ @box ||= false
250
+
251
+ @have_defaults = true
252
+ elsif !view
253
+ msg = 'Need to know, what to scroll! (argument "view" missing)'
254
+ @log.error(yellow(msg))
255
+ raise ScrollException.new(msg)
256
+ else
257
+ msg = 'The "view" to scroll must have a method "to_s()"'
258
+ @log.error(yellow(msg))
259
+ raise ScrollException.new(msg)
260
+ end
261
+ end
262
+
263
+ # draw lines of color, wherever the content is clipped.
264
+
265
+ def box(view)
266
+ nl = "\n"
267
+ lines = view.split(nl)
268
+
269
+ ctl = @@box[0]
270
+ bl = @@box[5]
271
+ cbl = @@box[2]
272
+
273
+ bt = @@box[4]
274
+
275
+ ctr = @@box[1]
276
+ br = @@box[5]
277
+ cbr = @@box[3]
278
+
279
+ bb = @@box[4]
280
+
281
+ if clipped_left
282
+ ctl = bold(blue(ctl))
283
+ bl = bold(blue(bl))
284
+ cbl = bold(blue(cbl))
285
+ end
286
+ if clipped_right
287
+ ctr = bold(blue(ctr))
288
+ br = bold(blue(br))
289
+ cbr = bold(blue(cbr))
290
+ end
291
+
292
+ if clipped_top
293
+ ctr = bold(blue(ctr))
294
+ bt = bold(blue(bt))
295
+ ctl = bold(blue(ctl))
296
+ end
297
+
298
+ if clipped_bottom
299
+ cbl = bold(blue(cbl))
300
+ cbr = bold(blue(cbr))
301
+ bb = bold(blue(bb))
302
+ end
303
+ size = `stty size`.split.map { |x| x.to_i }.reverse
304
+ @width -= 1 until (@width + 5) <= size[0]
305
+ #@height -= 1 until (@height + 2) <= size[1]
306
+ puts "%s%s%s" %[ctl, bt * (@width + 3), ctr ]
307
+ height.times {|n| puts "%s %-#{@width+1}s %s" %[bl, lines[n], br] }
308
+ puts "%s%s%s" %[cbl, bb * (@width + 3), cbr ]
309
+ end
310
+ end
311
+
312
+ #### TEST
313
+
314
+ if __FILE__ == $0
315
+ # ------------> create a view
316
+ h = 60
317
+ w = 30
318
+ test_view = " |"
319
+ w.times {|col| test_view << "%s%2d|" %['c', col.next]}
320
+ test_view << "\n" << "-" * test_view.length << "\n"
321
+ h.times do |row|
322
+ test_view << "r%2d|" %row.next
323
+ w.times do |col|
324
+ content = "%3d" %col.next
325
+ if( col%3 == 0)
326
+ content = "\e[33m" << content << "\e[0m"
327
+ end
328
+ test_view << "%s|" %content
329
+ end
330
+ test_view << "\n"
331
+ end
332
+ # <------------- View done
333
+
334
+ test_view.extend(Scrollable)
335
+
336
+ # show all
337
+ test_view.show
338
+ while true
339
+ case wait_for_user.chr
340
+ # scroll
341
+ when "i"
342
+ test_view.up
343
+ when "k"
344
+ test_view.down
345
+ when "j"
346
+ test_view.left
347
+ when "l"
348
+ test_view.right
349
+ # change step-width
350
+ when "y"
351
+ print "step y: "
352
+ step = wait_for_user.chr().to_i
353
+ puts step
354
+ begin
355
+ test_view.step_y = step
356
+ puts "step y changed\n"
357
+ rescue Exception => ex
358
+ puts ex.message
359
+ end
360
+ test_view.show
361
+ when "r"
362
+ print "fix rows: "
363
+ fr = wait_for_user.chr().to_i
364
+ begin
365
+ test_view.fixed_rows = fr
366
+ puts "fixed " << fr.to_s << "rows"
367
+ rescue Exception => ex
368
+ puts ex.message
369
+ end
370
+ test_view.show
371
+ when "c"
372
+ print "fix columns: "
373
+ fc = wait_for_user.chr().to_i
374
+ begin
375
+ test_view.fixed_cols = fc
376
+ puts "fixed " << fc.to_s << "columns"
377
+ rescue Exception => ex
378
+ puts ex.message
379
+ end
380
+ test_view.show
381
+ when 'w'
382
+ print "display width: "
383
+ w = STDIN.gets.chomp.to_i
384
+ test_view.width = w
385
+ test_view.show
386
+ when 'q'
387
+ exit true
388
+ end
389
+ end
390
+ end
391
+
392
+