레이블이 GUI인 게시물을 표시합니다. 모든 게시물 표시
레이블이 GUI인 게시물을 표시합니다. 모든 게시물 표시

2022년 7월 27일 수요일

인공지능+영상처리+OceanOptics USB2000+Flir Camera 를 위한 시스템 설정

# 작업 시스템

윈도우즈 11, 64bits

# 설치 소프트웨어

## Python 3.8.xx

64bit 버전으로 설치하고, 현 시점에 최신 버전은 3.10이지만, 인공지능을 위한 tensorflow가 지원하는 최신 버전인 3.9.13버전이고 Spinnaker라는 Camera 소프트웨어는 3.8.xx버전을 지원한다.

### pip(Python Install Package) 설치

설치하고 가장 먼저해야할 작은 pip(Python Install Package)를 업그레이드 하는 것이다.

PS D:\python -m pip install --upgrade pip

그럼 tensorflow를 설치할 수 있는 환경이 조성되었다.

### tensorflow 와 tensorflow-cpu 설치 

PS D:\python -m pip install tensorflow

설치된 tensorflow를 확인해보자.

PS D:\> python

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import tensorflow as tf

2022-07-27 11:53:34.911913: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found

2022-07-27 11:53:34.912570: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.

>>> quit()

CUDA 런타임 라이브러리가 없다고 에러를 발생시킨다. 그래서 cpu전용 구동할 수 있는 tensorflow-cpu를 추가 설치한다.

PS D:\> python -m pip install tensorflow-cpu

다시, 설치된 tensorflow의 버전을 확인해 보자.

PS D:\> python

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import tensorflow as tf

>>> tf.__version__

'2.9.1'

>>> quit()

### PySide6 설치

PS D:\> python -m pip install PySide6

설치된 버전 확인한다.

PS D:\> python

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import PySide6

>>> PySide6.__version__

'6.3.1'

>>> quit()

### OpenCV 설치

PS D:\> python -m pip install opencv-python

설치된 버전 확인한다.

PS D:\> python

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import cv2

>>> cv2.__version__

'4.6.0'

>>> quit()

## Visual Studio Code 설치

아래의 사이트를 방문해서 해당 시스템에 알맞은 설치파일을 다운로드 받는다.

https://code.visualstudio.com/download

설치 후 PySide6 예제를 코딩하고 실행해 본다.


## FLIR 카메라를 위한 환경 설정

과거의 Point Gray사의 카메라를 사용하기 위해서 현재는 FLIR로 합병된 Spinnaker SDK를 설치해야한다.

SpinnakerSDK_FULL_2.7.0.128.x64.exe 파일을 다운로드하고 설치한다.

그리고,  Python 에서 카메라를 운용하기 위해서 spinnaker_python-2.7.0.128-cp38-cp38-win_amd64.zip 파일도 다운로드하고, 다음의 명령어로 설치한다.

PS D:\> python -m pip install .\spinnaker_python-2.7.0.128-cp38-cp38-win_amd64.whl

그리고 설치확인한다.


import os
import PySpin
import sys

if __name__ == '__main__':
    # Retrieve singleton reference to system object
    system = PySpin.System.GetInstance()

    # Get current library version
    version = system.GetLibraryVersion()
    print('Library version: %d.%d.%d.%d' % (version.major, version.minor, version.type, version.build))

    # Retrieve list of cameras from the system
    cam_list = system.GetCameras()

    num_cameras = cam_list.GetSize()

    print('Number of cameras detected: %d' % num_cameras)


## Ocean Optics의 USB 4000을 위한 환경 설정

사전설치 사항으로 SpectraSuite가 설치되어 WinUSB 드라이버가 작동하고 있어야 한다.

그렇지 않다면, libusb-win32를 설치하고 수작업으로 드라이버를 설정하고 설치해야 한다.

아직은 seabreeze가 libusb-1.0을 지원하고 있지는 않는 것 같아. 위의 방법이 유일한 것 같다.

그럼 python에서 usb4000을 운용할 seabreeze를 설치한다.

PS D:\> python -m pip install seabreeze

설치된 버전을 확인한다.

PS D:\> python

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import seabreeze

>>> seabreeze.use('cseabreeze')

>>> from seabreeze.spectrometers import list_devices, Spectrometer

>>>  devices = list_devices()

  File "<stdin>", line 1

    devices = list_devices()

IndentationError: unexpected indent

>>> devices = list_devices()

>>> devices

[<SeaBreezeDevice USB4000:USB4U30285>]

설치는 잘 된 것 같다. 



2019년 1월 24일 목요일

Install wxPython 4.0.4 in raspberry pi

1. preparation
1.2. sdhc >= 16GB installed raspbian
1.3. Install dependencies
# sudo apt install libjpeg-dev libtiff5-dev libnotify-dev libgtk2.0-dev libgtk-3-dev libsdl1.2-dev libgstreamer-plugins-base0.10-dev libwebkitgtk-dev freeglut3 freeglut3-dev

1.4. Turn off the GUI boot chaning to CLI
Preferences -> Raspberry Pi Configuration -> At Boot, select To CLI


2. Install wxpython 4.0.4
# sudo pip3 install wxpython

3. Wait and do not see .....

4. return to GUI
#sudo raspi-config
 -> 3. BootOptions -> B1 Desktop / CLI -> B4 Desktop Autologin -> double 'TAB' -> Finish

# reboot

5. Check the installation
$ python3

>>> import wx
>>> wx.__version__
'4.0.4'



2018년 4월 11일 수요일

Install QTCreator in Debian

# apt install qtcreator qtbase5-dev libqt5serialport-dev


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들은 


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

2013년 8월 11일 일요일

GTK+-3.0과 OpenGL을 같이 사용할 수 있는 방법

GTK+-2.x 버전에서는 gtkglext이라는 것이 있어서 사용하기 편했는데

GTK+-3.0을 위한 gtkglext3은 아직 개발 중에 있다.

하지만, 3가지 정도의 회피 방법이 있다.

1. 아직 개발 중인 gtkglext3을 따로 얻어서 사용하는 방법

https://github.com/tdz/gtkglext

조금 불편하고 불안정 하지만 가장 손쉬운 방법이 아닌가 싶다.

사용 방법은 다음과 같다. 우선 소스코드를 다운 받고

기본적으로 libgtk-3.0-dev, libgtk-3.0-doc, gtk-doc-tools 등의 패키지가 설치 되어 있어야 한다.

그럼 다운 로드한 gtkglext의 디렉토리로 이동하자.

# cd ~/gtkglext-VERSION

README 파일을 읽어 보면 autogen.sh 스크립트를 이용해서 설치할 것을 권고한다.

하지만, bootstrap을 이용해서 설치용 스크립트를 생성하자.

# env ACLOCAL_FLAGS='-I /pkgconfig/share/aclocal -I /gtk/share/aclocal' ./bootstrap --prefix=${HOME}/gtkglext --enable-debug=yes

기본적인 준비는 갖추어 졌다. 다음과 같이 설정하고, 컴파일하고 설치하면 된다.

# ./configure
# make
# make install

마지막으로 /usr/local/include에서 잘 설치가 되어 있는지 확인하자.

참조 문서 : http://spectrum3d.sourceforge.net/documentation.html

