1. 첫 번째 프로그램


자, 그럼 이번 절에서는 본격적으로 EFL 애플리케이션을 작성해 보도록 하자. 프로그래밍 관례에 따라 "Hello E World"를 출력하는 매우 단순한 예제로 시작한다.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   printf("Hello E World!\n");
 
   elm_run();
 
   elm_shutdown();
 
   return 0;
}

무엇보다도, 여러분이 당황하지 않도록 최소한의 API를 이용하여 예제를 완성해 보았다. 우선 Elementary의 헤더파일을 추가한다. 당연하겠지만, Elementary API를 이용하기 위해선 API가 선언되어 있어야 하며 이는 Elementary.h 파일 내부에 모두 명시되어 있다. 다음으로 매우 익숙한 main() 함수가 존재하고 main() 함수 안에는 "Hello E World!"를 출력하는 printf() 함수가 존재한다. 이외로 생소한 세 개의 API 호출이 보인다. 이들이 바로 Elementary에서 제공하는 API들이며, 하는 역할은 매우 직관적이다. elm_init()은 라이브러리를 초기화한다. Elementary의 기능들을 사용하기 위한 기본 설정들을 내부적으로 수행할 것이다.

int elm_init(int argc, char **argv);

이 API는 두 개의 인자를 전달받는데, 이들은 프로그램 실행 시 시스템을 통해 전달받는 인자의 개수와 그 개수에 해당하는 문자열 배열의 이중 포인터이다. main() 함수에서 전달받은 매개변수들을 그대로 넘겨주면 된다.

elm_run()은 프로그램의 메인 루프를 작동시킨다. 일부 라이브러리들과는 다르게 Elementary는 라이브러리 내부적으로 프로그램 기본 루프를 제공하고 있으며 이 루프는 애플리케이션에서 필요한 메인 루프의 역할을 대신 담당하게 될 것이다. 이해하기 쉽게 비유하자면, 다음과 같은 코드와 동일하다.

#include <Elementary.h>
 
int main(int argc, char **argv)
{
   elm_init(argc, argv);

   printf("Hello E World!\n");

   while(1)
   { 
      //애플리케이션은 여기서 어떤 작업을 수행할 것이다.
   }

   elm_shutdown();

   return 0;
}

물론, elm_run()이 수행하는 메인 루프 내부에서는 앞선 예제와 다르게 매우 중요한 작업들을 수행한다. 여기서는 단지 여러분의 이해를 돕기 위해 매우 추상적으로 설명했음을 알아두자. 어찌되었던 간에, 다음을 살펴보면 elm_shutdown()을 호출하고 있다. elm_shutdown()은 elm_init()과 반대로, 메인 루프 내부에서 작동 중이던 모든 기능들을 중단하고 할당했던 모든 리소스를 해제한다. 따라서 elm_shutdown()은 애플리케이션이 종료될 시점에는 반드시 호출되어야 하며 항상 elm_init()과 한 쌍으로 호출되어야 한다.

그런데, 이 예제에서는 elm_run()에 의해 가동된 메인 루프가 무한 루프와 같이 계속 작동하기 때문에 elm_shutdown()에는 도달하지 못할 것이다. 물론 어떤 이벤트에 의해 루프는 종료될 수 있지만, 그 부분은 좀 더 뒤에서 살펴보도록 하고, 일단 이 프로그램을 가동해 보자. 미안한 말이지만, 만약 여러분이 뭔가 그럴 듯한 결과물을 기대하고 있었다면 실망도 그만큼 클 것이다. 심지어, 아직은 프로그램을 중단할 방법이 없기 때문에 Ctrl+C를 이용하여 프로그램을 중단해야 한다.

그림 1. 첫 번째 예제 실행 결과


그렇다고 해서 이런 시시한 예제에 분노하기엔 아직 이르다. 다음 예제를 계속 보도록 하자. 이번에는 콘솔에 단순한 글자를 출력하는 것이 아닌 하나의 윈도우가 존재하는 진짜 프로그램(?)이 될 것이다.

#include <Elementary.h>

