Module: Lich::Common::GUI::Accessibility

Defined in:
documented/common/gui/accessibility.rb

Class Method Summary collapse

Class Method Details

.add_keyboard_navigation(widget, can_focus = true, tab_order = nil) ⇒ void

This method returns an undefined value.

Adds keyboard navigation capabilities to a widget.

Examples:

Adding keyboard navigation

Accessibility.add_keyboard_navigation(my_widget, true, 1)

Parameters:

  • widget (Object)

    The widget to add keyboard navigation to.

  • can_focus (Boolean) (defaults to: true)

    Whether the widget can receive focus.

  • tab_order (Integer, nil) (defaults to: nil)

    The tab order of the widget.



145
146
147
148
149
150
151
152
153
154
155
156
# File 'documented/common/gui/accessibility.rb', line 145

def self.add_keyboard_navigation(widget, can_focus = true, tab_order = nil)
  begin
    widget.can_focus = can_focus

    # In GTK3, we need to check if the property exists before setting it
    if tab_order && widget.class.property?('tab-position')
      widget.set_property('tab-position', tab_order)
    end
  rescue StandardError => e
    Lich.log "warning: Could not set keyboard navigation: #{e.message}"
  end
end

.add_keyboard_shortcut(widget, key, modifiers = []) ⇒ void

This method returns an undefined value.

Adds a keyboard shortcut to a widget.

Examples:

Adding a keyboard shortcut

Accessibility.add_keyboard_shortcut(my_widget, "F1", [:control])

Parameters:

  • widget (Object)

    The widget to add the shortcut to.

  • key (String)

    The key for the shortcut.

  • modifiers (Array<Symbol>) (defaults to: [])

    The modifiers for the shortcut (e.g., [:control]).



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'documented/common/gui/accessibility.rb', line 165

def self.add_keyboard_shortcut(widget, key, modifiers = [])
  return unless widget.respond_to?(:add_accelerator)

  begin
    # Convert modifiers to Gdk::ModifierType
    modifier_mask = 0
    modifiers.each do |mod|
      case mod
      when :control, :ctrl
        modifier_mask |= Gdk::ModifierType::CONTROL_MASK
      when :shift
        modifier_mask |= Gdk::ModifierType::SHIFT_MASK
      when :alt
        modifier_mask |= Gdk::ModifierType::MOD1_MASK
      end
    end

    # Find or create accelerator group
    if widget.parent.is_a?(Gtk::Window)
      # Get the first accel group (replacing the unreachable loop)
      accel_group = widget.parent.accel_groups.first

      # Create new accel group if none found
      if accel_group.nil?
        accel_group = Gtk::AccelGroup.new
        widget.parent.add_accel_group(accel_group)
      end

      # Add accelerator
      widget.add_accelerator(
        "activate",
        accel_group,
        Gdk::Keyval.from_name(key),
        modifier_mask,
        Gtk::AccelFlags::VISIBLE
      )
    end
  rescue StandardError => e
    Lich.log "warning: Could not add keyboard shortcut: #{e.message}"
  end
end

.announce(widget, message, _priority = :medium) ⇒ void

This method returns an undefined value.

Announces a message through a widget for assistive technologies.

Examples:

Announcing a message

Accessibility.announce(my_widget, "New message received")

Parameters:

  • widget (Object)

    The widget to announce the message through.

  • message (String)

    The message to announce.

  • _priority (Symbol) (defaults to: :medium)

    The priority of the announcement (default: :medium).



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
243
244
245
# File 'documented/common/gui/accessibility.rb', line 214

def self.announce(widget, message, _priority = :medium)
  return unless widget.respond_to?(:get_accessible)

  begin
    accessible = widget.get_accessible
    return unless accessible

    # In GTK3/ATK, we can use state changes to trigger screen reader announcements
    if accessible.respond_to?(:notify_state_change)
      # Toggle state to trigger announcement
      accessible.notify_state_change(Atk::StateType::SHOWING, true)

      # Set name to message temporarily
      original_name = nil
      if accessible.respond_to?(:get_name) && accessible.respond_to?(:set_name)
        original_name = accessible.get_name
        accessible.set_name(message)
      end

      # Restore original name after a short delay
      if original_name
        # Using one-shot timeout (returns false to prevent repetition)
        GLib::Timeout.add(1000) do
          accessible.set_name(original_name)
          false # Intentionally return false to run only once
        end
      end
    end
  rescue StandardError => e
    Lich.log "warning: Could not announce message: #{e.message}"
  end
end

.create_accessible_label(container, input, text, position = :left) ⇒ Gtk::Label

Creates an accessible label for an input widget.

Examples:

Creating an accessible label

label = Accessibility.create_accessible_label(my_box, my_entry, "Username", :left)

Parameters:

  • container (Gtk::Box, Gtk::Grid)

    The container to add the label and input to.

  • input (Gtk::Widget)

    The input widget to associate with the label.

  • text (String)

    The text for the label.

  • position (Symbol) (defaults to: :left)

    The position of the label relative to the input (e.g., :left, :right).

