Pagina principala
Informatii
Internationalizarea
pROgrame KDE/Qt
Download
Documentatii
Despre LKR
Contact
Resurse
Harta site-ului

Programarea setarilor utilizator in KDE 3


   Andreas Nicolai
   18 Mai 2003

Partea a-VIII-a: Implementarea butoanelor "Implicit" (Default) si "Seteaza" (Apply)

Acum ne trebuie un loc central unde sa definim valorile implicite. Altfel, trebuie sa le setam de doua ori - in functia read() a obiectului de configurare si in dialogul de preferinte cind utilizatorul apasa "Implicit". Evident cel mai bun loc este chiar obiectul de configurare. Asa ca vom crea varabile statice pentru valorile inplicite, cum arata urmatoarele 3 linii pe care le-am adaugat in configuration.h:

///< Textul implicit de tiparit
static const QString m_defaultText;
///< Fontul implicit pentru text
static const QFont   m_defaultFont;
///< Culoarea implicita a textului
static const QColor  m_defaultTextColor;

Initializarea o vom face in fisierul sursa configuration.cpp. Variabilele temporare din functia read() nu mai sint necesare deoarece putem sa le inlocuim cu variabilele statice. Noul fisier configuration.cpp arata asa:


#include <kapplication.h>     // pentru 'kapp'
#include <kconfig.h>          // pentru KConfig
#include <klocale.h>          // pentru i18n()

#include "configuration.h"

const QString Configuration::m_defaultText = i18n("Hello World");
const QFont   Configuration::m_defaultFont = QFont("Helvetica");
const QColor  Configuration::m_defaultTextColor = Qt::black;

Configuration::Configuration() {
  read(); // read the settings or set them to the default values
};

void Configuration::read() {
  KConfig *conf=kapp->config();
  // read general options
  conf->setGroup("General");
  m_text = conf->readEntry("text", m_defaultText );
  // read style options
  conf->setGroup("Style");
  m_font = conf->readFontEntry("font", &m_defaultFont );
  m_textColor = conf->readColorEntry("textColor", &m_defaultTextColor);
};

void Configuration::write() const {
  KConfig *conf=kapp->config();
  // write general options
  conf->setGroup("General");
  conf->writeEntry("text", m_text);
  // write style options
  conf->setGroup("Style");
  conf->writeEntry("font", m_font);
  conf->writeEntry("textColor", m_textColor);
};

Configuration& Config() {
    static Configuration conf;
    return conf;
};
configuration.cpp

Daca mai tirziu doriti sa schimbati valorile implicite, trebuie doar sa modificati valorile cu care sint initializate variabilele la inceputul fisierului.

Este nevoie sa modificam inca o data dialogul de preferinte. Ne trebuie o functie care sa seteze toate widget-urile din dialog la valorile implicite cind utilizatorul va apasa butonul "Implicit". Au fost citeva contradictii pe lista de discutii kde-devel despre cum ar trebui sa functioneze un buton implicit. Marea intrebare a fost: Butonul implicit ar trebui sa reseteze valorile in pagina de configurare curenta sau ar trebui sa reseteze toate setarile? Nu s-a ajuns la un consens, dar s-a dat totusi o recomandare.

Nota:
Resetarea se poate face pe toate paginile doar cu confirmarea utilizatorului. Daca utilizatorul nu vrea sa reseteze toate paginile, cel putin este prevenit.

Dupa parerea mea acesta este modul corect deoarece butonul implicit nu este parte din nici o pagina de configurare, ci apartine intregului dialog.

KDialogBase are o proprietate draguta. Ori de cite ori alegem sa avem un buton implicit in pagina (specificind butonul in constructor), putem sa reimplementam slotul slotDefault() si aceasta functie va fi apelata cind butonul "Implicit" este apasat. Este exact ce vom face si noi. Adaugati aceste linii in fisierul prefdialog.h:

  public slots:
    void slotDefault();

si aceasta va fi implementarea functiei in prefdialog.cpp:


void PrefDialog::slotDefault() {
  if (KMessageBox::warningContinueCancel(this,
          i18n("This will set the default options "
          "in ALL pages of the preferences dialog! Continue?"),
          i18n("Set default options?"),
          i18n("Set defaults"))==KMessageBox::Continue)
  {
    m_prefGeneral->m_textEdit->setText(Config().m_defaultText);
    m_prefStyle->m_colorBtn->setColor(Config().m_defaultTextColor);
    m_prefStyle->m_fontLabel->setFont(Config().m_defaultFont);
  }
};
prefdialog.cpp

Functia KMessageBox::warningContinueCancel() deschide inca un dialog de-a gata al librariei KDE. Aruncati o privire asupra clasei KMessageBox pentru a vedea cite tipuri de dialoguri exista. Pentru dialogul de avertizare trebuie sa dam patru argumente:

  1. un pointer la widget-ul parinte (this -> dialogul de preferinte)
  2. mesajul pentru utilizator (nu uitati sa folositi metoda i18n())
  3. titlul dialogului
  4. textul care va fi afisat in butonul "Continua" (Continue)

Cind utilizatorul va apasa butonul "Continua", dialogul intoarce KMessageBox::Continue dupa care vom transfera valorile implicite din obiectul de configurare in widget-uri. Simplu si robust! Cam asta a fost cu butonul implicit.

Dupa cum am mai spus, butonul "Seteaza" este mai special. Nu din cauza implementarii, ci pentru ca au fost ceva discutii despre cum ar trebui sa se comporte un buton "Seteaza". Asta a fost intelegerea la care au ajuns cei mai multi:

Nota:
La afisarea dialogului, butonul "Seteaza" (Apply) ar trebui sa fie implicit dezactivat. Atunci cind utilizatorul schimba o setare, butonul "Seteaza" trebuie activat. Cind dam clic pe butonul "Seteaza", setarile ar trebui transferate intr-un obiect de configurare si modificarile ar trebui aplicate imediat (daca este posibil). Dupa aceea butonul ar trebui dezactivat din nou. In acest mod utilizatorul va recunoaste dupa starea butonului daca a facut sau nu vreo modificare a setarilor.

Oricum, au fost discutii si in ceea ce priveste aplicarea imediata a setarilor, dar din experienta mea majoritatea setarilor pot fi aplicate imediat. Asa vom face si in tutorialul nostru.

