• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-installation / 8705582428

16 Apr 2024 12:27PM UTC coverage: 40.913% (-0.2%) from 41.105%
8705582428

Pull #1114

github

shundhammer
Version bump and change log
Pull Request #1114: WIP: Handle autoinst AND autoupgrade (bsc#1222153)

0 of 1 new or added line in 1 file covered. (0.0%)

95 existing lines in 13 files now uncovered.

4489 of 10972 relevant lines covered (40.91%)

6.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.65
/src/lib/installation/proposal_runner.rb
1
# ------------------------------------------------------------------------------
2
# Copyright (c) 2006-2012 Novell, Inc. All Rights Reserved.
3
#
4
#
5
# This program is free software; you can redistribute it and/or modify it under
6
# the terms of version 2 of the GNU General Public License as published by the
7
# Free Software Foundation.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License along with
14
# this program; if not, contact Novell, Inc.
15
#
16
# To contact Novell about this file by physical or electronic mail, you may find
17
# current contact information at www.novell.com.
18
# ------------------------------------------------------------------------------
19

20
require "yast"
1✔
21

22
require "installation/proposal_store"
1✔
23
require "installation/proposal_errors"
1✔
24
require "ui/text_helpers"
1✔
25

26
module Installation
1✔
27
  # Create and display reasonable proposal for basic
28
  # installation and call sub-workflows as required
29
  # on user request.
30
  #
31
  # See {Installation::ProposalClient} from yast2 for API overview
32
  class ProposalRunner
1✔
33
    include Yast::I18n
1✔
34
    include Yast::UIShortcuts
1✔
35
    include Yast::Logger
1✔
36
    include ::UI::TextHelpers
1✔
37

38
    def self.run
1✔
39
      new.run
×
40
    end
41

42
    def initialize(store = ::Installation::ProposalStore)
1✔
43
      Yast.import "Pkg"
9✔
44
      Yast.import "UI"
9✔
45
      textdomain "installation"
9✔
46

47
      Yast.import "Label"
9✔
48
      Yast.import "Mode"
9✔
49
      Yast.import "Stage"
9✔
50
      Yast.import "AutoinstConfig"
9✔
51
      Yast.import "AutoinstFunctions"
9✔
52
      Yast.import "Wizard"
9✔
53
      Yast.import "HTML"
9✔
54
      Yast.import "Popup"
9✔
55
      Yast.import "Language"
9✔
56
      Yast.import "GetInstArgs"
9✔
57
      Yast.import "ProductControl"
9✔
58
      Yast.import "HTML"
9✔
59
      Yast.import "Packages"
9✔
60
      Yast.import "Report"
9✔
61
      Yast.import "UI"
9✔
62

63
      # values used in defined functions
64

65
      @submodules_presentation = []
9✔
66
      @mod2tab = {} # module -> tab it is in
9✔
67
      @current_tab = 0 # ID of current tab
9✔
68
      @html = {} # proposals of all modules - HTML part
9✔
69
      @have_blocker = false
9✔
70

71
      # BNC #463567
72
      @submods_already_called = []
9✔
73
      # NOTE: it would be better to receive the object already initialized. Why? Because if it
74
      # needs some argument, we do not know how to handle it.
75
      @store_class = store
9✔
76
      @errors = ProposalErrors.new
9✔
77
    end
78

79
    def run
1✔
80
      if Yast::Mode.auto
8✔
81
        # Checking if second stage is needed and the environment has been setup.
82
        second_stage_error = Yast::AutoinstFunctions.check_second_stage_environment
1✔
83

84
        if Yast::AutoinstConfig.Confirm
1✔
85
          # This string will be shown in the proposal overview
86
          Yast::AutoinstData.autoyast_second_stage_error = second_stage_error
×
87
        else
88
          # Checking if vnc, ssh,... is available
89
          error_message = Yast::Packages.check_remote_installation_packages
1✔
90
          # Fit to the given UI
91
          displayinfo = Yast::UI.GetDisplayInfo || {}