2. OpenGL을 지원하는 libsdl을 사용하여 GTK+에 심어 넣는 방법.

이 방법은 두 종류의 용도가 겹치는 이종 라이브러리를 결합해야 하기 때문에 코드가 지져분해진다.

사용 예는 Gtk+-2.x 버전에 libsdl을 사용하는 방법이긴 하지만 거의 동일할 것으로 여겨진다.

출처 : http://sdl.5483.n7.nabble.com/SDL-WINDOWID-embedding-SDL-into-gtkmm-td13354.html

 #include <gtk/gtk.h>   
 #include <SDL.h>   
   
 /*   
  * GTK+ SDL Window reparenting test   
  * written by Mikael Eriksson <[hidden email]>   
  *   
  * Compile with:   
  * cc -g -o gtk-sdl-test gtk-sdl-test.c `pkg-config --cflags --libs gtk+-2.0 sdl`   
  */   
   
 gboolean do_sdl_stuff( gpointer data ) {   
     SDL_Event event;   
     SDL_Surface *display = data;   
   
     while ( SDL_PollEvent( &event ) ) {   
         /* Handle quit event, not sure if this will ever appear */   
         if ( event.type == SDL_QUIT ) return FALSE;   
         /* Handle clear userevent */   
         if ( event.type == SDL_USEREVENT && event.user.code == 0 ) {   
             SDL_FillRect( display, NULL, 0 );   
             SDL_Flip( display );   
         }   
         /* Handle draw rect userevent */   
         if ( event.type == SDL_USEREVENT && event.user.code == 1 ) {   
             SDL_Rect rect;   
               
             rect.x = rand() % 320;   
             rect.y = rand() % 240;   
             rect.w = rand() % 100 + 10;   
             rect.h = rand() % 100 + 10;   
   
             printf( "%ux%u+%u+%u\n", rect.w, rect.h, rect.x, rect.y );   
   
             SDL_FillRect( display, &rect, SDL_MapRGB( display->format, rand()%255, rand()%255, rand()%255 ) );   
             SDL_Flip( display );   
         }   
     }   
   
     return TRUE;   
 }   
   
 void button_clicked( GtkButton *button, gpointer data ) {   
     /* Put draw rect userevent on queue */   
     SDL_Event event;   
     event.type = SDL_USEREVENT;   
     event.user.code = 1;   
     SDL_PushEvent( &event );   
 }   
   
 void clear(void) {   
     /* Put clear userevent on queue */   
     SDL_Event event;   
     event.type = SDL_USEREVENT;   
     event.user.code = 0;   
     SDL_PushEvent( &event );   
 }   
   
 int main( int argc, char **argv ) {   
     GtkWidget *window, *sock, *box, *button;   
     char winhack[1024];   
     SDL_Surface *display;   
   
     gtk_init( &argc, &argv );   
   
     /* Create widgets */   
     window = gtk_window_new( GTK_WINDOW_TOPLEVEL );   
     sock = gtk_socket_new();   
     box = gtk_vbox_new( FALSE, 0 );   
     button = gtk_button_new_with_label( "Press me!" );   
   
     /* Setup socket widget */   
     gtk_widget_set_size_request( sock, 320, 240 );   
     gtk_box_pack_start( GTK_BOX(box), sock, TRUE, FALSE, 0 );   
   
     /* Setup button */   
     gtk_container_set_border_width( GTK_CONTAINER(button), 5 );   
     g_signal_connect( G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), display );   
     gtk_box_pack_start( GTK_BOX(box), button, FALSE, FALSE, 0 );   
   
     /* Setup window */   
     gtk_container_add( GTK_CONTAINER(window), box );   
     g_signal_connect( G_OBJECT(window), "delete-event", G_CALLBACK(gtk_main_quit), NULL );   
     gtk_widget_show_all( window );   
   
     /* Make SDL windows appear inside socket window */   
     snprintf( winhack, sizeof winhack, "SDL_WINDOWID=%i", gtk_socket_get_id( GTK_SOCKET(sock) ) );   
     SDL_putenv( winhack );   
   
     /* Init SDL */   
     if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {   
         fprintf( stderr, "%s.\n", SDL_GetError() );   
         return EXIT_FAILURE;   
     }   
   
     /* Create SDL window */   
     if ( !( display = SDL_SetVideoMode( 320, 240, 0, 0 ) ) ) {   
         fprintf( stderr, "%s.\n", SDL_GetError() );   
         return EXIT_FAILURE;   
     }   
   
     /* Clear the SDL window */   
     clear();   
   
     /* Setup SDL event handlig timer */   
     g_timeout_add( 100, do_sdl_stuff, display );   
   
     /* Start main loop */   
     gtk_main();   
   
     return 0;   
 }   

3. 마지막으로 새로운 Gtk+-3.0 을 위한 opengl binding을 새로 만드는 방법.

이 방법도 이미 구현되어져 있다. 조금만 시간이 많다면 시도해 볼만한 방법이 있다.

https://github.com/andrepiske/tegtkgl

위의 링크를 찾아 가보면 소스 코드를 받아 볼 수 있다.

그리고 마지막으로 위의 두 가지의 방법을 사용한 예로 spectrum3D라는 프로젝트가 있다.

http://spectrum3d.sourceforge.net/

마찬가지로 시간이 많다면 살펴보고 시도해 볼만 하다.

무엇보다도 gtkglext이 debian 패키지로 빨리 나오길 바란다.

2011년 7월 17일 일요일

Use GdkPixbuf in GTK+

At first it needs to create GtkImage on GtkViewport. Then, we can use GdkPixbuf-embedded GtkImage.

static void gdk_pixbuf_clear(GdkPixbuf *pixbuf)
{
   int x, y;
   int width, height, rowstride, n_channels;
   guchar *pixels, *p;

   n_channels = gdk_pixbuf_get_n_channels(pixbuf);
   g_assert(gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB);
   g_assert(gdk_pixbuf_get_bits_per_sample(pixbuf) == 8);
   g_assert(gdk_pixbuf_get_has_alpha(pixbuf));
   g_assert(n_channels == 4);

   width = gdk_pixbuf_get_width(pixbuf);
   height = gdk_pixbuf_get_height(pixbuf);
   rowstride = gdk_pixbuf_get_rowstride(pixbuf);
   pixels = gdk_pixbuf_get_pixels(pixbuf);

   for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
         p = pixels + y*rowstride + x*n_channels;
         *(p+0) = 255; // red channel
         *(p+1) = 0; // green channel
         *(p+2) = 0; // blue channel
         *(p+3) = 255; // alpha channel
      }
   }
}


....

image = gtk_image_new();

....

pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 640, 480);
g_assert(pixbuf);

gdk_pixbuf_clear(pixbuf);

gtk_image_set_from_pixbuf(image, pixbuf);

...

2011년 6월 13일 월요일

[번역] GTK+ Tutorial

출처 : http://zetcode.com/tutorials/gtktutorial/

* 소개
* 첫번째 프로그램
* 메뉴와 툴바
* 모양세 관리
* 이벤트
* 다이얼로그
* 위젯
* 위젯 II
* GtkTreeView 위젯
* GtkTextView 위젯
* 사용자 GTK+ 위젯

