2013년 10월 4일 금요일

[번역중] GTK+에서의 화면 관리

출처 : http://www.compsci.hunter.cuny.edu/~sweiss/course_materials/csci493.70/lecture_notes/GTK_drawing.pdf

이 내용은 Prof. Stewart Weiss가 강의 노트를 위의 링크에 올려 놓은 것을 단순히 번역한 것입니다.

배경 지식

GTK에서 어떻게 화면을 그리는 것을 이해하기 위해서,
여러분들은 GTK가 어떻게 widget 들을 그리는지에대한 것을 먼저 이해 해야만 한다.
그 이유는 GTK가 widget을 그리는 방식은 여러분의 그림을 출력하는 응용 프로그램을 어떻게 디자인해야하는데 중요한 역할을 가지고 있기 때문이다.
GTK가 widget들을 어떻게 그리는지를 이해하는 것은 여러분들이 자신만의 자체 widget들을 만들 계획이 있다면 또한 필요하다.

윈도우와 윈도우 갱신

대부분의 윈도우 시스템들은 응용 프로그램의 외형 표현이 윈도우라고 불리우는 화면의 네모난 영역내에 구현되는 개념으로 디자인 되어 있다. Gnome, KDE 혹은 Explorer 같은 윈도우 시스템은 응용 프로그램의 윈도우의 보여지는 내용을 자동적으로 저장하지 않는다. 그 대신 시스템은 필요할때 응용 프로그램 자신에게 자신의 윈도우를 다시 그려야 한다고 요청한다. 예를 들면, 다른 윈도우들 밑에 깔려 있는 어떤 윈도우가 가장 위에 놓이게 된다면, 해당 프로그램은 이전에 가려진 영역을 다시 그려야한다. 윈도우 시스템은 윈도우의 가려진 부분을 다시 그리도록 해당 프로그램에게 요청할때, 그 윈도우를 포함하는 프로그램에게 exposure 라는 이벤트를 보낸다. exposure 이벤트는 자체적으로 다시 그려져야 한다는 것을 알리기 위해 윈도우 시스템이 Widget에게 보내지는 단순한 이벤트이다.

이 단락에서는 윈도우는 자동적인 갱신이 가능한 내모난 영역을 의미하고 고차원의 응용 프로그램을 의미하는 것은 아니다. 화면 갱신은 다시 그려질 필요가 없거나 다른 방식으로 보여져야하는 윈도우의 어떤 부분을 제거하는 작업이다. 이것은 어떤 것들이 다시 그려져야하는 윈도우의 영역인지 판단하는 것이다. 그림 1에서 윈도우 A는 윈도우 C 밑에 있는 윈도우 B 밑에 있다. 사용자가 윈도우 A를 화면의 앞으로 가져오는 작업을 한다면, 다시 그려져야만하는 윈도우 A의 영역은 그림에서 그림자로 되어 있는 A ∩ (B ∪ C)안에 있는 윈도우 A의 부분이다.




대부분의 윈도우 시스템들은 자식 윈도우라고 불리우는 중첩된 윈도우를 지원한다. 고차원의 윈도우는 차례로(?) 자식 윈도우를 포함할지도 모르는 많은 자식윈도우들을 포함할 지도 모른다. 예를 들면, 고차원의 윈도우는 일반적으로 메뉴 바를 위한 윈도우와 문서 영역을 위한 윈도우,  각 스크롤바를 위한 윈도우, 그리고 상태바를 위한 윈도우를 포함할 것이다. 게다가, 클릭이 가능한 버튼 (일반적으로 자체 하위 윈도우를 같는) 같은 사용자 입력을 받는 컨트롤도 포함한다. GTK+는 중첩된 윈도우들에 대한 인지 없이 윈도우 시스템에서 실행되는 것을 가능하다. 이러한 방식은 GDK가 해당 시스템하에서 존재하는 그림 그리기 기능을 제공하기 때문에 문제가 되지 않는다. 그러므로, GTK+에서 중첩된 윈도우는 항상 가능하다.