1✔
92
          width = displayinfo["TextMode"] ? displayinfo.fetch("Width", 80) : 80
1✔
93
          unless error_message.empty?
1✔
94
            Yast::Report.Warning(wrap_text(error_message,
×
95
              width - 4))
96
          end
97
          unless second_stage_error.empty?
1✔
98
            Yast::Report.Warning(wrap_text(second_stage_error,
×
99
              width - 4))
100
          end
101
          # skip if not interactive mode.
102
          return :auto
1✔
103
        end
104
      end
105

106
      log.info "Installation step #2"
7✔
107
      @proposal_mode = Yast::GetInstArgs.proposal
7✔
108

109
      return :auto if Yast::ProductControl.GetDisabledProposals.include?(@proposal_mode)
7✔
110

111
      @store = @store_class.new(@proposal_mode)
6✔
112

113
      build_dialog
6✔
114

115
      #
116
      # Get submodule descriptions
117
      #
118
      proposal_result = load_matching_submodules_list
6✔
119
      return :abort if proposal_result == :abort
6✔
120

121
      Yast::UI.ChangeWidget(Id(:menu_dummy), :Enabled, false) if Yast::UI.TextMode
6✔
122
      richtext_busy_cursor(Id(:proposal))
6✔
123

124
      # The "next" button is disabled via Wizard::SetContents() until everything is set up allright
125
      Yast::Wizard.EnableNextButton
6✔
126
      Yast::Wizard.EnableAbortButton
6✔
127

128
      return :auto if !submod_descriptions_and_build_menu
6✔
129

130
      # Default language is the most often set by language proposal later in the workflow
131
      # and overwrites this.
132
      # However, some products do not contain such proposal (e.g. CaaSP) and needs this
133
      # setup to avoid false language change detection (e.g. in software proposal)
134
      Yast::Pkg.SetPackageLocale(Yast::Language.language)
5✔
135

136
      #
137
      # Make the initial proposal
138
      #
139
      make_proposal(false, false)
5✔
140

141
      # Set keyboard focus to the [Install] / [Update] or [Next] button
142
      Yast::Wizard.SetFocusToNextButton
5✔
143

144
      input_loop
5✔
145
    end
146

147
  private
1✔
148

149
    # Shows dialog to user to confirm update and return user response.
150
    # Returns 'true' if the user confirms, 'false' otherwise.
151
    #
152
    def confirm_update
1✔
153
      # Heading for confirmation popup before the update really starts
154
      heading = Yast::HTML.Heading(_("Confirm Update"))
×
155

UNCOV
156
      body =
×
157
        # Text for confirmation popup before the update really starts 1/3
158
        _("<p>Information required to perform an update is now complete.</p>") +
159
        # Text for confirmation popup before the update really starts 2/3
160
        _(
161
          "\n" \
162
          "<p>If you continue now, data on your hard disk will be overwritten\n" \
163
          "according to the settings in the previous dialogs.</p>"
164
        ) +
165
        # Text for confirmation popup before the update really starts 3/3
166
        _("<p>Go back and check the settings if you are unsure.</p>")
167

168
      # Label for the button that confirms startint the installation
169
      confirm_button_label = _("Start &Update")
×
170

171
      size_x = 70
×
172
      size_y = 18
×
173

174
      Yast::UI.OpenDialog(
×
175
        VBox(
176
          VSpacing(0.4),
177
          HSpacing(size_x), # force width
178
          HBox(
179
            HSpacing(0.7),
180
            VSpacing(size_y), # force height
181
            RichText(heading + body),
182
            HSpacing(0.7)
183
          ),
184
          ButtonBox(
185
            PushButton(
186
              Id(:cancel),
187
              Opt(:cancelButton, :key_F10, :default),
188
              Yast::Label.BackButton
189
            ),
190
            PushButton(Id(:ok), Opt(:okButton, :key_F9), confirm_button_label)
191
          )
192
        )
193
      )
194

