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