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:
- un pointer la widget-ul parinte (this -> dialogul de preferinte)
- mesajul pentru utilizator (nu uitati sa folositi metoda i18n())
- titlul dialogului
- 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:
- sa dezactivam butonul seteaza in functiile membru updateDialog()
si updateConfiguration()
- sa cream un slot public enableApply()
- 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.
|