int main(int argc, char **argv)
{
   elm_init(argc, argv);
 
   //윈도우를 생성하고 초기화 한다.
   Evas_Object *win = elm_win_add(NULL, "Elementary Window", ELM_WIN_BASIC);
   elm_win_title_set(win, "Elementary Window");
   evas_object_resize(win, 480, 400);
   evas_object_move(win, 100, 100);
   evas_object_show(win);

   elm_run();

   elm_shutdown();

   return 0;
}

자, 단 다섯 라인을 추가하여 윈도우를 생성하는 프로그램을 완성하였다. 추가한 부분은 볼드체로 표기하였는데 한 라인씩 살펴보도록 하자.

우선, 첫 번째 라인에서는 윈도우를 생성하고 생성된 윈도우 오브젝트를 반환받는다. 이 때, 반환되는 윈도우 오브젝트의 데이터 타입은 Evas_Object의 포인터 형이다. Evas_Object는 EFL에서 오브젝트를 가리키는 단일 인터페이스이며 반드시 포인터 형으로만 쓸 수 있다. 우리는 Evas_Object 타입을 통해 다양한 종류의 오브젝트들을 일관된 방식으로 이용할 수 있다는 장점을 얻을 수 있다.

윈도우를 생성하는 API는 elm_win_add()인데, 이 API는 세 개의 매개변수를 갖는다.

Evas_Object *elm_win_add(Evas_Object *parent, const char *name, Elm_Win_Type type);

첫 번째 매개변수는 부모 윈도우 오브젝트를 전달받는다. 만약 부모가 없는 독립 윈도우라면 예제와 같이 NULL을 넘겨주면 된다. 만약 여러분이 타 윈도우 프로그래밍에 대한 경험이 있다면 윈도우엔 부모-자식 관계가 있다는 사실을 알고 있을 지도 모른다. EFL에서도 윈도우 간에는 부모-자식 간의 관계가 존재하며 이들의 개념은 타 윈도우 프로그래밍의 부모-자식 개념과 대체로 유사하다. 일반적으로 자식 윈도우의 기능 동작은 부모 윈도우의 기능 동작에 영향을 받도록 되어 있다. 일부 독자들에겐 생소한 개념일 지도 모르나, 당장은 중요하지 않으므로 일단은 부모-자식 메커니즘이 준비되어 있다는 사실만 알아두고 넘어가자.

두 번째 매개변수는 윈도우 이름을 전달받는다. elm_win_add()를 통해 윈도우를 생성하는 과정에서는 내부적으로 윈도우 관리자가 개입하며, 윈도우 관리자는 전달받은 윈도우 이름을 식별하여 경우에 따라 어떤 작업을 수행할 수도 있지만 일반 애플리케이션에서는 큰 의미는 없다. 여러분의 독자적인 이름을 정하여 전달하면 된다.

마지막 세 번째 매개변수는 윈도우 타입을 전달받는다. Elm_Win_Type 이라는 열거형이 선언되어 있는데, 우리는 다음과 같은 값들을 이용할 수 있다.

ELM_WIN_BASIC - 일반 윈도우 타입. 일반적으로는 이 값을 이용한다.
ELM_WIN_DIALOG_BASIC - 다이얼로그 윈도우 타입
ELM_WIN_DESKTOP – 데스크탑 배경 화면처럼 아이콘이 배치될 수 있는 백그라운드 윈도우 타입
ELM_WIN_DOCK - 도킹이나 패널 윈도우 타입. 보통 다른 윈도우들 보다 상단에 존재한다.
ELM_WIN_TOOLBAR - 툴바와 같이 위치가 고정되어 있지 않고 분리되어 이동할 수 있는 타입
ELM_WIN_MENU - ELM_WIN_TOOLBAR와 거의 유사
ELM_WIN_NOTIFICATION - 경고 메시지나 이메일 도착을 알려주는 알림 윈도우 타입
ELM_WIN_DND - 타 윈도우와 드래그 앤 드롭(Drag & Drop) 기능을 지원하기 위한 윈도우 타입. elm_win_override_set() API를 이용해야 한다.
ELM_WIN_INLINED_IMAGE - 어떤 특정 이미지 버퍼에 그려질 윈도우. 윈도우뿐만 아니라 윈도우 내의 콘텐츠 역시 동일한 버퍼에 그려질 것이다. 주의할 점은 반드시 부모 윈도우를 가져야 하며 이 타입의 윈도우는 자식 윈도우로서 버퍼에 그려진다. 사용자는 이 버퍼의 데이터를 조작하거나 Evas_Map등을 이용하여 GUI 이펙트를 적용할 수 있다.
ELM_WIN_SOCKET_IMAGE - ELM_WIN_INLINE_IMAGE 타입과 동일하게 이 윈도우는 이미지 버퍼에 그려진다. 하지만 이 이미지 버퍼는 다른 EFL 애플리케이션과 공유되며, 플러그(Plug) 오브젝트가 해당 이미지를 대신 그려줄 수 있다. 달리 말하면, 윈도우 출력물을 타 프로세스들과 공유할 때 쓰인다.

