|
Puteti descarca o exemplele utilizate de aici:
kfifteenapplet-1.0.tar.gz (317 kO)
khelloworldapplet.tar.gz (314 kO)
Cuprins
Introducere
Descriere
O miniaplicatie de panou 'hello world'
Descrierea clasei HelloWorldApplet
Implementarea clasei HelloWorldApplet
Functia fabrica
Fisierul de descriere: helloworldapplet.desktop
Fisierul de intrare pentru automake: Makefile.am
Generare si instalare
Hello world!
Miniaplicatia de panou 'fifteen pieces'
Miniaplicatii cu marime variabila
Managementul sesiunii
Miniaplicatii 'strech'
Functia generala de tratare a unei actiuni
Actiuni de la tastatura
Proiectarea interfetei grafice cu utilizatorul
Sfaturi si trucuri
Introducere
Panoul KDE (mai jos il vom intilni si sub denumirea de
"kicker") pentru KDE 2.0 a fost scris de la zero. Unul din scopurile
principale ale rescrierii a fost marirea extensibilitatii. Din acest motiv,
incepind cu KDE 2.0, a fost introdus un API pentru miniaplicatiile
de panou, urmat in versiunea KDE 2.1 de un API pentru extinderea
functionalitatii lui. In urmatoarele paragrafe miniaplicatiile
de panou si extensiile sint numite simplu, module (plugin-uri).
Miniaplicatiile de panou sint, dupa cum sugereaza si numele, mici aplicatii ce
"traiesc" in interiorul panoului. Gama miniaplicatiilor
disponibile se intinde de la paginatoare de ecran si bare de procese,
pina la mici jocuri si jucarii. In contrast cu miniaplicatiile,
API-ul pentru extensia panoului este o interfata pentru extensii care
se afla in afara panoului, in zona de docare a managerului de ferestre
(asa cum este definit in
specificatiile managerului de ferestre de la freedesktop.org)
si care sint administrate de catre panou. Exemple de extensii de panou:
bara de procese externa si bara andocabila pentru aplicatii, ce adauga
panoului KDE (kicker) suport pentru miniaplicatii
Window Maker
si pentru alte aplicatii care folosesc mecanismul de andocare standard X11.
Incepind cu o scurta descriere a tehnologiei implicate,
aceasta lucrare practica discuta implementarea unei simple miniaplicatii
de panou. Miniaplicatiile si API-urile pentru extensii sint simple.
Acest lucru indica scrierea miniaplicatiilor de panou ca pe o sarcina
potrivita pentru o introducere in programarea KDE. Documentul de fata
presupune ca cititorul are cunostinte de baza de programare C++
si cunostinte despre Qt. Un indrumar de programare Qt gasiti la:
http://doc.trolltech.com/tutorial.html.
Descriere
Modulele de panou sint implementate ca
DSO-uri - obiecte partajate dinamic.
Panoul localizeaza miniaplicatiile disponibile cautind fisierele de
descriere ale acestora in (ALL_KDEDIRS)/share/apps/kicker/applets.
Fiecare modul trebuie sa instaleze un fisier de descriere pentru a
fi recunoscut de catre panou. Extensiile panoului sint gasite cautind
fisiere de descriere similare in
(ALL_KDEDIRS)/share/apps/kicker/extensions.
Implementarea unei miniaplicatii de panou
este la fel de usoara ca si mostenirea clasei de baza KPanelApplet
(declarata in kdelibs/kdeui/kpanelapplet.h),
furnizarea unei functii fabrica (factory) si instalarea
fisierului de descriere mentionat mai sus.
Pentru extensii clasa de baza este KPanelExtension,
declarata in kdelibs/kdeui/kpanelextension.h.
Daca modulele sint implementate
ca biblioteci partajate, fiind incarcate in spatiul de adrese al panoului,
din motive de siguranta si stabilitate kicker
implementeaza si incarcarea modulellor prin procese
proxy externe apelind appletproxy si
extensionproxy. Comportamentul de incarcare este configurabil
de catre utilizator cu ajutorul kcontrol.
Avantajul evident este ca utilizatorii pot configura panoul
pentru a incarca module neverificate de la terti prin
acele proxy-uri. In acest fel se evita ca modulele ce contin erori sa
afecteze intregul panou odata cu ele.
Ca autor de module nu trebuie sa va
puneti problema implementarii acestor detalii. Este bine de stiut ce se
intimpla in spatele scenei. Din acest motiv trebuie sa mentionez aici
ca panoul comunica cu aplicatia proxy-ul prin protocolul DCOP
(vedeti kdelibs/dcop) si foloseste QXEmbed
(vedeti kdelibs/kdeui/qxembed.h) pentru a ingloba module
externe in panou (incarcate prin unul sau mai multe proxy-uri).
O miniaplicatie de panou 'hello world'
Vom incepe cu o miniaplicatie e panou,
'hello world', ce poate fi implementata doar cu citeva linii de cod si
este o buna baza pe care putem construi o miniaplicatie mult mai complexa.
Asa cum deja am mentionat in sectiunea de descriere, tot ce trebuie facut este mostenirea din
KPanelApplet, furnizarea unei functii fabrica si a unui fisier de descriere
.desktop.
Micul nostru proiect este constituit din patru fisiere:
helloworldapplet.h ce contine declaratia clasei,
helloworldapplet.cpp ce contine implementarea clasei si functia fabrica,
helloworldapplet.desktop fisierul de descriere si
Makefile.am
ca resursa pentru sistemul de generare automata KDE bazat pe automake/autoconf.
class HelloWorldApplet : public KPanelApplet
{
Q_OBJECT
public:
HelloWorldApplet( const QString& configFile, Type t = Stretch,
int actions = 0, QWidget *parent = 0,
const char *name = 0 );
int widthForHeight( int height ) const;
int heightForWidth( int width ) const;
};
Declaratia clasei HelloWorldApplet
Daca ne uitam la kpanelapplet.h vedem ca tot
ce trebuie sa facem este sa reimplementam functiile virtuale
int widthForHeight(int)
si heightForWidth(int).
Jucindu-va cu kicker-ul puteti observa ca puteti aranja panoul fie pe orizontala,
fie pe verticala, la marginile ecranului. De asemenea veti observa ca puteti alege
dintr-un set fix de marimi ale panoului (reprezentind dimensiunea pe orizontala si pe verticala).
Conceptul miniaplicatiilor de panou este ca o componenta dimensionala
(marime) este dictata de catre panou, in timp ce pentru cealalta componenta miniaplicatia
este libera sa aleaga dimensiunea. Pentru panourile orizontale inaltimea miniaplicatiei
este fixata in conformitate cu inaltimea panoului in timp ce miniaplicatia are libertatea
de a alege lungimea sa. In mod corespunzator, pentru panourile verticale latimea miniaplicatiei
este fixa, iar inaltimea sa este la libera alegere.
Fiecare miniaplicatie trebuie sa fie pregatita pentru a fi folosita atit
pe panourile orizontale, cit si pe cele verticale. Panoul foloseste cele doua functii pe care
le reimplementam pentru HelloWorldApllet pentru a determina dimensiunea preferata a
miniaplicatiei. Pentru a afla componenta libera a dimensiunii miniaplicatiei,
un panou orizontal va apela widthForHeight(),
iar unul vertical va apela heightForWidth().
Este garantat ca miniaplicatiile sint redimensionabile conform cu marimea pe care o cer.
HelloWorldApplet::HelloWorldApplet(const QString& configFile,
Type type, int actions,
QWidget *parent, const char *name)
: KPanelApplet( configFile, type, actions, parent, name )
{
setBackgroundColor( blue );
setFrameStyle( StyledPanel | Sunken );
}
int HelloWorldApplet::widthForHeight( int h ) const
{
return h; // vrem sa fie patratica
}
int HelloWorldApplet::heightForWidth( int w ) const
{
return w; // vrem sa fie patratica
}
Implementarea clasei HelloWorldApplet
Constructorul nostru transmite argumentele sale implicite
constructorului, KPanelApplet stabileste stilul ferestrei la
StyledPanel Sunken (vedeti fisierul antet qframe.h) si stabileste
culoarea de fundal a miniaplicatiei ca fiind culoarea albastra astfel incit sa-l
putem recunoaste mai tirziu cand va rula in panou.
widthForHeight() si heightForWidth()
sint astfel implementate incit sa obtinem o miniaplicatie de forma patrata.
Astfel ne vom astepta ca atunci cind miniaplicatia va functiona, sa apara pe panou
ca un patrat adincit de culoare albastra.
 |
