8.6. A Larger Example: The Option Dialog in KEdit

This section describes a real, and quite big, dialog—the Option dialog of KEdit. It is based on the KDialogBase class used in IconList mode. Figure 8.5 shows what this dialog looks like. This way of representing data is effective when you want to keep the number of dialog boxes to a minimum.

It may seem complicated to make such a dialog, but in fact it is very easy. As a bonus, it is also easy to add or remove a page without interfering with the rest of the code. Listing 8.13 shows how the dialog code is partitioned. A good technique is to deal with each page of the dialog in a separate function or even as a separate class. The first approach is used here. This way, you keep the code clean and easy to understand. The setupColorPage() method starting on Line 105 creates the color page. The method KdialogBase::addPage(…) prepares the page and returns the top level widget of the page. An icon list layout is selected by specifying IconList in the KDialogBase constructor. If you change the flag to Tabbed or IconList, the dialog will switch to a tabbed or a tree list shaped dialog respectively. This feature is especially useful when you make a dialog in Tabbed mode, but after a while realize that too many tabs are present in the dialog (the Qt library does not support multiple rows of tabs). You can then easily switch to a TreeList or IconList layout by changing only the flag. The KDialogBase class will take care of the rest; no redesign is required.


Figure 8.5. The KEdit Option dialog. The dialog is based on the KDialogBase class in IconList mode.



