A feladat a következő: adott egy modelviewer, amiben lehet forgatni a kamerát az egér lenyomva tartásával. A modelviewernek viszont van GUI-ja is (az engine-el rajzolva), ami szintén az egérrel működik. Hogyan lehet biztosítani a kölcsönös kizárást (az egeret csak egy valami birtokolhassa egy adott időben)? Kérdezek jobbat: hogyan lehet adott esetben megkerülni a kölcsönös kizárást?
Az elvárások a vázolt esetben az alábbiak:
- a GUI elsőbbséget élvez
- a kamera klikkelésre kap fókuszt, a GUI viszont egérmozgatásra (hover)
- ha a kamera birtokolja a fókuszt, akkor a GUI nem reagál semmire
- ha a GUI birtokolja a fókuszt, akkor a kamera nem reagál
Az eddigi megoldás ilyen hackelés szerű volt: a kamera értesítette a GUI-t, hogy megszerezte a fókuszt, a GUI ilyenkor letiltotta magát. A GUI értesítette a főprogramot, hogy megkapta a fókuszt, a főprogram letiltotta a kamerát (meg mást is csinált, de feleslegesen).
Egy másik feladatban kicsit más a helyzet:
- a GUI és a kamera viszonya ugyanaz
- de van egy harmadik elem, egy nagyító
- a nagyító kizárja a GUI-t
- a GUI kizárja a nagyítót
- a nagyító a kamerával együtt tud működni
Több feltételt is teljesíteni kell tehát, másrészt könnyen jöhet olyan eset, hogy sok objektum birtokolhatja a fókuszt és akkor a 0. megoldás követhetetlen.
1. megoldás: event ignore
A legtöbb ilyen checkbox1_clicked() esemény szignálokkal van megoldva. A probléma az, hogy a szignálok tüzelését nem lehet megszakítani. Ha meglehetne, azzal egy objektum kisajátíthatja a fókuszt. Ez egy (nagyon) rossz megoldás, egyrészt mert az összes helyen át kell írni a slotokat, hogy a szignált ignorálni lehessen (akár visszatérő értékkel, vagy 0. paraméterként átadva), másrészt ez egy óriási korlátozás az engine használója felé (bár megkerülhető).
2. megoldás: fókusz objektum
Egy singleton, amit egy lock-hoz hasonlóan meg lehet fogni. Alapvetően jó ötlet, de a fókusz megosztása nehézkes. Miben tároljam? Multimap? Dictionary? Hashtable? Akármelyiket is választom "lassú" lesz. Nyilván kell tartani azt is, hogy épp kik birtokolják a fókuszt (ez megint overhead).
3. megoldás: osztott nyomkövetés
Végülis ezt csináltam meg mert elég egyszerű megcsinálni, és rugalmas. Csináltam egy Focusable nevű osztályt, aminek a lényege annyi, hogy a nyilvánvaló Acquire() és Unacquire() metóduson kivül van neki egy focuschanged<bool> szignálja és egy onfocuschanged(bool) slotja. A member változók pedig: hasfocus, canfocus, refcount.
Minden olyan osztály ami tud fókuszt szerezni, az ebből kell származzon. A kölcsönös kizárás biztosítása a programozóra van bízva, mégpedig úgy, hogy az egyik objektum szignálját ráköti egy másik slotjára (amennyiben az egyik kizárja a másikat).
Na de hogyan is működik és miért működik?
Focusable::Focusable()
{
hasfocus = false;
canfocus = true;
refcount = 0;
}
void Focusable::AcquireFocus()
{
if( !hasfocus )
focuschanged(hasfocus = true);
}
void Focusable::UnacquireFocus()
{
if( hasfocus )
focuschanged(hasfocus = false);
}
void Focusable::onfocuschanged(bool otherhasfocus)
{
if( otherhasfocus )
++refcount;
else if( refcount > 0 )
--refcount;
canfocus = (refcount == 0);
}
Bár az Acquire()-be be lehetne rakni a canfocus-t is feltételnek, nem raktam be. Ezek után a fenti nagyítós példára a következőket kell csinálni:
form->focuschanged.connect(&camera, ...);
form->focuschanged.connect(magnifier, ...);
magnifier->focuschanged.connect(form, ...);
camera.focuschanged.connect(form, ...);
A ... mindenhol &Focusable::onfocuschanged (nem fér ki)...
Annyi megkötés van (de ez elég nyilvánvaló), hogy a mousedown eseményre először a GUI-t kell rákötni (hogy ha tudja, akkor ő kaphassa meg először a fókuszt).
Ez nem a nagyítós példa |
Ez a megoldás tűnik a legjobbnak, és semmiben sem korlátoz (ha nem akarod használni, nem használod). Memóriaügyileg elhanyagolható, teljesítményben pedig a szignál implementációjától függ (ami viszont már egy létező objektum, így plusz teljesítményromlás nincs).