195
      button = Yast::UI.UserInput
×
196
      Yast::UI.CloseDialog
×
197

198
      button == :ok
×
199
    end
200

201
    def input_loop
1✔
202
      loop do
5✔
203
        richtext_normal_cursor(Id(:proposal))
5✔
204

205
        # bnc #431567
206
        # Some proposal module can change it while called
207
        assign_next_button
5✔
208

209
        restore_vertical_scroll
5✔
210
        input = Yast::UI.UserInput
5✔
211

212
        log.info "Proposal - UserInput: '#{input}'"
5✔
213
        return :next if input == :accept
5✔
214
        return :abort if input == :cancel
×
215

216
        save_vertical_scroll
×
217
        richtext_busy_cursor(Id(:proposal))
×
218

219
        case input
×
220
        when ::Integer # tabs
221
          switch_to_tab(input)
×
222

223
        when ::String # hyperlink
224
          input = submod_ask_user(input)
×
225

226
          # The workflow_sequence doesn't get handled as a workflow sequence
227
          # so we have to do this special case here. Kind of broken.
228
          return :finish if input == :finish
×
229

230
        when :finish
231
          return :finish
×
232

233
        when :abort
234
          abort_mode = Yast::Stage.initial ? :painless : :incomplete
×
235
          return :abort if Yast::Popup.ConfirmAbort(abort_mode)
×
236

237
        when :reset_to_defaults
238
          next unless Yast::Popup.ContinueCancel(
×
239
            # question in a popup box
240
            _("Really reset everything to default values?") + "\n" +
241
              # explain consequences of a decision
242
              _("You will lose all changes.")
243
          )
244

245
          make_proposal(true, false) # force_reset
×
246

247
        when :skip, :dontskip
248
          handle_skip
×
249

250
        when :next
251
          input = pre_continue_handling
×
252
          if input == :next
×
253
            # anything that needs to be done before
254
            # real installation starts
255

256
            write_settings unless @skip
×
257

258
            return :next
×
259
          end
260

261
        when :back
262
          Yast::Wizard.SetNextButton(:next, Yast::Label.NextButton) if Yast::Stage.initial
×
263
          return :back
×
264
        end
265
      end
266

267
      nil
268
    end
269

270
    def save_vertical_scroll
1✔
271
      @proposal_vscroll = Yast::UI.QueryWidget(Id(:proposal), :VScrollValue)
×
272
    end
273

274
    # Restores the vertical scroll if previously saved
275
    def restore_vertical_scroll
1✔
276
      return unless @proposal_vscroll
5✔
277

278
      Yast::UI.ChangeWidget(Id(:proposal), :VScrollValue, @proposal_vscroll)
×
279
    end
280

281
    def switch_to_tab(input)
1✔
282
      @current_tab = input
×
283
      load_matching_submodules_list
×
284
      @proposal = ""
×
285
      @submodules_presentation.each do |mod|
×
286
        @proposal << (@html[mod] || "")
×
287
      end
288
      display_proposal(div_with_direction(@proposal))
×
289
      submod_descriptions_and_build_menu
×
290
    end
291

292
    def handle_skip
1✔
293
      if Yast::UI.QueryWidget(Id(:skip), :Value)
×
294
        # User doesn't want to use any of the settings
295
        Yast::UI.ChangeWidget(
×
296
          Id(:proposal),
297
          :Value,
298
          Yast::HTML.Newlines(3) +
299
            # message show when user has disabled the configuration
300
            Yast::HTML.Para(_("Skipping configuration upon user request"))
301
        )
302
        Yast::UI.ChangeWidget(Id(:menu), :Enabled, false)
×
303
      else
304
        # User changed his mind and wants the settings back - recreate them
305
        make_proposal(false, false)
×
306
        Yast::UI.ChangeWidget(Id(:menu), :Enabled, true)
×
307
      end
308
    end
309

310
    def pre_continue_handling
1✔
311
      @skip = if Yast::UI.WidgetExists(Id(:skip))
