24044bec62c0468570c9a56d7aca4150a19c0279
[duncan/yast2-qt4.git] / src / YQWizard.cc
1 /*---------------------------------------------------------------------\
2 |                                                                      |
3 |                      __   __    ____ _____ ____                      |
4 |                      \ \ / /_ _/ ___|_   _|___ \                     |
5 |                       \ V / _` \___ \ | |   __) |                    |
6 |                        | | (_| |___) || |  / __/                     |
7 |                        |_|\__,_|____/ |_| |_____|                    |
8 |                                                                      |
9 |                               core system                            |
10 |                                                    (c) SuSE Linux AG |
11 \----------------------------------------------------------------------/
12
13   File:         YQWizard.cc
14
15   Author:       Stefan Hundhammer <sh@suse.de>
16
17   Textdomain    "packages-qt"
18
19 /-*/
20
21
22 #include "YQWizard.h"
23 #define y2log_component "qt-wizard"
24 #include <ycp/y2log.h>
25 #include "YCPValueWidgetID.h"
26
27 // For the command parser
28
29 #include <string>
30
31 #include <ycp/YCPBoolean.h>
32 #include <ycp/YCPCode.h>
33 #include <ycp/YCPInteger.h>
34 #include "ycp/YCPInteger.h"
35 #include <ycp/YCPList.h>
36 #include <ycp/YCPMap.h>
37 #include <ycp/YCPString.h>
38 #include <ycp/YCPSymbol.h>
39 #include <ycp/YCPTerm.h>
40 #include <ycp/YCPValue.h>
41 #include <ycp/YCPVoid.h>
42 #include <YShortcut.h>
43
44 #include <q3hbox.h>
45 #include <q3header.h>
46 #include <qimage.h>
47 #include <qlabel.h>
48 #include <qlayout.h>
49 #include <qmenubar.h>
50 #include <qmenudata.h>
51 #include <qobject.h>
52 #include <qpixmap.h>
53 #include <q3popupmenu.h>
54 #include <qpushbutton.h>
55 #include <qregexp.h>
56 #include <qtabwidget.h>
57 #include <q3textbrowser.h>
58 #include <qtoolbutton.h>
59 #include <q3widgetstack.h>
60
61 #include "QY2ListView.h"
62 //Added by qt3to4:
63 #include <q3boxlayout.h>
64 #include <q3gridlayout.h>
65 #include <q3frame.h>
66 #include <qevent.h>
67 #include <q3boxlayout.h>
68
69 #include "utf8.h"
70 #include "YQi18n.h"
71 #include "YQUI.h"
72 #include "YQApplication.h"
73 #include "YQDialog.h"
74 #include "YQAlignment.h"
75 #include "YQReplacePoint.h"
76 #include "YQEmpty.h"
77 #include "YQLabel.h"
78 #include "YQWizardButton.h"
79 #include "YQIconPool.h"
80 #include "YQWidgetFactory.h"
81 #include "YQSignalBlocker.h"
82 #include "QY2LayoutUtils.h"
83 #include "YEvent.h"
84
85 using std::string;
86
87
88 #define PIXMAP_DIR THEMEDIR "/wizard/"
89
90 #ifdef TEXTDOMAIN
91 #    undef TEXTDOMAIN
92 #endif
93
94 #define TEXTDOMAIN "packages-qt"
95
96 #define ENABLE_GRADIENTS                1
97
98 #define USE_SEPARATOR                   1
99
100 #define WORK_AREA_TOP_MARGIN            10
101
102 #if ENABLE_GRADIENTS
103 #  define WORK_AREA_BOTTOM_MARGIN       8
104 #  define WORK_AREA_RIGHT_MARGIN        8
105 #else
106 #  define WORK_AREA_BOTTOM_MARGIN       8
107 #  define WORK_AREA_RIGHT_MARGIN        6
108 #endif
109
110 #define BUTTON_BOX_TOP_MARGIN           10
111
112 #define SEPARATOR_MARGIN                6
113 #define STEPS_MARGIN                    10
114 #define STEPS_SPACING                   2
115 #define STEPS_HEADING_SPACING           8
116 #define MENU_BAR_MARGIN                 8
117
118 #define USE_FIXED_STEP_FONTS            0
119 #define STEPS_FONT_FAMILY               "Sans Serif"
120 #define STEPS_FONT_SIZE                 11
121 #define STEPS_HEADING_FONT_SIZE         11
122
123 #define USE_ICON_ON_HELP_BUTTON         0
124
125
126 YQWizard::YQWizard( YWidget *   parent,
127                     YWidgetID * backButtonId,   const string & backButtonLabel,
128                     YWidgetID * abortButtonId,  const string & abortButtonLabel,
129                     YWidgetID * nextButtonId,   const string & nextButtonLabel,
130                     YWizardMode wizardMode )
131     : QFrame( (QWidget *) parent->widgetRep() )
132     , YWizard( parent,
133                backButtonId,    backButtonLabel,
134                abortButtonId,   abortButtonLabel,
135                nextButtonId,    nextButtonLabel,
136                wizardMode )
137 {
138     QVBoxLayout* layout = new QVBoxLayout( this );
139     layout->setSpacing( 0 );
140     layout->setMargin( 0 );
141     setLayout( layout );
142
143     setWidgetRep( this );
144
145     _stepsEnabled = (wizardMode == YWizardMode_Steps);
146     _treeEnabled  = (wizardMode == YWizardMode_Tree);
147
148     _verboseCommands    = false;
149     _protectNextButton  = false;
150     _stepsDirty         = false;
151     _direction          = YQWizard::Forward;
152
153     _sideBar            = 0;
154     _stepsPanel         = 0;
155     _stepsBox           = 0;
156     _stepsGrid          = 0;
157     _helpButton         = 0;
158     _stepsButton        = 0;
159     _treeButton         = 0;
160     _releaseNotesButton = 0;
161     _treePanel          = 0;
162     _tree               = 0;
163     _helpPanel          = 0;
164     _helpBrowser        = 0;
165     _clientArea         = 0;
166     _menuBarBox         = 0;
167     _menuBar            = 0;
168     _dialogIcon         = 0;
169     _dialogHeading      = 0;
170     _contents           = 0;
171     _backButton         = 0;
172     _backButtonSpacer   = 0;
173     _abortButton        = 0;
174     _nextButton         = 0;
175     _sendButtonEvents   = true;
176
177     _stepsList.setAutoDelete( true );
178     _stepsIDs.setAutoDelete( false );   // Only for one of both!
179
180     YQUI::setTextdomain( TEXTDOMAIN );
181
182
183     //
184     // Load graphics
185     //
186
187 #if ENABLE_GRADIENTS
188     loadGradientPixmaps();
189 #endif
190
191     if ( _stepsEnabled )
192         loadStepsIcons();
193
194
195     //
196     // Create widgets
197     //
198
199
200     if ( ! _titleBarGradientPixmap.isNull() )
201     {
202         layoutTitleBar( this );
203     }
204     else
205     {
206         QWidget * spacer = addVSpacing( this, WORK_AREA_TOP_MARGIN );
207         Q_CHECK_PTR( spacer );
208
209 #       if ENABLE_GRADIENTS
210             spacer->setPaletteBackgroundColor( _gradientTopColor );
211 #       endif
212     }
213
214     Q3HBox * hBox = new Q3HBox( this );
215     YUI_CHECK_NEW( hBox );
216     layout->addWidget( hBox );
217
218     layoutSideBar( hBox );
219     layoutWorkArea( hBox );
220
221     y2debug( "Constructor finished." );
222 }
223
224
225
226 YQWizard::~YQWizard()
227 {
228     deleteSteps();
229 }
230
231
232
233 void YQWizard::layoutTitleBar( QWidget * parent )
234 {
235     if ( ! highColorDisplay() )         // 8 bit display or worse?
236     {
237         // No colorful title bar, just a spacing to match the one at the bottom.
238         addVSpacing( parent, WORK_AREA_BOTTOM_MARGIN );
239
240         return;
241     }
242
243
244     Q3HBox * titleBar = new Q3HBox( parent );
245     YUI_CHECK_NEW( titleBar );
246
247 #if ENABLE_GRADIENTS
248     setGradient( titleBar, _titleBarGradientPixmap );
249 #endif
250
251     titleBar->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) ); // hor/vert
252
253     //
254     // Left logo
255     //
256
257     QLabel * left = new QLabel( titleBar );
258     left->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); // hor/vert
259
260     QPixmap leftLogo( PIXMAP_DIR "title-bar-left.png" );
261
262     if ( ! leftLogo.isNull() )
263     {
264         left->setPixmap( leftLogo );
265         left->setFixedSize( leftLogo.size() );
266         left->setBackgroundOrigin( QWidget::ParentOrigin );
267     }
268
269
270     //
271     // Center stretch space
272     //
273
274     addHStretch( titleBar );
275
276
277     //
278     // Right logo
279     //
280
281     QLabel * right = new QLabel( titleBar );
282     YUI_CHECK_NEW( right );
283
284     QPixmap rightLogo( PIXMAP_DIR "title-bar-right.png" );
285
286     if ( ! rightLogo.isNull() )
287     {
288         right->setPixmap( rightLogo );
289         right->setFixedSize( rightLogo.size() );
290         right->setBackgroundOrigin( QWidget::ParentOrigin );
291     }
292 }
293
294
295
296 void YQWizard::layoutSideBar( QWidget * parent )
297 {
298     _sideBar = new Q3WidgetStack( parent );
299     YUI_CHECK_NEW( _sideBar );
300     _sideBar->setMinimumWidth( YQUI::ui()->defaultSize( YD_HORIZ ) / 5 );
301     _sideBar->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ) ); // hor/vert
302     _sideBar->setMargin( 0 );
303
304
305     layoutHelpPanel();
306
307     if ( _treeEnabled )
308     {
309         layoutTreePanel();
310         showTree();
311     }
312     else if ( _stepsEnabled )
313     {
314         layoutStepsPanel();
315         showSteps();
316     }
317 }
318
319
320
321 void YQWizard::layoutStepsPanel()
322 {
323     _stepsPanel = new Q3VBox( _sideBar );
324     YUI_CHECK_NEW( _stepsPanel );
325
326
327 #if ENABLE_GRADIENTS
328     if ( ! _titleBarGradientPixmap.isNull() )
329     {
330         // Top gradient
331
332         QLabel * topGradient = new QLabel( _stepsPanel );
333         Q_CHECK_PTR( topGradient );
334         setGradient( topGradient, _topGradientPixmap );
335     }
336 #endif
337
338
339     // Steps
340
341     _sideBar->addWidget( _stepsPanel );
342
343     _stepsBox = new Q3VBox( _stepsPanel );
344     YUI_CHECK_NEW( _stepsBox );
345 #if ENABLE_GRADIENTS
346     _stepsBox->setPaletteBackgroundColor( _gradientCenterColor );
347 #endif
348     _stepsBox->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ) ); // hor/vert
349
350     QWidget * stretch = addVStretch( _stepsPanel );
351     YUI_CHECK_NEW( stretch );
352 #if ENABLE_GRADIENTS
353     stretch->setPaletteBackgroundColor( _gradientCenterColor );
354 #endif
355
356
357     // Steps panel bottom buttons ("Help", "Release Notes")
358
359     QLabel * helpButtonBox = new QLabel( _stepsPanel );
360
361 #if ENABLE_GRADIENTS
362     YUI_CHECK_NEW( helpButtonBox );
363     setGradient( helpButtonBox, _bottomGradientPixmap );
364 #endif
365
366
367
368     // Layouts for the buttons
369
370     QVBoxLayout * vbox = new QVBoxLayout( helpButtonBox, 0, 0 ); // parent, margin, spacing
371     YUI_CHECK_NEW( vbox );
372     vbox->addStretch( 99 );
373
374
375     QHBoxLayout * hbox = new QHBoxLayout( vbox, 0 );    // parent, spacing
376     hbox->addStretch( 99 );
377
378     _releaseNotesButton = new QPushButton( _( "Release Notes..." ), helpButtonBox );
379     hbox->addWidget( _releaseNotesButton );
380
381
382     connect( _releaseNotesButton,       SIGNAL( clicked()  ),
383              this,                      SLOT  ( releaseNotesClicked() ) );
384
385     _releaseNotesButton->hide();        // hidden until showReleaseNotesButton() is called
386
387     hbox->addStretch( 99 );
388     vbox->addStretch( 99 );
389
390     hbox = new QHBoxLayout( vbox, 0 );  // parent, spacing
391     hbox->addStretch( 99 );
392
393     // Help button - intentionally without keyboard shortcut
394     _helpButton = new QPushButton( _( "Help" ), helpButtonBox );
395     YUI_CHECK_NEW( _helpButton );
396
397     hbox->addWidget( _helpButton );
398     hbox->addStretch( 99 );
399
400     connect( _helpButton, SIGNAL( clicked()  ),
401              this,       SLOT  ( showHelp() ) );
402
403 #if USE_ICON_ON_HELP_BUTTON
404     QPixmap pixmap = QPixmap( PIXMAP_DIR "help-button.png" );
405
406     if ( ! pixmap.isNull() )
407         _helpButton->setPixmap( pixmap );
408 #endif
409
410
411     vbox->addSpacing( WORK_AREA_BOTTOM_MARGIN );
412 }
413
414
415
416 void YQWizard::addStep( const QString & text, const QString & id )
417 {
418     if ( _stepsIDs[ id ] )
419     {
420         y2error( "Step ID \"%s\" (\"%s\") already used for \"%s\"",
421                  (const char *) id,
422                  (const char *) text,
423                  (const char *) _stepsIDs[ id ]->name() );
424         return;
425     }
426
427     if ( _stepsList.last() && _stepsList.last()->name() == text )
428     {
429         // Consecutive steps with the same name will be shown as one single step.
430         //
431         // Since steps are always added at the end of the list, it is
432         // sufficient to check the last step of the list. If the texts are the
433         // same, the other with the same text needs to get another (additional)
434         // ID to make sure setCurrentStep() works as it should.
435         _stepsList.last()->addID( id );
436     }
437     else
438     {
439         _stepsList.append( new YQWizard::Step( text, id ) );
440         _stepsDirty = true;
441     }
442
443     _stepsIDs.insert( id, _stepsList.last() );
444 }
445
446
447
448 void YQWizard::addStepHeading( const QString & text )
449 {
450     _stepsList.append( new YQWizard::StepHeading( text ) );
451     _stepsDirty = true;
452 }
453
454
455
456 void YQWizard::updateSteps()
457 {
458     if ( ! _stepsBox )
459         return;
460
461     //
462     // Delete any previous step widgets
463     //
464
465     if ( _stepsGrid )
466     {
467         delete _stepsGrid->mainWidget();
468         _stepsGrid = 0;
469     }
470
471
472     //
473     // Create a new parent widget for the steps
474     //
475
476     QWidget * stepsParent = new QWidget( _stepsBox );
477     YUI_CHECK_NEW( stepsParent );
478     stepsParent->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ) ); // hor/vert
479 #if ENABLE_GRADIENTS
480     stepsParent->setPaletteBackgroundColor( _gradientCenterColor );
481 #endif
482
483     // Create a grid layout for the steps
484
485     _stepsGrid = new Q3GridLayout( stepsParent,                 // parent
486                                   _stepsList.count(), 4,        // rows, cols
487                                   0, STEPS_SPACING );           // margin, spacing
488     YUI_CHECK_NEW( _stepsGrid );
489
490     const int statusCol = 1;
491     const int nameCol   = 2;
492
493     _stepsGrid->setColStretch( 0, 99 );         // Left margin column - stretch
494     _stepsGrid->setColStretch( statusCol, 0 );  // Status column - don't stretch
495     _stepsGrid->setColStretch( nameCol,   0 );  // Name column - don't stretch
496     _stepsGrid->setColStretch( 3, 99  );        // Left margin column - stretch
497
498
499     // Work around Qt bug: Grid layout only works right if the parent widget isn't visible.
500     stepsParent->hide();
501
502     //
503     // Add left and right (but not top and bottom) margins
504     //
505
506     int row = 0;
507
508     QWidget * leftSpacer  = addHSpacing( stepsParent, STEPS_MARGIN );
509     YUI_CHECK_NEW( leftSpacer );
510     _stepsGrid->addWidget( leftSpacer, row, 0 );
511
512     QWidget * rightSpacer = addHSpacing( stepsParent, STEPS_MARGIN );
513     YUI_CHECK_NEW( rightSpacer );
514     _stepsGrid->addWidget( rightSpacer, row, 3 );
515
516
517     //
518     // Create widgets for all steps and step headings in the internal list
519     //
520
521     YQWizard::Step * step = _stepsList.first();
522
523     while ( step )
524     {
525         if ( step->isHeading() )
526         {
527             if ( row > 0 )
528             {
529                 // Spacing
530
531                 QWidget * spacer = addVSpacing( stepsParent, STEPS_HEADING_SPACING );
532                 YUI_CHECK_NEW( spacer );
533                 _stepsGrid->addWidget( spacer, row++, nameCol );
534             }
535
536             //
537             // Heading
538             //
539
540             QLabel * label = new QLabel( step->name(), stepsParent );
541             YUI_CHECK_NEW( label );
542             label->setAlignment( Qt::AlignLeft | Qt::AlignTop );
543
544 #if USE_FIXED_STEP_FONTS
545             QFont font( STEPS_FONT_FAMILY, STEPS_HEADING_FONT_SIZE );
546             font.setWeight( QFont::Bold );
547             label->setFont( font );
548 #else
549             QFont font = YQUI::yqApp()->currentFont();
550
551             int size = font.pointSize();
552
553             if ( size > 1 )
554                 font.setPointSize( size + 2 );
555
556             font.setBold( true );
557             label->setFont( font );
558 #endif
559
560             step->setNameLabel( label );
561             _stepsGrid->addMultiCellWidget( label,
562                                             row, row,                   // from_row, to_row
563                                             statusCol, nameCol );       // from_col, to_col
564         }
565         else    // No heading - ordinary step
566         {
567             //
568             // Step status
569             //
570
571             QLabel * statusLabel = new QLabel( stepsParent );
572             YUI_CHECK_NEW( statusLabel );
573
574             step->setStatusLabel( statusLabel );
575             _stepsGrid->addWidget( statusLabel, row, statusCol );
576
577
578             //
579             // Step name
580             //
581
582             QLabel * nameLabel = new QLabel( step->name(), stepsParent );
583             YUI_CHECK_NEW( nameLabel );
584             nameLabel->setAlignment( Qt::AlignLeft | Qt::AlignTop );
585
586 #if USE_FIXED_STEP_FONTS
587             nameLabel->setFont( QFont( STEPS_FONT_FAMILY, STEPS_FONT_SIZE ) );
588 #else
589             nameLabel->setFont( YQUI::yqApp()->currentFont() );
590 #endif
591
592             step->setNameLabel( nameLabel );
593             _stepsGrid->addWidget( nameLabel, row, nameCol );
594         }
595
596         step = _stepsList.next();
597         row++;
598     }
599
600     _stepsGrid->activate();
601     stepsParent->show();
602     _stepsDirty = false;
603 }
604
605
606 void YQWizard::updateStepStates()
607 {
608     if ( _stepsDirty )
609         updateSteps();
610
611     YQWizard::Step * currentStep = findStep( _currentStepID );
612     YQWizard::Step * step = _stepsList.first();
613
614     if ( currentStep )
615     {
616         // Set status icon and color for the current step
617         setStepStatus( currentStep, _stepCurrentIcon, _stepCurrentColor );
618
619
620         //
621         // Set all steps before the current to "done"
622         //
623
624         while ( step && step != currentStep )
625         {
626             setStepStatus( step, _stepDoneIcon, _stepDoneColor );
627             step = _stepsList.next();
628         }
629
630         // Skip the current step - continue with the step after it
631
632         if ( step )
633             step = _stepsList.next();
634     }
635
636     //
637     // Set all steps after the current to "to do"
638     //
639
640     while ( step )
641     {
642         setStepStatus( step, _stepToDoIcon, _stepToDoColor );
643         step = _stepsList.next();
644     }
645 }
646
647
648 void YQWizard::setStepStatus( YQWizard::Step * step, const QPixmap & icon, const QColor & color )
649 {
650     if ( step )
651     {
652         if ( step->nameLabel() )
653             step->nameLabel()->setPaletteForegroundColor( color );
654
655         if ( step->statusLabel() )
656             step->statusLabel()->setPixmap( icon );
657     }
658 }
659
660
661 void YQWizard::setCurrentStep( const QString & id )
662 {
663     _currentStepID = id;
664     updateStepStates();
665 }
666
667
668 void YQWizard::deleteSteps()
669 {
670     _stepsList.clear();
671     _stepsIDs.clear();
672 }
673
674
675 YQWizard::Step * YQWizard::findStep( const QString & id )
676 {
677     if ( id.isEmpty() )
678         return 0;
679
680     return _stepsIDs[ id ];
681 }
682
683
684 void YQWizard::layoutHelpPanel()
685 {
686     _helpPanel = new Q3HBox( _sideBar );
687     YUI_CHECK_NEW( _helpPanel );
688     _sideBar->addWidget( _helpPanel );
689
690     addGradientColumn( _helpPanel );
691
692     Q3VBox * vbox = new Q3VBox( _helpPanel );
693     YUI_CHECK_NEW( vbox );
694
695
696     // Help browser
697
698     _helpBrowser = new Q3TextBrowser( vbox );
699     YUI_CHECK_NEW( _helpBrowser );
700
701     _helpBrowser->setMimeSourceFactory( 0 );
702     _helpBrowser->installEventFilter( this );
703     _helpBrowser->setTextFormat( Qt::RichText );
704     _helpBrowser->setMargin( 4 );
705     _helpBrowser->setResizePolicy( Q3ScrollView::Manual );
706
707
708     // Set help browser text color
709     QPixmap fgPixmap = QPixmap( PIXMAP_DIR "help-text-color.png" );
710     if (! fgPixmap.isNull() )
711         _helpBrowser->setPaletteForegroundColor( pixelColor( fgPixmap, 0, 0, paletteForegroundColor() ) );
712
713
714     if ( highColorDisplay() )
715     {
716         // Set fancy help browser background pixmap
717
718         QPixmap bgPixmap( PIXMAP_DIR "help-background.png" );
719
720         if ( ! bgPixmap.isNull() )
721             _helpBrowser->setPaletteBackgroundPixmap( bgPixmap );
722     }
723
724
725
726     //
727     // Button box with bottom gradient
728     //
729
730
731     QLabel * buttonBox = new QLabel( vbox );
732     YUI_CHECK_NEW( buttonBox );
733
734     QPushButton * button;
735     QPixmap pixmap;
736
737     if ( _treeEnabled )
738     {
739         // "Tree" button - intentionally without keyboard shortcut
740         button = new QPushButton( _( "Tree" ), buttonBox );
741         YUI_CHECK_NEW( button );
742         _treeButton = button;
743
744 #if USE_ICON_ON_HELP_BUTTON
745         pixmap = QPixmap( PIXMAP_DIR "tree-button.png" );
746 #endif
747     }
748     else
749         if ( _stepsEnabled )
750     {
751         // "Steps" button - intentionally without keyboard shortcut
752         button = new QPushButton( _( "Steps" ), buttonBox );
753         YUI_CHECK_NEW( button );
754         _stepsButton = button;
755
756 #if USE_ICON_ON_HELP_BUTTON
757         pixmap = QPixmap( PIXMAP_DIR "steps-button.png" );
758 #endif
759     }
760     else
761     {
762         // Create a dummy button just to find out how high it would become
763         button = new QPushButton( "Dummy", buttonBox );
764         YUI_CHECK_NEW( button );
765     }
766
767
768     if ( ! pixmap.isNull() )
769         button->setPixmap( pixmap );
770
771     layoutSideBarButtonBox( buttonBox, button );
772
773     if ( _treeEnabled )
774     {
775         connect( button, SIGNAL( clicked()  ),
776                  this,   SLOT  ( showTree() ) );
777     }
778     else if ( _stepsEnabled )
779     {
780         connect( button, SIGNAL( clicked()   ),
781                  this,   SLOT  ( showSteps() ) );
782     }
783     else
784     {
785         // Hide the dummy button - the button box height is fixed now.
786         button->hide();
787     }
788
789     addGradientColumn( _helpPanel );
790 }
791
792
793
794 void YQWizard::layoutSideBarButtonBox( QWidget * parent, QPushButton * button )
795 {
796     QVBoxLayout * vbox = new QVBoxLayout( parent, 0, 0 );       // parent, margin, spacing
797     YUI_CHECK_NEW( vbox );
798     vbox->addSpacing( BUTTON_BOX_TOP_MARGIN );
799
800     QHBoxLayout * hbox = new QHBoxLayout( vbox, 0 );            // parent, spacing
801     YUI_CHECK_NEW( hbox );
802
803     hbox->addStretch( 99 );
804     hbox->addWidget( button );
805     hbox->addStretch( 99 );
806
807     vbox->addSpacing( WORK_AREA_BOTTOM_MARGIN );
808
809     // For whatever strange reason, parent->sizeHint() does not return anything
810     // meaningful yet - not even after vbox->activate() or parent->adjustSize()
811     int height = button->sizeHint().height() + BUTTON_BOX_TOP_MARGIN + WORK_AREA_BOTTOM_MARGIN;
812
813 #if ENABLE_GRADIENTS
814     if ( ! _bottomGradientPixmap.isNull() )
815         setBottomCroppedGradient( parent, _bottomGradientPixmap, height );
816 #endif
817
818     parent->setFixedHeight( height );
819 }
820
821
822
823 void YQWizard::layoutTreePanel()
824 {
825     _treePanel = new Q3HBox( _sideBar );
826     YUI_CHECK_NEW( _treePanel );
827     _sideBar->addWidget( _treePanel );
828
829     // Left margin (with gradients)
830     addGradientColumn( _treePanel );
831
832     Q3VBox * vbox = new Q3VBox( _treePanel );
833     YUI_CHECK_NEW( vbox );
834
835
836     // Selection tree
837
838     _tree = new QY2ListView( vbox );
839     YUI_CHECK_NEW( _tree );
840     
841     _tree->addColumn( "" );
842     _tree->header()->hide();
843     _tree->setRootIsDecorated( true );
844     _tree->setSortByInsertionSequence( true );
845
846     connect( _tree,     SIGNAL( selectionChanged     ( void ) ),
847              this,      SLOT  ( treeSelectionChanged ( void ) ) );
848
849     connect( _tree,     SIGNAL( spacePressed  ( Q3ListViewItem * ) ),
850              this,      SLOT  ( sendTreeEvent ( Q3ListViewItem * ) ) );
851
852     connect( _tree,     SIGNAL( doubleClicked ( Q3ListViewItem * ) ),
853              this,      SLOT  ( sendTreeEvent ( Q3ListViewItem * ) ) );
854
855
856     // Bottom gradient
857
858     QLabel * buttonBox = new QLabel( vbox );
859     YUI_CHECK_NEW( buttonBox );
860
861
862     // "Help" button - intentionally without keyboard shortcut
863     QPushButton * button = new QPushButton( _( "Help" ), buttonBox );
864     YUI_CHECK_NEW( button );
865
866 #if USE_ICON_ON_HELP_BUTTON
867     QPixmap pixmap( PIXMAP_DIR "help-button.png" );
868
869     if ( ! pixmap.isNull() )
870         button->setPixmap( pixmap );
871 #endif
872
873     layoutSideBarButtonBox( buttonBox, button );
874
875     connect( button, SIGNAL( clicked()  ),
876              this,   SLOT  ( showHelp() ) );
877
878
879     // Right margin (with gradients)
880     addGradientColumn( _treePanel );
881 }
882
883
884
885 void YQWizard::addTreeItem( const QString & parentID, const QString & text, const QString & id )
886 {
887     if ( ! _tree )
888     {
889         y2error( "YQWizard widget not created with `opt(`treeEnabled) !" );
890         return;
891     }
892
893     YQWizard::TreeItem * item   = 0;
894     YQWizard::TreeItem * parent = 0;
895
896     if ( ! parentID.isEmpty() )
897     {
898         parent = findTreeItem( parentID );
899     }
900
901     if ( parent )
902     {
903         item = new YQWizard::TreeItem( parent, text, id );
904         YUI_CHECK_NEW( item );
905     }
906     else
907     {
908         item = new YQWizard::TreeItem( _tree, text, id );
909         YUI_CHECK_NEW( item );
910     }
911
912     if ( ! id.isEmpty() )
913         _treeIDs.insert( id, item );
914 }
915
916
917
918 void YQWizard::deleteTreeItems()
919 {
920     if ( _tree )
921         _tree->clear();
922
923     _treeIDs.clear();
924 }
925
926
927
928 YQWizard::TreeItem * YQWizard::findTreeItem( const QString & id )
929 {
930     if ( id.isEmpty() )
931         return 0;
932
933     return _treeIDs[ id ];
934 }
935
936
937 void YQWizard::selectTreeItem( const QString & id )
938 {
939     if ( _tree )
940     {
941         YQWizard::TreeItem * item = findTreeItem( id );
942
943         if ( item )
944         {
945             YQSignalBlocker sigBlocker( _tree );
946
947             _tree->setSelected( item, true );
948             _tree->ensureItemVisible( item );
949         }
950     }
951 }
952
953
954 void YQWizard::sendTreeEvent( Q3ListViewItem * listViewItem )
955 {
956     if ( listViewItem )
957     {
958         YQWizard::TreeItem * item = dynamic_cast<YQWizard::TreeItem *> ( listViewItem );
959
960         if ( item && ! item->id().isEmpty() )
961             sendEvent( YCPString( toUTF8( item->id() ) ) );
962     }
963 }
964
965
966 void YQWizard::treeSelectionChanged()
967 {
968     if ( _tree )
969         sendTreeEvent( _tree->selectedItem() );
970 }
971
972
973 YCPString YQWizard::currentTreeSelection()
974 {
975     if ( _tree )
976     {
977         Q3ListViewItem * sel = _tree->selectedItem();
978
979         if ( sel )
980         {
981             YQWizard::TreeItem * item = dynamic_cast<YQWizard::TreeItem *> (sel);
982
983             if ( item && ! item->id().isEmpty() )
984                 return YCPString( (const char *) item->id() );
985         }
986     }
987
988     return YCPString( "" );
989 }
990
991
992
993
994 void YQWizard::layoutWorkArea( QFrame * parentHBox )
995 {
996     Q3VBox * workAreaVBox = new Q3VBox( parentHBox );
997     YUI_CHECK_NEW( workAreaVBox );
998
999     // An extra QVBox inside the workAreaVBox is needed for frame and margin
1000
1001     Q3VBox * workArea = new Q3VBox( workAreaVBox );
1002     YUI_CHECK_NEW( workArea );
1003
1004 #if ENABLE_GRADIENTS
1005     workArea->setFrameStyle( QFrame::Box | QFrame::Plain );
1006     workArea->setMargin( 4 );
1007 #else
1008     workArea->setFrameStyle( QFrame::Box | QFrame::Sunken );
1009     // workArea->setFrameStyle( QFrame::TabWidgetPanel | QFrame::Sunken );
1010 #endif
1011
1012
1013     //
1014     // Menu bar
1015     //
1016
1017     // Placed directly inside workArea the menu bar positions itself at (0,0)
1018     // and so obscures any kind of frame there might be.
1019     _menuBarBox = new Q3VBox( workArea );
1020     YUI_CHECK_NEW( _menuBarBox );
1021
1022     _menuBar = new QMenuBar( _menuBarBox );
1023     YUI_CHECK_NEW( _menuBar );
1024
1025     _menuBarBox->hide(); // will be made visible when menus are added
1026
1027
1028     //
1029     // Dialog icon and heading
1030     //
1031
1032     Q3HBox * headingHBox = new Q3HBox( workArea );
1033     YUI_CHECK_NEW( headingHBox );
1034     headingHBox->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ) ); // hor/vert
1035
1036     addHSpacing( headingHBox, SEPARATOR_MARGIN );
1037
1038     _dialogIcon = new QLabel( headingHBox );
1039     YUI_CHECK_NEW( _dialogIcon );
1040     _dialogIcon->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) ); // hor/vert
1041
1042     addHSpacing( headingHBox );
1043
1044     _dialogHeading = new QLabel( headingHBox );
1045     YUI_CHECK_NEW( _dialogHeading );
1046     _dialogHeading->setFont( YQUI::yqApp()->headingFont() );
1047     _dialogHeading->setAlignment( Qt::AlignLeft | Qt::WordBreak );
1048     _dialogHeading->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ) ); // hor/vert
1049
1050 #if 0
1051     addHStretch( headingHBox );
1052 #endif
1053     addVSpacing( workArea );
1054
1055 #if USE_SEPARATOR
1056
1057     Q3HBox * hbox = new Q3HBox( workArea );
1058
1059     addHSpacing( hbox, SEPARATOR_MARGIN );
1060
1061     QFrame * separator = new QFrame( hbox );
1062     YUI_CHECK_NEW( separator );
1063     separator->setFrameStyle( QFrame::HLine | QFrame::Sunken );
1064
1065     addHSpacing( hbox, SEPARATOR_MARGIN );
1066     addVSpacing( workArea );
1067 #endif
1068
1069     //
1070     // Client area (the part that belongs to the YCP application)
1071     //
1072
1073     layoutClientArea( workArea );
1074
1075
1076     //
1077     // Button box
1078     //
1079
1080     layoutButtonBox( workAreaVBox );
1081
1082
1083     //
1084     // Spacer (purely decorative) at the right of the client area
1085     //
1086
1087     addGradientColumn( parentHBox, WORK_AREA_RIGHT_MARGIN );
1088 }
1089
1090
1091
1092 void YQWizard::layoutClientArea( QWidget * parent )
1093 {
1094     _clientArea = new Q3VBox( parent );
1095     YUI_CHECK_NEW( _clientArea );
1096     _clientArea->setMargin( 4 );
1097
1098 #if 0
1099     _clientArea->setPaletteBackgroundColor( QColor( 0x60, 0x60, 0x60 ) );
1100 #endif
1101
1102
1103     //
1104     // HVCenter for wizard contents
1105     //
1106
1107     _contents = new YQAlignment( this, _clientArea, YAlignCenter, YAlignCenter );
1108     YUI_CHECK_NEW( _contents );
1109
1110     _contents->setStretchable( YD_HORIZ, true );
1111     _contents->setStretchable( YD_VERT,  true );
1112     _contents->installEventFilter( this );
1113     _contents->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) ); // hor/vert
1114
1115
1116     //
1117     // Replace point for wizard contents
1118     //
1119
1120     YReplacePoint * replacePoint = YUI::widgetFactory()->createReplacePoint( _contents );
1121     replacePoint->setId( new YCPValueWidgetID( YCPSymbol( YWizardContentsReplacePointID ) ) ); // `id(`contents)
1122
1123
1124     //
1125     // Initial YEmpty widget contents of replace point
1126     //
1127
1128     YUI::widgetFactory()->createEmpty( replacePoint );
1129     replacePoint->showChild();
1130 }
1131
1132
1133
1134 void YQWizard::layoutButtonBox( QWidget * parent )
1135 {
1136     //
1137     // Button box and layout
1138     //
1139
1140     QWidget * buttonBox = new QWidget( parent );
1141     YUI_CHECK_NEW( buttonBox );
1142
1143     // Using old-style layouts to enable a seamless background with the
1144     // gradient pixmap: Any sub-widgets (Q3VBox, QHBox) would have to get yet
1145     // another portion of that gradient as their backround pixmap, and it would
1146     // be very hard to cover all cases - resizing, hiding individual buttons, etc.
1147
1148     QVBoxLayout * vbox = new QVBoxLayout( buttonBox, 0, 0 );    // parent, margin, spacing
1149     YUI_CHECK_NEW( vbox );
1150
1151     vbox->addSpacing( BUTTON_BOX_TOP_MARGIN );
1152
1153
1154     //
1155     // QHBoxLayout for the buttons
1156     //
1157
1158     QHBoxLayout * hbox = new QHBoxLayout( vbox, 2 );            // parent, spacing
1159     YUI_CHECK_NEW( hbox );
1160
1161
1162     //
1163     // "Back" button
1164     //
1165
1166     _backButton  = new YQWizardButton( this, buttonBox, backButtonLabel(), backButtonId() );
1167     YUI_CHECK_NEW( _backButton );
1168
1169     hbox->addWidget( (QWidget *) _backButton->widgetRep() );
1170     connect( _backButton,       SIGNAL( clicked()               ),
1171              this,              SLOT  ( slotBackClicked()       ) );
1172
1173     _backButtonSpacer = new QSpacerItem( 0, 0,                          // width, height
1174                                          QSizePolicy::Expanding,        // horizontal
1175                                          QSizePolicy::Minimum );        // vertical
1176     YUI_CHECK_NEW( _backButtonSpacer );
1177     hbox->addItem( _backButtonSpacer );
1178
1179
1180     if ( _backButton->text().isEmpty() )
1181     {
1182         _backButton->hide();
1183
1184         // Minimize _backButtonSpacer
1185         _backButtonSpacer->changeSize( 0, 0,                            // width, height
1186                                        QSizePolicy::Minimum,            // horizontal
1187                                        QSizePolicy::Minimum );          // vertical
1188     }
1189
1190
1191     //
1192     // "Abort" button
1193     //
1194
1195     _abortButton = new YQWizardButton( this, buttonBox, abortButtonLabel(), abortButtonId() );
1196     YUI_CHECK_NEW( _abortButton );
1197
1198     hbox->addWidget( (QWidget *) _abortButton->widgetRep() );
1199     connect( _abortButton,      SIGNAL( clicked()               ),
1200              this,              SLOT  ( slotAbortClicked()      ) );
1201
1202
1203     // Using spacer rather than addSpacing() since the default stretchability
1204     // of a QSpacerItem is undefined, i.e. centering the middle button could
1205     // not be guaranteed.
1206
1207     QSpacerItem * spacer = new QSpacerItem( 0, 0,                       // width, height
1208                                             QSizePolicy::Expanding,     // horizontal
1209                                             QSizePolicy::Minimum );     // vertical
1210     YUI_CHECK_NEW( spacer );
1211     hbox->addItem( spacer );
1212
1213
1214     //
1215     // "Next" button
1216     //
1217
1218     _nextButton  = new YQWizardButton( this, buttonBox, nextButtonLabel(), nextButtonId() );
1219     YUI_CHECK_NEW( _nextButton );
1220
1221     hbox->addWidget( (QWidget *) _nextButton->widgetRep() );
1222     connect( _nextButton,       SIGNAL( clicked()               ),
1223              this,              SLOT  ( slotNextClicked()       ) );
1224
1225
1226     //
1227     // Bottom margin and gradient
1228     //
1229
1230     vbox->addSpacing( WORK_AREA_BOTTOM_MARGIN );
1231
1232 #if ENABLE_GRADIENTS
1233     setBottomCroppedGradient( buttonBox, _bottomGradientPixmap, buttonBox->sizeHint().height() );
1234 #endif
1235
1236     buttonBox->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); // hor/vert
1237 }
1238
1239
1240
1241 void YQWizard::loadGradientPixmaps()
1242 {
1243 #if ENABLE_GRADIENTS
1244     if ( highColorDisplay() )
1245     {
1246         _topGradientPixmap      = QPixmap( PIXMAP_DIR "top-gradient.png"        );
1247         _bottomGradientPixmap   = QPixmap( PIXMAP_DIR "bottom-gradient.png"     );
1248         _titleBarGradientPixmap = QPixmap( PIXMAP_DIR "title-bar-gradient.png"  );
1249         _gradientCenterColor    = pixelColor( _bottomGradientPixmap, 0, 0, paletteBackgroundColor() );
1250         _gradientTopColor       = pixelColor( _topGradientPixmap, 0, 0 , paletteBackgroundColor() );
1251     }
1252     else // 8 bit display or worse - don't use gradients
1253     {
1254         // Gradient pixmaps remain empty. The respecive widgets will retain the
1255         // default widget background (grey, depending on the widget theme).
1256
1257         // Use deault widget background (some shade of grey) for the center
1258         // stretchable part of the side bar.
1259         _gradientCenterColor = paletteBackgroundColor();
1260         _gradientTopColor = paletteBackgroundColor();
1261     }
1262 #endif
1263 }
1264
1265
1266 void YQWizard::loadStepsIcons()
1267 {
1268     _stepCurrentIcon    = YQIconPool::stepCurrent();
1269     _stepToDoIcon       = YQIconPool::stepToDo();
1270     _stepDoneIcon       = YQIconPool::stepDone();
1271
1272     if ( highColorDisplay() )
1273     {
1274         _stepCurrentColor       = pixelColor( QPixmap( PIXMAP_DIR "color-step-current.png" ), 0, 0, paletteForegroundColor() );
1275         _stepToDoColor          = pixelColor( QPixmap( PIXMAP_DIR "color-step-todo.png"    ), 0, 0, paletteForegroundColor() );
1276         _stepDoneColor          = pixelColor( QPixmap( PIXMAP_DIR "color-step-done.png"    ), 0, 0, paletteForegroundColor() );
1277     }
1278     else
1279     {
1280         _stepCurrentColor       = paletteForegroundColor();
1281         _stepToDoColor          = paletteForegroundColor();
1282         _stepDoneColor          = paletteForegroundColor();
1283     }
1284 }
1285
1286
1287
1288 void YQWizard::setGradient( QWidget * widget, const QPixmap & pixmap )
1289 {
1290 #if ENABLE_GRADIENTS
1291     if ( widget && ! pixmap.isNull() )
1292     {
1293         widget->setFixedHeight( pixmap.height() );
1294         widget->setPaletteBackgroundPixmap( pixmap );
1295     }
1296 #endif
1297 }
1298
1299
1300
1301 void YQWizard::setBottomCroppedGradient( QWidget * widget, const QPixmap & pixmap, int croppedHeight )
1302 {
1303 #if ENABLE_GRADIENTS
1304     setGradient( widget, bottomCropPixmap( pixmap, croppedHeight ) );
1305 #endif
1306 }
1307
1308
1309
1310 QPixmap YQWizard::bottomCropPixmap( const QPixmap & full, int croppedHeight )
1311 {
1312     QPixmap pixmap;
1313
1314 #if ENABLE_GRADIENTS
1315
1316     if ( full.height() > croppedHeight )
1317     {
1318         pixmap = QPixmap( full.width(), croppedHeight );
1319
1320         bitBlt( &pixmap, 0, 0,                                  // dest, dest_x, dest_y
1321                 &full,   0, full.height() - croppedHeight - 1,  // src, src_x, src_y
1322                 full.width(), croppedHeight );                  // src_width, src_height
1323     }
1324     else
1325     {
1326         pixmap = full;
1327     }
1328 #endif
1329
1330     return pixmap;
1331 }
1332
1333
1334
1335 QColor YQWizard::pixelColor( const QPixmap & pixmap, int x, int y, const QColor & defaultColor )
1336 {
1337     if ( pixmap.isNull() )
1338         return defaultColor;
1339
1340     // QPixmap doesn't allow direct access to pixel values (which makes some
1341     // sense since this requires a round-trip to the X server - pixmaps are X
1342     // server resources), so we need to convert the QPixmap to a QImage to get
1343     // that information. But since this conversion is expensive, we might save
1344     // some performance if we only convert the part we really need - so let's
1345     // cut out a tiny portion of the original pixmap and convert only that tiny
1346     // portion.
1347
1348
1349     QPixmap tiny( 1, 1 );
1350
1351     bitBlt( &tiny, 0, 0,        // dest, dest_x, dest_y
1352             &pixmap, x, y,      // src, src_x, src_y
1353             1, 1 );             // src_width, src_height
1354
1355     QImage image = tiny.convertToImage();
1356
1357
1358     return QColor( image.pixel( 0, 0 ) );
1359 }
1360
1361
1362
1363 void YQWizard::addGradientColumn( QWidget * parent, int width )
1364 {
1365     if ( ! parent )
1366         return;
1367
1368     Q3VBox * vbox = new Q3VBox( parent );
1369     YUI_CHECK_NEW( vbox );
1370
1371 #if ENABLE_GRADIENTS
1372     QWidget * topGradient = addHSpacing( vbox, width );
1373     YUI_CHECK_NEW( topGradient );
1374     setGradient( topGradient, _topGradientPixmap );
1375
1376     QWidget * centerStretch = new QWidget( vbox );
1377     YUI_CHECK_NEW( centerStretch );
1378     centerStretch->setPaletteBackgroundColor( _gradientCenterColor );
1379
1380
1381     QWidget * bottomGradient = new QWidget( vbox );
1382     YUI_CHECK_NEW( bottomGradient );
1383     setGradient( bottomGradient, _bottomGradientPixmap );
1384 #else
1385     vbox->setFixedWidth( width );
1386 #endif
1387
1388 }
1389
1390
1391 void YQWizard::destroyButtons()
1392 {
1393     if ( _backButton  )
1394     {
1395         delete _backButton;
1396         _backButton = 0;
1397     }
1398
1399     if ( _abortButton )
1400     {
1401         delete _abortButton;
1402         _abortButton = 0;
1403     }
1404
1405     if ( _nextButton  )
1406     {
1407         delete _nextButton;
1408         _nextButton = 0;
1409     }
1410 }
1411
1412
1413
1414 bool YQWizard::highColorDisplay() const
1415 {
1416     return depth() > 8;
1417 }
1418
1419
1420 void YQWizard::connectNotify ( const char * signal )
1421 {
1422     if ( QString( signal ).contains( "nextClicked()" ) )
1423     {
1424         y2debug( "nextClicked connected, no longer directly sending button events" );
1425         _sendButtonEvents = false;
1426     }
1427 }
1428
1429
1430 void YQWizard::disconnectNotify ( const char * signal )
1431 {
1432     if ( QString( signal ).contains( "nextClicked()" ) )
1433     {
1434         y2debug( "nextClicked disconnected, directly sending button events again" );
1435         _sendButtonEvents = true;
1436     }
1437 }
1438
1439
1440 void YQWizard::setDialogIcon( const char * iconName )
1441 {
1442     if ( _dialogIcon )
1443     {
1444         if ( iconName && *iconName )
1445         {
1446             QPixmap icon( iconName );
1447
1448             if ( icon.isNull() )
1449                 y2warning( "Couldn't load dialog icon \"%s\"", iconName );
1450             else
1451             {
1452                 _dialogIcon->setPixmap( icon );
1453                 topLevelWidget()->setIcon( icon );
1454             }
1455         }
1456         else
1457         {
1458             _dialogIcon->clear();
1459             topLevelWidget()->setIcon( QPixmap() );
1460         }
1461     }
1462 }
1463
1464
1465 void YQWizard::setDialogHeading( const QString & headingText )
1466 {
1467     if ( _dialogHeading )
1468     {
1469         if ( ! headingText.isEmpty() )
1470             _dialogHeading->setText( headingText );
1471         else
1472             _dialogHeading->clear();
1473     }
1474 }
1475
1476 string YQWizard::debugLabel()
1477 {
1478     if ( _dialogHeading )
1479     {
1480         QString label = _dialogHeading->text();
1481         label.simplifyWhiteSpace(); // Replace any embedded newline with a single blank
1482
1483         if ( ! label.isEmpty() )
1484         {
1485             label.prepend( "YQWizard \"" );
1486             label.append( "\"" );
1487
1488             return toUTF8( label );
1489         }
1490     }
1491
1492     return "untitled YQWizard";
1493 }
1494
1495 void YQWizard::setHelpText( QString helpText )
1496 {
1497     if ( _helpBrowser )
1498     {
1499         if ( ! helpText.isEmpty() )
1500         {
1501             helpText.replace( "&product;", YQUI::ui()->productName() );
1502             _helpBrowser->setText( helpText );
1503         }
1504         else
1505             _helpBrowser->clear();
1506     }
1507 }
1508
1509
1510 void YQWizard::slotBackClicked()
1511 {
1512     emit backClicked();
1513
1514     if ( _sendButtonEvents )
1515         sendEvent( _backButton->id() );
1516
1517     _direction = YQWizard::Backward;
1518 }
1519
1520
1521 void YQWizard::slotAbortClicked()
1522 {
1523     emit abortClicked();
1524
1525     if ( _sendButtonEvents )
1526         sendEvent( _abortButton->id() );
1527 }
1528
1529
1530 void YQWizard::slotNextClicked()
1531 {
1532     emit nextClicked();
1533
1534     if ( _sendButtonEvents )
1535         sendEvent( _nextButton->id() );
1536
1537     _direction = YQWizard::Forward;
1538 }
1539
1540
1541 void YQWizard::showHelp()
1542 {
1543     if ( _sideBar && _helpPanel )
1544     {
1545         _sideBar->raiseWidget( _helpPanel );
1546     }
1547 }
1548
1549
1550 void YQWizard::releaseNotesClicked()
1551 {
1552     if ( ! _releaseNotesButtonId.isNull() )
1553     {
1554         y2milestone( "Release Notes button clicked" );
1555         sendEvent( _releaseNotesButtonId );
1556     }
1557 }
1558
1559
1560 void YQWizard::showSteps()
1561 {
1562     if ( _sideBar && _stepsPanel )
1563     {
1564         _sideBar->raiseWidget( _stepsPanel );
1565     }
1566 }
1567
1568
1569 void YQWizard::showTree()
1570 {
1571     if ( _sideBar && _treePanel )
1572     {
1573         _sideBar->raiseWidget( _treePanel );
1574     }
1575 }
1576
1577
1578
1579 void YQWizard::addMenu( const QString & text,
1580                         const QString & id )
1581 {
1582     if ( _menuBar )
1583     {
1584         Q3PopupMenu * menu = new Q3PopupMenu( _menuBar );
1585         YUI_CHECK_NEW( menu );
1586
1587         _menuIDs.insert( id, menu );
1588         _menuBar->insertItem( text, menu );
1589
1590         connect( menu, SIGNAL( activated    ( int ) ),
1591                  this, SLOT  ( sendMenuEvent( int ) ) );
1592
1593         if ( _menuBarBox && _menuBarBox->isHidden() )
1594         {
1595             _menuBarBox->show();
1596             _menuBarBox->setFixedHeight( _menuBar->sizeHint().height() + MENU_BAR_MARGIN );
1597         }
1598     }
1599 }
1600
1601
1602 void YQWizard::addSubMenu( const QString & parentMenuID,
1603                            const QString & text,
1604                            const QString & id )
1605 {
1606     Q3PopupMenu * parentMenu = _menuIDs[ parentMenuID ];
1607
1608     if ( parentMenu )
1609     {
1610         Q3PopupMenu * menu = new Q3PopupMenu( _menuBar );
1611         YUI_CHECK_NEW( menu );
1612
1613         _menuIDs.insert( id, menu );
1614         parentMenu->insertItem( text, menu );
1615
1616         connect( menu, SIGNAL( activated    ( int ) ),
1617                  this, SLOT  ( sendMenuEvent( int ) ) );
1618     }
1619     else
1620     {
1621         y2error( "Can't find menu with ID %s", (const char *) parentMenuID );
1622     }
1623 }
1624
1625
1626 void YQWizard::addMenuEntry( const QString & parentMenuID,
1627                              const QString & text,
1628                              const QString & idString )
1629 {
1630     Q3PopupMenu * parentMenu = _menuIDs[ parentMenuID ];
1631
1632     if ( parentMenu )
1633     {
1634         int id = _menuEntryIDs.size();
1635         _menuEntryIDs.push_back( idString );
1636         parentMenu->insertItem( text, id );
1637     }
1638     else
1639     {
1640         y2error( "Can't find menu with ID %s", (const char *) parentMenuID );
1641     }
1642 }
1643
1644
1645 void YQWizard::addMenuSeparator( const QString & parentMenuID )
1646 {
1647     Q3PopupMenu * parentMenu = _menuIDs[ parentMenuID ];
1648
1649     if ( parentMenu )
1650     {
1651         parentMenu->insertSeparator();
1652     }
1653     else
1654     {
1655         y2error( "Can't find menu with ID %s", (const char *) parentMenuID );
1656     }
1657 }
1658
1659
1660 void YQWizard::deleteMenus()
1661 {
1662     if ( _menuBar )
1663     {
1664         _menuBarBox->hide();
1665         _menuBar->clear();
1666         _menuIDs.clear();
1667         _menuEntryIDs.clear();
1668     }
1669 }
1670
1671
1672 void YQWizard::sendMenuEvent( int numID )
1673 {
1674     if ( numID >= 0 && numID < (int) _menuEntryIDs.size() )
1675     {
1676         sendEvent( YCPString( toUTF8( _menuEntryIDs[ numID ] ) ) );
1677     }
1678     else
1679     {
1680         y2error( "Invalid menu ID: %d", numID );
1681     }
1682 }
1683
1684
1685 void YQWizard::sendEvent( YWidgetID * rawId )
1686 {
1687     if ( rawId )
1688     {
1689         YCPValueWidgetID * id = dynamic_cast<YCPValueWidgetID *> (rawId );
1690
1691         if ( id )
1692             sendEvent( id->value() );
1693     }
1694 }
1695
1696
1697 void YQWizard::sendEvent( YCPValue id )
1698 {
1699     // Wizard events are sent as menu events - the semantics are similar.
1700     //
1701     // Widget events wouldn't do since they use their widget's ID as the ID to
1702     // return (which would be inappropriate since that would be the ID of the
1703     // wizard widget). Another type of event (WizardEvent) could be introduced,
1704     // but it would add little more information (if any) than MenuEvent.
1705     //
1706     // YQPackageSelector uses the same approach. After all, one widget that can
1707     // return multiple IDs is roughly the semantics of MenuEvents.
1708
1709     YQUI::ui()->sendEvent( new YMenuEvent( id ) );
1710 }
1711
1712
1713
1714 int YQWizard::preferredWidth()
1715 {
1716     return sizeHint().width();
1717 }
1718
1719
1720 int YQWizard::preferredHeight()
1721 {
1722     return sizeHint().height();
1723 }
1724
1725
1726 void YQWizard::setSize( int newWidth, int newHeight )
1727 {
1728     resize( newWidth, newHeight );
1729     resizeClientArea();
1730 }
1731
1732
1733
1734 void YQWizard::resizeClientArea()
1735 {
1736     // y2debug( "resizing client area" );
1737     QRect contentsRect = _clientArea->contentsRect();
1738     _contents->setSize( contentsRect.width(), contentsRect.height() );
1739 }
1740
1741
1742
1743 bool YQWizard::eventFilter( QObject * obj, QEvent * ev )
1744 {
1745     if ( ev->type() == QEvent::Resize && obj == _contents )
1746     {
1747         resizeClientArea();
1748         return true;            // Event handled
1749     }
1750
1751     return QWidget::eventFilter( obj, ev );
1752 }
1753
1754
1755 void YQWizard::setButtonLabel( YQWizardButton * button, const QString & newLabel )
1756 {
1757     if ( button )
1758     {
1759         button->setLabel( newLabel );
1760         YDialog::currentDialog()->checkShortcuts();
1761
1762         if ( newLabel.isEmpty() )
1763         {
1764             button->hide();
1765
1766             if ( button == _backButton && _backButtonSpacer )
1767             {
1768                 // Minimize _backButtonSpacer
1769
1770                 _backButtonSpacer->changeSize( 0, 0,                            // width, height
1771                                                QSizePolicy::Minimum,            // horizontal
1772                                                QSizePolicy::Minimum );          // vertical
1773             }
1774         }
1775         else
1776         {
1777             button->show();
1778
1779             if ( button == _backButton && _backButtonSpacer )
1780             {
1781                 // Restore _backButtonSpacer to normal size
1782
1783                 _backButtonSpacer->changeSize( 0, 0,                            // width, height
1784                                                QSizePolicy::Expanding,          // horizontal
1785                                                QSizePolicy::Minimum );          // vertical
1786             }
1787         }
1788     }
1789 }
1790
1791
1792 void YQWizard::setButtonID( YQWizardButton * button, const YCPValue & id )
1793 {
1794     if ( button )
1795     {
1796         button->setId( new YCPValueWidgetID( id ) );
1797     }
1798 }
1799
1800
1801 void YQWizard::enableButton( YQWizardButton * button, bool enabled )
1802 {
1803     if ( button == _nextButton && _protectNextButton && ! enabled )
1804         return;
1805
1806     if ( button )
1807         button->setEnabled( enabled );
1808 }
1809
1810
1811 void YQWizard::setButtonFocus( YQWizardButton * button )
1812 {
1813     if ( button )
1814         button->setKeyboardFocus();
1815 }
1816
1817
1818 void YQWizard::showReleaseNotesButton( string label, const YCPValue & id )
1819 {
1820     if ( ! _releaseNotesButton )
1821     {
1822         y2error( "NULL Release Notes button" );
1823         if ( ! _stepsBox )
1824             y2error( "This works only if there is a \"steps\" panel!" );
1825
1826         return;
1827     }
1828
1829     label = YShortcut::cleanShortcutString( label );    // no way to check the shortcut, so strip it
1830     _releaseNotesButton->setText( fromUTF8( label ) );
1831     _releaseNotesButtonId = id;
1832
1833
1834     if ( _releaseNotesButton->isHidden() )
1835         _releaseNotesButton->show();
1836
1837 }
1838
1839
1840 void YQWizard::hideReleaseNotesButton()
1841 {
1842     if ( _releaseNotesButton && _releaseNotesButton->isShown() )
1843         _releaseNotesButton->hide();
1844 }
1845
1846
1847 void YQWizard::retranslateInternalButtons()
1848 {
1849     YQUI::setTextdomain( TEXTDOMAIN );
1850
1851     if ( _helpButton )
1852         // "Help" button - intentionally without keyboard shortcut
1853         _helpButton->setText( _( "Help" ) );
1854
1855     if ( _stepsButton )
1856         // "Steps" button - intentionally without keyboard shortcut
1857         _stepsButton->setText( _( "Steps" ) );
1858
1859     if ( _treeButton )
1860         // "Tree" button - intentionally without keyboard shortcut
1861         _treeButton->setText( _( "Tree" ) );
1862 }
1863
1864
1865 void YQWizard::ping()
1866 {
1867     y2debug( "YQWizard is active" );
1868 }
1869
1870
1871 bool YQWizard::isCommand( QString declaration, const YCPTerm & term )
1872 {
1873     declaration = declaration.simplifyWhiteSpace();
1874
1875     // Check command name
1876
1877     QString command = declaration;
1878     command.remove( QRegExp( "\\s*\\(.*$" ) );  // remove arguments
1879
1880     if ( term->name().c_str() != command )
1881         return false;
1882
1883     //
1884     // Check arguments
1885     //
1886
1887     QString arg_decl = declaration;
1888     arg_decl.remove( QRegExp( "^.*\\(" ) );     // remove "command ("
1889     arg_decl.remove( QRegExp( "\\).*$" ) );     // remove ")"
1890
1891     QStringList argDeclList = arg_decl.split( ",", QString::SkipEmptyParts );
1892
1893     //
1894     // Check number of arguments
1895     //
1896
1897     if ( argDeclList.size() != term->size() )
1898     {
1899         y2error( "Bad arguments for wizard command %s : %s",
1900                  (const char *) declaration, term->toString().c_str() );
1901         return false;
1902     }
1903
1904
1905     //
1906     // Check each individual argument
1907     //
1908
1909     bool ok = true;
1910
1911     for ( int i=0; i < argDeclList.size() && ok; i++ )
1912     {
1913         QString wanted = argDeclList[ i ].stripWhiteSpace();
1914         YCPValue seen  = term->value( i );
1915
1916         if      ( wanted == "string"    )       ok = seen->isString();
1917         else if ( wanted == "boolean"   )       ok = seen->isBoolean();
1918         else if ( wanted == "bool"      )       ok = seen->isBoolean();
1919         else if ( wanted == "list"      )       ok = seen->isList();
1920         else if ( wanted == "map"       )       ok = seen->isMap();
1921         else if ( wanted == "integer"   )       ok = seen->isInteger();
1922         else if ( wanted == "int"       )       ok = seen->isInteger();
1923         else if ( wanted == "any"       )       ok = true;
1924         else
1925         {
1926             y2error( "Bad declaration for wizard command %s : Unknown type \"%s\"",
1927                      (const char *) declaration, (const char *) wanted );
1928         }
1929     }
1930
1931     if ( ! ok )
1932     {
1933         y2error( "Bad arguments for wizard command %s : %s",
1934                  (const char *) declaration, term->toString().c_str() );
1935     }
1936
1937     if ( ok && _verboseCommands )
1938     {
1939         // Intentionally logging as milestone because a YCP app just explicitly
1940         // requested this log level
1941         y2milestone( "Recognized wizard command %s : %s",
1942                      (const char *) declaration, term->toString().c_str() );
1943     }
1944
1945     return ok;
1946 }
1947
1948
1949 QString YQWizard::qStringArg( const YCPTerm & term, int argNo )
1950 {
1951     return fromUTF8( stringArg( term, argNo ).c_str() );
1952 }
1953
1954
1955 string YQWizard::stringArg( const YCPTerm & term, int argNo )
1956 {
1957     if ( term->size() > argNo )
1958     {
1959         YCPValue arg( term->value( argNo ) );
1960
1961         if ( arg->isString() )
1962             return arg->asString()->value();
1963     }
1964
1965     y2error( "Couldn't convert arg #%d of '%s' to string", argNo, term->toString().c_str() );
1966     return "";
1967 }
1968
1969
1970 bool YQWizard::boolArg( const YCPTerm & term, int argNo )
1971 {
1972     if ( term->size() > argNo )
1973     {
1974         YCPValue arg( term->value( argNo ) );
1975
1976         if ( arg->isBoolean() )
1977             return arg->asBoolean()->value();
1978     }
1979
1980     y2error( "Couldn't convert arg #%d of '%s' to bool", argNo, term->toString().c_str() );
1981     return false;
1982 }
1983
1984
1985 YCPValue YQWizard::anyArg( const YCPTerm & term, int argNo )
1986 {
1987     if ( term->size() > argNo )
1988     {
1989         return term->value( argNo );
1990     }
1991
1992     return YCPVoid();
1993 }
1994
1995
1996
1997 YCPValue YQWizard::command( const YCPTerm & cmd )
1998 {
1999 #define OK YCPBoolean( true );
2000
2001
2002     if ( isCommand( "SetHelpText          ( string )", cmd ) )  { setHelpText   ( qStringArg( cmd, 0 ) );               return OK; }
2003     if ( isCommand( "SetDialogIcon        ( string )", cmd ) )  { setDialogIcon ( qStringArg( cmd, 0 ) );               return OK; }
2004     if ( isCommand( "SetDialogHeading     ( string )", cmd ) )  { setDialogHeading( qStringArg( cmd, 0 ) );             return OK; }
2005
2006     if ( isCommand( "SetCurrentStep       ( string )", cmd ) )  { setCurrentStep( qStringArg( cmd, 0 ) );               return OK; }
2007     if ( isCommand( "AddStep ( string, string )"     , cmd ) )  { addStep( qStringArg( cmd, 0 ), qStringArg( cmd, 1 )); return OK; }
2008     if ( isCommand( "AddStepHeading       ( string )", cmd ) )  { addStepHeading( qStringArg( cmd, 0 ) );               return OK; }
2009     if ( isCommand( "DeleteSteps()"                  , cmd ) )  { deleteSteps();                                        return OK; }
2010     if ( isCommand( "UpdateSteps()"                  , cmd ) )  { updateSteps();                                        return OK; }
2011
2012     if ( isCommand( "SetAbortButtonLabel  ( string )", cmd ) )  { setButtonLabel( _abortButton, qStringArg( cmd, 0 ) ); return OK; }
2013     if ( isCommand( "SetBackButtonLabel   ( string )", cmd ) )  { setButtonLabel( _backButton,  qStringArg( cmd, 0 ) ); return OK; }
2014     if ( isCommand( "SetNextButtonLabel   ( string )", cmd ) )  { setButtonLabel( _nextButton,  qStringArg( cmd, 0 ) ); return OK; }
2015     if ( isCommand( "SetCancelButtonLabel ( string )", cmd ) )  { setButtonLabel( _abortButton, qStringArg( cmd, 0 ) ); return OK; }
2016     if ( isCommand( "SetAcceptButtonLabel ( string )", cmd ) )  { setButtonLabel( _nextButton,  qStringArg( cmd, 0 ) ); return OK; }
2017
2018     if ( isCommand( "SetAbortButtonID     ( any )"   , cmd ) )  { setButtonID( _abortButton,    anyArg( cmd, 0 ) );     return OK; }
2019     if ( isCommand( "SetBackButtonID      ( any )"   , cmd ) )  { setButtonID( _backButton,     anyArg( cmd, 0 ) );     return OK; }
2020     if ( isCommand( "SetNextButtonID      ( any )"   , cmd ) )  { setButtonID( _nextButton,     anyArg( cmd, 0 ) );     return OK; }
2021
2022     if ( isCommand( "EnableBackButton     ( bool )"  , cmd ) )  { enableButton( _backButton,    boolArg( cmd, 0 ) );    return OK; }
2023     if ( isCommand( "EnableNextButton     ( bool )"  , cmd ) )  { enableButton( _nextButton,    boolArg( cmd, 0 ) );    return OK; }
2024     if ( isCommand( "EnableAbortButton    ( bool )"  , cmd ) )  { enableButton( _abortButton,   boolArg( cmd, 0 ) );    return OK; }
2025     if ( isCommand( "ProtectNextButton    ( bool )"  , cmd ) )  { _protectNextButton = boolArg( cmd, 0 );               return OK; }
2026
2027     if ( isCommand( "SetFocusToNextButton ()"        , cmd ) )  { setButtonFocus( _nextButton );                        return OK; }
2028     if ( isCommand( "SetFocusToBackButton ()"        , cmd ) )  { setButtonFocus( _backButton );                        return OK; }
2029
2030
2031     if ( isCommand( "SetVerboseCommands   ( bool )"  , cmd ) )  { setVerboseCommands( boolArg( cmd, 0 ) );              return OK; }
2032
2033     if ( isCommand( "DeleteTreeItems()"              , cmd ) )  { deleteTreeItems();                                    return OK; }
2034     if ( isCommand( "SelectTreeItem( string )"       , cmd ) )  { selectTreeItem( qStringArg( cmd, 0 ) );               return OK; }
2035     if ( isCommand( "AddTreeItem( string, string, string )", cmd ) )    { addTreeItem   ( qStringArg( cmd, 0 ),
2036                                                                                           qStringArg( cmd, 1 ),
2037                                                                                           qStringArg( cmd, 2 )  );      return OK; }
2038
2039     if ( isCommand( "AddMenu      ( string, string )"         , cmd ) ) { addMenu       ( qStringArg( cmd, 0 ),
2040                                                                                           qStringArg( cmd, 1 ) );       return OK; }
2041
2042     if ( isCommand( "AddSubMenu   ( string, string, string )" , cmd ) ) { addSubMenu    ( qStringArg( cmd, 0 ),
2043                                                                                           qStringArg( cmd, 1 ),
2044                                                                                           qStringArg( cmd, 2 ) );       return OK; }
2045
2046     if ( isCommand( "AddMenuEntry ( string, string, string )" , cmd ) ) { addMenuEntry  ( qStringArg( cmd, 0 ),
2047                                                                                           qStringArg( cmd, 1 ),
2048                                                                                           qStringArg( cmd, 2 ) );       return OK; }
2049
2050     if ( isCommand( "AddMenuSeparator ( string )"            , cmd ) )  { addMenuSeparator( qStringArg( cmd, 0 ) );     return OK; }
2051     if ( isCommand( "DeleteMenus ()"                         , cmd ) )  { deleteMenus();                                return OK; }
2052     if ( isCommand( "ShowReleaseNotesButton( string, any )"  , cmd ) )  { showReleaseNotesButton( stringArg( cmd, 0 ),
2053                                                                                                   anyArg   ( cmd, 1 )); return OK; }
2054     if ( isCommand( "HideReleaseNotesButton()"               , cmd ) )  { hideReleaseNotesButton();                     return OK; }
2055     if ( isCommand( "RetranslateInternalButtons()"           , cmd ) )  { retranslateInternalButtons() ;                return OK; }
2056     if ( isCommand( "Ping()"                                 , cmd ) )  { ping() ;                                      return OK; }
2057     y2error( "Undefined wizard command: %s", cmd->toString().c_str() );
2058     return YCPBoolean( false );
2059
2060 #undef OK
2061 }
2062
2063
2064
2065 #include "YQWizard.moc"