環境
この記事は以下の環境で動いています。
項目 | 値 |
---|---|
CPU | Core i5-8250U |
Ubuntu | 22.04 |
ROS2 | Humble |
Qt | 5.15.3 |
概要
Qtでは様々なWidgetがありますが、タッチパットのようなGUIに対応するWidgetはデフォルトではありません。
基本クラスであるQWidgetを継承することでタッチパットのWidgetを作成します。
ソースコード
C++
qt_lecture/src/qt_sample5/touch_widget.hpp
#pragma once
#include <QtWidgets>
class TouchWidget : public QWidget
{
Q_OBJECT
public:
TouchWidget(QWidget *parent = 0);
// property
bool grayout_{false};
QPointF latest_scaled_value_{};
// event
void setEnabled(bool enable);
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
public Q_SLOTS:
void setEnabled(int state);
Q_SIGNALS:
void notifyScaledPoint(QPointF);
void notifyScaledPoint(QString);
private:
void setValue(QPointF point);
int getPadSize(void) const;
QPoint getCenter(void) const;
QPointF getScaledPoint(const QPoint &point) const;
QPoint getPadPoint(const QPointF &point) const;
};
-
QWidget
を継承したクラスを作ります。
qt_lecture/src/qt_sample5/touch_widget.cpp
#include "touch_widget.hpp"
#include <sstream>
#include <iostream>
TouchWidget::TouchWidget(QWidget *parent) : QWidget(parent)
{
setMinimumSize(100, 100);
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
}
void TouchWidget::setEnabled(bool enable)
{
grayout_ = !enable;
update();
}
void TouchWidget::setEnabled(int state)
{
grayout_ = (state != Qt::Checked);
update();
}
void TouchWidget::paintEvent(QPaintEvent *event)
{
(void)event;
QPoint center = getCenter();
int pad_size = getPadSize();
QPainter painter(this);
if (!grayout_)
{
painter.setBrush(Qt::white);
painter.setPen(Qt::black);
}
else
{
painter.setBrush(Qt::lightGray);
painter.setPen(Qt::darkGray);
}
painter.drawRect(QRect(center.x() - pad_size / 2, center.y() - pad_size / 2, pad_size, pad_size));
painter.drawLine(center.x(), center.y() - pad_size / 2, center.x(), center.y() + pad_size / 2);
painter.drawLine(center.x() - pad_size / 2, center.y(), center.x() + pad_size / 2, center.y());
if (!grayout_)
{
QPen arrow;
arrow.setWidth(pad_size / 20);
arrow.setColor(Qt::black);
arrow.setCapStyle(Qt::RoundCap);
arrow.setJoinStyle(Qt::RoundJoin);
painter.setPen(arrow);
QPoint arrows[2];
arrows[0] = center;
arrows[1] = getPadPoint(latest_scaled_value_);
painter.drawPolyline(arrows, 2);
}
}
void TouchWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!grayout_)
{
setValue(getScaledPoint(QPoint(event->x(), event->y())));
update();
}
}
void TouchWidget::mousePressEvent(QMouseEvent *event)
{
if (!grayout_)
{
setValue(getScaledPoint(QPoint(event->x(), event->y())));
update();
}
}
void TouchWidget::mouseReleaseEvent(QMouseEvent *event)
{
(void)event;
setValue(QPointF{});
update();
}
// private
void TouchWidget::setValue(QPointF point)
{
latest_scaled_value_ = point;
Q_EMIT notifyScaledPoint(point);
std::string stm = std::to_string(point.x()) + "," + std::to_string(point.y());
QString text = QString::fromStdString(stm.c_str());
Q_EMIT notifyScaledPoint(text);
}
int TouchWidget::getPadSize(void) const
{
int w = width();
int h = height();
int half_size = ((w > h) ? (h - 1) : (w - 1)) / 2;
return half_size * 2;
}
QPoint TouchWidget::getCenter(void) const
{
return QPoint((width() - 1) / 2, (height() - 1) / 2);
}
QPointF TouchWidget::getScaledPoint(const QPoint &point) const
{
int pad_size = getPadSize();
QPoint center = getCenter();
float scaled_x = (float)(point.x() - center.x()) / (pad_size / 2);
float scaled_y = (float)(point.y() - center.y()) / (pad_size / 2);
QPointF output;
output.setX(std::min(std::max(scaled_x, -1.0f), 1.0f));
output.setY(std::min(std::max(scaled_y, -1.0f), 1.0f));
return output;
}
QPoint TouchWidget::getPadPoint(const QPointF &point) const
{
int pad_size = getPadSize();
QPoint center = getCenter();
QPoint output;
output.setX(center.x() + point.x() * (pad_size / 2));
output.setY(center.y() + point.y() * (pad_size / 2));
return output;
}
- コンストラクタではWidgetのサイズの設定をします。
-
setMinimumSize()
ではWidgetの最小サイズを決めます。ウィンドウを縮小してもこれ以上は小さくならなくなります。 -
setSizePolicy()
ではウィンドウ全体のサイズを変えた時にこのwidgetのサイズをどう変化させるかを指定します。縦横それぞれ指定できます。Expanding
は広げられるときは広げるといったオプションです。ほかのオプションについては公式ページを参照
-
- GUIのイベントが起きた時のcallbackをoverrideして処理を追加しています。
-
mousePressEvent()
はポインターがWidget上で左ボタンが押下されたとき、mouseMoveEvent()
は左ボタンを押下しながらポインターが動いたときに発生します。この時にタッチパッドの値を更新します。-
update()
を呼ぶことによって
-
-
mouseReleaseEvent()
は左ボタンが離されたときに発生します。このタイミングでタッチパットの値をクリアしています。 -
paintEvent()
は画面描画をするときに呼ばれるcallbackです。この中で背景の枠と、ドラックしている場所への線を描画します。
-
qt_lecture/src/qt_sample5/main.cpp
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QCheckBox>
#include "touch_widget.hpp"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
TouchWidget *touch_widget = new TouchWidget();
touch_widget->setEnabled(false);
QCheckBox *check = new QCheckBox("enable");
QLabel *label = new QLabel("");
layout->addWidget(touch_widget);
layout->addWidget(label);
layout->addWidget(check);
window->setLayout(layout);
QObject::connect(check, &QCheckBox::stateChanged, touch_widget, qOverload<int>(&TouchWidget::setEnabled));
QObject::connect(touch_widget, qOverload<QString>(&TouchWidget::notifyScaledPoint), label, &QLabel::setText);
window->show();
return app.exec();
}
-
QCheckBox
での設定によって、タッチパッドの有効無効を切り替えます。 - タッチパッドでの取得値を
QLabel
に表示します。 - Widgetのsignal/slotにオーバーロードメソッド(引数だけが違う同じ名前のメソッド)がある場合はconnectで
unknown overload
のエラーが出ます。この場合はqOverload<QString>()
のマクロを記述することで、オーバーロードメソッドの関数型を記述することで指定することが出来ます。
cmake
qt_lecture/CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(qt_lecture)
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rviz_common REQUIRED)
set(CMAKE_AUTOMOC ON)
add_executable(qt_sample5
src/qt_sample5/main.cpp
src/qt_sample5/touch_widget.cpp
)
ament_target_dependencies(qt_sample5
"rviz_common"
)
install(
TARGETS qt_sample5
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
- Qtのcmakeは直に書くといろいろ面倒なのでROS2のマクロを使います。
- rviz_commonへの依存を記述すれば、Qtへの依存が入ります。
- Qtのマクロを実行するためには
set(CMAKE_AUTOMOC ON)
の記述が必要です。
ビルド&実行
ビルド
source /opt/ros/humble/setup.bash
cd ros2_ws
colcon build
実行
ros2 run qt_lecture qt_sample5
- タッチパッドで取得した値をlabelに表示します。