추후에는 더 많은 윈도우 타입이 지원될 수 있으므로 필요하다면 최신 Elementary 문서를 참고하길 바란다. 일반적인 애플리케이션이라면 ELM_WIN_BASIC 타입의 윈도우가 널리 쓰이므로 여러분은 예제와 같이 ELM_WIN_BASIC을 전달해야 할 것이다.

자, 다음 API를 살펴보면 생성한 윈도우에 타이틀을 지정한다. 이 타이틀은 보통 윈도우 관리자에 의해 윈도우 상단 타이틀 바에 표시될 것이다.

void elm_win_title_set(Evas_Object *obj, const char *title);

예제와 같이, 이 API는 윈도우 오브젝트와 표시하고자 하는 윈도우 타이틀 문자열을 전달받는다.

다음에 이어지는 세 개의 API들은 윈도우 크기와 위치를 지정하고 화면 상에 보이겠다고 명시하는 API들이다.

void evas_object_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);

void evas_object_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);

void evas_object_show(Evas_Object *obj);

각 API들의 이름 및 매개변수는 상당히 직관적이라서 추가적인 설명이 필요 없을 듯 하다. 다만 앞서 살펴본 Elementary API들과 비교하여 뭔가 다른 점이 보였을 텐데, 이들의 API들은 접두사가 elm이 아닌 evas로 시작하고 있다. 말 그대로, 앞서 살펴봤던 API들은 Elementary 라이브러리에서 제공하던 API들이고 이 세 개의 API들은 Evas 라이브러리에서 제공하는 API들이다. 실제로 Elementary는 Evas를 기반으로 작성된 라이브러리이며 Evas_Object 인터페이스 역시 Evas에서 정의하고 있다. Evas에서는 이러한 추상화된 Evas_Object 인터페이스를 정의함과 동시에 이 인터페이스로 사용할 수 있는 일반적인 기능들을 제공하는데 그러한 기능들 중 일부가 바로 move, resize, show와 같은 API들이다.

이 후에도 여러분은 이 책의 예제들을 통해, Evas, Elementary와 같은 서로 다른 EFL 라이브러리 간의 기능들이 혼재되어 사용되는 경우를 종종 목격할 수 있을 것이다. 그리고 여러분이 어느 정도 EFL에 숙달된다면, 필요한 기능들을 적절한 EFL 라이브러리에서 가져다 쓸 수 있을 것이다.

좌우지간, 이렇게 함으로써 우리는 480 x 400 픽셀 사이즈의 윈도우를 하나 생성하고 100, 100 좌표에 이 윈도우를 위치시켜 화면에 보여주도록 프로그래밍을 했다. 자 그럼, 결과물을 확인해 보도록 하자.


그림 2. 윈도우 생성 결과 화면


만약 프로그램을 가동시킨 콘솔 창 이외에 윈도우가 하나 더 추가적으로 생성되었다면 여러분은 이번 예제를 성공적으로 수행했음을 확신할 수 있다. 물론 윈도우 내에는 빈 영역만 존재하므로 다소 싱겁긴 마찬가지일 수도 있다. 필자도 동의하므로 잠시 휴식을 갖은 후, 다음 절에서는 이 윈도우 내부에 뭔가를 조금 채워보도록 하자.


저작자 표시
신고