Returns:

  • (Gtk::Label)

    The created label.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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
302
303
304
305
306
307
308
309
310
311
# File 'documented/common/gui/accessibility.rb', line 255

def self.create_accessible_label(container, input, text, position = :left)
  label = Gtk::Label.new(text)
  label.set_alignment(position == :left ? 1 : 0, 0.5)

  # Connect label to input for screen readers
  if input.respond_to?(:get_accessible) && label.respond_to?(:get_accessible)
    input_accessible = input.get_accessible
    label_accessible = label.get_accessible

    if input_accessible && label_accessible &&
       input_accessible.respond_to?(:add_relationship) &&
       defined?(Atk::RelationType::LABEL_FOR)
      label_accessible.add_relationship(Atk::RelationType::LABEL_FOR, input_accessible)
    end
  end

  # Add to container based on position
  case container
  when Gtk::Box
    case position
    when :left
      container.pack_start(label, expand: false, fill: false, padding: 5)
      container.pack_start(input, expand: true, fill: true, padding: 5)
    when :right
      container.pack_start(input, expand: true, fill: true, padding: 5)
      container.pack_start(label, expand: false, fill: false, padding: 5)
    when :top, :bottom
      # For top/bottom, we need to change the box orientation or create a vertical box
      vbox = if container.orientation == :vertical
               container
             else
               vbox = Gtk::Box.new(:vertical, 5)
               container.add(vbox)
               vbox
             end

      if position == :top
        vbox.pack_start(label, expand: false, fill: false, padding: 2)
        vbox.pack_start(input, expand: true, fill: true, padding: 2)
      else
        vbox.pack_start(input, expand: true, fill: true, padding: 2)
        vbox.pack_start(label, expand: false, fill: false, padding: 2)
      end
    end
  when Gtk::Grid
    # For Grid, we need row and column information which isn't provided
    # This is a simplified version
    container.add(label)
    container.add(input)
  else
    # For other containers, just add both
    container.add(label)
    container.add(input)
  end

  label
end

.get_atk_role(role_symbol) ⇒ Atk::Role?

Retrieves the ATK role corresponding to a given symbol.

Examples:

Getting an ATK role

role = Accessibility.get_atk_role(:button)

Parameters:

  • role_symbol (Symbol)

    The symbol representing the role (e.g., :button).

Returns:

  • (Atk::Role, nil)

    The corresponding ATK role or nil if not found.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'documented/common/gui/accessibility.rb', line 318

def self.get_atk_role(role_symbol)
  return nil unless defined?(Atk::Role)

  begin
    case role_symbol
    when :button then Atk::Role::PUSH_BUTTON
    when :text then Atk::Role::TEXT
    when :combo_box then Atk::Role::COMBO_BOX
    when :page_tab then Atk::Role::PAGE_TAB
    when :panel then Atk::Role::PANEL
    when :window then Atk::Role::FRAME
    when :label then Atk::Role::LABEL
    when :list then Atk::Role::LIST
    when :list_item then Atk::Role::LIST_ITEM
    when :menu then Atk::Role::MENU
    when :menu_item then Atk::Role::MENU_ITEM
    when :check_box then Atk::Role::CHECK_BOX
    when :radio_button then Atk::Role::RADIO_BUTTON
    when :dialog then Atk::Role::DIALOG
    when :separator then Atk::Role::SEPARATOR
    when :scroll_bar then Atk::Role::SCROLL_BAR
    when :slider then Atk::Role::SLIDER
    when :spin_button then Atk::Role::SPIN_BUTTON
    when :table then Atk::Role::TABLE
    when :tree then Atk::Role::TREE
    when :tree_item then Atk::Role::TREE_ITEM
    else nil
    end
  rescue StandardError => e
    Lich.log "warning: Could not get ATK role: #{e.message}"
    nil
  end
end

.initialize_accessibilityObject

Note:

Accessibility is not available on any non-linux platform.

Initializes accessibility features for the GUI. In GTK3, accessibility is enabled by default through ATK.

Examples:

Initializing accessibility

Lich::Common::GUI::Accessibility.initialize_accessibility


11
12
13
14
15
16
17
18
19
20
# File 'documented/common/gui/accessibility.rb', line 11

def self.initialize_accessibility
  # In GTK3, accessibility is enabled by default through ATK
  # Ensure ATK is loaded by referencing a Gail widget type
  # accessibility is not available on any non-linux platform
  begin
    GLib::Object.type_from_name('GailWidget')
  rescue NoMethodError => e
    Lich.log "warning: Could not initialize accessibility: #{e.message}" if OS.linux?
  end
end

.make_accessible(widget, label, description = nil, role = nil) ⇒ void

This method returns an undefined value.

Makes a widget accessible for assistive technologies.

Examples:

Making a widget accessible

Accessibility.make_accessible(my_widget, "My Widget", "This is a widget", :button)