×
312
        val = Yast::UI.QueryWidget(Id(:skip), :Value)
×
313
        log.info "there is :skip widget with value #{val.inspect}."
×
314
        val
×
315
      else
316
        true
×
317
      end
318
      skip_blocker = Yast::UI.WidgetExists(Id(:skip)) && @skip
×
319
      if @have_blocker && !skip_blocker
×
320
        # error message is a popup
321
        Yast::Popup.Error(
×
322
          _(
323
            "The proposal contains an error that must be\nresolved before continuing.\n"
324
          )
325
        )
326
        return nil
×
327
      end
328

329
      return nil unless @errors.approved?
×
330

331
      if Yast::Stage.stage == "initial"
×
332
        input = Yast::WFM.CallFunction("inst_doit", [])
×
333
      # bugzilla #219097, #221571, yast2-update on running system
334
      elsif Yast::Stage.stage == "normal" && Yast::Mode.update
×
335
        input = confirm_update ? :next : nil
×
336
        log.info "Update not confirmed, returning back..." unless input
×
337
      else
338
        # sane default for other cases (bsc#944334)
339
        input = :next
×
340
      end
341

342
      input
×
343
    end
344

345
    # Display preformatted proposal in the RichText widget
346
    #
347
    # @param [String] proposal human readable proposal preformatted in HTML
348
    #
349
    def display_proposal(proposal)
1✔
350
      if Yast::UI.WidgetExists(Id(:proposal))
1✔
351
        Yast::UI.ChangeWidget(Id(:proposal), :Value, proposal)
×
352
      else
353
        Yast::Builtins.y2error(-1, "Widget `proposal does not exist")
1✔
354
      end
355

356
      nil
357
    end
358

359
    def check_windows_left
1✔
360
      if !Yast::UI.WidgetExists(Id(:proposal))
×
361
        Yast::Builtins.y2error(-1, "Widget `proposal is not active!!!")
×
362
        log.info "--- Current widget tree ---"
×
363
        Yast::UI.DumpWidgetTree
×
364
        log.info "--- Current widget tree ---"
×
365
      end
366

367
      nil
368
    end
369

370
    # Call a submodule's AskUser() function.
371
    #
372
    # @param [String] input passed link from proposal dispatcher
373
    # @return workflow_sequence see proposal-API.txt, or nil if the link cannot be handled
374
    #   (is read-only)
375
    def submod_ask_user(input)
1✔
376
      # Call the AskUser() function
377
      ask_user_result = @store.handle_link(input)
×
378

379
      # read-only proposal
380
      return nil if ask_user_result.nil?
×
381

382
      workflow_sequence = ask_user_result["workflow_sequence"] || :next
×
383
      language_changed = ask_user_result.fetch("language_changed", false)
×
384
      mode_changed = ask_user_result.fetch("mode_changed", false)
×
385

386
      if ![:cancel, :back, :abort, :finish].include?(workflow_sequence)
×
387
        if language_changed
×
388
          retranslate_proposal_dialog
×
389
          Yast::Pkg.SetTextLocale(Yast::Language.language)
×
390
          Yast::Pkg.SetPackageLocale(Yast::Language.language)
×
391
          Yast::Pkg.SetAdditionalLocales([Yast::Language.language])
×
392
        end
393

394
        if mode_changed
×
395
          Yast::Wizard.SetHelpText(@store.help_text(@current_tab))
×
396

397
          build_dialog
×
398
          load_matching_submodules_list
×
399
          log.error "i'm in dutch" unless submod_descriptions_and_build_menu
×
400
        end
401

402
        # Make a new proposal based on those user changes
403
        make_proposal(false, language_changed)
×
404
      end
405

406
      # There might be some UI layers left
407
      # we need to close them
408
      check_windows_left
×
409

410
      workflow_sequence
×
411
    end
412

413
    # Checks if given proposal map contains an error report
414
    #
415
    # @param [Hash] proposal map as returned by make_proposal