이것은 GTK+ 프로그래밍 지침서이다. GTK+ 사용 지침서는 C 프로그래밍 언어에 관한 것만 쓰여졌다. GTK+ 사용 지침서는 새내기, 혹은 중급 정도의 프로그래머에 알맞도록 작성되어 졌다. Cario graphic 사용지침서도 살펴보는 것을 꼭 잊지 말아달라.

GTK+ 소개

이 사용 지침서에 관해서

이것은 소개를 목적으로하는 GTK+ 프로그래밍 사용지침서이다. 이 사용지침서는 C 프로그래밍 언어를 대상으로 다루고 있다. 모든 내용은 리눅스 시스템 상에서 작성되고 테스트 되었다. GTK+ 프로그래밍 지침서는 초심자 및 중급 프로그래머에 적합하도록 작성되었다.

GTK+

GTK+는 그래픽 사용자 인터페이스를 작성하기 위한 라이브러리다. 이 라이브러리는 C 프로그래밍 언어에서 작성되었다. GTK+ 라이브러리는 또한 GIMP 툴킷이라고도 불린다. 원래, 이 라이브러리는 GIMP 영상 조작 프로그램을 개발할때 작성되었다. 그 이후로 GTK+는 리눅스와 BSD 유닉스에서 가장 인기있는 툴킷중 하나가 되었다. 오늘날 오픈 소스 세계의 대부분의 GUI 소프트웨어는 QT혹은 GTK+로 작성되었다. GTK+는 Object Oriented 응용 프로그램 인터페이스이다. GObject는 다양한 다른 프로그래밍 언어를 위한 language binding을 생성할 수 있게 도와 준다. language binding은 C++, Python, Perl, Java, C# 그리고 다른 많은 언어에 대해서 존재한다.

GTK+ 자체는 다음과 같은 라이브러리에 의존하고 있다.

* Glib
* Pango
* ATK
* GDK
* GdkPixbuf
* Cairo

Glib는 일반적인 사용 용도의 라이브러리이다. 이것은 데이터 타입, 문자열관련 도구, 에러 처리, 메세지 추적, 쓰레드 관련 그리고 다른 유용한 프로그래밍 특빙들을 제공한다. Pango는 국제화를 가능하게 해주는 라이브러리이다. ATK는 사용자 접근을 돕는 툴킷이다. 이 툴킷은 물리적으로 사용자가 컴퓨터를 작동할 수 있도록 돕는 도구를 제공한다. GDK는 물밑에서 작동하는 그래픽 시스템에 의해서 제공되는 저수준의 그리기와 윈도우 함수와 관련된 wrapper이다. 최근에는 대부분의 이기능은 Cairo 라이브러리로 위임되었다. GdkPixbuf 라이브러리는 영상을 읽고 pixel buffer를 조작하는 툴킷이다. Cairo는 2D 벡터 그래픽을 위한 라이브러리이다. 이것은 2.8버전 이후로 GTK+에 포함되어졌다.

Gnome과 XFce 데스크탑 환경은 GTK+ 라이브러리를 사용해서 작성되었다. SWT와 wxWidgets 또한 GTK+를 사용하는 프로그래밍 frameworks로 널리 알려져있다. GTK+를 사용하는 많은 소프트웨어 응용프로그램중에 Firefox나 Inkscape이 있다.

GTK+ 응용프로그램 컴파일하기

GTK+ 응용프로그램을 컴파일하기 위해선 pkt-config라는 유용한 도구를 사용한다. pkg-config는 설치된 라이브러리의 metadata를 되돌려준다. 간단하게 말하자면, 특정 라이브러리를 사용하고자할 때, 우리가 필요로하는 include 파일과 라이브러리파일을 알려준다. pkt-config 프로그램은  특별한 metadata 파일에서 설치된 패키지에 관한 정보를 얻어온다.

gcc -o simple simple.c `pkg-config --libs --cflags gtk+-2.0`


자 그럼 여기에 어떻게 간단한 프로그램을 컴파일 할 수 있는지 보여준다. 소스코드는 simple.c라는 하나의 파일로만 구성되어 있다.

$ pkg-config --cflags gtk+-2.0
-I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/atk-1.0 
-I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 
-I/usr/lib/glib-2.0/include -I/usr/include/freetype2 -I/usr/include/libpng12  


이 리스트는 GTK+ 프로그래밍에 필요한 모든 include 파일들을 보여주고 있다.

$ pkg-config --libs gtk+-2.0
-lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgdk_pixbuf-2.0 -lm -lpangocairo-1.0 
-lfontconfig -lXext -lXrender -lXinerama -lXi -lXrandr 
-lXcursor -lXfixes -lpango-1.0 -lcairo -lX11 -lgobject-2.0 
-lgmodule-2.0 -ldl -lglib-2.0 


그리고 이 리스트는 필요한 라이브러리들을 보여주고 있다.

참고 자료

* gtk.org
* gtkforums.com
* GTK+ / Gnome application development


GTK+를 이용한 첫번째 프로그램

GTK+ 프로그래밍 사용 지침서의 이 부분에서는 GTK+로 된 첫번째 프로그램을 작성할 것이다.

간단한 예제

아주 기본 윈도우를 갖는 간단한 예제로 시작해 보자.

#include 

int main( int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_show(window);

  gtk_main();

  return 0;
}


이 예제는 화면에 기본 윈도우를 보여줄 것이다.

gcc -o simple simple.c `pkg-config --libs --cflags gtk+-2.0`


이것은 예제를 컴파일 하는 방법을 보여준다.

gtk_init(&argc, &argv);


여기선 GTK+ 라이브러리를 초기화 한다.

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);


GtkWindow 위젯을 생성한다. 이 윈도우의 타입은  GTK_WINDOW_TOPLEVEL이다. 최상위 윈도우는 titlebar와 경계선만 가지고 있다. 이것들은 윈도우 관리자에 의해서 관리된다.

gtk_widget_show(window);


위젯을 생성한 후 보여주는 작업이 필요한다.

gtk_main();


이 코드에서는 GTK+의 메인 루프에 진입한다. 이 시점으로부터 응용프로그램은 발생하는 이벤트를 앉아서 기다리게 된다.















그림: 간단

윈도우를 화면의 중심에 놓기

우리는 윈도우 자체를 위치시킬 수 없다면, 윈도우 관리자가 우리를 위해서 위치시켜 줄 것이다. 다음 예제는 윈도우를 중심에 위치시키는 것이다.

#include 

int main( int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Center");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_widget_show(window);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_main();

  return 0;
}

이 예제에서 윈도우를 중심에 위치 시키고 윈도우의 타이틀과 크기를 지정한다.

gtk_window_set_title(GTK_WINDOW(window), "Center");


gtk_window_set_title() 함수는 윈도우 타이틀을 지정할 것이다. 타이틀을 지정하지 않으면, GTK+는 소스파일의 이름을 타이틀로 사용할 것이다.

gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);


이 코드는 윈도우의 크기를 230x250 픽셀로 지정한다. 주의할 것은 윈도우 관리자에 의해서 제공되는 장식물을 제외한 클라이언트 영역에 대해서만 이야기 하고 있다는 것이다.

gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);


이 코드는 윈도우를 중심에 위치시킨다.

