Class: Lich::Common::GUI::AccountManagerUI

Inherits:
Object
  • Object
show all
Defined in:
documented/common/gui/account_manager_ui.rb

Overview

User Interface for managing accounts in the Lich application. Provides functionality to create, add, and manage accounts and characters.

Examples:

Creating the management window

Lich::Common::GUI::AccountManagerUI.create_management_window("/path/to/data")

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data_dir) ⇒ AccountManagerUI

Returns a new instance of AccountManagerUI.



30
31
32
33
34
35
36
# File 'documented/common/gui/account_manager_ui.rb', line 30

def initialize(data_dir)
  @data_dir = data_dir
  @msgbox = ->(message) { show_message_dialog(message) }
  @data_change_callback = nil
  @tab_communicator = nil
  @notifications_registered = false
end

Class Method Details

.create_management_window(data_dir) ⇒ void

This method returns an undefined value.

Creates and displays the account management window.

Examples:

Lich::Common::GUI::AccountManagerUI.create_management_window("/path/to/data")

Parameters:

  • data_dir (String)

    The directory where account data is stored.



22
23
24
25
26
27
28
# File 'documented/common/gui/account_manager_ui.rb', line 22

def self.create_management_window(data_dir)
  # Create instance with data directory
  manager = new(data_dir)

  # Create and show the management window
  manager.show_management_window
end

Instance Method Details

#create_accounts_tab(notebook, insert_at_position = nil) ⇒ void

This method returns an undefined value.

Creates the accounts tab in the management window.

Examples:

manager.create_accounts_tab(notebook)

Parameters:

  • notebook (Gtk::Notebook)

    The notebook to add the tab to.

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

    The position to insert the tab at, or nil to append.



113
114
115
116
117
118
119
120
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
156
157
158
159
160
161
162
163
164
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
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
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
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
312
313
314
315
316
317
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/account_manager_ui.rb', line 113

