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

2015/12/24

Qt5에서 ActiveX 사용


Qt5를 갖고 놀고 있는데 ActiveX를 사용해야 할 일이 생겼다. ActiveX를 걷어내야 한다고 아우성치는 마당에 내가 생각해도 좀 웃프다. Windows 프로그램을 짜본 적이 없어서 ActiveX니 COM Object니 하는 것들이 낯설다. 그래도 몇일 뒤면 잊어 버리기 때문에 정리해 두고 간다.

Qt가 좋은 점이 Multi-platform을 지원하기 때문에 특정 OS API를 몰라도 쉽게 사용할 수 있다는 점이다. 요즘은 Python을 많이 사용하는 추세인듯 싶은데, PyQt 같은 것을 써도 아래의 기본적인 방법들을 똑같이 적용할 수 있다.

기본적으로 Qt Document 홈페이지에서 ActiveQt 사용법에 대한 기본적인 정보를 얻을 수 있다. 하지만, 실제로 프로그램이 돌아가도록 하려면 기본적인 정보만으로는 부족하더라.

Qt 프로젝트 설정

우선, ActiveX Control을 사용하기 위한 Class는 QAxWidget이다. 이 놈의 애비는 QAxBase이고, COM Object를 지원하는 QAxObject는 형이다. 이 놈들을 사용하려면 Qt 프로젝트 생성 후에 Qt 프로젝트 설정 파일(*.pro)에 아래와 같이 설정을 추가해야 한다.
QT += axcontainer

Qt 프로그램에 ActiveX Control 불러오기

ActiveX Control은 3rd Party가 제공하는 것이다. 우리나라에서는 Internet Explorer로 웬만한 웹사이트 돌아 다니다 보면 설치해야 하는 경우가 허다하다. 아무튼 이놈들이 설치되면 Windows Registry에 Class로 등록이 되고, 각자 고유한 Class ID(CLSID)를 갖고 있다. ActiveX Control을 사용하려면 이 CLSID 또는 고유한 이 Object의 명칭을 알아야 하고 아래와 같이 불러올 수 있다.

QAxWidget *axControl = new QAxWidget("{CLSID}"); 또는,
QAxWidget *axControl = new QAxWidget("고유 명칭");
위에서 CLSID는 실제 등록된 값을 사용해야 한다. 예를 들어, AAAAAAAA-1111-BBBB-1111-AAAAAAAAAAAA와 같은 값이다. 또는 아래와 같이 해도 된다.

QAxWidget *axControl = new QAxWidget;
axControl->setControl("{AAAAAAAA-1111-BBBB-1111-AAAAAAAAAAAA}");
초기화가 됐으니, ActiveX Control이 제공하는 함수나 Event들을 Qt 애플리케이션에서 사용할 수 있게 된다. 프로그램에서 사용하려면 매뉴얼이 있어야 하겠지만, API Header를 dump 받을 수도 있다.

ActiveX Control의 함수 사용

가령, ActiveX가 제공하는 아래의 함수를 사용하려면 QAxBase가 제공하는 dynamicCall()을 사용하면 된다. 물론, Windows의 변수 Type에 대응하는 Qt 변수 Type을 사용해야 한다.
CString GetData(LPCTSTR sRQ, LPCTSTR sCode, long index);

QString sRQ="RQ", sCode="1111";
int index = 0;
QVariant received = axControl->dynamicCall("GetData(QString, QString, int)", sRQ, sCode, index);
QString result = received.toString().trimmed();
ActiveX나 COM Object에서 Variant Type을 사용할 경우 QVariant Type을 사용하면 된다. QT가 제공하는 dumpcpp를 사용할 수 있다면 dynamicCall 보다 C++ 스럽게 ActiveX Control 함수를 사용할 수도 있다.

ActiveX Control의 Event 사용

