Wymagane jest zapoznanie się z następującymi podrozdziałami:
P019 - Pierwszy program dla Windows
OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokątaOL038 - podstawowe algorytmy wypełniania obszarów
OL039 - algorytm Smitha
OL040 - praca w środowisku sterowanym zdarzeniami
OL041 - czcionka bitmapowa
OL042 - czcionka wektorowa
OL043 - przyciski poleceń
OL044 - menu
UWAGA!!!
Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.
Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział. Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL100 i GUI002.
Artykuł nie jest już rozwijany
Wiele aplikacji wymaga pobrania danych tekstowych od użytkownika - np. liczby, nazwy plików, imiona i nazwiska, itp.. W celu realizacji tego zadania zaprojektujemy prostą kontrolkę - jednowierszowy edytor tekstu (ang. single line text control). Będzie on wyświetlał jeden wiersz tekstu, do którego użytkownik będzie dopisywał własne literki. W obrębie wiersza użytkownik będzie się mógł poruszać przy pomocy kursora sterowanego zarówno myszką jak i klawiszami strzałkowymi prawo / lewo. Kontrolka będzie przechowywała edytowany tekst w specjalnym buforze w postaci tablicy znaków. Tekst powinien być zakończony znakiem o kodzie 0. Poniżej opisujemy szczegółowo operacje, które będą wykonywane przez kontrolkę tekstową na tej tablicy.
Tablica znakowa jest ciągłym obszarem pamięci operacyjnej komputera, w którym przechowuje się kolejne znaki tekstu. W języku C i C++ teksty zakończone są znakiem o kodzie 0, który nie wchodzi w skład tekstu - pełni funkcję wartownika. Jeśli chcemy przechować w tablicy teksty o np. 100 znakach, to rozmiar tablicy powinien być zadeklarowany jako o 1 większy, aby pomieścić również wartownika:
char t[101];
char * t = new char[101] ;
Powyższe dwa przykłady tworzą tablicę t[ ], w której można przechować 100 znaków tekstu + wartownik.
W celu bezpiecznego umieszczania tekstu w tablicy znakowej, musimy pamiętać liczbę znaków tekstu, który w tablicy jest umieszczony. Poniższy przykład programu tworzy tablicę t[] o pojemności 20 znaków, a następnie wprowadza do niej dwa teksty, wypisuje je oraz wypisuje ich długość. Zwróć uwagę, iż drugi tekst jest zbyt długi i nie mieści sie w całości w tablicy t[], zostanie zatem obcięty do rozmiaru tej tablicy - tutaj widać wyraźnie powód posiadania bezpiecznej funkcji kopiującej.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P035 - inicjalizacja tablicy tekstowej //--------------------------------------- #include <iostream> using namespace std; #define MAXLEN 20 char t[MAXLEN + 1]; int tlen; // Bezpieczna funkcja kopiująca //----------------------------- void Kopiuj(char * tx) { tlen = 0; while((tlen < MAXLEN) && (t[tlen] = tx[tlen])) tlen++; t[tlen] = 0; } // Funkcja wypisuje zawartość t[] oraz liczbę znaków w t[] //-------------------------------------------------------- void Pisz() { cout << "t[] = " << t << endl << "len = " << tlen << endl << endl; } //********************** //*** PROGRAM GŁÓWNY *** //********************** main() { Kopiuj("Ala ma kocura"); Pisz(); Kopiuj("Ale kocur Ali nie jest na fali"); Pisz(); system("PAUSE"); }
t[] = Ala ma kocura len = 13 t[] = Ale kocur Ali nie je len = 20 Aby kontynuować, naciśnij dowolny klawisz . . . |
Chcąc wstawić znak c w tablicy tekstowej t[] na pozycji cpos musimy rozsunąć znaki od pozycji cpos to końca tablicy. Poniższy program pokazuje poprawny sposób wykonania tej operacji.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P036 - wstawianie znaku do tablicy znakowej //-------------------------------------------- #include <iostream> using namespace std; #define MAXLEN 20 char t[MAXLEN + 1]; int tlen; // Bezpieczna funkcja kopiująca //----------------------------- void Kopiuj(char * tx) { tlen = 0; while((tlen < MAXLEN) && (t[tlen] = tx[tlen])) tlen++; t[tlen] = 0; } // Funkcja wypisuje zawartość t[] oraz liczbę znaków w t[] //-------------------------------------------------------- void Pisz() { cout << "t[] = " << t << endl << "len = " << tlen << endl << endl; } // Funkcja wstawia znak c na pozycji cpos //--------------------------------------- void Wstaw(char c, int cpos) { if(cpos < MAXLEN) { for(int i = MAXLEN - 2; i >= cpos; i--) t[i + 1] = t[i]; t[cpos++] = c; if(tlen < MAXLEN) tlen++; } } //********************** //*** PROGRAM GŁÓWNY *** //********************** main() { Kopiuj("Ala ma kocura"); Pisz(); Wstaw('b', 2); Pisz(); Kopiuj("Ale kocur Ali nie jest na fali"); Pisz(); Wstaw('b', 12); Pisz(); system("PAUSE"); }
t[] = Ala ma kocura len = 13 t[] = Alba ma kocura len = 14 t[] = Ale kocur Ali nie je len = 20 t[] = Ale kocur Albi nie j len = 20 Aby kontynuować, naciśnij dowolny klawisz . . . |
Kasowanie znaku wymaga zsunięcia znaków w tablicy, tak aby pozycja cpos została zapisana sąsiednim znakiem. Poniższy przykład pokazuje poprawny sposób wykonania tej operacji.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P037 - usuwanie znaków z tablicy znakowej //------------------------------------------ #include <iostream> using namespace std; #define MAXLEN 20 char t[MAXLEN + 1]; int tlen; // Bezpieczna funkcja kopiująca //----------------------------- void Kopiuj(char * tx) { tlen = 0; while((tlen < MAXLEN) && (t[tlen] = tx[tlen])) tlen++; t[tlen] = 0; } // Funkcja wypisuje zawartość t[] oraz liczbę znaków w t[] //-------------------------------------------------------- void Pisz() { cout << "t[] = " << t << endl << "len = " << tlen << endl << endl; } // Funkcja usuwa znak na pozycji cpos //----------------------------------- void Usun(int cpos) { if(tlen) { for(int i = cpos; i < tlen; i++) t[i]= t[i + 1]; tlen--; } } //********************** //*** PROGRAM GŁÓWNY *** //********************** main() { Kopiuj("Ala ma kocura"); Pisz(); Usun(0); Pisz(); Usun(3); Pisz(); Kopiuj("Ale kocur Ali nie jest na fali"); Pisz(); Usun(19); Pisz(); Usun(0); Pisz(); system("PAUSE"); }
t[] = Ala ma kocura len = 13 t[] = la ma kocura len = 12 t[] = la a kocura len = 11 t[] = Ale kocur Ali nie je len = 20 t[] = Ale kocur Ali nie j len = 19 t[] = le kocur Ali nie j len = 18 Aby kontynuować, naciśnij dowolny klawisz . . . |
Utwórz projekt SDL. Dodaj do niego pliki biblioteki SDL_gfx, które znajdziesz w podsumowaniu SDL100 (nie zapomnij o ustawieniach linkera). Następnie dołącz pliki interfejsu GUI z podsumowania GUI002. Do katalogu projektowego przekopiuj czcionkę vecprop9x12.fnt (możesz również poeksperymentować z inną czcionką, my wybraliśmy tę czcionkę z powodu jej czytelności). Jesteś gotowy do tworzenia edytora jednowierszowego dla interfejsu GUI.
Na końcu pliku nagłówkowego SDL_gui.h dopisz poniższy fragment tekstu:
class gfxEdit : public gfxGUIObject { private: Sint32 cstart; public: Sint32 maxlen, ctextlen, cpos, cxpos; gfxEdit(Uint32 tg, bool en, SDL_Surface * s, gfxFont * f, SDL_Rect * r, char * t, Sint32 len, void (* fn)(gfxGUIObject *)); ~gfxEdit(); bool DoEvents(SDL_Event * e); unsigned char KeyboardHandler(SDL_Event * e); void Refresh(); };Jest to definicja klasy gfxEdit edytora jednowierszowego. Klasa gfxEdit dziedziczy wszystkie pola i metody klasy gfxGUIObject. Znaczenie pól i funkcji składowych jest następujące:
gfxEdit
cstart - jeśli tekst jest dłuższy niż rozmiar okienka, to wyświetlany jest tylko fragment począwszy od pozycji cstart. maxlen - maksymalna liczba znaków tekstu w tablicy text[] ctextlen - aktualna liczba znaków przechowywanych w tablicy text[] cpos - pozycja wstawiania i usuwania znaków - ogólnie pozycja kursora cxpos - współrzędna x kursora na ekranie graficznym gfxEdit() - konstruktor klasy
gfxEdit(tg,en,s,f,r,t,len,fn)
tg - identyfikator kontrolki edytora
en - określa, czy kontrolka ma być aktywna - true, czy zablokowana - false
s - wskaźnik struktury SDL_Surface, na której będzie rysowana kontrolka edytora
f - wskaźnik struktury gfxFont określającej czcionkę dla tekstu kontrolki
r - prostokąt definiujący obszar kontrolki - wymiary są korygowane do wysokości tekstu z marginesem 4 pikseli.
t - wskaźnik tekstu początkowego, który pojawi się w oknie edytora
fn - wskaźnik funkcji wywoływanej przy naciśnięciu klawisza Enter.~gfxEdit() - destruktor DoEvents() - obsługa zdarzeń dla edytora
DoEvents(e)
e - wskaźnik struktury SDL_Event.KeyboardHandler() - zwraca kod naciśniętego klawisza uwzględniający CapsLock, Shift i Alt.
KeyboardHandler(e)
e - wskaźnik struktury SDL_Event.Refresh() - wyświetla okno edytora Na końcu pliku SDL_gui.cpp dopisz poniższy fragment programu:
// ************************** // *** Obsługa klasy Edit *** // ************************** // Konstruktor klasy gfxEdit //-------------------------- gfxEdit::gfxEdit(Uint32 tg, bool en, SDL_Surface * s, gfxFont * f, SDL_Rect * r, char * t, Sint32 len, void (* fn)(gfxGUIObject *)) { tag = tg; enabled = en; screen = s; font = f; rect = * r; if(rect.h < font->h + 8) rect.h = font->h + 8; maxlen = len; cstart = cpos = ctextlen = 0; char * p = text = new char[len + 1]; while((ctextlen < len) && (* p++ = * t++)) ctextlen++; * p = 0; cxpos = rect.x + 4; call = fn; sel = false; SDL_EnableKeyRepeat(200, 100); Refresh(); } // Destruktor klasy gfxEdit //------------------------- gfxEdit::~gfxEdit() { delete [] text; } // Obsługa zdarzeń myszki i klawiatury //------------------------------------- bool gfxEdit::DoEvents(SDL_Event * e) { switch(e -> type) { case SDL_MOUSEBUTTONDOWN: // naciśnięty klawisz myszki if((e->button.button == SDL_BUTTON_LEFT) && MouseHit(e->button.x, e->button.y) && enabled) { cpos = cstart; cxpos = rect.x + 4; char * p = text + cstart; unsigned char c; while((c = * p) && (cxpos + font->cw[c] < rect.x + rect.w - 4) && (e->button.x >= cxpos)) if(e->button.x <= cxpos + font->cw[c]) { if(cxpos + font->cw[c] - e->button.x < e->button.x - cxpos) { cxpos += font->cw[c]; cpos++; } break; } else { cpos++; cxpos += font->cw[c]; p++; } sel = true; Refresh(); return false; } else if(sel) { sel = false; Refresh(); } break; case SDL_KEYDOWN: // naciśnięty klawisz na klawiaturze if(sel) { switch(e->key.keysym.sym) { case SDLK_RETURN: // zatwierdzenie wiersza case SDLK_KP_ENTER: if(call) (* call)(this); break; case SDLK_LEFT: // skursor w lewo if(cpos) { cpos--; cxpos -= font->cw[(unsigned char)text[cpos]]; if(cxpos < rect.x + 4) { cstart = cpos; cxpos = rect.x + 4; } } break; case SDLK_RIGHT: // kursor w prawo if(cpos < ctextlen) { cxpos += font->cw[(unsigned char)text[cpos++]]; while(cxpos >= rect.x + rect.w - 4) cxpos -= font->cw[(unsigned char)text[cstart++]]; } break; case SDLK_HOME: // kursor na początek wiersza cstart = cpos = 0; cxpos = rect.x + 4; break; case SDLK_END: // kursor na koniec wiersza while(cpos < ctextlen) { cxpos += font->cw[(unsigned char)text[cpos++]]; while(cxpos >= rect.x + rect.w - 4) cxpos -= font->cw[(unsigned char)text[cstart++]]; } break; case SDLK_BACKSPACE: // kasowanie znaku na lewo od kursora if(cpos) { cpos--; cxpos -= font->cw[(unsigned char)text[cpos]]; if(cxpos < rect.x + 4) { cstart = cpos; cxpos = rect.x + 4; } for(int i = cpos; i < ctextlen; i++) text[i] = text[i + 1]; if((cpos == cstart) && (cstart)) cxpos += font->cw[(unsigned char)text[--cstart]]; ctextlen--; } break; case SDLK_DELETE: // kasowanie znaku na prawo od kursora if(cpos < ctextlen) { for(int i = cpos; i < ctextlen; i++) text[i] = text[i + 1]; ctextlen--; } break; default: // reszta znaków unsigned char c = KeyboardHandler(e); if(c) { if(cpos < maxlen) { for(int i = maxlen - 2; i >= cpos; i--) text[i + 1] = text[i]; text[cpos++] = c; cxpos += font->cw[c]; while(cxpos >= rect.x + rect.w - 4) cxpos -= font->cw[(unsigned char)text[cstart++]]; if(ctextlen < maxlen) ctextlen++; break; } } return true; } Refresh(); return false; } } return true; } // Przetwarzanie naciśniętego klawisza na kod ASCII //------------------------------------------------- unsigned char gfxEdit::KeyboardHandler(SDL_Event * e) { if((e->key.keysym.sym < 32) || (e->key.keysym.sym > 126)) return 0; unsigned char c = e->key.keysym.sym; bool caps = e->key.keysym.mod & KMOD_CAPS; bool shift = e->key.keysym.mod & KMOD_SHIFT; bool alt = e->key.keysym.mod & KMOD_RALT; if(((caps && !shift) || (!caps && shift)) && ((c >= SDLK_a) && (c <= SDLK_z))) c -= 32; if(alt) switch(c) { case 'A' : c = 'Ą'; break; case 'a' : c = 'ą'; break; case 'C' : c = 'Ć'; break; case 'c' : c = 'ć'; break; case 'E' : c = 'Ę'; break; case 'e' : c = 'ę'; break; case 'L' : c = 'Ł'; break; case 'l' : c = 'ł'; break; case 'N' : c = 'Ń'; break; case 'n' : c = 'ń'; break; case 'O' : c = 'Ó'; break; case 'o' : c = 'ó'; break; case 'S' : c = 'Ś'; break; case 's' : c = 'ś'; break; case 'Z' : c = 'Ż'; break; case 'z' : c = 'ż'; break; case 'X' : c = 'Ź'; break; case 'x' : c = 'ź'; break; } if(shift) switch(c) { case '`' : c = '~'; break; case '1' : c = '!'; break; case '2' : c = '@'; break; case '3' : c = '#'; break; case '4' : c = '$'; break; case '5' : c = '%'; break; case '6' : c = '^'; break; case '7' : c = '&'; break; case '8' : c = '*'; break; case '9' : c = '('; break; case '0' : c = ')'; break; case '-' : c = '_'; break; case '=' : c = '+'; break; case '[' : c = '{'; break; case ']' : c = '}'; break; case '\\' : c = '|'; break; case ';' : c = ':'; break; case '\'' : c = '"'; break; case ',' : c = '<'; break; case '.' : c = '>'; break; case '/' : c = '?'; break; } return c; } // Rysowanie kontrolki edytora //---------------------------- void gfxEdit::Refresh() { SDL_Rect r = ShrinkRect(); if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, &r, C_TEXTBG); gfxHLine(screen, rect.x, rect.y, C_LOWER, rect.w); gfxVLine(screen, rect.x, r.y, C_LOWER, rect.h - 1); gfxHLine(screen, rect.x, rect.y + rect.h - 1, C_UPPER, rect.w); gfxVLine(screen, rect.x + rect.w - 1, r.y, C_UPPER, rect.h - 1); Sint32 x = r.x + 3, y = r.y + 3; Uint32 color = enabled ? C_TEXT : C_DIS; char * p = text + cstart; unsigned char c; while((c = * p++) && (x + font->cw[c] <= r.x + r.w - 3)) x += gfxDrawChar(screen, font, c, x, y, color, -1); if(sel) gfxVLine(screen, cxpos, y - 1, C_CURSOR, font->h + 2); if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); Update(); }Poniżej prezentujemy program testujący kontrolkę edytora. Pozwala ona edytować tekst do 480 znaków. Naciśnięcie klawisza Enter wywołuje funkcję wypisującą tekst w kilku wierszach pod kontrolką.
// I Liceum Ogólnokształcące // w Tarnowie // Koło informatyczne // // P038 - Edytor wierszowy //------------------------ #include <SDL/SDL_gfx.h> #include <SDL/SDL_gui.h> const int SCRX = 320; // stałe określające szerokość i wysokość const int SCRY = 240; // ekranu w pikselach SDL_Surface * screen; gfxFont * font = gfxOpenFont("vecprop9x12.fnt"); // Funkcja wywoływana przez edytor po naciśnięciu Enter //----------------------------------------------------- void fn(gfxGUIObject * sender) { SDL_Rect r; r.x = 0; r.y = sender->rect.y + sender->rect.h + 8; r.h = screen->h - r.y; r.w = screen->w; if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, &r, 0xff0000); Sint32 x,y; x = 2; y = r.y + 2; for(int d = 0; d < 2; d++) gfxDrawText(screen, font, "W edytorze wpisałeś następujący tekst:", x + d, y, 0xffffff, -1); y += 2 * font->h; unsigned char c; char * p = sender->text; while(c = * p++) { if(x + font->cw[c] > r.x + r.w - 2) { x = 2; y += font->h; } x += gfxDrawChar(screen, font, c, x, y, 0xffff00, -1); } if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen,r.x, r.y, r.w, r.h); } //*********************** // *** Program główny *** //*********************** int main(int argc, char * argv[]) { char * t = "Test edytora jednowierszowego"; if(SDL_Init(SDL_INIT_VIDEO)) exit(-1); atexit(SDL_Quit); if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1); SDL_WM_SetCaption(t, ""); SDL_Rect r; r.x = 8; r.y = 8; r.w = screen->w - 16; r.h = 0; if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); SDL_FillRect(screen, NULL, C_FACE); if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_UpdateRect(screen, 0, 0, 0, 0); gfxEdit * ed = new gfxEdit(0, true, screen, font, &r, t, 480, fn); SDL_Event event; bool running = true; while(running && SDL_WaitEvent(&event)) { // najpierw obsługujemy zdarzenia w edytorze if(ed->DoEvents(&event)) // a później reszta zdarzeń switch(event.type) { case SDL_QUIT: running = false; break; } } // zamykamy czcionkę gfxCloseFont(font); // zamykamy edytor delete ed; return 0; }
Nasz edytor jest bardzo prymitywny. Zastanów się nad rozszerzeniem jego funkcji o:
- zaznaczanie fragmentu tekstu myszką i z klawiatury - Shift-strzałka
- usuwanie zaznaczonego fragmentu tekstu klawiszami Backspace lub Del
- kopiowanie zaznaczonego fragmentu do schowka - Ctrl-C
- wstawianie ze schowka fragmentu tekstu - Ctrl-V
- wymiana zaznaczonego fragmentu z treścią ze schowka - Ctrl-V
- wycinanie zaznaczonego fragmentu tekstu do schowka - Ctrl-X
- zaznaczanie całych słów kombinacją klawiszy Ctrl-strzałka
- zaznaczanie całego tekstu kombinacją klawiszy Ctrl-A.
- cofanie wykonanej operacji kombinacją klawiszy Ctrl-Z
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe