From 09c3d65ce85cefc8022460def507eb2a13499c31 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Thu, 31 Mar 2022 13:38:48 +0200 Subject: [PATCH 01/13] Added DelayedProgressPopup --- .../src/lib/ui/delayed_progress_popup.rb | 205 ++++++++++++++++++ .../src/lib/ui/examples/delayed_progress_1.rb | 37 ++++ 2 files changed, 242 insertions(+) create mode 100644 library/general/src/lib/ui/delayed_progress_popup.rb create mode 100644 library/general/src/lib/ui/examples/delayed_progress_1.rb diff --git a/library/general/src/lib/ui/delayed_progress_popup.rb b/library/general/src/lib/ui/delayed_progress_popup.rb new file mode 100644 index 000000000..47f51b4a4 --- /dev/null +++ b/library/general/src/lib/ui/delayed_progress_popup.rb @@ -0,0 +1,205 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2022 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------------------ + +require "yast2/system_time" +require "yast" + +module Yast + # Progress popup dialog that only opens after a certain delay, so it never + # opens for very short operations (< 4 seconds by default), only when an + # operation takes long enough to actually give feedback to the user. + # + # This is less disruptive than a progress dialog that always opens, and in + # most cases, flashes by so fast that the user can't recognize what it says. + # + # The tradeoff is that it takes a few seconds until there is any visual + # feedback (until the delay is expired). + # + # Notice that this does not use an active timer; the calling application has + # to trigger the check for the timeout by calling progress() in regular + # intervals. + # + # You can change the delay by changing the delay_seconds member variable, you + # can force the dialog to open with open!, and you can stop and (re-) start + # the timer. + # + # In any case, when done with this progress reporting, call close(). You + # don't need to check if it ever opened; close() does that automatically. + # + # see examples/delayed_progress_1.rb for a usage example. + # + class DelayedProgressPopup + include Yast::UIShortcuts + include Yast::Logger + + # @return [String] Text for the dialog heading. Default: nil. + attr_accessor :heading + + # @return [Integer] Delay (timeout) in seconds. + attr_accessor :delay_seconds + + # @return [Boolean] Add a "Cancel" button to the dialog. Default: true. + attr_accessor :use_cancel_button + + # Constructor. + # + # This also starts the timer with a default (4 seconds) timeout. + # Call stop_timer() immediately if that is not desired. + # + def initialize + Yast.import "UI" + Yast.import "Label" + + @delay_seconds = 4 + @use_cancel_button = true + @is_open = false + start_timer + end + + # Update the progress. + # + # If the dialog is not open yet, this opens it if the timeout is expired. + # + # @param [Integer] progress_percent numeric progress bar value + # @param [nil|String] progress_text optional progress bar label text + # + def progress(progress_percent, progress_text = nil) + open_if_needed + return unless open? + + update_progress(progress_percent, progress_text) + end + + # Open the dialog if needed, i.e. if it's not already open and if the timer + # expired. + # + # Notice that progress() does this automatically. + # + def open_if_needed + return if open? + + open! if timer_expired? + end + + # Open the dialog unconditionally. + def open! + log.info "Opening the delayed progress popup" + UI.OpenDialog(dialog_widgets) + @is_open = true + stop_timer + end + + # Close the dialog if it is open. Only stop the timer if it is not (because + # the timer didn't expire). + # + # Do not call this if another dialog was opened on top of this one in the + # meantime: Just like a normal UI.CloseDialog call, this closes the topmost + # dialog; which in that case might not be the right one. + # + def close + stop_timer + return unless open? + + UI.CloseDialog + @is_open = false + end + + # Start or restart the timer. + def start_timer + @start_time = Yast2::SystemTime.uptime + end + + # Stop the timer. + def stop_timer + @start_time = nil + end + + # Check if the dialog is open. + def open? + @is_open + end + + # Check if the timer expired. + def timer_expired? + return false unless timer_running? + + now = Yast2::SystemTime.uptime + now > @start_time + delay_seconds + end + + # Check if the timer is running. + def timer_running? + !@start_time.nil? + end + + protected + + # Return a widget term for the dialog widgets. + # Reimplement this in inherited classes for a different dialog content. + # + def dialog_widgets + placeholder_label = " " # at least one blank + heading_spacing = @heading.nil? ? 0 : 0.4 + MinWidth( + 40, + VBox( + MarginBox( + 1, 0.4, + VBox( + dialog_heading, + VSpacing(heading_spacing), + VCenter( + ProgressBar(Id(:progress_bar), placeholder_label, 100, 0) + ) + ) + ), + VSpacing(0.4), + dialog_buttons + ) + ) + end + + # Return a widget term for the dialog heading. + def dialog_heading + return Empty() if @heading.nil? + + Left(Heading(@heading)) + end + + # Return a widget term for the dialog buttons. + # Reimplement this in inherited classes for different buttons. + # + # Notice that the buttons only do anything if the calling application + # handles them, e.g. with UI.PollInput(). + # + # Don't forget that in the Qt UI, every window has a WM_CLOSE button (the + # [x] icon in the window title bar that is meant for closing the window) + # that returns :cancel in UI.UserInput() / UI.PollInput(). + # + def dialog_buttons + return Empty() unless @use_cancel_button + + ButtonBox( + PushButton(Id(:cancel), Opt(:cancelButton), Yast::Label.CancelButton) + ) + end + + # Update the progress bar. + def update_progress(progress_percent, progress_text = nil) + return unless UI.WidgetExists(:progress_bar) + + UI.ChangeWidget(Id(:progress_bar), :Value, progress_percent) + UI.ChangeWidget(Id(:progress_bar), :Label, progress_text) unless progress_text.nil? + end + end +end diff --git a/library/general/src/lib/ui/examples/delayed_progress_1.rb b/library/general/src/lib/ui/examples/delayed_progress_1.rb new file mode 100644 index 000000000..a1d120827 --- /dev/null +++ b/library/general/src/lib/ui/examples/delayed_progress_1.rb @@ -0,0 +1,37 @@ +# Example for the DelayedProgressPopup +# +# Start with: +# +# y2start ./delayed_progress_1.rb qt +# or +# y2start ./delayed_progress_1.rb ncurses +# + +require "yast" +require "ui/delayed_progress_popup" + +popup = Yast::DelayedProgressPopup.new + +# All those parameters are optional; +# comment out or uncomment to experiment. +popup.heading = "Deep Think Mode" +popup.delay_seconds = 2 +# popup.use_cancel_button = false + +puts("Nothing happens for #{popup.delay_seconds} seconds, then the popup opens.") + +10.times do |sec| + puts "#{sec} sec" + popup.progress(10 * sec, "Working #{sec}") + if popup.open? + # Checking for popup.open? is only needed here because otherwise there is + # no window at all yet, so UI.WaitForEvent() throws an exception. Normal + # applications have a main window at this point. + + event = Yast::UI.WaitForEvent(1000) # implicitly sleeps + break if event["ID"] == :cancel + else + sleep(1) + end +end +popup.close From 4b2f4dffd1c1d47d1fb795a263d5c451234f331a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ladislav=20Slez=C3=A1k?= Date: Thu, 31 Mar 2022 14:54:22 +0200 Subject: [PATCH 02/13] DelayedProgressPopup - added block variant --- .../src/lib/ui/delayed_progress_popup.rb | 28 +++++++++++++-- .../src/lib/ui/examples/delayed_progress_2.rb | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 library/general/src/lib/ui/examples/delayed_progress_2.rb diff --git a/library/general/src/lib/ui/delayed_progress_popup.rb b/library/general/src/lib/ui/delayed_progress_popup.rb index 47f51b4a4..5af99f149 100644 --- a/library/general/src/lib/ui/delayed_progress_popup.rb +++ b/library/general/src/lib/ui/delayed_progress_popup.rb @@ -56,16 +56,40 @@ class DelayedProgressPopup # This also starts the timer with a default (4 seconds) timeout. # Call stop_timer() immediately if that is not desired. # - def initialize + # The `close` method must be explicitly called at the end when the progress + # is finished. + # + # @param delay [Integer,nil] optional delay in seconds + # @param heading [String,nil] optional popup heading + def initialize(delay: nil, heading: nil) Yast.import "UI" Yast.import "Label" - @delay_seconds = 4 + @delay_seconds = delay || 4 + @heading = heading @use_cancel_button = true @is_open = false start_timer end + # A static variant with block, it automatically closes the popup at the end. + # + # @param delay [Integer,nil] optional delay in seconds + # @param heading [String,nil] optional popup heading + # @example + # Yast::DelayedProgressPopup.run(delay: 5, heading: "Working...") do |popup| + # 10.times do |sec| + # popup.progress(10 * sec, "Working #{sec}") + # sleep(1) + # end + # end + def self.run(delay: nil, heading: nil, &block) + popup = new(delay: delay, heading: heading) + block.call(popup) + ensure + popup&.close + end + # Update the progress. # # If the dialog is not open yet, this opens it if the timeout is expired. diff --git a/library/general/src/lib/ui/examples/delayed_progress_2.rb b/library/general/src/lib/ui/examples/delayed_progress_2.rb new file mode 100644 index 000000000..ea0c0a610 --- /dev/null +++ b/library/general/src/lib/ui/examples/delayed_progress_2.rb @@ -0,0 +1,35 @@ +# Example for the DelayedProgressPopup +# +# Start with: +# +# y2start ./delayed_progress_2.rb qt +# or +# y2start ./delayed_progress_2.rb ncurses +# + +require "yast" +require "ui/delayed_progress_popup" + +Yast::DelayedProgressPopup.run(delay: 2, heading: "Deep Think Mode") do |popup| + # All those parameters are optional; + # comment out or uncomment to experiment. + # popup.heading = "Deep Think Mode" + # popup.use_cancel_button = false + + puts("Nothing happens for #{popup.delay_seconds} seconds, then the popup opens.") + + 10.times do |sec| + puts "#{sec} sec" + popup.progress(10 * sec, "Working #{sec}") + if popup.open? + # Checking for popup.open? is only needed here because otherwise there is + # no window at all yet, so UI.WaitForEvent() throws an exception. Normal + # applications have a main window at this point. + + event = Yast::UI.WaitForEvent(1000) # implicitly sleeps + break if event["ID"] == :cancel + else + sleep(1) + end + end +end From b2f77581595ef1ede6f3b429fa0401db794e3be8 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Thu, 31 Mar 2022 15:16:06 +0200 Subject: [PATCH 03/13] Use the new DelayedProgressPopup --- .../src/lib/packages/file_conflict_callbacks.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index 0069335f7..57f50f96a 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------ -# Copyright (c) 2016 SUSE LLC +# Copyright (c) 2016-2022 SUSE LLC # # 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 @@ -13,6 +13,7 @@ # require "yast" +require "ui/delayed_progress_popup" module Packages # Default file conflicts callbacks for package bindings. To register the @@ -83,11 +84,7 @@ def start Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Value, 0) Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Label, label) else - Yast::Wizard.CreateDialog - # TRANSLATORS: help text for the file conflict detection progress - help = _("

