« Previous : 1 : 2 : 3 : 4 : 5 : 6 : Next »
이번 시간엔 지난 시간에 작성한 ui파일을 이용하여 실제로 어떻게 프로그램에 적용하는지 알아보도록하겠습니다.
이번엔 ui파일을 포함한 소스파일도 첨부합니다.
testdialog.tar.gz

예제 소스 파일


main.cpp
[code]#include <QApplication>
#include "testdialog.h"

int main(int argc, char **argv) {
    QApplication app(argc, argv);
    TestDialog dlg;
    dlg.show();
    return app.exec();
}
[/code]testdialog.h
[code]
#ifndef TESTDIALOG_H
#define TESTDIALOG_H

#include <QDialog>
#include "ui_testdialog.h"

class QButtonGroup;

class TestDialog : public QDialog {
    Q_OBJECT
public:
    enum Color {RGB = 0, CMYK, YUV};
    TestDialog();
private slots:
    void setColorChecked(int id);
private:
    Ui::TestDialog ui;
    QButtonGroup *buttons;
};

#endif
[/code]testdialog.cpp
[code]
#include <QButtonGroup>
#include "testdialog.h"

TestDialog::TestDialog() {
    ui.setupUi(this);
    buttons = new QButtonGroup(this);
    buttons->addButton(ui.rgb_radio, RGB);
    buttons->addButton(ui.cmyk_radio, CMYK);
    buttons->addButton(ui.yuv_radio, YUV);
    connect(buttons, SIGNAL(buttonClicked(int)), ui.color_combo, SLOT(setCurrentIndex(int)));
    connect(ui.color_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(setColorChecked(int)));
}

void TestDialog::setColorChecked(int id) {
    buttons->button(id)->setChecked(true);
}
[/code]
지난번에 만든 ui파일(전 testdialog.ui 란 이름으로 저장하였습니다)과, 위의 세 파일을 작성한후, 여느때처럼 qmake -project를 이용하여 프로젝트파일을 생성해보면 평소와 다른게 하나 있을 것입니다.

바로 FORMS += testdialog.ui 라는 항목인데요, 이렇게 생성한 ui파일을 프로젝트파일의 FORMS 항목에 추가해주면 컴파일시에 자동으로 ui파일로부터 소스코드를 생성해줍니다.

그러므로, 만약 qmake -project를 이용하지 않고, 직접 프로젝트 파일을 만드시는 분은, FORMS 항목을 추가하는 걸 잊지 마시기 바랍니다.

프로젝트 파일을 생성한후, qmake로 Makefile을 만들고, make를 실행해보면 지금까지 본적 없는 작업이 앞쪽에 추가된 것이 보일 것입니다.

/usr/bin/uic-qt4 testdialog.ui -o ui_testdialog.h

이런 작업이 처음에 실행됩니다(/usr/bin/uic-qt4 부분은 Qt의 설치 환경에 따라 다를수 있지만 거의 다 프로그램 이름에는 uic가 들어가 있을 것입니다).

컴파일이 끝난후 작업 디렉토리를 보면 moc_*파일 이외에 ui_testdialog.h라는 파일이 생성된 것을 알 수 있습니다.

이 파일이 실제로 소스코드에서 쓰이는 파일입니다.

한번 살짝 열어보면...
[code]...
#include <QtGui/QRadioButton>
#include <QtGui/QSlider>
...[/code]이렇게 ui파일에서 이용한 클래스들을 위한 해더파일의 인클루드문이나[code]...
class Ui_TestDialog
{
public:
...
    void setupUi(QDialog *TestDialog)
    {
    ...
    } // setupUi

    void retranslateUi(QDialog *TestDialog)
    {
    ...
    } // retranslateUi

};

namespace Ui {
    class TestDialog: public Ui_TestDialog {};
} // namespace Ui
...[/code]이러한 클래스 선언과 정의가 포함되어있습니다.

이 ui_testdialog.h파일이야 말로 디자이너를 이용한 UI 구현의 본체인 것입니다.

간단하게 이 파일이 만들어 지는 과정을 설명하자면, 우선 디자이너를 이용해서 xx.ui파일을 만듭니다.

이 xx.ui파일은 수정 편집이 용이하도록 XML로 기술되어있기 때문에 소스로 바로 이용하는 것은 불가능합니다.

여기서 uic라는 Qt와 함께 설치되는 툴을 이용하여, xx.ui파일을 ui_xx.h로 변환해주는 것입니다.

그러므로 FORMS 항목을 적지 않더라도, uic로 .ui파일을 해더파일로 변환해주기만 하면 문제 없습니다만, 매번 수정할때마다 작업하는게 귀찮으므로, 프로젝트 파일의 FORMS항목에 적어두어서, make할때 자동으로 uic를 돌리도록 하는게 편합니다.

이번엔 ui_testdialog.h파일에 선언된 클래스를 살펴보겠습니다.

우선 Ui_TestDialog 입니다.

이 클래스의 이름은 .ui파일에서 지정한 폼의 objectName으로 결정됩니다.

지난 시간에 TestDialog란 이름으로 지었기 때문에, 앞에 Ui_가 붙어 Ui_TestDialog란 이름이 됩니다.

폼을 만드는데 필요한 객체들이 선언되어있고, setupUi와 restranslateUi라는 두개의 함수가 선언되어있습니다.

retranslateUi는 UI의 문자열들을 번역내용을 갱신하기 위한 함수인데, 여기선 번역을 이용하지 않으므로 이용할일이 없습니다.

중요한건 setupUi라는 함수입니다.

이 함수의 내용을 보면 전전시간에 설명한 레이아웃 시스템처럼 각종 레이아웃이 생성되고 위젯들이 배치되는 과정과, 디자이너에서 연결한 시그널/슬롯들이 연결되도록 되어있습니다.

바로 이 setupUi가 UI를 위젯이나 다이얼로그등에 적용시켜주는 함수인 것입니다.

Ui_TestDialog외에, Ui라는 네임스페이스 속에, Ui_TestDialog를 상속하기만 하고 아무것도 하지 않는 TestDialog란 클래스가 선언되어있습니다.

결국 Ui_TestDialog와 Ui::TestDialog는 동등하므로 어느쪽을 쓰든 상관없습니다.

이제 소스코드를 살펴보겠습니다.

testdialog.h의 5번째 줄을 보면 ui_testdialog.h파일을 인클루드하고 있는 것을 알 수 있습니다. 이렇게 uic로 생성된 해더파일을 직접 인클루드하여 이용합니다.

그리고 Ui용 클래스를 이용하는 방법에는 두가지가 있습니다.

제가 이용한 것과 같은 private 멤버로 선언하는 방법과, priavte 상속을 이용하는 방법입니다.

상속에 대해 공부하셨다면 아시겠지만, private 상속은 public 상속과 달리 A has B 의 관계이므로 결국 private 멤버로 선언하는 것과 다를바가 없습니다.

17번째 줄을 보면 Ui::TestDialog ui; 와 같이 UI 클래스의 객체를 선언하고 있는 것을 알 수 있습니다.