416
    # @return [Boolean] true if the map reports an issue in proposal
417
    # @see ProposalClient#make_proposal
418
    def proposal_failed?(proposal)
1✔
419
      proposal && [:blocker, :fatal, :error].include?(proposal["warning_level"])
1✔
420
    end
421

422
    def make_proposal(force_reset, language_changed)
1✔
423
      tab_to_switch = 999
5✔
424
      current_tab_affected = false
5✔
425
      @have_blocker = false
5✔
426

427
      Yast::UI.ReplaceWidget(
5✔
428
        Id("inst_proposal_progress"),
429
        ProgressBar(
430
          Id("pb_ip"),
431
          "",
432
          2 * @store.proposal_names.size,
433
          0
434
        )
435
      )
436

437
      @html = {}
5✔
438
      @store.proposal_names.each do |submod|
5✔
439
        prop = html_header(submod)
9✔
440
        # BNC #463567
441
        # Submod already called
442
        if @submods_already_called.include?(submod)
9✔
443
          # busy message
444
          message = _("Adapting the proposal to the current settings...")
×
445
        # First run
446
        else
447
          # busy message;
448
          message = _("Analyzing your system...")
9✔
449
          @submods_already_called << submod
9✔
450
        end
451
        @html[submod] = prop + Yast::HTML.Para(message)
9✔
452
      end
453

454
      Yast::Wizard.DisableNextButton
5✔
455
      Yast::UI.BusyCursor
5✔
456

457
      submodule_nr = 0
5✔
458
      make_proposal_callback = proc do |submod, prop_map|
5✔
459
        submodule_nr += 1
1✔
460
        Yast::UI.ChangeWidget(Id("pb_ip"), :Value, submodule_nr)
1✔
461
        force_rw = proposal_failed?(prop_map) && @store.soft_read_only?(submod)
1✔
462
        prop = html_header(submod, force_rw: force_rw)
1✔
463

464
        # check if it is needed to switch to another tab
465
        # because of an error
466
        if Yast::Builtins.haskey(@mod2tab, submod)
1✔
467
          log.info "Mod2Tab: '#{@mod2tab[submod]}'"
×
468
          if proposal_failed?(prop_map)
×
469
            # bugzilla #237291
470
            # always switch to more detailed tab only
471
            # value 999 means to keep current tab, in case of error,
472
            # tab must be switched (bnc #441434)
473
            if @mod2tab[submod] > tab_to_switch ||
×
474
                tab_to_switch == 999
475
              tab_to_switch = @mod2tab[submod]
×
476
            end
477
            current_tab_affected = true if @mod2tab[submod] == @current_tab
×
478
          end
479
        end
480

481
        submodule_nr += 1
1✔
482
        Yast::UI.ChangeWidget(Id("pb_ip"), :Value, submodule_nr)
1✔
483

484
        if prop_map["language_changed"]
1✔
485
          retranslate_proposal_dialog
×
486
          submodule_nr = 0
×
487
        else
488
          prop << format_sub_proposal(prop_map)
1✔
489

490
          @html[submod] = prop
1✔
491

492
          # now do the complete html
493
          load_matching_submodules_list
1✔
494
          proposal = @submodules_presentation.reduce("") do |res, mod|
1✔
495
            res << (@html[mod] || "")
1✔
496
          end
497
          display_proposal(div_with_direction(proposal))
1✔
498
        end
499
      end
500

501
      @errors.clear
5✔
502
      @store.make_proposals(
5✔
503
        force_reset:      force_reset,
504
        language_changed: language_changed,
505
        callback:         make_proposal_callback
506
      )
507

508
      # FATE #301151: Allow YaST proposals to have help texts
509
      Yast::Wizard.SetHelpText(@store.help_text(@current_tab))
5✔
510

511
      switch_to_tab(tab_to_switch) if @store.tabs? && Yast::Ops.less_than(tab_to_switch,
5✔
512
        999) && !current_tab_affected
513

514
      # now do the display-only proposals
515