g_signal_connect_swapped(G_OBJECT(window), "destroy",
     G_CALLBACK(gtk_main_quit), NULL);


이전의 예제에서 윈도우는 x 버튼을 클릭했을 때 완벽하게 종료되지 않았다. 명령행에서 예제를 실행시켜 보면 확인해 볼 수 있을 것이다. 윈도우는 기본적으로 destroy 시그널에 반응 하지 않는다. 그래서 우리는 destroy 시그널을 gtk_main_quit()함수에 연결시켜서 응용프로그램을 분명하게 종료해주어야 한다.

응용프로그램의 아이콘

다음 예제에서 우리는 응용프로그램의 아이콘을 살펴본다. 대부분의 윈도우 관리자는 titlebar와 taskbar 의 왼쪽 구석에 아이콘을 표현한다.

#include 

GdkPixbuf *create_pixbuf(const gchar * filename)
{
   GdkPixbuf *pixbuf;
   GError *error = NULL;
   pixbuf = gdk_pixbuf_new_from_file(filename, &error);
   if(!pixbuf) {
      fprintf(stderr, "%s\n", error->message);
      g_error_free(error);
   }

   return pixbuf;
}

int main( int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "icon");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_icon(GTK_WINDOW(window), create_pixbuf("web.png"));
  gtk_widget_show(window);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_main();

  return 0;
}


이 코드 예제는 응용프로그램 아이콘을 보여준다.

gtk_window_set_icon(GTK_WINDOW(window), create_pixbuf("web.png"));


gtk_window_set_icon()은 윈도우를 위한 아이콘을 표출한다. create_pixbuf()는 png 영상파일로부터 GdkPixbuf를 생성한다.

pixbuf = gdk_pixbuf_new_from_file(filename, &error);


문서에 따르면 gdk_pixbuf_new_from_file() 함수는 파일에서 영상을 읽어서 새로운 pixbuf를 생성한다. 파일 형식은 자동적으로 감지한다. NULL이 리턴되면, error가 설정될 것이다.



그림: 아이콘

증가하기와 감소하기

우리는 세개의 자식 위젯을 갖는 GTK+ 프로그래밍 지침서의 첫번째 장을 예제로 마무리한다.  두개의 버튼과 하나의 라벨. 라벨을 정수의 숫자를 가지고 있을 것이다. 그리고 버튼은 이 숫자를 증가시키고 감소시킬 것이다.
#include 

gint count = 0;
char buf[5];

void increase(GtkWidget *widget, gpointer label)
{
  count++;

  sprintf(buf, "%d", count);
  gtk_label_set_text(label, buf);
}

void decrease(GtkWidget *widget, gpointer label)
{
  count--;

  sprintf(buf, "%d", count);
  gtk_label_set_text(label, buf);
}

int main(int argc, char** argv) {

  GtkWidget *label;
  GtkWidget *window;
  GtkWidget *frame;
  GtkWidget *plus;
  GtkWidget *minus;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 180);
  gtk_window_set_title(GTK_WINDOW(window), "+-");

  frame = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), frame);

  plus = gtk_button_new_with_label("+");
  gtk_widget_set_size_request(plus, 80, 35);
  gtk_fixed_put(GTK_FIXED(frame), plus, 50, 20);

  minus = gtk_button_new_with_label("-");
  gtk_widget_set_size_request(minus, 80, 35);
  gtk_fixed_put(GTK_FIXED(frame), minus, 50, 80);

  label = gtk_label_new("0");
  gtk_fixed_put(GTK_FIXED(frame), label, 190, 58); 

  gtk_widget_show_all(window);

  g_signal_connect(window, "destroy",
      G_CALLBACK (gtk_main_quit), NULL);

  g_signal_connect(plus, "clicked", 
      G_CALLBACK(increase), label);

  g_signal_connect(minus, "clicked", 
      G_CALLBACK(decrease), label);

  gtk_main();

  return 0;
}


이 코드 예제는 GtkLabel에 있는 값을 증가시키거나 감소시킨다.

g_signal_connect(plus, "clicked", 
     G_CALLBACK(increase), label);


우리는 increase() 콜백함수를 plus 버튼에 연결한다. 주의할 것은 콜백함수에 매개변수로 label을 보낸다는 것이다. 콜백함수 내부에서 이 label을 가지고 작업하게 될 것이다.
count++;

 sprintf(buf, "%d", count);
 gtk_label_set_text(label, buf);


increase 콜백함수 내부에서 우리는 counter의 값을 증가시키고, 숫자 값을 textual 데이터로 만들고 label을 업데이트한다.














그림 : 증가하기 -  감소하기

이 장에서 우리는 약간의 간단한 GTK+ 프로그램을 소개했다.


GTK+에서의 메뉴와 툴바

GTK+ 프로그램 지침서의 이 장에서 우리는 메뉴와 툴바를 다루어 볼것이다.

menubar는 GUI 응용프로그램의 매우 공통적인 부분 중 하나이다. 이것은 다양한 메뉴에 위치한 명령어의 모음이다. 콘솔 응용프로그램에서는 모든 이런 비밀의 명령을 기억해야하는 반면에 여기선 대부분의 명령어의 모음을 논리적인 부분으로 다룬다. 새로운 프로그램을 습득하는데 드는 시간을 많이 만축시키주는 승인된 표준이다.

간단한 메뉴 예제

우리의 첫번째 예제에서 하나의 파일 메뉴를 갖는 menubar를 만들어 볼 것이다. 이 메뉴는 하나의 메뉴 항목만 갖는다. 이 항목을 선택하면 응용프로그램은 종료된다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *filemenu;
  GtkWidget *file;
  GtkWidget *quit;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "menu");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  filemenu = gtk_menu_new();

  file = gtk_menu_item_new_with_label("File");
  quit = gtk_menu_item_new_with_label("Quit");

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), filemenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), quit);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 3);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(quit), "activate",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}



menubar를 생성하는 것은 약간 혼란 스럽다. menubar와 메뉴 둘다 menu shell이라 불리는 동일한 위젯에서 생성되는 것을 유념하자.  메뉴 항목들은 메뉴를 위한 하위 개념으로써만 유용하다. 그들은 서브메뉴를 구현할때도 사용되어진다.

menubar = gtk_menu_bar_new();
filemenu = gtk_menu_new();


 이 코드에서 우리는 메뉴와 menubar를 생성한다.

gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), filemenu);


이 코드 라인은  파일 메뉴를 구현한다. 그 내부는 menubar가 menu shell로 되어 있다. 파일 메뉴는 또한 menu shell이기도 한다. 이것이 우리가 파일 메뉴를 서브메뉴나 subshell로 보는 이유다.

gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), quit);
 gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file);


 메뉴 항목들은 gtk_menu_shell_append()함수를 호출해서 구현된다. 메뉴 항목들은 menu shell에 덧 붙여진다. 우리의 경우에서는 quit 메뉴 항목은 file 메뉴에 덧 붙여지고 file 메뉴는 menubar에 덧붙여진다.

g_signal_connect(G_OBJECT(quit), "activate",
      G_CALLBACK(gtk_main_quit), NULL);


quit 메뉴항목을 선택함으로써 응용프로그램은 종료된다.















그림: 간단한 메뉴

그림 메뉴, mnemonics과 accelerators