def create_accounts_tab(notebook, insert_at_position = nil)
  # Store notebook reference for use in callbacks
  @notebook = notebook

  # Create tab content
  accounts_box = Gtk::Box.new(:vertical, 10)
  accounts_box.border_width = 10

  # Create accounts treeview with favorites support
  accounts_store = Gtk::TreeStore.new(String, String, String, String, String, String) # Added favorites column
  @accounts_store = accounts_store # Store reference for refresh operations
  accounts_view = Gtk::TreeView.new(accounts_store)

  # Enable sortable columns
  accounts_view.set_headers_clickable(true)
  accounts_store.set_default_sort_func { |_model, _a, _b| 0 } # Default no-op sort

  # Add columns with sorting
  renderer = Gtk::CellRendererText.new

  # Account column (not sortable - maintains account grouping)
  col = Gtk::TreeViewColumn.new("Account", renderer, text: 0)
  col.resizable = true
  accounts_view.append_column(col)

  # Character column - sortable
  col = Gtk::TreeViewColumn.new("Character", renderer, text: 1)
  col.resizable = true
  col.set_sort_column_id(1)
  col.clickable = true
  accounts_view.append_column(col)

  # Game column - sortable
  col = Gtk::TreeViewColumn.new("Game", renderer, text: 2)
  col.resizable = true
  col.set_sort_column_id(2)
  col.clickable = true
  accounts_view.append_column(col)

  # Frontend column - sortable
  col = Gtk::TreeViewColumn.new("Frontend", renderer, text: 3)
  col.resizable = true
  col.set_sort_column_id(3)
  col.clickable = true
  accounts_view.append_column(col)

  # Favorites column with clickable star (not sortable)
  favorites_renderer = Gtk::CellRendererText.new
  favorites_col = Gtk::TreeViewColumn.new("Favorite", favorites_renderer, text: 5)
  favorites_col.resizable = true
  accounts_view.append_column(favorites_col)

  # Set up custom sort functions that maintain account grouping
  (accounts_store)

  # Set up sort state persistence
  setup_sort_state_persistence(accounts_store)

  # Set up favorites column click handler
  setup_favorites_column_handler(accounts_view, favorites_col, @data_dir)

  # Create scrolled window
  sw = Gtk::ScrolledWindow.new
  sw.set_policy(:automatic, :automatic)
  sw.add(accounts_view)
  accounts_box.pack_start(sw, expand: true, fill: true, padding: 0)

  # Create button box
  button_box = Gtk::Box.new(:horizontal, 5)

  # Create refresh button
  refresh_button = Gtk::Button.new(label: "Refresh")
  button_box.pack_start(refresh_button, expand: false, fill: false, padding: 0)

  # Create remove button
  remove_button = Gtk::Button.new(label: "Remove")
  remove_button.sensitive = false
  button_box.pack_start(remove_button, expand: false, fill: false, padding: 0)

  # Create add account button
   = Gtk::Button.new(label: "Add Account")
  button_box.pack_start(, expand: false, fill: false, padding: 0)

  # Create add character button
  add_character_button = Gtk::Button.new(label: "Add Character")
  button_box.pack_start(add_character_button, expand: false, fill: false, padding: 0)

  # Create change password button
  change_password_button = Gtk::Button.new(label: "Change Password")
  change_password_button.sensitive = false

  # Set accessible properties for screen readers
  Accessibility.make_button_accessible(
    change_password_button,
    "Change Password Button",
    "Change the password for the selected account"
  )

  button_box.pack_start(change_password_button, expand: false, fill: false, padding: 0)

  accounts_box.pack_start(button_box, expand: false, fill: false, padding: 0)

  # Add tab to notebook and store index
  @tab_indices[:accounts] = notebook.append_page(accounts_box, Gtk::Label.new("Accounts"))

  # If recreating, move tab back to position 0
  if !insert_at_position.nil?
    notebook.reorder_child(accounts_box, insert_at_position)
  end

  # Set up refresh button handler
  refresh_button.signal_connect('clicked') do
    populate_accounts_view(accounts_store)
  end

  # Set up selection handler
  selection = accounts_view.selection
  selection.signal_connect('changed') do
    iter = selection.selected
    if iter
       = iter[0]
      character = iter[1] # Character is in column 1, not 2

      # Enable remove button for all selections
      remove_button.sensitive = !iter.nil?

      # Only enable change password for account nodes (not character nodes)
      change_password_button.sensitive = !.nil? && (character.nil? || character.empty?)
    else
      remove_button.sensitive = false
      change_password_button.sensitive = false
    end
  end

  # Set up remove button handler
  remove_button.signal_connect('clicked') do
    iter = selection.selected
    if iter
       = iter[0]
      character = iter[1] # Character is in column 1, not 2
      _game_name = iter[2] # Game name is in column 2, not character
      frontend_display = iter[3] # Frontend display name
      game_code = iter[4] # Game code is in hidden column 4

      if character.nil? || character.empty?
        # This is an account node
        # Confirm deletion
        dialog = Gtk::MessageDialog.new(
          parent: @window,
          flags: :modal,
          type: :question,
          buttons: :yes_no,
          message: "Delete account #{} and all its characters?"
        )
        response = dialog.run
        dialog.destroy

        if response == Gtk::ResponseType::YES
          # Remove account
          if AccountManager.(@data_dir, )
            @msgbox.call("Account removed successfully.")
            populate_accounts_view(accounts_store)
            # Notify other tabs of data change
            notify_data_changed(:account_removed, { account:  })
          else
            @msgbox.call("Failed to remove account.")
          end
        end
      else
        # This is a character node
        # Confirm deletion
        dialog = Gtk::MessageDialog.new(
          parent: @window,
          flags: :modal,
          type: :question,
          buttons: :yes_no,
          message: "Delete #{character} from #{}?"
        )
        response = dialog.run
        dialog.destroy

        if response == Gtk::ResponseType::YES
          # Convert display name back to internal frontend format for precise removal
          frontend = case frontend_display.downcase
                     when 'custom'
                       'stormfront' # Custom launches use stormfront as base
                     when 'wrayth'
                       'stormfront' # Wrayth is display name for stormfront
                     when 'wizard'
                       'wizard'
                     when 'avalon'
                       'avalon'
                     else
                       frontend_display.downcase
                     end

          # Remove character with frontend precision
          if AccountManager.remove_character(@data_dir, , character, game_code, frontend)
            @msgbox.call("Character removed successfully.")
            populate_accounts_view(accounts_store)
            # Notify other tabs of data change
            notify_data_changed(:character_removed, {
              account: ,
              character: character,
              game_code: game_code,
              frontend: frontend
            })
          else
            @msgbox.call("Failed to remove character.")
          end
        end
      end
    end
  end

  # Set up add account button handler - switch to Add Account tab
  .signal_connect('clicked') do
    # Switch to Add Account tab (index 2)
    notebook.set_page(2)
  end

  # Set up add character button handler - switch to Add Character tab
  add_character_button.signal_connect('clicked') do
    # Switch to Add Character tab (index 1)
    notebook.set_page(1)
  end
  # Set up change password button handler
  change_password_button.signal_connect('clicked') do
    iter = selection.selected
    if iter
       = iter[0]
      PasswordChange.show_password_change_dialog(@window, @data_dir, )
    end
  end

  # Populate accounts view
  populate_accounts_view(accounts_store)