Example 8.13. The KEdit Dialog Code, Somewhat Simplified

   1 
   2   1:
   3   2: class COptionDialog : public KDialogBase
   4   3: {
   5   4:   Q_OBJECT
   6   5:
   7   6:   public:
   8   7:     enum Page
   9   8:     {
  10   9:       page_font = 0,
  11  10:       page_color,
  12  11:       page_spell,
  13  12:       page_misc,
  14  13:       page_max
  15  14:     };
  16  15:
  17  16:     COptionDialog( QWidget *parent = 0, char *name = 0, bool modal = false );
  18  17:     ~COptionDialog();
  19  18:
  20  19:     void setFont( const SFontState &font );
  21  20:     void setColor( const SColorState &color );
  22  21:     void setSpell( const SSpellState &spell );
  23  22:     void setMisc( const SMiscState &misc );
  24  23:     void setState( const SOptionState &state );
  25  24:
  26  25:   public slots:
  27  26:
  28  27:     virtual void slotDefault();
  29  28:     virtual void slotOk();
  30  29:     virtual void slotApply();
  31  30:
  32  31:   private:
  33  32:     struct SFontWidgets
  34  33:     {
  35  34:       KFontChooser *chooser;
  36  35:     };
  37  36:
  38  37:     struct SColorWidgets
  39  38:     {
  40  39:       KColorButton *fgColor;
  41  40:       KColorButton *bgColor;
  42  41:     };
  43  42:
  44  43:     struct SSpellWidgets
  45  44:     {
  46  45:       KSpellConfig *config;
  47  46:     };
  48  47:
  49  48:     struct SMiscWidgets
  50  49:     {
  51  50:       QComboBox *wrapCombo;
  52  51:       QLabel    *wrapLabel;
  53  52:       QLineEdit *wrapInput;
  54  53:       QCheckBox *backupCheck;
  55  54:       QLineEdit *mailInput;
  56  55:     };
  57  56:
  58  57:   private slots:
  59  58:     void wrapMode( int mode );
  60  59:
  61  60:   private:
  62  61:     void setupFontPage();
  63  62:     void setupColorPage();
  64  63:     void setupSpellPage();
  65  64:     void setupMiscPage();
  66  65:
  67  66:   signals:
  68  67:     void fontChoice( const SFontState &font );
  69  68:     void colorChoice( const SColorState &color );
  70  69:     void spellChoice( const SSpellState &spell );
  71  70:     void miscChoice( const SMiscState &misc );
  72  71:
  73  72:   private:
  74  73:     SOptionState   mState;
  75  74:     SColorWidgets  mColor;
  76  75:     SFontWidgets   mFont;
  77  76:     SSpellWidgets  mSpell;
  78  77:     SMiscWidgets   mMisc;
  79  78: };
  80  79:
  81  80:
  82  81:
  83  82:
  84  83:
  85  84: COptionDialog::COptionDialog( QWidget *parent, char *name, bool modal )
  86  85:   :KDialogBase( Iconist, i18n("Options"), Help|Default|Apply|Ok|Cancel,
  87  86:          Ok, parent, name, modal, true )
  88  87: {
  89  88:   setHelp( "kedit/index.html", QString::null ); // When Help is pressed
  90  89:
  91  90:   setupFontPage();
  92  91:   setupColorPage();
  93  92:   setupSpellPage();
  94  93:   setupMiscPage();
  95  94: }
  96  95:
  97  96: COptionDialog::~COptionDialog()
  98  97: {
  99  98: }
 100  99:
 101 100:
 102 101: void
 103 102: COptionDialog::setupFontPage()
 104 103: {
 105 104:   QVBox *page = addVBoxPage( i18n("Font"),
 106 105:     i18n("Editor font" ), UserIcon("fonts") );
 107 106:   mFont.chooser = new KFontChooser( page, "font", false, QStringList(),
 108 107:     false, 6 );
 109 108:   mFont.chooser->setSampleText( i18n("KEdit editor font") );
 110 109: }
 111 110:
 112 111:
 113 112: void
 114 113: COptionDialog::setupColorPage()
 115 114: {
 116 115:   QFrame *page = addPage( i18n("Color"), i18n("Text color in editor area"),
 117 116:     UserIcon("colors") );
 118 117:   QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
 119 118:   if( topLayout == 0 ) { return; }
 120 119:
 121 120:   QGridLayout *gbox = new QGridLayout( 2, 2 );
 122 121:   topLayout->addLayout(gbox);
 123 122:
 124 123:   QLabel *label;
 125 124:   mColor.fgColor = new KColorButton( page );
 126 125:   mColor.bgColor = new KColorButton( page );
 127 126:   label = new QLabel( mColor.fgColor, "Foreground color", page );
 128 127:   label = new QLabel( mColor.bgColor, "Background color", page );
 129 128:
 130 129:   gbox->addWidget( label, 0, 0 );
 131 130:   gbox->addWidget( label, 1, 0 );
 132 131:   gbox->addWidget( mColor.fgColor, 0, 1 );
 133 132:   gbox->addWidget( mColor.bgColor, 1, 1 );
 134 133:
 135 134:   topLayout->addStretch(10);
 136 135: }
 137 136:
 138 137:
 139 138: void
 140 139: COptionDialog::setupSpellPage()
 141 140: {
 142 141:   QFrame *page = addPage( i18n("Spelling"), i18n("Spell checker behaviour"),
 143 142:     SmallIcon("spell") );
 144 143:   QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
 145 144:   if( topLayout == 0 ) { return; }
 146 145:
 147 146:   mSpell.config = new KSpellConfig( page, "spell");
 148 147:   topLayout->addWidget( mSpell.config );
 149 148:
 150 149:   topLayout->addStretch(10);
 151 150: }
 152 151:
 153 152:
 154 153: void
 155 154: COptionDialog::setupMiscPage()
 156 155: {
 157 156:   QFrame *page = addPage( i18n("Miscellaneous"), i18n("Various properties"),
 158 157:     UserIcon("misc") );
 159 158:   QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
 160 159:   if( topLayout == 0 ) { return; }
 161 160:
 162 161:   QGridLayout *gbox = new QGridLayout( 5, 2 );
 163 162:   topLayout->addLayout( gbox );
 164 163:
 165 164:   QString text;
 166 165:
 167 166:   text = i18n("Word Wrap");
 168 167:   QLabel *label = new QLabel( text, page, "wraplabel" );
 169 168:   gbox->addWidget( label, 0, 0 );
 170 169:   QStringList wrapList;
 171 170:   wrapList.append( i18n("Disable wrapping") );
 172 171:   wrapList.append( i18n("Let editor width decide") );
 173 172:   wrapList.append( i18n("At specified column") );
 174 173:   mMisc.wrapCombo = new QComboBox( false, page );
 175 174:   connect(mMisc.wrapCombo,SIGNAL(activated(int)),this,SLOT (wrapMode(int)));
 176 175:   mMisc.wrapCombo->insertStringList( wrapList );
 177 176:   gbox->addWidget( mMisc.wrapCombo, 0, 1 );
 178 177:
 179 178:   text = i18n("Wrap Column");
 180 179:   mMisc.wrapLabel = new QLabel( text, page, "wrapcolumn" );
 181 180:   gbox->addWidget( mMisc.wrapLabel, 1, 0 );
 182 181:   mMisc.wrapInput = new QLineEdit( page, "values" );
 183 182:   mMisc.wrapInput->setMinimumWidth( fontMetrics().maxWidth()*10 );
 184 183:   gbox->addWidget( mMisc.wrapInput, 1, 1 );
 185 184:
 186 185:   gbox->addRowSpacing( 2, spacingHint()*2 ); 
 187 186:
 188 187:   text = i18n("Make backup when saving a file");
 189 188:   mMisc.backupCheck = new QCheckBox( text, page, "backup" );
 190 189:   gbox->addMultiCellWidget( mMisc.backupCheck, 3, 3, 0, 1 );
 191 190:
 192 191:   mMisc.mailInput = new QLineEdit( page, "mailcmd" );
 193 192:   mMisc.mailInput->setMinimumWidth(fontMetrics().maxWidth()*10);
 194 193:   text = i18n("Mail Command");
 195 194:   label = new QLabel( text, page,mailcmdlabel );
 196 195:   gbox->addWidget( label, 4, 0 );
 197 196:   gbox->addWidget( mMisc.mailInput, 4, 1 );
 198 197:
 199 198:   topLayout->addStretch(10);
 200 199: }
 201 200:
 202 201:
 203 202: void
 204 203: COptionDialog::wrapMode( int mode )
 205 204: {
 206 205:   bool state = mode == 2 ? true : false;
 207 206:   mMisc.wrapInput->setEnabled( state );
 208 207:   mMisc.wrapLabel->setEnabled( state );
 209 208: }
 210 209:
 211 210:
 212 211: void
 213 212: COptionDialog::slotOk()
 214 213: {
 215 214:   slotApply();
 216 215:   accept();
 217 216: }
 218 217:
 219 218:
 220 219: void
 221 220: COptionDialog::slotApply()
 222 221: {
 223 222:   switch( activePageIndex() )
 224 223:   {
 225 224:     case page_font:
 226 225:       mState.font.font = mFont.chooser->font();
 227 226:       emit fontChoice( mState.font );
 228 227:     break;
 229 228:
 230 229:     case page_color:
 231 230:       mState.color.textFg = mColor.fgColor->color();
 232 231:       mState.color.textBg = mColor.bgColor->color();
 233 232:       emit colorChoice( mState.color );
 234 233:     break;
 235 234:
 236 235:     case page_spell:
 237 236:       mState.spell.config = *mSpell.config;
 238 237:       emit spellChoice( mState.spell );
 239 238:     break;
 240 239:
 241 240:     case page_misc:
 242 241:       mState.misc.wrapMode    = mMisc.wrapCombo->currentItem();
 243 242:       mState.misc.backupCheck = mMisc.backupCheck->isChecked();
 244 243:       mState.misc.wrapColumn  = mMisc.wrapInput->text().toInt();
 245 244:       mState.misc.mailCommand = mMisc.mailInput->text();
 246 245:       emit miscChoice( mState.misc );
 247 246:     break;
 248 247:   }
 249 248: }
 250 249:
 251 250:
 252 251: void
 253 252: COptionDialog::slotDefault()
 254 253: {
 255 254:   //
 256 255:   // The constructors store the default settings.
 257 256:   //
 258 257:   switch( activePageIndex() )
 259 258:   {
 260 259:     case page_font:
 261 260:       setFont( SFontState() );
 262 261:     break;
 263 262:
 264 263:     case page_color:
 265 264:       setColor( SColorState() );
 266 265:     break;
 267 266:
 268 267:     case page_spell:
 269 268:       setSpell( SSpellState() );
 270 269:     break;
 271 270:
 272 271:     case page_misc:
 273 272:       setMisc( SMiscState() );
 274 273:     break;
 275 274:   }
 276 275: }
 277 276:
 278 277:
 279 278: void
 280 279: COptionDialog::setFont( const SFontState &font )
 281 280: {
 282 281:   mState.font = font;
 283 282:   mFont.chooser->setFont( font.font, false );
 284 283: }
 285 284:
 286 285:
 287 286: void
 288 287: COptionDialog::setColor( const SColorState &color )
 289 288: {
 290 289:   mState.color = color;
 291 290:   mColor.fgColor->setColor( color.textFg );
 292 291:   mColor.bgColor->setColor( color.textBg );
 293 292: }
 294 293:
 295 294:
 296 295: void
 297 296: COptionDialog::setSpell( const SSpellState &spell )
 298 297: {
 299 298:   *mSpell.config = spell.config;
 300 299: }
 301 300:
 302 301:
 303 302: void
 304 303: COptionDialog::setMisc( const SMiscState &misc )
 305 304: {
 306 305:   mState.misc = misc;
 307 306:   mMisc.wrapCombo->setCurrentItem( misc.wrapMode );
 308 307:   mMisc.wrapInput->setText( QString().setNum(misc.wrapColumn) );
 309 308:   mMisc.backupCheck->setChecked( misc.backupCheck );
 310 309:   mMisc.mailInput->setText( misc.mailCommand );
 311 310:   wrapMode( mMisc.wrapCombo->currentItem() );
 312 311: }
 313 312:
 314 313:
 315 314: void
 316 315: COptionDialog::setState( const SOptionState &state )
 317 316: {
 318 317:   setFont( state.font );
 319 318:   setColor( state.color );
 320 319:   setSpell( state.spell );
 321 320:   setMisc( state.misc );
 322 321: }
 323 322:
 324 323:
 325 324: // The dialog used as the main application window
 326 325: #include <kcmdlineargs.h>
 327 326: int main( int argc, char **argv )
 328 327: {
 329 328:   KCmdLineArgs::init(argc, argv, "kedit", 0, 0);
 330 329:   KApplication app;
 331 330:   COptionDialog *dialog = new COptionDialog():
 332 331:   dialog->show();
 333 332:   int result = app.exec();
 334 333:   return( result );
 335 334: }
 336 

In the constructor, the path to the Help file is set. If you have specified a Help button, you should make sure the help path is defined. If you do not specify a path, the PC bell will make a short alarm when the user activates the Help button. You can disable (gray out) any button by using the KDialogBase::enableButton(…) method. Notice how the spacingHint() function is used everywhere to get identical spacing as on line 117. You never have to worry again that the spacing use the same value as well.

The dialog has embedded a regular font selector widget in the Font page starting on line 101. This is the same widget that is used in the font selector dialog. By doing it this way, the user has one less dialog to remember while the familiar font selector interface remains intact but embedded into a larger dialog. The interface becomes easier to understand and remember.

The kind of dialog boxes that contain many settings should always include a Default button. When activated, this button resets the dialog settings of the displayed page to the default hard-coded settings. By having this kind of feature, you will get more satisfied users. Many people are reluctant to try the available options because they are afraid to "destroy" the program. By providing a way to reset any settings to a known state, this fear will often vanish.