1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ROS2講座06 タッチパネルWidgetを作る

Last updated at Posted at 2023-07-01

環境

この記事は以下の環境で動いています。

項目
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

qt_sample5.gif

  • タッチパッドで取得した値をlabelに表示します。

目次ページへのリンク

ROS2講座の目次へのリンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?