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

Extinderea panoului KDE


   Matthias Elter
   2001

Acest articol reprezinta traducerea si adaptarea dupa originalul publicat la: http://developer.kde.org/documentation/tutorials/dot/panel-applets.html. Tot acest efort de traducere este meritul lui Daniel Ionescu <dan.ionescu@seca.ro>.

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.