Cum vom realiza activarea/dezactivarea butonului? Este foarte simplu: vom dezactiva butonul ori de cite ori transferam setarile din sau in dialog (deoarece in ambele cazuri butonul va fi setat ca dezactivat (disabled). Avem nevoie de un slot public pentru a activa butonul si in cele din urma il conectam la toate semnalele widget-urilor interactive sau la semnalele pe care le vom emite chiar noi. Putem activa si dezactiva butonul "Seteaza" prin functia membru enabledButtonApply(bool) din clasa KDialogBase. Daca trimitem ca parametru true, butonul va fi activat, altfel nu. Vom avea de facut urmatoarele operatii:

  1. sa dezactivam butonul seteaza in functiile membru updateDialog() si updateConfiguration()
  2. sa cream un slot public enableApply()
  3. sa conectam toate widget-urile interactive la acest slot

Ne trebuie ceva special pentru eticheta care afiseaza fontul curent. Deoarece QLabel nu este un widget interactiv, nu are nici un semnal la care sa ne putem conecta. Dar avem slotul chooseButtonClicked() in clasa PrefStyle. Putem sa emitem acolo un semnal "fontChanged", (adica s-a modificat fontul) la care conectam slotul enableApply(). Aceasta este noua si ultima versiune a fisierului prefstyle.h:


#ifndef PREFSTYLE_H
#define PREFSTYLE_H

#include <qwidget.h>
#include <prefstylelayout.h>

/// Implementarea paginii "Style settings" din
/// dialogul de preferinte.
class PrefStyle : public PrefStyleLayout {
    Q_OBJECT
  public:
    /// Constructorul.
    PrefStyle(QWidget *parent, const char *name=0, WFlags f=0);

  private slots:
    /// Apelata cind se apasa butonul "Choose..."
    void chooseBtnClicked();

  signals:
    /// Va fi emis atunci cind este ales un font diferit
    void fontChanged();
};

#endif  // PREFSTYLE_H
prefstyle.h

si prefstyle.cpp:


#include <qfont.h>          // pentru QFont
#include <qlabel.h>         // pentru QLabel
#include <kpushbutton.h>    // pentru KPushButton
#include <kfontdialog.h>    // pentru KFontDialog

#include "prefstyle.h"
#include "prefstyle.moc"

PrefStyle::PrefStyle(QWidget *parent, const char *name, WFlags f)
 : PrefStyleLayout(parent, name, f)
{
  connect(m_fontBtn, SIGNAL(clicked()),
  this, SLOT(chooseBtnClicked()));
}

void PrefStyle::chooseBtnClicked() {
  QFont tmpFont = m_fontLabel->font();
  int result = KFontDialog::getFont(tmpFont);
  if (result==KFontDialog::Accepted) {
     m_fontLabel->setFont(tmpFont);
     emit fontChanged();
  };
};
prefstyle.cpp

Schimbarile sint minime dar observati cum se declara si cum se emit semnalele (penultima linie de cod a functiei chooseBtnClicked()).

Avem un semnal la care se poate conecta un slot asa ca haideti sa terminam dialogul de preferinte:


#ifndef PREFDIALOG_H
#define PREFDIALOG_H

#include <qwidget.h>
#include <kdialogbase.h>

class PrefGeneral;
class PrefStyle;

/// Dialogul de preferinte.
class PrefDialog : public KDialogBase {
    Q_OBJECT
  public:
    /// Constructorul
    PrefDialog(QWidget *parent, const char *name=0, WFlags f=0);

    /// Transfera setarile din obiectul de configurare
    /// in dialog
    void updateDialog();
    /// Transfera setarile din dialog
    /// in obiectul de configurare
    void updateConfiguration();

  public slots:
    /// Va fi apelat cind este apasat butonul "Default"
    void slotDefault();
    /// Va fi apelat cind este apasat butonul "Apply"
    void slotApply();
    /// Va fi apelat oricind este modificata
    /// o setare
    void enableApply();

  signals:
    /// Va fi emis cind trebuie ca noile
    /// setari sa fie aplicate
    void settingsChanged();

  private:
    PrefGeneral    *m_prefGeneral;
    PrefStyle      *m_prefStyle;
};

#endif  // PREFDIALOG_H
prefdialog.h

Dupa cum ati observat nu am adaugat doar slotul enableApply()! Slotul slotApply() lucreaza la fel ca slotDefault(). El va fi apelat este apasat cind butonul "Seteaza". In cele din urma am adaugat si semnalul settingsChanged(). Acesta ne permite sa anuntam restul programului ca s-a facut o modificare a setarilor si trebuie aplicate imediat.

Aici aveti sursa finala a dialogului:


#include <qlayout.h>        // pentru QVBoxLayout
#include <qlabel.h>         // pentru QLabel
#include <qframe.h>         // pentru QFrame
#include <kcolorbutton.h>   // pentru KColorButton
#include <kpushbutton.h>    // pentru KPushButton
#include <klocale.h>        // pentru i18n()
#include <kiconloader.h>    // pentru KIconLoader
#include <kglobal.h>        // pentru KGlobal
#include <klineedit.h>      // pentru KLineEdit
#include <kmessagebox.h>    // pentru KMessageBox

#include "prefdialog.h"     // clasa PrefDialog
#include "prefdialog.moc"

#include "configuration.h"  // clasa Configuration si Config()
#include "prefgeneral.h"    // clasa PrefGeneral
#include "prefstyle.h"      // clasa PrefStyle

PrefDialog::PrefDialog(QWidget *parent, const char *name, WFlags f)
 : KDialogBase(IconList, i18n("Preferences"),
   Default|Ok|Apply|Cancel, Ok, parent, name, f)
{
  // adaugam pagina "General options"
  QFrame *frame = addPage(i18n("General"), i18n("General options"),
      KGlobal::iconLoader()->loadIcon("kfm",KIcon::Panel,0,false));
  QVBoxLayout *frameLayout = new QVBoxLayout(frame, 0, 0);
  m_prefGeneral = new PrefGeneral(frame);
  frameLayout->addWidget(m_prefGeneral);

  // adaugam pagina "Style settings"
  frame = addPage(i18n("Style"), i18n("Style settings"),
      KGlobal::iconLoader()->loadIcon("style",KIcon::Panel,0,false));
  frameLayout = new QVBoxLayout(frame, 0, 0);
  m_prefStyle = new PrefStyle(frame);
  frameLayout->addWidget(m_prefStyle);

  // conectam widget-urile interactive si semnnalele proprii
  // la sloturile enableApply() si slotDefault()
  connect(m_prefGeneral->m_textEdit,
          SIGNAL(textChanged(const QString&)),
          this, SLOT(enableApply()));
  connect(m_prefStyle->m_colorBtn, SIGNAL(changed(const QColor&)),
          this, SLOT(enableApply()));
  connect(m_prefStyle, SIGNAL(fontChanged()),
          this, SLOT(enableApply()));
};

void PrefDialog::updateDialog() {
  m_prefGeneral->m_textEdit->setText(Config().m_text);
  m_prefStyle->m_colorBtn->setColor(Config().m_textColor);
  m_prefStyle->m_fontLabel->setFont(Config().m_font);
  enableButtonApply(false);   // dezactivam butonul de setare
};

void PrefDialog::updateConfiguration() {
  Config().m_text         = m_prefGeneral->m_textEdit->text();
  Config().m_textColor    = m_prefStyle->m_colorBtn->color();
  Config().m_font         = m_prefStyle->m_fontLabel->font();
  enableButtonApply(false);   // dezactivam butonul de setare
};

void PrefDialog::slotDefault() {
  if (KMessageBox::warningContinueCancel(this,
      i18n("This will set the default options "
      "in ALL pages of the preferences dialog! Continue?"),
      i18n("Set default options?"),
      i18n("Set defaults"))==KMessageBox::Continue)
  {
    m_prefGeneral->m_textEdit->setText(Config().m_defaultText);
    m_prefStyle->m_colorBtn->setColor(Config().m_defaultTextColor);
    m_prefStyle->m_fontLabel->setFont(Config().m_defaultFont);
    enableApply();   // activam butonul de setare
  };
};

void PrefDialog::slotApply() {
  // transferam setarile in obiectul de configurare
  updateConfiguration();
  // aplicam preferintele
  emit settingsChanged();
  // dezactivam din nou butonul "Seteaza"
  enableButtonApply(false);
};

void PrefDialog::enableApply() {
  enableButtonApply(true);   // activam butonul de setare
};
prefdialog.cpp

Inainte de a citi mai departe, incercati sa vedeti singur ce modificari am facut. Iata care sint. La inceput am conectat semnalele widget-urilor la slotul enableApply(). Observati asta in prima linie de conectare

connect(m_prefGeneral->m_textEdit, SIGNAL(textChanged(const QString&)),
        this, SLOT(enableApply()));

Conectez semnalul emis de widget-ul KLineEdit. In a treia linie de conectare:

connect(m_prefStyle, SIGNAL(fontChanged()),
        this, SLOT(enableApply()) );

conectez semnalul emis de pagina de configurare.

Urmatoarea schimbare am facut-o in functia membru updateDialog(). Butonul "Seteaza" va fi dezactivat de fiecare data cind este apelata aceasta functie. Acelasi lucru se intimpla cind obiectul de configurare este actualizat, deoarece atunci sint aplicate schimbarile. Asa ca adaugam aceasta linie si in functia updateConfiguration().

Mai exista o mica modificare si in functia slotDefault(). Resetarea configuratiei ar trebui sa activeze butonul "Seteaza".

In cele din urma sint implementate noile sloturi. Comentariile din sursa ar trebui sa fie suficiente. Retineti ca am emis semnalul settingsChanged() dupa ce am transferat datele in obiectul de configurare. Bineinteles este necesar sa facem transferul prima data pentru ca toate widget-urile si clasele programului isi vor lua valorile din obiectul de configurare.

Mai trebuie sa captam semnalul settingsChanged(), altfel nu vom vedea schimbarile imediat. Unde trebuie conectat semnalul emis de un dialog? In clasa unde este creat dialogul. Aceasta este ultima schimbare facuta in fisierul settingstutorial.cpp:


void SettingsTutorial::executePreferencesDlg() {
  if (m_prefDialog==0) {
    // cream dialogul la cerere
    m_prefDialog=new PrefDialog(this);
    // conectam semnalul "settingsChanged"
    connect(m_prefDialog, SIGNAL(settingsChanged()),
            this, SLOT(applyPreferences()));
  }
  // actualizeaza widget-urile dialogului
  m_prefDialog->updateDialog();
  // executam dialogul
  if (m_prefDialog->exec()==QDialog::Accepted) {
    // salvam setarile in obiectul de configurare
    m_prefDialog->updateConfiguration();
    // aplicam modificarile
    applyPreferences();
  };
};
settingstutorial.cpp

Conectam semnalul emis de dialogul de preferinte la slotul applyPreferences() si cu aceasta schimbare, in sfirsit, am terminat acest tutorial.

Aici veti gasi ultimele surse: settingstutorial-final.tar.gz

Aruncati o privire in subdirectorul html. Acolo veti gasi documentatia API generata de doxygen.

Pentru cei care nu au citit tot tutorialul, iata instructiunile de compilare si executare a programului. Extrageti fisierele intr-un director, deschideti proiectul cu Gideon (KDevelop 3). Selectati meniul "Build -> Run automake & friends", iar apoi "Build -> Run Configure". Dupa aceea compilati ("Build -> Build project") si rulati programul ("Build -> Execute Program"). Alternativ, intrati in directorul proiectului si executati urmatoarele comenzi:

make -f Makefile.cvs
./configure
make

Acestea ar trebui sa creeze executabilul in subdirectorul src.



Traducere de Bogdan Daniel Vatra. Adaptare de Claudiu Costin.