Bakalářský projekt

 
 
 
VEDOUCÍ: Lubomír Krejčí
 
NÁZEV: Realizace primitiv skriptového jazyka z projektu Petra Kuby
 
 
ŘEŠENÍ: Realizaci projektu jsem pojal jako vytvoření jednoduchého grafického prostředí, které uživateli umožní jednoduchým způsobem vytvářet grafické rozhraní ke svému programu. Dále umožní číst stav klávesnice a myši jednotným, jednoduchým způsobem. 

Prostředí je navrženo jako objektové a událostmi řízené. V prostředí jsou implementovány některé základní objekty - obrázek, čára, výpis textu, vstupní textový řádek. Uživatel může změnit chování objektů tím, že vytvoří potomka, ve kterém si potřebné metody předefinuje. 

 

Komunikace objektů mezi sebou je řešena formou zasílání událostí. Události jsou shromažďovány ve frontě a odtud později vybírány a obsluhovány. Také klávesnice, myš a časovač jsou zdroji událostí. Událost může být různého druhu – stisk klávesy, aktivita myši, příkaz,… 

Objekt může v sobě obsahovat i jiné objekty - stává se skupinou. 

Prostředí dále poskytuje uživateli snadnou správu paměti pomocí vlastních knihovních funkcí a objektů.

 
 
IMPLEMENTACE: Prostředí je celé napsáno v Borland C++ a některé pasáže v Assembleru (kvůli rychlosti). Jádrem celého systému je objekt desktop typu “t_desktop”. Do tohoto objektu uživatel vkládá / odebírá objekty, které chce zobrazit. Tedy, jeho funkcí je správné zobrazení všech do něj vložených objektů a výběr událostí z fronty a posílání jich jednotlivým objektům. 
 
Nyní se blíže podíváme na jeho implementaci. Desktop obsahuje dynamicky alokované pole vrstev. Vrstvy mají umožnit umístit objekty nejen v rovině ale i v prostoru, tím je umožněno, aby objekt zakrýval jiný apod. Vrstva je reprezentována řetězeným seznamem objektů, přičemž z důvodu urychlení při vykreslování může být tento seznam setřízen, například dle souřadnice x. 

Vykreslování objektů je realizováno do 64KB bufferu. Buffer reprezentuje výřez obrazovky, který je po vykreslení všech objektů do něj zasahujících zobrazen na správné místo na obrazovce. Objekty se malují v pořadí od nejvzdálenějších k nejbližším (od nejvzdálenější vrstvy k nejbližší). Důvodem k použití této metody mne vedlo zamezení efektu blikání při překreslování. 

V seznamech objektů v desktopu může být jeden objekt význačný – aktivní. To znamená, že jako první dostává možnost obsloužit vzniklou událost. Tím dostáváme i aktivní vrstvu – obsahuje aktivní objekt. Dalším význačným objektem je objekt předvolený – tento objekt dostává možnost zpracovat událost hned po aktivním (vhodné například při dialogu se vstupním polem a potvrzovacím tlačítkem – po stisku ENTER je dialog potvrzen). 

Událost je deklarována jako struktura: 

struct tevent { // struktura udalosti  evTypes what; 
union { // jedna z nasl. moznosti  tmouse mouse; // mys 
tkey key; // klavesnice 
tcommand command; // prikaz 
tfunc func; // zavolej funkci 
}; 
};
kde what určuje typ události – myš, klávesnice, příkaz, volání funkce 
např.: evLBMDown = 0x0002 // leve tlac. Mysi stisknuto 
    evLBMUp = 0x0004 // leve tlac. Mysi uvolneno 
    evKeyDown = 0x0400 // klavesa stisknuta 

Struktura tmouse