| Miniaplicatia 'Hello, World!' |
extern "C"
{
KPanelApplet* init(QWidget *parent, const QString& configFile)
{
KGlobal::locale()->insertCatalogue("helloworldapplet");
return new HelloWorldApplet(configFile, KPanelApplet::Normal,
0, parent, "helloworldapplet");
}
}
Initializare fabrica HelloWorldApplet
La functia fabrica munca consta indeosebi in copiere si lipire, prin care veti inlocui "helloworldapplet" cu "myapplet" pentru a o ajusta conform aplet-ului dumneavoastra particularizat. Observatie: Nu schimbati semnatura functiei altfel panel-ul va esua in incercarea de incarcare a aplet-ului. Dupa ce vom adauga functia fabrica va insemna ca am terminat cu partea de cod si ca numai fisierul de descriere si fisierul
Makefile.am
au mai ramas sa fie scrise.
[Desktop Entry]
Name = Hello World
Comment = Hello World Applet
X-KDE-Library = libhelloworldapplet
X-KDE-UniqueApplet = true
Fisierul de descriere helloworldapplet.desktop
Alaturi de cuvintele cheie ale fisierului standard
.desktop precum "Name", "Comment", and "Icon", mai sint doua cuvinte cheie specifice
miniaplicatiilor de panou:
- X-KDE-Library
- este folosit de catre panou pentru a localiza o miniaplicatie partajabila dinamic,
DSO (Dynamic Shared Object). Exemplu:
X-KDE-Library=libexampleapplet
- X-KDE-UniqueApplet
- Asemanator cu KApplication si KUniqueApplication,
exista doua tipuri de miniaplicatii de panou. Folositi miniaplicatii de tip "unic" cind nu
are rost sa rulati mai mult de o singura instanta. Un bun exemplu pentru miniaplicatii
unice este "bara de procese". Folositi miniaplicatii normale cind aveti nevoie sa rulati instante
cu configurari diferite. Un exemplu este "koolclock" la care puteti rula doua instante, una configurata
ca ceas analogic, iar cealalta ca ceas digital.
X-KDE-UniqueApplet este o cheie de tip boolean (logic) setata implicit la valoarea "false".
Exemplu: X-KDE-UniqueApplet=true
Pentru miniaplicatiile DSO sint folosite urmatoarele conventii:
- Name
- libappletnameapplet.la
- LDFLAGS
- -module -no-undefined
INCLUDES =
lib_LTLIBRARIES = libhelloworldapplet.la
libhelloworldapplet_la_SOURCES = helloworldapplet.cpp
METASOURCES = AUTO
noinst_HEADERS = helloworldapplet.h
lnkdir = /kicker/applets
lnk_DATA = helloworldapplet.desktop
EXTRA_DIST =
libhelloworldapplet_la_LDFLAGS = \
-version-info 1:0:0 -module \
-no-undefined
libhelloworldapplet_la_LIBADD =
messages:
*.cpp *.h -o /helloworldapplet.pot
Fisierul Makefile.am
Explicarea detaliilor acestui fisier particular pentru automake
si sistemul de generare al KDE nu este scopul acestui indrumar si poate fi subiectul unei alte
lucrari practice a lui Stephan Kulow. Asadar, pentru a-l configura pentru miniaplicatia
dumneavoastra, inlocuiti toate aparitiile lui "helloworldapplet" cu "myapplet".
Generare si instalare
Puteti descarca un pachet complet helloworldapplet.tar.gz de aici:
http://www.ro.kde.org/ftparea/kde/khelloworldapplet.tar.gz (308 KO).
In plus fata de cele patru fisiere mentionate mai sus, contine sistemului de generare KDE,
bazat pe automake si autoconf. Este convenabil sa va bazati propria miniaplicatie pe acest sistem.
Despachetati-l, intrati in directorul in care a fost dezarhivat pachetul, construiti-l si instalati-l cu:
./configure -prefix=<your-kde-dir>
make
su -c 'make install'
Hello world!
Acum ca ati instalat panoul si fisierul de descriere este la locul lui,
in submeniul "Adauga miniaplicatie" al panoului apare itemul "Hello World".
Selectati "Hello Applet" si veti vedea in panou un patrat adincit de culoare albastra.
Veti observa de asemenea un mic indicator (handler) de culoare gri in partea din stinga
a miniaplicatiei. Folositi meniul contextul pentru a muta sau ascude miniaplicatia.
De asemenea o puteti de asemenea muta tragind-o de miner.
Miniaplicatia de panou 'fifteen pieces'
In cautarea unei miniaplicatii potrivite pentru acest indrumar am gasit o captura
de ecran al unui vechi joc cu cinsprezece piese. Desi este mai mult o jucarie si nu
ceva folositor, este simplu si usor de realizat si astfel devine perfect pentru un tutorial.
Sint sigur ca stiti jocul cu cinsprezece piese al carui scop este de a aranja piesele intr-o ordine numerica.
Idea jocului este ca pe o table de joc, de forma patrata cu 4x4 celule, piesele numerotate de la 1-15 trebuie
aranjate in ordine numerica, piesele putind fi mutate numai pe orizontala sau pe verticala folosind o singura
celula libera.
Pentru suprafata de joc vom folosi un widget de tip
QTableView
particularizat. Asemanator cu miniaplicatia 'hello world', micul nostru proiect va consta din
patru fisiere, fiftenapplet.cpp, fifteenapplet.h, fifteenapplet.desktop
si fisierul Makefile.am.
fifteenapplet.h contine declaratia clasei FifteenApplet, clasa mostenita din
KPanelApplet:
class FifteenApplet : public KPanelApplet
{
Q_OBJECT
public:
FifteenApplet(const QString& configFile, Type t = Stretch,
int actions = 0,
QWidget *parent = 0, const char *name = 0);
int widthForHeight(int height) const;
int heightForWidth(int width) const;
void about();
private:
PiecesTable *_table;
KAboutData *_aboutData;
};
Clasa mostenita din KPanelApplet
Este asemanatoare cu declaratia lui HelloWorldApplet,
dar am adaugat o reimplementare a lui void about() (asa cum este definit in
kpanelapplet.h), un pointer de tip privat catre clasa PiecesTable si
un obiect de tip KAboutData
(vedeti kdelibs/kdecore/kaboutdata.h) folosit pentru construirea unei
ferestre de dialog "About".
Cautind prototipul functiei void about() in declaratia clasei
KPanelApplet veti gasi doua functii cu acces de tip protejat,
void help() si void preferences().
In meniul contextual al unor miniaplicatii veti gasi in plus, pe linga itemii de meniu
deja mentionati, "Move" si "Remove", elemente de meniu numite "About", "Help" sau "Preferences".
Atunci cind utilizatorul selecteza din meniul contextual "About", "Help" sau "Preferences",
cele trei metode protejate trateaza evenimentele asociate.
Trebuie sa le reimplementati in clasa dumneavoastra pentru a le putea trata.
Nu orice miniaplicatie implementeaza, de exemplu, tratarea evenimentului
"Preferences" deoarece s-ar putea sa nu aiba nimic de configurat. Pentru a evita afisarea
itemilor de meniu inutili in meniul contextual,
kicker nu va afisa decit acele elemente de meniu pentru care au fost reimplementate
metodele asociate sau va tine cont de un al treilea argument pasat constructorului clasei miniaplicatiei.
Acest argument de tip intreg reprezinta numarul elementelor de meniu ce se doreste a fi afisat.
A doua clasa declarata in
fifteenapplet.h este tabla de joc mostenita din QTableView:
class PiecesTable : public QTableView
{
Q_OBJECT
public:
PiecesTable(QWidget* parent = 0, const char* name = 0);
protected:
void resizeEvent(QResizeEvent*);
void mousePressEvent(QMouseEvent*);
void mouseMoveEvent(QMouseEvent*);
void paintCell(QPainter *, int row, int col);
void initMap();
void initColors();
void randomizeMap();
void checkwin();
private:
QArray<int> _map;
QArray<QColor> _colors;
QPopupMenu *_menu;
int _activeRow, _activeCol;
bool _randomized;
enum MenuOp { mRandomize = 1, mReset = 2 };
};
Tabla de joc mostenita din QTableView
In continuare voi explica detaliile de implementare a acestei clase.
Functia fabrica este similara cu cea a miniaplicatiei
"helloworld", toate aparitiile lui "helloworldapplet" fiind inlocuite cu "fifteenapplet".
A doua diferenta pe care o puteti observa este ca al treilea argument al constructorului
FifteenApplet este KPanelApplet::About si aceasta
pentru a ne asigura ca elementul de meniu "About" va apare in meniul contextual:
extern "C"
{
KPanelApplet* init(QWidget *parent, const QString& configFile)
{
KGlobal::locale()->insertCatalogue("kfifteenapplet");
return new FifteenApplet(configFile, KPanelApplet::Normal,
KPanelApplet::About, parent, "kfifteenapplet");
}
}
Initializare fabrica FifteenApplet
Implementarea clasei FifteenApplet
este foarte scurta deoarece clasa care implementeaza suprafata de joc se va ocupa de
desenare si de logica jocului.
FifteenApplet::FifteenApplet(const QString& configFile, Type type, int actions,
QWidget *parent, const char *name)
: KPanelApplet(configFile, type, actions, parent, name), _aboutData(0)
{
// setup table
_table = new PiecesTable(this);
// setup layout
QHBoxLayout *_layout = new QHBoxLayout(this);
_layout->add(_table);
srand(time(0));
}
Implementarea claseiFifteenApplet
Constructorul creeaza o instanta a clasei ce reprezinta suprafata de joc si o
plaseaza intr-un simplu obiect QLayout
pentru a putea fi redimensionata de fiecare data la intreaga marime a miniaplicatiei.
Functia srandom() este folosita pentru a initializa generatorul de numere aleatoare cu
timpul actual exprimat in secunde ca valoare initiala (seed).
Vedeti si paginile de manual srandom: man srandom.
int FifteenApplet::widthForHeight(int h) const
{
return h; // dorim sa fie patrat
}
int FifteenApplet::heightForWidth(int w) const
{
return w; // dorim sa fie patrat
}
Redimensionare FifteenApplet
Asemanator cu miniaplicatiai "helloworld",
"fifteenpieces" va avea o forma patrata:
void FifteenApplet::about()
{
if(!_aboutData) {
_aboutData = new KAboutData("kfifteenapplet", I18N_NOOP("KFifteenApplet"),
"1.0", I18N_NOOP("Fifteen pieces applet.\n\n"
"The goal is to put the sliding pieces into numerical order.\n"
"Select \"Randomize Pieces\" from the RMB menu to start a game."),
KAboutData::License_BSD, "(c) 2001, Matthias Elter");
_aboutData->addAuthor("Matthias Elter", 0, "elter@kde.org");
}
KAboutApplication dialog(_aboutData);
dialog.show();
}
Obiectul "About" pentru FifteenApplet
Implementarea metodei de tratare a evenimentului
void about(), pe care o reimplementam din clasa de baza
KPanelApplet, creeaza un obiect KAboutData object
(vedeti kdelibs/kdecore/kaboutdata.h). Se foloseste de
KAboutApplication
(vedeti kdelibs/kdeui/kaboutapplication.h)
pentru a afisa o fereastra de dialog "About" in momentul in care utilizatorul selecteaza
"About" din meniul contextual.
 |