Detecting the file conflicts is in progress.

") - # Use the same label for the window title and the progressbar label - Yast::Progress.Simple(label, label, 100, help) + @@delayed_progress_popup = Yast::DelayedProgressPopup.new(heading: label) end end @@ -102,7 +99,7 @@ def progress(progress) elsif pkg_installation? Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Value, progress) else - Yast::Progress.Step(progress) + @@delayed_progress_popup.progress(progress) end ui = Yast::UI.PollInput unless Yast::Mode.commandline @@ -152,8 +149,7 @@ def finish return if Yast::Mode.commandline || pkg_installation? # finish the opened progress dialog - Yast::Progress.Finish - Yast::Wizard.CloseDialog + @@delayed_progress_popup.progress.close unless @@delayed_progress_popup.nil? end # Construct the file conflicts dialog. From b89c44b4b4fcb6fba01388192bd11c3f416d0ce1 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Thu, 31 Mar 2022 15:32:34 +0200 Subject: [PATCH 04/13] Removed dead code --- .../lib/packages/file_conflict_callbacks.rb | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index 57f50f96a..11fde28d5 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -19,9 +19,6 @@ module Packages # Default file conflicts callbacks for package bindings. To register the # callbacks in Yast::Pkg just call {Packages::FileConflictCallbacks.register} class FileConflictCallbacks - # Widget ID (created by other code) - PKG_INSTALL_WIDGET = :progressCurrentPackage - class << self include Yast::Logger include Yast::I18n @@ -65,12 +62,6 @@ def register_file_conflict_callbacks nil end - # Is the package installation progress displayed? - # @return [Boolean] true if package installation progress is displayed - def pkg_installation? - Yast::UI.WidgetExists(PKG_INSTALL_WIDGET) - end - # Handle the file conflict detection start callback. def start log.info "Starting the file conflict check..." @@ -79,10 +70,6 @@ def start if Yast::Mode.commandline Yast::CommandLine.PrintVerbose(label) - elsif pkg_installation? - # package slideshow with progress already present - Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Value, 0) - Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Label, label) else @@delayed_progress_popup = Yast::DelayedProgressPopup.new(heading: label) end @@ -96,8 +83,6 @@ def progress(progress) if Yast::Mode.commandline Yast::CommandLine.PrintVerboseNoCR("#{Yast::PackageCallbacksClass::CLEAR_PROGRESS_TEXT}#{progress}%") - elsif pkg_installation? - Yast::UI.ChangeWidget(Id(PKG_INSTALL_WIDGET), :Value, progress) else @@delayed_progress_popup.progress(progress) end @@ -146,10 +131,9 @@ def report(excluded_packages, conflicts) # Handle the file conflict detection finish callback. def finish log.info "File conflict check finished" - return if Yast::Mode.commandline || pkg_installation? + return if Yast::Mode.commandline || @@delayed_progress_popup.nil? - # finish the opened progress dialog - @@delayed_progress_popup.progress.close unless @@delayed_progress_popup.nil? + @@delayed_progress_popup.progress.close end # Construct the file conflicts dialog. From 2708e066e69ce8b690a46c3e723ac4fa7cd52f7a Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Thu, 31 Mar 2022 17:19:15 +0200 Subject: [PATCH 05/13] Fixed unit tests --- .../lib/packages/file_conflict_callbacks.rb | 13 +++-- .../test/file_conflict_callbacks_test.rb | 52 ++++++++----------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index 11fde28d5..d2f805b7c 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -62,6 +62,10 @@ def register_file_conflict_callbacks nil end + def delayed_progress_popup + @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new + end + # Handle the file conflict detection start callback. def start log.info "Starting the file conflict check..." @@ -71,7 +75,7 @@ def start if Yast::Mode.commandline Yast::CommandLine.PrintVerbose(label) else - @@delayed_progress_popup = Yast::DelayedProgressPopup.new(heading: label) + @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new(heading: label) end end @@ -84,7 +88,7 @@ def progress(progress) if Yast::Mode.commandline Yast::CommandLine.PrintVerboseNoCR("#{Yast::PackageCallbacksClass::CLEAR_PROGRESS_TEXT}#{progress}%") else - @@delayed_progress_popup.progress(progress) + delayed_progress_popup.progress(progress) end ui = Yast::UI.PollInput unless Yast::Mode.commandline @@ -131,9 +135,10 @@ def report(excluded_packages, conflicts) # Handle the file conflict detection finish callback. def finish log.info "File conflict check finished" - return if Yast::Mode.commandline || @@delayed_progress_popup.nil? + return if Yast::Mode.commandline || @@delayed_progress_popup.nil? - @@delayed_progress_popup.progress.close + @@delayed_progress_popup.close + @@delayed_progress_popup = nil end # Construct the file conflicts dialog. diff --git a/library/packages/test/file_conflict_callbacks_test.rb b/library/packages/test/file_conflict_callbacks_test.rb index bd889488e..3978b762a 100755 --- a/library/packages/test/file_conflict_callbacks_test.rb +++ b/library/packages/test/file_conflict_callbacks_test.rb @@ -162,17 +162,8 @@ def CallbackFileConflictFinish(func) allow(Yast::Mode).to receive(:commandline).and_return(false) end - it "reuses the package installation progress" do - expect(Yast::UI).to receive(:WidgetExists).and_return(true) - expect(Yast::UI).to receive(:ChangeWidget).twice - - start_cb.call - end - - it "opens a new progress if installation progress was not displayed" do - expect(Yast::UI).to receive(:WidgetExists).and_return(false) - expect(Yast::Wizard).to receive(:CreateDialog) - expect(Yast::Progress).to receive(:Simple) + it "uses a delayed progress popup" do + expect(Yast::DelayedProgressPopup).to receive(:new) start_cb.call end @@ -180,6 +171,11 @@ def CallbackFileConflictFinish(func) end describe "the registered progress callback handler" do + let(:start_cb) do + Packages::FileConflictCallbacks.register + dummy_pkg.fc_start + end + let(:progress_cb) do Packages::FileConflictCallbacks.register dummy_pkg.fc_progress @@ -216,6 +212,13 @@ def CallbackFileConflictFinish(func) allow(Yast::Mode).to receive(:commandline).and_return(false) end + it "receives the progress call" do + expect_any_instance_of(Yast::DelayedProgressPopup).to receive(:progress) + + start_cb.call + progress_cb.call(progress) + end + it "returns false to abort if user clicks Abort" do expect(Yast::UI).to receive(:PollInput).and_return(:abort) @@ -233,20 +236,6 @@ def CallbackFileConflictFinish(func) expect(progress_cb.call(progress)).to eq(true) end - - it "uses the existing widget if package installation progress was displayed" do - expect(Yast::UI).to receive(:WidgetExists).and_return(true) - expect(Yast::UI).to receive(:ChangeWidget) - - progress_cb.call(progress) - end - - it "sets the progress if package installation progress was not displayed" do - expect(Yast::UI).to receive(:WidgetExists).and_return(false) - expect(Yast::Progress).to receive(:Step).with(progress) - - progress_cb.call(progress) - end end end @@ -357,6 +346,11 @@ def CallbackFileConflictFinish(func) end describe "the registered finish callback handler" do + let(:start_cb) do + Packages::FileConflictCallbacks.register + dummy_pkg.fc_start + end + let(:finish_cb) do Packages::FileConflictCallbacks.register dummy_pkg.fc_finish @@ -387,14 +381,12 @@ def CallbackFileConflictFinish(func) finish_cb.call end - it "closes progress if installation progress was not displayed" do - allow(Yast::UI).to receive(:WidgetExists).and_return(false) - expect(Yast::Wizard).to receive(:CloseDialog) - expect(Yast::Progress).to receive(:Finish) + it "closes the delayed progress popup" do + expect_any_instance_of(Yast::DelayedProgressPopup).to receive(:close) + start_cb.call finish_cb.call end - end end end From 94d0cfb1394ce14622584db2c7d2b93f480b58e7 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Mon, 4 Apr 2022 15:18:46 +0200 Subject: [PATCH 06/13] New parameter auto_start --- .../src/lib/ui/delayed_progress_popup.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/library/general/src/lib/ui/delayed_progress_popup.rb b/library/general/src/lib/ui/delayed_progress_popup.rb index 5af99f149..dd95abe7e 100644 --- a/library/general/src/lib/ui/delayed_progress_popup.rb +++ b/library/general/src/lib/ui/delayed_progress_popup.rb @@ -53,15 +53,16 @@ class DelayedProgressPopup # Constructor. # - # This also starts the timer with a default (4 seconds) timeout. - # Call stop_timer() immediately if that is not desired. + # If `auto_start` is `true` (default), this also starts the timer with a + # default (4 seconds) timeout. # # The `close` method must be explicitly called at the end when the progress # is finished. # - # @param delay [Integer,nil] optional delay in seconds + # @param delay [Integer,nil] optional delay in seconds + # @param auto_start [Boolean] start the timer immediately # @param heading [String,nil] optional popup heading - def initialize(delay: nil, heading: nil) + def initialize(delay: nil, auto_start: true, heading: nil) Yast.import "UI" Yast.import "Label" @@ -69,7 +70,8 @@ def initialize(delay: nil, heading: nil) @heading = heading @use_cancel_button = true @is_open = false - start_timer + start_timer if auto_start + log.info "Created delayed progress popup" end # A static variant with block, it automatically closes the popup at the end. @@ -83,8 +85,8 @@ def initialize(delay: nil, heading: nil) # sleep(1) # end # end - def self.run(delay: nil, heading: nil, &block) - popup = new(delay: delay, heading: heading) + def self.run(delay: nil, auto_start: true, heading: nil, &block) + popup = new(delay: delay, auto_start: auto_start, heading: heading) block.call(popup) ensure popup&.close @@ -98,6 +100,7 @@ def self.run(delay: nil, heading: nil, &block) # @param [nil|String] progress_text optional progress bar label text # def progress(progress_percent, progress_text = nil) + log.info "progress_percent: #{progress_percent}" open_if_needed return unless open? From 725d8d60cbc0d3e74b5fef02944a7e34c0290015 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Mon, 4 Apr 2022 15:19:23 +0200 Subject: [PATCH 07/13] Avoid YaST component problem: Create popup in advance --- .../lib/packages/file_conflict_callbacks.rb | 43 ++++++++++++++----- .../test/file_conflict_callbacks_test.rb | 41 ++++++++---------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index d2f805b7c..b97ea5d16 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -38,12 +38,13 @@ def register textdomain "base" + create_delayed_progress_popup unless Yast::Mode.commandline register_file_conflict_callbacks end private - # Helper function for creating an YaST function reference + # Helper function for creating a YaST function reference def fun_ref(*args) Yast::FunRef.new(*args) end @@ -62,20 +63,43 @@ def register_file_conflict_callbacks nil end + # Create the DelayedProgressPopup, but don't start the timer yet. + # + # This needs to be done outside of any of the callbacks to avoid clashes + # between Ruby, C++ and ex-YCP YaST components. + # + # rubocop:disable Style/ClassVars + def create_delayed_progress_popup + log.info "Creating DelayedProgressPopup" + @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new( + auto_start: false, + heading: progress_bar_label + ) + nil + end + def delayed_progress_popup - @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new + # Intentionally NOT creating the DelayedProgressPopup here so it isn't + # accidentially created in one of the callbacks. + @@delayed_progress_popup + end + # rubocop:enable Style/ClassVars + + def progress_bar_label + # TRANSLATORS: progress bar label + _("Checking file conflicts...") end # Handle the file conflict detection start callback. def start log.info "Starting the file conflict check..." - # TRANSLATORS: progress bar label - label = _("Checking file conflicts...") if Yast::Mode.commandline - Yast::CommandLine.PrintVerbose(label) + Yast::CommandLine.PrintVerbose(progress_bar_label) else - @@delayed_progress_popup ||= Yast::DelayedProgressPopup.new(heading: label) + # Don't create the DelayedProgressPopup here, otherwise there will be + # conflicts between the Ruby, C++ and ex-YCP YaST components. + delayed_progress_popup.start_timer end end @@ -83,7 +107,7 @@ def start # @param [Fixnum] progress progress in percents # @return [Boolean] true = continue, false = abort def progress(progress) - log.debug "File conflict progress: #{progress}%" + log.info "File conflict progress: #{progress}%" if Yast::Mode.commandline Yast::CommandLine.PrintVerboseNoCR("#{Yast::PackageCallbacksClass::CLEAR_PROGRESS_TEXT}#{progress}%") @@ -135,10 +159,9 @@ def report(excluded_packages, conflicts) # Handle the file conflict detection finish callback. def finish log.info "File conflict check finished" - return if Yast::Mode.commandline || @@delayed_progress_popup.nil? + return if Yast::Mode.commandline - @@delayed_progress_popup.close - @@delayed_progress_popup = nil + delayed_progress_popup.close end # Construct the file conflicts dialog. diff --git a/library/packages/test/file_conflict_callbacks_test.rb b/library/packages/test/file_conflict_callbacks_test.rb index 3978b762a..b97b6c901 100755 --- a/library/packages/test/file_conflict_callbacks_test.rb +++ b/library/packages/test/file_conflict_callbacks_test.rb @@ -128,13 +128,25 @@ def CallbackFileConflictFinish(func) end describe ".register" do - it "calls the Pkg methods for registering the file conflicts handlers" do - expect(dummy_pkg).to receive(:CallbackFileConflictStart).at_least(:once) - expect(dummy_pkg).to receive(:CallbackFileConflictProgress).at_least(:once) - expect(dummy_pkg).to receive(:CallbackFileConflictReport).at_least(:once) - expect(dummy_pkg).to receive(:CallbackFileConflictFinish).at_least(:once) + context "in UI mode" do + before do + allow(Yast::Mode).to receive(:commandline).and_return(false) + end - Packages::FileConflictCallbacks.register + # Must come first since this uses a class variable + it "creates a delayed progress popup in advance" do + expect(Yast::DelayedProgressPopup).to receive(:new).at_least(:once) + Packages::FileConflictCallbacks.register + end + + it "calls the Pkg methods for registering the file conflicts handlers" do + expect(dummy_pkg).to receive(:CallbackFileConflictStart).at_least(:once) + expect(dummy_pkg).to receive(:CallbackFileConflictProgress).at_least(:once) + expect(dummy_pkg).to receive(:CallbackFileConflictReport).at_least(:once) + expect(dummy_pkg).to receive(:CallbackFileConflictFinish).at_least(:once) + + Packages::FileConflictCallbacks.register + end end end @@ -156,18 +168,6 @@ def CallbackFileConflictFinish(func) start_cb.call end end - - context "in UI mode" do - before do - allow(Yast::Mode).to receive(:commandline).and_return(false) - end - - it "uses a delayed progress popup" do - expect(Yast::DelayedProgressPopup).to receive(:new) - - start_cb.call - end - end end describe "the registered progress callback handler" do @@ -253,11 +253,6 @@ def CallbackFileConflictFinish(func) allow(Yast::Mode).to receive(:commandline).and_return(true) end - it "does not check the command line mode, it behaves same as in the UI mode" do - expect(Yast::Mode).to_not receive(:commandline) - report_cb.call(excluded, conflicts) - end - it "does not call any UI method" do ui = double("no method call expected") stub_const("Yast::UI", ui) From 1a3d2a0d7dfb436455f973588b75ab3431dce8fb Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Mon, 4 Apr 2022 16:33:36 +0200 Subject: [PATCH 08/13] Less verbose logging --- library/packages/src/lib/packages/file_conflict_callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index b97ea5d16..0a554d18e 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -107,7 +107,7 @@ def start # @param [Fixnum] progress progress in percents # @return [Boolean] true = continue, false = abort def progress(progress) - log.info "File conflict progress: #{progress}%" + log.debug "File conflict progress: #{progress}%" if Yast::Mode.commandline Yast::CommandLine.PrintVerboseNoCR("#{Yast::PackageCallbacksClass::CLEAR_PROGRESS_TEXT}#{progress}%") From 102c046c8a8f7da687a98e125dcf0df1cfbf461a Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Mon, 4 Apr 2022 16:37:47 +0200 Subject: [PATCH 09/13] Removed test that works only when running rspec with just this file --- library/packages/test/file_conflict_callbacks_test.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/library/packages/test/file_conflict_callbacks_test.rb b/library/packages/test/file_conflict_callbacks_test.rb index b97b6c901..9e10c0d7d 100755 --- a/library/packages/test/file_conflict_callbacks_test.rb +++ b/library/packages/test/file_conflict_callbacks_test.rb @@ -133,12 +133,6 @@ def CallbackFileConflictFinish(func) allow(Yast::Mode).to receive(:commandline).and_return(false) end - # Must come first since this uses a class variable - it "creates a delayed progress popup in advance" do - expect(Yast::DelayedProgressPopup).to receive(:new).at_least(:once) - Packages::FileConflictCallbacks.register - end - it "calls the Pkg methods for registering the file conflicts handlers" do expect(dummy_pkg).to receive(:CallbackFileConflictStart).at_least(:once) expect(dummy_pkg).to receive(:CallbackFileConflictProgress).at_least(:once) From 4171b8c217764d9d5c0652fafeecbf952873352e Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Tue, 5 Apr 2022 15:26:06 +0200 Subject: [PATCH 10/13] Don't open popup if progress is almost done --- .../src/lib/ui/delayed_progress_popup.rb | 14 ++++++-- .../examples/delayed_progress_almost_done.rb | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 library/general/src/lib/ui/examples/delayed_progress_almost_done.rb diff --git a/library/general/src/lib/ui/delayed_progress_popup.rb b/library/general/src/lib/ui/delayed_progress_popup.rb index dd95abe7e..7336b2936 100644 --- a/library/general/src/lib/ui/delayed_progress_popup.rb +++ b/library/general/src/lib/ui/delayed_progress_popup.rb @@ -48,6 +48,11 @@ class DelayedProgressPopup # @return [Integer] Delay (timeout) in seconds. attr_accessor :delay_seconds + # @return [Integer] Percent (0..100) that are considered "almost done" + # so the dialog is not opened anymore if it isn't already. Default: 80 + # Set this to 100 to disable that. + attr_accessor :almost_done_percent + # @return [Boolean] Add a "Cancel" button to the dialog. Default: true. attr_accessor :use_cancel_button @@ -69,6 +74,7 @@ def initialize(delay: nil, auto_start: true, heading: nil) @delay_seconds = delay || 4 @heading = heading @use_cancel_button = true + @almost_done_percent = 80 @is_open = false start_timer if auto_start log.info "Created delayed progress popup" @@ -94,14 +100,18 @@ def self.run(delay: nil, auto_start: true, heading: nil, &block) # Update the progress. # - # If the dialog is not open yet, this opens it if the timeout is expired. + # If the dialog is not open yet, this opens it if the timeout is expired; + # unless the whole process is almost done anyway, i.e. at the time when the + # dialog would be opened, progress_percent is already at the + # @almost_done_percent threshold, so it would just open and then close + # almost immediately again. # # @param [Integer] progress_percent numeric progress bar value # @param [nil|String] progress_text optional progress bar label text # def progress(progress_percent, progress_text = nil) log.info "progress_percent: #{progress_percent}" - open_if_needed + open_if_needed unless progress_percent >= @almost_done_percent return unless open? update_progress(progress_percent, progress_text) diff --git a/library/general/src/lib/ui/examples/delayed_progress_almost_done.rb b/library/general/src/lib/ui/examples/delayed_progress_almost_done.rb new file mode 100644 index 000000000..a32f9cad6 --- /dev/null +++ b/library/general/src/lib/ui/examples/delayed_progress_almost_done.rb @@ -0,0 +1,36 @@ +# Example for the DelayedProgressPopup +# +# Start with: +# +# y2start ./delayed_progress_almost_done.rb qt +# or +# y2start ./delayed_progress_almost_done.rb ncurses +# + +require "yast" +require "ui/delayed_progress_popup" + +Yast::DelayedProgressPopup.run(delay: 3, heading: "Deep Think Mode") do |popup| + # All those parameters are optional; + # comment out or uncomment to experiment. + # popup.heading = "Deep Think Mode" + # popup.use_cancel_button = false + + puts("This will never open, not even after the #{popup.delay_seconds} sec delay.") + + 5.times do |sec| + percent = 80 + sec + puts "#{sec} sec; progress: #{percent}%" + popup.progress(percent, "Working #{sec}") + if popup.open? + # Checking for popup.open? is only needed here because otherwise there is + # no window at all yet, so UI.WaitForEvent() throws an exception. Normal + # applications have a main window at this point. + + event = Yast::UI.WaitForEvent(1000) # implicitly sleeps + break if event["ID"] == :cancel + else + sleep(1) + end + end +end From 935d5663b75540b85a5c16d84f182da77bfc3d41 Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Wed, 6 Apr 2022 14:35:55 +0200 Subject: [PATCH 11/13] Immediate feedback in progress bar label --- .../lib/packages/file_conflict_callbacks.rb | 25 +++++++++++++++++-- .../test/file_conflict_callbacks_test.rb | 7 ------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/library/packages/src/lib/packages/file_conflict_callbacks.rb b/library/packages/src/lib/packages/file_conflict_callbacks.rb index 0a554d18e..86c87cd74 100644 --- a/library/packages/src/lib/packages/file_conflict_callbacks.rb +++ b/library/packages/src/lib/packages/file_conflict_callbacks.rb @@ -90,6 +90,23 @@ def progress_bar_label _("Checking file conflicts...") end + # Set the label text of the global progress bar, if it exists. + # + # @param [String] text + # + def update_progress_text(text) + # This uses the widget ID of the global progress bar of the SlideShow + # module directly to keep the cross-dependencies down at least a little + # bit. + # + # On the plus side, this is so defensive (it does nothing if there is + # no such widget) that the worst thing that can happen is that there is + # that there is no immediate feedback by setting the progress bar label. + return unless Yast::UI.WidgetExists(:progressTotal) + + Yast::UI.ChangeWidget(:progressTotal, :Label, text) + end + # Handle the file conflict detection start callback. def start log.info "Starting the file conflict check..." @@ -100,6 +117,7 @@ def start # Don't create the DelayedProgressPopup here, otherwise there will be # conflicts between the Ruby, C++ and ex-YCP YaST components. delayed_progress_popup.start_timer + update_progress_text(progress_bar_label) # Immediate feedback end end @@ -122,13 +140,15 @@ def progress(progress) end # Handle the file conflict detection result callback. - # Ask to user whether to continue. In the AutoYaST mode an error is reported - # but the installation will continue ignoring the confliucts. + # Ask the user whether to continue. In AutoYaST mode, an error is + # reported, but the installation will continue ignoring the conflicts. + # # @param excluded_packages [Array] packages ignored in the check # (e.g. not available for check in the download-as-needed mode) # @param conflicts [Array] list of translated descriptions of # the detected file conflicts # @return [Boolean] true = continue, false = abort + # def report(excluded_packages, conflicts) log.info "Excluded #{excluded_packages.size} packages in file conflict check" log.debug "Excluded packages: #{excluded_packages.inspect}" @@ -162,6 +182,7 @@ def finish return if Yast::Mode.commandline delayed_progress_popup.close + update_progress_text(" ") # One blank to maintain the label's height end # Construct the file conflicts dialog. diff --git a/library/packages/test/file_conflict_callbacks_test.rb b/library/packages/test/file_conflict_callbacks_test.rb index 9e10c0d7d..1a9db1294 100755 --- a/library/packages/test/file_conflict_callbacks_test.rb +++ b/library/packages/test/file_conflict_callbacks_test.rb @@ -363,13 +363,6 @@ def CallbackFileConflictFinish(func) allow(Yast::Mode).to receive(:commandline).and_return(false) end - it "no change if installation progress was already displayed" do - ui = double("no method call expected", WidgetExists: true) - stub_const("Yast::UI", ui) - - finish_cb.call - end - it "closes the delayed progress popup" do expect_any_instance_of(Yast::DelayedProgressPopup).to receive(:close) From bc2b7696c458bb32b9a42c4a1f499d699df9d65a Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Wed, 6 Apr 2022 16:02:18 +0200 Subject: [PATCH 12/13] Version bump and change log --- package/yast2.changes | 6 ++++++ package/yast2.spec | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package/yast2.changes b/package/yast2.changes index eb9d7b133..6d1a284d1 100644 --- a/package/yast2.changes +++ b/package/yast2.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Apr 6 13:59:53 UTC 2022 - Stefan Hundhammer + +- Show file conflict checking progress in delayed popup (bsc#1195608) +- 4.4.48 + ------------------------------------------------------------------- Fri Mar 11 13:05:14 UTC 2022 - Imobach Gonzalez Sosa diff --git a/package/yast2.spec b/package/yast2.spec index 6e2832d67..986cb3520 100644 --- a/package/yast2.spec +++ b/package/yast2.spec @@ -17,7 +17,7 @@ Name: yast2 -Version: 4.4.47 +Version: 4.4.48 Release: 0 Summary: YaST2 Main Package From 941df074e5f1449dd76359c26e04c1850ff129ba Mon Sep 17 00:00:00 2001 From: Stefan Hundhammer Date: Wed, 6 Apr 2022 16:05:48 +0200 Subject: [PATCH 13/13] Added PR link to change log --- package/yast2.changes | 1 + 1 file changed, 1 insertion(+) diff --git a/package/yast2.changes b/package/yast2.changes index 6d1a284d1..0d7a9f6f1 100644 --- a/package/yast2.changes +++ b/package/yast2.changes @@ -2,6 +2,7 @@ Wed Apr 6 13:59:53 UTC 2022 - Stefan Hundhammer - Show file conflict checking progress in delayed popup (bsc#1195608) + PR: https://github.com/yast/yast-yast2/pull/1250 - 4.4.48 -------------------------------------------------------------------