만약 private 상속을 이용한다면, 클래스 정의 부분이
[code]class TestDialog : public QDialog, private Ui::TestDialog {[/code]와 같이 바뀌고, ui선언은 없어지겠지요.

그리고 보통 생성사에서 Ui::TestDialog::setupUi를 호출함으로써 UI를 적용합니다.

이 예제처럼 새로운 위젯을 직접 정의하는 경우라면 두가지 방법이 차이가 없지만, 예를 들어 특별히 복잡한 함수나 슬롯/시그널이 필요없이 간단하게 값만 입력받거나 디스플레이하기 위한 폼의 경우라면

QDialog dlg(this);
Ui::Form ui;
ui.setupUi(&dlg);

와 같이 함으로써 폼만 만들고 특별히 새로운 클래스를 작성하지 않고 이용하는 방법도 있습니다.

이경우는 새로운 클래스를 만들지 않으므로 다중 상속을 이용한 UI의 적용은 불가능하겠지요.

또, QButtonGroup이라는 처음보는 클래스가 있는데요, 이 클래스는 이름 그대로 버튼들을 하나의 그룹으로 묶어서 관리할때 편리합니다.

여기서 말하는 버튼은 QAbstractButton클래스를 상속받은 클래스들로, QPushButton이나 QRadioButton, QCheckBox, QToolButton 등이 해당합니다.

특히, QRadioButton은 처음부터 여러개중에 하나를 선택하기 위한 목적으로 연관된 버튼들이 여러개 있는 경우가 많기 때문에, QButtonGroup이 유용합니다.

testdialog.cpp파일을 보겠습니다.

소스를 보면 ui의 멤버로 버튼들로 접근 하고 있는 것을 알 수 있습니다.

각 멤버의 이름은 역시 전회에서 설명한대로 디자이너에서 objectName로 지정한 값이 이용됩니다.

만약 private멤버로 ui를 선언하지 않고, Ui::TestDialog를 private 상속 받은 경우라면 ui.대신에 보통 private 멤버처럼 접근해서 이용하면 됩니다.

여기서 7~9번째 줄을 보면 addButton을 이용하여 라디오 버튼들을 추가하고 있는데요, 버튼 포인터외에 인자를 하나 더 넘기고 있습니다.

이 인자는 버튼을 구분하기 위한 'id'입니다.

이 id로 버튼의 고유한 값을 지정하면 유용한 경우가 많습니다.

예를 들어, 이경우에는 enum을 이용하여 RGB, CMYK, YUV라는 상수를 정의하였고, 각각을 0, 1, 2로 대응시켰습니다.

이 값은 color_combo의 RGB, CMYK, YUV의 인덱스에 대응하기 때문에, id로 이 값들을 넘겨줌으로써 간단하게 10번째 줄과 같은 시그널/슬롯의 연결이 가능합니다.

또한, 반대로 color_combo의 인덱스가 바뀔때마다 라디오 버튼의 선택이 바뀌도록 setColorChecked라는 슬롯을 이용합니다.

이경우도 역시 id와 인덱스를 대응시켜뒀기 때문에, QButtonGroup::button()에 인자로 인덱스를 넘겨주어서 거기에 해당하는 id의 버튼 포인터를 가져오고, 그 버튼이 체크되도록 합니다.

한번 컴파일된 파일을 실행해보면, 라디오 버튼의 선택이 바뀔때마다 콤보박스의 값이 바뀌고, 반대로 콤보박스의 값을 바꿀때마다, 라디오 버튼의 선택이 바뀔 것입니다.

이상으로 2회에걸쳐 디자이너의 사용 방법에 대해 알아보았습니다.

일단 디자이너를 쓰는데 있어서 필요한 내용은 설명했다고 생각합니다. 액션이나 리소스에 대해서는 설명하지 않았는데, 차후에 메인윈도우에 대한 설명을 하게 되면 따로 설명하지 않더라도 어떻게 이용하는지 알 수 있게 될 것이라고 생각합니다.

다음 시간에는 각종 위젯에 대해 소개하도록 하겠습니다.

Posted by xylosper

2008/04/07 05:09 2008/04/07 05:09
,
Response
No Trackback , No Comment
RSS :
http://xylosper.net/rss/response/116

Trackback URL : http://xylosper.net/trackback/116

Qt에 대하여 - 8. Qt Designer - 폼 디자인

복잡한 레이아웃, 일일이 손으로 짤려면 귀찮고 나중에 좀 수정하기도 어렵죠.

이럴때 Qt Designer가 유용하게 쓰일 것입니다.

Qt Designer는 Qt로 프로그램을 만들떄 GUI적인 부분을 쉽게 디자인 할 수 있도록 도와주는 프로그램입니다.

소스로부터 컴파일 한경우는 자동으로 컴파일되어서 만들어지지만, 리눅스에서 패키지를 다운 받아서 Qt를 설치하신 분은 아마 designer는 별도로 설치하셔야 할겁니다(이부분은 배포판마다 다르므로 사용하시는 배포판의 패키지 저장소에서 검색해보세요).
Qt Designer

Qt Designer의 모습

Qt Designer(앞으로 그냥 디자이너 라고 하겠습니다)를 실행하면, 뭔가 묻는 다이얼로그가 나옵니다만, 일단은 무시해보시고 창을 닫으면 위와같은 모습이 나타날 것입니다.

혹시 위 스샷과 달리 여러개의 창이 따로따로 뜬다면 Multiple Top-Level Windows 모드이기 때문입니다.

Edit->Preferences 에서 User Interface Mode설정에 따라 인터페이스를 변경할 수 있습니다.

개인적으로 Docked Window모드를 선호하기 때문에 앞으로도 이 화면으로 설명하겠습니다(아마 창이 따로 노는거 말곤 다른 점 없을 것같습니다).

각 번호가 적혀있는 부분에 대해서 설명해보면..

1: Widget Box입니다. 각종 위젯들이 등록되어있어서 이걸 끌어다가 놓는 것만으로 위젯을 생성할 수 있습니다.
2. Object Inspector입니다. 객체들간의 포함관계가 트리형태로 제공됩니다. 객체의 갯수가 많아져서 찾기 힘들다거나, 객체가 겹쳐있어서 선택하기 힘든 경우에 유용합니다.
3. Property Editor입니다. 선택된 객체의 프로퍼티를 설정할 수 있습니다.
4. Signal/Slot Editor입니다. 연결된 시그널과 슬롯 페어가 표시되며, 여기서 시그널/슬롯 페어를 추가하는 것도 가능합니다.
5. Resource Editor입니다. 간편하게 리소스 파일을 열고 편집할 수 있습니다.
6. Action Editor입니다. 액션 클래스의 객체들을 편집할 수 있습니다.

여기서 5, 6은 아직 설명하지 않은 리소스나 액션이 들어가기 떄문에 이번에는 이용하지 않습니다.

뭐 암만 말로 열심히 설명해도, 직접 해보는 것만 못합니다.

이번엔 여러가지 위젯으로 간단해보이지만 손으로 짤려면 귀찮은 레이아웃의 폼을 만들어보면서, 디자이너를 이용하면 얼마나 간단하게 폼디자인을 할수 있는지 알아보겠습니다.

디자이너를 열면(혹은 위 화면에서 File->New Form) New Form 다이얼로그가 나타납니다.

왼쪽에 Dialog with Buttons Bottom 등의 몇가지 선택사항이 나타납니다.

이것들은 기본적인 위젯의 템플릿을 나타냅니다.

다이얼로그를 만들 것이라면 위의 셋중에 하나를, 위젯을 만들 것이라면 맨 아래의 Widget을 선택합니다(Main Window역시 아직 설명하지 않은 부분이므로 패스합니다).

위젯은 다른 위젯의 일부로 포함될 수 있지만, 다이얼로그는 무조건 별도의 창으로 나타나게 됩니다.

이번 예제는 다이얼로그를 선택하되, 직접 버튼을 추가할 예정이므로 Dialog without Buttons를 선택합니다.

사용자 삽입 이미지
위와 같이 가운데 부분에 새로운 창이 나타납니다.

이 창이 우리가 앞으로 디자인해갈 부분입니다.

먼저 오른쪽의 Object Inspector에서 위젯이 선택되어있는 것을 확인하고, Property Editor에서 이 위젯의 이름을 지정해줍니다.

이 이름은 객체에 따라서 역할이 다릅니다.

지금 수정하려는 최상위 위젯의 이름은, 나중에 생성될 C++코드에서 이 위젯의 클래스이름이 됩니다.

반면에 이제부터 여기에 추가해갈 위젯들의 이름은, C++코드상에서 그 위젯을 가리키는 변수명이 됩니다.

적당히 objectName란에 TestWidget이라고 적어줍니다.

이제 왼쪽의 Widget Box에서 필요한 위젯들을 가져와서 아래와 같이 배치합니다.
위젯을 배치한 모습

위젯을 배치한 모습


위젯을 추가할때는 Widget Box에서 원하는 위젯을 선택하고 마우스로 위의 폼으로 끌고와서 적당한 위치에 놓습니다.

이름이 적혀있는 위젯들은 이름을 보면 어떤 것인지 알수 있으므로 생략하고, 이름이 적혀있지 않은 위젯들만 무엇인지 적어보면, 첫째줄 두번째는 Line Edit, 두번쨰 줄의 가운데는 Horizontal Slider, 그 오른쪽은 Spin Box, 셋째줄의 스프링처럼 생긴것은 Horizontal Spacer, Group Box안의 가장 오른쪽 위젯은 Combo Box, 그리고 맨 아래의 OK/Cancel버튼은 Push Button이 아니라 Button Box 라는 녀석입니다.

배치할때는 정확하게 위치를 정해서 할 필요없이 대강 위젯들간의 위치관계만 생각해서 놓으면 됩니다.

추가가 끝났으면, 이번엔 아래 그림처럼 각 위젯의 텍스트를 수정합니다.
수정된 다이얼로그

수정된 다이얼로그


텍스트도 프로퍼티이므로 Property Editor에서 text 항목을 찾아서 수정하면됩니다만, text 프로퍼티를 가지고 있는 위젯들은 대게 더블 클릭하면 바로 text를 수정할 수 있게 되어있습니다.

또 Combo Box에는 RGB, CMYK, YUV 세개의 항목이 들어가있습니다. 역시 더블클릭으로 쉽게 추가하고 수정할 수 있습니다.

이외에도 각 위젯들의 각종 프로퍼티들은 거의다 Property Editor에서 수정가능합니다.

예를 들어 위 그림을 보면 그룹박스에 체크표시가 있는 것을 알수 있는데요, 그룹박스의 checkable 항목을 true로 설정하면 그룹박스도 체크할수있도록 됩니다.

다음으로 실제 소스코드에서 참조할 위젯들은 적당한 변수명을 정해줍니다.

이경우는 디자인을 위한 예제이므로 대부분 실제 코드에서 참조할일이 없기 때문에 거의 다 그냥 두어도 됩니다만, RGB, CMYK, YUV 라디오버튼의 objectName을 각각 rgb_radio, cmyk_radio, yuv_radio라고 하고, 콤보박스는 color_combo라고 합니다((위에서 설명했듯 이 이름은 클래스 이름이 아니라 변수명입니다).

이제 레이아웃을 짜봅니다.

우선 색반전하기 체크박스와 그옆의 미리보기 버튼, 그리고 스프링(-_-;)을 쉬프트키를 누른 상태로 차례대로 선택하여 동시에 세개가 선택된 상태가 되게 합니다.

그 후, 마우스 오른쪽 버튼을 클릭하고 Lay out -> Lay out Horizontal을 선택하면 이 셋이 하나의 레이아웃으로 묶이게 됩니다.

참고로 선택후 툴바의 Lay out Horizontal 버튼을 이용해도 됩니다.

이런식으로 위젯들을 선택해가면서 레이아웃을 눌러주면 됩니다.

다음으로 그룹박스의 레이아웃을 만듭니다.

당연히 Horizontal 레이아웃을 쓸것인데, 이경우는 그룹박스만 선택한 채로 Lay out Horizontal을 선택하면, 자동으로 그룹박스 내부의 위젯들이 Horizontal 레이아웃으로 배열됩니다.

이렇게 한 위젯안에 다른 위젯이 들어있는 경우는, 그 위젯들을 포함하고 있는 위젯을 선택한 상태에서 레이아웃을 선택해줍니다.

이번엔 슬라이더와 스핀박스를 선택하고 Horizontal 레이아웃을 짭니다.

그리고 다음과 같이 적당히 위젯을 예쁘게(-_-;) 배치합니다.

재배치된 모습

재배치된 모습

완벽할 필욘 없지만 가급적이면 줄이 맞게 배치하는게 좋습니다. 이번엔 Grid Lay out이란 것을 이용합니다.

지난시간에 설명하지 않은 레이아웃인데요, QGridLayout이라는 클래스로 구현되며, 표와 같은 모습은 레이아웃을 짜줍니다.

셀병합도 가능합니다.

위의 그림처럼 위젯들을 선택하고, Lay out in a Grid 를 선택하면, 자동으로 가장 적합한 모양의 그리드가 생성됩니다.

예를 들어 맨밑의 색반전하기 부분의 레이아웃은 자동으로 두칸을 병합하여 한줄을 다 차지하도록 됩니다.

마지막으로 남은 부분을 전부 세로로 나열하기 위해서 TestDialog를 선택하고 Lay out Vertical을 선택한후 적당한 크기로 만들기 위해 Adjust Size를 선택하면 다음과 같은 모습이 될것입니다.
최종 모습

최종 모습

이제 폼을 짜는건 끝났습니다. 하지만 디자이너의 기능은 이게 다가 아닙니다.

디자이너상에서 기본적인 시그널/슬롯도 연결해주는게 가능합니다.

예를 들어, 슬라이더를 움직이면 자동으로 스핀박스의 값도 바뀌고, 반대로 스핀박스의 값을 변경하면 자동으로 슬라이더가 움직이게 할려고 합니다.

스핀박스와 슬라이더는 값이 변경되면 valueChanged(int)라는 시그널을 뱉고, 동시에 setValue(int)라는 값을 설정해주는 슬롯도 있습니다.

그러므로 스핀박스의 valueChanged를 슬라이더의 setValue에, 슬라이더의 valueChanged를 스핀박스의 setValue로 연결해주면 됩니다.

먼저 슬라이더를 움직이면 스핀박스의 값이 바뀌도록 해봅시다.

우선 Edit 메뉴나 툴바에서 Edit Signals/Slots를 선택하여 시그널/슬롯 편집모드로 바꿉니다.

그리고 시그널을 뱉는 슬라이더를 클릭한 상태에서 마우스를 드래그하면 화살표가 나타납니다.

드래그 한채로 이 화살표를 시그널을 연결할 위젯위로 가져가서 놓으면 아래와 같이 연결할 시그널과슬롯을 묻는 다이얼로그(Configure Connection)가 나타납니다.
시그널/슬롯 연결 대화창

시그널/슬롯 연결 대화창

위와같이 연결할 시그널(valueChanged)와 슬롯(setValue)를 선택하고 OK를 누르면 이들이 연결됩니다.

참고로 선택된 시그널과 인자가 맞지 않아서 연결할 수 없는 슬롯은 선택할 수 없게 되어있습니다.

이런식으로 이번엔 스핀박스의 valueChanged시그널을 슬라이더의 setValue슬롯에 연결합니다.

이번엔 색반전하기 체크박스가 체크되어있을 떄만 미리보기가 가능하게 할려고 합니다.

체크박스는 체크상태가 변경될때마다 toggled(bool)이란 시그널을 뱉습니다. 이것을 미리보기 버튼의 setEnabled(bool)로 연결합니다.

그런데, 이때 Configure Connection창에서 버튼의 슬롯 목록을 보면 setEnabled(bool)이 안보일 것입니다.

setEnabled(bool)이란 슬롯은 QPushButton 자체의 슬롯이라기보다는, QWidget 클래스로부터 상속받은 슬롯이고, 처음 상태에서는 상속받은 시그널이나 슬롯은 나타나지 않게 되어있기 때문입니다.

이럴 때는 Configure Connection창의 하단에 있는 Show all signals and slots를 선택하면 상속받은 시그널과 슬롯까지 전부 표시해주므로, 이걸 체크하고 선택하면 됩니다.

마지막으로 버튼그룹의 accepted를 전체 다이얼로그(위젯을 배치하지 않은 빈공간으로 가져가면 됩니다. 화살표가 마치 접지마크 비슷한 상태입니다)의 accept로, rejected를 reject로 연결합니다.

accepted는 OK버튼이 눌렸을때, rejected는 Cancel이 눌렸을 때 뱉어집니다. 그래서 OK가 눌리면 다이얼로그가 '받아들여졌다(예를 들어, 설정 창이라면 설정을 적용한다든가)'라는 의미로 accept로 연결하고, Cancel이 눌리면 취소되었단 의미로 reject로 연결합니다.

이 두 슬롯은 모두 다이얼로그를 안보이게 하는 역할을 하고, 모달 다이얼로그의 반환값을 설정합니다.
연결된 시그널과 슬롯들

연결된 시그널과 슬롯들


이제 슬롯이 모두 연결되었는데요, 프로그램을 짜기 전에 한번 제대로 작동하는지 확인하고 싶습니다.

이럴땐, Form메뉴의 Preview(단축키:Ctrl+R)를 선택하면 미리보기가 나타납니다.

이상태에서 연결된 슬롯들은 모두 작동합니다.

예를 들어 슬라이더를 움직이면 스핀박스의 값이 바뀔 것이고, 체크박스를 체크했다가 해재할때마다 버튼은 enbaled/disabled 상태가 반복됩니다.

마지막으로 OK나 Cancel을 누르면 창을 닫게 됩니다.

이제 폼디자인이 끝났습니다.

원래는 이번회에 실제로 코드상에서 디자인한 폼을 어떻게 가져다 쓰는지까지 할려고 했습니다만, 그림이 많아서 내용이 길어진 관계로(언제나 길어지는 느낌입니다만-_-;) 실제로 프로그램을 만드는 것은 다음 시간에 소개하도록 하겠습니다.

Posted by xylosper

2008/04/06 19:08 2008/04/06 19:08
, ,
Response
No Trackback , 3 Comments
RSS :
http://xylosper.net/rss/response/114

Trackback URL : http://xylosper.net/trackback/114

CMP 0.1.0 공개

CMP는 (좀 무식한 방법으로 구현했지만) 통합/다중자막을 지원하는 리눅스용 동영상 플레이어입니다.

Custom Media Player 라는 이름을 붙여서 혼자서 쓰던 플레이어인데, 이번기회에 좀 손봐서 공개하기로 하였습니다.

다만 제가 직접 파싱하기 때문에, 현재는 제가 아는 포맷인 sami와 subrip밖에 지원하지 않습니다-_-;

다른 포맷을 알려주시면 추가하도록 하겠습니다.

구현은 Qt로 하였고, MPlayer자체도 크로스 플랫폼이므로 소스 자체는 크로스플랫폼입니다만, 리눅스에서밖에 써보지 않았기 때문에 다른 플랫폼에서는 어떻게 돌아갈지 모르겠습니다.

손봤다고 해도 말그대로 '좀'이기 때문에 다른 분들이 쓰시기에는 불편한점이나 부족한점도 많을 것 같고, 테스트도 혼자 써본게 다이므로 버그도 많을 듯 합니다.

이러한 점들은 저에게 알려주시면 개선해나가도록 하겠습니다.

라이센스는 GPLv2를 따르며, Qt4.3이상과 MPlayer(rc2에서만 테스트해봤습니다)가 필요합니다.

0.1.1이 공개되었습니다!

이하 개발후기랄까 잡설입니다-_-;

제가 리눅스를 이용하면서 가장 아쉬웠던 것이 통합자막을 제대로 보여주는 플레이어가 없다는 점이었습니다.

그나마 위안이었던게 Kipple님이 만든신 MPlayer용 통합자막 패치인데, 소스를 보니 두 클래스가 싱크가 일치하는 경우에만 함께 표시하도록 되어있었습니다.

실제로 통합자막은 약간 어긋나는 경우도 많고, 게다가 이쪽 언어는 두마디로 나오는게 다른 언어로는 한마디로 나오거나 하는 경우도 있기 때문에, 제 욕구를 완벽히 충족시키진 못했습니다.

한번은 저도 직접 MPlayer의 자막 파싱 부분을 수정해볼려고 들여다 보았지만, 역시나 C는 너무 어렵더군요.

그러던 중, 2007년말, Qt4.4tp1이 발표되면서 Phonon을 이용하여 간단하게 동영상 플레이어를 만들 수 있게 되어, '자막을 제대로 보여주는 플레이어 만들자'는 생각에 처음엔 Phonon을 이용하여 동영상 플레이어를 만들기 시작하였습니다.

이래저래 만들고나니, 대강 혼자선 쓸만하다고 생각될 만큼은 만들어졌는데, Qt4.4tp1은 아직 개발 도중인지라 부족한 점이 많았습니다.

그러다가 MPlayer의 slave모드를 이용하면 Phonon못지 않게 쉽게 동영상 플레이어를 만들 수 있단 것을 알았고, 백엔드를 Phonon에서 MPlayer로 옮겼습니다.

이런 과정때문에 Phonon만큼 구조적이지 못하지만 클래스 설계는 (Phonon만큼 방대하진 않지만) Phonon과 비슷한 것같습니다.

또 MPlayer와 연동하는 방법은 SMPlayer를 대부분 베꼈참조했습니다.

결국 제가 짠 부분과 Phonon을 흉내낸 부분, SMPlayer를 베낀참조한 부분이 뒤섞여 뒤죽박죽인 소스가 되었지만, 혼자 쓰는 거니까 걍 두고있었습니다.

이번에 공개하기전에 소스를 좀 정리하긴 했지만 그래도 아직 뭔가 꺼림직한 부분이 많습니다-_-;

이름은 뭐 말그대로 나를 위한 커스텀 플레이어란 뜻입니다.

참고로 Phonon으로 만들던 초기 이름은 IWSUS(I Want to See Unified Subtitles) 였습니다-_-;

차후 Qt4.4가 정식 릴리즈되고 Phonon이 MPlayer만큼 쓸만해지면 Phonon으로 옮길지도 모르고, 또 계속 만들어 나가면 이름도 또 바뀔지도 모르겠습니다. 아니 그전에 계속 개발 할지도 모르겠네요.-_-;

통합자막을 구현하는데 있어서 정말 단순한 방법을 썼습니다.

일단 자막을 다 파싱한 다음에, 하나로 합쳐진 임시 자막파일을 만들고, 그자막 파일을 다시 읽어들이는 방법이지요.

중간에 어차피 파싱을 다시하는 거면 굳이 플레이어를 만들 필요 없이 그냥 통합자막을 변환해주는 프로그램을 만들면 되지 않나, 라는 생각에 자막 변환기로 노선을 변경한 적도 있었습니다.

하지만 그냥 플레이어가 만들어 보고 싶어서 다시 돌아왔습니다.

처음엔 느리면 어떻게하나 걱정했는데, 다행히도 요즘 컴퓨터가 빠른 덕에 별로 티안나더군요.

OSD로 임시파일인 temp.smi를 읽어들인다고 나타나는 부분은, 일부러 OSD를 끄지 않고 냅뒀습니다.

어차피 소스도 공개되있는데 숨겨봤자 나중에 소스보면 다 알테니 첨부터 다 까발리자는 생각입니다.

사실 벌써 진작에 완성되었을 건데, 중간에 Phonon에서 MPlayer로 백엔드를 바꾸는 바람에 UI를 제외하고 코드는 사실상 처음부터 다시 쓰게되었고, 결국 황금같은 방학을 전부 써버렸습니다.

아이콘은 KDE4의 아이콘 테마인 Oxygen을 가져다 썼습니다.

스스로 그릴 실력은 못되니까요...

그런데 어플레이케이션을 나타내는 아이콘이 마땅치가 않네요.

어플리케이션의 아이콘은 유일해야할 아이콘이니 가져다 쓸수도 없고, 그릴 실력은 안되고...

언젠가 허접한거라도 하나 그려넣을지도 모르겠습니다.

써보시고 조금이라도 도움되셨다면 한마디씩 해주시면 감사하겠습니다.

Posted by xylosper

2008/04/04 17:49 2008/04/04 17:49
, ,
Response
A trackback , 6 Comments
RSS :
http://xylosper.net/rss/response/115

Trackback URL : http://xylosper.net/trackback/115

Trackbacks List

  1. 내 데비안 패키지 설치 순서와 설정-멀티미디어

    Tracked from Polaris 2008/04/12 14:17 Delete

    <멀티미디어> 1. beep-media-player 설치(환경설정 들어가서 alsa로 설정) -- 뭐 다른게 좋다는 분도 계시지만 그래도 전 이게 좋은거 같네여... 버그도 좀 있지만... ---->근데 요세는 그냥 vlc로 듣고 있습니다. 2. gnome-alsamixer 설치 # apt-get install gnome-alsamixer 제 노트북이 소리 조정이 미세하게 안되더군요... 3. mplayer 설치 1) audio sdl(compiz-..

Qt에 대하여 - 7. 레이아웃 시스템

보통 다이얼로그같은 것을 하나 만들면 그 안에는 여러개의 위젯이 들어갑니다.

무작정 생성만 해두면 다 한군데에 몰려있으니 위치관계를 고려해서 리사이즈도 해주고 위치도 옮겨주고 줄간격도 맞춰주고...어떻게 깔끔한 모양을 만들었습니다.

그런데 창을 리사이즈했더니 위젯은 고대로 있고 창만 커집니다.

기껏 좌표 계산해서 가운데로 가져다 놨더니 창을 키우니 더이상 가운데가 아니게 되고, 반대로 창을 작게 했더니 다 짤려 나오고...

그럼 리사이즈 할때마다 다시 계산을 다 해서 배열해야 할까요?

우리의 강력한 큐티는 이런 경우에도 쉽게 대처할 수 있는 레이아웃 시스템을 가지고 있습니다.

일단 소스코드를 보시죠.

mainwidget.h
[code]
#include <QWidget>

class QLabel;
class QLineEdit;

class MainWidget : public QWidget {
    Q_OBJECT
public:
    MainWidget();
private slots:
    void setName(const QString &name);
private:
    QLabel *printName;
    QLineEdit *nameEdit;
};
[/code]mainwidget.cpp
[code]
#include "mainwidget.h"
#include <QLabel>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSpacerItem>

MainWidget::MainWidget()
: QWidget() {
    printName = new QLabel();
    nameEdit = new QLineEdit(this);
   
    QHBoxLayout *hbox1 = new QHBoxLayout;
    hbox1->addWidget(new QLabel(trUtf8("이름을 적어주세요:"), this));
    hbox1->addWidget(nameEdit);
   
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addLayout(hbox1);

    QHBoxLayout *hbox2 = new QHBoxLayout;
    hbox2->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
    hbox2->addWidget(printName);
    hbox2->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
    vbox->addLayout(hbox2);

    setLayout(vbox);

    connect(nameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setName(const QString&)));

    setName(QString());
}

void MainWidget::setName(const QString &name) {
    static const QString text = trUtf8("당신의 이름은 %1입니다.");
    if (name.isEmpty())
        printName->setText(text.arg(trUtf8("무명씨")));
    else
        printName->setText(text.arg(name));
    adjustSize();
}
[/code]main.cpp
[code]
#include <QApplication>
#include "mainwidget.h"

int main(int argc, char **argv) {
    QApplication app(argc, argv);
    MainWidget mw;
    mw.show();
    return app.exec();
}
[/code]
whats-your-name

실행화면

이번 예제는 이름을 적으면 적은 이름을 표시해주는 whats-your-name이라는 프로그램입니다.

mainwidget.h는 QLineEdit라는 새로운 위젯이 나온 것 외에는 딱히 설명할 것이 없어보입니다.

간단히 복습해보면 Q_OBJECT는 메타 오브젝트 시스템을 위한 키워드이며, 시그널/슬롯을 이용할려면 선언해줘야하고, slots는 함수를 슬롯으로 이용할 수 있게 만들어주는 키워드입니다.

QLineEdit는 위의 스샷에서 보듯이 무엇가를 입력받기 위한 위젯입니다(오래되서 확실친 않지만 아마 MFC에선 CEdit란 이름의 클래스가 같은 역할을 했던거 같습니다).

이름에 Line이라고 적혀있는 대로, 입력 받을 수 있는건 한줄이며, QLabel등과 동일하게 text()/setText()로 현재 글을 가져오거나 지정할수도 있습니다.

이 위젯은 글자가 입력/수정될때마다 textEdited(const QString&)시그널과 textChanged(const QString&)시그널을 내뱉습니다.

둘의 차이는, textEdited는 사용자가 QLineEdit에 직접 입력한 경우에만 발생되지만, textChanged는 프로그램내에서 setText등을 이용해서 글자를 바꿨을때도 발생한다는 것입니다.

우리는 여기서 textChanged시그널을 이용해서 입력된 이름을 표시할 것입니다.

이제 mainwidget.cpp파일로 넘어갑니다.

해더파일중에 QVBoxLayout, QHBoxLayout, QSpacerItem 등 처음 보는 클래스들이 보일텐데요, 이건 생성자안에서 생성할때 설명하겠습니다.

printName은 "당신의 이름은 ~입니다."라고 표시하기 위한 라벨이고, nameEdit가 이름을 입력받기위한 에디트입니다.

13번째 줄을 보면 QHBoxLayout *hbox1를 할당하고 있습니다.

이름에서 알수 있듯이 오늘의 주인공인 레이아웃 클래스중 하나입니다.

QHBoxLayout의 H는 Horizontal의 H입니다.

즉 수평으로 위젯들을 배치할때 유용하게 쓸수 있는 레이아웃 클래스입니다.

다음줄을 보시면 addWidget이란 멤버함수를 호출하고 있습니다.

이 함수는 레이아웃에 widget을 추가해줍니다.

QHBoxLayout클래스에 추가된 widget은 추가된 순서대로 배열됩니다.

addWidget의 안을 보면 new QLabel(trUtf8("이름을 적어주세요:"), this) 이렇게 바로 동적할당한 포인터를 넘겨주고 있습니다.

이건 두줄로 풀어쓰면

QLabel *label = new QLabel(trUtf8("이름을 적어주세요:"), this);
hbox1->addWidget(label);

과 동등한데, 여기서 label은 딱히 다음에 쓸일이 없기 때문에, 그냥 변수를 선언하지 않고 바로 포인터를 넘겨준 것입니다(전회에 설명한대로, 동적할당하더라도 this의 자식객체로 들어가기 때문에 나중에 자동으로 해제되므로 이런식으로 짜는게 가능합니다).

그리고 또 다음줄에 addWidget(nameEdit)가 호출되고 있습니다.

따라서 "이름을 적어주세요:"라벨이 제일 왼쪽에 들어가고, 그오른쪽에 nameEdit가 들어갑니다.

만약 여기에 새로운 위젯을 추가한다면 이번엔 nameEdit의 오른쪽에 추가되겠죠?

17번째 줄을 보면 이번엔 QVBoxLayout *vbox를 할당하고 있습니다.

눈치빠른 분이라면 V가 vertical의 이니셜이란 것을 눈치채셨을 듯합니다.

QHBoxLayout과 기본적인 사용법은 똑같으나, 위젯을 배치하는 방향이 옆으로(왼쪽에서 오른쪽으로)가 아니라 세로로(위에서 아래로)배치하는 레이아웃 클래스가 QVBoxLayout입니다.

다음줄을 보면 addLayout이란 함수로 hbox1를 추가하고 있습니다.

addLayout은 기본적인 동작방식은 addWidget과 거의 같지만, 그 대상이 위젯이 아니라 다른 레이아웃 클래스라는 점만 다릅니다.

위젯들을 포함한 레이아웃이 통째로 삽입됩니다(결과는 스샷을 보는 쪽이 알기쉽습니다).

20번째 줄을 보면 hbox2라는  새로운 QHBoxLayout을 할당하고 있습니다.

사실 더이상 hbox1은 쓸일이 없기때문에, 새로운 변수를 선언할 필요없이 hbox1에 새로 할당해도 되지만, 편의상 구분하기 위해 새로운 변수를 선언하였습니다.

hbox2에서는 addItem이라는 또다른 함수가 호출되고 있습니다.

'item'이라는 것은 레이아웃에 들어갈수 있는 아이템(마땅히 다른 말이 떠오르지 않네요)입니다.

이렇게 레이아웃에 추가될수 있는 것은 위젯, 다른 레이아웃, 그리고 아이템 이렇게 세가지입니다.

그리고 아이템의 경우는 클래스가 딱 두개밖에 없습니다.

하나는 여기서 만들고 있는 QSpacerItem, 다른 하나는 QWidgetItem입니다.

사실 addWidget함수는 내부적으로 QWidgetItem을 생성하여, addItem함수를 이용하도록 구현되어있습니다.

하지만 addWidget이나 기타 위젯으로 바로 접근 가능한 함수들을 제공하고 있으므로, 사실성 QWidgetItem을 직접 보게될일은 거의없고, 결국 우리가 보게될 아이템은 QSpacerItem 한개뿐입니다.

QSpacerItem은 이름그대로 공간을 때우기위한 아이템입니다.

예를 들어, 다이얼로그에서는 자주 확인 버튼이 맨아래 오른쪽에 위치한걸 볼 수 있습니다.

이러한 것은 QHBoxLayout에 QSpacerItem과 버튼을 차례로 추가해주는 방법으로 구현가능합니다.

이렇게 하면 다이얼로그가 리사이즈되도 버튼은 그대로 두고 QSpacerItem이 알아서 늘어났다 줄어들었다 하면서 공간을 채워주는 것이죠.

생성자는

QSpacerItem(int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum,QSizePolicy::Policy vPolicy = QSizePolicy::Minimum)

이런 모습인데요,  w와 h는 sizeHint를 제공하고, hPolicy와 vPolicy는 sizePolicy를 제공합니다.
저의 경우는 0, 0, Expanding, Minimum 이라고 적었는데요, 이렇게 하면 기본적인 크기는 너비 높이 0으로 하되, 리사이즈될때는 옆으로는 늘어나도 되지만, 세로로는 늘어나지 않는 QSpacerItem을 생성한 셈입니다.

21-23라인을 보시면, 두개의 QSpacerItem을 생성하여 그 중간에 printName을 끼워넣는 방식으로 레이아웃이 만들어지고 있습니다.

이렇게 하면, 양쪽에 늘어나는 QSpacer가 놓여있기 때문에, printName은 창크기가 바껴도 항상 한가운데에 위치하게됩니다(사실, 좀더 쉬운 구현방법은 그냥 아이템없이 printName이 직접 늘어나게 한다음, printName의 글자를 중앙정렬시키는 건데요, 일부러 아이템을 소개하기위해 이렇게 했습니다).

이렇게 만들어진 hbox2를 다시 vbox에 추가합니다.

최종적으로 만들어진 레이아웃의 모양을 시각화하면 다음과 같습니다.
시각화된 레이아웃

시각화된 레이아웃

위의 빨간 사각형이 hbox1, 아래의 빨간 사각형이 hbox2, 그리고 아래에 양쪽으로 그려져있는 파란 스프링이 두개의 QSapcerItem입니다.

그리고 빨간 사각형 두개의 주변으로 vbox가 있는 셈이지요.

이렇게 레이아웃이 완성되면 이 레이아웃을 위젯에 세팅해주기만 하면되고, setLayout함수가 그역할을 합니다(26번째 줄).

이때 주의할 것은 이미 한번 레이아웃이 세팅된 위젯에는, 그 레이아웃을 delete하기전엔 새로운 레이아웃을 세팅할 수 없다는 것입니다.

이정도면 레이아웃에 대해서 알아야 할건 거의다 설명했는데, 한가지 설명안한것이 소유권(부모-자식관계)입니다.

여기서는 레이아웃 클래스들은 전부 부모를 지정하지 않고(널로 지정하고) 생성하였습니다.

하지만 한 레이아웃이 다른 레이아웃으로 들어가거나, 어떤 위젯의 레이아웃으로 지정되면(setLayout), 그 레이아웃이나 위젯이 부모가 됩니다.

최종적으로 이 예제에서는 vbox는 hbox1과 hbox2의 부모가, this(MainWidget)은 vbox의 부모가 되어 있는 것입니다.

또, addItem으로 추가된 아이템의 소유권(아이템클래스같이 QObject를 상속받지 않는데, 자동으로 삭제되는 경우는 자식-부모라고 할 수 없어서 소유권이란 말도 자주 씁니다)은 레이아웃이 가지고 있으므로, 레이아웃이 해제될때 자동으로 추가된 아이템들도 해제됩니다.

단 addWidget으로 추가된 위젯의 경우는 소유권(부모)이 바뀌지 않습니다.

다만, setLayout으로 레이아웃을 지정하면, 레이아웃의 위젯들은 자동으로 setLayout을 호출한 위젯의 자식이 됩니다.

따라서 이 예제에서는 printName등을 할당할때, this를 넘겨주지 않더라도 최종적으로는 this의 자식이 될것입니다.

만약 레이아웃 클래스(QHBoxLayout등)을 생성할때 부모 위젯을 넘겨주면 어떻게 될까요?

이경우는 자동적으로 부모위젯의 setLayout함수가 호출되어 레이아웃을 세팅합니다...만, 이경우도 이미 세팅된 레이아웃이 있는 경우 무시됩니다.

이제 다시 예제로 돌아갑니다.

connect는 처음 설명한데로 QLineEdit의 textChanged시그널과 MainWidget::setName을 연결하고 있고, 마지막으로 빈이름을 설정하고 있습니다.

이제 setName함수를 보면, static 변수로 printName라벨의 텍스트로 설정할 바탕이될 문자열을 선언하고 있습니다.

그런데 %1이란 이상한 글자가 들어가 있네요.

if문을 보면 name이 비어있는 경우와 비어있지 않은 경울 놔눠서 하고 있는데, QString::arg()라는 함수가 호출되고 있습니다.

이 %1이라는 건, 비유하자면 printf나 sprintf등에서 쓰는 %d 라던가 %s 같은 다른 변수로 치환될 부분을 나타냅니다.

그리고 숫자의 순서대로 치환해갑니다.

예를 들어 QString("%1 + %2 = %3").arg(1).arg(2).arg(3) 와 같이 하면 "1 + 2 = 3"이라는 문자열이 반환되는 거죠.

예제에서는 text.arg(trUtf8("무명씨")) 혹은 text.arg(name)라고 하고 있으므로 name이 비어있으면 %1이 무명씨로, 그렇지 않으면 %1이 name으로 치환될 것입니다.

또한 sprintf처럼 숫자를 치환할때 적당히 0이나 다른 문자를 체워넣거나 하는 것도 가능합니다.

하지만 %치환자와 arg를 이용한 치환에서만 가능한 것이 있습니다.

바로 번역할 때, 어순때문에 치환자의 순서가 바뀌더라도, 치환의 순서는 숫자로 지정되므로 그대로 유지된다는 것입니다.

예를 들어 tr("I do %2 width %3")을 "나는 %3을 가지고 %2를 한다"와 같이 번역해도 치환에는 문제가 없는것이죠.

마지막으로  adjustSize()라는 함수를 호출하고 있는데, 이것이 참 편리한 함수입니다.

이 예제에서 글자가 길어지면 저절로 printName라벨의 크기가 늘어납니다. 그리고 printName은 hbox2에 들어있으므로 hbox2도자동으로 늘어나고, hbox2는 vbox에, vbox는 MainWidget에 들어가있으므로 최종적으로 MainWidget의 크기가 늘어나면서 레이아웃이 유지됩니다.

그런데 일단 늘어난 다음에 다시 글자수를 적게하면 한번 늘어난 크기가 줄어들지 않습니다.

다시 줄어들게 하고는 싶은데, 글자수에 따라서 얼마나 줄여야될지를 계산할려니 또 귀찮아집니다.

이럴때 adjustSize()함수를 호출하면 sizeHint()에 기반하여 적절한 크기를 계산하여 리사이즈 해주므로 너무 크기도, 너무 작지도 않게 적당하게 자동으로 크기가 변경됩니다.

이 레이아웃 시스템, 코드도 그리 어렵지 않습니다만, 예를 들어 학생정보를 입력받기위해서 수많은 라벨과 에디트등이 필요한 상황이라면 이것도 또 코드짜는게 만만치 않을 것입니다.

하지만 우리의 사랑스러운 큐티(여담이지만 트롤테크 내부에서는 Qt를 cute처럼 발음한다고 하네요)는 이런 경우에 쉽게 GUI디자인을 가능케하는 Qt Designer라는 프로그램을 제공하고 있습니다(사실 이미 여러분은 디자이너의 화면을 보았습니다. 저위에 제가 '시각화'라는 이름으로 달아놓은 스샷이 바로 디자이너에서 디자인할때의 모습입니다).

다음시간에는 이 Designer에 대해 알아보도록 하겠습니다.

Posted by xylosper

2008/03/28 05:09 2008/03/28 05:09
, ,
Response
No Trackback , No Comment
RSS :
http://xylosper.net/rss/response/113

Trackback URL : http://xylosper.net/trackback/113

Qt에 대하여 - 6. Meta-Object System

제목을 쓰면서 느낀건데 아무래도 귀찮아서 그냥 Meta-Object는 메타오브젝트라고 한글로 적어야겠습니다-_-;

다시 전편의 소스를 살펴보시면, 두개의 해더파일에 모두 Q_OBJECT 라는 키워드가 들어가있는 것이 보일 것입니다.

이 키워드야말로 큐티의 메타오브젝트시스템의 핵심이라고 할수 있습니다.

메타 오브젝트 시스템의 기본은 QObject클래스와 Q_OBJECT 키워드, 그리고 moc(메타 오브젝트 컴파일러)라는 큐티전용 전처리기입니다.

메타 오브젝트 시스템이란, 이름대로만 설명하자면 객체스스로 객체에 대한 정보를 담고 있는 것을 말합니다.

일반적인 C++클래스는 그 클래스 자체에 대한 설명은 담고 있지 않습니다.

예를 들어 A라는 클래스의 인스턴스 a를 생성했을때, 따로 클래스의 선언이나 정의를 한 소스를 보거나, 클래스를 설명한 문서를 보는 등의 일을 하지 않으면, 이 인스턴스 a로부터 a의 클래스 '이름'은 무엇인가, 또 다른 클래스 B와의 상속관계는 어떠한가와 같은 정보는 알수 없습니다.

이에반해 큐티의 메타오브젝트 시스템이 적용된 클래스는, 인스턴스만 가지고도 그 인스턴스의 형에 대한 정보를 알 수 있습니다.

사실 이것과 메타 오브젝트 시스템 전반에 관련되어서 어떤 연결점이 있는진 저도 잘 모르므로, 너무 구체적인 설명은 피하도록하고, 메타 오브젝트 시스템덕분에 어떤게 가능하고, 어떻게 이용하지는에 대해서 적도록 하겠습니다.

일단 전편에서 설명한 시그널/슬롯 또한 이 메타 오브젝트 시스템덕분에 가능합니다.

또, qobject_cast라고 하는 형변환 연산자가 제공됩니다.

이 형변환 연산자는 QObject를 상속받은 클래스간에서 형변환을 해주는데 쓸수 있습니다.

원래 C++에는 dynamic_cast라는 상속관계에 있는 클래스간에 부모클래스에서 자식클래스로, 혹은 자식클래에서 부모클래스로 형변환해주는 캐스팅연산자가 있습니다.

하지만, 이 dynamic_cast는 RTTI에 의해서 캐스팅되기 때문에 이식성이 없는 코드가 되기 쉽고, 무엇보다 성능면에서 심각하게 느립니다.

그래서 보통 이건 100% 상속관계에 있다고 확신할땐 그냥 static_cast를 씁니다.

하지만, 형변환을 해야겠는데, 상속관계에 있을수도 있고 아닐수도 있다거나, 서로다른 동적 라이브러리간에서 형변환을 해야하는 경우에 유용한 것이 qobject_cast입니다.

이 연산자(내장된게 아니므로 정확히는 연산자가 아니라 템플릿함수입니다만 앞으로 그냥 편의상 형변환 연산자와 똑같이 취급하겠습니다)는 QObject를 상속한 두 클래스간에 형변환을 해주고, 만약 형변환이 불가능한 경우는 널 포인터를 반환해줍니다.

예를 들어 QLabel은 QWidget을 상속했으므로

QLabel *label = new QLabel;
QWidget *widget = qobject_cast<QWidget*>(label);

과 같은 형변환에서는 제대로 형변환이 이루어질테고, QLabel과 QPushButton은 상속관게에 없으므로

QPushButton *button = qobject_cast<QPushButton*>(label);

과 같은 변환에서는 button은 널 포인터가 됩니다.

그럼 qobject_cast<QPushButton*>(widget)의 결과는 어떨까요?

얼핏 QPushButton은 QWidget을 상속받으니까 widget을 QPushButton*으로 형변환 가능할 듯하지만, 이게 기똥차게도 널을 반환해줍니다.

실제로 widget은 QLabel의 인스턴스니까 여기서 만약 널이 반환되지 않으면 라벨을 토글하는 등의 어처구니 없는 일이 발생하겠죠?

특히 유용하게 쓰이는 경우는 예를 들어 어떤 함수의 인자로 QWidget*을 받아오는데, QPushButton*인 경우만 특별한 처리를 하고 싶거나 하는 경우에, 간단하게 qobject_cast로 캐스팅해보고 널인지 아닌지 검사해서 QPushButton의 포인터만 골라내는 것이 가능합니다.

또한, tr/trUtf8을 이용한 번역기능도 역시 메타 오브젝트 시스템의 덕분입니다(사실 이부분은 저도 실제로 번역해본 적이 없기 때문에 자세한 설명은 못하겠습니다. 그냥 습관적으로 일단 외부로 보여지는 문자열은 전부 tr로 싸고 있습니다.)

Q_PROPERTY키워드를 이용한 프로퍼티도 지원됩니다만, 프로퍼티는 제가 써본적이 없네요-_-;

위에서 설명한 기능들을 이용하기위해서는 메타오브젝트 시스템이 필요하고, 여기서 Q_OBJECT의 역할이 나옵니다.

Q_OBJECT는 매크로인데, 메타오브젝트를 위한 각종 함수들을 자동으로 선언해줍니다.

그리고 moc는 컴파일하기전에 Q_OBJECT가 포함된 클래스 선언문을 찾아서 적절한 moc파일을 생성해줍니다.

moc는 qmake로 생성한 Makefile에서 알아서 돌려주므로, 우리가 신경써야 할것은 첫째, QObject를 상속받을 것(당연히 직접 상속받지 않고 QObject를 상속받은 클래스를 상속받아도 상관없습니다), 그리고 클래스 선언내에 private영역에 Q_OBJECT라는 키워드를 적어둘 것, 이 두가지입니다.

connect함수의 원형을 보면 알겠지만, const QObject *로 넘기도록 되있으므로 일단 QObject를 상속받지 않으면 connect함수 자체를 이용 할 수 없습니다.

하지만 저도 종종 Q_OBJECT선언하는 것을 까먹곤 합니다.

이런 경우 컴파일은 잘되도 실행했을때 해당하는 슬롯이나 시그널이 없다는 문구가 콘솔창에 나타나므로, 분명히 제대로 했는데도 그럴땐 혹시 Q_OBJECT를 빼먹은 것 아닌가 확인하시구요, Q_OBJECT를 추가했을땐 꼭 그파일도 moc가 돌아가도록 qmake를 한번 더 실행해줘야합니다.

또 한가지 주의할점이 있는데요, Q_OBJECT는 반드시 해더파일에 있어야 합니다.

확실친 않지만 아마도 moc가 해더파일만 도는건지, cpp파일에 Q_OBJECT를 포함한 채로 클래스를 선언하면 링크단계에서 어떤 vtable이 존재하지 않는다는 에러가 나옵니다.

이상 메타오브젝트 시스템에 대한 설명이었습니다.

마지막으로, 지난 예제 clicks의 MainWidget의 생성자에 대해서 좀 설명해볼려고 합니다.

생성자부분만 다시 가지고 오면,
[code]
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent) {
    button = new DoubleButton(this);
    label = new QLabel(trUtf8("클릭 안됨"), this);

    connect(button, SIGNAL(doubleClicked()), this, SLOT(setLabelDoubleClicked()));

    label->move(100, 0);
    resize(200, 50);
}
[/code]
여기서 보통 C++을 쓰는 사람이라면 new연산자를 이용하는 부분이 잘 이해가 안될수 있습니다.