end

#create_add_account_tab(notebook) ⇒ void

This method returns an undefined value.

Creates the add account tab in the management window.

Examples:

manager.(notebook)

Parameters:

  • notebook (Gtk::Notebook)

    The notebook to add the tab to.



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'documented/common/gui/account_manager_ui.rb', line 470

def (notebook)
  # Create tab content
   = Gtk::Box.new(:vertical, 10)
  .border_width = 10

  # Create username entry
  username_box = Gtk::Box.new(:horizontal, 5)
  username_box.pack_start(Gtk::Label.new("Username:"), expand: false, fill: false, padding: 0)

  username_entry = Gtk::Entry.new

  # Set accessible properties for screen readers
  Accessibility.make_entry_accessible(
    username_entry,
    "Username Entry",
    "Enter your account username"
  )

  username_box.pack_start(username_entry, expand: true, fill: true, padding: 0)

  .pack_start(username_box, expand: false, fill: false, padding: 0)

  # Create password entry
  password_box = Gtk::Box.new(:horizontal, 5)
  password_box.pack_start(Gtk::Label.new("Password:"), expand: false, fill: false, padding: 0)

  password_entry = Gtk::Entry.new
  password_entry.visibility = false
  password_box.pack_start(password_entry, expand: true, fill: true, padding: 0)

  .pack_start(password_box, expand: false, fill: false, padding: 0)

  # Create button box
  button_box = Gtk::Box.new(:horizontal, 5)

  # Create back button to return to accounts tab
  back_button = Gtk::Button.new(label: "Back to Accounts")
  button_box.pack_start(back_button, expand: false, fill: false, padding: 0)

  # Create add button
  add_button = Gtk::Button.new(label: "Add Account")
  button_box.pack_end(add_button, expand: false, fill: false, padding: 0)

  .pack_start(button_box, expand: false, fill: false, padding: 0)

  # Add tab to notebook and store index
  @tab_indices[:add_account] = notebook.append_page(, Gtk::Label.new("Add Account"))

  # Set up back button handler
  back_button.signal_connect('clicked') do
    # Switch to Accounts tab
    notebook.set_page(@tab_indices[:accounts])
  end

  # Set up add button handler with automatic account information collection
  add_button.signal_connect('clicked') do
    username = username_entry.text
    password = password_entry.text

    if username.empty?
      @msgbox.call("Username cannot be empty.")
      next
    end

    if password.empty?
      @msgbox.call("Password cannot be empty.")
      next
    end

    # Step 1: Check if account already exists in YAML structure
    if (username)
      @msgbox.call("Account '#{username}' already exists. Use 'Change Password' to update the password.")
      next
    end

    # Step 2: Perform authentication like manual login to collect account information
    begin
      # Authenticate with legacy mode to get character list (returns array of character hashes)
      auth_data = Authentication.authenticate(
        account: username,
        password: password,
        legacy: true
      )

      # Step 3: Collect all account information and convert to YAML format
      if auth_data && auth_data.is_a?(Array) && !auth_data.empty?
        # Show frontend selection dialog
        selected_frontend = show_frontend_selection_dialog
        return if selected_frontend.nil? # User cancelled

        # Convert character data to the format expected by YAML storage
        character_list = Lich::Common::GUI::AccountManager.convert_auth_data_to_characters(auth_data, selected_frontend)

        # Step 4: Save account and characters to entry.yml file
        if AccountManager.(@data_dir, username, password, character_list)
          @msgbox.call("Account '#{username}' added successfully with #{character_list.length} character(s).")
          username_entry.text = ""
          password_entry.text = ""

          # Step 5: Reset window interface to display results
          # Notify other tabs of data change
          notify_data_changed(:account_added, {
            account: username,
            characters: character_list
          })

          notebook.set_page(0)
          # Find the accounts store in the first tab and refresh it
          accounts_tab = notebook.get_nth_page(0)
          accounts_view = find_treeview_in_container(accounts_tab)
          if accounts_view
            populate_accounts_view(accounts_view.model)
          end
        else
          @msgbox.call("Failed to save account information.")
        end
      elsif auth_data && auth_data.is_a?(Array) && auth_data.empty?
        @msgbox.call("No characters found for account '#{username}'. Account will be added without characters.")
        # Save account without characters
        if AccountManager.(@data_dir, username, password, [])
          username_entry.text = ""
          password_entry.text = ""
          # Notify other tabs of data change
          notify_data_changed(:account_added, {
            account: username,
            characters: []
          })

          notebook.set_page(0)
          accounts_tab = notebook.get_nth_page(0)
          accounts_view = find_treeview_in_container(accounts_tab)
          if accounts_view
            populate_accounts_view(accounts_view.model)
          end
        else
          @msgbox.call("Failed to save account information.")
        end
      else
        @msgbox.call("Authentication failed or returned unexpected data format.")
      end
    rescue StandardError => e
      @msgbox.call("Authentication failed: #{e.message}")
    end
  end
  # Set up ENTER key handlers for username and password fields
  username_entry.signal_connect('key-press-event') { |_widget, event|
    if event.keyval == Gdk::Keyval::KEY_Return
      add_button.clicked
      true
    else
      false
    end
  }

  password_entry.signal_connect('key-press-event') { |_widget, event|
    if event.keyval == Gdk::Keyval::KEY_Return
      add_button.clicked
      true
    else
      false
    end
  }