3rd Party ActiveX Control을 사용할 때 매뉴얼이 부실한 경우도 많고, API를 dump할 경우에도 함수 Prototype은 알 수 있지만, Event 함수의 Prototype을 알 수 없을 경우도 있다. 특히, ActiveX Control이 네트워크 상의 서버에서 데이터를 가져오는 기능을 할 때 애플리케이션에서 요청한 데이터가 준비됐는지를 알려면 ActiveX Control이 제공하는 Event를 반드시 사용해야 한다. 다행히, Qt Designer를 사용하면 ActiveX Control이 제공하는 Event Prototype을 알 수 있다.

예를 들어, ActivX Control이 제공하는 Event Prototype이 OnReceive(QString, int, QString)이라면, 아래와 같이 Qt의 Signals & Slots를 사용하면 된다.
connect(axControl, SIGNAL(OnReceive(QString, int, QString)), this, SLOT(onReceive(QString, int, QString));

MyClass::onReceive(QString RQ, int index, QString sCode)
{
    qDebug() << RQ << index << sCode;
}
즉, MyClass::onReceive()에서 ActiveX Control이 넘겨준 값들을 사용할 수 있게 된다.

2015/05/06

Qt 5.4 fcitx immodule Build 및 한글 사용


이전 글에서 우분투 15.04 Unity Desktop 환경에서 한글 입력기로 fcitx를 추전했는데, 막상 Qt 5.4의 Qt creator에서는 한영키가 동작하지 않아 황당했다. 당연히, 이 Qt creator로 build한 Qt 5.4 애플리케이션에서도 한영키가 동작할리 없다. 이전 글에서 테스트한 Qt 앱은 Qt 5.3에서 build 했던것 같다. 한마디로 우분투 패키지로 다운 받은 fcitx는 최신 버전의 Qt 5.4를 지원하지 않는 것 같다. 뭔가 달라진 듯...

구글링하니까 마침 일본 블로그에 fcitx build하는 방법이 올라와 있어서 다시 build 한 후 Qt5 immodule를 복사해 넣었더니 모든 Qt 애플리케이션에서 한글입력이 잘 된다. 다만, 그 블로그 내용을 그대로 따라하니까 안되더라... 그래서 다시 정리한다.

Qt 5.4에서 fcitx Qt5 immodule build

$ sudo apt-get install git cmake

$ git clone https://github.com/fcitx/fcitx-qt5.git
$ cd fcitx-qt5
$ git checkout 0.1.3

build 하기 전에 Qt 5.4가 설치된 홈 폴더를 미리 알아 두어야 한다. 나의 PC에는 /opt/OpenSrc/Qt에 설치하였다.

$ cmake . -DCMAKE_PREFIX_PATH=/opt/OpenSrc/Qt/5.4/gcc_64
$ make

기존 fcitx 패키지로 설치된 Qt5 immodule 대체 설치

기존의 우분투 fcitx 패키지에 딸려온 Qt5 immodule을 대체해버리는 것이 가장 좋은 설치 방법이다. 안전을 위해 원래 파일은 org.libfcitxplatforminputcontextplugin.so로 백업했다.

$ sudo mv /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/org.libfcitxplatforminputcontextplugin.so

$ sudo cp ./src/libfcitxplatforminputcontextplugin.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/

또 한가지 대체해야 할 파일이 있다는 것을 나중에 알았다. Qt5 immodule이 사용하는 runtime library 파일 하나가 같이 build 되었다. 이 놈까지 버전이 일치해야 한다.

$ sudo mv /usr/lib/x86_64-linux-gnu/libfcitx-qt.so.0.1 /usr/lib/x86_64-linux-gnu/org.libfcitx-qt.so.0.1

$ sudo cp ./fcitx-qt5/libfcitx-qt5.so.0.1 /usr/lib/x86_64-linux-gnu/libfcitx-qt.so.0.1

참고 사항

참조한 일본 블로그에는 환경 변수 설정 같은 것들이 있다. 그렇지만 .bashrc에 입력기 환경변수를 넣으면 X-Window의 실행 순서 때문에 환경변수가 제대로 동작하지 않을 수도 있다. 편하고도 안전한 방법은 먼저 fcitx 패키지를 설치하는 것이다. 이 글은 fcitx 패키지가 먼저 설치된 것을 전제로 했다.

일본 블로그에서 설명하듯이 기존 파일들을 대체하지 않고도 새로 build한 Qt5 immodule을 사용할 수도 있는데 runtime library가 두 개라 환경 설정을 제대로 해주어야 한다(기존 패키지와의 충돌때문에 설정이 다소 복잡하므로 세부 설명은 안한다). 다만, Qt5 immodule을 복사해야 할 폴더는 /opt/OpenSrc/Qt/5.4/gcc_64/plugins/platforminputcontexts가 아니고, /opt/OpenSrc/Qt/Tools/QtCreator/bin/plugins/platforminputcontexts이더라. 뭐, 두 곳에 다 복사해도 상관은 없다.

2015/05/01

Qt5 Mouse Event 처리


Qt5도 배울 겸 재미삼아 마우스 이벤트 처리 프로그램을 만들어 본다. UI 프로그램을 거의 짜 본적이 없어서 그런지 마우스 이벤트 처리가 생각보다 복잡하다. 소시적부터 관심이 있었으면 Libre Office Impress 같은 것을 만들었을지도... 도형 들을 마우스로 선택하고 옮기고 크기를 조절하고 Widget 창 크기 조절시 창크기에 비례해서 도형 크기도 변해야 한다. 마우스 커서도 선택 모드, 드래그 모드, 리사이즈 모드에 따라 바꿔주고... 우분투의 Screenshot으로 캡춰했더니 마우스 커서는 모양이 안바뀌네...

그런데, Qt에서 사각형 Class인 QRect가 좀 웃긴다. QRect(x, y, width, height)로 생성할 수 있는데 x2 = x+width-1, y2 = y+height-1로 정의하고 있다. 가령, rect.x2()나 rect.y2()를 호출하면 각각 1pixel이 모자라게 된다. 그런데 놀라운 것은 painter.drawRect(rect)로 그리면 1pixel이 모자라지 않게 잘 그려준다. 별 문제가 없어 보이지만, 계산을 할때는 x2 = x+width, y2 = y+height로 계산해야 1pixel에 대해 제대로 고려가 된다. 또, 한가지 이로 인해 파생되는 문제는 rect.normalized()라는 함수가 width나 height가 음수일 때 양수로 사각형 좌표를 바꿔주는데 x와 x2를 swapping 해버림으로써 사각형 크기가 어긋나게 된다.

1pixel 차이 가지고 뭐 그리 쫀쫀하게 구냐고 할지도 모른다. 예전에 MS Powerpoint 발표자료 만들때 그룹 resize 같은 거 하면 pixel이 어긋나는 한 두놈이 꼭 생겨서 그거 맟추느라 고생했던 기억이 남아 있다. 왜냐하면 1pixel이라도 어긋나면 시청자에게 굉장히 허술하게 보이기 때문에... 최근 버전의 Powerpoint에서는 도형을 움직이거나 resize 하면 근처 도형에 정렬할 수 있도록 기능이 강화된 듯하다. 하지만 근본적인 문제가 해결된 것은 아니었다. 이 문제는, 표를 만들 때 무식한 방법으로 사각형 하나를 복제해서 한 줄을 만든 뒤 이 줄을 다시 복제해서 여러 줄을 만들고, 그룹 resize 후에 cell 들을 정렬시키고 나서 Powerpoint Window 화면을 resize해 보면, 금방 확인해 볼 수 있다. 표의 경계선 굵기가 일정하지 않게 되는 문제가 생긴다. Libre Office Impress에서는 resize시에도 정렬이 잘 되는 편이다.

그리고, Qt는 개발자에게 모든 필요한 것을 제공해 주려는 의도가 강해 보인다. 잡다하게 많은 Class 들이 뭐 썩먹을 수야 있지만 좀 난잡해 보인달까... 가령, int 버전의 QRect와 qreal(float) 버전의 QRectF와 같이 종류별로 int 버전과 qreal 버전을 구비하고 있다. 화면에서의 pixel들은 궁극적으로 int형이긴 하지만 resize나 reshaping 같은 것을 하기 위해서는 qreal 버전이 필요하기는 하다. 위의 pixel이 어긋나는 문제가 생기는 원인도 실수를 정수로 변환하면서 발생하는 rounding 오류 때문에 생긴다. 해결 방법은 가능한한 rounding 오류가 발생하지 않도록 해주면 된다. 가령, 특정 정수의 배수를 사용하여 나머지가 항상 0이 되도록 만들어 준다든지...


Mouse Press Event
void ChartWidget::mousePressEvent(QMouseEvent* event)
{
    m_firstPos = boundPos(event->pos());
    m_lastPos = m_firstPos;

    bool newPos = true;
    bool selectStat = false;
    bool modKey = event->modifiers().testFlag(Qt::ShiftModifier);
    bool itemSelected = m_selectedItems.size() ? true : false;
    QVariant info;

    if(itemSelected) {
        // if mod key pressed, deselect the selected item.
        if(modKey) {
            foreach(Drawable* drawable, m_selectedItems) {
                if(drawable->contains(m_firstPos, &info)) {
                    removeSelectedItem(drawable);
                    selectStat |= drawable->onDeselect();
                    newPos = false;
                    break;
                }
            }
        }
        else {
            // if reclick one of selected items, check new mouse event.
            foreach(Drawable* drawable, m_selectedItems) {
                if((m_resizeIndex = drawable->selectIndex(m_firstPos))) {
                    m_resizeMode = true;
                    m_mouseItem = drawable;
                    return;
                }
                if(drawable->contains(m_firstPos, &info)) {
                    m_dragMode = true;
                    return;
                }
            }
            // if new mouse position, deselect all the selected items.
            foreach(Drawable* drawable, m_selectedItems) {
                removeSelectedItem(drawable);
                selectStat |= drawable->onDeselect();
            }
        }
    }

    if(!itemSelected || newPos) {
        Drawable* clicked = drawableAt(m_firstPos, &info);
        // if new item is clicked select the item.
        if(clicked) {
            m_selectMode = false;
            m_dragMode = true;
            addSelectedItem(clicked);
            selectStat |= clicked->onSelect(event, modKey, info);
        }
        // if no item is selected check new select event.
        else m_selectMode = true;
    }

    if(selectStat) emit selectionChanged();
    if(!m_selectMode) reDraw();
    QWidget::mousePressEvent(event);
}
Mouse Move Event
void ChartWidget::mouseMoveEvent(QMouseEvent* event)
{
    m_lastPos = boundPos(event->pos());

    m_cursor = mcPointer;
    if(m_selectedItems.size()) {
        if(m_resizeMode) {
            m_cursor = (m_resizeIndex < 3) ? mcSizeLCross :
                       (m_resizeIndex < 5) ? mcSizeRCross :
                       (m_resizeIndex < 7) ? mcSizeVert :
                       mcSizeHoriz;
            m_selectRect = m_mouseItem->region();
            foreach(Drawable* drawable, m_selectedItems) drawable->onResize(m_resizeIndex);
            m_selectRect = m_mouseItem->region();
        }
        else if(m_dragMode) {
            m_cursor = mcDrag;
            foreach(Drawable* drawable, m_selectedItems) drawable->onMouseMove(event);
            m_firstPos = m_lastPos;
        }
        else {
            foreach(Drawable* drawable, m_selectedItems) {
                if((m_resizeIndex = drawable->selectIndex(m_lastPos))) {
                    m_cursor = (m_resizeIndex < 3) ? mcSizeLCross :
                               (m_resizeIndex < 5) ? mcSizeRCross :
                               (m_resizeIndex < 7) ? mcSizeVert :
                               mcSizeHoriz;
                    break;
                }
                if(drawable->contains(m_lastPos)) {
                    m_cursor = mcDrag;
                    break;
                }
            }
        }

    }

    reDraw();
    QWidget::mouseMoveEvent(event);
}
void Drawable::onMouseMove(QMouseEvent* event)
{
    Q_UNUSED(event)
    
    QRect rect = region();
    QPoint pos = rect.topLeft() + owner()->lastPos() - owner()->firstPos();
    setRegion(QRect(pos, rect.size()));
}
Mouse Release Event
void ChartWidget::mouseReleaseEvent(QMouseEvent* event)
{
    m_lastPos = boundPos(event->pos());
    //m_lastPos = adjustPosition(m_lastPos);

    bool modKey = event->modifiers().testFlag(Qt::ControlModifier);

    // if mouse left-click position changed.
    if(event->button()==Qt::LeftButton && (m_firstPos-m_lastPos).manhattanLength()>TOL_SELECT) {
        if(!modKey && m_selectedItems.size()) {
            foreach(Drawable* drawable, m_selectedItems) drawable->onMouseRelease(event);
        }
        else {
            bool selectStat = false;
            QVariant info = 0;
            QRect rect = QRect(m_firstPos, m_lastPos);
            foreach(Layer* layer, m_layers) {
                foreach(Drawable* drawable, layer->children()) {
                    // if non-selected items are in mouse dragged region, select new items.
                    if(rect.contains(drawable->region())) {
                        addSelectedItem(drawable);
                        selectStat |= drawable->onSelect(event, false, info);
                    }
                }
            }
            if(selectStat) emit selectionChanged();
        }
    }

    m_selectMode = false;
    m_dragMode = false;
    m_resizeMode = false;
    m_resizeIndex = 0;
    if(m_mouseItem) m_mouseItem = 0;
    reDraw();
    QWidget::mouseReleaseEvent(event);
}
void Drawable::onMouseRelease(QMouseEvent* event)
{
    Q_UNUSED(event)
    
    setRegion(positiveRect(m_rect));
    QVector2D ar = owner()->aspectRatio();
    m_rectOrigin.setWidth(m_rect.width()/ar.x());
    m_rectOrigin.setHeight(m_rect.height()/ar.y());
}
Widget Resize Event
void ChartWidget::resizeEvent(QResizeEvent* event)
{
    if(m_pixmap) delete m_pixmap;
    m_pixmap = new QPixmap(event->size());

    setViewportChanged();
    setViewport(rect());
    qreal arX = m_viewport.width() / m_viewportOrigin.width();
    qreal arY = m_viewport.height() / m_viewportOrigin.height();
    setAspectRatio(arX, arY);

    reDraw();
    setViewportChanged(false);
}
void Drawable::update()
{
    QVector2D ar = owner()->aspectRatio();
    qreal arX = ar.x();
    qreal arY = ar.y();
    if(owner()->viewportChanged()) {
        qreal x1 = arX * m_posOrigin.x();
        qreal y1 = arY * m_posOrigin.y();
        qreal x2 = x1 + arX * m_rectOrigin.width();
        qreal y2 = y1 + arY * m_rectOrigin.height();
        m_posCurrent = QPointF(x1, y1);
        // rounding errors are increased if you rsize chartwidget frequently.
        m_rect = QRect(RoundToMultiple(x1), RoundToMultiple(y1), 
                       RoundToMultiple(x2-x1), RoundToMultiple(y2-y1));
    }
    else {
        m_posCurrent = QPointF(m_rect.x(), m_rect.y());
        m_posOrigin = QPointF(m_posCurrent.x()/arX, m_posCurrent.y()/arY);
    }
}

2014/09/27

uim 벼루 Qt5 immodule build


우분투 uim 기본 패키지에는 Qt5 immodule이 들어 있지 않아서 Qt5 애플리케이션에서 한글 입력이 안된다. uim 사이트에 가보니 2013/6/30일자 uim-1.8.6 소스가 최신인데 받아 보니 Qt5를 지원하지 않는다. 문서에도 Qt5에 대한 언급이 전혀 없다. 혹시나 해서 github에서 소스를 받아 보니 Qt5 immodule 소스가 있었다. Qt5 모듈을 build하는 문서가 없어서 좀 헤매야 했는데 헤맸던 것들을 정리한다.

결론은, uim Qt5 모듈을 사용하면 Qt5 애플리케이션에서도 한글입력은 잘 되는데, 안정성 문제가 있다. 한글 입력 문제에 관한 한 uim이 ibus보다 좋아 보인다.

uim Build 및 설치

uim 설치 가이드를 따라가면, 소스를 build하기 위해서 아래의 tool 들이 필요하다.

$ sudo apt-get install intltool
$ sudo apt-get install librsvg2-bin libtool ruby git

build에 필요한 header 파일들도 있어야 하고...

$ sudo apt-get install libanthy-dev libgtk2.0-dev libgtk-3-dev libqt4-dev

그런데, Qt5는 우분투 패키지 구성이 꽤 복잡하다. 한마디로 libqt5-dev가 없다. 일단, 아래 정도가 필요해 보인다.

$ sudo apt-get install qtbase5-dev qtbase5-private-dev qt5-default qtdeclarative5-dev

이제 다시 가이드 대로, 소스를 github에서 받아서 build하고 설치까지 문제가 없다. 아, 도중에 폴더 권한이 없다는 오류가 발생했었다. 해당 폴더(/usr/plugins)를 만들어 주고 사용자 권한을 주면 해결된다. Qt5 모듈이 엉뚱한 폴더를 중간 폴더로 사용하고 있었다.

그리고, ./uim/qt5/immodule/quimplatforminputcontext.cpp 파일 윗부분에 debug 사용하도록 define이 있는데 주석 처리하는게 좋다.

$ git clone https://github.com/uim/uim.git
$ cd uim
$ ./make-wc.sh --with-qt5 --with-qt5-immodule
$ make
$ sudo make install

위와 같이 해서 설치하면 /usr/local 하위 폴더들에 uim 파일들이 분산된다. 단, Qt5 모듈은 Qt5 폴더 (/usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts)에 설치된다.

Qt5 모듈은 가야할 위치에 이미 가 있어서 Dynamic loading 문제가 없다. 그런데, GTK 모듈들은 immodules.cache에 등록해 주어야 한다. GTK2는 별로 쓸일이 없을 것 같아서 GTK3 모듈만 아래와 같이 등록하였다.

$ /usr/lib/x86_64-linux-gnu/libgtk-3-0/gtk-query-immodules-3.0 /usr/local/lib/gtk-3.0/3.0.0/immodules/im-uim.so >> /usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules.cache

uim 자동 실행

시스템 설정 가이드에 보면 ~/.xinitrc나 ~/.xsession에 아래의 내용을 넣어 주면 된다는데 잘 안되었다.
export GTK_IM_MODULE=uim
export QT_IM_MODULE=uim
export QT4_IM_MODULE=uim
export XMODIFIERS=@im=uim
/usr/local/bin/uim-xim &
그래서, 우분투의 im-config를 사용하기로 하였다. 먼저, ~/.xinputrc에 아래 내용을 등록해 주고,
run_im uim
/usr/share/im-config/data/24_uim.rc 파일을 수정해서 /usr/local 폴더 위치를 잡아 주었다. 그리고 나서 재로그인하면 uim이 잘 기동된다.

uim Qt5 모듈의 문제점

Qt5 애플리케이션에서 한글입력은 아무 문제없다. 심지어 ibus의 Qt5 애플리케이션에서 발생하는 한글입력 문제도 없다. 그런데, 공식 사이트에 Qt5 모듈이 없는 이유를 알게 되었다. 불안정하기 때문이다. 일단, 두어가지 문제를 발견했다.
  • Qt5 애플리케이션을 새로 실행할 때마다 uim-candwin-qt5 프로세스 수가 계속 증가함
  • 애플리케이션을 중지하면 프로세스가 좀 줄어 들지만 다시 새로운 프로세스가 생김
  • Qt5 애플리케이션을 쓰다 보면 컴이 느려지는데 uim-helper-server 프로세스가 CPU를 거의 100% 가까이 잡아먹고 있음

참고 사이트

https://code.google.com/p/uim/wiki/InstallUim
https://code.google.com/p/uim/wiki/UimSystemConfiguration