다음 예제에서 우리는 GTK+에서 사용할 수 있는 기능들을 좀더 탐색해 볼 것이다. Accelerators는 메뉴 항목을 할성화하기위한 키보드 단축기이다. Mnemonics는 GUI 항목을 위한 키보드 단축이다. 이 둘 모두 밑줄 쳐진 문자로 표현된다.

주의할 것은 Gnome 이 메뉴 영상을 보여주도록 설정되어 있는 않을 수도 있다는 것이다. 메뉴 영상 끄거나 켜기 위해선 gconf-editor를 실행하고 /desktop/gnome/interface/menus_have_icons로 가서 옵션을 설정해야한다.

#include 
#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *filemenu;
  GtkWidget *file;
  GtkWidget *new;
  GtkWidget *open;
  GtkWidget *quit;

  GtkWidget *sep;

  GtkAccelGroup *accel_group = NULL;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "menu");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  filemenu = gtk_menu_new();

  accel_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);

  file = gtk_menu_item_new_with_mnemonic("_File");
  new = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL);
  open = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
  sep = gtk_separator_menu_item_new();
  quit = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);

  gtk_widget_add_accelerator(quit, "activate", accel_group, 
      GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); 


  gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), filemenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), new);
  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), open);
  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), sep);
  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), quit);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 3);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(quit), "activate",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}



 이 예제는 GTK+응용 프로그램에서 어떻게 영상을 메뉴 항목에 추가하는지, accelerator를 설정하고, mnemonics를 사용하는지를 보여준다.

accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
...
quit = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);
gtk_widget_add_accelerator(quit, "activate", accel_group, 
    GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); 


 accelerator 그룹은 키보드 accelerator의 그룹이다. 일반적으로 toplevel 윈도우에 붙여서 사용한다. 여기서 Ctrl+q 라는 키보드 accelerator를 생성한다.

file = gtk_menu_item_new_with_mnemonic("_File");


mnemonic을 생성하기 위해서 우리는 gtk_menu_item_new_with_mnemonic() 함수를 호출한다. Alt-F를 눌려서 file 메뉴 항목을 선택한다.

new = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL);
open = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);


 여기서 우리는 두개의 영상 메뉴 항목을 생성한다. 함수의 두번째 매개변수를 NULL로 설정해서 자동적으로 accelerator를 생성한다. 우리는 GTK+의 내부 리소스로부터 메뉴항목을 위한 영상과 문자 제공한다.

sep = gtk_separator_menu_item_new();


메뉴 항목들은 수평 분리자로 분리될 수 있다. 이 방법으로 우리는 메뉴 항목을 logical 그룹에 넣을 수 있다.















그림: 메뉴 예제

메뉴 항목 체크

GtkCheckMenuItem은 체크박스를 위한 메뉴 항목이다.

#include 


void toggle_statusbar(GtkWidget *widget, gpointer statusbar) 
{
  if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
    gtk_widget_show(statusbar);
  } else {
    gtk_widget_hide(statusbar);
  }
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *viewmenu;
  GtkWidget *view;
  GtkWidget *tog_stat;
  GtkWidget *statusbar;
  

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "view statusbar");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  viewmenu = gtk_menu_new();

  view = gtk_menu_item_new_with_label("View");
  tog_stat = gtk_check_menu_item_new_with_label("View Statusbar");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(tog_stat), TRUE);

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(view), viewmenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(viewmenu), tog_stat);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), view);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 3);

  statusbar = gtk_statusbar_new();
  gtk_box_pack_end(GTK_BOX(vbox), statusbar, FALSE, TRUE, 1);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(tog_stat), "activate", 
        G_CALLBACK(toggle_statusbar), statusbar);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}



 예제에서 우리는 체크메뉴 항목을 보여준다. 체크박스가 활성화 된다면, startusbar 위젯은 보여진다. 그렇지 않다면 statusbar는 감추어진다.

tog_stat = gtk_check_menu_item_new_with_label("View Statusbar");


gtk_check_menu_item_new_with_label()함수 호출은 새로운 체크 메뉴 항목을 생성한다.

if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
   gtk_widget_show(statusbar);
 } else {
   gtk_widget_hide(statusbar);
 }


메뉴 항목에서 체크박스를 활성화 하면, 우리는 statusbar 위젯을 볼 수 있을 것이다. 그렇지 않으면 statusbar는 감추어진다.















그림 : 체크 메뉴 항목

툴바

메뉴 그룹은 우리가 응용 프로그램에서 사용할 수 있는 명령을 내린다. 툴바는 아주 자주 사용하는 명령에 빠르게 접근하도록 제공한다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;
  
  GtkWidget *toolbar;
  GtkToolItem *new;
  GtkToolItem *open;
  GtkToolItem *save;
  GtkToolItem *sep;
  GtkToolItem *exit;


  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "toolbar");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);


  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

  new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), new, -1);

  open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), open, -1);

  save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), save, -1);

  sep = gtk_separator_tool_item_new();
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 

  exit = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exit, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);

  g_signal_connect(G_OBJECT(exit), "clicked", 
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

이 코드 예제는 간단한 툴바 예제를 생성한다.

toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS)



우리는 새로운 툴바를 생성하고 툴바 버튼이 아이콘만 보여주도록 설정한다. 글자 없이 말이다.

new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), new, -1);


우리는 stock으로 부터 툴바 버튼을 생성한다. 툴바 버튼은 gtk_toolbar_insert()함수 호출에 의해서 툴바에 추가된다.
sep = gtk_separator_tool_item_new();
 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 


여기는 툴바에 구분자를 넣는 것이다.
















그림: 툴바

Undo redo

다음 예제는 우리가 어떻게 툴바에서 툴바 버튼을 비활성화시키는지를 보여주고 있다. 이것은 GUI 프로그래밍에서 기본적인 동작이다. 저장 버튼을 예로 들어 보자. 문서의 모든 변화를 디스크에 저장하면, 저장 버튼을 데부분의 문서 편집기에서 비활성화 된다. 이러한 방법으로 응용프로그램은 모든 변경사항들이 이미 저장되었다는 것을 사용자에세 알려준다.

#include 
#include 