화면 갱신 반복 작업

일반적으로 화면 갱신 반복 작업은 GTK+가 하부에서 작동하는 윈도우 시스템으로 부터 exposure 이벤트를 받을때 시작된다. 예를 들면 사용자가 윈도우를 다른 윈도우 위로 드레그하게 되었을때가 대표적이다. 이러한 경우 윈도우 시스템은 자체적으로 다시 그릴 필요가 있는 아래에 있는 윈도우에게 알려 주게 될 것이다. 또한, 이러한 화면 갱신 반복 작업은 윈도우 내의 Widget 자체가 다시 그려져야할 필요성이 있을때 시작되어 질 수도 있다. 예를 들면, 사용자가 GtkEntry Widget에 글자를 적어 넣을 때, GtkEntry Widget은 GTK+에게 자신을 다시 그려야 한다는 요구을 접수해 줄 것을 요청한다. 다른 말로, Widget은 윈도우 시스템 자체가 자신을 다시 그리는 event를 보내도록 요청하는 기능을 호출 할 수 있다. 이것은 여러분이 바로 볼 수 있을 만큼 프로그램을 단순화 시킨다.

GdkWindow는 GTK+가 실행되는 상황에서 맨 밑에서 있는 윈도우 시스템의 윈도우를 대표한다. X11 에서 이것은 (X) Window에 해당하고 Win32의 경우 HANDLE에 해당한다. 이것은 이러한 윈도우들을 위한 event를 생성하는 윈도우 시스템이다. 이러한 event 들은 GDK 인터페이스로 전달된다. 이는 이러한 원래의 event들을 GdkEvent 의 형태로 변환하고, GTK 층으로 전달한다. 그리고 그 다음으로 GTK widget 층은 특정 GdkWindow에 해당하는 widget을 검색하고, 그 widget에 연계되는 event 시그널들을 발생한다.

GdkWindow에 있거나 없는 Widget들

모든 단일 widget은 그 자신의 GdkWindow을 가졌다면, 화면 갱신 작업은 당연히 이루어 질 것이다; 밑에서 동작하는 윈도우 시스템은  다시 그려질 필요가 있는 윈도우에 해당하는 GDK에게 통지한다. 그리고 GDK는 특정 GdkWindow를 위한 exposure event를 GTK 층에 전달하고 GTK 층은 그 widget을 위한 exposure event 시그널을 발생 시키게 될 것이다. 그러면 widget의 exposure event 핸들러는 widget을 다시 그리게 될  것이다. 그 밖의 다른 것은 할 것이 없다; 윈도우 시스템은 필요로 하는 각각의 윈도우를 위한 exposure event를 발생하고, 그에 해당하는 각 widget은 자신을 다시 그리게 될 것이다.

그러나, 실제적으로 widget들이 자신의 GdkWindow를 갖지 못하도록 하는 것이 더 효과적이고 편리하기 때문이다. 그러나 대신 상위 widget으로 부터 하나를 공유 한다. 모든 widget은 widget이 자신의 윈도우를 갖는지 않 갖는지를 알리는 GTK_NO_WINDOW를 갖는다. widget이 윈도우를 갖지 않는다면 반드시 생성될때 이 플레그(GTK_NO_WINDOW)를 true로 설정하여 GTK에게 갖고 잊지 않다는 것을 알려야 한다. (GTK+ 2.18 혹은 그 이후의 버전에서 widget은 gtk_widget_set_has_window()의 두 번째 매개변수를 false로 설정하여 호출한다.) 이것은 프로그래머로써 해야하는 것은 아니다. 이것은 widget의 생성시에 이루어져야 하는 widget의 구현자가 해야하는 작업이다. 프로그래머로써 여러분들은 특정 widget이 GdkWindow를 가지고 있는지 아닌지를 GTK_NO_WINDOW의 값을 검사하거나 GTK+-2.18 이후의 버전에서는 gtk_widget_get_has_window() 함수를 호출하여 알아 볼 수 있다. GdkWindow를 갖지 않는 widget들은 no-window widget 혹은 GTK_NO_WINDOW widget으로 불린다.