딱히 new를 이용해서 동적할당할 필요성도 못느끼는데다가, 아무리 눈씻고 찾아봐도 여기서 할당한 메모리를 delete하는 부분이 없으니까요.

물론 '간단한 프로그램이고 한번 할당한담에 종료할때 알아서 운영체제가 해제할테니 별로 신경안써도 되'라고 생각 할수도 있지만, 사실 프로그램이 종료될때 button과 label은 잘 delete된후에 종료됩니다.

그 비밀은 바로 QObject가 가지고 있습니다(그래서 딱히 메타 오브젝트 시스템을 이용한게 아님에도 밀접한 연관이 있는 클래스이므로 이 글에 같이 적습니다)

전에 위젯 클래스를 소개할때 생성자로 넘기는 '부모 위젯'에 대해 이야기한적이 있습니다.

사실 그때 설명하지 않은 것이 있는데, 부모는 위젯에만 있는게 아닙니다.

QObject를 상속받는 모든 클래스는 '부모'를 지정할 수 있습니다.

QObject의 생성자를 보아도 QObject(QObject *parent = 0) 처럼 QWidget과 동일한 모습을 가지고 있습니다.

이렇게 어떤 인스턴스가 다른 인스턴스의 '자식'이 되면, 부모 인스턴스가 소멸될때 자식으로 등록된 인스턴스를 알아서 지워줍니다.