void undo_redo(GtkWidget *widget,  gpointer item) 
{
  static int count = 2;
  const char *name = gtk_widget_get_name(widget);

  if ( strcmp(name, "undo") ) {
    count++;
  } else {
    count--;
  }
 
  if (count < 0) {
     gtk_widget_set_sensitive(widget, FALSE);
     gtk_widget_set_sensitive(item, TRUE);
  } 

  if (count > 5) {
     gtk_widget_set_sensitive(widget, FALSE);
     gtk_widget_set_sensitive(item, TRUE);
  }
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkToolItem *undo;
  GtkToolItem *redo;
  GtkToolItem *sep;
  GtkToolItem *exit;


  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "undoredo");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);


  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

  undo = gtk_tool_button_new_from_stock(GTK_STOCK_UNDO);
  gtk_widget_set_name(GTK_WIDGET(undo), "undo");
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), undo, -1);

  redo = gtk_tool_button_new_from_stock(GTK_STOCK_REDO);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), redo, -1);

  sep = gtk_separator_tool_item_new();
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 

  exit = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exit, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);


  g_signal_connect(G_OBJECT(undo), "clicked", 
        G_CALLBACK(undo_redo), redo);

  g_signal_connect(G_OBJECT(redo), "clicked", 
        G_CALLBACK(undo_redo), undo);

  g_signal_connect(G_OBJECT(exit), "clicked", 
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


이 예제는 GTK+ stock 리소스에서 undo , redo 버튼을 생성한다. 버튼을 각각 몇 번 클릭해서 비활성화 시키면 버튼이 회색조으로 변한다.

if (count < 0) {
    gtk_widget_set_sensitive(widget, FALSE);
    gtk_widget_set_sensitive(item, TRUE);
 } 

 if (count > 5) {
    gtk_widget_set_sensitive(widget, FALSE);
    gtk_widget_set_sensitive(item, TRUE);
 }


 gtk_widget_set_sensitive()함수 호출은 툴바 버튼을 활성/비활성화 하는데 사용되어진다.















그림 : undo redo


GTK+ 모양새 관리

이 장에서 우리는 윈도우와 다이얼로그에서 위젯을 어떻게 배치할 것인지를 보여준다.

우리가 응용프로그램의 GUI를 디자인할 때, 사용할 위젯이 무엇이고 응용프로그램에서 어떻게 배치시켜야 하는지 결정해야한다. 위젯을 배치시키기 위해서 layout container라고 불리는 특성화된 보이지 않는 위젯을 사용한다. 이 장에서 우리는 GtkAlignment, GtkFixed, GtkVBox와 GtkTable을 언급할 것이다.

GtkFixed

GtkFixed 컨테이너는 고정된 위치와 크기로 child widget에 위치한다. 이 컨테이너는 자동 모양새관리를 수행하지는 않는다. 대부분의 응용프로그램에는 이 GtkFixed 컨테이너를 사용하지는 않는다. 하지만 이 컨테이너를 사용하는 몇가지 특수한 영역이 있다. 예를 들면, 게임같은 경우, diagram을 가지고 작업하는 특화된 응용프로그램이나 spreadsheet 응용프로그램에 있는 chart와 같이 움직일 수 있는 크기를 변경할 수 있는 요소를 갖는 작은 교육용 예제들이다.

#include 


int main( int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *fixed;

  GtkWidget *button1;
  GtkWidget *button2;
  GtkWidget *button3;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkFixed");
  gtk_window_set_default_size(GTK_WINDOW(window), 290, 200);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button1 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), button1, 150, 50);
  gtk_widget_set_size_request(button1, 80, 35);

  button2 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), button2, 15, 15);
  gtk_widget_set_size_request(button2, 80, 35);

  button3 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), button3, 100, 100);
  gtk_widget_set_size_request(button3, 80, 35);

  g_signal_connect_swapped(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


위의 예제에서 세 개의 버튼을 생성하고 고정된 좌표에 위치 시킨다. 응용프로그램의 창을 변경시키더라도 이 버튼들의 크기와 위치는 변함이 없다.

fixed = gtk_fixed_new();


여기서 GtkFixed 컨테이너 widget이 생성된다.

gtk_fixed_put(GTK_FIXED(fixed), button1, 150, 50);


첫 번째 버튼은 좌표 x=150, y=50에 gtk_fixed_put()함수를 이용해서 위치시킨다.


그림: GtkFixed 컨테이너

GtkVBox

GtkVBox는 vertical box container (세로로 적재하기 위한 컨테이너). 이 것은 하나의 column(하나의 행)에 child widget을 위치시킨다. GtkHBox는 이와 유사한 컨테이너이다. 이 컨테이너는 하나의 row(하나의 열)에 child widget을 위치시킨다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *settings;
  GtkWidget *accounts;
  GtkWidget *loans;
  GtkWidget *cash;
  GtkWidget *debts;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 250);
  gtk_window_set_title(GTK_WINDOW(window), "GtkVBox");
  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  vbox = gtk_vbox_new(TRUE, 1);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  settings = gtk_button_new_with_label("Settings");
  accounts = gtk_button_new_with_label("Accounts");
  loans = gtk_button_new_with_label("Loans");
  cash = gtk_button_new_with_label("Cash");
  debts = gtk_button_new_with_label("Debts");

  gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), accounts, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), loans, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), cash, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), debts, TRUE, TRUE, 0);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


이 예제는 GtkVBox의 동작을 보여준다. 하나의 column(하나의 행)에 다섯개의 버튼을 적재 시킨다. 응용프로그램의 창을 변경시키면 child widget들도 같이 크기가 변경된다.

vbox = gtk_vbox_new(TRUE, 1);


GtkVBox가 생성된다. homogeneous 매개변수를 TRUE로 설정한다. 이 의미는 모든 버튼을 같은 크기로 만들 것이라는 것을 말한다. 그리고 widget간의 공간은 1 픽셀로 설정되었다.

gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);


우리는 이 컨테이너에 setting 버튼을 적재한다. 첫 번째 두 매개변수는 컨테이너와 child widget이다. 그리고 다음 세 개의 매개변수는 expand, fill 그리고 padding이다. 주의 할 것은 fill 매개변수는 expand 매개변수가 FALSE로 설정되면 동작하지 않는다. 이와 유사하게 컨테이너를 homegeneous 매개 변수를 TRUE로 설정해서 생성하면 expand 매개변수는 작동하지 않는다.


















그림: GtkVBox 컨테이너

GtkTable

GtkTable widget은 widget을 가로나 세로로 정렬한다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;

  GtkWidget *table;
  GtkWidget *button;

  char *values[16] = { "7", "8", "9", "/", 
     "4", "5", "6", "*",
     "1", "2", "3", "-",
     "0", ".", "=", "+"
  };

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 180);
  gtk_window_set_title(GTK_WINDOW(window), "GtkTable");

  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  table = gtk_table_new(4, 4, TRUE);
  gtk_table_set_row_spacings(GTK_TABLE(table), 2);
  gtk_table_set_col_spacings(GTK_TABLE(table), 2);

  int i = 0;
  int j = 0;
  int pos = 0;

  for( i=0; i < 4; i++) {
    for( j=0; j < 4; j++) {
      button = gtk_button_new_with_label(values[pos]);
      gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1 );
      pos++;
    }
  }

  gtk_container_add(GTK_CONTAINER(window), table);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


이 예제에서 계산기에서 볼 수 있는 버튼 모음을 생성할 것이다.

table = gtk_table_new(4, 4, TRUE);


4개의 열과 4개의 행을 갖는 새로운 GtkTable widget을 생성한다.

gtk_table_set_row_spacings(GTK_TABLE(table), 2);
 gtk_table_set_col_spacings(GTK_TABLE(table), 2);


우리는 모든 열과 모든 행 사이에 어떤 공간을 설정한다.

for( i=0; i < 4; i++) {
   for( j=0; j < 4; j++) {
     button = gtk_button_new_with_label(values[pos]);
     gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1 );
     pos++;
   }
 }


이 코드는 16개의 버튼을 생성하고 이 컨테이너에 위치 시킬 것이다.














그림: GtkTable 컨테이너

GtkAlignment

