home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / root / usr / share / YaST2 / clients / inst_source.ycp < prev    next >
Text File  |  2006-11-29  |  20KB  |  661 lines

  1. /**
  2.  * Module:         inst_source.ycp
  3.  *
  4.  * Author:        Cornelius Schumacher <cschum@suse.de>
  5.  *
  6.  * Purpose:
  7.  * Adding, removing and prioritizing of installation sources for packagemanager.
  8.  *
  9.  */
  10.  
  11. {
  12.     textdomain "packager";
  13.  
  14.     import "Confirm";
  15.     import "Mode";
  16.     import "Installation";
  17.     import "PackageCallbacksInit";
  18.     import "PackageLock";
  19.     import "PackageSystem";
  20.     import "Report";
  21.     import "SLP";
  22.     import "Stage";
  23.     // SourceManager overlaps quite a bit with inst_source,
  24.     // so far we only use it for ZMD sync, TODO refactor better
  25.     import "SourceManager";
  26.     import "SuSEFirewall";
  27.     import "Wizard";
  28.  
  29.     import "Label";
  30.     import "Popup";
  31.     import "AddOnProduct";
  32.     import "Sequencer";
  33.  
  34.     // Do not sync the changes to ZENworks even if rug is present
  35.     // This is handy for repairing an already out-of-sync situation
  36.     boolean have_rug = (integer)SCR::Read (.target.size, "/usr/bin/rug") >= 0;
  37.     boolean norug = !have_rug || WFM::Args(0) == "norug";
  38.  
  39.     // constant Plaindir
  40.     string plaindir_type = "Plaindir"; 
  41.  
  42.     Wizard::CreateDialog();
  43.     Wizard::SetDesktopIcon("sw_source");
  44.     // dialog caption
  45.     Wizard::SetContents(_("Initializing..."), `Empty (), "", false, true);
  46.  
  47.    // check whether running as root
  48.    if (! Confirm::MustBeRoot () || ! PackageLock::Check ())
  49.    {
  50.     UI::CloseDialog ();
  51.     return (any)`abort;
  52.    }
  53.  
  54.     PackageCallbacksInit::InitPackageCallbacks ();
  55.  
  56.     // constructor of Product is needed in order to initialize the product
  57.     // macro. Takes a lot of time because whole package manager target
  58.     // is initialized
  59.     import "Product";
  60.  
  61.     include "packager/inst_source_dialogs.ycp";
  62.  
  63.     integer numSources = 0;
  64.  
  65.     list<map<string,any> > sourceStatesIn = [];
  66.     list<map<string,any> > sourceStatesOut = [];
  67.     list<integer> sourcesToDelete = [];
  68.  
  69.     /**
  70.         Create a table item from a map as returned by the InstSrcManager agent.
  71.         @param source The map describing the source as returned form the agent.
  72.         @return An item suitable for addition to a Table.
  73.     */
  74.     define term createItem( integer index, map source ) ``{
  75.         integer id = source[ "SrcId" ]:0;
  76.         map generalData = Pkg::SourceGeneralData( id );
  77.         map productData = Pkg::SourceProductData( id );
  78.     y2milestone("generalData: %1", generalData);
  79.     y2milestone("productData: %1", productData);
  80.         term item = `item(
  81.               `id( index ),
  82.               // corresponds to the "Enable/Disable" button
  83.               source[ "enabled" ]:true ? _("On") : _("Off"),
  84.               source["autorefresh"]:true ? _("On") : _("Off"),
  85.               // translators: unknown name for a given source
  86.               productData[ "label" ]:generalData[ "type" ]: _("unknown"),
  87.               generalData[ "url" ]:""
  88.               );
  89.         return item;
  90.     }
  91.  
  92.     /**
  93.      * Fill sources table with entries from the InstSrcManager agent.
  94.      */
  95.     define void fillTable() ``{
  96.     y2milestone ("Filling source table");
  97.         list items = [];
  98.  
  99.         numSources = size( sourceStatesOut );
  100.  
  101.         integer i = 0;
  102.         while ( i < numSources ) {
  103.             items = add( items, createItem( i, sourceStatesOut[ i ]:$[] ) );
  104.             i = i + 1;
  105.         }
  106.  
  107.         UI::ChangeWidget( `id( `table ), `Items, items );
  108.     }
  109.  
  110.     boolean LicenseAccepted (integer id) {
  111.     Wizard::CreateDialog ();
  112.     boolean ret = AddOnProduct::AcceptedLicenseAndInfoFile (id);
  113.     UI::CloseDialog ();
  114.     return ret;
  115.     }
  116.  
  117.     define symbol createSource( string url ) ``{
  118.     y2milestone("createSource: %1", url);
  119.  
  120.         if ( url != "" )
  121.         {
  122.         // for Plaindir source we have to use SourceCreateType() binding
  123.         boolean plaindir = false;
  124.         map parsed = URL::Parse(url);
  125.         string scheme = parsed["scheme"]:"";
  126.  
  127.         if (scheme == "pkg")
  128.         {
  129.         parsed["scheme"] = "dir";
  130.         url = URL::Build(parsed);
  131.         plaindir = true;
  132.         }
  133.  
  134.         // check if SMB/CIFS share can be mounted
  135.         if (scheme == "smb" && SCR::Read(.target.size, "/sbin/mount.cifs") < 0)
  136.         {
  137.         y2milestone("SMB/CIFS share cannot be mounted, installing missing 'cifs-mount' package...");
  138.         // install cifs-mount package
  139.         PackageSystem::CheckAndInstallPackages(["cifs-mount"]);
  140.         }
  141.  
  142.             list<integer> newSources = (plaindir) ?
  143.         [ Pkg::SourceCreateType(url, "", plaindir_type) ] :
  144.         Pkg::SourceScan( url, "" );
  145.  
  146.             if ( size( newSources ) == 0  )
  147.             {
  148.         // message part 1
  149.                 string _msg1 = sformat( _("Unable to create installation source
  150. from URL '%1'."), url );
  151.         /* FIXME Pkg::LastErrorId always returns 'ok'
  152.                 string err = Pkg::LastErrorId();
  153.                 if ( err != "ok" ) {
  154.                     if ( err == "instsrc_duplicate" )
  155.             // message part 2 alt. 1
  156.                         _msg2 = _("A catalog for this product already exists.");
  157.                     else
  158.                 }
  159.         */
  160.         // message part 2 alt. 2 followed by description
  161.                 string _msg2 = _("Details:") + "\n" + Pkg::LastError() + "\n" +
  162.             // message part 3
  163.             _("Try again?");
  164.  
  165.                 boolean tryagain = Popup::YesNo( _msg1 + "\n" + _msg2 );
  166.                 if ( tryagain ) return `again;
  167.                 else return `cancel;
  168.             }
  169.             else
  170.             {
  171.         list<integer> prod_sources = filter (integer s, newSources, {
  172.             map src_data = Pkg::SourceGeneralData (s);
  173.             string src_type = src_data["type"]:"";
  174.             return (src_type == "YaST" || src_type == "YUM" || src_type == plaindir_type);
  175.         });
  176.         if (size (prod_sources) == 0)
  177.         {
  178.             if (! Popup::AnyQuestion (
  179.             Popup::NoHeadline (),
  180. // continue-back popup
  181. _("There is no product information available at the given location.
  182. If you expected to address a product, return back and enter
  183. the correct location.
  184. To make rpm packages located at the specified location available
  185. in the packages selection, continue."),
  186.             Label::ContinueButton (),
  187.             Label::BackButton (),
  188.             `focus_yes))
  189.             {
  190.             return `again;
  191.             }
  192.         }
  193.                 foreach( integer id, newSources, ``{
  194.           if (! LicenseAccepted (id))
  195.           {
  196.             Pkg::SourceDelete (id);
  197.           }
  198.           else
  199.           {
  200.             map src_data = Pkg::SourceGeneralData (id);
  201.             boolean auto_refresh = src_data["autorefresh"]:false;
  202.                     map<string, any> sourceState = $[ "SrcId": id, "enabled": true, "autorefresh" : auto_refresh ];
  203.                     sourceStatesOut = add( sourceStatesOut, sourceState ); 
  204.           }
  205.                 } );
  206.                 return `ok;
  207.             }
  208.         }
  209.     }
  210.  
  211.     /**
  212.      * Find which sources have to be added or deleted to ZENworks.
  213.      * #182992: formerly we did not consider the enabled attribute.
  214.      * But ZENworks cannot completely disable a source (unsubscribing a
  215.      * catalog merely decreases its priority) so we consider a disabled source
  216.      * like a deleted one.
  217.      * @param statesOld sourceStates{In or Out}
  218.      * @param statesNew sourceStates{In or Out}
  219.      * @return the list of SrcId's that are enabled in statesNew
  220.      *  but are not enabled in statesOld
  221.      */
  222.     list<integer> newSources (list<map<string,any> > statesOld,
  223.                   list<map<string,any> > statesNew) {
  224.     y2milestone ("From %1 To %2", statesOld, statesNew);
  225.     list<integer> ret = [];
  226.     map<integer, boolean> seen = listmap (
  227.         map<string, any> src, statesOld,
  228.         ``( $[(src["SrcId"]:-1) : (src["enabled"]:true) ] ));
  229.     foreach (map<string, any> src, statesNew, {
  230.         integer newid = src["SrcId"]:-1;
  231.         boolean newena = src["enabled"]:true;
  232.         if (newena && ! seen[newid]:false)
  233.         ret = add (ret, newid);
  234.     });
  235.     y2milestone ("Difference %1", ret);
  236.     return ret;
  237.     }
  238.  
  239.     define void deleteSource( integer index ) ``{
  240.     integer srcid = sourceStatesOut[index, "SrcId"]:-1;
  241.  
  242.     if( srcid != -1)
  243.         sourcesToDelete = add( sourcesToDelete, srcid );
  244.  
  245.         sourceStatesOut = remove( sourceStatesOut, index );
  246.     }
  247.  
  248.     boolean Write() {
  249.      boolean success = Pkg::SourceEditSet( sourceStatesOut );
  250.  
  251.     // we must sync before the sources are deleted from zypp
  252.     // otherwise we will not get their details
  253.     list<integer> added   = newSources (sourceStatesIn, sourceStatesOut);
  254.     list<integer> deleted = newSources (sourceStatesOut, sourceStatesIn);
  255.     boolean have_rug = !norug && (integer)SCR::Read (.target.size, "/usr/bin/rug") >= 0;
  256.     boolean any_changed = added != [] || deleted != []; // #217697
  257.     if (success && have_rug && any_changed) {
  258.         UI::OpenDialog (
  259.         `VBox (
  260.             `Label (SourceManager::SyncLabel ()),
  261.             `PushButton (`id (`abort), Label::AbortButton ())
  262.             ));
  263.         boolean syncok = SourceManager::SyncAddedAndDeleted (added, deleted);
  264.         UI::CloseDialog ();
  265.         if (!syncok)
  266.         {
  267.         // yes/no popup
  268.         if (!Popup::YesNo (_("Source synchronization with ZMD failed.
  269. Save changes anyway?")))
  270.             success    = false;
  271.  
  272.         }
  273.     }
  274.     else {
  275.         y2milestone ("No rug, not syncing");
  276.     }
  277.  
  278.     foreach( integer id, sourcesToDelete, ``{
  279.         success = success && Pkg::SourceDelete(id);
  280.     });
  281.  
  282.     // store in the persistent libzypp storage
  283.     success = success && Pkg::SourceSaveAll(); // #176013
  284.  
  285.     return success;
  286.    }
  287.  
  288.  
  289. symbol SummaryDialog () {
  290.     y2milestone ("Running Summary dialog");
  291.     list items = [];
  292.  
  293.     // pusg button
  294.     string replaceButtonLabel = _("&Replace...");
  295.     // pusg button
  296.     string refreshButtonLabel = _("Re&fresh Now...");
  297.     // pusg button
  298.     string enableButtonLabel = _("Enab&le or Disable");
  299.     // push button
  300.     string refreshOnOffButtonLabel = _("Refre&sh On or Off");
  301.  
  302.     term contents =
  303.         `VBox(
  304.           `HBox(
  305.             `Table( `id( `table ),/* `opt( `keepSorting ),*/
  306.                 // table header
  307.                 `header( _("Status"),
  308.                 // table header
  309.                 _("Refresh"),
  310.                 // table header
  311.                 _("Name"),
  312.                 // table header
  313.                 _("URL") ),
  314.                 items ),
  315.             `HSpacing()
  316.             ),
  317.           // TODO Help
  318.           `Left (`CheckBox (`id (`zmdsync),
  319.                 // Checkbox label
  320.                 _("Synchronize Changes with &ZENworks"),
  321.                 !norug)),
  322.           `HBox(
  323.             `PushButton (`id (`add), `opt(`key_F3),
  324.             Label::AddButton ()),
  325.             `PushButton(`id(`replace), `opt(`key_F4),
  326.             Label::EditButton ()),
  327.             `PushButton (`id(`delete), `opt(`key_F5),
  328.             Label::DeleteButton ()),
  329.             `HStretch (),
  330.             // menu button label
  331.             `MenuButton (`opt(`key_F6), _("Source Settings"), [
  332.             `item(`id(`enable), enableButtonLabel),
  333.             `item(`id(`refresh_on_off), refreshOnOffButtonLabel),
  334.             `item(`id(`refresh), refreshButtonLabel )
  335.             ])
  336.         ),
  337.                 `VSpacing( 0.5 )
  338.         );
  339.  
  340.     // dialog caption
  341.     string title = _("Configured Software Catalogs");
  342. //    string title = _("Media Containing the Software Catalog");
  343.  
  344.     // help
  345.     string help_text = _("<p>
  346. In this dialog, manage configured software catalogs.</p>");
  347.  
  348.     help_text = help_text + _("<p>
  349. <b>Adding a New Catalog</b><br>
  350. To add a new catalog, use <b>Add</b> and specify the software catalog.
  351. </p>");
  352.  
  353.     // help, continued
  354.     help_text = help_text + _("<p>
  355. To install packages from <b>CD</b>,
  356. have the &product; CD set or the DVD available.
  357. </p>
  358. ");
  359.  
  360.     // help, continued
  361.     help_text = help_text + _("<p>
  362. The &product; CDs can be copied to the <b>hard disk</b>.
  363. Then use that as the installation source.
  364. Insert the path name where the first
  365. CD is located, for example, /data1/<b>CD1</b>.
  366. Only the base path is required if all CDs are copied
  367. into one directory.
  368. </p>
  369. ");
  370.  
  371.     // help, continued
  372.     help_text = help_text + _("<p>
  373. <b>Network</b> installation requires a working network connection.
  374. Configure YaST2's \"Network Devices\" module first,
  375. if required.  Specify the directory where the packages from
  376. the first CD are located, such as /data1/CD1.
  377. Only the base path is
  378. required if packages are not divided, for example, /usr/full-i386.
  379. The directory must be listed in the file <i>/etc/exports</i>
  380. on the NFS server.
  381. </p>
  382. ");
  383.  
  384.     // help, continued
  385.     help_text = help_text + _("<p>
  386. <b>Modifying a Catalog</b>
  387. To change a catalog media, use <b>Edit</b>. To remove a catalog, use
  388. <b>Delete</b>. To enable or disable the catalog, set refreshing on the
  389. initialization time on or off, or refresh it immediatelly, use 
  390. <b>Source Settings</b>.");
  391.  
  392.     // help, continued
  393.     help_text = help_text + _("<p>
  394. <b>Synchronize Changes with ZENworks</b> will call <tt>rug</tt>
  395. to perform the changes also in that package management system.</p>");
  396.  
  397.     Wizard::SetNextButton(`next, Label::FinishButton() );
  398.     Wizard::SetContents(title, contents, help_text, false, true);
  399.     Wizard::HideBackButton();
  400.     UI::ChangeWidget (`id (`zmdsync), `Enabled, have_rug);
  401.  
  402.     fillTable();
  403.  
  404.     symbol input = nil;
  405.  
  406.     integer current = -1;
  407.  
  408.     string url = "";
  409.  
  410.     boolean exit = false;
  411.  
  412.     repeat {
  413.  
  414.     if ( current >= 0 ) {
  415.         UI::ChangeWidget( `id( `table ), `CurrentItem, current );
  416.     }
  417.  
  418.     input = (symbol)Wizard::UserInput();
  419.     y2debug( "Input: %1", input );
  420.  
  421.     symbol createResult = `again;
  422.  
  423.     if (input == `add)
  424.     {
  425.         return `add;
  426.     }
  427.     if ( input == `next )
  428.         {
  429.       norug = ! (boolean) UI::QueryWidget (`id (`zmdsync), `Value);
  430.       // store the new state
  431.           boolean success = Write();
  432.           if ( !success ) {
  433.         // popup message part 1
  434.                 string _msg1 = _("Unable to save changes to installation source
  435. configuration.");
  436.                 string details = Pkg::LastError();
  437.         // popup message part 2 followed by other info
  438.                 string _msg2 = details != "" ? (_("Details:") + "\n" + details)
  439.             : "";
  440.         // popup message part 3
  441.                 _msg2 = _msg2 + "\n" + _("Try again?");
  442.  
  443.                 boolean tryagain = Popup::YesNo( _msg1 + "\n" + _msg2 );
  444.                 if ( !tryagain ) exit = true;
  445.           } else {
  446.             exit = true;
  447.           }
  448.         }
  449.     // Wizard::UserInput returns `back instead of `cancel when window is closed by WM
  450.         else if (input == `abort || input == `back)
  451.         {
  452.       // popup headline
  453.           string headline = _("Abort Catalog Configuration");
  454.       // popup message
  455.           string msg = _("Abort the catalog configuration?
  456. All changes will be lost.");
  457.           if ( Popup::YesNoHeadline( headline, msg ) ) {
  458.             exit = true;
  459.           }
  460.         }
  461.         else
  462.         {
  463.             current = (integer) UI::QueryWidget( `id( `table ), `CurrentItem );
  464.  
  465.             y2debug( "Current item: %1", current );
  466.  
  467.             map<string, any> sourceState = sourceStatesOut[ current ]:$[];
  468.             integer id = sourceState[ "SrcId" ]:-1;
  469.             
  470.             if ( id < 0 ) {
  471.               y2internal( "Unable to determine source id" );
  472.               continue;
  473.             }
  474.  
  475.             if ( input == `replace )
  476.             {
  477.                 map generalData = Pkg::SourceGeneralData( id );
  478.                 string url = generalData[ "url" ]:"";
  479.         boolean auto_refresh = sourceState["autorefresh"]:true;
  480.         boolean plaindir = generalData["type"]:"YaST" == plaindir_type;
  481.  
  482.                 do {
  483.             // change schema if the source type is plaindir
  484.             // to show the right popup dialog
  485.             if (plaindir)
  486.             {
  487.             map parsed = URL::Parse(url);
  488.             parsed["scheme"] = "pkg";
  489.             url = URL::Build(parsed);
  490.             }
  491.  
  492.                     url = editUrl( url );
  493.  
  494.                     if ( size( url ) == 0 ) break;
  495.                     createResult = createSource( url );
  496.                     if ( createResult == `ok ) {
  497.             deleteSource( current );
  498.             fillTable();
  499.                     }
  500.                 } while ( createResult == `again );
  501.             }
  502.         else if ( input == `refresh )
  503.             {
  504.         Pkg::SourceRefreshNow (id);
  505.         fillTable ();
  506. /*                map generalData = Pkg::SourceGeneralData( id );
  507.                 string url = generalData[ "url" ]:"";
  508.  
  509.                 do {
  510.                     // url = editUrl( url );
  511.                     if ( size( url ) == 0 ) break;
  512.                     deleteSource( current );
  513.                     createResult = createSource( url );
  514.                     if ( createResult == `ok ) {
  515.             fillTable();
  516.                     }
  517.                 } while ( createResult == `again );*/
  518.             }
  519.             else if ( input == `delete )
  520.             {
  521.         // yes-no popup
  522.                 if ( Popup::YesNo( _("Delete the selected catalog from the list?") ) )
  523.                 {
  524.                     deleteSource( current );
  525.                     fillTable();
  526.                 }
  527.             }
  528.             else if ( input == `enable )
  529.             {
  530.                 boolean state = sourceState[ "enabled" ]:true;
  531.                 state = !state;
  532.         // corresponds to the "Enable/Disable" button
  533.                 string newstate = ( state ? _("On") : _("Off") );
  534.                 UI::ChangeWidget( `id( `table ), `Item( current, 0 ), newstate );
  535.                 sourceState[ "enabled" ] = state;
  536.                 sourceStatesOut[ current ] = sourceState;
  537.             }
  538.             else if ( input == `refresh_on_off )
  539.             {
  540.         integer source_id = sourceState["SrcId"]:0;
  541.         map src_data = Pkg::SourceGeneralData (source_id);
  542.         string type = src_data["type"]:"UnitedLinux";
  543.  
  544.                 boolean state = sourceState[ "autorefresh" ]:true;
  545.         if (type == "PlainDir" && ! state)
  546.         {
  547.             // popup message
  548.             Popup::Message (_("For the selected source, refresh
  549. cannot be set."));
  550.         }
  551.         else
  552.         {
  553.             state = !state;
  554.         }
  555.         // corresponds to the "Enable/Disable" button
  556.                 string newstate = ( state ? _("On") : _("Off") );
  557.                 UI::ChangeWidget( `id( `table ), `Item( current, 1 ), newstate );
  558.                 sourceState["autorefresh"] = state;
  559.                 sourceStatesOut[ current ] = sourceState;
  560.             }
  561.         }
  562.  
  563.     } until ( exit );
  564.  
  565.     UI::CloseDialog();
  566.  
  567.     y2debug( "Return: %1", input );
  568.  
  569.     return input;
  570. }
  571.  
  572. symbol StoreSource () {
  573.     string url = SourceDialogs::GetURL ();
  574.     if (url == "slp://")
  575.     {
  576.     string service = SourceManager::AddSourceTypeSLP ();
  577.     y2milestone ("Trying to add source '%1'", service);
  578.     if (service != nil)
  579.     {
  580.         // add the installation source
  581.         symbol createResult = createSource(service);
  582.         y2milestone ("Adding source result: %1", createResult);
  583.         return `next;
  584.     }
  585.     return `back;
  586.     }
  587.     if (createSource(url) == `again)
  588.     return `back;
  589.     return `next;
  590. }
  591.  
  592. boolean restore = Pkg::SourceStartManager( false );
  593. if( ! restore )
  594. {
  595.     // #210514 (L3): unconditionally removing the sources is BAD.
  596.     // Usually it is just a temporary error because of misconfigured proxy.
  597.     boolean cleanup = Popup::AnyQuestion( // [1]
  598.     Label::ErrorMsg(),
  599.     // Error popup (part 1)
  600.     _("There were errors when restoring the source configuration.
  601. Not all sources are available for configuration.
  602. ") + Pkg::LastError() + "\n\n" +
  603.     // Error popup (part 2): Yes/No question.
  604.     // "immediately" is important: because of stupid design [2],
  605.     // they will be removed even if the user then chooses Abort :(
  606.     _("Do you want to immediately remove these sources?"),
  607.     Label::YesButton(), Label::NoButton(), `focus_no);
  608.     // Notes irrelevant for translators:
  609.     // [1]: we want the focus on "No"; Popup::YesNo is too specialized
  610.     // [2]: using SourceManager and not just PersistentStorage in inst_source
  611.     
  612.     // delete the broken sources from the persistent store
  613.     if (cleanup) {
  614.     Pkg::SourceCleanupBroken();
  615.     }
  616. }
  617.  
  618. sourceStatesIn = Pkg::SourceEditGet();
  619. y2milestone( "Found sources: %1", sourceStatesIn);
  620. sourceStatesOut = sourceStatesIn;
  621.  
  622. map<string,any> aliases = $[
  623.     "summary" : ``(SummaryDialog ()),
  624.     "type" : ``(SourceDialogs::TypeDialog ()),
  625.     "edit" : ``(SourceDialogs::EditDialog ()),
  626.     "store" : ``(StoreSource ())
  627. ];
  628.  
  629. map sequence = $[
  630.     "ws_start" : "summary",
  631.     "summary" : $[
  632.     `add : "type",
  633.     `edit : "edit",
  634.     `abort : `abort,
  635.     `next : `next,
  636.     ],
  637.     "type" : $[
  638.     `next : "edit",
  639.     `finish : "store",
  640.     `abort : `abort,
  641.     ],
  642.     "edit" : $[
  643.     `next : "store",
  644.     `abort : `abort,
  645.     ],
  646.     "store" : $[
  647.     `next : "summary",
  648.     `abort : `abort,
  649.     ],
  650. ];
  651.  
  652. y2milestone ("Starting source sequence");
  653. symbol ret = Sequencer::Run (aliases, sequence);
  654. if (ret == `next && Mode::normal ())
  655.     ret    = `restart_menu;
  656.  
  657. UI::CloseDialog ();
  658. return ret;
  659.  
  660. } // EOF
  661.