struct tmouse { // mys 
int buttons; // stav tlacitek 
int x, y; // souradnice ukazovatka 
int rx, ry; // relativni změna sourad. 
unsigned flags; // stav preradovacu a
                //zamku klavesnice
};
Struktura tkey
struct tkey { // klavesnice 
unsigned flags; // viz vyse
union {
char keychar; // ASCII hodnota
unsigned keycode; // Scan-code
}; 
};
Struktura tcommandstruct tcommand { // prikaz  unsigned command; // cislo prikazu 
union { // co je zrovna potreba  void *infoPtr; 
void (*infoFunc)(); 
long infoLong; 
int infoInt; 
unsigned infoUnsigned; 
char infoChar; 
}; 
}; 
 
Správu událostí zabezpečuje knihovna “message”. Při inicializaci alokuje pole událostí (frontu) a pole požadavků na časovač. Toto pole používá rutina časovače pro obsluhu uživatelských požadavků (uživatel chce, aby se 2x za sekundu volala tato funkce nebo poslala tahle událost). Dále na přerušení klávesnice a časovače nastaví vlastní obsluhu. Obsluha myši je řešena registrací uživatelské exitové rutiny (mouse fn ax=0x0C). Tyto 3 obsluhy zasílají do fronty příslušné události. Knihovna voláním funkcí umožňuje vkládat události do fronty, odebírat je nebo je pouze přečíst (bez odebrání), registrovat nové požadavky na časovač nebo je aktualizovat, příp. odebrat. 

Obsluhu událostí vybraných z fronty objekt desktop řeší metodou handle_ev (obsluž událost). Událost zaslaná určitému objektu je zpracována vlastní metodou handle_ev. Desktop zasílá události objektům dle následujícího pořadí: 

  • Aktivní objekt (existuje-li)
  • Předvolený objekt (existuje-li)
  • Desktop (i on má právo mít vlastní události)
  • Objekty v aktivní vrstvě (existuje-li vrstva)
  • Objekty v ostatních vrstvách
Přijde-li událost objektu, který je skupinou, chová se jako objekt desktop, jinak ji obslouží – umí-li to. 

Tento způsob zpracování událostí je nazýván jako z-pořadí. 

Knihovna “xms” zpřístupňuje uživateli XMS paměť voláním například těchto funkcí: 

  • XMSinit – inicializace ovladače
  • XMSstatus – zjištění volného místa a max. délky volného bloku 
  • XMSopen – alokace bloku paměti 
  • XMSclose – uvolnění bloku paměti 
  • XMScopy – kopírování paměti mezi bloky paměti (i do / ze základní paměti)
Při práci s XMS se používá systém handlů – každý alokovaný blok má vlastní handle. 
Podobné funkce má i knihovna “ems” pro práci s EMS pamětí. 

Na knihovně “xms” je postaven objekt bmpmanager. Tento objekt umožňuje načítání obrázků ze souboru a jejich uložení v paměti a samozřejmě i zpřístupnění těchto dat. Na data se odkazuje způsobem stejným jako v XMS, tedy pomocí handlu. Voláním funkce ins vložíme obrázek ze souboru, bmpmanager vrací handle na data nebo chybový kód. Pomocí del obrázek uvolníme. Funkce copy_to_buffer zpřístupňuje data. 

Bmpmanager se chová inteligentně, vkládáme-li stejný obrázek vícekrát, pak nealokuje paměť pro každý zvlášť, ale u prvního obrázku zvýší počet odkazů na něj. Při uvolňování sníží počet odkazů a při nule paměť uvolní. Chová se tedy podobně jako i-uzly v UNIXu. 
Při vkládání se alokuje potřebné místo v základní paměti, není-li zde pokusí se alokovat v XMS. Zatím umožňuje načítat obrázky ve formátu BMP, dále načítá soubory s fonty FNT (vlastní formát), počítám s rozšířením o další rozšířené formáty, např. PCX. 
V objektu bmpmanager je možno přidat i odkládání nepoužívaných dat na disk – swapovat. 

Knihovna “mouse” usnadňuje práci s myší. Funkce: 

  • MouseShow – zobrazí ukazovátko
  • MouseHide – schová ukazovátko
  • MouseGetPos – zjištění pozice ukazovátka
  • MouseMoveTo – přesun na novou pozici
  • MouseSetRange – omezení pohybu ukazovátka na obdélníkovou oblast