516
      Yast::UI.ReplaceWidget(Id("inst_proposal_progress"), Empty())
5✔
517
      Yast::Wizard.EnableNextButton
5✔
518
      Yast::UI.NormalCursor
5✔
519

520
      nil
521
    end
522

523
    def format_sub_proposal(prop)
1✔
524
      html = ""
1✔
525
      warning = prop["warning"] || ""
1✔
526

527
      if !warning.empty?
1✔
528
        level = prop["warning_level"] || :warning
×
529
        log.info "proposal returns warning with level #{level} and msg #{warning}"
×
530

531
        case level
×
532
        when :notice
533
          warning = Yast::HTML.Bold(warning)
×
534
        when :warning
535
          warning = Yast::HTML.Colorize(warning, "red")
×
536
        when :error
537
          @errors.append(warning)
×
538
          warning = Yast::HTML.Colorize(warning, "red")
×
539
        when :blocker, :fatal
540
          @have_blocker = true
×
541
          warning = Yast::HTML.Colorize(warning, "red")
×
542
        end
543

544
        html << Yast::HTML.Para(warning)
×
545
      end
546

547
      preformatted_prop = prop["preformatted_proposal"] || ""
1✔
548

549
      if preformatted_prop.empty?
1✔
550
        # fallback proposal, means usually an internal error
551
        raw_prop = prop["raw_proposal"] || [_("ERROR: No proposal")]
1✔
552
        html << Yast::HTML.List(raw_prop)
1✔
553
      else
554
        html << preformatted_prop
×
555
      end
556

557
      html
1✔
558
    end
559

560
    # Call a submodule's Write() function.
561
    #
562
    # @param [String] submodule  name of the submodule's proposal dispatcher
563
    # @return success    true if Write() was successful of if there is no Write() function
564
    #
565
    def submod_write_settings(submodule)
1✔
566
      result = Yast::WFM.CallFunction(submodule, ["Write", {}]) || {}
×
567

568
      result.fetch("success", true)
×
569
    end
570

571
    # Call each submodule's "Write()" function to let it write its settings,
572
    # i.e. the settings effective.
573
    #
574
    def write_settings
1✔
575
      success = true
×
576

577
      log.info "Writting settings for proposal"
×
578

579
      @store.proposal_names.each do |submod|
×
580
        submod_success = submod_write_settings(submod)
×
581
        submod_success = true if submod_success.nil?
×
582
        log.error "Write() failed for submodule #{submod}" unless submod_success
×
583
        success &&= submod_success
×
584
      end
585

586
      return nil if success
×
587

588
      log.error "Write() failed for one or more submodules"
×
589

590
      # Submodules handle their own error reporting
591
      # text for a message box
592
      Yast::Popup.TimedMessage(_("Configuration saved.\nThere were errors."), 3)
×
593
    end
594

595
    # Force a RichText widget to use the busy cursor
596
    #
597
    # @param [Object] widget_id  ID  of the widget, e.g. `id(`proposal)
598
    #
599
    def richtext_busy_cursor(widget_id)
1✔
600
      Yast::UI.ChangeWidget(widget_id, :Enabled, false)
6✔
601

602
      nil
603
    end
604

605
    # Switch a RichText widget back to use the normal cursor
606
    #
607
    # @param [Object] widget_id  ID  of the widget, e.g. `id(`proposal)
608
    #
609
    def richtext_normal_cursor(widget_id)
1✔
610
      Yast::UI.ChangeWidget(widget_id, :Enabled, true)
5✔
611

612
      nil
613
    end
614

615
    def retranslate_proposal_dialog
1✔
616
      log.debug "Retranslating proposal dialog"
×
617

618
      build_dialog
×
619
      Yast::ProductControl.RetranslateWizardSteps
×
620
      Yast::Wizard.RetranslateButtons
×
621
      submod_descriptions_and_build_menu
×
622

623
      nil
624
    end
625

626
    def load_matching_submodules_list
