Salome HOME
be056b12d0c4d9f2658aaeae75558b224eb057ee
[tools/install.git] / src / InstallWizard.cpp
1 //  File      : InstallWizard.cpp
2 //  Created   : Thu Mar 27 12:01:00 2003
3 //  Author    : Vadim SANDLER
4 //  Project   : SALOME Professional
5 //  Module    : InstallWizard
6 //  Copyright : 2003 CEA/DEN, EDF R&D
7 //  $Header$ 
8
9 #include "InstallWizard.h"
10
11 #include "qlayout.h"
12 #include "qpushbutton.h"
13 #include "qcursor.h"
14 #include "qlabel.h"
15 #include "qwidgetstack.h"
16 #include "qapplication.h"
17 #include "qptrlist.h"
18 #include "qpainter.h"
19 #include "qaccel.h"
20
21 class InstallWizardPrivate
22 {
23 public:
24   struct Page {
25     Page( QWidget * widget, const QString & title ):
26           w( widget ), t( title ),
27     backEnabled( TRUE ), nextEnabled( TRUE ), finishEnabled( FALSE ),
28     helpEnabled( TRUE ),
29     appropriate( TRUE )
30   {}
31   QWidget * w;
32   QString t;
33   bool backEnabled;
34   bool nextEnabled;
35   bool finishEnabled;
36   bool helpEnabled;
37   bool appropriate;
38   };
39   
40   QVBoxLayout * v;
41   Page * current;
42   QWidgetStack * ws;
43   QPtrList<Page> pages;
44   QLabel * title;
45   QPushButton * backButton;
46   QPushButton * nextButton;
47   QPushButton * finishButton;
48   QPushButton * cancelButton;
49   QPushButton * helpButton;
50   QFrame * hbar1, * hbar2;
51   
52 #ifndef QT_NO_ACCEL
53   QAccel * accel;
54   int backAccel;
55   int nextAccel;
56 #endif
57   
58   Page * page( const QWidget * w )
59   {
60     if ( !w )
61       return 0;
62     int i = pages.count();
63     while( --i >= 0 && pages.at( i ) && pages.at( i )->w != w ) { }
64     return i >= 0 ? pages.at( i ) : 0;
65   }
66 };
67
68
69 /*!  Constructs an empty wizard dialog.
70 The \a parent, \a name, \a modal and \a f arguments are passed to
71 the QDialog constructor.
72
73 */
74
75 InstallWizard::InstallWizard( QWidget *parent, const char *name, bool modal,
76                              WFlags f )
77   : QDialog( parent, name, modal, f )
78 {
79   d = new InstallWizardPrivate();
80   d->current = 0; // not quite true, but...
81   d->ws = new QWidgetStack( this, "qt_widgetstack" );
82   d->pages.setAutoDelete( TRUE );
83   d->title = new QLabel( this, "title label" );
84   
85   // create in nice tab order
86   d->nextButton = new QPushButton( this, "next" );
87   d->finishButton = new QPushButton( this, "finish" );
88   d->helpButton = new QPushButton( this, "help" );
89   d->backButton = new QPushButton( this, "back" );
90   d->cancelButton = new QPushButton( this, "cancel" );
91   
92   d->ws->installEventFilter( this );
93   
94   d->v = 0;
95   d->hbar1 = 0;
96   d->hbar2 = 0;
97   
98   d->cancelButton->setText( tr( "&Cancel" ) );
99   d->backButton->setText( tr( "< &Back" ) );
100   d->nextButton->setText( tr( "&Next >" ) );
101   d->finishButton->setText( tr( "&Finish" ) );
102   d->helpButton->setText( tr( "&Help" ) );
103   
104   d->nextButton->setDefault( TRUE );
105   
106   connect( d->backButton, SIGNAL(clicked()),
107     this, SLOT(back()) );
108   connect( d->nextButton, SIGNAL(clicked()),
109     this, SLOT(next()) );
110   connect( d->finishButton, SIGNAL(clicked()),
111     this, SLOT(accept()) );
112   connect( d->cancelButton, SIGNAL(clicked()),
113     this, SLOT(reject()) );
114   connect( d->helpButton, SIGNAL(clicked()),
115     this, SLOT(help()) );
116   
117 #ifndef QT_NO_ACCEL
118   d->accel = new QAccel( this, "arrow-key accel" );
119   d->backAccel = d->accel->insertItem( Qt::ALT + Qt::Key_Left );
120   d->accel->connectItem( d->backAccel, this, SLOT(back()) );
121   d->nextAccel = d->accel->insertItem( Qt::ALT + Qt::Key_Right );
122   d->accel->connectItem( d->nextAccel, this, SLOT(next()) );
123 #endif
124 }
125
126
127 /*!
128 Destroys the object and frees any allocated resources, including
129 all pages and controllers.
130 */
131
132 InstallWizard::~InstallWizard()
133 {
134   delete d;
135 }
136
137
138 /*!  \reimp  */
139
140 void InstallWizard::show()
141 {
142   if ( d->current )
143     showPage( d->current->w );
144   else if ( pageCount() > 0 )
145     showPage( d->pages.at( 0 )->w );
146   else
147     showPage( 0 );
148   
149   QDialog::show();
150 }
151
152
153 /*! \reimp */
154
155 void InstallWizard::setFont( const QFont & font )
156 {
157   QApplication::postEvent( this, new QEvent( QEvent::LayoutHint ) );
158   QDialog::setFont( font );
159 }
160
161
162 /*!  Adds \a page to the end of the page sequence, with the title, \a title.
163 */
164
165 void InstallWizard::addPage( QWidget * page, const QString & title )
166 {
167   if ( !page )
168     return;
169   if ( d->page( page ) ) {
170 #if defined(QT_CHECK_STATE)
171     qWarning( "InstallWizard::addPage(): already added %s/%s to %s/%s",
172       page->className(), page->name(),
173       className(), name() );
174 #endif
175     return;
176   }
177   int i = d->pages.count();
178   
179   if( i > 0 )
180     d->pages.at( i - 1 )->nextEnabled = TRUE;
181   
182   InstallWizardPrivate::Page * p = new InstallWizardPrivate::Page( page, title );
183   p->backEnabled = ( i > 0 );
184   d->ws->addWidget( page, i );
185   d->pages.append( p );
186 }
187
188 /*!
189 Inserts \a page at position \a index into the page sequence, with
190 title \a title. If \a index is -1, the page will be appended to
191 the end of the wizard's page sequence.
192 */
193
194 void InstallWizard::insertPage( QWidget * page, const QString & title, int index )
195 {
196   if ( !page )
197     return;
198   if ( d->page( page ) ) {
199 #if defined(QT_CHECK_STATE)
200     qWarning( "InstallWizard::insertPage(): already added %s/%s to %s/%s",
201       page->className(), page->name(),
202       className(), name() );
203 #endif
204     return;
205   }
206   
207   if ( index < 0  || index > (int)d->pages.count() )
208     index = d->pages.count();
209   
210   if( index > 0 && ( index == (int)d->pages.count() ) )
211     d->pages.at( index - 1 )->nextEnabled = TRUE;
212   
213   InstallWizardPrivate::Page * p = new InstallWizardPrivate::Page( page, title );
214   p->backEnabled = ( index > 0 );
215   p->nextEnabled = ( index < (int)d->pages.count() );
216   
217   d->ws->addWidget( page, index );
218   d->pages.insert( index, p );
219 }
220
221 /*!
222 \fn void InstallWizard::selected(const QString&)
223
224   This signal is emitted when the current page changes. The parameter
225   contains the title of the selected page.
226 */
227
228
229 /*!  Makes \a page the current page and emits the selected() signal. */
230
231 void InstallWizard::showPage( QWidget * page )
232 {
233   InstallWizardPrivate::Page * p = d->page( page );
234   if ( p ) {
235     int i;
236     for( i = 0; i < (int)d->pages.count() && d->pages.at( i ) != p; i++ );
237     bool notFirst( FALSE );
238     
239     if( i ) {
240       i--;
241       while( ( i >= 0 ) && !notFirst ) {
242         notFirst |= appropriate( d->pages.at( i )->w );
243         i--;
244       }
245     }
246     setBackEnabled( notFirst );
247     setNextEnabled( TRUE );
248     d->ws->raiseWidget( page );
249     d->current = p;
250   }
251   
252   layOut();
253   updateButtons();
254   emit selected( p ? p->t : QString::null );
255 }
256
257
258 /*!  Returns the number of pages in the wizard. */
259
260 int InstallWizard::pageCount() const
261 {
262   return d->pages.count();
263 }
264
265 /*!
266 Returns the position of page \a page.
267 If the page is not part of the wizard -1 is returned.
268 */
269
270 int InstallWizard::indexOf( QWidget* page ) const
271 {
272   InstallWizardPrivate::Page * p = d->page( page );
273   if ( !p ) return -1;
274   
275   return d->pages.find( p );
276 }
277
278 /*!
279 Called when the user clicks the Back button; this function shows
280 the preceding relevant page in the sequence.
281
282   \sa appropriate()
283 */
284 void InstallWizard::back()
285 {
286   int i = 0;
287   
288   while( i < (int)d->pages.count() && d->pages.at( i ) &&
289      d->current && d->pages.at( i )->w != d->current->w )
290      i++;
291   
292   i--;
293   while( i >= 0 && ( !d->pages.at( i ) || !appropriate( d->pages.at( i )->w ) ) )
294     i--;
295   
296   if( i >= 0 )
297     if( d->pages.at( i ) )
298       showPage( d->pages.at( i )->w );
299 }
300
301
302 /*!
303 Called when the user clicks the Next button, this function shows
304 the next relevant page in the sequence.
305
306   \sa appropriate()
307 */
308 void InstallWizard::next()
309 {
310   int i = 0;
311   while( i < (int)d->pages.count() && d->pages.at( i ) &&
312     d->current && d->pages.at( i )->w != d->current->w )
313     i++;
314   i++;
315   while( i <= (int)d->pages.count()-1 &&
316     ( !d->pages.at( i ) || !appropriate( d->pages.at( i )->w ) ) )
317      i++;
318   // if we fell of the end of the world, step back
319   while ( i > 0 && (i >= (int)d->pages.count() || !d->pages.at( i ) ) )
320     i--;
321   if ( d->pages.at( i ) ) {
322     if ( d->current && !acceptData( d->current->t ) )
323       return;
324     showPage( d->pages.at( i )->w );
325   }
326 }
327
328
329 /*!
330 \fn void InstallWizard::helpClicked()
331
332   This signal is emitted when the user clicks on the Help button.
333 */
334
335 /*!  Called when the user clicks the Help button, this function emits the
336 helpClicked() signal.
337 */
338
339 void InstallWizard::help()
340 {
341   QWidget * page = d->ws->visibleWidget();
342   if ( !page )
343     return;
344   
345 #if 0
346   if ( page->inherits( "InstallWizardPage" ) )
347     emit ((InstallWizardPage *)page)->helpClicked();
348 #endif
349   emit helpClicked();
350 }
351
352
353 void InstallWizard::setBackEnabled( bool enable )
354 {
355   d->backButton->setEnabled( enable );
356 #ifndef QT_NO_ACCEL
357   d->accel->setItemEnabled( d->backAccel, enable );
358 #endif
359 }
360
361
362 void InstallWizard::setNextEnabled( bool enable )
363 {
364   d->nextButton->setEnabled( enable );
365 #ifndef QT_NO_ACCEL
366   d->accel->setItemEnabled( d->nextAccel, enable );
367 #endif
368 }
369
370
371 void InstallWizard::setHelpEnabled( bool enable )
372 {
373   d->helpButton->setEnabled( enable );
374 }
375
376
377 /*!
378 \fn void InstallWizard::setFinish( QWidget *, bool )
379 \obsolete
380
381   Use setFinishEnabled instead
382 */
383
384 /*!
385 If \a enable is TRUE, page \a page has a Back button; otherwise \a
386 page has no Back button.
387 By default all pages have this button.
388 */
389 void InstallWizard::setBackEnabled( QWidget * page, bool enable )
390 {
391   InstallWizardPrivate::Page * p = d->page( page );
392   if ( !p )
393     return;
394   
395   p->backEnabled = enable;
396   updateButtons();
397 }
398
399
400 /*!
401 If \a enable is TRUE, page \a page has a Next button; otherwise
402 the Next button on \a page is disabled. By default all pages have
403 this button.
404 */
405
406 void InstallWizard::setNextEnabled( QWidget * page, bool enable )
407 {
408   InstallWizardPrivate::Page * p = d->page( page );
409   if ( !p )
410     return;
411   
412   p->nextEnabled = enable;
413   updateButtons();
414 }
415
416
417 /*!
418 If \a enable is TRUE, page \a page has a Finish button; otherwise \a
419 page has no Finish button.
420 By default \e no page has this button.
421 */
422 void InstallWizard::setFinishEnabled( QWidget * page, bool enable )
423 {
424   InstallWizardPrivate::Page * p = d->page( page );
425   if ( !p )
426     return;
427   
428   p->finishEnabled = enable;
429   updateButtons();
430 }
431
432
433 /*!
434 If \a enable is TRUE, page \a page has a Help button; otherwise \a
435 page has no Help button.
436 By default all pages have this button.
437 */
438 void InstallWizard::setHelpEnabled( QWidget * page, bool enable )
439 {
440   InstallWizardPrivate::Page * p = d->page( page );
441   if ( !p )
442     return;
443   
444   p->helpEnabled = enable;
445   updateButtons();
446 }
447
448
449 /*!
450 Called when the Next button is clicked; this virtual function
451 returns TRUE if \a page is relevant for display in the current
452 context; otherwise it is ignored by InstallWizard and returns FALSE. The
453 default implementation returns the value set using
454 setAppropriate(). The ultimate default is TRUE.
455
456   \warning The last page of the wizard will be displayed if no page is relevant
457   in the current context.
458 */
459
460 bool InstallWizard::appropriate( QWidget * page ) const
461 {
462   InstallWizardPrivate::Page * p = d->page( page );
463   return p ? p->appropriate : TRUE;
464 }
465
466
467 /*!
468 If \a appropriate is TRUE then page \a page is considered relevant
469 in the current context and should be displayed in the page sequence;
470 otherwise \a page should not be displayed in the page sequence.
471
472   \sa appropriate()
473 */
474 void InstallWizard::setAppropriate( QWidget * page, bool appropriate )
475 {
476   InstallWizardPrivate::Page * p = d->page( page );
477   if ( p )
478     p->appropriate = appropriate;
479 }
480
481
482 void InstallWizard::updateButtons()
483 {
484   if ( !d->current )
485     return;
486   
487   int i;
488   for( i = 0; i < (int)d->pages.count() && d->pages.at( i ) != d->current; i++ );
489   bool notFirst( FALSE );
490   if( i ) {
491     i--;
492     while( ( i >= 0 ) && !notFirst ) {
493       notFirst |= appropriate( d->pages.at( i )->w );
494       i--;
495     }
496   }
497   setBackEnabled( d->current->backEnabled && notFirst );
498   setNextEnabled( d->current->nextEnabled );
499   d->finishButton->setEnabled( d->current->finishEnabled );
500   d->helpButton->setEnabled( d->current->helpEnabled );
501   
502   if ( ( d->current->finishEnabled && !d->finishButton->isVisible() ) ||
503     ( d->current->backEnabled && !d->backButton->isVisible() ) ||
504     ( d->current->nextEnabled && !d->nextButton->isVisible() ) ||
505     ( d->current->helpEnabled && !d->helpButton->isVisible() ) )
506     layOut();
507 }
508
509
510 /*!  Returns a pointer to the current page in the sequence.
511 Although the wizard does its best to make sure that this value is
512 never 0, it can be if you try hard enough.
513 */
514
515 QWidget * InstallWizard::currentPage() const
516 {
517   return d->ws->visibleWidget();
518 }
519
520
521 /*!  Returns the title of page \a page.
522 */
523
524 QString InstallWizard::title( QWidget * page ) const
525 {
526   InstallWizardPrivate::Page * p = d->page( page );
527   return p ? p->t : QString::null;
528 }
529
530 /*!  Sets the title for page \a page to \a title.
531 */
532
533 void InstallWizard::setTitle( QWidget *page, const QString &title )
534 {
535   InstallWizardPrivate::Page * p = d->page( page );
536   if ( p )
537     p->t = title;
538   if ( page == currentPage() )
539     d->title->setText( title );
540 }
541
542 /*!
543 \property InstallWizard::titleFont
544 \brief the font used for page titles
545
546   The default is QApplication::font().
547 */
548 QFont InstallWizard::titleFont() const
549 {
550   return d->title->font();
551 }
552
553 void InstallWizard::setTitleFont( const QFont & font )
554 {
555   d->title->setFont( font );
556 }
557
558
559 /*!
560 Returns a pointer to the dialog's Back button
561
562   By default, this button is connected to the back() slot,
563   which is virtual so you can reimplement it in a InstallWizard subclass.
564 */
565 QPushButton * InstallWizard::backButton() const
566 {
567   return d->backButton;
568 }
569
570
571 /*!
572 Returns a pointer to the dialog's Next button
573
574   By default, this button is connected to the next() slot,
575   which is virtual so you can reimplement it in a InstallWizard subclass.
576 */
577 QPushButton * InstallWizard::nextButton() const
578 {
579   return d->nextButton;
580 }
581
582
583 /*!
584 Returns a pointer to the dialog's Finish button
585
586   By default, this button is connected to the QDialog::accept() slot,
587   which is virtual so you can reimplement it in a InstallWizard subclass.
588 */
589 QPushButton * InstallWizard::finishButton() const
590 {
591   return d->finishButton;
592 }
593
594
595 /*!
596 Returns a pointer to the dialog's Cancel button
597
598   By default, this button is connected to the QDialog::reject() slot,
599   which is virtual so you can reimplement it in a InstallWizard subclass.
600 */
601 QPushButton * InstallWizard::cancelButton() const
602 {
603   return d->cancelButton;
604 }
605
606
607 /*!
608 Returns a pointer to the dialog's Help button
609
610   By default, this button is connected to the help() slot,
611   which is virtual so you can reimplement it in a InstallWizard subclass.
612 */
613 QPushButton * InstallWizard::helpButton() const
614 {
615   return d->helpButton;
616 }
617
618
619 /*!  This virtual function is responsible for adding the bottom
620 divider and the buttons below it.
621
622   \a layout is the vertical layout of the entire wizard.
623 */
624
625 void InstallWizard::layOutButtonRow( QHBoxLayout * layout )
626 {
627   bool hasHelp = FALSE;
628   bool hasEarlyFinish = FALSE;
629   
630   int i = d->pages.count() - 2;
631   while ( !hasEarlyFinish && i >= 0 ) {
632     if ( d->pages.at( i ) && d->pages.at( i )->finishEnabled )
633       hasEarlyFinish = TRUE;
634     i--;
635   }
636   i = 0;
637   while ( !hasHelp && i < (int)d->pages.count() ) {
638     if ( d->pages.at( i ) && d->pages.at( i )->helpEnabled )
639       hasHelp = TRUE;
640     i++;
641   }
642   
643   QBoxLayout * h = new QBoxLayout( QBoxLayout::LeftToRight );
644   layout->addLayout( h );
645   
646   h->addWidget( d->cancelButton );
647   
648   h->addStretch( 42 );
649   
650   h->addWidget( d->backButton );
651   
652   h->addSpacing( 6 );
653   
654   if ( hasEarlyFinish ) {
655     d->nextButton->show();
656     d->finishButton->show();
657     h->addWidget( d->nextButton );
658     h->addSpacing( 12 );
659     h->addWidget( d->finishButton );
660   } else if ( d->pages.count() == 0 ||
661     d->current->finishEnabled ||
662     d->current == d->pages.at( d->pages.count()-1 ) ) {
663     d->nextButton->hide();
664     d->finishButton->show();
665     h->addWidget( d->finishButton );
666   } else {
667     d->nextButton->show();
668     d->finishButton->hide();
669     h->addWidget( d->nextButton );
670   }
671   
672   // if last page is disabled - show finished btn. at lastpage-1
673   i = d->pages.count()-1;
674   if ( i >= 0 && !appropriate( d->pages.at( i )->w ) &&
675     d->current == d->pages.at( d->pages.count()-2 ) ) {
676     d->nextButton->hide();
677     d->finishButton->show();
678     h->addWidget( d->finishButton );
679   }
680   
681   if ( hasHelp ) {
682     h->addSpacing( 12 );
683     h->addWidget( d->helpButton );
684   } else {
685     d->helpButton->hide();
686   }
687 }
688
689
690 /*!
691 This virtual function is responsible for laying out the title row
692 and adding the vertical divider between the title and the wizard
693 page. \a layout is the vertical layout for the wizard, and \a
694 title is the title for this page. This function is called every
695 time \a title changes.
696 */
697
698 void InstallWizard::layOutTitleRow( QHBoxLayout * layout, const QString & title )
699 {
700   d->title->setText( title );
701   layout->addWidget( d->title, 10 );
702 }
703
704 /*!
705 Validates page when 'Next' or 'Finish' button is clicked.
706 Should return true in success
707 */
708 bool InstallWizard::acceptData( const QString& )
709 {
710   return TRUE;
711 }
712
713 /*
714
715 */
716
717 void InstallWizard::layOut()
718 {
719   delete d->v;
720   d->v = new QVBoxLayout( this, 11, 0, "top-level layout" );
721   
722   QHBoxLayout * l;
723   l = new QHBoxLayout( 6 );
724   d->v->addLayout( l, 0 );
725   layOutTitleRow( l, d->current ? d->current->t : QString::null );
726   
727   if ( ! d->hbar1 ) {
728     d->hbar1 = new QFrame( this, "<hr>", 0 );
729     d->hbar1->setFrameStyle( QFrame::Sunken + QFrame::HLine );
730     d->hbar1->setFixedHeight( 12 );
731   }
732   
733   d->v->addWidget( d->hbar1 );
734   
735   d->v->addWidget( d->ws, 10 );
736   
737   if ( ! d->hbar2 ) {
738     d->hbar2 = new QFrame( this, "<hr>", 0 );
739     d->hbar2->setFrameStyle( QFrame::Sunken + QFrame::HLine );
740     d->hbar2->setFixedHeight( 12 );
741   }
742   d->v->addWidget( d->hbar2 );
743   
744   l = new QHBoxLayout( 6 );
745   d->v->addLayout( l );
746   layOutButtonRow( l );
747   d->v->activate();
748 }
749
750
751 /*! \reimp */
752
753 bool InstallWizard::eventFilter( QObject * o, QEvent * e )
754 {
755   if ( o == d->ws && e && e->type() == QEvent::ChildRemoved ) {
756     QChildEvent * c = (QChildEvent*)e;
757     if ( c->child() && c->child()->isWidgetType() )
758       removePage( (QWidget *)c->child() );
759   }
760   return QDialog::eventFilter( o, e );
761 }
762
763
764 /*!
765 Removes \a page from the page sequence but does not delete the page.
766 If \a page is currently being displayed, InstallWizard will display the
767 page that precedes it, or the first page if this was the first page.
768 */
769
770 void InstallWizard::removePage( QWidget * page )
771 {
772   if ( !page )
773     return;
774   
775   int i = d->pages.count();
776   QWidget* cp = currentPage();
777   while( --i >= 0 && d->pages.at( i ) && d->pages.at( i )->w != page ) { }
778   if ( i < 0 )
779     return;
780   InstallWizardPrivate::Page * p = d->pages.at( i );
781   d->pages.removeRef( p );
782   d->ws->removeWidget( page );
783   
784   if( cp == page ) {
785     i--;
786     if( i < 0 )
787       i = 0;
788     if ( pageCount() > 0 )
789       showPage( InstallWizard::page( i ) );
790   }
791 }
792
793
794 /*!
795 Returns a pointer to the page at position \a index in the sequence,
796 or 0 if \a index is out of range. The first page has index 0.
797 */
798
799 QWidget* InstallWizard::page( int index ) const
800 {
801   if ( index >= pageCount() || index < 0 )
802     return 0;
803   
804   return d->pages.at( index )->w;
805 }
806
807 /*!
808 Returns a pointer to the page with a title \a title in the sequence,
809 or 0 if not found.
810 */
811 QWidget* InstallWizard::page( const QString& title ) const
812 {
813   for( int i = 0; i < (int)d->pages.count(); i++ ) {
814     if ( d->pages.at( i )->t == title )
815       return d->pages.at( i )->w;
816   }
817   return 0;
818 }