end

#create_add_character_tab(notebook) ⇒ void

This method returns an undefined value.

Creates the add character tab in the management window.

Examples:

manager.create_add_character_tab(notebook)

Parameters:

  • notebook (Gtk::Notebook)

    The notebook to add the tab to.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'documented/common/gui/account_manager_ui.rb', line 357

def create_add_character_tab(notebook)
  # Create tab content
  add_box = Gtk::Box.new(:vertical, 10)
  add_box.border_width = 10

  # Create account selection
   = Gtk::Box.new(:horizontal, 5)
  .pack_start(Gtk::Label.new("Account:"), expand: false, fill: false, padding: 0)

   = Gtk::ComboBoxText.new
  .pack_start(, expand: true, fill: true, padding: 0)

  refresh_button = Gtk::Button.new(label: "Refresh")
  .pack_start(refresh_button, expand: false, fill: false, padding: 0)

  add_box.pack_start(, expand: false, fill: false, padding: 0)

  # Create character name entry
  char_box = Gtk::Box.new(:horizontal, 5)
  char_box.pack_start(Gtk::Label.new("Character:"), expand: false, fill: false, padding: 0)

  char_name_entry = Gtk::Entry.new
  char_box.pack_start(char_name_entry, expand: true, fill: true, padding: 0)

  add_box.pack_start(char_box, expand: false, fill: false, padding: 0)

  # Create game selection
  game_box = Gtk::Box.new(:horizontal, 5)
  game_box.pack_start(Gtk::Label.new("Game:"), expand: false, fill: false, padding: 0)

  game_combo = GameSelection.create_game_selection_combo
  game_box.pack_start(game_combo, expand: true, fill: true, padding: 0)
  add_box.pack_start(game_box, expand: false, fill: false, padding: 0)

  # Create frontend selection
  frontend_box = Gtk::Box.new(:horizontal, 5)
  frontend_box.pack_start(Gtk::Label.new("Frontend:"), expand: false, fill: false, padding: 0)

  frontend_radio_box = Gtk::Box.new(:horizontal, 5)
  stormfront_option = Gtk::RadioButton.new(label: 'Wrayth')
  wizard_option = Gtk::RadioButton.new(label: 'Wizard', member: stormfront_option)
  avalon_option = Gtk::RadioButton.new(label: 'Avalon', member: stormfront_option)

  frontend_radio_box.pack_start(stormfront_option, expand: false, fill: false, padding: 0)
  frontend_radio_box.pack_start(wizard_option, expand: false, fill: false, padding: 0)
  frontend_radio_box.pack_start(avalon_option, expand: false, fill: false, padding: 0) if RUBY_PLATFORM =~ /darwin/i

  frontend_box.pack_start(frontend_radio_box, expand: true, fill: true, padding: 0)

  add_box.pack_start(frontend_box, expand: false, fill: false, padding: 0)

  # Create custom launch options
  custom_launch_box = Gtk::Box.new(:horizontal, 5)
  custom_launch_box.pack_start(Gtk::Label.new("Custom Launch:"), expand: false, fill: false, padding: 0)

  custom_launch_entry = Gtk::Entry.new
  custom_launch_box.pack_start(custom_launch_entry, expand: true, fill: true, padding: 0)

  add_box.pack_start(custom_launch_box, expand: false, fill: false, padding: 0)

  # Create custom launch dir options
  custom_launch_dir_box = Gtk::Box.new(:horizontal, 5)
  custom_launch_dir_box.pack_start(Gtk::Label.new("Custom Launch Dir:"), expand: false, fill: false, padding: 0)

  custom_launch_dir_entry = Gtk::Entry.new
  custom_launch_dir_box.pack_start(custom_launch_dir_entry, expand: true, fill: true, padding: 0)

  add_box.pack_start(custom_launch_dir_box, expand: false, fill: false, padding: 0)

  # Create add button
  button_box = Gtk::Box.new(:horizontal, 5)
  add_button = Gtk::Button.new(label: "Add Character")
  button_box.pack_end(add_button, expand: false, fill: false, padding: 0)

  # Create back button to return to accounts tab
  back_button = Gtk::Button.new(label: "Back to Accounts")
  button_box.pack_start(back_button, expand: false, fill: false, padding: 0)

  add_box.pack_start(button_box, expand: false, fill: false, padding: 0)

  # Add tab to notebook and store index
  @tab_indices[:add_character] = notebook.append_page(add_box, Gtk::Label.new("Add Character"))

  # Set up back button handler
  back_button.signal_connect('clicked') do
    # Switch to Accounts tab
    notebook.set_page(@tab_indices[:accounts])
  end

  # Set up event handlers
  setup_add_character_handlers(
    add_button,
    ,
    refresh_button,
    char_name_entry,
    game_combo,
    stormfront_option,
    wizard_option,
    avalon_option,
    custom_launch_entry,
    custom_launch_dir_entry,
    notebook
  )

  # Populate account combo
  ()
