Class: RNDK::Screen

Inherits:
Object
  • Object
show all
Defined in:
lib/rndk/core/screen.rb,
lib/rndk/core/quick_widgets.rb

Overview

Placeholder for RNDK Widgets.

Since all Widgets are bonded to a Screen, you pretty much won't need to call any of the methods here.

The only methods you should worry about are:

  • Screen#initialize
  • Screen#finish
  • Screen#draw or #refresh
  • Screen#erase

Developer Notes

When a Widget is created, it calls Screen#register with it's type and self pointer.

That adds the widget pointer to an Array @widget that contains all the widgets inside this screen.

During it's lifetime, most widgets would call only Screen#erase and Screen#refresh.

When Widget#destroy is called, we call Screen#unregister to remove it from the @widget array.

Now, what happens when the Ruby garbage collector kills the widget?

Constant Summary collapse

NOEXIT =
0
EXITOK =
1
EXITCANCEL =
2

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ncurses_window = nil) ⇒ Screen

Takes a Ncurses WINDOW* pointer and creates a CDKScreen.

This also starts Ncurses, if it wasn't started before or no WINDOW* were provided



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rndk/core/screen.rb', line 72

def initialize(ncurses_window=nil)

  # If the user didn't start Ncurses for us,
  # we'll do it anyway.
  if RNDK::ALL_SCREENS.empty? or ncurses_window.nil?

    ## Why is this here?
    # Set up basic curses settings.
    # #ifdef HAVE_SETLOCALE
    # setlocale (LC_ALL, "");
    # #endif

    ncurses_window = Ncurses.initscr
    Ncurses.noecho
    Ncurses.cbreak
    Ncurses.keypad(ncurses_window, true)
  end

  RNDK::ALL_SCREENS << self
  @widget_count = 0
  @widget_limit = 2
  @widget = Array.new(@widget_limit, nil)
  @window = ncurses_window
  @widget_focus = 0
end

Instance Attribute Details

#exit_statusObject

Nothing... for now



51
52
53
# File 'lib/rndk/core/screen.rb', line 51

def exit_status
  @exit_status
end

#widgetObject

Array with all the widgets currently on the screen



45
46
47
# File 'lib/rndk/core/screen.rb', line 45

def widget
  @widget
end

#widget_countObject

How many widgets we currently have



39
40
41
# File 'lib/rndk/core/screen.rb', line 39

def widget_count
  @widget_count
end

#widget_focusObject

Index of the current focused widget



36
37
38
# File 'lib/rndk/core/screen.rb', line 36

def widget_focus
  @widget_focus
end

#widget_limitObject

Maximum widget capacity of the screen (always expands)



42
43
44
# File 'lib/rndk/core/screen.rb', line 42

def widget_limit
  @widget_limit
end

#windowObject

Raw Ncurses window that represents this Screen



48
49
50
# File 'lib/rndk/core/screen.rb', line 48

def window
  @window
end

Class Method Details

.finishObject

Shuts down RNDK and Ncurses, plus destroying all the widgets ever created.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rndk/core/screen.rb', line 100

def self.finish

  ## If I do this it gives me a segmentation fault.
  ## I guess that would happen because of the
  ## ruby garbage collector.
  ##
  ## What should I do instead?
  #
  # RNDK::ALL_WIDGETS.each { |obj| obj.destroy }
  # RNDK::ALL_SCREENS.each { |scr| scr.destroy }

  Ncurses.echo
  Ncurses.nocbreak
  Ncurses.endwin
end

.heightObject

Returns the whole terminal screen height.



63
64
65
# File 'lib/rndk/core/screen.rb', line 63

def self.height
  Ncurses.LINES
end

.lower_widget(widget) ⇒ Object

Has the opposite effect of #raise_widget.



194
195
196
197
198
# File 'lib/rndk/core/screen.rb', line 194

def self.lower_widget widget
  return unless widget.valid_type?

  widget.screen.swap_indexes(widget.screen_index, 0)
end

.raise_widget(widget) ⇒ Object

Raises the Widget to the top of the screen. It will now overlap any other obstructing Widgets.

rndktype states what RNDK Widget type this widget is. widget is a pointer to the Widget itself.



186
187
188
189
190
191
# File 'lib/rndk/core/screen.rb', line 186

def self.raise_widget widget
  return unless widget.valid_type?

  screen = widget.screen
  screen.swap_indexes(widget.screen_index, screen.widget_count - 1)
end

.widthObject

Returns the whole terminal screen width.



58
59
60
# File 'lib/rndk/core/screen.rb', line 58

def self.width
  Ncurses.COLS
end

Instance Method Details

#cleanly(&block) ⇒ Object

Executes a block of code without modifying the screen state.

See usage right below.



32
33
34
35
36
37
38
39
40
# File 'lib/rndk/core/quick_widgets.rb', line 32

def cleanly &block
  prev_state = Ncurses.curs_set 0

  yield

  Ncurses.curs_set prev_state
  self.erase
  self.refresh
end

#destroyObject

Note:

