ACC SHELL
/**
* Module: SlideShow.ycp
*
* Purpose: Slide show during installation
*
* Author: Stefan Hundhammer <sh@suse.de>
* Stanislav Visnovsky <visnov@suse.cz>
*
* $Id: SlideShow.ycp 61236 2010-03-10 15:39:50Z mvidner $
*
* Usage:
* This is a generic module for handling global progress bar with optional slideshow/release notes etc.
*
* Global progress
* ===============
* The basic idea is that the progress consists of "stages" - during a new install,
* there are 3: disk preparation, image deployment and package installation.
*
* Before the first client using the unified progress, the stages need to be set
* up, e.g. :
*
* list< map<string,any> > stages = [
* $[
* "name" : "disk",
* "description": _("Preparing disks..."),
* "value" : Mode::update() ? 0 : 120, // 2 minutes, who needs more? ;-)
* "units" : `sec,
* ],
* $[
* "name" : "images",
* "description": _("Deploying Images..."),
* "value" : ImageInstallation::TotalSize() / 1024, // kilobytes
* "units" : `kb,
* ],
* $[
* "name" : "packages",
* "description": _("Installing Packages..."),
* // here, we do a hack, because until images are deployed, we cannot
* determine how many
* // packages will be really installed additionally
* "value" : (PackageSlideShow::total_size_to_install -
* ImageInstallation::TotalSize()) / 1024 , // kilobytes
* "units" : `kb,
* ],
* ];
*
* SlideShow::Setup( stages );
*
* The function will calculate the partitioning of the unified progress based on
* estimate of a needed time. A stage can provide the estimate of time or an
* amount of data to be transferred (the constants used are based on assumption
* of 15 min install time and that the data are downloaded and written to disk).
* The logic is no rocket science as the only goal for a progress bar is to have
* it move somewhat regularly. Also, the function resets timers and other
* progress status information, including which parts are shown. See \ref SlideShow::Reset.
*
* A client using the new unified progress will do basically 2 things:
*
* 1) calls SlideShow::MoveToStage( stage-id )
* - this will move the global progress to a proper position for start of the
* stage and updates also the label ("description" entry in the map)
*
* 2) calls regularly SlideShow::StageProgress( new_percent, new_label )
* - new_percent is the progress inside of the current stage, the library will
* recompute this to get a global progress percents.
* - if new_label is nil, label is not updated.
*
* SlideShow dialogs
* =================
* // SlideShow language must be set before opening the dialog
* SlideShow::SetLanguage( Language::language );
* SlideShow::OpenDialog ();
* ... <update stages, progress ...> ...
* SlideShow::CloseDialog ();
*
* More functionality
* ==================
* The SlideShow dialog contains the following functionality:
* - global progress (see above)
* - subprogress for the current action (e.g. download a package, format
* disk, ...)
* - installation log
* - slide show support
* - optional package table
* - release notes viewer
*/
{
module "SlideShow";
textdomain "base";
import "Label";
import "Stage";
import "Wizard";
import "Mode";
import "Popup";
import "Slides";
global integer total_time_elapsed = 0;
global integer start_time = -1;
global integer initial_recalc_delay = 60; // const - seconds before initially calculating remaining times
global integer recalc_interval = 30; // const - seconds between "remaining time" recalculations
global integer next_recalc_time = time();
global integer current_slide_no = 0;
global integer slide_start_time = 0;
global integer slide_min_interval = 30; // const - minimum seconds between slide changes
global integer slide_max_interval = 3*60; // const - maximum seconds between slide changes
global integer slide_interval = slide_min_interval;
global string language = "en";
global boolean widgets_created = false;
global boolean user_switched_to_details = false;
global boolean opened_own_wizard = false;
global string inst_log = "";
global boolean debug = false;
boolean user_abort = false;
// we need to remember the values for tab switching
string total_progress_label = _("Installing...");
string sub_progress_label = _("Installing...");
integer total_progress_value = 0;
integer sub_progress_value =0;
list<term> table_items = [];
boolean _show_table = false;
// properties of the current UI
global boolean textmode = false;
global integer display_width = 80;
global string relnotes = nil;
global void ChangeSlideIfNecessary(); // forward declaration
/**
* Set the flag that user requested abort of the installation
* @param abort new state of the abort requested flag (true = abort requested)
*/
global void SetUserAbort(boolean abort)
{
user_abort = abort;
}
/**
* Get the status of the flag that user requested abort of the installation
* @return boolean state of the abort requested flag (true = abort requested)
*/
global boolean GetUserAbort()
{
return user_abort;
}
/**
* Start the internal (global) timer.
**/
global void StartTimer()
{
start_time = time();
}
/**
* Reset the internal (global) timer.
**/
global void ResetTimer()
{
start_time = time();
}
/**
* Stop the internal (global) timer and account elapsed time.
**/
global void StopTimer()
{
if ( start_time < 0 )
{
y2error( "StopTimer(): No timer running." );
return;
}
integer elapsed = time() - start_time;
start_time = -1;
total_time_elapsed = total_time_elapsed + elapsed;
y2debug("StopTimer(): Elapsed this time: %1 sec; total: %2 sec (%3:%4)",
elapsed, total_time_elapsed,
total_time_elapsed / 60, // min
total_time_elapsed % 60 ); // sec
}
/**
* Check if currently the "Details" page is shown
* @return true if showing details, false otherwise
**/
global boolean ShowingDetails()
{
return widgets_created && UI::WidgetExists(`detailsPage );
}
/**
* Check if currently the "Slide Show" page is shown
* @return true if showing details, false otherwise
**/
global boolean ShowingSlide()
{
return widgets_created && UI::WidgetExists(`slideShowPage );
}
/**
* Check if currently the "Release Notes" page is shown
* @return true if showing details, false otherwise
**/
global boolean ShowingRelNotes()
{
return widgets_created && UI::WidgetExists(`relNotesPage);
}
/**
* Restart the subprogress of the slideshow. This means the
* label will be set to \param text, value to 0.
* @param text new label for the subprogress
*/
global void SubProgressStart(string text)
{
if ( UI::WidgetExists(`progressCurrentPackage ) )
{
UI::ChangeWidget(`progressCurrentPackage, `Value, 0);
UI::ChangeWidget(`progressCurrentPackage, `Label, text);
}
sub_progress_label = text;
}
/**
* Update status of subprogress of the slideshow. The new value will be set
* to \param value, if the \text is not nil, the label will be updated
* to this text as well. Otherwise label will not change.
* @param value new value for the subprogress
* @param text new label for the subprogress
*/
global void SubProgress(integer value, string text)
{
if( UI::WidgetExists( `progressCurrentPackage ) )
{
UI::ChangeWidget(`progressCurrentPackage, `Value, value );
if( text != nil )
UI::ChangeWidget(`progressCurrentPackage, `Label, text );
}
sub_progress_value = value;
if (text != nil)
sub_progress_label = text;
}
/**
* Restart the global progress of the slideshow. This means the
* label will be set to \param text, value to 0.
* @param text new label for the global progress
*/
global void GlobalProgressStart(string text)
{
total_progress_label = text;
if ( UI::WidgetExists(`progressTotal ) )
{
UI::ChangeWidget(`progressTotal, `Value, 0);
UI::ChangeWidget(`progressTotal, `Label, text );
}
total_progress_label = text;
total_progress_value = 0;
}
/**
* Update status of global progress of the slideshow. The new value will be set
* to \param value, if the \text is not nil, the label will be updated
* to this text as well. Otherwise label will not change.
* @param value new value for the global progress
* @param text new label for the global progress
*/
void UpdateGlobalProgress(integer value, string new_text)
{
if( new_text != nil)
total_progress_label = new_text;
total_progress_value = value;
if ( UI::WidgetExists(`progressTotal ) )
{
UI::ChangeWidget(`progressTotal, `Value, value);
if( new_text != nil )
UI::ChangeWidget(`progressTotal, `Label, new_text );
}
else
y2milestone( "progressTotal widget missing" );
// update slide
if( ShowingSlide() )
{
ChangeSlideIfNecessary();
}
}
map<string, map<string,any> > _stages = $[]; // list of the configured stages
map<string, any> _current_stage = nil; // current stage
/**
* Return the description for the current stage.
* @return string localized string description
*/
global string CurrentStageDescription()
{
return _current_stage["description"]:_("Installing...");
}
/**
* Move the global progress to the beginning of the given stage.
* @param stage_name id of the stage to move to
*/
global void MoveToStage( string stage_name )
{
if( ! haskey( _stages, stage_name ) )
{
y2error( "Unknown progress stage \"%1\"", stage_name );
return;
}
_current_stage = _stages[stage_name]:nil;
y2milestone( "Moving to stage %1 (%2)", stage_name, _stages[stage_name, "start"]:0 );
// translators: default global progress bar label
UpdateGlobalProgress( _stages[stage_name, "start"]:0, _current_stage["description"]:_("Installing...") );
}
/**
* Update the global progress according to the progress in the current stage.
* The new value will be set to the per cent of the current stage according to \param value.The
* value must be lower that 100 (or it's corrected to 100).
* If the \text is not nil, the label will be updated
* to this text as well. Otherwise label will not change.
*
* @param value new value for the stage progress in per cents
* @param text new label for the global progress
*/
global void StageProgress( integer value, string text )
{
if( value > 100 )
{
y2error( "Stage progress value larger than expected: %1", value );
value = 100;
}
UpdateGlobalProgress( _current_stage["start"]:0 + (value * _current_stage["size"]:1 / 100), text );
}
/**
* Return the current global progress label.
* @return string current label
*/
global void SetGlobalProgressLabel( string text )
{
total_progress_label = text;
if ( UI::WidgetExists(`progressTotal ) )
{
UI::ChangeWidget(`progressTotal, `Label, text);
}
}
/**
* Append message to the installation log.
* @param msg message to be added, without trailing eoln
*/
global void AppendMessageToInstLog (string msg)
{
string log_line = msg + "\n";
inst_log = inst_log + log_line;
if ( ShowingDetails() )
{
if ( UI::WidgetExists( `instLog ) )
UI::ChangeWidget(`instLog, `LastLine, log_line );
}
}
/**
* Check if the dialog is currently set up so the user could switch to the slide page.
**/
global boolean HaveSlideWidget()
{
return UI::WidgetExists(`dumbTab);
}
/**
* Check if the slide show is available. This must be called before trying
* to access any slides; some late initialization is done here.
**/
global void CheckForSlides()
{
Slides::CheckBasePath();
if ( Stage::initial () || Stage::cont () )
{
if ( Slides::HaveSlideSupport() )
{
y2milestone( "Display OK for slide show, loading" );
Slides::LoadSlides( language );
}
else
{
y2warning( "Disabling slide show - insufficient display capabilities" );
}
}
}
/**
* Set the slide show text.
* @param text
**/
void SetSlideText( string text )
{
if ( UI::WidgetExists(`slideText ) )
{
UI::ChangeWidget(`slideText, `Value, text );
}
}
/**
* Set the curent language. Must be called once during initialization.
**/
global void SetLanguage( string new_language )
{
language = new_language;
y2milestone ("New SlideShow language: %1", language);
}
/**
* Create one single item for the CD statistics table
**/
global term TableItem( string id, string col1, string col2, string col3, string col4 )
{
return `item(`id( id ), col1, col2, col3, col4 );
}
/**
* Load a slide image + text.
* @param slide_no number of slide to load
**/
void LoadSlide( integer slide_no )
{
if ( slide_no > size( Slides::slides ) )
{
slide_no = 0;
}
current_slide_no = slide_no;
string slide_name = Slides::slides[slide_no]:"";
slide_start_time = time();
SetSlideText( Slides::LoadSlideFile( slide_name ) );
}
/**
* Check if the current slide needs to be changed and do that if
* necessary.
**/
global void ChangeSlideIfNecessary()
{
if ( current_slide_no + 1 < size( Slides::slides )
&& time() > slide_start_time + slide_interval )
{
y2debug( "Loading slide #%1", current_slide_no + 2 );
LoadSlide( current_slide_no + 1 );
}
}
/**
* Add widgets for progress bar etc. around a slide show page
* @param page_id ID to use for this page (for checking with UI::WidgetExists() )
* @param page_contents The inner widgets (the page contents)
* @return A term describing the widgets
**/
term AddProgressWidgets( symbol page_id, term page_contents )
{
term widgets =
`HBox(`id( page_id ),
`HSpacing( 1 ),
`VBox(
`VWeight( 1, // lower layout priority
page_contents ),
// Progress bar for overall progress of software package installation
`ProgressBar(`id(`progressTotal ), total_progress_label, 100, total_progress_value)
// intentionally omitting `Label(`nextMedia) -
// too much flicker upon update (UI::RecalcLayout() ) on NCurses
),
`HSpacing( 0.5 )
);
y2debug( "widget term: \n%1", widgets );
return widgets;
}
/**
* Construct widgets describing a page with the real slide show
* (the RichText / HTML page)
*
* @return A term describing the widgets
**/
term SlidePageWidgets()
{
term widgets =
AddProgressWidgets( `slideShowPage,
`RichText(`id(`slideText), "" )
);
y2debug( "widget term: \n%1", widgets );
return widgets;
}
term DetailsTableWidget()
{
return `VWeight( 1,
`Table( `id(`cdStatisticsTable), `opt(`keepSorting),
`header(
// Table headings for CD statistics during installation
_("Media"),
// Table headings for CD statistics during installation - keep as short as possible!
`Right( _("Installed Size") ),
// Table headings for CD statistics during installation
`Right( _("Packages") ),
// Table headings for CD statistics during installation
`Right( _("Time") )
),
table_items
)
);
}
/**
* Construct widgets for the "details" page
*
* @return A term describing the widgets
**/
term DetailsPageWidgets()
{
term widgets =
AddProgressWidgets( `detailsPage,
`VBox( _show_table ? DetailsTableWidget() : `Empty(),
`VWeight( 1,
`LogView(`id(`instLog ), _("Actions performed:"), 6, 0 )
),
`ProgressBar(`id(`progressCurrentPackage), sub_progress_label, 100, sub_progress_value )
)
);
y2debug( "widget term: \n%1", widgets );
return widgets;
}
/**
* Construct widgets for the "release notes" page
*
* @return A term describing the widgets
**/
term RelNotesPageWidgets() {
term widgets = AddProgressWidgets (`relNotesPage,
`RichText (relnotes)
);
y2debug( "widget term: \n%1", widgets );
return widgets;
}
/**
* Switch from the 'details' view to the 'slide show' view.
**/
global void SwitchToSlideView()
{
if ( ShowingSlide() )
return;
if ( UI::WidgetExists(`tabContents ) )
{
UI::ChangeWidget(`dumbTab, `CurrentItem, `showSlide );
UI::ReplaceWidget(`tabContents, SlidePageWidgets() );
// UpdateTotalProgress(false); // FIXME: this breaks other stages!
}
}
/**
* Rebuild the details page.
*/
void RebuildDetailsView()
{
if ( UI::WidgetExists(`tabContents ) )
{
UI::ChangeWidget(`dumbTab, `CurrentItem, `showDetails );
UI::ReplaceWidget(`tabContents, DetailsPageWidgets() );
y2milestone( "Contents set to details" );
}
if ( UI::WidgetExists( `instLog ) && inst_log != "" )
UI::ChangeWidget(`instLog, `Value, inst_log );
}
/**
* Switch from the 'slide show' view to the 'details' view.
**/
global void SwitchToDetailsView()
{
if ( ShowingDetails() )
{
y2milestone( "Already showing details" );
return;
}
RebuildDetailsView();
}
/**
* Switch to the 'release notes' view.
**/
global void SwitchToReleaseNotesView()
{
if ( ShowingRelNotes() )
return;
if ( UI::WidgetExists(`tabContents ) )
{
UI::ChangeWidget(`dumbTab, `CurrentItem, `showRelNotes );
UI::ReplaceWidget(`tabContents, RelNotesPageWidgets() );
// UpdateTotalProgress(false);
}
}
/**
* Help text for the dialog
*/
string HelpText()
{
// Help text while software packages are being installed (displayed only in rare cases)
string help_text = _("<p>Please wait while packages are being installed.</p>")
+ _("<P><B>Aborting Installation</B> Package installation can be aborted using the <B>Abort</B> button. However, the system then can be in an inconsistent or unusable state or it may not boot if the basic system component is not installed.</P>");
return help_text;
}
/**
* Rebuild the dialog. Useful if slides become available post-creating the dialog.
*/
global void RebuildDialog()
{
term contents = `Empty();
if ( UI::HasSpecialWidget(`DumbTab) && Slides::HaveSlideSupport()
&& Slides::HaveSlides() )
{
list tabs = [
// tab
`item(`id(`showSlide ), _("Slide Sho&w") ),
// tab
`item(`id(`showDetails ), _("&Details") )
];
if (relnotes != nil && relnotes != "")
// tab
tabs = add (tabs, `item (`id (`showRelNotes), _("Release &Notes")));
contents =
`DumbTab(`id(`dumbTab ), tabs,
`VBox(
`VSpacing( 0.4 ),
`VWeight( 1, // lower layout priority
`HBox(
`HSpacing( 1 ),
`ReplacePoint(`id(`tabContents), SlidePageWidgets() ),
`HSpacing( 0.5 )
)
),
`VSpacing( 0.4 )
)
);
}
else
{
// no tabs
contents = DetailsPageWidgets();
}
y2milestone( "SlideShow contents: %1", contents);
Wizard::SetContents(
// Dialog heading while software packages are being installed
_("Perform Installation"),
contents,
HelpText(),
false, false ); // has_back, has_next
widgets_created = true;
// if no tabs, update the log
if( ShowingDetails() )
RebuildDetailsView();
if ( ! Slides::HaveSlides() && ShowingSlide() )
SwitchToDetailsView();
}
/**
* Open the slide show base dialog with empty work area (placeholder for
* the image) and CD statistics.
**/
void OpenSlideShowBaseDialog()
{
if ( ! Wizard::IsWizardDialog() ) // If there is no Wizard dialog open already, open one
{
Wizard::OpenNextBackDialog();
opened_own_wizard = true;
}
UI::WizardCommand(`ProtectNextButton( false ) );
Wizard::RestoreBackButton();
Wizard::RestoreAbortButton();
Wizard::EnableAbortButton();
Wizard::RestoreNextButton();
Wizard::SetContents(
// Dialog heading while software packages are being installed
_("Package Installation"),
`Empty(), // Wait until InitPkgData() is called from outside
HelpText(),
false, false ); // has_back, has_next
RebuildDialog();
Wizard::SetTitleIcon("yast-sw_single");
// reset abort status
SetUserAbort(false);
}
/**
* Initialize generic data to default values
*/
global void Reset()
{
current_slide_no = 0;
slide_start_time = 0;
total_time_elapsed = 0;
start_time = -1;
next_recalc_time = -1;
textmode = UI::GetDisplayInfo()["TextMode"]:false;
display_width = UI::GetDisplayInfo()["Width"]:0;
}
/**
* Process (slide show) input (button press).
**/
global void HandleInput( any button )
{
if ( button == `showDetails && ! ShowingDetails() )
{
y2milestone( "User asks to switch to details" );
user_switched_to_details = true ;
SwitchToDetailsView();
}
else if ( button == `showSlide && ! ShowingSlide() )
{
if ( Slides::HaveSlides() )
{
user_switched_to_details = false;
SwitchToSlideView();
LoadSlide( current_slide_no );
}
else
{
UI::ChangeWidget(`dumbTab, `CurrentItem, `showDetails );
}
}
else if ( button == `showRelNotes && ! ShowingRelNotes() )
{
user_switched_to_details = false;
SwitchToReleaseNotesView();
}
else if ( button == `debugHotkey )
{
debug = ! debug;
y2milestone( "Debug mode: %1", debug );
}
// note: `abort is handled in SlideShowCallbacks::HandleInput()
}
/**
* Check for user button presses and handle them. Generic handling to be used in the
* progress handlers.
**/
global void GenericHandleInput()
{
// any button = SlideShow::debug ? UI::PollInput() : UI::TimeoutUserInput( 10 );
any button = UI::PollInput();
// in case of cancel ask user if he really wants to quit installation
if ( button == `abort || button == `cancel )
{
if ( Mode::normal () )
{
SlideShow::SetUserAbort(Popup::AnyQuestion( Popup::NoHeadline(),
// popup yes-no
_("Do you really want\nto quit the installation?"),
Label::YesButton(),
Label::NoButton(),
`focus_no ));
}
else if ( Stage::initial () )
{
SlideShow::SetUserAbort(Popup::ConfirmAbort( `unusable ));
}
else // Mode::update (), Stage::cont ()
{
SlideShow::SetUserAbort(Popup::ConfirmAbort( `incomplete ));
}
if (SlideShow::GetUserAbort())
{
SlideShow::AppendMessageToInstLog (_("Aborted"));
}
}
else
{
SlideShow::HandleInput( button );
}
}
/**
* Open the slide show dialog.
**/
global void OpenDialog()
{
// call SlideShowCallbacks::InstallSlideShowCallbacks()
WFM::call("wrapper_slideshow_callbacks", ["InstallSlideShowCallbacks"]);
// check for slides first, otherwise dialogs will be built without them
CheckForSlides();
OpenSlideShowBaseDialog();
if ( Slides::HaveSlides() )
LoadSlide(0);
else
SwitchToDetailsView();
}
/**
* Close the slide show dialog.
**/
global void CloseDialog()
{
if ( opened_own_wizard )
Wizard::CloseDialog();
// call SlideShowCallbacks::RemoveSlideShowCallbacks()
WFM::call("wrapper_slideshow_callbacks", ["RemoveSlideShowCallbacks"]);
}
global void ShowTable()
{
if ( ShowingDetails() && ! _show_table )
{
_show_table = true;
RebuildDetailsView();
}
_show_table = true;
}
global void HideTable()
{
if ( ShowingDetails() && _show_table )
{
_show_table = false;
RebuildDetailsView();
}
_show_table = false;
}
global void UpdateTable( list<term> items )
{
table_items = items;
if( ShowingDetails() && _show_table )
{
UI::ChangeWidget( `id(`cdStatisticsTable), `Items, items );
}
}
/**
* Prepare the stages for the global progressbar. Will compute the total estimate of time and
* partition the global 100% to given stages based on their estimates. Can compute out of
* time and size to download.
*
* The stages description list example:
* [
* $[
* "name" : "disk",
* "description" : "Prepare disk...",
* "value" : 85, // disk speed can be guessed by the storage, thus passing time
* "units" : `sec
* ],
* $[
* "name" : "images";
* "description" : "Deploying images...",
* "value" : 204800, // amount of kb to be downloaded/installed
* "units" : `kb
* ],
* ]
*/
global void Setup( list< map<string,any> > stages )
{
// initiliaze the generic counters
Reset();
// gather total amount of time need
integer total_time = 0;
foreach( map<string,any> stage, stages, {
if( stage["units"]:`sec == `sec )
{
total_time = total_time + stage["value"]:0;
}
else // assume kilobytes
{
// assume 15 minutes for installation of openSUSE 11.0, giving 3495 as the constant for kb/s
total_time = total_time + (stage["value"]:0 / 3495);
}
});
// avoid division by zero, set at least 1 second
if (total_time == 0)
{
total_time = 1;
}
y2milestone( "Total estimated time: %1", total_time );
integer start = 0; // value where the current stage starts
_stages = $[]; // prepare a new stages description
// distribute the total time to stages as per cents
foreach( map<string,any> stage, stages, {
if( stage["units"]:`sec == `sec )
{
stage["size"] = stage["value"]:0 * 100 / total_time;
stage["start"] = start;
start = start + stage["size"]:0;
}
else // assume kilobytes
{
// assume 15 minutes for installation of openSUSE 11.0, giving 3495 as the constant
stage["size"] = ( stage["value"]:0 * 100 ) / 3495 / total_time;
stage["start"] = start;
if( stage["size"]:0 + start > 100 )
stage["size"] = 100 - start;
start = start + stage["size"]:0;
}
_stages[ stage["name"]:"" ] = stage;
// setup first stage
if( _current_stage == nil )
_current_stage = stage;
});
y2milestone( "Global progress bar: %1", _stages );
}
/**
* Returns the current setup defined by Setup().
*
* @return map <string, map <string,any> > stages
* @see Setup()
* @struct $[ stage_name : $[ stage_setup ], ... ]
*/
global map <string, map <string,any> > GetSetup () {
return _stages;
}
}
ACC SHELL 2018