From 23eaa14af0fdbcc34d10cde433145eb9887ec92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Tue, 10 Mar 2020 16:04:32 +0000 Subject: [PATCH] WIP --- src/lib/y2packager/dialogs/addon_selector.rb | 184 ++++-------------- src/lib/y2packager/widgets/addons_selector.rb | 167 ++++++++++++++++ .../y2packager/widgets/product_summary.erb | 36 ++++ 3 files changed, 241 insertions(+), 146 deletions(-) create mode 100644 src/lib/y2packager/widgets/addons_selector.rb create mode 100644 src/lib/y2packager/widgets/product_summary.erb diff --git a/src/lib/y2packager/dialogs/addon_selector.rb b/src/lib/y2packager/dialogs/addon_selector.rb index d9ab353a5..0a036585b 100644 --- a/src/lib/y2packager/dialogs/addon_selector.rb +++ b/src/lib/y2packager/dialogs/addon_selector.rb @@ -11,27 +11,26 @@ # ------------------------------------------------------------------------------ require "yast" -require "erb" -require "ui/installation_dialog" +require "cwm/dialog" require "y2packager/resolvable" +require "y2packager/widgets/addons_selector" Yast.import "AddOnProduct" Yast.import "Mode" Yast.import "ProductFeatures" Yast.import "Report" Yast.import "Stage" -Yast.import "UI" Yast.import "Wizard" module Y2Packager module Dialogs # Dialog which shows the user available products on the medium - class AddonSelector < ::UI::InstallationDialog + class AddonSelector < ::CWM::Dialog include Yast::Logger - include ERB::Util # @return [Array] Products on the medium attr_reader :products + # @return [Array] User selected products attr_reader :selected_products @@ -53,6 +52,37 @@ def initialize(products) @selected_products = [] end + # The dialog entry point + # + # Display the dialog title on the left side at installation (in the first + # stage) to have the same layout as in the registration addons dialog. + # + # @see {CWM::Dialog#run} + def run + Yast::Wizard.OpenLeftTitleNextBackDialog if Yast::Stage.initial + + super + end + + # @macro seeDialog + def title + # TODO: does it make sense also for the 3rd party addons? + _("Extension and Module Selection") + end + + # @macro seeDialog + def contents + addons_selector_widget + end + + private + + attr_writer :selected_products + + def addons_selector_widget + @addons_selector_widget ||= Widgets::AddonsSelector.new(products, preselected_products) + end + # Handler for the :next action # # This action happens when the user clicks the 'Next' button @@ -61,7 +91,7 @@ def next_handler return if selected_products.empty? && !Yast::Popup.ContinueCancel(continue_msg) - finish_dialog(:next) + :next end # Handler for the :abort action @@ -69,7 +99,7 @@ def next_handler def abort_handler return if Yast::Stage.initial && !Yast::Popup.ConfirmAbort(:painless) - finish_dialog(:abort) + :abort end # Text to display when the help button is pressed @@ -81,117 +111,21 @@ def help_text "subdirectories. Select which products you want to install.

") end - # Handle changing the current item or changing the selection - def addon_repos_handler - current_product = find_current_product - return unless current_product - - refresh_details(current_product) - - select_dependent_products - end - - # Display the the dialog title on the left side at installation - # (in the first stage) to have the same layout as in the registration - # addons dialog. - def run - Yast::Wizard.OpenLeftTitleNextBackDialog if Yast::Stage.initial - super() - ensure - Yast::Wizard.CloseDialog if Yast::Stage.initial - end - - # overwrite dialog creation to always enable back/next by default - def create_dialog - res = super - Yast::Wizard.EnableNextButton - Yast::Wizard.EnableBackButton - Yast::UI.SetFocus(Id(:addon_repos)) - res - end - - private - - attr_writer :selected_products - - def selection_content - defaults = preselected_products - products.map { |p| Item(Id(p.dir), p.summary || p.name, defaults.include?(p)) } - end - - # Dialog content - # - # @see ::UI::Dialog - def dialog_content - VBox( - # TRANSLATORS: Product selection label (above a multi-selection box) - Left(Heading(_("Available Extensions and Modules"))), - VWeight(60, MinHeight(8, - MultiSelectionBox( - Id(:addon_repos), - Opt(:notify, :immediate), - "", - selection_content - ))), - VSpacing(0.4), - details_widget - ) - end - - # select the dependent products for the active selection - def select_dependent_products - # select the dependent products - new_selection = current_selection - - # the selection has not changed, nothing to do - return if new_selection == selected_products - - # add the dependent items to the selected list - selected_items = Yast::UI.QueryWidget(Id(:addon_repos), :SelectedItems) - new_items = new_selection - selected_products - new_items.each do |p| - # the dependencies contain also the transitive (indirect) dependencies, - # we do not need to recursively evaluate the list - dependencies = p&.details&.depends_on - selected_items.concat(dependencies) if dependencies - end - - selected_items.uniq! - - Yast::UI.ChangeWidget(:addon_repos, :SelectedItems, selected_items) - end - - # refresh the details of the currently selected add-on - def refresh_details(current_product) - details = product_description(current_product) - Yast::UI.ChangeWidget(Id(:details), :Value, details) - Yast::UI.ChangeWidget(Id(:details), :Enabled, true) - end - def read_user_selection self.selected_products = current_selection log.info("Selected products: #{selected_products.inspect}") end - # # The currently selected products # # @return [Array] list of selected products # def current_selection - selected_items = Yast::UI.QueryWidget(Id(:addon_repos), :SelectedItems) + selected_items = addons_selector_widget.selected_items.map(&:id) products.select { |p| selected_items.include?(p.dir) } end - # Dialog title - # - # @see ::UI::Dialog - def dialog_title - # TODO: does it make sense also for the 3rd party addons? - _("Extension and Module Selection") - end - # A message for asking the user whether to continue without adding any addon. # # @return [String] translated message @@ -201,40 +135,6 @@ def continue_msg "Do you really want to continue without adding any product?") end - # description widget - # @return [Yast::Term] the addon details widget - def details_widget - VWeight( - 40, - RichText(Id(:details), Opt(:disabled), initial_description) - ) - end - - # extra help text - # @return [String] first product description - def initial_description - return "" if products.empty? - - product_description(products.first) - end - - def product_description(product) - erb_file = File.join(__dir__, "product_summary.erb") - log.info "Loading ERB template #{erb_file}" - erb = ERB.new(File.read(erb_file)) - - # compute the dependent products - dependencies = [] - product&.details&.depends_on&.each do |p| - # display the human readable product name instead of the product directory - prod = @products.find { |pr| pr.dir == p } - dependencies << (prod.summary || prod.name) if prod - end - - # render the ERB template in the context of this object - erb.result(binding) - end - # return a list of the preselected products depending on the installation mode # @return [Array] the products def preselected_products @@ -278,14 +178,6 @@ def preselected_installation_products default_modules.include?(p.details&.product) end end - - # Returns the current product (the one which has the focus in the addons list) - # - # @return [Y2Packager::Product,nil] - def find_current_product - current_item = Yast::UI.QueryWidget(Id(:addon_repos), :CurrentItem) - products.find { |p| p.dir == current_item } - end end end end diff --git a/src/lib/y2packager/widgets/addons_selector.rb b/src/lib/y2packager/widgets/addons_selector.rb new file mode 100644 index 000000000..b2f85e4b2 --- /dev/null +++ b/src/lib/y2packager/widgets/addons_selector.rb @@ -0,0 +1,167 @@ +# 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/multi_state_selector" + +module Y2Packager + module Widgets + # A custom widget to display a multi status selector list + class AddonsSelector < CWM::MultiStateSelector + include Yast::Logger + include Yast::UIShortcuts + + attr_reader :items + + # Constructor + # + # @param [Array] an addons collection + def initialize(products, preselected_products) + @products = products + @items = products.map do |product| + dependencies = product&.details&.depends_on || [] + selected = preselected_products.include?(product) + + Item.new(product, dependencies, selected) + end + end + + # @macro seeAbstractWidget + def contents + VBox( + VWeight(60, super), + VWeight( + 40, + VBox( + Left(Label("Details (English only)")), + details_widget + ) + ) + ) + end + + def toggle(item) + item.toggle + refresh_details(item) + select_dependencies + end + + def selected_items + items.select { |i| i.selected? || i.auto_selected? } + end + + # @macro seeAbstractWidget + def help + # CheckboxItem.help + end + + private + + def refresh_details(item) + details_widget.value = item.description + end + + # @macro seeMultiStateSelector + def over_item_label(item) + refresh_details(item) + end + + # select the dependent products for the active selection + def select_dependencies + @items.select(&:auto_selected?).map(&:unselect!) + selected_items = @items.select(&:selected?) + + dependencies = selected_items.flat_map(&:dependencies).uniq + dependencies_to_select = dependencies - selected_items.map(&:label) + + @items.select { |i| dependencies_to_select.include?(i.label) }.map(&:auto_select!) + end + + # Returns the Yast::Term to fill the detail area + # + # @return [Yast::Term] + def details_widget + @details_widget ||= CWM::RichText.new + end + + # Internal class representing the prodcut item + class Item < Item + include Yast::Logger + include ERB::Util + include Yast::I18n + + def initialize(product, dependencies, selected) + @product = product + @dependencies = dependencies + @status = selected ? :selected : :unselected + end + + attr_reader :dependencies, :status + + def id + product.dir + end + + def label + product.summary || product.name + end + + def enabled + true + end + + def description + erb_file = File.join(__dir__, "product_summary.erb") + log.info "Loading ERB template #{erb_file}" + erb = ERB.new(File.read(erb_file)) + + erb.result(binding) + end + + def toggle + @status = next_status[@status] + end + + def select! + @status = :selected + end + + def unselect! + @status = :unselected + end + + def auto_select! + @status = :auto_selected + end + + def next_status + { + selected: :unselected, + auto_selected: :selected, + unselected: :selected + } + end + + private + + attr_reader :product + end + end + end +end diff --git a/src/lib/y2packager/widgets/product_summary.erb b/src/lib/y2packager/widgets/product_summary.erb new file mode 100644 index 000000000..464be6899 --- /dev/null +++ b/src/lib/y2packager/widgets/product_summary.erb @@ -0,0 +1,36 @@ +<% +textdomain "packager" +%> + +<%# TRANSLATORS: the RichText header, followed by the name of the directory %> +<%= _("Directory on the Media:") %> <%= h(product.dir) %>
+<%# TRANSLATORS: the RichText header, followed by the name of the medium %> +<%= _("Media Name:") %> <%= h(product.name) %>
+ +<% details = product.details %> +<% if details %> + <% if details.product %> + <%# TRANSLATORS: the RichText header, followed by the product identifier, e.g. "SLES" %> + <%= _("Product ID:") %> <%= h(details.product) %> + <% end %> + + <% if dependencies.nil? %> + <%# TRANSLATORS: error message in a RichText summary %> +

<%= _("Cannot evaluate the product dependencies.") %>

+ <% elsif !dependencies.empty? %> + <%# TRANSLATORS: the RichText section header, followed by the names of the dependent products %> +

<%= _("Dependencies") %>

+
    + <% dependencies.each do |dep| %> +
  • <%= h(dep) %>
  • + <% end %> +
+ <% end %> + + <% if !details.description.empty? %> + <%# TRANSLATORS: the RichText header, it is followed by an untranslated (English) product description %> +

<%= _("Product Description (English Only)") %>

+ <%# no escaping, the description already *is* a rich text %> +

<%= details.description %>

+ <% end %> +<% end %> \ No newline at end of file