Edytor jednowierszowy

Powrót do spisu treści

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ąta
OL038 - 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ł w PRZEBUDOWIE


SDL

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.

Inicjowanie tablicy znakowej

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 . . .

Wstawianie znaku

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

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 . . .

 

Edytor jednowierszowy

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:


Podsumowanie



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.