드래그 앤 드롭 로봇 예제
그래픽 뷰에서 항목을 드래그 앤 드롭하는 방법을 보여줍니다.
드래그 앤 드롭 로봇 예제는 QGraphicsItem 서브클래스에서 드래그 앤 드롭을 구현하는 방법과 Qt의 애니메이션 프레임워크를 사용하여 항목에 애니메이션을 적용하는 방법을 보여줍니다.
그래픽 뷰는 QGraphicsItem 클래스에서 파생된 수많은 사용자 정의 2D 그래픽 항목을 관리하고 상호 작용하기 위한 QGraphicsScene 클래스와 확대/축소 및 회전을 지원하여 항목을 시각화하기 위한 QGraphicsView 위젯을 제공합니다.
이 예제는 Robot
클래스, ColorItem
클래스, 메인 함수로 구성되어 있습니다. Robot
클래스는 RobotHead
, RobotLimb
등 여러 개의 RobotPart
파생된 팔다리로 구성된 간단한 로봇을 설명하고, ColorItem
클래스는 드래그 가능한 컬러 타원을 제공하며, main()
함수는 메인 애플리케이션 창을 제공합니다.
먼저 Robot
클래스를 검토하여 QPropertyAnimation 을 사용하여 개별적으로 회전하고 애니메이션을 적용할 수 있도록 여러 부품을 조립하는 방법을 살펴본 다음 ColorItem
클래스를 검토하여 항목 간 드래그 앤 드롭을 구현하는 방법을 시연해 보겠습니다. 마지막으로 main() 함수를 검토하여 모든 조각을 어떻게 조합하여 최종 애플리케이션을 구성할 수 있는지 살펴봅니다.
로봇 클래스 정의
로봇은 RobotHead
, RobotTorso
, 그리고 위쪽과 아래쪽 팔과 다리에 사용되는 RobotLimb
의 세 가지 주요 클래스로 구성됩니다. 모든 부품은 RobotPart
클래스에서 파생되며, 이는 차례로 QGraphicsObject
클래스를 상속합니다. Robot
클래스 자체는 시각적 외관이 없으며 로봇의 루트 노드 역할만 합니다.
RobotPart
클래스 선언부터 시작하겠습니다.
class RobotPart : public QGraphicsObject { public: RobotPart(QGraphicsItem *parent = nullptr); protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; QColor color = Qt::lightGray; bool dragOver = false; };
이 베이스 클래스는 QGraphicsObject 을 상속하고 QGraphicsObject 은 QObject 을 상속하여 신호와 슬롯을 제공하며, Q_PROPERTY 을 사용하여 QGraphicsItem 의 프로퍼티를 선언하여 QPropertyAnimation 에서 프로퍼티에 액세스할 수 있도록 합니다.
로봇 파트는 또한 드롭 이벤트 수락에 가장 중요한 세 가지 이벤트 핸들러를 구현합니다: dragEnterEvent(), dragLeaveEvent(), dropEvent().
색상은 dragOver
변수와 함께 멤버 변수로 저장되며, 나중에 팔다리가 드래그된 색상을 받아들일 수 있음을 시각적으로 표시하는 데 사용할 것입니다.
RobotPart::RobotPart(QGraphicsItem *parent) : QGraphicsObject(parent), color(Qt::lightGray) { setAcceptDrops(true); }
RobotPart
의 생성자는 dragOver 멤버를 초기화하고 색상을 Qt::lightGray 로 설정합니다. 생성자 본문에서는 setAcceptDrops(참)을 호출하여 드롭 이벤트 수락 지원을 활성화합니다.
이 클래스의 나머지 구현은 드래그 앤 드롭을 지원하기 위한 것입니다.
void RobotPart::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasColor()) { event->setAccepted(true); dragOver = true; update(); } else { event->setAccepted(false); } }
dragEnterEvent() 핸들러는 드래그 앤 드롭 요소를 로봇 파트의 영역으로 드래그할 때 호출됩니다.
핸들러 구현은 이 항목 전체가 들어오는 드래그 객체와 연관된 마임 데이터를 받아들일 수 있는지 여부를 결정합니다. RobotPart
은 색상 드롭을 허용하는 모든 파트에 대한 기본 동작을 제공합니다. 따라서 들어오는 드래그 객체에 색상이 포함되어 있으면 이벤트가 수락되고 dragOver
을 true
으로 설정하고 update()를 호출하여 사용자에게 긍정적인 시각적 피드백을 제공하며, 그렇지 않으면 이벤트가 무시되어 이벤트가 부모 요소로 전파될 수 있도록 합니다.
void RobotPart::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { Q_UNUSED(event); dragOver = false; update(); }
dragLeaveEvent() 핸들러는 드래그 앤 드롭 요소가 로봇 파트의 영역에서 드래그될 때 호출됩니다. 저희 구현에서는 dragOver를 false로 재설정하고 update()를 호출하여 드래그가 이 항목을 떠났다는 시각적 피드백을 제공합니다.
void RobotPart::dropEvent(QGraphicsSceneDragDropEvent *event) { dragOver = false; if (event->mimeData()->hasColor()) color = qvariant_cast<QColor>(event->mimeData()->colorData()); update(); }
dropEvent() 핸들러는 드래그 앤 드롭 요소를 항목에 놓을 때(즉, 드래그하는 동안 마우스 버튼을 항목 위로 놓을 때) 호출됩니다.
dragOver
을 false로 재설정하고 항목의 새 색상을 지정한 다음 update()을 호출합니다.
RobotHead
, RobotTorso
, RobotLimb
의 선언과 구현은 거의 동일합니다. 이 클래스에는 한 가지 사소한 차이점이 있으므로 RobotHead
에 대해 자세히 살펴보고 다른 클래스는 독자의 연습용으로 남겨두겠습니다.
class RobotHead : public RobotPart { public: RobotHead(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; private: QPixmap pixmap; };
RobotHead
클래스는 RobotPart
을 상속하고 boundingRect() 및 paint()의 필요한 구현을 제공합니다. 또한 dragEnterEvent() 및 dropEvent()를 재구현하여 이미지 드롭에 대한 특수 처리를 제공합니다.
이 클래스에는 이미지 드롭을 받아들이는 지원을 구현하는 데 사용할 수 있는 비공개 픽셀맵 멤버가 포함되어 있습니다.
RobotHead::RobotHead(QGraphicsItem *parent) : RobotPart(parent) { }
RobotHead
에는 RobotPart
의 생성자로 단순히 전달되는 다소 평범한 생성자가 있습니다.
boundingRect() 재구현은 머리의 확장을 반환합니다. 회전 중심이 항목의 하단 중심이 되기를 원하므로 (-15, -50)에서 시작하여 너비 30단위, 높이 50단위까지 확장되는 경계 사각형을 선택했습니다. 머리를 회전할 때 '목'은 움직이지 않고 머리 위쪽이 좌우로 기울어집니다.
void RobotHead::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); if (pixmap.isNull()) { painter->setBrush(dragOver ? color.lighter(130) : color); painter->drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt::RelativeSize); painter->setBrush(Qt::white); painter->drawEllipse(-7, -3 - 20, 7, 7); painter->drawEllipse(0, -3 - 20, 7, 7); painter->setBrush(Qt::black); painter->drawEllipse(-5, -1 - 20, 2, 2); painter->drawEllipse(2, -1 - 20, 2, 2); painter->setPen(QPen(Qt::black, 2)); painter->setBrush(Qt::NoBrush); painter->drawArc(-6, -2 - 20, 12, 15, 190 * 16, 160 * 16); } else { painter->scale(.2272, .2824); painter->drawPixmap(QPointF(-15 * 4.4, -50 * 3.54), pixmap); } }
paint()에서는 실제 머리를 그립니다. 구현은 두 부분으로 나뉘는데, 이미지가 머리 위에 놓여 있으면 이미지를 그리고 그렇지 않으면 간단한 벡터 그래픽으로 둥근 직사각형 로봇 머리를 그립니다.
성능상의 이유로, 그리는 대상의 복잡성에 따라 일련의 벡터 연산을 사용하는 것보다 이미지로 머리를 그리는 것이 더 빠를 수 있습니다.
void RobotHead::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { event->setAccepted(true); dragOver = true; update(); } else { RobotPart::dragEnterEvent(event); } }
로봇 헤드는 이미지 드롭을 수용할 수 있습니다. 이를 지원하기 위해 dragEnterEvent()의 재구현은 드래그 객체에 이미지 데이터가 포함되어 있는지 확인하고, 포함되어 있으면 이벤트가 수락됩니다. 그렇지 않으면 기본 RobotPart
구현으로 돌아갑니다.
void RobotHead::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { dragOver = false; pixmap = qvariant_cast<QPixmap>(event->mimeData()->imageData()); update(); } else { RobotPart::dropEvent(event); } }
이미지 지원에 대한 후속 조치를 취하려면 dropEvent()도 구현해야 합니다. 드래그 객체에 이미지 데이터가 포함되어 있는지 확인하고, 포함되어 있으면 이 데이터를 멤버 픽셀맵으로 저장하고 update()를 호출합니다. 이 픽셀맵은 앞서 살펴본 paint() 구현 내부에서 사용됩니다.
RobotTorso
와 RobotLimb
는 RobotHead
과 유사하므로 Robot
클래스로 바로 넘어가겠습니다.
class Robot : public RobotPart { public: Robot(QGraphicsItem *parent = nullptr); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; };
Robot
클래스는 RobotPart
을 상속하며 다른 부분과 마찬가지로 boundingRect() 및 paint()도 구현합니다. 하지만 다소 특별한 구현을 제공합니다:
QRectF Robot::boundingRect() const { return QRectF(); } void Robot::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(painter); Q_UNUSED(option); Q_UNUSED(widget); }
Robot
클래스는 나머지 로봇의 기본 노드로만 사용되기 때문에 시각적 표현이 없습니다. 따라서 boundingRect() 구현은 null QRectF 을 반환할 수 있으며 페인트() 함수는 아무 작업도 수행하지 않습니다.
Robot::Robot(QGraphicsItem *parent) : RobotPart(parent) { setFlag(ItemHasNoContents); QGraphicsObject *torsoItem = new RobotTorso(this); QGraphicsObject *headItem = new RobotHead(torsoItem); QGraphicsObject *upperLeftArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftArmItem = new RobotLimb(upperLeftArmItem); QGraphicsObject *upperRightArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightArmItem = new RobotLimb(upperRightArmItem); QGraphicsObject *upperRightLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightLegItem = new RobotLimb(upperRightLegItem); QGraphicsObject *upperLeftLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftLegItem = new RobotLimb(upperLeftLegItem);
생성자는 시각적 외관이 없는 항목에 대한 사소한 최적화인 ItemHasNoContents 플래그를 설정하는 것으로 시작합니다.
그런 다음 모든 로봇 부품(머리, 몸통, 상/하부 팔과 다리)을 구성합니다. 스태킹 순서는 매우 중요하며, 요소들이 제대로 회전하고 움직일 수 있도록 상위-하위 계층 구조를 사용합니다. 몸통은 루트 요소이므로 먼저 몸통을 구성합니다. 그런 다음 머리를 구성하고 몸통을 HeadItem
의 생성자에게 전달합니다. 이렇게 하면 머리가 몸통의 자식이 되고 몸통을 회전하면 머리가 따라갑니다. 나머지 팔다리에도 동일한 패턴이 적용됩니다.
headItem->setPos(0, -18); upperLeftArmItem->setPos(-15, -10); lowerLeftArmItem->setPos(30, 0); upperRightArmItem->setPos(15, -10); lowerRightArmItem->setPos(30, 0); upperRightLegItem->setPos(10, 32); lowerRightLegItem->setPos(30, 0); upperLeftLegItem->setPos(-10, 32); lowerLeftLegItem->setPos(30, 0);
각 로봇 부품은 신중하게 배치됩니다. 예를 들어 왼쪽 위 팔은 몸통의 왼쪽 상단 영역으로 정확하게 이동하고 오른쪽 위 팔은 오른쪽 상단 영역으로 이동합니다.
QParallelAnimationGroup *animation = new QParallelAnimationGroup(this); QPropertyAnimation *headAnimation = new QPropertyAnimation(headItem, "rotation"); headAnimation->setStartValue(20); headAnimation->setEndValue(-20); QPropertyAnimation *headScaleAnimation = new QPropertyAnimation(headItem, "scale"); headScaleAnimation->setEndValue(1.1); animation->addAnimation(headAnimation); animation->addAnimation(headScaleAnimation);
다음 섹션에서는 모든 애니메이션 객체를 만듭니다. 이 스니펫은 머리의 크기와 회전에 따라 작동하는 두 개의 애니메이션을 보여줍니다. 두 개의 QPropertyAnimation 인스턴스는 객체, 속성, 각각의 시작 및 종료 값을 설정하기만 하면 됩니다.
모든 애니메이션은 하나의 최상위 병렬 애니메이션 그룹에 의해 제어됩니다. 스케일 및 회전 애니메이션은 이 그룹에 추가됩니다.
나머지 애니메이션도 비슷한 방식으로 정의됩니다.
for (int i = 0; i < animation->animationCount(); ++i) { QPropertyAnimation *anim = qobject_cast<QPropertyAnimation *>(animation->animationAt(i)); anim->setEasingCurve(QEasingCurve::SineCurve); anim->setDuration(2000); } animation->setLoopCount(-1); animation->start();
마지막으로 각 애니메이션에 완화 커브와 지속 시간을 설정하고 최상위 애니메이션 그룹이 영원히 반복되도록 한 다음 최상위 애니메이션을 시작합니다.
ColorItem 클래스 정의
ColorItem
클래스는 로봇 부품에 색상을 드래그하기 위해 누를 수 있는 원형 아이템을 나타냅니다.
class ColorItem : public QGraphicsItem { public: ColorItem(); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; private: QColor color; };
이 클래스는 매우 간단합니다. 애니메이션을 사용하지 않으며 프로퍼티나 신호 및 슬롯이 필요하지 않으므로 리소스를 절약하기 위해 QGraphicsItem ( QGraphicsObject)을 상속하는 것이 가장 자연스럽습니다.
필수 boundingRect() 및 paint() 함수를 선언하고 mousePressEvent(), mouseMoveEvent() 및 mouseReleaseEvent()의 재구현을 추가합니다. 여기에는 단일 프라이빗 컬러 멤버가 포함되어 있습니다.
구현을 살펴봅시다.
ColorItem::ColorItem() : color(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)) { setToolTip(QString("QColor(%1, %2, %3)\n%4") .arg(color.red()).arg(color.green()).arg(color.blue()) .arg("Click and drag this color onto the robot!")); setCursor(Qt::OpenHandCursor); setAcceptedMouseButtons(Qt::LeftButton); }
ColorItem
의 생성자는 QRandomGenerator 를 사용하여 색상 멤버에 불투명한 임의 색상을 할당합니다. 사용 편의성을 높이기 위해 사용자에게 유용한 힌트를 제공하는 툴팁을 할당하고 적절한 커서도 설정합니다. 이렇게 하면 마우스 포인터가 항목 위로 마우스를 가져가면 커서가 Qt::OpenHandCursor 로 이동합니다.
마지막으로 setAcceptedMouseButtons()를 호출하여 이 항목이 Qt::LeftButton 만 처리할 수 있도록 합니다. 이렇게 하면 마우스 이벤트 핸들러가 크게 단순화됩니다. 항상 마우스 왼쪽 버튼만 눌렀다 놓는다고 가정할 수 있기 때문입니다.
항목의 바운딩 직사각형은 항목의 원점(0, 0)을 중심으로 30x30 단위로 고정되고 모든 방향으로 0.5 단위씩 조정되어 스케일러블 펜으로 윤곽을 그릴 수 있습니다. 마지막 시각적 터치를 위해 바운드는 몇 단위 아래쪽과 오른쪽으로 보정하여 간단한 그림자를 위한 공간을 만듭니다.
void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setPen(Qt::NoPen); painter->setBrush(Qt::darkGray); painter->drawEllipse(-12, -12, 30, 30); painter->setPen(QPen(Qt::black, 1)); painter->setBrush(QBrush(color)); painter->drawEllipse(-15, -15, 30, 30); }
paint() 구현은 1단위 검정색 윤곽선, 일반 색상 채우기 및 짙은 회색 그림자가 있는 타원을 그립니다.
void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::ClosedHandCursor); }
mousePressEvent() 핸들러는 항목 영역 내에서 마우스 버튼을 누르면 호출됩니다. 이 구현에서는 커서를 Qt::ClosedHandCursor 로 설정합니다.
void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::OpenHandCursor); }
mouseReleaseEvent() 핸들러는 항목 영역 안에서 마우스 버튼을 누른 후 손을 떼면 호출됩니다. 구현에서는 커서를 Qt::OpenHandCursor 로 다시 설정합니다. 마우스 누르기 및 놓기 이벤트 핸들러는 사용자에게 유용한 시각적 피드백을 제공합니다. CircleItem
위로 마우스 포인터를 이동하면 커서가 열린 손으로 바뀝니다. 해당 항목을 누르면 닫힌 손 커서가 표시됩니다. 손을 떼면 다시 열린 손 커서로 복원됩니다.
void ColorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton)) .length() < QApplication::startDragDistance()) { return; } QDrag *drag = new QDrag(event->widget()); QMimeData *mime = new QMimeData; drag->setMimeData(mime);
mouseMoveEvent() 핸들러는 ColorItem
영역 내에서 마우스 버튼을 누른 후 마우스를 움직일 때 호출됩니다. 이 구현은 드래그를 시작하고 관리하는 코드인 CircleItem
에 가장 중요한 로직을 제공합니다.
구현은 마우스가 마우스 지터 노이즈를 제거할 수 있을 만큼 충분히 드래그되었는지 확인하는 것으로 시작됩니다. 마우스가 애플리케이션의 드래그 시작 거리보다 더 멀리 드래그된 경우에만 드래그를 시작하려고 합니다.
계속해서 QDrag 객체를 생성하고 widget 이벤트(즉, QGraphicsView 뷰포트)를 생성자에게 전달합니다. Qt는 이 객체가 적시에 삭제되도록 합니다. 또한 색상 또는 이미지 데이터를 포함할 수 있는 QMimeData 인스턴스를 생성하고 이를 드래그 객체에 할당합니다.
static int n = 0; if (n++ > 2 && QRandomGenerator::global()->bounded(3) == 0) { QImage image(":/images/head.png"); mime->setImageData(image); drag->setPixmap(QPixmap::fromImage(image).scaled(30, 40)); drag->setHotSpot(QPoint(15, 30));
이 코드 조각은 다소 무작위적인 결과를 가져옵니다. 가끔씩 드래그 객체의 마임 데이터에 특별한 이미지가 할당됩니다. 픽셀맵도 드래그 객체의 픽셀맵으로 할당됩니다. 이렇게 하면 마우스 커서 아래에서 드래그 중인 이미지를 픽셀맵으로 볼 수 있습니다.
} else { mime->setColorData(color); mime->setText(QString("#%1%2%3") .arg(color.red(), 2, 16, QLatin1Char('0')) .arg(color.green(), 2, 16, QLatin1Char('0')) .arg(color.blue(), 2, 16, QLatin1Char('0'))); QPixmap pixmap(34, 34); pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.translate(15, 15); painter.setRenderHint(QPainter::Antialiasing); paint(&painter, nullptr, nullptr); painter.end(); pixmap.setMask(pixmap.createHeuristicMask()); drag->setPixmap(pixmap); drag->setHotSpot(QPoint(15, 20)); }
그렇지 않으면 가장 일반적인 결과이며 드래그 개체의 마임 데이터에 단순한 색상이 할당됩니다. 이 ColorItem
을 새 픽셀맵으로 렌더링하여 사용자에게 색상이 '드래그되고 있다'는 시각적 피드백을 제공합니다.
drag->exec(); setCursor(Qt::OpenHandCursor); }
마지막으로 드래그를 실행합니다. QDrag::exec()는 이벤트 루프에 다시 들어가고, 드래그가 삭제되거나 취소된 경우에만 종료됩니다. 어떤 경우든 커서는 Qt::OpenHandCursor 로 재설정됩니다.
main() 함수
Robot
및 ColorItem
클래스가 완성되었으므로 이제 main() 함수 안에 모든 조각을 모을 수 있습니다.
int main(int argc, char **argv) { QApplication app(argc, argv);
먼저 QApplication 를 작성하고 난수 생성기를 초기화합니다. 이렇게 하면 애플리케이션이 시작될 때마다 색상 항목이 다른 색상을 갖도록 할 수 있습니다.
QGraphicsScene scene(-200, -200, 400, 400); for (int i = 0; i < 10; ++i) { ColorItem *item = new ColorItem; item->setPos(::sin((i * 6.28) / 10.0) * 150, ::cos((i * 6.28) / 10.0) * 150); scene.addItem(item); } Robot *robot = new Robot; robot->setTransform(QTransform::fromScale(1.2, 1.2), true); robot->setPos(0, -20); scene.addItem(robot);
고정된 크기의 장면을 구성하고 원으로 배열된 ColorItem
인스턴스 10개를 만듭니다. 각 항목이 장면에 추가됩니다.
이 원의 중앙에 Robot
인스턴스 하나를 만듭니다. 로봇의 크기를 조정하고 몇 단위 위로 이동합니다. 그런 다음 장면에 추가합니다.
GraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); view.setBackgroundBrush(QColor(230, 200, 167)); view.setWindowTitle("Drag and Drop Robot"); view.show(); return app.exec(); }
마지막으로 QGraphicsView 창을 만들고 씬을 할당합니다.
시각적 품질을 높이기 위해 앤티앨리어싱을 활성화합니다. 또한 시각적 업데이트 처리를 간소화하기 위해 경계 사각형 업데이트를 사용하도록 선택합니다. 뷰에는 고정된 모래색 배경과 창 제목이 지정됩니다.
그런 다음 뷰를 표시합니다. 애니메이션은 컨트롤이 이벤트 루프에 진입한 직후에 시작됩니다.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.