| Fereastra de dialog "About" |
Sa urmarim acum modalitatea de implementare a
clasei pentru suprafata de joc numita PiecesTable.
Deoarece mosteneste QtableView, veti dori sa vedeti descrierea acestei clase
abstracte de baza pentru tabele, in excelenta documentatie Qt:
http://doc.trolltech.com/qtableview.html#details
PiecesTable::PiecesTable(QWidget* parent, const char* name)
: QTableView(parent, name), _menu(0), _activeRow(-1),
_activeCol(-1), _randomized(false)
{
// setare vizualizare tabel
setFrameStyle(StyledPanel | Sunken);
setBackgroundMode(NoBackground);
setMouseTracking(true);
setNumRows(4);
setNumCols(4);
// initalizare vectori
initMap();
initColors();
}
Initializare PiecesTable
Constructorul initializeaza citeva variabile membru folosite de
catre obiectul "suprafata de joc", stabileste stilul ferestrei tabelei
(QTableView mosteneste din QFrame), stabileste modul background la
NoBackground (procedam astfel pentru a evita fenomenul de pilpiire (flicker),
activeaza pentru widget monitorizarea miscari mouse-lui si configureaza numarul de rinduri si coloane
al tabelei. Vom apela doua functii de initializare cu acces protejat, pe care le-am definit in clasa
PiecesTable pentru a stabili doi vectori, unul (un vector de intregi) folosit pentru a
reprezenta suprafata de joc si celelalt (vector de obiecte QColor) in care vom stoca
valorile "culoare" pentru cele cinsprezece piese.
void PiecesTable::initMap()
{
_map.resize(16);
for (unsigned int i = 0; i < 16; i++)
_map[i] = i;
_randomized = false;
}
Initializare harta de valori piese
initMap() redimensioneaza vectorul QArray<int>
pe care il folosim pentru a reprezenta suprafata de joc la dimensiunea 16
(4x4 campuri) si il initializeaza cu valori de la 1 la 15.
void PiecesTable::initColors()
{
_colors.resize(numRows() * numCols());
for (int r = 0; r < numRows(); r++)
for (int c = 0; c < numCols(); c++)
_colors[c + r *numCols()] = QColor(255 - 70 * c,255 - 70 * r, 150);
}
Initializare culori
initColors() este asemanatoare cu
initMap(), dar in plus calculeaza valori diferite de culoare pentru fiecare piesa
pentru a mari efectul estetic al miniaplicatiei.
void PiecesTable::paintCell(QPainter *p, int row, int col)
{
int w = cellWidth();
int h = cellHeight();
int x2 = w - 1;
int y2 = h - 1;
int number = _map[col + row * numCols()] + 1;
bool active = (row == _activeRow && col == _activeCol);
// deseneaza fundalul celulei
if(number == 16)
p->setBrush(colorGroup().background());
else
p->setBrush(_colors[number-1]);
p->setPen(NoPen);
p->drawRect(0, 0, w, h);
// deseneaza marginile
if (height() > 40) {
p->setPen(colorGroup().text());
if(col < numCols()-1)
p->drawLine(x2, 0, x2, y2); // right border line
if(row < numRows()-1)
p->drawLine(0, y2, x2, y2); // bottom border line
}
// deseneaza numarul
if (number == 16) return;
if(active)
p->setPen(white);
else
p->setPen(black);
p->drawText(0, 0, x2, y2, AlignHCenter | AlignVCenter,
QString::number(number));
}
Desenarea unei piese
Am reimplementat metoda clasei QTableView,
void paintCell(), pentru a desena celulele tabelei care reprezinta piesele de pe
suprafata de joc, atunci cind widget-ul primeste un eveniment paint.
Functia este putin cam stufoasa pentru a fi explicata in detaliu, dar este usor de inteles
(pentru definitiile apelurilor de functii
vedeti documentatia QPainter
si QTableView).
void PiecesTable::resizeEvent(QResizeEvent *e)
{
QTableView::resizeEvent(e);
// setez fontul
QFont f = font();
if (height() > 50)
f.setPixelSize(8);
else if (height() > 40)
f.setPixelSize(7);
else if (height() > 24)
f.setPixelSize(5);
else
f.setPixelSize(3);
setFont(f);
setCellWidth(contentsRect().width()/ numRows());
setCellHeight(contentsRect().height() / numCols());
}
Redimensionarea piesei
Pentru a ajusta suprafata de joc corespunzator la geometria la care ne obliga
miniaplicatia, reimplementam si functia de tratare a evenimentului
resizeEvent(), a widget-ului QTableView.
Amintiti-va ca atit panoul, cit si miniaplicatiile pot avea marimi diferite.
Tot ce facem in functia resizeEvent este sa ajustam marimea fontului
folosit pentru a desena pe widget (folosit astfel in paintEvent
pentru a desena numarul piesei) la marimea actuala a widget-ului, pentru a mari
lizibilitatea pe panouri de mici dimensiuni. Mai departe stabilim latimea si
inaltimea unei celule tinind cont de geometria widget-ului. Algoritmul este simplu deoarece
toate celulele (piesele) de pe suprafata noastra de joc au aceiasi dimensiune.
Pentru a evidentia celula (piesa) deasupra caruia se afla cursorul mouse-lui,
vom reimplementa si functia de tratare a evenimentului mouseMoveEvent() a suprafetei de joc:
void PiecesTable::mouseMoveEvent(QMouseEvent* e)
{
QTableView::mouseMoveEvent(e);
// evidentiaza piesa cind cursorul
// mouse-ului e deasupra
int row = findRow(e->y());
int col = findCol(e->x());
int oldrow = _activeRow;
int oldcol = _activeCol;
if(row >= numRows()
|| col >= numCols()
|| row < 0
|| col < 0) {
_activeRow = -1;
_activeCol = -1;
} else {
_activeRow = row;
_activeCol = col;
}
updateCell(oldrow, oldcol, false);
updateCell(row, col, false);
}
Tratarea miscarii mouselui peste piesa
Am terminat desenarea suprafetei de joc si ajustarea marimi acesteia.
Nu ne este de nici un folos o miniaplicatie care arata destul de bine,
daca logica jocului pentru miscarea pieselor pe suprafata de joc nu este implementata.
 |
| Miniaplicatia "fifteenpieces" |
Deoarece ideea jocului este de a aranja intr-o
ordine numerica piesele aranjate initial intr-un mod intimplator,
pentru a realiza dispunerea intr-un mod aleator a pieselor pe suprafata de joc,
mai intii implementam o functie ajutatoare:
void PiecesTable::randomizeMap()
{
QArray<int> positions;
positions.fill(0, 16);
for (unsigned int i = 0; i < 16; i++) {
while(1) {
int r = (int) (((double)rand() / RAND_MAX) * 16);
if(positions[r] == 0) {
_map[i] = r;
positions[r] = 1;
break;
}
}
}
repaint();
_randomized = true;
}
Functia de amestecare a pieselor
Avem nevoie de o a doua functie ajutatoare
care sa fie apelata dupa fiecare operatie de mutare, pentru a verifica daca jucatorul a
cistigat cu miscarea anterioara si pentru a afisa o casuta de dialog in cazul unei victorii.
Poate ca ati observat variabila membru _randomized in functia de mai sus, din
codul functiei checkwin().Puteti vedea ca este folosita pentru a fi siguri ca vom
declansa afisarea dialogului "You win" numai daca jucatorul a amestecat intr-o ordine aleatoare
piesele de pe suprafata de joc inainte de a le aranja in ordine numerica.
void PiecesTable::checkwin()
{
if(!_randomized) return;
int i;
for (i = 0; i < 16; i++)
if(i != _map[i])
break;
if (i == 16)
KMessageBox::information(this,
i18n("Congratulations!\nYou win the game!"),
i18n("Fifteen Pieces"));
}
Verificare daca piesele sint aranjate
Operatiile de mutare sint declasante de catre jucator in momentul in care da
clic pe o piesa. Pentru widget-ul suprafata de joc am reimplementat functia de tratare a evenimentului
mousePressEvent(). Un clic cu butonul din dreapta al mouse-lui declanseaza afisarea unui
meniu contextual sau reseteaza suprafata de joc. Un clic cu butonul stinga al mouse-lui
declanseaza logica jocului care este foarte simpla daca stati si va ginditi cinci minute la ea.
Veti avea posibilitatea sa intelegeti detaliile citind numeroasele comentarii din cod:
void PiecesTable::mousePressEvent(QMouseEvent* e)
{
QTableView::mousePressEvent(e);
if (e->button() == RightButton) {
// seteaza meniul contextual
if(!_menu) {
_menu = new QPopupMenu(this);
_menu->insertItem(i18n("R&andomize Pieces"), mRandomize);
_menu->insertItem(i18n("&Reset Pieces"), mReset);
_menu->adjustSize();
}
// executa meniul contextual si verifica rezultatul
switch(_menu->exec(mapToGlobal(e->pos()))) {
case mRandomize:
randomizeMap();
break;
case mReset:
initMap();
repaint();
break;
default:
break;
}
} else {
// LOGICA JOCULUI
// cauta pozitia libera
int pos = _map.find(15);
if(pos < 0) return;
int frow = pos / numCols();
int fcol = pos - frow * numCols();
// cauta pozitia clicului de mouse
int row = findRow(e->y());
int col = findCol(e->x());
// verificari suplimentare
if (row < 0 || row >= numRows()) return;
if (col < 0 || col >= numCols()) return;
// mutare valida?
if(row != frow && col != fcol) return;
// linia se potriveste -> muta piesele
if(row == frow) {
if (col < fcol) {
for(int c = fcol; c > col; c-) {
_map[c + row * numCols()] = _map[ c-1 + row *numCols()];
updateCell(row, c, false);
}
} else if (col > fcol) {
for(int c = fcol; c < col; c++) {
_map[c + row * numCols()] = _map[ c+1 + row *numCols()];
updateCell(row, c, false);
}
}
} else if (col == fcol) {
// coloana se potriveste -> muta piesele
if (row < frow) {
for(int r = frow; r > row; r-) {
_map[col + r * numCols()] = _map[ col + (r-1) *numCols()];
updateCell(r, col, false);
}
} else if (row > frow) {
for(int r = frow; r < row; r++) {
_map[col + r * numCols()] = _map[ col + (r+1) *numCols()];
updateCell(r, col, false);
}
}
}
// muta celula libera la pozitia clicului
_map[col + row * numCols()] = 15;
updateCell(row, col, false);
// verifica daca jucatorul a cistigat
// la aceasta mutare
checkwin();
}
}
Initializare fabrica HelloWorldApplet
Am terminat. Acum fie descarcati si modificati khelloworldapplet.tar.gz (308 kB)
asa cum este descris mai sus, fie descarcati kfifteenapplet-1.0.tar.gz (311 kB)
pentru a incerca miniaplicatia. Sint sigur ca veti fi de acord ca desi 'fifteen pieces'
nu este prea folositor, este totusi o jucarie interesanta pentru petrecerea timpului.
Sinteti bun daca reusiti sa descoperiti miscarile de baza folosite pentru a castiga efectiv fiecare joc in
mai putin de 10 minute!
 |
| Meniul contextual al miniaplicatiei "fifteenpieces" |
Acum ca am implementat o miniaplicatie demonstrativa, voi discuta notiuni
API avansate de care nu ne-am folosit in implementarea "fifteen pieces".
Miniaplicatii cu marime variabila
Unele miniaplicatii de panou, precum "pager", au nevoie de marimi variabile ce
depind de starea lor (de exemplu numarul de ecrane afisate). Pentru a schimba latimea sau inaltimea
nu trebuie decat sa emiteti un semnalul void updateLayout()
(vedeti kpanelapplet.h) si kicker va rearanja panoul si va reinteroga
widthForHeight() (panoul orizontal) sau
heightForWidth().
Managementul sesiunii
Fata de celelalte aplicatii KDE, miniaplicatiile de panou nu sint
controlate de ksmserver, managerul KDE de sesiune, ci sint salvate si restaurate de
catre panou. Managementul sesiunii este transparent pentru miniaplicatie. Tot ce trebuie facut pentru
implementarea unui management functional al sesiunii este sa va asigurati ca
pentru a salva si citi setarile de configurare
veti folosi intotdeuna obiectul KConfig, returnat de KPanelApplet::config().
Pentru miniaplicatii unice (X-KDE-UniqueApplet=true), acest obiect de configurare va scrie
setarile in $KDEDIR/share/config/appletidrc,
unde appletid este numele bibliotecii de functii, asa cum este definit in fisierul
.desktop cu X-KDE-Library.
Pentru miniaplicatii non-unice numele fisierului config va contine un singur ID de sesiune.
Miniaplicatii strech
Cele doua miniaplicatii, 'helloworld' si 'fifteen pieces', despre care am discutat,
sint de tipul KPanelApplet::Normal,
asa cum s-a transmis in constructorul miniaplicatiei catre constructorul
KPanelApplet. Oricum exista si un al doilea tip,
KPanelApplet::Stretch,
care este folosit pentru miniaplicatii stretch (intinse) cum este
de exemplu bara de procese din kicker. Pentru acet tip de miniplicatii
valorile returnate de widthForHeight() si
heightForWidth() sint interpretate ca fiind valorile minime ale dimeniunii.
Asta inseamna ca o miniaplicatie stretch niciodata nu va fi mai mica decit valoarea marimii
pe care ati ales-o, dar va fi intinsa de catre kicker pentru a ocupa tot spatiul disponibil
pina la urmatoarea miniaplicatie din panou. Mutati bara de procese pentru a observa efectul.
Functia generala de tratare a unei actiuni
Uitindu-va la declaratia clasei KPanelApplet
puteti obseva ca exista o singura valoare (ReportBug) in enumerarea
Action care nu are asociata o functie virtuala pentru tratarea evenimentului.
Motivul este acela ca ReportBug a fost adaugat dupa KDE 2.0 si astfel nu mai este posibil
sa se adauge o functie virtuala fara sa se strice compatibilitatea binara cu platforma KDE 2.
Din acest motiv, pentru a manipula evenimentele (actiunile)
ReportBug, trebuie sa reimplementati functia generala pentru tratarea unei actiuni,
void action(Action).
Aceasta este apelata cind utilizatorul selecteaza oricare din elementele meniului contextual.
Actiuni de la tastatura
Panoul in mod normal nu accepta caractere de la tastatura. Din acest motiv
miniaplicatiile ce au nevoie de control de la tastatura (de exemplu pentru introducerea de text),
trebuie sa emita semnalul void requestFocus()
(de exemplu ca reactie la evenimentul 'apasarea unui buton al mouse-ului').
Proiectarea interfetei grafice cu utilizatorul
Asa cum am precizat mai devreme, panoul poate avea diferite
orientari (orizontala sau verticala) si poate fi dispus pe diferite margini ale ecranului.
Astfel, poate doriti sa ajustati interfata grafice a miniaplicatiei in functie de
orientarea panoului. Exista doua functii cu acces de tip protejat declarate in
KPanelApplet. Panoul poate fi interogat in privinta orientarii si directiei in
care trebuie pozitionat meniul contextual, functie de pozitia panoului.
Metodele sint Orientation orientation() si
Direction popupDirection(). Sint de asemenea definite si doua functii
virtuale de tratare a evenimentelor: void orientationChange() si
void popupDirectionChange(). Acestea pot fi
reimplementate pentru a reactiona la schimbarile de directie si orientare a panoului.
Sfaturi si trucuri
Niciodata sa nu apelati
show() in constructorul miniaplicatiei. Nu exista nici un motiv pentru
apelarea lui show() in constructorul widget-ului. Acest lucru este important
pentru ca QXEmbed (kdelibs/kdecore/qxembed.h), clasa folosita pentru a impacheta
miniaplicatiile externe (cele rulate prin intermediul unui proxy) in kicker,
sufera adesea de un "conflict de acces" (race condition) cind o fereastra este vizibila
inainte de a-i fi stabilit parintele.
Cum miniaplicaiile de panou furnizeaza o foarte mica interfata cu utilizatorul,
adesea este mai usor sa se faca pozitionarea manuala in resizeEvent(),
decit folosind QLayout.
|