GtkAlignment 컨테이너는 child widget의 크기와 정렬을 제어한다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *ok;
  GtkWidget *close;

  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *halign;
  GtkWidget *valign;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 350, 200);
  gtk_window_set_title(GTK_WINDOW(window), "GtkAlignment");
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  vbox = gtk_vbox_new(FALSE, 5);

  valign = gtk_alignment_new(0, 1, 0, 0);
  gtk_container_add(GTK_CONTAINER(vbox), valign);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  hbox = gtk_hbox_new(TRUE, 3);

  ok = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(ok, 70, 30);
  gtk_container_add(GTK_CONTAINER(hbox), ok);
  close = gtk_button_new_with_label("Close");
  gtk_container_add(GTK_CONTAINER(hbox), close);

  halign = gtk_alignment_new(1, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), hbox);

  gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


이 코드 예제에서는 창의 우측 하단에 두 개의 버튼을 위치시킨다. 이를 위해서 하나의 horizontal box와 하나의 vertical box 그리고 두 개의 alignment 컨테이너를 사용한다.

valign = gtk_alignment_new(0, 1, 0, 0);


이 것은 child widget을 하단에 위치 시킬 것이다.

gtk_container_add(GTK_CONTAINER(vbox), valign);


여기선 vertical box에 alignment widget을 위치시킨다.

hbox = gtk_hbox_new(TRUE, 3);

 ok = gtk_button_new_with_label("OK");
 gtk_widget_set_size_request(ok, 70, 30);
 gtk_container_add(GTK_CONTAINER(hbox), ok);
 close = gtk_button_new_with_label("Close");
 gtk_container_add(GTK_CONTAINER(hbox), close);


horizontal box를 생성하고 두 개의 버튼을 그 안에 위치 시킨다.

halign = gtk_alignment_new(1, 0, 0, 0);
 gtk_container_add(GTK_CONTAINER(halign), hbox);

 gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);


이 것은 우측에 child widget을 위치 시킬 alignment 컨테이너를 생성할 것이다. horizontal box를 이 alignment 컨테이너에 추가하고 이 alignment 컨테이너는 vertical box에 적재한다. 우리는 alignment 컨테이너는 단 하나의 child widget밖에 갖을 수 없다는 것을 명심해야한다. 그래서 여기서 우리가 여러 개의 box를 사용해야하는 이유가 그것 때문인 것이다.













그림: GtkAlignment 컨테이너

Windows

다음은 좀 더 확장된 예제를 생성하게 될 것이다. JDeveloper IDE에서 찾아 볼 수 있는 윈도우를 살펴볼 것이다.

















그림: JDeveloper에서의 Window dialog

이 다이얼로그는 JDeveloper 응용프로그램에서 열려 있는 모든 윈도우들을 좀더 정확히 이야기 하자면 Tab들을 보여준다.

#include 


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *table;

  GtkWidget *title;
  GtkWidget *activate;
  GtkWidget *halign;
  GtkWidget *halign2;

  GtkWidget *valign;
  GtkWidget *close;
  GtkWidget *wins;

  GtkWidget *help;
  GtkWidget *ok;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_widget_set_size_request (window, 300, 250);
  gtk_window_set_resizable(GTK_WINDOW(window), FALSE);

  gtk_window_set_title(GTK_WINDOW(window), "Windows");

  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  table = gtk_table_new(8, 4, FALSE);
  gtk_table_set_col_spacings(GTK_TABLE(table), 3);

  title = gtk_label_new("Windows");
  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), title);
  gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
      GTK_FILL, GTK_FILL, 0, 0);

  wins = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
  gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3,
      GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);

  activate = gtk_button_new_with_label("Activate");
  gtk_widget_set_size_request(activate, 50, 30);
  gtk_table_attach(GTK_TABLE(table), activate, 3, 4, 1, 2, 
      GTK_FILL, GTK_SHRINK, 1, 1);

  valign = gtk_alignment_new(0, 0, 0, 0);
  close = gtk_button_new_with_label("Close");
 
  gtk_widget_set_size_request(close, 70, 30);
  gtk_container_add(GTK_CONTAINER(valign), close);
  gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
  gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
      GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);

  halign2 = gtk_alignment_new(0, 1, 0, 0);
  help = gtk_button_new_with_label("Help");
  gtk_container_add(GTK_CONTAINER(halign2), help);
  gtk_widget_set_size_request(help, 70, 30);
  gtk_table_set_row_spacing(GTK_TABLE(table), 3, 6);
  gtk_table_attach(GTK_TABLE(table), halign2, 0, 1, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  ok = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(ok, 70, 30);
  gtk_table_attach(GTK_TABLE(table), ok, 3, 4, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  gtk_container_add(GTK_CONTAINER(window), table);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}


이 코드는 어떻게 GTK+에서 유사한 윈도우를 생성할 수 있는지 보여준다.

table = gtk_table_new(8, 4, FALSE);


table 컨테이너 widget을 사용한다.

title = gtk_label_new("Windows");
 halign = gtk_alignment_new(0, 0, 0, 0);
 gtk_container_add(GTK_CONTAINER(halign), title);
 gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
     GTK_FILL, GTK_FILL, 0, 0);


이 코드는 왼쪽으로 정렬된 label을 생성한다. 이 label은 GtkTable 컨테이너의 첫 번째 열에 위치한다.

wins = gtk_text_view_new();
 gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
 gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3, 
     GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);


text view widget은 두 개의 열과 두 개의 행을 차지하게 된다. 이 widget은 편집 불가능하게 만들고 cursor는 숨긴다.

valign = gtk_alignment_new(0, 0, 0, 0);
 close = gtk_button_new_with_label("Close");
 
 gtk_widget_set_size_request(close, 70, 30);
 gtk_container_add(GTK_CONTAINER(valign), close);
 gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
 gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
     GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);


close button은 text view widget 옆의 4번째 column에 배치시킨다. (숫자 매기기는 0부터 시작한다.)  이 button은 alignment widget에 추가한다. 맨 위쪽에 위치 시킬 수 있도록 하기위해서,


















그림: windows

GTK+ events and signals


GTK+ 프로그래밍 도움말의 이번 부분에서는 GTK+ 라이브러리의 event system에 대해서 이야기 할 것이다. GTK+ 라이브러리는 event 기반으로 운용되는 system이다. 모든 GUI 응용프로그램은 event로 운용된다. 응용프로그램은 새롭게 생성되는 event들에 대해서 계속적으로 검사하는 main loop를 시작한다. 만약 아무런 event가 없다면, 응용프로그램은 대기 하면서 아무런 동작도 하지 않는다. GTK+에서 event는 X server로부터의 message이다. event가 widget에 도달할때 signal을 방출하면서 이 event에 대해서 반응한다. GTK+ 프로그래머는 signal에 특정 callback함수를 연결할 수 있다. 이 callback 함수는 signal에 반응하는 handler 함수이다.

#include 

void button_clicked(GtkWidget *widget, gpointer data)
{
  g_print("clicked\n");
}

int main( int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);
  gtk_widget_set_size_request(button, 80, 35);

  g_signal_connect(G_OBJECT(button), "clicked", 
      G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


이 응용프로그램에서 두 개의 signal을 다룬다. clicked signal과 destroy signal.

g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);


g_signal_connect() 함수는 clicked signal을 button_clicked() callback 함수에 연결한다.

