yast/yast-registration

View on GitHub
src/lib/registration/ui/addon_selection_base_dialog.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany.

require "yast"
require "registration/ui/abort_confirmation"
require "registration/addon"
require "registration/addon_sorter"
require "registration/sw_mgmt"

module Registration
  module UI
    # this class displays and runs the dialog with addon selection
    class AddonSelectionBaseDialog
      include Yast::Logger
      include Yast::I18n
      include Yast::UIShortcuts
      include Yast

      Yast.import "GetInstArgs"
      Yast.import "Popup"
      Yast.import "Report"
      Yast.import "UI"
      Yast.import "Wizard"
      Yast.import "Stage"
      Yast.import "Arch"

      class << self
        attr_accessor :filter_devel
      end

      FILTER_DEVEL_INITIALLY = true

      # constructor
      # @param registration [Registration::Registration] use this Registration object for
      #   communication with SCC
      def initialize(registration)
        textdomain "registration"
        @all_addons = Addon.find_all(registration)

        # sort the addons
        @all_addons.sort!(&::Registration::ADDON_SORTER)

        self.class.filter_devel = FILTER_DEVEL_INITIALLY if self.class.filter_devel.nil?
        preselect_recommended
        filter_devel_releases(self.class.filter_devel)

        @old_selection = Addon.selected.dup
      end

      # reimplement this in a subclass
      # display the extension selection dialog and wait for a button click
      # @return [Symbol] user input
      def run
        raise "Not implemented"
      end

    protected

      # create widget ID for an addon
      # @param [<Addon>] addon the addon
      # @return [String] widget id
      def addon_widget_id(addon)
        "#{addon.identifier}-#{addon.version}-#{addon.arch}"
      end

      # Enables or disables devel addons filtering
      # @param [Boolean] enable true for filtering devel releases
      def filter_devel_releases(enable)
        self.class.filter_devel = enable
        if enable
          @addons = @all_addons.select do |a|
            a.registered? || a.selected? || a.auto_selected? ||
              a.released?
          end
        else
          @addons = @all_addons
        end
      end

    private

      # reimplement this in a subclass
      # @return [String] dialog head
      def heading
        raise "Not implemented"
      end

      # create the main dialog definition
      # @return [Yast::Term] the main UI dialog term
      def content
        check_filter =
          self.class.filter_devel.nil? ? FILTER_DEVEL_INITIALLY : self.class.filter_devel
        vbox_elements = [Left(Heading(heading))]
        available_addons = @all_addons.reject(&:registered?)

        unless available_addons.empty? || available_addons.all?(&:released?)
          vbox_elements.push(Left(CheckBox(Id(:filter_devel), Opt(:notify),
            # TRANSLATORS: Checkbox label, hides alpha or beta versions (not released yet)
            _("&Hide Development Versions"), check_filter)))
        end

        vbox_elements.concat([addons_box, Left(Label(_("Details (English only)"))), details_widget])
        VBox(*vbox_elements)
      end

      # addon description widget
      # @return [Yast::Term] the addon details widget
      def details_widget
        MinHeight(8,
          VWeight(25, RichText(Id(:details), Opt(:disabled), "<small>" +
                _("Select an extension or a module to show details here") + "</small>")))
      end

      # @return [String] a Value for a RichText
      def addon_checkboxes
        @addons.map { |a| addon_checkbox(a) }.join("\n")
      end

      # @param [<Addon>] addon the addon
      # @return [String] a Value for a RichText
      def addon_checkbox(addon)
        # checkbox label for an unavailable extension
        # (%s is an extension name)
        label = addon.available? ? addon.label : (_("%s (not available)") % addon.label)
        richtext_checkbox(id:     addon_widget_id(addon),
                          label:  label,
                          status: addon.status)
      end

      IMAGE_DIR = "/usr/share/YaST2/theme/current/wizard".freeze
      IMAGES = {
        "normal:auto:enabled" => "auto-selected.png",
        "normal:on:enabled"   => "checkbox-on.png",
        "normal:off:enabled"  => "checkbox-off.png",
        # theme has no special images for disabled checkboxes
        "normal:on:disabled"  => "checkbox-on.png",
        "normal:off:disabled" => "checkbox-off.png",
        "inst:auto:enabled"   => "auto-selected.png",
        "inst:on:enabled"     => "inst_checkbox-on.png",
        "inst:off:enabled"    => "inst_checkbox-off.png",
        "inst:on:disabled"    => "inst_checkbox-on-disabled.png",
        "inst:off:disabled"   => "inst_checkbox-off-disabled.png"
      }.freeze

      INDENT = "&nbsp;".freeze

      # Make a simulation of a CheckBox displayed in a RichText
      # @param id [String]
      # @param label [String]
      # @param status [Symbol]
      # @return [String] a Value for a RichText
      def richtext_checkbox(id:, label:, status:)
        enabled = [:selected, :auto_selected, :available].include?(status)
        if Yast::UI.TextMode
          check = case status
          when :selected, :registered
            "[x]"
          when :auto_selected
            "[a]"
          else
            "[ ]"
          end
          widget = "#{check} #{label}"
          enabled_widget = enabled ? "<a href=\"#{id}\">#{widget}</a>" : widget
          "#{INDENT}#{enabled_widget}<br>"
        else
          # check for installation style, which is dark, FIXME: find better way
          installation = ENV["Y2STYLE"] == "installation.qss"

          selected = case status
          when :selected, :registered
            "on"
          when :auto_selected
            "auto"
          else
            "off"
          end

          image = (installation ? "inst:" : "normal:") +
            selected + ":" + (enabled ? "enabled" : "disabled")

          check = "<img src='#{IMAGE_DIR}/#{IMAGES[image]}'></img>"
          widget = "#{check} #{label}"
          enabled_widget = if enabled
            "<a href='#{id}' class='enabled-item'>#{widget}</a>"
          else
            "<span class='disabled-item'>#{widget}</span>"
          end
          "<p>#{INDENT}#{enabled_widget}</p>"
        end
      end

      # create UI box with addon check boxes
      # @return [Yast::Term] the main UI dialog term
      def addons_box
        content = RichText(Id(:items), addon_checkboxes)

        VWeight(75, MinHeight(12, content))
      end

      # the main event loop - handle the user in put in the dialog
      # @return [Symbol] the user input
      def handle_dialog
        Yast::UI.SetFocus(Id(:items))

        ret = nil
        continue_buttons = [:next, :back, :abort, :skip]

        until continue_buttons.include?(ret)
          ret = Yast::UI.UserInput

          case ret
          when :next
            ret = handle_next_button
          when :cancel, :abort
            ret = Stage.initial && !AbortConfirmation.run ? nil : :abort
            # when canceled switch to old selection
            Addon.selected.replace(@old_selection) if ret == :abort
          when :filter_devel
            filter_devel_releases(Yast::UI.QueryWidget(Id(ret), :Value))
            show_addons
          else
            handle_addon_selection(ret)
          end
        end

        ret
      end

      # handler for the :next button in the main loop
      def handle_next_button
        return nil unless supported_addon_count?

        log.info "Selected addons: #{Addon.selected.map(&:name)}"

        Addon.selected.empty? ? :skip : :next
      end

      # handler for changing the addon status in the main loop
      # @param id [String] addon widget id
      def handle_addon_selection(id)
        # check whether it's an add-on ID (checkbox clicked)
        addon = @addons.find { |a| addon_widget_id(a) == id }
        return unless addon

        addon.toggle_selected
        show_addon_details(addon)
        show_addons
      end

      # update addon details after changing the current addon in the UI
      # @param addon []
      def show_addon_details(addon)
        # addon description is a rich text
        Yast::UI.ChangeWidget(Id(:details), :Value, addon.description)
        Yast::UI.ChangeWidget(Id(:details), :Enabled, true)
      end

      # show the addon list when some are filtered, enabled, selected
      def show_addons
        Yast::UI.ChangeWidget(Id(:items), :Value, addon_checkboxes)
      end

      # the maximum number of reg. codes displayed vertically,
      # this is the limit for 80x25 textmode UI
      MAX_REGCODES_PER_COLUMN = 8

      # check the number of required reg. codes
      # @return [Boolean] true if the number of the required reg. codes fits
      #  the maximum limit
      def supported_addon_count?
        # maximum number or reg codes which can be displayed in two column layout
        max_supported = 2 * MAX_REGCODES_PER_COLUMN

        # check if the count of addons requiring a reg. code fits two columns
        if Addon.selected.count { |a| a.registered? && a.free } > max_supported
          Report.Error(_("YaST allows to select at most %s extensions or modules.") % max_supported)
          return false
        end

        true
      end

      # shared part of the help text
      # @return [String] translated help text
      def generic_help_text
        # help text (2/3)
        _("<p>Please note, that some extensions or modules might need "\
            "specific registration code.</p>") +
          # help text (3/3)
          _("<p>If you want to remove any extension or module you need to log "\
              "into the SUSE Customer Center and remove them manually there.</p>")
      end

      def checkboxes_help
        header = _("<p>The extensions and modules can have several states depending " \
          "how they were selected.</p>")

        # TRANSLATORS: help text for checked check box
        selected = _("The extension or module is selected to install by user or is " \
          "pre-selected as a recommended addon.") + "<br>"
        # TRANSLATORS: help text for unchecked check box
        deselected = _("The extension or module is not selected to install.") + "<br>"
        # TRANSLATORS: help text for automatically checked check box (it has a
        # different look that a user selected check box)
        auto_selected = _("The extension or module was selected automatically as a dependency " \
          "of another extension or module.")

        if Yast::UI.TextMode
          return header + "<p>" \
              "[x] = " + selected +
              "[ ] = " + deselected +
              "[a] = " + auto_selected +
              "</p>"
        end

        mode = (ENV["Y2STYLE"] == "installation.qss") ? "inst" : "normal"

        header + "<p>" \
          "<img src='#{IMAGE_DIR}/#{IMAGES["#{mode}:on:enabled"]}'></img> = " + selected +
          "<img src='#{IMAGE_DIR}/#{IMAGES["#{mode}:off:enabled"]}'></img> = " + deselected +
          "<img src='#{IMAGE_DIR}/#{IMAGES["#{mode}:auto:enabled"]}'></img> = " + auto_selected +
          "</p>"
      end

      def preselect_recommended
        # something is already selected/registered, keep the user selection unchanged
        return if !Addon.selected.empty? || !Addon.registered.empty?

        @all_addons.each do |a|
          next unless a.recommended
          log.info("Preselecting a default addon: #{a.friendly_name}")
          a.selected
        end
      end
    end
  end
end