cdk 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,727 @@
1
+ require_relative 'cdk_objs'
2
+
3
+ module CDK
4
+ class DIALOG < CDK::CDKOBJS
5
+ attr_reader :current_button
6
+ MIN_DIALOG_WIDTH = 10
7
+
8
+ def initialize(cdkscreen, xplace, yplace, mesg, rows, button_label,
9
+ button_count, highlight, separator, box, shadow)
10
+ super()
11
+ box_width = DIALOG::MIN_DIALOG_WIDTH
12
+ max_message_width = -1
13
+ button_width = 0
14
+ xpos = xplace
15
+ ypos = yplace
16
+ temp = 0
17
+ buttonadj = 0
18
+ @info = []
19
+ @info_len = []
20
+ @info_pos = []
21
+ @button_label = []
22
+ @button_len = []
23
+ @button_pos = []
24
+
25
+ if rows <= 0 || button_count <= 0
26
+ self.destroy
27
+ return nil
28
+ end
29
+
30
+ self.setBox(box)
31
+ box_height = if separator then 1 else 0 end
32
+ box_height += rows + 2 * @border_size + 1
33
+
34
+ # Translate the string message to a chtype array
35
+ (0...rows).each do |x|
36
+ info_len = []
37
+ info_pos = []
38
+ @info << CDK.char2Chtype(mesg[x], info_len, info_pos)
39
+ @info_len << info_len[0]
40
+ @info_pos << info_pos[0]
41
+ max_message_width = [max_message_width, info_len[0]].max
42
+ end
43
+
44
+ # Translate the button label string to a chtype array
45
+ (0...button_count).each do |x|
46
+ button_len = []
47
+ @button_label << CDK.char2Chtype(button_label[x], button_len, [])
48
+ @button_len << button_len[0]
49
+ button_width += button_len[0] + 1
50
+ end
51
+
52
+ button_width -= 1
53
+
54
+ # Determine the final dimensions of the box.
55
+ box_width = [box_width, max_message_width, button_width].max
56
+ box_width = box_width + 2 + 2 * @border_size
57
+
58
+ # Now we have to readjust the x and y positions.
59
+ xtmp = [xpos]
60
+ ytmp = [ypos]
61
+ CDK.alignxy(cdkscreen.window, xtmp, ytmp, box_width, box_height)
62
+ xpos = xtmp[0]
63
+ ypos = ytmp[0]
64
+
65
+ # Set up the dialog box attributes.
66
+ @screen = cdkscreen
67
+ @parent = cdkscreen.window
68
+ @win = Ncurses::WINDOW.new(box_height, box_width, ypos, xpos)
69
+ @shadow_win = nil
70
+ @button_count = button_count
71
+ @current_button = 0
72
+ @message_rows = rows
73
+ @box_height = box_height
74
+ @box_width = box_width
75
+ @highlight = highlight
76
+ @separator = separator
77
+ @accepts_focus = true
78
+ @input_window = @win
79
+ @shadow = shadow
80
+
81
+ # If we couldn't create the window, we should return a nil value.
82
+ if @win.nil?
83
+ self.destroy
84
+ return nil
85
+ end
86
+ @win.keypad(true)
87
+
88
+ # Find the button positions.
89
+ buttonadj = (box_width - button_width) / 2
90
+ (0...button_count).each do |x|
91
+ @button_pos[x] = buttonadj
92
+ buttonadj = buttonadj + @button_len[x] + @border_size
93
+ end
94
+
95
+ # Create the string alignments.
96
+ (0...rows).each do |x|
97
+ @info_pos[x] = CDK.justifyString(box_width - 2 * @border_size,
98
+ @info_len[x], @info_pos[x])
99
+ end
100
+
101
+ # Was there a shadow?
102
+ if shadow
103
+ @shadow_win = Ncurses::WINDOW.new(box_height, box_width,
104
+ ypos + 1, xpos + 1)
105
+ end
106
+
107
+ # Register this baby.
108
+ cdkscreen.register(:DIALOG, self)
109
+ end
110
+
111
+ # This lets the user select the button.
112
+ def activate(actions)
113
+ input = 0
114
+
115
+ # Draw the dialog box.
116
+ self.draw(@box)
117
+
118
+ # Lets move to the first button.
119
+ Draw.writeChtypeAttrib(@win, @button_pos[@current_button],
120
+ @box_height - 1 - @border_size, @button_label[@current_button],
121
+ @highlight, CDK::HORIZONTAL, 0, @button_len[@current_button])
122
+ @win.wrefresh
123
+
124
+ if actions.nil? || actions.size == 0
125
+ while true
126
+ input = self.getch([])
127
+
128
+ # Inject the character into the widget.
129
+ ret = self.inject(input)
130
+ if @exit_type != :EARLY_EXIT
131
+ return ret
132
+ end
133
+ end
134
+ else
135
+ # Inject each character one at a time.
136
+ actions.each do |action|
137
+ ret = self.inject(action)
138
+ if @exit_type != :EARLY_EXIT
139
+ return ret
140
+ end
141
+ end
142
+ end
143
+
144
+ # Set the exit type and exit
145
+ self.setExitType(0)
146
+ return -1
147
+ end
148
+
149
+ # This injects a single character into the dialog widget
150
+ def inject(input)
151
+ first_button = 0
152
+ last_button = @button_count - 1
153
+ pp_return = 1
154
+ ret = -1
155
+ complete = false
156
+
157
+ # Set the exit type.
158
+ self.setExitType(0)
159
+
160
+ # Check if there is a pre-process function to be called.
161
+ unless @pre_process_func.nil?
162
+ pp_return = @pre_process_func.call(:DIALOG, self,
163
+ @pre_process_data, input)
164
+ end
165
+
166
+ # Should we continue?
167
+ if pp_return != 0
168
+ # Check for a key binding.
169
+ if self.checkBind(:DIALOG, input)
170
+ complete = true
171
+ else
172
+ case input
173
+ when Ncurses::KEY_LEFT, Ncurses::KEY_BTAB, Ncurses::KEY_BACKSPACE
174
+ if @current_button == first_button
175
+ @current_button = last_button
176
+ else
177
+ @current_button -= 1
178
+ end
179
+ when Ncurses::KEY_RIGHT, CDK::KEY_TAB, ' '.ord
180
+ if @current_button == last_button
181
+ @current_button = first_button
182
+ else
183
+ @current_button += 1
184
+ end
185
+ when Ncurses::KEY_UP, Ncurses::KEY_DOWN
186
+ CDK.Beep
187
+ when CDK::REFRESH
188
+ @screen.erase
189
+ @screen.refresh
190
+ when CDK::KEY_ESC
191
+ self.setExitType(input)
192
+ complete = true
193
+ when Ncurses::ERR
194
+ self.setExitType(input)
195
+ when Ncurses::KEY_ENTER, CDK::KEY_RETURN
196
+ self.setExitType(input)
197
+ ret = @current_button
198
+ complete = true
199
+ end
200
+ end
201
+
202
+ # Should we call a post_process?
203
+ if !complete && !(@post_process_func.nil?)
204
+ @post_process_func.call(:DIALOG, self,
205
+ @post_process_data, input)
206
+ end
207
+ end
208
+
209
+ unless complete
210
+ self.drawButtons
211
+ @win.wrefresh
212
+ self.setExitType(0)
213
+ end
214
+
215
+ @result_data = ret
216
+ return ret
217
+ end
218
+
219
+ # This moves the dialog field to the given location.
220
+ # Inherited
221
+ # def move(xplace, yplace, relative, refresh_flag)
222
+ # end
223
+
224
+ # This function draws the dialog widget.
225
+ def draw(box)
226
+ # Is there a shadow?
227
+ unless @shadow_win.nil?
228
+ Draw.drawShadow(@shadow_win)
229
+ end
230
+
231
+ # Box the widget if they asked.
232
+ if box
233
+ Draw.drawObjBox(@win, self)
234
+ end
235
+
236
+ # Draw in the message.
237
+ (0...@message_rows).each do |x|
238
+ Draw.writeChtype(@win,
239
+ @info_pos[x] + @border_size, x + @border_size, @info[x],
240
+ CDK::HORIZONTAL, 0, @info_len[x])
241
+ end
242
+
243
+ # Draw in the buttons.
244
+ self.drawButtons
245
+
246
+ @win.wrefresh
247
+ end
248
+
249
+ # This function destroys the dialog widget.
250
+ def destroy
251
+ # Clean up the windows.
252
+ CDK.deleteCursesWindow(@win)
253
+ CDK.deleteCursesWindow(@shadow_win)
254
+
255
+ # Clean the key bindings
256
+ self.cleanBindings(:DIALOG)
257
+
258
+ # Unregister this object
259
+ CDK::SCREEN.unregister(:DIALOG, self)
260
+ end
261
+
262
+ # This function erases the dialog widget from the screen.
263
+ def erase
264
+ if self.validCDKObject
265
+ CDK.eraseCursesWindow(@win)
266
+ CDK.eraseCursesWindow(@shadow_win)
267
+ end
268
+ end
269
+
270
+ # This sets attributes of the dialog box.
271
+ def set(highlight, separator, box)
272
+ self.setHighlight(highlight)
273
+ self.setSeparator(separator)
274
+ self.setBox(box)
275
+ end
276
+
277
+ # This sets the highlight attribute for the buttons.
278
+ def setHighlight(highlight)
279
+ @highlight = highlight
280
+ end
281
+
282
+ def getHighlight
283
+ return @highlight
284
+ end
285
+
286
+ # This sets whether or not the dialog box will have a separator line.
287
+ def setSeparator(separator)
288
+ @separator = separator
289
+ end
290
+
291
+ def getSeparator
292
+ return @separator
293
+ end
294
+
295
+ # This sets the background attribute of the widget.
296
+ def setBKattr(attrib)
297
+ @win.wbkgd(attrib)
298
+ end
299
+
300
+ # This draws the dialog buttons and the separation line.
301
+ def drawButtons
302
+ (0...@button_count).each do |x|
303
+ Draw.writeChtype(@win, @button_pos[x],
304
+ @box_height -1 - @border_size,
305
+ @button_label[x], CDK::HORIZONTAL, 0,
306
+ @button_len[x])
307
+ end
308
+
309
+ # Draw the separation line.
310
+ if @separator
311
+ boxattr = @BXAttr
312
+
313
+ (1...@box_width).each do |x|
314
+ @win.mvwaddch(@box_height - 2 - @border_size, x,
315
+ Ncurses::ACS_HLINE | boxattr)
316
+ end
317
+ @win.mvwaddch(@box_height - 2 - @border_size, 0,
318
+ Ncurses::ACS_LTEE | boxattr)
319
+ @win.mvwaddch(@box_height - 2 - @border_size, @win.getmaxx - 1,
320
+ Ncurses::ACS_RTEE | boxattr)
321
+ end
322
+ Draw.writeChtypeAttrib(@win, @button_pos[@current_button],
323
+ @box_height - 1 - @border_size, @button_label[@current_button],
324
+ @highlight, CDK::HORIZONTAL, 0, @button_len[@current_button])
325
+ end
326
+
327
+ def focus
328
+ self.draw(@box)
329
+ end
330
+
331
+ def unfocus
332
+ self.draw(@box)
333
+ end
334
+
335
+ def object_type
336
+ :DIALOG
337
+ end
338
+
339
+ def position
340
+ super(@win)
341
+ end
342
+ end
343
+
344
+ class GRAPH < CDK::CDKOBJS
345
+ def initialize(cdkscreen, xplace, yplace, height, width,
346
+ title, xtitle, ytitle)
347
+ super()
348
+ parent_width = cdkscreen.window.getmaxx
349
+ parent_height = cdkscreen.window.getmaxy
350
+
351
+ self.setBox(false)
352
+
353
+ box_height = CDK.setWidgetDimension(parent_height, height, 3)
354
+ box_width = CDK.setWidgetDimension(parent_width, width, 0)
355
+ box_width = self.setTitle(title, box_width)
356
+ box_height += @title_lines
357
+ box_width = [parent_width, box_width].min
358
+ box_height = [parent_height, box_height].min
359
+
360
+ # Rejustify the x and y positions if we need to
361
+ xtmp = [xplace]
362
+ ytmp = [yplace]
363
+ CDK.alignxy(cdkscreen.window, xtmp, ytmp, box_width, box_height)
364
+ xpos = xtmp[0]
365
+ ypos = ytmp[0]
366
+
367
+ # Create the widget pointer
368
+ @screen = cdkscreen
369
+ @parent = cdkscreen.window
370
+ @win = Ncurses::WINDOW.new(box_height, box_width, ypos, xpos)
371
+ @box_height = box_height
372
+ @box_width = box_width
373
+ @minx = 0
374
+ @maxx = 0
375
+ @xscale = 0
376
+ @yscale = 0
377
+ @count = 0
378
+ @display_type = :LINE
379
+
380
+ if @win.nil?
381
+ self.destroy
382
+ return nil
383
+ end
384
+ @win.keypad(true)
385
+
386
+ # Translate the X axis title string to a chtype array
387
+ if !(xtitle.nil?) && xtitle.size > 0
388
+ xtitle_len = []
389
+ xtitle_pos = []
390
+ @xtitle = CDK.char2Chtype(xtitle, xtitle_len, xtitle_pos)
391
+ @xtitle_len = xtitle_len[0]
392
+ @xtitle_pos = CDK.justifyString(@box_height,
393
+ @xtitle_len, xtitle_pos[0])
394
+ else
395
+ xtitle_len = []
396
+ xtitle_pos = []
397
+ @xtitle = CDK.char2Chtype("<C></5>X Axis", xtitle_len, xtitle_pos)
398
+ @xtitle_len = title_len[0]
399
+ @xtitle_pos = CDK.justifyString(@box_height,
400
+ @xtitle_len, xtitle_pos[0])
401
+ end
402
+
403
+ # Translate the Y Axis title string to a chtype array
404
+ if !(ytitle.nil?) && ytitle.size > 0
405
+ ytitle_len = []
406
+ ytitle_pos = []
407
+ @ytitle = CDK.char2Chtype(ytitle, ytitle_len, ytitle_pos)
408
+ @ytitle_len = ytitle_len[0]
409
+ @ytitle_pos = CDK.justifyString(@box_width, @ytitle_len, ytitle_pos[0])
410
+ else
411
+ ytitle_len = []
412
+ ytitle_pos = []
413
+ @ytitle = CDK.char2Chtype("<C></5>Y Axis", ytitle_len, ytitle_pos)
414
+ @ytitle_len = ytitle_len[0]
415
+ @ytitle_pos = CDK.justifyString(@box_width, @ytitle_len, ytitle_pos[0])
416
+ end
417
+
418
+ @graph_char = 0
419
+ @values = []
420
+
421
+ cdkscreen.register(:GRAPH, self)
422
+ end
423
+
424
+ # This was added for the builder.
425
+ def activate(actions)
426
+ self.draw(@box)
427
+ end
428
+
429
+ # Set multiple attributes of the widget
430
+ def set(values, count, graph_char, start_at_zero, display_type)
431
+ ret = self.setValues(values, count, start_at_zero)
432
+ self.setCharacters(graph_char)
433
+ self.setDisplayType(display_type)
434
+ return ret
435
+ end
436
+
437
+ # Set the scale factors for the graph after wee have loaded new values.
438
+ def setScales
439
+ @xscale = (@maxx - @minx) / [1, @box_height - @title_lines - 5].max
440
+ if @xscale <= 0
441
+ @xscale = 1
442
+ end
443
+
444
+ @yscale = (@box_width - 4) / [1, @count].max
445
+ if @yscale <= 0
446
+ @yscale = 1
447
+ end
448
+ end
449
+
450
+ # Set the values of the graph.
451
+ def setValues(values, count, start_at_zero)
452
+ min = 2**30
453
+ max = -2**30
454
+
455
+ # Make sure everything is happy.
456
+ if count < 0
457
+ return false
458
+ end
459
+
460
+ if !(@values.nil?) && @values.size > 0
461
+ @values = []
462
+ @count = 0
463
+ end
464
+
465
+ # Copy the X values
466
+ values.each do |value|
467
+ min = [value, @minx].min
468
+ max = [value, @maxx].max
469
+
470
+ # Copy the value.
471
+ @values << value
472
+ end
473
+
474
+ # Keep the count and min/max values
475
+ @count = count
476
+ @minx = min
477
+ @maxx = max
478
+
479
+ # Check the start at zero status.
480
+ if start_at_zero
481
+ @minx = 0
482
+ end
483
+
484
+ self.setScales
485
+
486
+ return true
487
+ end
488
+
489
+ def getValues(size)
490
+ size << @count
491
+ return @values
492
+ end
493
+
494
+ # Set the value of the graph at the given index.
495
+ def setValue(index, value, start_at_zero)
496
+ # Make sure the index is within range.
497
+ if index < 0 || index >= @count
498
+ return false
499
+ end
500
+
501
+ # Set the min, max, and value for the graph
502
+ @minx = [value, @minx].min
503
+ @maxx = [value, @maxx].max
504
+ @values[index] = value
505
+
506
+ # Check the start at zero status
507
+ if start_at_zero
508
+ @minx = 0
509
+ end
510
+
511
+ self.setScales
512
+
513
+ return true
514
+ end
515
+
516
+ def getValue(index)
517
+ if index >= 0 && index < @count then @values[index] else 0 end
518
+ end
519
+
520
+ # Set the characters of the graph widget.
521
+ def setCharacters(characters)
522
+ char_count = []
523
+ new_tokens = CDK.char2Chtype(characters, char_count, [])
524
+
525
+ if char_count[0] != @count
526
+ return false
527
+ end
528
+
529
+ @graph_char = new_tokens
530
+ return true
531
+ end
532
+
533
+ def getCharacters
534
+ return @graph_char
535
+ end
536
+
537
+ # Set the character of the graph widget of the given index.
538
+ def setCharacter(index, character)
539
+ # Make sure the index is within range
540
+ if index < 0 || index > @count
541
+ return false
542
+ end
543
+
544
+ # Convert the string given to us
545
+ char_count = []
546
+ new_tokens = CDK.char2Chtype(character, char_count, [])
547
+
548
+ # Check if the number of characters back is the same as the number
549
+ # of elements in the list.
550
+ if char_count[0] != @count
551
+ return false
552
+ end
553
+
554
+ # Everything OK so far. Set the value of the array.
555
+ @graph_char[index] = new_tokens[0]
556
+ return true
557
+ end
558
+
559
+ def getCharacter(index)
560
+ return graph_char[index]
561
+ end
562
+
563
+ # Set the display type of the graph.
564
+ def setDisplayType(type)
565
+ @display_type = type
566
+ end
567
+
568
+ def getDisplayType
569
+ @display_type
570
+ end
571
+
572
+ # Set the background attribute of the widget.
573
+ def setBKattr(attrib)
574
+ @win.wbkgd(attrib)
575
+ end
576
+
577
+ # Move the graph field to the given location.
578
+ def move(xplace, yplace, relative, refresh_flag)
579
+ current_x = @win.getbegx
580
+ current_y = @win.getbegy
581
+ xpos = xplace
582
+ ypos = yplace
583
+
584
+ # If this is a relative move, then we will adjust where we want
585
+ # to move to
586
+ if relative
587
+ xpos = @win.getbegx + xplace
588
+ ypos = @win.getbegy + yplace
589
+ end
590
+
591
+ # Adjust the window if we need to.
592
+ xtmp = [xpos]
593
+ tymp = [ypos]
594
+ CDK.alignxy(@screen.window, xtmp, ytmp, @box_width, @box_height)
595
+ xpos = xtmp[0]
596
+ ypos = ytmp[0]
597
+
598
+ # Get the difference
599
+ xdiff = current_x - xpos
600
+ ydiff = current_y - ypos
601
+
602
+ # Move the window to the new location.
603
+ CDK.moveCursesWindow(@win, -xdiff, -ydiff)
604
+ CDK.moveCursesWindow(@shadow_win, -xdiff, -ydiff)
605
+
606
+ # Touch the windows so they 'move'.
607
+ CDK::SCREEN.refreshCDKWindow(@screen.window)
608
+
609
+ # Reraw the windowk if they asked for it
610
+ if refresh_flag
611
+ self.draw(@box)
612
+ end
613
+ end
614
+
615
+ # Draw the grpah widget
616
+ def draw(box)
617
+ adj = 2 + (if @xtitle.nil? || @xtitle.size == 0 then 0 else 1 end)
618
+ spacing = 0
619
+ attrib = ' '.ord | Ncurses::A_REVERSE
620
+
621
+ if box
622
+ Draw.drawObjBox(@win, self)
623
+ end
624
+
625
+ # Draw in the vertical axis
626
+ Draw.drawLine(@win, 2, @title_lines + 1, 2, @box_height - 3,
627
+ Ncurses::ACS_VLINE)
628
+
629
+ # Draw in the horizontal axis
630
+ Draw.drawLine(@win, 3, @box_height - 3, @box_width, @box_height - 3,
631
+ Ncurses::ACS_HLINE)
632
+
633
+ self.drawTitle(@win)
634
+
635
+ # Draw in the X axis title.
636
+ if !(@xtitle.nil?) && @xtitle.size > 0
637
+ Draw.writeChtype(@win, 0, @xtitle_pos, @xtitle, CDK::VERTICAL,
638
+ 0, @xtitle_len)
639
+ attrib = @xtitle[0] & Ncurses::A_ATTRIBUTES
640
+ end
641
+
642
+ # Draw in the X axis high value
643
+ temp = "%d" % [@maxx]
644
+ Draw.writeCharAttrib(@win, 1, @title_lines + 1, temp, attrib,
645
+ CDK::VERTICAL, 0, temp.size)
646
+
647
+ # Draw in the X axis low value.
648
+ temp = "%d" % [@minx]
649
+ Draw.writeCharAttrib(@win, 1, @box_height - 2 - temp.size, temp, attrib,
650
+ CDK::VERTICAL, 0, temp.size)
651
+
652
+ # Draw in the Y axis title
653
+ if !(@ytitle.nil?) && @ytitle.size > 0
654
+ Draw.writeChtype(@win, @ytitle_pos, @box_height - 1, @ytitle,
655
+ CDK::HORIZONTAL, 0, @ytitle_len)
656
+ end
657
+
658
+ # Draw in the Y axis high value.
659
+ temp = "%d" % [@count]
660
+ Draw.writeCharAttrib(@win, @box_width - temp.size - adj,
661
+ @box_height - 2, temp, attrib, CDK::HORIZONTAL, 0, temp.size)
662
+
663
+ # Draw in the Y axis low value.
664
+ Draw.writeCharAttrib(@win, 3, @box_height - 2, "0", attrib,
665
+ CDK::HORIZONTAL, 0, "0".size)
666
+
667
+ # If the count is zero then there aren't any points.
668
+ if @count == 0
669
+ @win.wrefresh
670
+ return
671
+ end
672
+
673
+ spacing = (@box_width - 3) / @count # FIXME magic number (TITLE_LM)
674
+
675
+ # Draw in the graph line/plot points.
676
+ (0...@count).each do |y|
677
+ colheight = (@values[y] / @xscale) - 1
678
+ # Add the marker on the Y axis.
679
+ @win.mvwaddch(@box_height - 3, (y + 1) * spacing + adj,
680
+ Ncurses::ACS_TTEE)
681
+
682
+ # If this is a plot graph, all we do is draw a dot.
683
+ if @display_type == :PLOT
684
+ xpos = @box_height - 4 - colheight
685
+ ypos = (y + 1) * spacing + adj
686
+ @win.mvwaddch(xpos, ypos, @graph_char[y])
687
+ else
688
+ (0..@yscale).each do |x|
689
+ xpos = @box_height - 3
690
+ ypos = (y + 1) * spacing - adj
691
+ Draw.drawLine(@win, ypos, xpos - colheight, ypos, xpos,
692
+ @graph_char[y])
693
+ end
694
+ end
695
+ end
696
+
697
+ # Draw in the axis corners.
698
+ @win.mvwaddch(@title_lines, 2, Ncurses::ACS_URCORNER)
699
+ @win.mvwaddch(@box_height - 3, 2, Ncurses::ACS_LLCORNER)
700
+ @win.mvwaddch(@box_height - 3, @box_width, Ncurses::ACS_URCORNER)
701
+
702
+ # Refresh and lets see it
703
+ @win.wrefresh
704
+ end
705
+
706
+ def destroy
707
+ self.cleanTitle
708
+ self.cleanBindings(:GRAPH)
709
+ CDK::SCREEN.unregister(:GRAPH, self)
710
+ CDK.deleteCursesWindow(@win)
711
+ end
712
+
713
+ def erase
714
+ if self.validCDKObject
715
+ CDK.eraseCursesWindow(@win)
716
+ end
717
+ end
718
+
719
+ def object_type
720
+ :GRAPH
721
+ end
722
+
723
+ def position
724
+ super(@win)
725
+ end
726
+ end
727
+ end