end

#create_encryption_management_tab(notebook) ⇒ void

This method returns an undefined value.

Creates the encryption management tab in the management window.

Examples:

manager.create_encryption_management_tab(notebook)

Parameters:

  • notebook (Gtk::Notebook)

    The notebook to add the tab to.



639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'documented/common/gui/account_manager_ui.rb', line 639

def create_encryption_management_tab(notebook)
  # Create encryption management content
  encryption_box = Gtk::Box.new(:vertical, 10)
  encryption_box.border_width = 10

  # Create header label
  header_label = Gtk::Label.new
  header_label.set_markup("<span size='large' weight='bold'>Encryption Management</span>")
  header_label.set_xalign(0)

  Accessibility.make_accessible(
    header_label,
    "Encryption Management Header",
    "Title for the encryption management section",
    :label
  )

  encryption_box.pack_start(header_label, expand: false, fill: false, padding: 0)

  # Add description
  description = Gtk::Label.new
  description.set_markup("Manage your encryption settings and passwords")
  description.set_xalign(0)
  description.set_line_wrap(true)

  Accessibility.make_accessible(
    description,
    "Encryption Management Description",
    "Description of encryption management options",
    :label
  )

  encryption_box.pack_start(description, expand: false, fill: false, padding: 10)

  # Create button box
  button_box = Gtk::Box.new(:vertical, 10)
  button_box.border_width = 10

  # Check encryption mode and keychain availability
  has_keychain = MasterPasswordManager.keychain_available?
  mode = nil
  yaml_file = YamlState.yaml_file_path(@data_dir)

  if File.exist?(yaml_file)
    begin
      yaml_data = YAML.load_file(yaml_file)
      mode = yaml_data['encryption_mode']
    rescue StandardError => e
      Lich.log "error: Failed to load YAML for encryption management tab: #{e.message}"
    end
  end

  # Add "Change Encryption Password" button (only for Enhanced mode)
  if has_keychain && mode == 'enhanced'
    @change_encryption_password_button = Gtk::Button.new(label: "Change Encryption Password")
    @change_encryption_password_button.sensitive = false

    Accessibility.make_button_accessible(
      @change_encryption_password_button,
      "Change Encryption Password Button",
      "Change the encryption password for Enhanced encryption mode"
    )

    @change_encryption_password_button.signal_connect('clicked') do
      Gtk.queue do
        success = MasterPasswordChange.show_change_master_password_dialog(@window, @data_dir)
        populate_accounts_view(@accounts_store) if success
        update_encryption_password_button_state(@change_encryption_password_button)
      end
    end

    button_box.pack_start(@change_encryption_password_button, expand: false, fill: false, padding: 0)
  end

  # Add "Change Encryption Mode" button (always visible)
  @change_encryption_mode_button = Gtk::Button.new(label: "Change Encryption Mode")
  @change_encryption_mode_button.sensitive = false

  Accessibility.make_button_accessible(
    @change_encryption_mode_button,
    "Change Encryption Mode Button",
    "Change the encryption mode for all saved accounts"
  )

  @change_encryption_mode_button.signal_connect('clicked') do
    Gtk.queue do
      # Pass callback to be invoked after mode change completes
      on_completion = lambda do
        populate_accounts_view(@accounts_store)
        update_change_encryption_mode_button_state
        # Notify that encryption mode has changed to refresh encryption management tab
        notify_data_changed(:encryption_mode_changed, {})
      end
      EncryptionModeChange.show_change_mode_dialog(@window, @data_dir, on_completion)
    end
  end

  button_box.pack_start(@change_encryption_mode_button, expand: false, fill: false, padding: 0)

  encryption_box.pack_start(button_box, expand: false, fill: false, padding: 0)

  # Add spacer
  spacer = Gtk::Label.new("")
  encryption_box.pack_start(spacer, expand: true, fill: true, padding: 0)

  # Add tab to notebook and store index
  @tab_indices[:encryption_management] = notebook.append_page(encryption_box, Gtk::Label.new("Encryption Management"))

  # Update button states based on current encryption mode and accounts
  if @change_encryption_password_button
    update_encryption_password_button_state(@change_encryption_password_button)
  end
  update_change_encryption_mode_button_state