왜 여러분은 윈도우가 없는 widget을 필요로 하게 될까요? 두 가지 큰 이유가 있다.

  • 어떤 widget들은 부분적으로 그려져야할 지라도 상위 widget의 배경이 보여지기를 원할지도 모른다. 예를 들면 다음과 같은 경우이다. label이 themed texture를 가지고 있는 button위에 위치하고 있을때, 각각의 widget이 윈도우를 가지고 있고 배경을 가지고 있다면, 그들을 둘러싸는 것을 고려하여 보여지는 것이 멈추게 될지 모르기 때문에 label은 나쁘게 보여질 것이다. 

  • GDK 윈도우들의 총 개수를 줄이는 것은 GDK와 GTK 사이의 데이터 교류의 양을 줄인다. 그래서 윈도우가 없는 widget을 구현할 수 있다면 성능은 한층 더 좋아 진다. 

계층적인 그리기

이와 같은 사항을 이해 한다면, 우리는 우리의 관심을 GTK가 exposure event를 받을 때 발생하는 일련의 과정을 이해하는 것으로 돌려 보자. 가장 맨 처음 과정은 GTK가 event를 받은 윈도우에 해당하는 Widget을 찾는 것이다. 이 Widget은 no-window widget일 수 없다. (만약 그렇다면 event를 받을 만한 자신의 윈도우를 아예 갖을 수 없기 때문에 처리할 event라는 것은 존재 하지 않는다.) 이와 같은 Widget들은 배경을 그리는 것을 시작한다. 그런 다음 해당 Widget이 container widget이라면, 윈도우를 갖지 않는 자식 Widget들 각각에게 자신들을 다시 그리도록 이야기 해준다. 이러한 과정은 원래의 Widget의 윈도우가 없는 모든 상속자들에게 반복적으로 적용된다.

이러한 과정은 (GTK_NO_WINDOW가 true로 설정된 Widget들인) 자신의 윈도우들을 갖는 Widget에게 전달되어 지지는 않는다. 이것은  이러한 어떤 Widget들이 다시 그려져야한다면, 윈도우 시스템은 그들에 해당하는 GDK 윈도우에 exposure events를 보내게 될 것이기 때문이다. 이와같이, GTK+의 관점에서 이들에게 exposure를 전달할 필요는 없다.

예를 들자면 (GTK API 문서로 부터)