It does nothing to the widgets inside it. You must either destroy them separatedly or call #destroy_widgets before.

Destroys this Screen.



276
277
278
# File 'lib/rndk/core/screen.rb', line 276

def destroy
  RNDK::ALL_SCREENS.delete self
end

#destroy_widgetsObject

Destroys all the Widgets inside this Screen.



258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/rndk/core/screen.rb', line 258

def destroy_widgets
  (0...@widget_count).each do |x|
    obj    = @widget[x]
    before = @widget_count

    if obj.valid_type?
      obj.erase
      obj.destroy
      x -= (@widget_count - before)
    end
  end
end

#drawObject

Redraws all Widgets inside this Screen.



201
202
203
# File 'lib/rndk/core/screen.rb', line 201

def draw
  self.refresh
end

#eraseObject

Note:

Erase in the sense of clearing the actual characters on the terminal screen. This does NOT destroy any widgets.

Erases all Widgets inside this Screen.



249
250
251
252
253
254
255
# File 'lib/rndk/core/screen.rb', line 249

def erase
  (0...@widget_count).each do |x|
    obj = @widget[x]
    obj.erase if obj.valid_type?
  end
  Ncurses.wrefresh(@window)
end

#get_list_index(title, list, numbers) ⇒ Object

Note:

list must be Arrays of Strings.

Display a scrollable list of strings in a Dialog, allowing the user to select one.

If numbers is true, the displayed list items will be numbered.

Returns:

  • The index in the list of the value selected.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rndk/core/quick_widgets.rb', line 227

def get_list_index(title, list, numbers)
  return nil if list.class != Array or list.empty?

  selected = -1
  height = 10
  width = -1
  len = 0

  # Determine the height of the list.
  if list.size < 10
    height = list.size + if title.size == 0 then 2 else 3 end
  end

  # Determine the width of the list.
  list.each { |item| width = [width, item.size + 10].max }

  width = [width, title.size].max
  width += 5

  scrollp = RNDK::Scroll.new(self, {
                               :x => RNDK::CENTER,
                               :y => RNDK::CENTER,
                               :width => width,
                               :height => height,
                               :title => title,
                               :items => list,
                               :numbers => numbers
                             })
  if scrollp.nil?
    self.refresh
    return -1
  end

  selected = scrollp.activate

  # Check how they exited.
  if scrollp.exit_type != :NORMAL
    selected = -1
  end

  scrollp.destroy
  self.refresh

  selected
end

#get_string(title, label, initial_text = "") ⇒ Object

Pops up an Entry Widget and returns the value supplied by the user.

title, label and initial_text are passed to the Widget.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/rndk/core/quick_widgets.rb', line 277

def get_string(title, label, initial_text="")

  widget = RNDK::Entry.new(self, {
                             :x => RNDK::CENTER,
                             :y => RNDK::CENTER,
                             :title => title,
                             :label => label,
                             :field_width => 40,
                             :initial_text => initial_text
                           })

  value = widget.activate

  # Make sure they exited normally.
  if widget.exit_type != :NORMAL
    widget.destroy
    return nil
  end

  # Return a copy of the string typed in.
  value = widget.get_text.clone
  widget.destroy

  value
end

Shows a centered pop-up Dialog box with message label and each button label on buttons.

@note: message and buttons must be Arrays of Strings.

Returns:

  • The user choice or nil if wrong parameters were given.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rndk/core/quick_widgets.rb', line 93

def popup_dialog(message, buttons)
  return nil if message.empty? or buttons.empty?

  choice = 0
  self.cleanly do
    popup = RNDK::Dialog.new(self, {
                               :x => RNDK::CENTER,
                               :y => RNDK::CENTER,
                               :text => message,
                               :buttons => buttons
                             })

    popup.draw
    choice = popup.activate
    popup.destroy
  end
  choice
end

Quickly pops up a message.

Creates a centered pop-up Label Widget that waits until the user hits a character.

@note: message must be a String or an Array of Strings.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/rndk/core/quick_widgets.rb', line 48

def popup_label message
  self.cleanly do
    popup = RNDK::Label.new(self, {
                              :x => RNDK::CENTER,
                              :y => RNDK::CENTER,
                              :text => message
                            })
    popup.draw

    # Wait for some input.
    Ncurses.keypad(popup.win, true)
    popup.getch
    popup.destroy
  end
end

Quickly pops up a message, using attrib for the background of the dialog.

@note: message must be an array of strings.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/rndk/core/quick_widgets.rb', line 68

def popup_label_color(message, attrib)
  self.cleanly do
    popup = RNDK::Label.new(self, {
                              :x => RNDK::CENTER,
                              :y => RNDK::CENTER,
                              :text => message
                            })

    popup.set_bg_color attrib
    popup.draw

    # Wait for some input
    Ncurses.keypad(popup.win, true)
    popup.getch
    popup.destroy
  end
end

#refreshObject

Redraws all Widgets inside this Screen.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/rndk/core/screen.rb', line 206

