yast/yast-registration

View on GitHub
src/lib/registration/widgets/package_search.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2020] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast"
require "cwm/custom_widget"
require "registration/widgets/package_search_form"
require "registration/widgets/remote_packages_table"
require "registration/widgets/remote_package_details"
require "registration/widgets/search_results_info"
require "registration/widgets/toggle_package_selection"
require "yast2/popup"

Yast.import "Popup"

module Registration
  module Widgets
    # Online package search widget
    #
    # This widget offers a UI to search for packages using the SCC API. It features
    # a search form ({PackageSearchForm}), a list of results ({RemotePackagesTable})
    # and the details of the selected package ({RemotePackageDetails}).
    #
    # Additionally, it allows the user to select packages for installation.
    class PackageSearch < CWM::CustomWidget
      include Yast::Logger

      # @return [Array<RemotePackage>] Packages found in the current search
      attr_reader :packages

      # Constructor
      #
      # @param controller [Registration::Controllers::PackageSearch] Package search controller
      def initialize(controller)
        textdomain "registration"
        self.handle_all_events = true
        @controller = controller
        @packages = []
        super()
      end

      # @macro seeAbstractWidget
      def contents
        MarginBox(
          0.5,
          0.5,
          HBox(
            VBox(
              search_form,
              VStretch()
            ),
            MinWidth(
              60,
              VBox(
                MinHeight(14, packages_table),
                HBox(
                  HWeight(50, search_results),
                  Right(toggle_package_selection)
                ),
                package_details
              )
            )
          )
        )
      end

      # @macro seeAbstractWidget
      def handle(event)
        if start_search_event?(event)
          search_package(search_form.text, search_form.ignore_case)
        elsif event["WidgetID"] == "remote_packages_table"
          handle_packages_table_event(event)
        elsif event["WidgetID"] == "toggle_package_selection"
          toggle_package
        end

        log.debug "Event handled #{event.inspect}"
        nil
      end

    private

      # @return [Registration::Controllers::PackageSearch] Widget's controller
      attr_reader :controller

      # Search form widget
      #
      # @return [PackageSearchForm] Search form widget instance
      def search_form
        @search_form ||= PackageSearchForm.new
      end

      # Packages table widget
      #
      # This widget is used to display the result of the search.
      #
      # @return [RemotePackagesTable] Packages table widget
      def packages_table
        @packages_table ||= RemotePackagesTable.new
      end

      # Widget to display search information
      #
      # @return [SearchResultsInfo] the search results info widget instance
      def search_results
        @search_results ||= SearchResultsInfo.new
      end

      # Package details widget
      #
      # This widget displays the details of the package which is selected in the
      # table.
      #
      # @return [RemotePackageDetails] Package details widget.
      def package_details
        @package_details ||= RemotePackageDetails.new
      end

      # The button to toggle the selection status for current selected package
      #
      # @return [TogglePackageSelection] a toggle package selection button
      def toggle_package_selection
        @toggle_package_selection ||= TogglePackageSelection.new
      end

      # Handles remote packages table events
      #
      # @param event [Hash] Widget event to process
      def handle_packages_table_event(event)
        case event["EventReason"]
        when "Activated"
          toggle_package
        when "SelectionChanged"
          update
        end
      end

      # Determines whether the event should start the search
      #
      # @param event [Hash] UI event to check
      # @return [Boolean]
      def start_search_event?(event)
        event["WidgetID"] == "search_form_button" ||
          (event["WidgetID"] == "search_form_text" && event["EventReason"] == "Activated")
      end

      # Performs the search and updates the packages table
      #
      # @param text        [String] Text to search for
      # @param ignore_case [Boolean] Whether the search is case sensitive or not
      def search_package(text, ignore_case)
        return unless valid_search_text?(text)
        # TRANSLATORS: searching for packages
        Yast::Popup.Feedback(searching_message, searching_header) do
          @packages = controller.search(text, ignore_case)
          selected_package_ids = controller.selected_packages.map(&:id)
          @packages.each do |pkg|
            pkg.select! if selected_package_ids.include?(pkg.id)
          end
        end
        packages_table.change_items(packages)
        update
      end

      # Finds out the current package which is selected in the packages table
      #
      # @return [RemotePackage,nil]
      def find_current_package
        # PackagesTable#value might be slow, so let's avoid to call it too many times
        packages_table_value = packages_table.value
        packages.find { |p| p.id == packages_table_value }
      end

      # Selects/unselects the current package for installation
      def toggle_package
        package = find_current_package

        return unless package

        controller.toggle_package(package)
        packages_table.update_item(package)
        update
      end

      # Updates the UI according to selected package
      def update
        current_package = find_current_package

        search_results.update(packages.size)

        if current_package
          package_details.update(current_package)
          toggle_package_selection.enabled = !current_package.installed?
        else
          package_details.clear
          toggle_package_selection.enabled = false
        end
      end

      MINIMAL_SEARCH_TEXT_SIZE = 2

      # Determines whether the search text is valid or not
      #
      # @param text [String] Text to search for
      def valid_search_text?(text)
        return true if text.to_s.size >= MINIMAL_SEARCH_TEXT_SIZE
        Yast2::Popup.show(
          format(
            # TRANSLATORS: the minimal size of the text to search for package names
            _("Please, type at least %{minimal_size} characters to search for."),
            minimal_size: MINIMAL_SEARCH_TEXT_SIZE
          )
        )
        false
      end

      # Returns the header to display in the feedback window while searching for packages
      #
      # @return [String]
      def searching_header
        _("Contacting the SUSE Customer Center. This may take some time.\n")
      end

      # Returns the message to display in the feedback window while searching for packages
      #
      # @return [String]
      def searching_message
        _("Searching for packages")
      end
    end
  end
end