end

#refresh_accounts_displayvoid

This method returns an undefined value.

Refreshes the display of accounts in the UI.

Examples:

manager.refresh_accounts_display

Raises:

  • (StandardError)

    If an error occurs while refreshing the display.



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'documented/common/gui/account_manager_ui.rb', line 93

def refresh_accounts_display
  return unless @accounts_store

  begin
    # Clear existing data
    @accounts_store.clear

    # Repopulate with current data
    populate_accounts_view(@accounts_store)
  rescue StandardError => e
    Lich.log "error: Error refreshing accounts display: #{e.message}"
  end
end

#register_for_notifications(tab_communicator) ⇒ void

This method returns an undefined value.

Registers the UI to receive notifications about account changes.

Examples:

manager.register_for_notifications(tab_communicator)

Parameters:

  • tab_communicator (Object)

    The communicator for tab notifications.



52
53
54
55
# File 'documented/common/gui/account_manager_ui.rb', line 52

def register_for_notifications(tab_communicator)
  @tab_communicator = tab_communicator
  @accounts_store = nil # Will be set when accounts tab is created
end

#register_notification_callbackObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'documented/common/gui/account_manager_ui.rb', line 57

def register_notification_callback
  return if @notifications_registered || !@tab_communicator
  return unless @notebook

  @notifications_registered = true

  # Register callback to handle incoming notifications
  @tab_communicator.register_data_change_callback(->(change_type, data) {
    case change_type
    when :favorite_toggled
      # Refresh accounts view to reflect favorite changes
      refresh_accounts_display if @accounts_store
      Lich.log "info: Account manager refreshed for favorite change: #{data}"
    when :character_added, :character_removed, :account_added, :account_removed
      # Refresh accounts view for structural changes
      refresh_accounts_display if @accounts_store
      Lich.log "info: Account manager refreshed for data change: #{change_type}"
    when :encryption_mode_changed
      # Recreate encryption management tab to update button visibility/state
      if @notebook
        @notebook.remove_page(@tab_indices[:encryption_management])
        create_encryption_management_tab(@notebook)
        @notebook.show_all
        # Return to encryption management tab after recreation
        Gtk.queue { @notebook.set_current_page(@tab_indices[:encryption_management]) }
        Lich.log "info: Encryption management tab recreated for mode change"
      end
    end
  })
end

#set_data_change_callback(callback) ⇒ void

This method returns an undefined value.

Sets a callback to be invoked when account data changes.

Examples:

manager.set_data_change_callback(->(type, data) { puts "Data changed: #{type}" })

Parameters:

  • callback (Proc)

    The callback to be invoked on data changes.



43
44
45
# File 'documented/common/gui/account_manager_ui.rb', line 43

def set_data_change_callback(callback)
  @data_change_callback = callback
end