1✔
627
      Yast::Builtins.y2milestone(
7✔
628
        "getting proposals for stage: \"%1\" mode: \"%2\" proposal type: \"%3\"",
629
        Yast::Stage.stage,
630
        Yast::Mode.mode,
631
        @proposal_mode
632
      )
633

634
      if @store.proposal_names.empty?
7✔
635
        log.error "No proposals available"
×
636
        return :abort
×
637
      end
638

639
      if @store.tabs?
7✔
640
        log.info "Proposal uses tabs"
2✔
641
        @submodules_presentation = @store.presentation_order[@current_tab]
2✔
642
        @mod2tab = {}
2✔
643
        @store.presentation_order.each_index do |index|
2✔
644
          @store.presentation_order[index].each do |mod|
4✔
645
            @mod2tab[mod] = index
6✔
646
          end
647
        end
648

649
        p = Yast::AutoinstConfig.getProposalList
2✔
650
        @submodules_presentation = Yast::Builtins.filter(@submodules_presentation) do |v|
2✔
651
          Yast::Builtins.contains(p, v) || p == []
2✔
652
        end
653
      else
654
        log.info "Proposal doesn't use tabs"
5✔
655

656
        # setup the list
657
        @submodules_presentation = @store.presentation_order
5✔
658

659
        proposals = Yast::AutoinstConfig.getProposalList
5✔
660

661
        if proposals && !proposals.empty?
5✔
662
          # array intersection
663
          @submodules_presentation &= proposals
1✔
664
        end
665
      end
666

667
      log.info "Presentation order: #{@submodules_presentation}"
7✔
668
      log.info "Execution order: #{@store.proposal_names}"
7✔
669

670
      nil
671
    end
672

673
    def skip_buttons
1✔
674
      # radiobuttons
675
      RadioButtonGroup(
×
676
        VBox(
677
          VSpacing(1),
678
          Left(
679
            RadioButton(
680
              Id(:skip),
681
              Opt(:notify),
682
              # Check box: Skip all the configurations in this dialog -
683
              # do this later manually or not at all
684
              # Translators: About 40 characters max,
685
              # use newlines for longer translations.
686
              # radio button
687
              _("&Skip Configuration"),
688
              false
689
            )
690
          ),
691
          Left(
692
            RadioButton(
693
              Id(:dontskip),
694
              Opt(:notify),
695
              # radio button
696
              _("&Use Following Configuration"),
697
              true
698
            )
699
          ),
700
          VSpacing(1)
701
        )
702
      )
703
    end
704

705
    def build_dialog
1✔
706
      headline = @store.headline
6✔
707
      change_point = Empty()
6✔
708

709
      if Yast::UI.TextMode()
6✔
710
        change_point = ReplacePoint(
×
711
          Id(:rep_menu),
712
          # menu button
713
          MenuButton(Id(:menu_dummy), _("&Change..."), [Item(Id(:dummy), "")])
714
        )
715
      end
716

717
      # change menu
718
      menu_box = VBox(
6✔
719
        HBox(
720
          HStretch(),
721
          change_point,
722
          HStretch()
723
        ),
724
        ReplacePoint(Id("inst_proposal_progress"), Empty())
725
      )
726

727
      enable_skip = @store.can_be_skipped?
6✔
728

729
      rt = RichText(
6✔
730
        Id(:proposal),
731
        Yast::HTML.Newlines(3) +
732
          # Initial contents of proposal subwindow while proposals are calculated
733
          Yast::HTML.Para(_("Analyzing your system..."))
734
      )
735

736
      if @store.tabs?
6✔
737
        tab_labels = @store.tab_labels
2✔
738
        if Yast::UI.HasSpecialWidget(:DumbTab)
2✔
739
          panes = tab_labels.map.with_index(0) do |label, id|
×
740
            Item(Id(id), label, label == tab_labels.first)
×
741
          end
742
          rt = DumbTab(Id(:_cwm_tab), panes, rt)
×
743
        else
744
          box = HBox()