다음 예제는 온라인상의 Gnome/GTK +의 문서 (https://developer.gnome.org/gtk3/unstable/chap-drawing-model.html에 있는 GTK+ Drawing Model) 로부터 가져왔다. 이것은 어떻게 상위 단계의 윈도우가 윈도우가 없는 자식들만 갖을때 자신을 다시 그리는지를 보여준다. 


  1. 최외각의 두꺼운 네모는 상위 레벨의 GtkWindow이다. 이것은 GTK_NO_WINDOW인 Widget은 아니다. 그러므로, exposure event를 반드시 받게된다. 왜냐하면, GDK로부터 생성되었기 때문이다. 먼저 GtkWindow는 그 자신의 배경을 그리게 될 것이다. 그런다음, 자식 윈도우에게 자신을 다시 그릴 것을 요청한다. 여기서는 2번으로 표시된 점선으로된 네모가 이에 해당한다. 
  2. 점선으로된 네모는 GtkVBox이다. 이것은 GtkWindow의 단독 자식을 형성한다. GtkVBox류는 윈도우를 갖지 못하는 Widget이다. 그들 자체로는 아무것도 그릴 것이 없는 그져 Layout Container이다. 그레서 GtkVBox는 그릴 것이 아무것도 없다. 그러나, 대신 그릴 것을 가지고 있는 자식들에게는 요청을 한다. 그 자식들은 3번과 6번에 해당한다.
  3. 얇은 네모는 GtkFrame이다. 물론 윈도우를 가지고 있지 않고 두 개의 자식들을 가지고 있다; 4번으로 표시된 프레임을 위한 Label과 5번으로 표시된 또 다른 내부의 Label이다. 먼저, 프레임은 그 자신의 중간이 짤린 박스를 그리게 되고, 프레임의 Label과 그 안의 자식들에게 그들 자신을 다시 그리도록 요청한다. 
  4. 프레임의 Label은 자식을 갖지 않는다. 그레서 그져 문자만 그린다. "Frame Label"
  5. 내부의 Label은 자식을 갖지 않는다. 그레서 그져 문자만 그린다. "This is some text inside the frame!"
  6. 점선으로 된 네모는 GtkHBox를 의미한다. 다시 말해서, 이것은 스스로 아무것도 그릴 것이 없지만, 그릴 것을 가지고 있는 그의 자식들에게는 요청을 한다. 그 자식들은 7번과 9번에 해당한다.
  7. 얇은 네모는 8번으로 표신된 자식을 하나만 가지고 있는 GtkButton이다. 먼저, 버튼은 그 자신의 박스를 그리고, 자식들에게 자신을 그릴 것을 요청하게 된다.
  8. 이것은 자식을 가지지 못하는 text label이다. 그레서 그져 문자만 그리면 된다.: "Cancel".
  9. 7번과 유사하게 이것은 10번으로 표시된 자식 하나만 가지고 있는 GtkButton이다. 먼저, 버튼은 박스를 그리고 그려야 할 것을 가지고 있는 자식들에게 요청을 하게된다.
  10. 8번과 유사하게 이것은 자식을 갖지 않는 GtkLabel이다. 그레서 자신의 문자만 그리면 된다: "OK".

위의 과정은 어떻게 Widget이 다시 그려지는지 보여준다. 이러한 그리기작업은 모두 화면에 직접적으로 위치해야 한다면, 다양한 부분들이 다시 그려짐에 따라 깜박거림이 발생하게 될 것이다. 이것은 부분적으로 배경과 장식용 요소같은 많은 영역들이 반복적으로 다시 그려지게 되기 때문이다.
그러므로, GTK는 GDK레벨에서 double buffering이라는 개념을 구현하게된다.

Double Buffering

double buffering에서 두개의 캔퍼스들이 존재한다: 화면에 안보이는 캔퍼스와 화면에 보이는 캔퍼스. 그리는 작업은 안보이는 캔퍼스위에서 발생하고 그러고 나서 화면에 그려지게 된다. 그리고 화면에 있었던 캔퍼스는 다음의 그리기 작업을 위해서 화면에 안보이는 캔퍼스가 된다. GTK에서 이런 double buffering은 Widget들에게 보이지 않는 부분이다; 그들은 일반적으로 그들이 화면에 안보이는 버퍼에 그려지고 있는 알지 못한다. 그들은 그져 그들의 일반적인 그리기 명령을 이슈화 할 뿐이다. 그리고 버퍼는 모든 그리기 작업이 완료되었을때 윈도우 시스템에게 돌려주게 된다.

GDK에서 두 개의 기본적인 함수들이 double-buffering의 작동원리의 핵심을 형성한다.
  • gdk_window_begin_paint_region()
  • gdk_window_end_paint()
첫째 함수는 GdkWindow에세 그리기 작업을 위해서 화면에 보이지 않는 버퍼를 생성할 것을 요청한다. 이러한 시스템에 가해지는 모든 차후의 그리기 과정들은 자동적으로 이 버퍼로 재 지정되게 된다. 두 번째 함수는 보이는 화면에 버퍼를 실질적으로 그리게 되고 버퍼를 해제하고 되돌려 주게된다. 

자동화된 double buffering

이것은 exposure 핸들러에 초반부에 gdk_window_begin_paint_region()을 호출하고 마지막 부분에서 gdk_window_end_paint()를 호출하는 모든 Widget들에게는 적용되지 않는다. 이걸 좀더 쉽게 하기 위해서, 모든 GTK+ Widget들은 


............ 번역중 ......

댓글 없음:

댓글 쓰기