다시 생성자를 보면, label도, button도 모두 생성자의 마지막에 this(MainWidget 인스턴스의 포인터)를 넘기고 있는 것을 알 수 있습니다.

MainWidget은 main함수의 지역변수로 선언되어있으므로, 프로그램이 종료될때 저절로 소멸될 것입니다.

그리고 이때, MainWidget의 자식으로 등록되어있는 label과 button도 알아서 delete 시켜주는 것이죠.

물론 동적 생성을 하지 않고 처음부터 그냥 인스턴스로 생성해도 상관없습니다.

이경우는 delete하지 않아도 자동으로 소멸될것이고, 또한 MainWidget의 소멸자에서 미리 delete하도록 해도 문제없습니다.

어떤 원리인지는 저도 잘 모르지만 알아서 delete가 필요하면 지우고 필요없으면 안하는 구조로 되어있으니까요.

이덕분에 Qt로 프로그래밍할때는 자바의 가비지 컬렉션처럼 메모리 관리가 자동으로 되면서도 가비지 컬렉션처럼 느리지도 않다는 잇점이 있습니다.

다만 주의할 점은 이것은 QObject를 상속받은 경우에만 해당하는 것입니다(일부 QObject를 상속받지 않고 구현된 큐티의 클래스들도 있는데, 이 클래스들에는 해당사항없습니다).

