viewworkbook 0.1.3

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.
@@ -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
+