Parameters:

  • widget (Object)

    The widget to make accessible.

  • label (String)

    The accessible name for the widget.

  • description (String, nil) (defaults to: nil)

    The accessible description for the widget.

  • role (Symbol, nil) (defaults to: nil)

    The role of the widget (e.g., :button, :text).



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'documented/common/gui/accessibility.rb', line 30

def self.make_accessible(widget, label, description = nil, role = nil)
  return unless widget.respond_to?(:get_accessible)

  begin
    accessible = widget.get_accessible
    return unless accessible

    # Set accessible name
    accessible.set_name(label) if accessible.respond_to?(:set_name)

    # Set accessible description
    accessible.set_description(description) if description && accessible.respond_to?(:set_description)

    # Set accessible role
    if role && accessible.respond_to?(:set_role)
      role_value = get_atk_role(role)
      accessible.set_role(role_value) if role_value
    end
  rescue StandardError => e
    Lich.log "warning: Could not make widget accessible: #{e.message}"
  end
end

.make_button_accessible(button, label, description = nil) ⇒ void

This method returns an undefined value.

Makes a button accessible for assistive technologies.

Examples:

Making a button accessible

Accessibility.make_button_accessible(my_button, "Submit", "Click to submit the form")

Parameters:

  • button (Gtk::Button)

    The button to make accessible.

  • label (String)

    The accessible name for the button.

  • description (String, nil) (defaults to: nil)

    The accessible description for the button.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'documented/common/gui/accessibility.rb', line 60

def self.make_button_accessible(button, label, description = nil)
  make_accessible(button, label, description, :button)

  # Ensure button has a visible label for screen readers
  begin
    if button.child.is_a?(Gtk::Label)
      button.child.set_text(label) if button.child.text.empty?
    elsif !button.label.nil? && button.label.empty?
      button.label = label
    end
  rescue StandardError => e
    Lich.log "warning: Could not set button label: #{e.message}"
  end
end

.make_combo_accessible(combo, label, description = nil) ⇒ void

This method returns an undefined value.

Makes a combo box accessible for assistive technologies.

Examples:

Making a combo box accessible

Accessibility.make_combo_accessible(my_combo, "Select an option", "Choose from the list")

Parameters:

  • combo (Gtk::ComboBox)

    The combo box to make accessible.

  • label (String)

    The accessible name for the combo box.

  • description (String, nil) (defaults to: nil)

    The accessible description for the combo box.



93
94
95
# File 'documented/common/gui/accessibility.rb', line 93

def self.make_combo_accessible(combo, label, description = nil)
  make_accessible(combo, label, description, :combo_box)
end

.make_entry_accessible(entry, label, description = nil) ⇒ void

This method returns an undefined value.

Makes an entry widget accessible for assistive technologies.

Examples:

Making an entry accessible

Accessibility.make_entry_accessible(my_entry, "Username", "Enter your username")

Parameters:

  • entry (Gtk::Entry)

    The entry widget to make accessible.

  • label (String)

    The accessible name for the entry.

  • description (String, nil) (defaults to: nil)

    The accessible description for the entry.



82
83
84
# File 'documented/common/gui/accessibility.rb', line 82

def self.make_entry_accessible(entry, label, description = nil)
  make_accessible(entry, label, description, :text)
end

.make_tab_accessible(notebook, page, tab_label, description = nil) ⇒ void

This method returns an undefined value.

Makes a tab in a notebook accessible for assistive technologies.

Examples:

Making a tab accessible

Accessibility.make_tab_accessible(my_notebook, my_page, "Tab 1", "This is the first tab")

Parameters:

  • notebook (Gtk::Notebook)

    The notebook containing the tab.

  • page (Gtk::Widget)

    The page to make accessible.

  • tab_label (String)

    The accessible name for the tab.

  • description (String, nil) (defaults to: nil)

    The accessible description for the tab.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'documented/common/gui/accessibility.rb', line 105

def self.make_tab_accessible(notebook, page, tab_label, description = nil)
  begin
    page_num = notebook.page_num(page)
    return if page_num == -1

    tab = notebook.get_tab_label(page)
    make_accessible(tab, tab_label, description, :page_tab)

    # Also make the page itself accessible
    make_accessible(page, tab_label, description, :panel)
  rescue StandardError => e
    Lich.log "warning: Could not make tab accessible: #{e.message}"
  end
end

.make_window_accessible(window, title, description = nil) ⇒ void

This method returns an undefined value.

Makes a window accessible for assistive technologies.

Examples:

Making a window accessible

Accessibility.make_window_accessible(my_window, "Main Window", "This is the main application window")

Parameters:

  • window (Gtk::Window)

    The window to make accessible.

  • title (String)

    The accessible name for the window.

  • description (String, nil) (defaults to: nil)

    The accessible description for the window.



127
128
129
130
131
132
133
134
135
136
# File 'documented/common/gui/accessibility.rb', line 127

def self.make_window_accessible(window, title, description = nil)
  make_accessible(window, title, description, :window)

  # Ensure window has a title for screen readers
  begin
    window.title = title if window.title.nil? || window.title.empty?
  rescue StandardError => e
    Lich.log "warning: Could not set window title: #{e.message}"
  end
end