void button_clicked(GtkWidget *widget, gpointer data)
 {
   g_print("clicked\n");
 }


이 callback 함수는 "clicked" 문자를 console에 출력할 것이다.  callback 함수의 첫 번째 매개변수는 signal을 방출하는 object이다. 이 경우에는 Click button이 된다. 두 번째 매개변수는 선택사항이다. 어떤 데이터라도 callback 함수에 전달 할 수 있을 것이다. 이 경우에는 어떤 데이터도 전달하지 않는다. g_signal_connect() 함수에서 NULL 매개변수를 전달한다.

g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);


titlebar의 오른쪽 구석에 위치한 x button을 누르거나 Alt+F4를 누르면, destroy signal을 방출한다. 그리고 응용프로그램을 종료하는 gtk_main_quit() 함수를 호출한다.

Moving window

다음 예제는 어떻게 window의 move event에 반응하는지를 보여준다.

#include 

void frame_callback(GtkWindow *window, 
      GdkEvent *event, gpointer data)
{
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
}


int main(int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Simple");
  gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  g_signal_connect(G_OBJECT(window), "configure-event",
        G_CALLBACK(frame_callback), NULL);

  gtk_widget_show(window);
  gtk_main();

  return 0;
}


위의 예제에서는 titlebar에 윈도우의 윗쪽 왼쪽 구석의 현재 위치를 보여준다.

gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);


widget의 event mask는 어떤 종류의 event가 특정 widget에서 처리될 것인지 결정한다. 어떤 event는 사전에 설정되어 있고, 그렇지 않은 종류의 event는 사후에 event mask에 추가되어져야 한다. gtk_widget_add_events() 함수는 GDK_CONFIGURE의 event 타입을 mask에 추가한다. GDK_CONFIGURE의 event type은  모든 크기, 위치, 스택 순서 event를 차지한다.

g_signal_connect(G_OBJECT(window), "configure-event",
     G_CALLBACK(frame_callback), NULL);


configure-event는 크기, 위치와 스텍의 순서 event들을 제공한다.

void frame_callback(GtkWindow *window, 
     GdkEvent *event, gpointer data)
 {
   int x, y;
   char buf[10];
   x = event->configure.x;
   y = event->configure.y;
   sprintf(buf, "%d, %d", x, y);
   gtk_window_set_title(window, buf);
 }


callback 함수는 세 개의 매개변수를 가진다. 그 object는 특정 signal, GdkEvent와 추가적인 data를 생성한다. 여기서는 그저 x, y 좌표를 결정하고 title에 출력한다.












그림: Move event

The enter signal

다음 예제는 enter signal에 어떻게 반응해야하는지를 보여준다. 마우스 위치가 widget위 영역에 들어 가면 enter signal은 발생된다.

#include 


void enter_button(GtkWidget *widget, gpointer data) 
{ 
  GdkColor color;
  color.red = 27000;
  color.green = 30325;
  color.blue = 34181;
  gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color);
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "enter signal");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Button");
  gtk_widget_set_size_request(button, 80, 35);
  gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50);

  g_signal_connect(G_OBJECT(button), "enter", 
      G_CALLBACK(enter_button), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}


여기서 button widget위에 마우스 포인터를 위치시키면 button widget위 배경색을 변경될 것이다.

g_signal_connect(G_OBJECT(button), "enter", 
    G_CALLBACK(enter_button), NULL);


여기서, enter signal이 발생하면 enter_button() 사용자 함수가 호출된다.

GdkColor color;
 color.red = 27000;
 color.green = 30325;
 color.blue = 34181;
 gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color);


callback함수 내부에서 gtk_widget_modify_bg()함수를 호출해서 button의 배경을 변경한다.

Disconnectiong a callback

signal에 연결된 callback함수를 연결해제 할 수 도 있다. 다음 예제 코드는 이런 케이스를 보여준다.

#include 


int handler_id;

void button_clicked(GtkWidget *widget, gpointer data) 
{ 
  g_print("clicked\n");
}

void toogle_signal(GtkWidget *widget, gpointer window)
{
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
     handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
           G_CALLBACK(button_clicked), NULL);
  } else {
     g_signal_handler_disconnect(window, handler_id);
  }
}


int main( int argc, char *argv[])
{

  GtkWidget *window;
  GtkWidget *fixed;
  GtkWidget *button;
  GtkWidget *check;


  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Disconnect");

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  button = gtk_button_new_with_label("Click");
  gtk_widget_set_size_request(button, 80, 30);
  gtk_fixed_put(GTK_FIXED(fixed), button, 30, 50);

  check = gtk_check_button_new_with_label("Connect");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE);
  gtk_fixed_put(GTK_FIXED(fixed), check, 130, 50);

  handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
        G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(check), "clicked",
        G_CALLBACK(toogle_signal), (gpointer) button);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}



이 코드 예제에선 버튼과 체크 박스를 가지고 있다. 체크박스는 button의 clicked signal에 해당하는 callback함수에 연결되거나 그렇지 않게 되어 있다.

handler_id = g_signal_connect(G_OBJECT(button), "clicked", 
     G_CALLBACK(button_clicked), NULL);


g_signal_connect() 함수는 callback 함수를 구분 짓는 handler id를 되돌려 준다.

if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
    handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
          G_CALLBACK(button_clicked), NULL);
 } else {
    g_signal_handler_disconnect(window, handler_id);
 }


이 코드는 체크박스의 상태를 결정한다. 체크가 선택되거나 그렇지 않을 경우의 callback함수를 연결시켜준다.












그림: Disconnect

Drag and Drop 예제

다음 예제에서 흥미로운 것을 보여 줄 것이다. 경계가 없는 윈도우와 그런 윈도우를 어떻게 움직이고 드레그 할 수 있는지를 보여줄 것이다.

#include 

gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
   event->x_root,
   event->y_root,
   event->time);
    }
  }

  return FALSE;
}


int main( int argc, char *argv[])
{

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Drag & drop");
  gtk_window_set_decorated(GTK_WINDOW (window), FALSE);
  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show(window);

  gtk_main();

  return 0;
}


이 예제는 경계가 없는 윈도우의 drag와 drop을 보여준다.

gtk_window_set_decorated(GTK_WINDOW (window), FALSE);


윈도우의 기타 요소들을 움직인다. 이것은 윈도우가 경계와 titlebar를 가지지 안을 것이라는것을 의미한다.

g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);


이것은 윈도우의 button-press-event 시그날을 연결시켜준다.

gboolean on_button_press (GtkWidget* widget,
  GdkEventButton * event, GdkWindowEdge edge)
{
  if (event->type == GDK_BUTTON_PRESS)
  {
    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
   event->x_root,
   event->y_root,
   event->time);
    }
  }

  return FALSE;
}


on_button_press() 함수의 내부에서는 drag와 drop의 동작을 구현한다. 마우스의 우측 버튼이 눌렸는지 않은지를 검사한다. 그리고 나서 gtk_window_begin_move_drag() 함수를 호출한다.

A timer example

다음 예제는 timer 예제를 보여준다. Timer는 어떤 반복적인 작업을 가지고 있을 때 사용된다.  그건 clock이거나 count down 이거나 visual 효과거나 animation일 수 있다.

#include 
#include 
#include