그래도 아직 따질게 있으신 분이 계실지도 모르겠습니다.

동적할당하는 메모리가 자동으로 관리된다고 해도, 이경우는 동적할당행위 그자체가 필요 없는데 굳이 동적할당을 하는 이유가 뭐냐? 고 물으실수 있는데요, 그 이유는 컴파일 의존성을 줄이기위해서입니다.

컴파일 의존성이 뭔데?


포인터로 선언하고 해더파일을 인클루드하는 대신에 전방선언을 해서 컴파일 의존성을 줄일수 있는 것이지요.

그래서 대부분의 QObject를 상속받는 클래스의 인스턴스는 동적할당으로 생성하게 됩니다.

이제 생성자에서 느껴진 위화감은 해소되었을꺼 같은데, 마지막 두줄이 신경쓰입니다.

    label->move(100, 0);
    resize(200, 50);

이 두줄인데요, 이 두줄을 적은 이유는, 그냥 생성만 해놓으면 button과 label이 겹쳐서 보이지 않기 때문에 적당히 label을 오른쪽으로 움직여준것이고, 또 움직인 영역이 위젯의 크기보다 크면 짤리거나 안보이기 때문에 위젯자체도 적당한 크기로 리사이즈 해준 것입니다.

그런데, 이게참 번거롭기 짝이없을 뿐만 아니라 모양새도 참 구리구리합니다.

그럼 매번 모든 위젯의 위치를 계산하고 움직여준다음에 크기를 변경해줘야하는 수고가 필요한걸까요?

절~대 아닙니다. 우리의 큐티는 이미 이 문제를 쉽게 해결할수 있는 레이아웃 시스템을 가지고 있습니다.

다음회에는 레이아웃에 대해서 소개해보겠습니다.

Posted by xylosper

2008/03/27 05:07 2008/03/27 05:07
, , ,
Response
No Trackback , 6 Comments
RSS :
http://xylosper.net/rss/response/112

Trackback URL : http://xylosper.net/trackback/112

« Previous : 1 : 2 : 3 : 4 : 5 : 6 : Next »

Notices

Archives

Calendar

«   2010/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
174161
Today:
29
Yesterday:
88
The images of equations
in this website are CodeCogs - An Open Source Numerical Library