임베디드 플랫폼에서 효율적인 GUI 개발

Qtopia 크로스 컴파일 환경의 구축크로스 컴파일러의 셋업우선 mips용 크로스 컴파일러 킷을 인스톨한다. 이것들은 인터넷상에서 손쉽게 인스톨 할 수 있도록 대부분 RPM으로 제공되고 있다. 아래 4개의 RPM을 다운로드한다. Qt의 개발 툴인 qmake도 있지만, Qtopia를 사용할 때 필요하므로 염두 해 두도록 한다. 모든 패키지는 각종 파일의 인스톨처가 /opt/mips를 기준으로 설명되므로 시스템에는 영향을 미치지 않을 것이다.# rpm -Uvh gcc-cross-mips-2. 95.2-0.i386.rpm위와 같이 RPM 패키지를 인스톨 한다. 파일의 prefix 인스톨처는 /opt/mips이다. 설치에 신중을 기하려면 rpm -qpl로 설치하기 전에 사전 조사를 해보도록 하자.Qtopia의 셋업 Qtopia는 C++와 트롤테크사의 Qt를 사용해 개발한다. Qtopia는 PIM용으로 통합되어진 Qt 베이스 환경이다. 아직 한 번도 못 보신 분들을 위해 스크린 캡처 화면을 보여드리겠지만, 기본적인 에디터나 mpeg player, 그 외 키보드 입력, 필기 인식 등의 기능을 갖추고 있다. 추가적으로 Qtopia에는 통상의 X11상에서 애플리케이션 테스트를 실행할 수 있는 가상 프레임 버퍼(qvfb)를 제공하고 있다. qvfb를 사용하면 크로스 컴파일 한 Qtopia 실행파일을 일부러 타깃 보드까지 끌어들여 오지 않아도 X상에서 시뮬레이트 하여 실행할 수 있다. 참고로 여기서 ‘Qtopia 용의 애플리케이션을 만든다’라고 하는 것은 ‘Qtopia용으로 작성한 프로그램을 mips 프로세서용으로 크로스 컴파일 한다’라고 하는 것이다.그래서, Qtopia와 Qt용 프로그램 라이브러리를 포함한 Qtopia의 SDK를 다운로드한다. Qtopia의 다운로드는 트롤테크 본사의 웹페이지로부터 찾을 수 있다. Open Source SDK for Qtopia를 다운로드한다. 여기서 다운로드하는 것은 개인용의 GPL 라이선스에 근거한 무상판이다. 상용판에 관해서는 별도로 트롤테크에 문의해야 하며, 두 제품 모두 코드 베이스는 동일하므로 실제 개발에서의 차이는 없다.Qtopia의 인스톨이 끝나면 다음 2개의 배치파일을 작성한다. x86용과 mips 크로스 컴파일용의 환경 변수설정을 하는 배치파일이다. x86용은 qvfb를 사용할 때에도 이용된다.X86 타겟용 「dev-x86-qpe.sh」#! /bin/bashCROSSCOMPILE=/opt/mips/toolsQPEDIR=/opt/QtopiaQTDIR=/opt/QtopiaPATH=$QTDIR/bin:$QPEDIR/bin:$PATH:/opt/mips/tools/binqmakePATH=/opt/Qtopia/qmake/lib/qws/linux-x86-g++/LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATHexport QPEDIR QTDIR PATH qmakePATH LD_LIBRARY_PATH PS1echo “Altered environment for mips Development x86”$*mips 타겟용 「dev-mips-qpe.sh」#! /bin/bashCROSSCOMPILE=/opt/mips/toolsQPEDIR=/opt/Qtopia/mipsQTDIR=/opt/Qtopia/mipsPATH=$QTDIR/bin:$QPEDIR/bin:$CROSSCOMPILE/bin:$PATHqmakePATH=/opt/Qtopia/qmake/lib/qws/linux-mips-g++/LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATHexport QPEDIR QTDIR PATH qmakePATH LD_LIBRARY_PATH PS1echo “Altered environment for mips Development mips”$*위의 스크립트를 다 만들면 스크립트 파일에 chmod+x하여 실행권한을 붙여준다. 이후, Qtopia의 X86용 파일을 컴파일 할 때 dev-x86-qpe.sh를, mips용 파일을 컴파일 할 때는 dev-mips-qpe.sh를 각각 이용하게 된다.mips용 환경 설정mips 크로스 컴파일용 qmake 설정 파일을 인스톨한다. Qtopia의 셋업 에서 얘기하고 있는 mips용 셸 스크립트를 기반으로 설명하고 있지만 환경 변수 PATH에서 목표가 되는 Makefile 환경 정의(실제는 qmake 설정 파일)를 지정하기 때문에 인스톨 해 두도록 한다.[embeddedworld@mskim embeddedworld]$ ls...qmake-mips.tar.gz...[embeddedworld@mskim embeddedworld]$ tar zxvf qmake-mips.tar.gzREADME-mipslinux-mips-g++/linux-mips-g++/app.tlinux-mips-g++/lib.tlinux-mips-g++/subdirs.tlinux-mips-g++/qmake.conf[embeddedworld@mskim embeddedworld]$ su여기서/opt/Qtopia/qmake/lib/qws를 보면 이미 linux-mips-g++디렉토리가 존재하고 있다. 적당하게 rename 해 준다.[root@mskim embeddedworld]# cp -rp linux-mips-g++ /opt/Qtopia/qmake/lib/qws/[root@mskim embeddedworld]# ls /opt/Qtopia/qmake/lib/qws/...linux-mips-g++/linux-mips-g++.bak/ (이전의 linux-mips-g++를 rename해 백업 한 디렉토리이다)...이상으로 컴파일에 필요한 사전 준비는 완료했다. Qtopia에 포함되어 오는 동작 확인용 샘플 프로그램을 컴파일 해 보도록 하자.컴파일 테스트Qtopia 포함된 동작 확인용 샘플 /opt/Qtopia/example를 적당한 장소에 복사해 둔다.[embeddedworld@mskim embeddedworld]$ cd temp/src[embeddedworld@mskim src]$ cp -rp /opt/Qtopia/example . /[embeddedworld@mskim src]$ ls...example/...[embeddedworld@mskim src]$ cd example/[embeddedworld@mskim example]$ lsExample.png example.cpp example.h example.pro main.cppexample.control example.desktop example.html examplebase.ui[embeddedworld@mskim example]$ cat example.proTEMPLATE = app#CONFIG = qt warn_on debugCONFIG = qt warn_on releaseHEADERS = example.hSOURCES = main.cpp example.cppINCLUDEPATH += $(QPEDIR) /includeDEPENDPATH += $(QPEDIR) /includeLIBS += -lqpeINTERFACES = examplebase.uiTARGET = example이미 qmake가 사용하는 프로젝트 파일이 만들어져 있다. 통상의 pro 파일과의 차이는 LIBS에 -lqpe가 추가되어 있는 것뿐이다.이것이 Qtopia용 프로그램 라이브러리 설정이다. 컴파일은 릴리스용으로 되어 있으며, 디버그 옵션이 comment out 되고 있다.이제 qmake로 만들어 보자. 그 전에 mips와 구별하기 쉽게 example.pro를 example-x86.pro와 example-mips.pro 2개로 복사한다. 그리고, 타깃을 example-x86와 example-mips에 각각 변경해 둔다.[embeddedworld@mskim example]$ mv example.pro example-x86.pro[embeddedworld@mskim example]$ cp example-x86.pro example-mips.pro[embeddedworld@mskim example]$ vi example-x86.pro[embeddedworld@mskim example]$ vi example-mips.proqmake를 사용한다. dev-x86-qpe.sh의 사용법에 주목해 본다.[embeddedworld@mskim example]$ dev-x86-qpe.sh qmake -o Makefile.x86 example-x86.proAltered environment for mips Development x86[embeddedworld@mskim example]$ ls...Makefile.x86...[embeddedworld@mskim example]$ dev-x86-qpe.sh make -f Makefile.x86Altered environment for mips Development x86/opt/Qtopia/bin/uic examplebase.ui -o . /examplebase.hg++ -c -pipe -DQWS -fno-exceptions -fno-rtti -Wall -W -O2 -fno-default-inline -DNO_DEBUG -I/opt/Qtopia/include -I/opt/Qtopia/include -o main.o main.cpp...gcc -o example-x86 main.o example.o examplebase.o moc_example.o moc_examplebase.o -L/opt/Qtopia/lib -lqpe -lqte[embeddedworld@mskim example]$ ls...Makefile.x86...example-x86*...[embeddedworld@mskim example]$ file example-x86example-x86: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped[embeddedworld@mskim example]$ dev-x86-qpe.sh[embeddedworld@mskim example]$ qmake -o Makefile.x86 example-x86.proAltered environment for mips Development x86[embeddedworld@mskim example]$ ls...Makefile.x86...[embeddedworld@mskim example]$ make -f Makefile.x86바이너리가 컴파일, 링크 되었다.qvfb를 미리 시작하여 실행해 본다. 실행 후, 서버 모드(-qws 옵션)로 example-x86를 작동한다.[embeddedworld@mskim example]$ dev-x86-qpe.sh qvfb &Altered environment for mips Development x86Using display 0(qvfb를 백그라운드에서 실행한다. qvfb의 패스는 dev-x86-qpe.sh중에서 설정되어 있다. )[embeddedworld@mskim example]$ dev-x86-qpe.sh . /example-x86 -qwsAltered environment for mips Development x86Connected to VFB server: 240 x 320 x 32그림 1에 나타내는 것이 qvfb를 시작한 초기 상태의 윈도우이다.그림 2는 example-x86를 시작한 상태의 윈도우이다.화면 하단부의 버튼을 클릭하면 그림 3과 같이 example-x86은 종료된다. qvfb 자체는 종료되지 않는다.계속해서 mips용 크로스 컴파일을 실행한다. 최초로 make clean을 통해 X86용으로 생성된 오브젝트 파일을 삭제하고 나서 qmake, make를 실행한다.[embeddedworld@mskim example]$ make -f Makefile.x86 cleanrm -f main.o example.o examplebase.o moc_example.o moc_examplebase.o moc_example.cpp moc_examplebase.cpp examplebase.cpp examplebase.h example-x86rm -f *~ core[embeddedworld@mskim example]$ dev-mips-qpe.sh qmake -o Makefile.mips example-mips.proAltered environment for mips Development mips[embeddedworld@mskim example]$ dev-mips-qpe.sh make -f Makefile.mipsAltered environment for mips Development mips/opt/Qtopia/mips/bin/uic examplebase.ui -o . /examplebase.hmips-linux-g++ -c -pipe -DQT_QWS_EBX -DQT_QWS_CUSTOM -DQWS -fno-exceptions -fno-rtti -Wall -W -O2 -DNO_DEBUG -I/opt/Qtopia/mips/include -I/opt/Qtopia/mips/include -o main.o main.cpp...mips-linux-g++ -o example-mips main.o example.o examplebase.o moc_example.o moc_examplebase.o -L/opt/Qtopia/mips/lib -lqpe -lqte[embeddedworld@mskim example]$ ls...example-mips*...Makefile.mipsexample-mips.proqvfb 사용 방법qvfb를 사용하는 것으로 애플리케이션 바이너리를 X11 상에서 동작시킬 수 있으며 이는, 일종의 시뮬레이션이다. 시뮬레이션을 개시하려면 방금전에 한 것처럼 Qtopia용 환경 변수를 유효하게 한 후 /opt/Qtopia/bin/qvfb를 백그라운드 실행한다. 다음은 qpe 실행화면이다.[embeddedworld@mskim example]$ dev-x86-qpe.sh qvfb &Altered environment for mips Development x86Using display 0[embeddedworld@mskim example]$ dev-x86-qpe.sh qpeAltered environment for mips Development x86Connected to VFB server: 240 x 320 x 32tr for sysapplet:/opt/Qtopia//i18n/ja_JP.eucJP/libbatteryapplet.qmtr for sysapplet:/opt/Qtopia//i18n/ja_JP.eucJP/libcardmonapplet.qmtr for sysapplet:/opt/Qtopia//i18n/ja_JP.eucJP/libclockapplet.qmtr for sysapplet:/opt/Qtopia//i18n/ja_JP.eucJP/libnetmonapplet.qmCategories::labels didn’t find app Document ViewQDir::readDirEntries: Cannot read the directory: /opt/Qtopia/plugins/obexcould not load IR pluginexecuting embeddedkonsoleConnected to VFB server: 240x320x32QDir::readDirEntries: Cannot read the directory: /opt/Qtopia/etc/keytabs에러가 표시되지만 무엇인가 Desktop 같은 흰색 화면이 표시된다.구체적으로 여기서부터 어떻게 example-x86을 실행하는가 보자면 화면안의 터미널을 기동한다. Linux의 친숙한 터미널l 화면이 표시되면 현재 디렉토리는 qpe를 기동한 시점에서의 디렉토리이므로 화면이 보이는 대로 바로 example-x86을 기동할 수 있다.터미널의 커멘드 라인으로부터 직접적으로 example-x86을 입력하여 실행해 보도록 하자.이와 같이, Qtopia의 데스크 탑 환경 하에서 애플리케이션을 테스트할 수 있으므로 매우 편리하다.콘솔에서의 개발애플리케이션에 의한 빌드 시스템을 이용하면 mips용 컴파일 설정으로 변경하는 것은 환경 변수 CC를 변경하는 것보다도 간단하지만 디스트리뷰션이나 빌드 시스템의 문서를 확인해야 한다. Qt의 프로그램 라이브러리와 링크 시키지 않고 qmake를 사용하려면 다음의 옵션을 프로젝트 파일에 추가한다.CONFIG -= qt # remove the qt linking주의:mips용 컴파일을 실시할 때는, mips용 프로그램 라이브러리가 링크되고 있는 것을 확인해 준다.Opie/i386실제 개발은 크로스 컴파일러를 이용하는 것보다는 직접 PC상 환경을 이용하여 개발한후 마지막 결과물만 크로스 컴파일하여 올려 보는게 낫기 때문에 qt 2.3.4 + opie snapshot 20021122로 i386을 만드는 것을 다뤄보겠다. Qt/Embedded+Qtopia와 혼동하지 않기 위해 베이스 디렉토리를 /usr/local/opie로한다.qte 2.3.4의 수정opie cvs로부터 패치를 각 해당 디렉토리마다(listview, makefile, override, setpalette, sigsegv, simpad, style, unpolish, gfxraster) 한다.또, Transformed 서버를 QVFb에 대응시키기 때문에,--- qgfxtransformed_qws.cpp~ Thu Feb 27 11:07:36 2003+++ qgfxtransformed_qws.cpp Tue Mar 25 21:45:09 2003@@ -43, 7 +43, 7 @@ #include #include -//#define QT_TRANSFORM_VFB+#define QT_TRANSFORM_VFB #ifndef QT_TRANSFORM_VFB # ifndef _OS_QNX6_으로 하며 2.3.3 이전과 달라서 Transformed에 대응한다.qconfig-fat.h 로서는#define QT_NO_PROPERTIES#define QT_NO_DRAGANDDROP#define QT_NO_COMPLEXTEXT#define QT_NO_STYLE_AQUA를 사용한다. PROPERTIES는 qt-designer가 사용 하고 있지만, QVFb상에서 움직일 리는 없기 때문에 생략하고, QT_NO_ COMPLEXTEXT, QT_NO_STYLE_AQUA는 원래 qt-embedded-free 2.3.4에 구현이 되지 않았음에도 opie로부터 플래그를 참조하고 있기 때문에 qt-embedded 측에서 명시해야 한다.configs/linux-x86-g++-shared-debug의 수정:--- linux-x86-g++-shared-debug.org Tue Mar 25 21:48:55 2003+++ linux-x86-g++-shared-debug Tue Mar 25 21:50:36 2003@@ -6, 17 +6, 21 @@ # Compiling with support libraries SYSCONF_CXXFLAGS_X11 =-SYSCONF_CXXFLAGS_QT = -I$(QTDIR) /include+SYSCONF_CXXFLAGS_QT = -I$(QTDIR) /include -I/usr/include/freetype2 SYSCONF_CXXFLAGS_QTOPIA = -I$(QPEDIR) /include SYSCONF_CXXFLAGS_OPENGL = -I/usr/X11R6/include+RUNTIME_QTDIR = /usr/local/opie+RUNTIME_QPEDIR = /usr/local/opie+RUNTIME_OPIEDIR = /usr/local/opie+ # Compiling YACC output SYSCONF_CXXFLAGS_YACC = -Wno-unused -Wno-parentheses # Linking with support libraries SYSCONF_RPATH_X11 =-SYSCONF_RPATH_QT = -Wl, -rpath,$(QTDIR) /lib-SYSCONF_RPATH_QTOPIA = -Wl, -rpath,$(QPEDIR) /lib+SYSCONF_RPATH_QT = -Wl, -rpath,$(RUNTIME_QTDIR) /lib+SYSCONF_RPATH_QTOPIA = -Wl, -rpath,$(RUNTIME_QPEDIR) /lib SYSCONF_RPATH_OPENGL = -Wl, -rpath, /usr/X11R6/lib # Linking with support libraries@@ -26, 7 +30, 7 @@ # Qt, Qt+OpenGL SYSCONF_LFLAGS_QT = -L$(QTDIR) /lib SYSCONF_LFLAGS_QTOPIA = -L$(QPEDIR) /lib-SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX)+SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX) -lfreetype SYSCONF_LIBS_QT_OPENGL = SYSCONF_LIBS_QTOPIA = -lqtopiaqte 2.3.4의 make% export QTDIR=/home/src/Qt/qt-2. 3.4% . /configure -qvfb -vnc -qconfig fat -system-jpeg -system-libpng -system-zlib -gif -depths 8,16,24,32 -debug% make디폴트로 xft는 유효하게 되어 있지만 -xft 해 두는 편이좋다.static library로 하는 경우에는% . /configure -qvfb -vnc -qconfig fat -system-jpeg -system-libpng -system-zlib -gif -depths 8,16,24,32 -static -platform linux-generic-g++와 같이 한다. linux-generic-g++와 linux-x86-g++ 의 차이는 inline function 전개 유무에 있어 inline이 아닌 실체가 필요한 linux-x86-g++는 static library화할 수 없다. 링커의 버그라고 생각하는데 컴파일러 버전에 따라 틀리다.% cd /home/src/Qt/opie-snapshot-20021122% rm -rf pics% cd /home/src/Qt/cvs/opie/opie% tar -cvp pics | tar -xpC /home/src/Qt/opie-snapshot-20021122% cd /home/src/Qt/opie-snapshot-20021122% cd pics% rm -rf **/CVS% cd ../library% /usr/local/qte/bin/qembed --images ../pics/inline/*. * > inlinepics_p.hOPIE snapshot 아이콘을 cvs로 교체library/mkinlinepics라고 하는 one line script 도 있지만, 패스를 이용하지 않기 때문에 직접 다음과 같이 한다. 역시 스냅샷은 최신 버전을 사용해도 된다.qembed 자체는 qt(e) tools/에 있다.반드시 qt2/qte2를 사용하여야 되며 qt3에도 파일은 있지만, 호환성이 없다.opie snapshot 20021122 의 수정우선 configs/linux-generic-g++-shared-debug의 수정을 한다.--- linux-generic-g++-shared-debug.org Tue Mar 25 21:48:37 2003+++ linux-generic-g++-shared-debug Tue Mar 25 21:58:08 2003@@ -3, 6 +3, 7 @@ SYSCONF_CXX = g++ SYSCONF_CC = gcc DASHCROSS =+RUNTIME_QTDIR = /usr/local/opie # Compiling with support libraries SYSCONF_CXXFLAGS_X11 =@@ -14, 7 +15, 7 @@ # Linking with support libraries SYSCONF_RPATH_X11 =-SYSCONF_RPATH_QT = -Wl, -rpath,$(QTDIR) /lib+SYSCONF_RPATH_QT = -Wl, -rpath,$(RUNTIME_QTDIR) /lib SYSCONF_RPATH_OPENGL = -Wl, -rpath, /usr/X11R6/lib # Linking with support libraries@@ -22, 8 +23, 8 @@ SYSCONF_LFLAGS_X11 = SYSCONF_LIBS_X11 = # Qt, Qt+OpenGL-SYSCONF_LFLAGS_QT = -L$(QTDIR) /lib-SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX)+SYSCONF_LFLAGS_QT = -L$(QTDIR) /lib+SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX) -lfreetype SYSCONF_LIBS_QT_OPENGL = # OpenGL SYSCONF_LFLAGS_OPENGL = -L/usr/X11R6/libfreetype/freetypefactoryimpl.h는 다음과 같다.--- freetypefactoryimpl.h~ Fri Oct 25 21:42:57 2002+++ freetypefactoryimpl.h Tue Mar 25 21:40:11 2003@@ -20, 8 +20, 8 @@ #include --class QFontFactoryFT;+#include +// class QFontFactoryFT; class FreeTypeFactoryImpl : public FontFactoryInterface {noncore/settings/freetype/freetypefactoryimpl.h:-- noncore/settings/appearance2/appearance.cpp.org Tue Mar 25 22:13:10 2003+++ noncore/settings/appearance2/appearance.cpp Tue Mar 25 22:13:26 2003@@ -117, 13 +117, 13 @@ list->insertItem( new StyleListItem ( “Windows”, new QWindowsStyle ( ))); list->insertItem( new StyleListItem ( “Light”, new LightStyle ( ))); #ifndef QT_NO_STYLE_MOTIF- list->insertItem( new StyleListItem ( “Motif”, new QMotifStyle ( )));+ // list->insertItem( new StyleListItem ( “Motif”, new QMotifStyle ( ))); #endif #ifndef QT_NO_STYLE_MOTIFPLUS- list->insertItem( new StyleListItem ( “MotifPlus”, new QMotifPlusStyle ( )));+ // list->insertItem( new StyleListItem ( “MotifPlus”, new QMotifPlusStyle ( ))); #endif #ifndef QT_NO_STYLE_PLATINUM- list->insertItem( new StyleListItem ( “Platinum”, new QPlatinumStyle ( )));+ // list->insertItem( new StyleListItem ( “Platinum”, new QPlatinumStyle ( ))); #endif #endif list->insertItem( new StyleListItem ( “QPE”, new QPEStyle ( )));qpdf in opie snapshot 20021122 수정noncore/graphics/qpdf만 유독 수정량이 좀 많다. -lstdc++만 추가하면 된다고 생각하지만 그렇진 않다.--- Makefile~ Tue Mar 25 21:28:45 2003+++ Makefile Tue Mar 25 21:36:57 2003@@ -29, 7 +29, 7 @@ SYSCONF_LIBS_X11 = # Qt, Qt+OpenGL SYSCONF_LFLAGS_QT = -L$(QTDIR) /lib-SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX) -lfreetype+SYSCONF_LIBS_QT = -lqte$(QT_THREAD_SUFFIX) -lfreetype -lstdc++ SYSCONF_LIBS_QT_OPENGL = # OpenGL SYSCONF_LFLAGS_OPENGL = -L/usr/X11R6/lib--- QOutputDev.cpp~ Fri Nov 22 21:37:18 2002+++ QOutputDev.cpp Tue Mar 25 21:35:47 2003@@ -672, 7 +672, 7 @@ if ( dorot ) { oldmat = m_painter-> worldMatrix ( );- std::cerr << std::endl << “ROTATED: “ << m11 << “, “ << m12 << “, “ << m21 << “, “ << m22 << “ / SIZE: “ << fsize << “ / TEXT: “ << str. local8Bit ( ) << endl << endl;+ std::cerr << std::endl << “ROTATED: “ << double(m11) << “, “ << double(m12) << “, “ << double(m21) << “, “ << double(m22) << “ / SIZE: “ << double(fsize) << “ / TEXT: “ << str. local8Bit ( ) << endl << endl; QWMatrix mat ( lrint ( m11 / fsize ), lrint ( m12 / fsize ), -lrint ( m21 / fsize ), -lrint ( m22 / fsize ), lrint ( x1 ), lrint ( y1 ));어쩌면, fixed.h의 comment out을 원래대로 되돌릴 필요가 있을지도 모르지만, 대부분 무난히 진행될 것이다.--- fixed.h~ Tue Mar 25 21:19:55 2003+++ fixed.h Tue Mar 25 21:33:34 2003@@ -181, 7 +181, 7 @@ return fixed ( a1, true ); }-#if 0 // no std::ostream needed in OPIE+#if 1 // no std::ostream needed in OPIE template inline std::ostream &operator << ( std::ostream &o, const fixed&f ) { o << double( f );fixed.h로의 수정은 필요 없다.opie snapshot 20021122 make% export QTDIR=/home/src/Qt/qt-2. 3.4% export OPIEDIR=/home/src/Qt/qt-2. 3.4% cp /usr/local/qte/bin/uic /home/src/Qt/qt-2. 3.4/bin% . /configure -debug% make인스톨qte와 lib, opie로부터 apps bin doc docs etc help i18n pics plugins share sounds를 /usr/local/opie에 복사하기 전과 같다.Screenshotopie도 분명하게 아이콘 재배열에 대응하고 있어서 옆에 4 개 이상 늘어놓기 위해서는 가로폭 420 pixel 이상이 필요하여 이 때문에 QVGA landscape에서는 3 개 밖에 정렬되지 않는다.Opie/mipsqte 2.3.5다음과 같은 순서로 작업한다.% export QTDIR=/home/src/Qt/mips/qt-2. 3.5% PATH=/opt/Embedix/tools/bin:${PATH}% cd /home/src/Qt/mips/qt-2.3.5% . /configure -qconfig qpe -depths 16,32 -xplatform linux-mips-g++ -system-jpeg -system-libpng -system-zlib -no-qvfb -no-vnc -no-xft% emacs src/MakefileQT_NO_FREETYPE comment out을 통해 libqte는 libfreetype FT_*를 부르게 되었지만, 링크가 되어있지 않기 때문에 make시 사용하기 위해 여기에 넣어 둔다. - QT_LIBS_OPT = -lpng -lz -ljpeg + QT_LIBS_OPT = -lpng -lz -ljpeg -lfreetype% cp /home/src/Qt/qt-2. 3.5/bin/uic bin% make% strip lib/libqte*Opie의 애플리케이션의 link 단계에서 virtual table 등이 대량으로 undefined되는 경우, Qt의 src/allmoc.cpp 작성에 실패할 가능성이 높다. 그 경우 src/allmoc.cpp를 지워 Qt를 다시 만든다.이 단계에서 libqte를 strip 해 둔다. opie는 libqte 타임 스탬프를 보고 있으므로, Opie를 만들고 나서 strip하면 opie make가 시작되기 때문에 주의한다.% export OPIEDIR=/home/src/Qt/mips/opie-cvs20030529-k4% export QTDIR=/home/src/Qt/mips/qt-2. 3.5% export HOSTQTDIR=/usr/local/qt3% export GSMLIBDIR=/home/src/Qt/mips/gsmlib-1. 10Opie cvs 20030529환경 설정을 끝마치고 나서 make xconfig로 컨피규레이션 하려 하지만, 바로 설정되지는 않으며, libkconfig.so가 없어 도중에 make xconfig에 실패한다. 그렇다고 먼저 libkconfg.so를 만들어 두어도 안 되기 때문에, make xconfig를 한 번 이상 하고 나서 다시 돌아온다.% make xconfig% pushd scripts/kconfig% make libkconfg.so% popd% make xconfig각자 틀릴수 있지만 Linux kernel 2.6의 xconfig와 비슷한 화면이 나타날 것이다.make에 들어가기 전에 qmake를 작성.추가로 PC 용으로 qmake를 make 할 때는% pushd mkspecs% ln -sf qws/linux-mips-g++ default% cd ../qmake% make% popd% make /home/src/Qt/mips/opie-cvs20030529-k4/gen.pro% pushd mkspecs% ln -sf qws/linux-x86-g++ default% cp qws/linux-mips-g++/qplatformdefs.h qws/linux-x86-g++% cd ../qmake% make으로 한다. qplatformdefs.h는 linux-x86-g++, linux-generic-g++에도 없지만 qmake 작성시에 참조된다. mips 특유의 무엇인가가 들어있는 것은 아니기 때문에 linux-mips-g++ 로부터 받아온다. qt 2.3.x와 달리 Opie 에서는 linux-x86-g++와 linux-generic-g++에 차이가 없기 때문에 어느 쪽으로도 해도 결과는 같다.% make% for name in **/Makefile ; do if [ ! -f ${name}. org ] ; then mv $name ${name}. org fi sed ‘s#-Wl, -rpath,$(OPIEDIR) /lib##g’ < ${name}. org > makefile. 1 sed ‘s#-Wl, -rpath,$(QTDIR) /lib##g’ < makefile. 1 > makefile. 2 sed ‘s#-Wl, -rpath, /home/src/Qt/mips/opie-cvs20030529-k4/. *##g’ < makefile. 2 > ${name} done으로 해두고 결과를 주목한다.% pushd bin% rm `file * | grep ELF | cut -f1 -d:`% cd ../plugins% rm **/*so*% cd ../lib% rm *so*% popd% make바이너리는 strip되지 않기 때문에, bin나 lib에 들어가 strip 해 둔다.인스톨apps bin doc docs etc help i18n lib pics plugins share sounds를 /opt/QtPalmtop에 복사하기 전과 같다.freetypeFreetype을 서포트 시켜도 속도 저하가 없으므로 관련 파일을 첨부한다. include/freetype/config/ftoption.h에서의 TT_CONFIG_OPTION_EMBEDDED_BITMAPS는 define한 채로 한다.undef 했을 경우.전영역에 vector인 glypth가 사용되어 문제없이 anti-alias를 사용할 수 있지만, anti-alias는 normal의 8배 메모리를 소비하기 때문에, 내장된 bitmap이 있는 area 품질이 저하된다.% apt-get source libfreetype6% cd freetype-2. 1.4% bzip2 -d < freetype-2. 1.4.tar.bz2 | tar -xv% emacs include/freetype/config/ftoption.h% . /configure --host=strongmips-linux% OBJ_DIR=. LIBTOOL=libtool makelibfreetype과 libqte의 관계libfreetype를 빌드 할 경우에 참조되는 헤더는 freetype 소스의 것이지만, libqte를 빌드 할 경우에 참조하는 freetype 관련 헤더는 /usr/include/* 쪽이다.libqte 자체가 TT_CONFIG_OPTION_EMBEDDED_ BITMAPS를 참조하고 있으므로 libfreetype의 설정을 바꾸려면 libfreetype.so.6 교체와 함께 /usr/include/freetype/config /ftoption.h 수정 및 libqte 리빌드가 필요하다.gsmlibOpie의 make xconfig는 GSM 모듈 관련 툴의 컴파일에 필요하다. 최근 Opie 에서는 config를 이용해 gsm 관련 툴을 대체하고 있다.% zcat gsmlib-1. 10. tar.gz | tar -xv% cd gsmlib-1. 10% . /configure% for name in **/Makefile sed ‘s/CC = gcc/CC = mips-linux-gcc/g’ < $name > makefile. 1 sed ‘s/CXX = g++/CXX = mips-linux-g++/g’ < makefile. 1 > $name fi% mv libtools libtools.org% sed ‘s#/usr/lib/ld#mips-linux-ld#g’ < libtools.org > libtools% makedialogsPDA나 스마트폰같이 제한된 QVGA(320x240)급 해상도에서는 기존의 데스크톱을 기준으로 한 가로가 긴 해상도인 VGA (640x480)이상 급에서 맞춰진 기준으로 디자인된 위젯들이 맞지 않게 되어 가로가 잘리는 것과 같은 부자연스러운 모습을 갖게 된다.따라서 윈도우들의 크기와 위젯을 조정할 필요가 있는데 그 중에서 주로 대화창(dialog)을 고쳐야 하는 경우에 대해서 설명한다.qconfig-qpe.h를 사용해 Qtopia 전용으로 Qt를 커스터마이즈 하면 libqte로부터 떼어져 버리는 위젯들이 꽤 있다. libqte를 리빌드 하면 대부분은 다시 넣을 수 있지만, 시험 삼아 QFileDialog, QFontDialog를 풀 화면 표시해 본다면 떼어져 있는 이유를 알게 된다.그림 5와 같이 주로 스크린이 좁아서 사용할 수 없는 QFontDialog의 예를 통해, 거의 QVGA 구현이 되지 않는 환경에서 QVGA로 표시하는 것이 문제점이 된다.qt-embedded와 qt-x11의 tarball을 나눌 정도라면 dialog 종류도 embedded에 한해 따로 디자인 해 두기 때문에 좋겠지만 그렇게 되어 있지 않으므로 원한다면 상용의 Qtopia를 구입하여 기술 지원을 받을 수 있다. 하지만, Opie에는 문제가 되는 dialog를 대체하는 dialog가 있다. 그것들은 libopie에 포함되어 Opie 환경이 아닌 Qtopia 환경에서도 사용할 수 있게 된다.OFileDialogOpie로 파일을 여는 dialog는 조금 귀찮은 구조를 하고 있다. 아마 ‘디렉토리를 의식하지 않아 좋다’라고 하는 Qtopia 대의명분과 조율 때문이겠지만, FileSelector, OFileSelector, OFileDialog::OFileDialog(), OFileDialog::getOpenFileName()등의 폴리시나 구성이 복잡하다.Opie의 file dialog에 관한 함수는 크게 나누어 5 종류가 있다.쪾새로운 OFileSelector::OFileSelector()쪾Qtopia 호환 OFileSelector::OFileSelector()쪾OFileDialog::OFileDialog()쪾OFileDialog::getOpenFileName()쪾OFileDialog::getCloseFileName()OFileDialog::getOpenFileName(), OFileDialog::get SaveFileName()는 OFileDialog의 static method이므로 constructor인 OFileDialog::OFileDialog()를 사전에 부를 필요는 없다.하위 디렉토리 탐사 여부에 대한 옵션은 Opie 에서 enum OFileSelector::Mode {OPEN=1, SAVE=2, FILESELECTOR=4, DIR=8 };하는 플래그로 동작을 변환시키게 되어 있다. 덧붙여서 ‘OPEN | FILESELECTOR’같이 OR로 연결해 표기할 수 있을 것 같지만, 그렇게 되지 않는다. 개별적으로 지정할 수 밖에 없다.OPEN, SAVE하위 디렉토리 탐색하지 않고, 각각 파일을 open 할 때FILELSELECTOR하위 디렉토리를 탐색해 리스트를 만든다.DIR지금은 사용안함.5 개의 함수에, Qtopia 호환 OFileSelector::OFileSelector()를 모두 쓰면: OFileSelector(const QString &mimeFilter, QWidget *parent, const char *name, bool newVisible = TRUE, bool closeVisible = FALSE );Mode를 지정하지 못하고 Mode=FILELSELECTOR로 동작한다. Qtopia 특징을 이용한 탐색과 함께 추가로 qpdf에서는 OFileSelector::OFileSelector()와 FileSelector::FileSelector()를 구사하고 있지만, 양자는 그대로 교환이 가능할 만큼 호환성을 가지고 있다.OFileDialog::getOpenFileName(), OFileDialog::getSave FileName()도 Mode를 지정할 수 없다. 각각 Mode=OPEN, Mode=SAVE로 동작하며 Qtopia에도 Qt에도 대응하는 함수는 없기 때문에, 사용자의 정의에 맞춰 쓰여지고 있다.OFileDialog::OFileDialog() 및 OFileSelector::OFileSelector()를 모두 쓰면:OFileDialog::OFileDialog(const QString &caption, QWidget *, int mode, int selector, const QString &dirName, const QString &fileName = QString::null, const MimeTypes &mimetypes = MimeTypes() );OFileSelector::OFileSelector(QWidget *wid, int mode, int selector, const QString &dirName, const QString &fileName = QString::null, const MimeTypes &mimetypes = MimeTypes() );모두 mode를 지정할 수 있다. OFileSelector::OFileSelector()가 Opie file dialog의 본체, OFileDialog::OFileDialog()는 wrapper가 된다. OFileDialog::OFileDialog()는 Qt의 QFileDialog::QFileDialog()와 이름이 닮아 있지만, FileSelector와 달리 함수로서의 호환성은 거의 없다.이것들을 통상의 애플리케이션에서 부를 일은 없다. 하위 디렉토리까지 탐색시키지 않으면 OFileDialog::getOpenFileName()를 부르면 좋고, 하위 디렉토리까지 탐색시킬 생각이라면 Qtopia 호환 OFileSelector::OFileSelector()를 사용하는 편이 Qtopia로의 이식에 편하다.그럼에도 불구하고, 이 2개를 여기서 설명하는 것은 OFileDialog::getOpenFileName() 등이 내부에서 OFileDialog: :OFileDialog()를 부르고 있어 OFileSelector::OFile Selector()와 굉장히 혼동하기 쉽기 때문이다. 얼핏 OFileDialog::getOpen FileName()도 하위 디렉토리 탐색을 하는 것처럼 보인다.그림 6이 OFileSelector이고 그림 7이 OFileDialog::get OpenFileName()이다.OFontSelector그림 6이 표준적인 OFontSelector이다. 테스트를 위해서이지만 하단의 The Quick ... 샘플 텍스트 부분을 포함해 이것 전체가 OFontSelector이다. 인수의 withpreview=true를 통해 표시된다.그림 7은 Opie의 Apearance로 이쪽은 샘플 텍스트 부분 없이 사용하고 있다. (withpreview = false). “Sample” 부분은 SampleWindow로서 Apearance내에서 구현되고 있다.덧붙여 그림 8과 같이 Qtopia 1.6.x 의 Apearance에도 폰트 메뉴가 있는데, 이쪽은 QComboBox 등을 사용해 직접 구현되기 때문에 폰트 메뉴의 형태로 위젯을 분리할 수 있도록 되어 있지는 않다.QPrintDialog, QProgressDialog, QInputDialog남는 3 개의 QPrintDialog,QProgressDialog,QInputDialog에 대해서는 Opie에도 대체 다이얼로그가 없다. Opie에서 일부러 분리할 만큼 복잡한 다이얼로그가 아닌 것도 확실하다.Qt Designer예를 들어 OFontSelector는 적당한 frame widget안에 넣는 경우가 있을 수 있다. 실제 Appearance는 OTabWidget에 붙여 사용하고 있으며 Qt Designer를 사용해 UI 디자인을 할 때 약간 주의해야 할 문제가 생긴다.Qt Designer는 libopie를 모르기 때문에 이러한 widget 를 취급할 수가 없으며 custom widget을 짜 넣을 수 있게 되지만 uic로 .cpp를 만들 때 서식이 맞지 않기 때문에 이용할 수 없다. uic는 custom widget을 QWidget의 도출 클래스로 보고 QWidget(QWidget *, const char *, WFlags) 형태로 부르려고 하지만, libopie의 각 클래스는 어느 것도 그러한 constructor를 가지지 않기 때문에 문제가 된다.OFileDialog::getOpenFileName()는 constructor가 아니며 widget조차 아니기 때문에 frame widget에 붙일 수 없으며, OFontSelector로 말하자면 다음과 같다.OFontSelector::OFontSelector ( bool withpreview, QWidget *parent, const char *name, WFlags fl );이런 형태를 하고 있지만 withpreview에 디폴트 인수가 설정되지 않아서 OFontSelector(QWidget *parent, const char *name, WFlags fl) 형태에서는 호출할 수 없다.Opie의 Appearance는 Qt Designer를 사용하지 않고 직접 .cpp나 .h를 쓰고 있는데 비해 Qtopia의 Appearance는 Qt Designer를 사용해 쓰여져 있다. Font Widget를 가질 수 없으므로, 그러한 방침이 일장일단이 있다고 할 수 있지만 현재 최신 버전인 Qtopia에 방법이 통일되어 있다.폰트를 만드는 방법BDF, TTF 로부터 QPF 폰트를 만든다. QPF 폰트를 dump 한다고 해도 할 수 있는 것과 할 수 없는 것이 있다.Anti alias에 대해bdf로부터 ttf 폰트로 컨버팅 할 수 있게 되어 있다. .pfa, .pfb도 할 수 있지만 ttf 변환이 실제로 가능한 폰트는 한정된다.ttftobdf등을 이용하면 ttf로부터 qpf에 폰트를 가지고 올 수 있지만, anti-alias를 서포트한 qpf를 만들려면 bdf 경유가 불가능하므로, 가능한 ttf로부터 직접 변환하고 있다.LC Font 에 대해QPF 포맷으로 배부되고 있는 것처럼 보이지만 실은 비슷하면서도 다른 포맷으로 qt-embedded-free로부터는 취급할 수 없다.makeqpfqpf를 만드는 작은 프로그램이다. 내용은 단지 Qt/Embedded 함수를 호출하고 있을 뿐이며 실제로 여러가지의 처리는 qte 서버가 담당한다. makeqpf는 Qt for X11에도 포함되어 있지만 (libqt.so를 link 하고 있는 makeqpf도 있다는 의미이지만) Transformed를 서포트하고 있지 않기 때문에 사용 되지 않는다. Qt/Embedded쪽(libqte.so를 link 하고 있는 것)을 사용한다.QPF dump를 서포트한 Qt/E 서버 준비Qtopia를 서포트하는 Qt/e 생성 시 bdf나 ttf를 사용할 수 없는 libqte가 생기게 된다. 우선은 bdf, ttf를 서포트 하는 libqte 생성부터 시작한다.qconfig-fat.h는 qconfig.h로부터 생성된다.#define QT_NO_FREETYPE#define QT_NO_BDF을 comment out하면 된다.을 comment out하면 된다.% cat qconfig-fat.h#define QT_NO_PROPERTIES#define QT_NO_DRAGANDDROPproperties와 Drag and Drop만 제외한 libqte를 만든다.QT_NO_PROPERTIES도 제외하면 애플리케이션이 qconfig-qpe.h로 만든 것과 호환이 안 되기 때문에(Default arguments 의 유·무 때문에 함수의 인수가 바뀌어 link 할 수 없게 되는 것이 있다) 제외할 수 없다. QT_NO_DRAGANDDROP쪽은 제외할 수 없는 것도 아니지만, qtopia-free examples의 make에 error가 나오는 것이 많다.Transformed 서버폰트를 다시 만드는 중요한 목적은 landscape용 폰트에 있다. VNC, QVFb, LinuxFb 서버에서 옆으로 길게 늘어뜨리는 세로쓰기 폰트는 dump 할 수 없다.그래서 Transformed 서버를 사용할 필요가 있다. 물론 linuxfb를 상대한 채로 makeqpf를 사용할 수도 있지만, 한번 qte가 linuxfb와 콘솔을 잡아 버리면 두 번 다시 돌아올 수 없다 (무리하게 kill 하면 키보드가 unicode map 상태가 되어, 키보드 초기화를 위한 커멘드 조차 칠 수 없게 되게 된다)그래서 QVFb 에 연결하는 편이 쓰기는 좋다.% cd /home/src/Qt/qt-embedded-2. 3.2% zcat ../qt-embedded-2. 3.2-k2.diff.gz | patch -p1% cp ../qtconfig-fat.h src/tools% export QTDIR=/home/src/Qt/qt-embedded-2. 3.2% . /configure -qvfb -vnc -qconfig fat -system-jpeg -gif -depths 8,16,24,32 -debug% make% su root# cp lib/libqte.so. 2.3. 2 /usr/local/qte/lib# cd /usr/local/qte/lib# ln -sf libqte.so. 2.3. 2 libqte.so. 2# ln -sf libqte.so. 2.3. 2 libqte.so# exit% make빌드 후에 이렇게 하면 90도로 회전한 폰트로 보이게 된다:% qvfb -width 320 -height 320 &% /usr/local/qte/bin/calculator -qws -diplay Transformed:Rot90TTF 소스의 준비libqte에 TrueType 서포트를 넣어 두어도 .ttc는 취급할 수 없다.그래서 ttc2ttf를 이용하여 ttf로 바꾼다.% . /ttc2ttf msgothic.ttc이렇게 했다면 아마 msgothic0.ttf, msgothic1.ttf 2개의 파일을 생성 할 수 있을 것이다.서버에 BDF, TTF를 읽어 들이게 한다위에서 만들었지만 BDF, TTF 서버에 읽게 하는데도 문제가 있다. qpf를 dump할 때에 makeqpf를 사용하지만, dump시에는 makeqpf 자신이 qte 서버가 된다. 그럼에도 불구하고, 서버가 사용하는 폰트와 makeqpf가 취급하는 폰트가 다른 개념이라는데 문제가 있다.Qt/E 폰트는 $QTDIR/lib/fonts/에 있다. qpf 가 아닌 폰트는$QTDIR/lib/fonts/fontdir에 리스트 업 되어 있지 않으면 안 되며 makeqpf는 $QTDIR/etc/fonts/fontdir로부터 자신이 취급하는 폰트의 리스트를 얻는다.dump 하려고 하는 폰트는 양쪽으로부터 인식되지 않으면 안된다.% cd /usr/local/qte/etc/fonts% ln -sf /usr/local/qte/lib/fonts/fontdir fontdirBDF 폰트 등은 $QTDIR/lib/fonts/쪽에 둔다. Dump 하는 주체는 서버이기 때문이다. TTF 쪽은 여기 이외에 두어도 서버가 적당하게 찾아내 와 주지만 저장장소는 결코 $QTDIR/etc/fonts/는 아니다.makefontdir-qws$QTDIR/lib/fonts내의 bdf로부터 fontdir을 만든다.fontdir# cd /usr/local/qte/lib/fonts# . /mkfontdir-qws *. bdf > fontdir# chown root.users .# chmod 1775 .fontdir 포맷은 다음과 같이 만들어 진다.:% cat fontdirnagaten knj10u2.bdf BDF n 50 90 umsgothic msgothic0.ttf FT n 50 0 su 90,100,160,170,220순서대로 플래그를 표시하면 다음과 같다name file renderer italic weight size flags각각 아래와 같은 의미를 가진다.폰트 정의 파일에서 QPF 폰트 지정은 실시하지 않는다. QPF 폰트는 fontdir 파일이 있는 디렉토리로부터 직접 읽히기 때문에 name_size_weight_italic_flag.qpf와 같은 이름을 붙이지 않으면 안 된다. 이는 각각 아래와 같은 의미를 가진다.커멘드 라인 옵션 -savefonts를 붙여 애플리케이션을 실행하면, QPF 이외의 폰트가 사용되었을 경우에 그 폰트로부터 QPF 파일이 작성되어 보존된다. 이것을 사용하면 애플리케이션에 의한 폰트사용 상황을 간단하게 알 수 있고, QPF 파일을 작성한 다음에 Qt/Embedded TTF와 BDF 서포트를 사용하지 않는 것으로, 애플리케이션의 메모리 사용량을 줄일 수가 있다. 이 옵션은 Qt/Embedded 프로그램 라이브러리의 원시 코드 kernel/qapplication_qws.cpp에 있는 qws_savefonts 초기화를 변경하는 것과 동일하다. 메모리 절약의 극단적인 예로서 폰트 중에서 필요한 캐릭터를 알고 있으면 폰트의 일부의 캐릭터(예: 메뉴에 표시되어 있는 캐릭터만)만을 렌더링 하는 것이 가능하다. 이 기능에 대해서는 QMemoryManager::savePrerendered Font()를 보도록 하자.% qvfb -width 20 -height 20 -depth 32 &% for rot in Rot0 Rot90 Rot180 Rot270 ; do . /makeqpf -qws -display Transformed:$rot -A doneQPF 덤프옵션의 ‘-A’를 지정하지 않으면 makeqpf는 폰트 선택 화면에 들어가지만 ‘-A’를 지정하면 모두 한 번에 덤프하므로 qvfb에는 아무것도 표시되지 않는다.폰트 패키지기본적으로 qte 애플리케이션으로부터 이용 가능한 것은 “lcfont”, “fixed”, “helvetica”, “unifont” 정도겠지만, 위의 폰트를 인스톨 해도 자동실행 되지는 않는다. 그래서 이러한 이름의 폰트들을 생성후에 옮겨 주는 작업이 필요하다.# cd /home/QtPalmtop/lib# for name in nagaten*qpf ; do ln -sf $name fixed_${name##nagaten_} done# kill -TERM `ps -o pid= -C qpe`간단하게 위와 같이 스크립트로 할 수도 있겠지만 데스크톱 환경에서 이용하는 폰트 패키지 매니저를 이용해도 된다.마치며이로써 Qtopia와 그와 호환되는 오픈소스 프로젝트 Opie 프레임워크로부터 필요한 빌드와 여러가지 테크닉 중 대화창 수정과 폰트 생성까지 살펴보았다. 처음으로 임베디드월드에서 임베디드 애플리케이션 개발을 장기간 연재로 다룬 만큼 좀 더 새롭고 알찬 내용으로 도움이 되는 것만 뽑아다가 쓸려고 했지만 항상 한정된 지면과 글의 전개 방향으로 인하여 모든 독자여러분께서 원하는 만큼 표현을 다 하지 못하는 경우가 많았다. 하지만 오늘도 실무에서 밤을 지새며 개발을 하시는 분들께 단 1%라도 문제 해결을 위한 도움이 되었다면 그걸로 만족하며 이만 물러가도록 하겠다. I’ll be back!
회원가입 후 이용바랍니다.
개의 댓글
0 / 400
댓글 정렬
BEST댓글
BEST 댓글 답글과 추천수를 합산하여 자동으로 노출됩니다.
댓글삭제
삭제한 댓글은 다시 복구할 수 없습니다.
그래도 삭제하시겠습니까?
댓글수정
댓글 수정은 작성 후 1분내에만 가능합니다.
/ 400
내 댓글 모음
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지