Z dalších funkcí: nastavit / zjistit citlivost a hlavně změnit tvar ukazovátka. 
Funkce mouseChangeCursor z knihovny “mouse” je používána každým objektem, protože každý objekt umožňuje změnit tvar myšky, když se nad ním pohybuje. 

Nastavení grafického režimu, inicializaci správy událostí a inicializaci bmpmanageru provádí metoda init objektu desktop. Po ukončení programu metoda done vrací vše do původního stavu. Vlastní běh programu je metoda run, ta v cyklu vybírá události z fronty a posílá je na zpracování. 

Na závěr bych uvedl možná rozšíření a příklad programu. Projekt lze rozšířit na větší počet rozlišení (zatím pouze v 320x200 256 barev). Jak jsem již zmínil podpora více grafických formátů, podpora odkládání na disk. A doplnit další užitečné objekty, jako je např. okno, tlačítko, nabídka, …

 

 
 
PŘÍKLAD:  Jednoduchý ukázkový příklad - pohybující se karta a vstupní řádek (input line). #include "desktop.h"
#include "message.h"
#include "event.h"
#include "image.h"
#include "inpline.h"
#include "bmpmanag.h"

#include <conio.h>
#include <stdio.h>
#include <dos.h>

t_image *image; // typ obrázek

volatile int s=0;
void my_func() { // funkce, která bude volána časovačem
  s = (s + 10) % 330; // pohyb obrázku po obrazovce
  image -> move(s, 70);
}

void main() {
  int id_timer, id_image, id_font;
  tevent ev;
  t_inputline *input;
  char data[11] = "Ahoj";

  ev.what = evCommand; // nastav událost na volání funkce my_func
  ev.command.command = cmCallFunc;
  ev.command.infoFunc = &my_func;

   desktop = new t_desktop(1); // vytvoř desktop s modrým pozadím (1)
  desktop -> init(meAll); // inicializuj ho

   id_image = BMPManager -> ins("j:\\vlasta\\obr.bmm", 2);
              // nacti obrazek ze souboru (.bmm – vlastní formát obsahující více
              // obrázků) a pořadí v souboru je 2 (počítáno od 0)
  if (id_image < 0) { // chyba obrázek se nenatáhl
    desktop -> done(); // vrať vše do původního stavu
    delete desktop;
    printf("CHYBA: BMPManager -> ins() (bmp): %d\n", id_image);
    getchar();
    return; // a skonči
  }
  image = new t_image(200, 100, id_image);
             // objekt obrázek na pozici 200,100 s daty obrázku v handlu id_image
  desktop -> add_view((t_object *)image, 2);
             // vlož ho do 2. Vrstvy (opět od nuly)

  id_font = BMPManager -> ins("j:\\vlasta\\obr.bmm", 1);
             // načti font, pozice dat fontu je v souboru na druhém místě
  if (id_font < 0) { // chyba font se nenatahl
    desktop -> done();
    delete desktop;
    printf("CHYBA: BMPManager -> ins() (fnt): %d\n", id_font);
    getchar();
    return;
  }

  input = new t_inputline(100, 100, id_font, data, 10);
              // vstupní řádek na pozici 100,100 s fontem id_font, textem data
              // a max. délkou 10 znaků
  desktop -> add_view((t_object *)input, 1); // vlož do vrstvy 1.
  desktop -> focus(input); // udělej ho aktivním

   desktop -> draw(); // vykresli všechny vložené objekty
  id_timer = TimerInsert(3, ev); // do časovače vlož požadavek

   desktop -> run(); // spusť program, ukončí se stiskem AltX

   TimerDelete(id_timer); // odeber požadavek z časovače
  BMPManager -> del(id_image); // uvolni obrázek a font z paměti
  BMPManager -> del(id_font);

   desktop -> done(); // vrať vše do původního stavu
  delete desktop;
}

VÝSTUP:  Výřezy obrazovkového výstupu. (pohybující se obrázek esa a změněný vstupní řádek).