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 / controller.ycp < prev    next >
Text File  |  2006-11-29  |  18KB  |  642 lines

  1. /**
  2.  * File:
  3.  *   controller.ycp
  4.  *
  5.  * Module:
  6.  *   Configuration of disk controller
  7.  *
  8.  * Summary:
  9.  *   Main file
  10.  *
  11.  * Authors:
  12.  *   THomas Fehr <fehr@suse.de>
  13.  *
  14.  * $Id: controller.ycp 27442 2006-01-30 14:53:09Z fehr $
  15.  *
  16.  */
  17.  
  18. {
  19. textdomain "storage";
  20.  
  21. import "Wizard";
  22. import "Misc";
  23. /*import "Bootloader";*/
  24.  
  25. import "Label";
  26. import "Popup";
  27.  
  28. list<string> initrd = [];
  29. list<string> initrd_orig = [];
  30. map<string,string> params = $[];
  31. map<string,string> params_orig = $[];
  32.  
  33. define string HelpText()
  34.     ``{
  35.     // help text
  36.     return( _("<p><big><b>Disk Controller Configuration</b></big></p>
  37. <p>Here, configure disk controllers by modifying the corresponding kernel module.</p>
  38. <p>The table at top contains the controllers to configure. If
  39. there is more than one controller, select the controller
  40. by clicking a line in the table.  The order in the table determines
  41. the order in which kernel modules are loaded.  Use <b>Move Up</b> and
  42. <b>Move Down</b> to change the order.
  43. </p>
  44. <p><b>Module to Use</b> selects the kernel module to use for the current controller.  Alternative modules are available for some controllers.</p>
  45. <p>The module status is shown in <b>Module Currently Loaded</b>.
  46. With <b>Load Module in initrd</b> set whether the module should be
  47. loaded during boot.</p>
  48. <p>For <b>Module Parameters</b>, enter any parameters for the module.
  49. This is often not needed.</p>
  50. <p><b>Test Loading of Module</b> tests whether the module can be loaded with the specified parameters.  This test is recommended if you have made changes.</p>
  51. "));
  52.     }
  53.  
  54. define term ModComboBox( list modules )
  55.     ``{
  56.     return( `ComboBox( `id(`module), `opt(`editable,`notify),
  57.                // label text
  58.                        _("&Module to Use"), modules ));
  59.     };
  60.  
  61. define term InitCheckBox( boolean val )
  62.     ``{
  63.     return( `CheckBox( `id(`initrd), `opt(`notify),
  64.                // button text, initrd should be left as-is
  65.                _("&Load Module in initrd"), val ));
  66.     };
  67.  
  68. define void SetModule( map cont )
  69.     ``{
  70.     integer num = 0;
  71.     list mod = maplist( map entry, cont["drivers"]:[],
  72.     ``{
  73.     term it = `item( `id(num), mergestring(entry["modules",0]:[]," "),
  74.                      cont["modnum"]:0==num );
  75.     num = num+1;
  76.     return( it );
  77.     });
  78.     y2milestone( "mod=%1", mod );
  79.     UI::ReplaceWidget( `id(`rep_mod), ModComboBox( mod ));
  80.     if( !haskey( cont, "modnum" ))
  81.     {
  82.     cont["modnum"] = 0;
  83.     }
  84.     };
  85.  
  86. define void SetLoaded( string mod )
  87.     ``{
  88.     SCR::UnmountAgent (.proc.modules);
  89.     map lmod = (map)SCR::Read(.proc.modules);
  90.     // label text
  91.     string val = haskey(lmod, mod) ? _("Yes") : _("No");
  92.     UI::ChangeWidget( `id(`loaded), `Value, val );
  93.     y2milestone( "Loaded=%1", val );
  94.     }
  95.  
  96. define void SetInitrd( string mod )
  97.     ``{
  98.     boolean val = contains(initrd,mod);
  99.     UI::ReplaceWidget( `id(`rep_initrd), InitCheckBox( val ));
  100.     y2milestone( "Initrd=%1", val );
  101.     }
  102.  
  103. define void SetParams( string mod, string defaultp )
  104.     ``{
  105.     string param = defaultp;
  106.     if( haskey( params, mod ))
  107.     {
  108.     param = params[mod]:"";
  109.     }
  110.     else if( haskey( params_orig, mod ))
  111.     {
  112.     param = params_orig[mod]:"";
  113.     }
  114.     else
  115.     {
  116.     list opts = (list)SCR::Read( .modules.options );
  117.     y2milestone( "ppts=%1", opts );
  118.     if( contains( opts, mod ))
  119.         {
  120.         map<string,string> options = (map<string,string>)SCR::Read( .modules.options, mod );
  121.         y2milestone( "options %1 = %2", mod, options );
  122.         foreach( string k, string v, options,
  123.         ``{
  124.         if( size(param)>0 )
  125.             {
  126.             param = param + " ";
  127.             }
  128.         if( size(v)==0 )
  129.             {
  130.             param = param + k;
  131.             }
  132.         else
  133.             {
  134.             param = param + k + "=" + v;
  135.             }
  136.         });
  137.         }
  138.     params_orig[mod] = param;
  139.     }
  140.     UI::ChangeWidget( `id(`param), `Value, param );
  141.     }
  142.  
  143. define string ModNameChanged( map cont, integer num, integer old )
  144.     ``{
  145.     string oldmod = cont["drivers",old,"modules",0,0]:"";
  146.     string mod = cont["drivers",num,"modules",0,0]:"";
  147.     y2milestone( "mod=%1 old=%2", mod, old );
  148.     if( oldmod != mod )
  149.     {
  150.     params[oldmod] = (string)UI::QueryWidget( `id(`param), `Value );
  151.         if( contains( initrd, oldmod ) )
  152.         {
  153.         initrd = maplist( string e, initrd, ``{ return( e==oldmod ? mod : e ); });
  154.         y2milestone( "new initrd=%1", initrd );
  155.         }
  156.     }
  157.     SetLoaded( mod );
  158.     SetInitrd( mod );
  159.     SetParams( mod, cont["drivers",num,"modules",1]:"" );
  160.     return( mod );
  161.     }
  162.  
  163. define integer TermToId( term t )
  164.     ``{
  165.     return( (integer)(t[0,0]:0) );
  166.     }
  167.  
  168. define integer GetArrayPos( list l, integer num )
  169.     ``{
  170.     integer i = 0;
  171.     while( i < size(l) && TermToId(l[i]:`Empty())!=num )
  172.     {
  173.     i = i+1;
  174.     }
  175.     return( i );
  176.     }
  177.  
  178. define string SetAllData( map cont, list clist, integer cur )
  179.     ``{
  180.     string ret = "";
  181.     if( size(cont)>0 )
  182.     {
  183.     SetModule( cont );
  184.     integer num = (integer)UI::QueryWidget( `id(`module), `Value );
  185.     ret = ModNameChanged( cont, num, num );
  186.     UI::ChangeWidget( `id(`moveup), `Enabled, GetArrayPos(clist,cur)>0 );
  187.     UI::ChangeWidget( `id(`movedown), `Enabled,
  188.                       GetArrayPos(clist,cur)<size(clist)-1 );
  189.     }
  190.     return( ret );
  191.     }
  192.  
  193. define boolean TestLoad( map cont )
  194.     ``{
  195.     y2milestone( "num %1 drivers %2", cont["modnum"]:0, cont["drivers"]:[] );
  196.     string cmd = "";
  197.     foreach( map e, cont["drivers"]:[],
  198.     ``{
  199.     foreach( list m, e["modules"]:[],
  200.         ``{
  201.         if( size(m[0]:"")>0 )
  202.         {
  203.         cmd = "/sbin/rmmod -r " + m[0]:"";
  204.         y2milestone( "cmd %1", cmd );
  205.         SCR::Execute( .target.bash, cmd );
  206.         }
  207.         });
  208.     });
  209.     map m = cont["drivers",cont["modnum"]:0]:$[];
  210.     if( size(m)>0 && size(m["modules"]:[])>0 )
  211.     {
  212.     boolean ok = true;
  213.     string param = (string) UI::QueryWidget( `id(`param), `Value );
  214.     integer num = 0;
  215.     foreach( list d, m["modules"]:[],
  216.         ``{
  217.         cmd = m["modprobe"]:false ? "/sbin/modprobe " : "/sbin/insmod ";
  218.         y2milestone( "cmd %1", cmd );
  219.         cmd = cmd + d[0]:"" + " ";
  220.         y2milestone( "cmd %1", cmd );
  221.         cmd = cmd + (num==0 ? param : d[1]:"");
  222.         y2milestone( "cmd %1", cmd );
  223.         map ret = (map)SCR::Execute( .target.bash_output, cmd );
  224.         if( ret["exit"]:1 == 1)
  225.         {
  226.         // error popup text; stderr=standard error
  227.         string text = sformat( _("
  228. Error loading module.
  229. Executed command was:
  230.     %1
  231.  
  232. stderr was:
  233. %2
  234. "), cmd, ret["stderr"]:"" );
  235.         Popup::Error( text );
  236.         ok = false;
  237.         }
  238.         });
  239.     if( ok )
  240.         {
  241.         // popup text, %1=module name
  242.         Popup::Message( sformat(_("Module %1 loaded successfully."),
  243.                                   m["modules",0,0]:"" ));
  244.         }
  245.     }
  246.     }
  247.  
  248.     Wizard::OpenAbortApplyFinishDialog();
  249.  
  250.     list<map> dev = [];
  251.     foreach( map entry, (list<map>)SCR::Read(.probe.storage),
  252.     ``{
  253.     map conf = (map)SCR::Read(.probe.status, entry["unique_key"]:"");
  254.     y2milestone( "entry:%1", entry );
  255.     y2milestone( "conf:%1", conf );
  256.     if( conf["available"]:`no != `no && size(entry["drivers"]:[])>0 &&
  257.             find( map e, dev, ``(e["bus"]:""==entry["bus"]:"" &&
  258.                  e["slot_id"]:0==entry["slot_id"]:0 &&
  259.                  e["bus_id"]:0==entry["bus_id"]:0))==nil )
  260.         {
  261.         entry["conf"] = conf;
  262.         dev = add( dev, entry );
  263.         }
  264.     });
  265.  
  266.     /*
  267.     dev = add( dev, $[ "conf" : $[ "configured" : `new ],
  268.                        "drivers" : [ $[ "modprobe" : false,
  269.                                 "modules" : [ [ "loop_fish2", "" ]]]],
  270.                "model" : "Loop driver" ] );
  271.     */
  272.  
  273.  
  274.  
  275.     boolean first_new = true;
  276.     integer cur_num = 0;
  277.     integer num = 0;
  278.     list<term> clist = maplist( map entry, dev,
  279.     ``{
  280.     term it = `item( `id(num) );
  281.     it = add( it, entry["model"]:"" );
  282.     if( entry["conf","configured"]:`no==`new )
  283.         {
  284.         // label text
  285.         it = add( it, _("Yes"));
  286.         if( first_new )
  287.         {
  288.         first_new = false;
  289.         cur_num = num;
  290.         }
  291.         }
  292.     else
  293.         {
  294.         // label text
  295.         it = add( it, _("No") );
  296.         }
  297.     num = num+1;
  298.     return( it );
  299.     });
  300.  
  301.     string tmp = Misc::SysconfigRead( .sysconfig.kernel.INITRD_MODULES, "");
  302.     initrd_orig = filter( string e, splitstring( tmp, " " ), ``(size(e)>0) );
  303.     initrd = initrd_orig;
  304.     y2milestone( "initrd %1", tmp );
  305.  
  306.     clist = sort( term a, term b, clist,
  307.     ``{
  308.     boolean ret = true;
  309.     integer numa = TermToId(a);
  310.     integer numb = TermToId(b);
  311.     list pos_mod = [];
  312.     foreach( map e, dev[numa,"drivers"]:[],
  313.         ``{
  314.         if( size(e["modules",0,0]:"")>0 )
  315.         pos_mod = add( pos_mod, e["modules",0,0]:"");
  316.         });
  317.     integer initrda = -1;
  318.     integer initrdb = -1;
  319.     integer posm = 0;
  320.     integer posi = 0;
  321.     while( posm < size(pos_mod) && initrda==-1 )
  322.         {
  323.         posi = 0;
  324.         while( posi<size(initrd) && initrda==-1 )
  325.         {
  326.         if( initrd[posi]:"" == pos_mod[posm]:"" )
  327.             {
  328.             initrda = posi;
  329.             }
  330.         posi = posi + 1;
  331.         }
  332.         posm = posm + 1;
  333.         }
  334.     pos_mod = [];
  335.     foreach( map e, dev[numb,"drivers"]:[],
  336.         ``{
  337.         if( size(e["modules",0,0]:"")>0 )
  338.         pos_mod = add( pos_mod, e["modules",0,0]:"");
  339.         });
  340.     posm = 0;
  341.     while( posm < size(pos_mod) && initrdb==-1 )
  342.         {
  343.         posi = 0;
  344.         while( posi<size(initrd) && initrdb==-1 )
  345.         {
  346.         if( initrd[posi]:"" == pos_mod[posm]:"" )
  347.             {
  348.             initrdb = posi;
  349.             }
  350.         posi = posi + 1;
  351.         }
  352.         posm = posm + 1;
  353.         }
  354.     y2debug( "a:%1 initrda:%2 b:%3 initrdb:%4", numa, initrda, numb,
  355.          initrdb );
  356.     if( initrda!=-1 && initrdb!=-1 )
  357.         {
  358.         ret = initrda<=initrdb ;
  359.         }
  360.     else if( initrda==-1 && initrdb==-1 )
  361.         {
  362.         boolean newa = dev[numa,"conf","configured"]:`no == `new;
  363.         boolean newb = dev[numb,"conf","configured"]:`no == `new;
  364.         if( newa && !newb )
  365.         ret = false;
  366.         else
  367.         ret = true;
  368.         }
  369.     else
  370.         {
  371.         ret = initrda!=-1;
  372.         }
  373.     ret = ret && a!=b;
  374.     y2debug( "ret %1", ret );
  375.     return( ret );
  376.     });
  377.     y2milestone( "clist=%1", clist );
  378.  
  379.     term dialog =
  380.     `VBox(
  381.         `VSpacing(1),
  382.         /*`Left(*/`HBox( `HWeight(70, `Table( `id(`controller),
  383.                                           `opt(`notify,`immediate,`keepSorting),
  384.                           // table heading text
  385.                                           `header( _("Disk Controller"), _("New")),
  386.                           clist )),
  387.              `HWeight(20,
  388.                   // button text
  389.                  `VBox( `PushButton( `id(`moveup), _("Move &Up") ),
  390.                         `PushButton( `id(`movedown),
  391.                          // button text
  392.                                  _("Move &Down")))))/*)*/,
  393.         `VSpacing(1),
  394.         /*`Left(*/`HBox( `HWeight(40, `ReplacePoint(`id(`rep_mod),
  395.                                                 ModComboBox( [] ) )),
  396.              `HWeight(60, `HStretch()) )/*)*/,
  397.         `VSpacing(1),
  398.         // provides Yes or No status info
  399.         `Left(`HBox( `Label( _("Module Currently Loaded:")), `HSpacing(2),
  400.              // label text
  401.                  `Label( `id(`loaded), _("Yes") ))),
  402.         `VSpacing(1),
  403.         `Left(`ReplacePoint(`id(`rep_initrd), InitCheckBox( false ))),
  404.         `VSpacing(1),
  405.         // label text
  406.         `Left(`TextEntry( `id(`param), _("Module &Parameters"), "abdef" )),
  407.         `VSpacing(1),
  408.         // button text
  409.         `PushButton( `id(`test_load), _("&Test Loading of Module")),
  410.         `VSpacing(2)
  411.         );
  412.  
  413.     if( size(dev)==0 )
  414.     {
  415.     // popup text
  416.     Popup::Message( _("No configurable controller found.") );
  417.     UI::CloseDialog();
  418.     return `cancel;
  419.     }
  420.     // heading text
  421.     Wizard::SetContents( _("Disk Controller Configuration"), dialog,
  422.              HelpText(), true, true );
  423.     Wizard::SetDesktopIcon("controller");
  424.  
  425.     UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
  426.     string modname = SetAllData( dev[cur_num]:$[], clist, cur_num );
  427.     symbol ret = `none;
  428.     boolean changed = false;
  429.     y2milestone( "cur %1", dev[cur_num]:$[] );
  430.     do
  431.     {
  432.     ret = (symbol)UI::UserInput();
  433.     y2milestone( "ret = %1", ret );
  434.     if( ret == `controller )
  435.         {
  436.         num = (integer)UI::QueryWidget( `id(`controller), `CurrentItem );
  437.         y2milestone( "num %1 cur_num %2", num, cur_num );
  438.         if( num != cur_num )
  439.         {
  440.         cur_num = num;
  441.         params[modname] = (string)UI::QueryWidget( `id(`param), `Value );
  442.         modname = SetAllData( dev[cur_num]:$[], clist, cur_num );
  443.         y2milestone( "controller module=%1", modname );
  444.         }
  445.         }
  446.     else if( ret == `moveup )
  447.         {
  448.         changed = true;
  449.         integer rpos = GetArrayPos( clist, cur_num );
  450.         term tmp = clist[rpos]:`Empty();
  451.         clist[rpos] = clist[rpos-1]:`Empty();
  452.         clist[rpos-1] = tmp;
  453.         UI::ChangeWidget( `id(`controller), `Items, clist );
  454.         UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
  455.         rpos = GetArrayPos( clist, cur_num );
  456.         y2milestone( "items=%1", clist );
  457.         y2milestone( "rpos=%1", rpos );
  458.         UI::ChangeWidget( `id(`moveup), `Enabled, rpos>0 );
  459.         UI::ChangeWidget( `id(`movedown), `Enabled, rpos<size(clist)-1 );
  460.         }
  461.     else if( ret == `movedown )
  462.         {
  463.         changed = true;
  464.         integer rpos = GetArrayPos( clist, cur_num );
  465.         term tmp = clist[rpos]:`Empty();
  466.         clist[rpos] = clist[rpos+1]:`Empty();
  467.         clist[rpos+1] = tmp;
  468.         UI::ChangeWidget( `id(`controller), `Items, clist );
  469.         UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
  470.         rpos = GetArrayPos( clist, cur_num );
  471.         y2milestone( "items=%1", clist );
  472.         y2milestone( "rpos=%1", rpos );
  473.         UI::ChangeWidget( `id(`moveup), `Enabled, rpos>0 );
  474.         UI::ChangeWidget( `id(`movedown), `Enabled, rpos<size(clist)-1 );
  475.         }
  476.     else if( ret == `module )
  477.         {
  478.         num = (integer)UI::QueryWidget( `id(`module), `Value );
  479.         if( num != dev[cur_num,"modnum"]:0 )
  480.         {
  481.         changed = true;
  482.         modname = ModNameChanged( dev[cur_num]:$[], num,
  483.                                   dev[cur_num,"modnum"]:0 );
  484.         dev[cur_num,"modnum"] = num;
  485.         y2milestone( "module=%1", modname );
  486.         }
  487.         }
  488.     else if( ret == `initrd )
  489.         {
  490.         changed = true;
  491.         if( contains( initrd, modname ))
  492.         {
  493.         initrd = filter( string e, initrd, ``(e!=modname));
  494.         }
  495.         else
  496.         {
  497.         initrd = add( initrd, modname );
  498.         }
  499.         y2milestone( "initrd=%1", initrd );
  500.         }
  501.     else if( ret == `test_load )
  502.         {
  503.         TestLoad( dev[cur_num]:$[] );
  504.         SetLoaded( modname );
  505.         }
  506.     else if( ret == `apply || ret == `finish )
  507.         {
  508.         boolean mkinitrd = false;
  509.         params[modname] = (string)UI::QueryWidget( `id(`param), `Value );
  510.         list<string> new_initrd = [];
  511.         list<string> tlist = initrd;
  512.         foreach( term e, clist,
  513.         ``{
  514.         integer num = TermToId( e );
  515.         integer modnum = dev[num,"modnum"]:0;
  516.         if( contains( tlist,
  517.                       dev[num,"drivers",modnum,"modules",0,0]:"" ) )
  518.             {
  519.             foreach( list d, dev[num,"drivers",modnum,"modules"]:[],
  520.             ``{
  521.             new_initrd = add( new_initrd, d[0]:"" );
  522.             tlist = filter( string e, tlist, ``(e!=d[0]:""));
  523.             if( size(params[d[0]:""]:"")==0 )
  524.                 params[d[0]:""] = d[1]:"";
  525.             mkinitrd = mkinitrd || size(d[1]:"")>0;
  526.             });
  527.             }
  528.         });
  529.         foreach( string e, initrd,
  530.         ``{
  531.         if( size(e)>0 && !contains( new_initrd, e ) )
  532.             new_initrd = add( new_initrd, e );
  533.         });
  534.         string newinit = mergestring( new_initrd, " " );
  535.         string old = Misc::SysconfigRead( .sysconfig.kernel.INITRD_MODULES,
  536.                                           "");
  537.         mkinitrd = mkinitrd || newinit != old;
  538.         if( newinit != old )
  539.         {
  540.         initrd = new_initrd;
  541.         initrd_orig = new_initrd;
  542.         }
  543.         y2milestone( "make %3 new \"%1\" old \"%2\"", newinit, old,
  544.                      mkinitrd );
  545.         y2milestone( "params %1", params );
  546.         boolean writemod = false;
  547.         foreach( string k, string v, params,
  548.         ``{
  549.         y2milestone( "par orig:\"%1\" new:\"%2\"", params_orig[k]:"",
  550.                      v );
  551.         if( params_orig[k]:"" != v )
  552.             {
  553.             mkinitrd = mkinitrd || contains(new_initrd,k);
  554.             params_orig[k] = v;
  555.             list<string> val = filter( string e, splitstring( v, " " ),
  556.                            ``(size(e)>0) );
  557.             y2milestone( "val %1", val );
  558.             map opts = $[];
  559.             foreach( string v, val,
  560.             ``{
  561.             integer pos = search( v, "=" );
  562.             y2milestone( "v %1 pos %2", v, pos );
  563.             if( pos==nil )
  564.                 {
  565.                 opts[v] = "";
  566.                 }
  567.             else
  568.                 {
  569.                 opts[substring(v,0,pos)] = substring( v, pos+1 );
  570.                 }
  571.             });
  572.             writemod = true;
  573.             y2milestone( "k %1 opts:%2 write:%3", k, opts, writemod );
  574.             SCR::Write( .modules.options, opts, k );
  575.             }
  576.         });
  577.         if( writemod )
  578.         {
  579.         y2milestone( "writing modules.conf" );
  580.         SCR::Write( .modules, nil );
  581.         y2milestone( "finished writing modules.conf" );
  582.         }
  583.         y2milestone( "mkinitrd %1", mkinitrd );
  584.         if( mkinitrd )
  585.         {
  586.         SCR::Write( .sysconfig.kernel.INITRD_MODULES, newinit );
  587.         SCR::Write( .sysconfig.kernel, nil );
  588.         // busy popup text, mkinitrd is a case-sensitive command
  589.         UI::OpenDialog( `Label( _("Executing mkinitrd...") ) );
  590.         integer exit = (integer)SCR::Execute( .target.bash, "/sbin/mkinitrd" );
  591.         UI::CloseDialog();
  592.         y2milestone( "exit /sbin/mkinitrd %1", exit );
  593.         if( exit != 0 )
  594.             {
  595.             // popup text
  596.             Popup::Error( _("Executing mkinitrd failed.
  597. Try executing /sbin/mkinitrd manually and checking the error messages.
  598. "));
  599.             }
  600.         else
  601.             {
  602.             map boot_write = $[];
  603.             boot_write["insert_initrd"] = true;
  604.             boot_write["initrd_changed_externally"] = true;
  605.             boot_write["forbid_save_initrd"] = true;
  606.             /* FIXME
  607.             Bootloader::SetWriteMode( boot_write );
  608.             Bootloader::Write();
  609.             */
  610.             }
  611.         }
  612.         foreach( map entry, dev,
  613.         ``{
  614.         if( size(entry["unique_key"]:"")>0 )
  615.             SCR::Write(.probe.status.configured, entry["unique_key"]:"",
  616.                    `yes);
  617.         });
  618.         changed = false;
  619.         SCR::Execute( .target.bash, "/sbin/hwscan --disk" );
  620.         SCR::Execute( .target.bash, "/sbin/hwscan --cdrom" );
  621.         }
  622.     else if( ret == `abort || ret == `cancel )
  623.         {
  624.         if( changed )
  625.         {
  626.         // popup text
  627.         if( !Popup::YesNo( _("You have unsaved changes.
  628. These changes will be lost if you abort now.
  629.  
  630. Discard changes?
  631. ")))
  632.             {
  633.             ret = `continue;
  634.             }
  635.         }
  636.         }
  637.     }
  638.     while( ret != `abort && ret!=`cancel && ret != `finish );
  639.     UI::CloseDialog();
  640.  
  641. }
  642.