def refresh
  focused = -1
  visible = -1

  RNDK.window_refresh(@window)

  # We erase all the invisible widgets, then only draw it all back, so
  # that the widgets can overlap, and the visible ones will always be
  # drawn after all the invisible ones are erased
  (0...@widget_count).each do |x|
    obj = @widget[x]
    if obj.valid_type?
      if obj.is_visible
        if visible < 0
          visible = x
        end
        if obj.has_focus && focused < 0
          focused = x
        end
      else
        obj.erase
      end
    end
  end

  (0...@widget_count).each do |x|
    obj = @widget[x]

    if obj.valid_type?
      obj.has_focus = (x == focused)

      if obj.is_visible
        obj.draw
      end
    end
  end
end

#register(rndktype, widget) ⇒ Object

Note:

This is called automatically when a widget is created.

Adds a Widget to this Screen.

  • rndktype states what RNDK Widget type this widget is.
  • widget is a pointer to the Widget itself.


123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/rndk/core/screen.rb', line 123

def register(rndktype, widget)

  # Expanding the limit by 2
  if (@widget_count + 1) >= @widget_limit
    @widget_limit += 2
    @widget_limit *= 2
    @widget.concat Array.new(@widget_limit - @widget.size, nil)
  end

  if widget.valid_type?
    self.set_screen_index(@widget_count, widget)
    @widget_count += 1
  end
end

#select_file(title) ⇒ Object

Displays a file-selection dialog with title.

TODO FIXME This widget is VERY buggy.

Returns:

  • The selected filename, or nil if none was selected.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/rndk/core/quick_widgets.rb', line 192

def select_file title

  fselect = RNDK::Fselect.new(self, {
                                :x => RNDK::CENTER,
                                :y => RNDK::CENTER,
                                :width =>-20,
                                :height => -4,
                                :title => title,
                                :label => 'File: '
                              })

  filename = fselect.activate

  # Check the way the user exited the selector.
  if fselect.exit_type != :NORMAL
    fselect.destroy
    self.refresh
    return nil
  end

  # Otherwise...
  fselect.destroy
  self.refresh
  filename
end

#unregister(widget) ⇒ Object

Note:

This is called automatically when a widget is destroyed.

Removes a Widget from this Screen.

This does NOT destroy the widget, it removes the Widget from any further refreshes by Screen#refresh.

rndktype states what RNDK Widget type this widget is. widget is a pointer to the Widget itself.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/rndk/core/screen.rb', line 148

def unregister widget
  return unless (widget.valid_type? and (widget.screen_index >= 0))

  index = widget.screen_index
  widget.screen_index = -1

  # Resequence the widgets
  (index...self.widget_count - 1).each do |x|
    self.set_screen_index(x, self.widget[x+1])
  end

  if self.widget_count <= 1
    # if no more widgets, remove the array
    self.widget = []
    self.widget_count = 0
    self.widget_limit = 0

  else
    self.widget[self.widget_count] = nil
    self.widget_count -= 1

    # Update the widget-focus
    if self.widget_focus == index
      self.widget_focus -= 1
      Traverse.set_next_focus(screen)

    elsif self.widget_focus > index
      self.widget_focus -= 1
    end
  end
end

#view_file(title, filename, buttons) ⇒ Object

Reads filename's contents and display it on a Viewer Widget.

title and buttons are applied to the Widget.

The viewer shows the contents of the file supplied by the filename value.

It returns the index of the button selected, or -1 if the file does not exist or if the widget was exited early.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/rndk/core/quick_widgets.rb', line 166

def view_file(title, filename, buttons)

  info = []
  result = 0

  # Open the file and read the contents.
  lines = RNDK.read_file filename

  # If we couldn't read the file, return an error.
  if lines == -1
    result = lines
  else
    result = self.view_info(title,
                            info,
                            buttons,
                            true)
  end
  result
end

#view_info(title, info, buttons, hide_control_chars = true) ⇒ Object

Note:

info and buttons must be Arrays of Strings.

Display a long string set info in a Viewer Widget.

title and buttons are applied to the Widget.

hide_control_chars tells if we want to hide those ugly ^J, ^M chars.

Returns:

  • The index of the selected button.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rndk/core/quick_widgets.rb', line 121

def view_info(title, info, buttons, hide_control_chars=true)
  return nil if info.class != Array or info.empty?
  return nil if buttons.class != Array or buttons.empty?

  selected = -1

  # Create the file viewer to view the file selected.
  viewer = RNDK::Viewer.new(self, {
                              :x => RNDK::CENTER,
                              :y => RNDK::CENTER,
                              :width => -6,
                              :height => -16,
                              :buttons => buttons,
                              :shadow => true
                            })

  # Set up the viewer title, and the contents of the widget.
  viewer.set({
               :title => title,
               :items => info,
               :hide_control_chars => hide_control_chars
             })

  selected = viewer.activate

  # Make sure they exited normally.
  if viewer.exit_type != :NORMAL
    viewer.destroy
    return -1
  end

  # Clean up and return the button index selected
  viewer.destroy
  selected
end