2✔
745
          tabbar = tab_labels.map.with_index(0) do |label, id|
2✔
746
            box.params << PushButton(Id(id), label)
4✔
747
          end
748
          rt = VBox(Left(tabbar), Frame("", rt))
2✔
749
        end
750
      end
751

752
      vbox = if enable_skip
6✔
753
        VBox(skip_buttons, HBox(HSpacing(4), rt), menu_box)
×
754
      else
755
        VBox(
6✔
756
          # Help message between headline and installation proposal / settings summary.
757
          # May contain newlines, but don't make it very much longer than the original.
758
          Left(
759
            Label(
760
              if Yast::UI.TextMode()
6✔
761
                _(
×
762
                  "Click a headline to make changes or use the \"Change...\" menu below."
763
                )
764
              else
765
                _(
6✔
766
                  "Click a headline to make changes."
767
                )
768
              end
769
            )
770
          ),
771
          rt,
772
          menu_box
773
        )
774
      end
775

776
      Yast::Wizard.SetContents(
6✔
777
        headline, # have_next_button
778
        vbox,
779
        @store.help_text(@current_tab),
780
        Yast::GetInstArgs.enable_back, # have_back_button
781
        false
782
      )
783

784
      if Yast::UI.WidgetExists(:_cwm_tab)
6✔
785
        Yast::UI.ChangeWidget(Id(:_cwm_tab), :CurrentItem, @current_tab)
×
786
      else
787
        log.info "Not using CWM tabs..."
6✔
788
      end
789

790
      nil
791
    end
792

793
    def submod_descriptions_and_build_menu
1✔
794
      return true unless Yast::UI.TextMode # have menu only in text mode
5✔
795

796
      # now build the menu button
797
      menu_list = @submodules_presentation.each_with_object([]) do |submod, menu|
×
798
        # skip read-only proposals
799
        next if @store.read_only?(submod)
×
800

801
        descr = @store.description_for(submod) || {}
×
802
        next if descr.empty?
×
803

804
        id = descr["id"]
×
805
        if descr.key? "menu_titles"
×
806
          descr["menu_titles"].each do |i|
×
807
            id2 = i["id"]
×
808
            title = i["title"]
×
809
            if id2 && title
×
810
              menu << Item(Id(id2), title + "...")
×
811
            else
812
              log.info "Invalid menu item: #{i}"
×
813
            end
814
          end
815
        else
816
          menu_title = descr["menu_title"] ||
×
817
            descr["rich_text_title"] ||
818
            submod
819

820
          menu << Item(Id(id), menu_title + "...")
×
821
        end
822
      end
823

824
      # menu button item
825
      menu_list << Item(Id(:reset_to_defaults), _("&Reset to defaults"))
×
826

827
      # menu button
828
      Yast::UI.ReplaceWidget(
×
829
        Id(:rep_menu),
830
        MenuButton(Id(:menu), _("&Change..."), menu_list)
831
      )
832

833
      !@store.descriptions.empty?
×
834
    end
835

836
    def assign_next_button
1✔
837
      if Yast::Stage.initial && @proposal_mode == "initial"
5✔
838
        Yast::Wizard.SetNextButton(
×
839
          :next,
840
          # FATE #120373
841
          Yast::Mode.update ? _("&Update") : _("&Install")
×
842
        )
843
      end
844

845
      nil
846
    end
847

848
    # Get the header for the specific proposal module
849
    # @param submod [String] the proposal module name
850
    # @return [String] richtext string with the proposal header
851
    def html_header(submod, force_rw: false)
1✔
852
      title = @store.title_for(submod)
11✔
853

854
      # do not add a link if the module is read-only or link is already included
855
      heading = if (!force_rw && @store.read_only?(submod)) || title.include?("<a")
11✔
856
        title
1✔
857
      else
858
        Yast::HTML.Link(title, @store.id_for(submod))
10✔
859
      end
860

861
      Yast::HTML.Heading(heading)
11✔
862
    end
863
  end
864
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc