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