Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) QtQuadBezierArrowItem

 

QtQt CreatorLubuntu

 

QtQuadBezierArrowItem is a QGraphicsItem for displaying a arrow that has a quadratic Bezier curve as its body.

 

For the math, see QGraphicsPathItem example 5: Bezier quadratic lines with arrow heads.

Technical facts

 

 

 

 

 

 

./CppQtQuadBezierArrowItem/CppQtQuadBezierArrowItem.pri

 

INCLUDEPATH += \
    ../../Classes/CppQtQuadBezierArrowItem

SOURCES += \
    ../../Classes/CppQtQuadBezierArrowItem/qtquadbezierarrowitem.cpp \
    ../../Classes/CppQtQuadBezierArrowItem/qtquadbezierarrowdialog.cpp

HEADERS  += \
    ../../Classes/CppQtQuadBezierArrowItem/qtquadbezierarrowitem.h \
    ../../Classes/CppQtQuadBezierArrowItem/qtquadbezierarrowdialog.h

OTHER_FILES += \
    ../../Classes/CppQtQuadBezierArrowItem/Licence.txt

FORMS += \
    ../../Classes/CppQtQuadBezierArrowItem/qtquadbezierarrowdialog.ui

 

 

 

 

 

./CppQtQuadBezierArrowItem/qtquadbezierarrowdialog.h

 

#ifndef QTQUADBEZIERARROWDIALOG_H
#define QTQUADBEZIERARROWDIALOG_H

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include <boost/shared_ptr.hpp>
#include "qthideandshowdialog.h"
#pragma GCC diagnostic pop

namespace Ui {
  class QtQuadBezierArrowDialog;
}

namespace ribi {

struct QtQuadBezierArrowItem;

///Dialog to display the members of a QtQuadBezierArrowItem
class QtQuadBezierArrowDialog final : public QtHideAndShowDialog
{
  Q_OBJECT

public:
  typedef boost::shared_ptr<QtQuadBezierArrowItem> Arrow;

  explicit QtQuadBezierArrowDialog(QWidget *parent = 0);
  QtQuadBezierArrowDialog(const QtQuadBezierArrowDialog&) = delete;
  QtQuadBezierArrowDialog& operator=(const QtQuadBezierArrowDialog&) = delete;
  ~QtQuadBezierArrowDialog() noexcept;

  Arrow GetArrow() const noexcept { return m_arrow; }

  double GetUiMidX() const noexcept;
  double GetUiMidY() const noexcept;

  static std::string GetVersion() noexcept;
  static std::vector<std::string> GetVersionHistory() noexcept;

  ///Set the arrow to work on
  void SetArrow(const Arrow& arrow) noexcept;

  void SetUiMidX(const double x) noexcept;
  void SetUiMidY(const double y) noexcept;

private slots:
  void keyPressEvent(QKeyEvent * event) noexcept override final;
  void on_box_focus_pen_width_valueChanged(double arg1);
  void on_box_from_x_valueChanged(double arg1);
  void on_box_from_y_valueChanged(double arg1);
  void on_box_has_head_clicked();
  void on_box_has_tail_clicked();
  void on_box_mid_x_valueChanged(double arg1);
  void on_box_mid_y_valueChanged(double arg1);
  void on_box_normal_pen_width_valueChanged(double arg1);
  void on_box_to_x_valueChanged(double arg1);
  void on_box_to_y_valueChanged(double arg1);

private:
  Ui::QtQuadBezierArrowDialog *ui;

  ///The arrow to work on
  Arrow m_arrow;

  void OnItemUpdated(const QtQuadBezierArrowItem * const item) noexcept;

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif
};

} //~namespace ribi

#endif // QTQUADBEZIERARROWDIALOG_H

 

 

 

 

 

./CppQtQuadBezierArrowItem/qtquadbezierarrowdialog.cpp

 

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include "qtquadbezierarrowdialog.h"

#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>

#include <QGraphicsItem>
#include <QKeyEvent>

#include <cassert>

#include "qtquadbezierarrowitem.h"
#include "trace.h"
#include "testtimer.h"
#include "ui_qtquadbezierarrowdialog.h"
#pragma GCC diagnostic pop

ribi::QtQuadBezierArrowDialog::QtQuadBezierArrowDialog(QWidget *parent)
  : QtHideAndShowDialog(parent),
    ui(new Ui::QtQuadBezierArrowDialog),
    m_arrow{}
{
  #ifndef NDEBUG
  Test();
  #endif
  ui->setupUi(this);
}

ribi::QtQuadBezierArrowDialog::~QtQuadBezierArrowDialog() noexcept
{
  delete ui;
}

double ribi::QtQuadBezierArrowDialog::GetUiMidX() const noexcept
{
  return ui->box_mid_x->value();
}

double ribi::QtQuadBezierArrowDialog::GetUiMidY() const noexcept
{
  return ui->box_mid_y->value();
}

std::string ribi::QtQuadBezierArrowDialog::GetVersion() noexcept
{
  return "1.0";
}


std::vector<std::string> ribi::QtQuadBezierArrowDialog::GetVersionHistory() noexcept
{
  return {
    "2014-07-29: version 1.0: initial version"
  };
}

void ribi::QtQuadBezierArrowDialog::on_box_from_x_valueChanged(double arg1)
{
  this->m_arrow->SetFromX(arg1);
}

void ribi::QtQuadBezierArrowDialog::on_box_from_y_valueChanged(double arg1)
{
  this->m_arrow->SetFromY(arg1);
}

void ribi::QtQuadBezierArrowDialog::on_box_has_head_clicked()
{
  m_arrow->SetHasHead(ui->box_has_head->isChecked());
}

void ribi::QtQuadBezierArrowDialog::on_box_has_tail_clicked()
{
  m_arrow->SetHasTail(ui->box_has_tail->isChecked());
}

void ribi::QtQuadBezierArrowDialog::on_box_mid_x_valueChanged(double arg1)
{
  this->m_arrow->SetMidX(arg1);
}

void ribi::QtQuadBezierArrowDialog::on_box_mid_y_valueChanged(double arg1)
{
  this->m_arrow->SetMidY(arg1);
}

void ribi::QtQuadBezierArrowDialog::on_box_normal_pen_width_valueChanged(double arg1)
{
  QPen new_pen = m_arrow->GetPen();
  new_pen.setWidthF(arg1);
  m_arrow->SetPen(new_pen);
}

void ribi::QtQuadBezierArrowDialog::on_box_focus_pen_width_valueChanged(double arg1)
{
  QPen new_pen = m_arrow->GetFocusPen();
  new_pen.setWidthF(arg1);
  m_arrow->SetFocusPen(new_pen);

}

void ribi::QtQuadBezierArrowDialog::on_box_to_x_valueChanged(double arg1)
{
  this->m_arrow->SetToX(arg1);
}

void ribi::QtQuadBezierArrowDialog::on_box_to_y_valueChanged(double arg1)
{
  this->m_arrow->SetToY(arg1);
}

void ribi::QtQuadBezierArrowDialog::OnItemUpdated(const QtQuadBezierArrowItem * const item) noexcept
{
  ui->box_focus_pen_width->setValue(item->GetFocusPen().widthF());
  ui->box_from_x->setValue(item->GetFromItem()->x());
  ui->box_from_y->setValue(item->GetFromItem()->y());
  ui->box_has_head->setChecked(item->HasHead());
  ui->box_has_tail->setChecked(item->HasTail());
  ui->box_mid_x->setValue(item->GetMidItem()->x());
  ui->box_mid_y->setValue(item->GetMidItem()->y());
  ui->box_normal_pen_width->setValue(item->GetPen().widthF());
  ui->box_to_x->setValue(item->GetToItem()->x());
  ui->box_to_y->setValue(item->GetToItem()->y());
}

void ribi::QtQuadBezierArrowDialog::keyPressEvent(QKeyEvent * event) noexcept
{
  if (event->key() == Qt::Key_Escape) { close(); return; }
}

void ribi::QtQuadBezierArrowDialog::SetArrow(const Arrow& arrow) noexcept
{
  const bool verbose{false};

  assert(arrow);
  if (m_arrow == arrow)
  {
    return;
  }
  if (verbose)
  {
    std::stringstream s;
    s << "Setting arrow '" << (*arrow) << "'\n";
  }

  const auto focus_pen_after = arrow->GetFocusPen();
  const auto from_item_after = arrow->GetFromItem();
  const auto has_head_after = arrow->HasHead();
  const auto mid_item_after = arrow->GetMidItem();
  const auto pen_after = arrow->GetPen();
  const auto has_tail_after = arrow->HasTail();
  const auto to_item_after = arrow->GetToItem();

  bool focus_pen_changed{true};
  bool from_item_changed{true};
  bool has_head_changed{true};
  bool mid_item_changed{true};
  bool pen_changed{true};
  bool has_tail_changed{true};
  bool to_item_changed{true};

  if (m_arrow)
  {
    const auto focus_pen_before = m_arrow->GetFocusPen();
    const auto from_item_before = m_arrow->GetFromItem();
    const auto has_head_before = m_arrow->HasHead();
    const auto mid_item_before = m_arrow->GetMidItem();
    const auto pen_before = m_arrow->GetPen();
    const auto has_tail_before = m_arrow->HasTail();
    const auto to_item_before = m_arrow->GetToItem();

    focus_pen_changed = focus_pen_before != focus_pen_after;
    from_item_changed = from_item_before != from_item_after;
    has_head_changed = has_head_before != has_head_after;
    mid_item_changed = mid_item_before != mid_item_after;
    pen_changed = pen_before != pen_after;
    has_tail_changed = has_tail_before != has_tail_after;
    to_item_changed = to_item_before != to_item_after;

    if (verbose)
    {
      if (focus_pen_changed)
      {
        std::stringstream s;
        s
          << "Focus pen will change from width "
          << focus_pen_before.widthF()
          << " to "
          << focus_pen_after.widthF()
          << '\n'
        ;
        TRACE(s.str());
      }
      if (from_item_changed)
      {
        std::stringstream s;
        s << "From item will change from " << from_item_before
          << " to " << from_item_after << '\n';
        TRACE(s.str());
      }
      if (has_head_changed)
      {
        std::stringstream s;
        s << "Has head will change from '" << has_head_before
          << "' to '" << has_head_after << "'\n";
        TRACE(s.str());
      }
      if (mid_item_changed)
      {
        std::stringstream s;
        s << "Mid item will change from " << mid_item_before
          << " to " << mid_item_after << '\n';
        TRACE(s.str());
      }
      if (pen_changed)
      {
        std::stringstream s;
        s << "Normal pen will change from " << pen_before.widthF()
          << " to " << pen_after.widthF() << '\n'
        ;
        TRACE(s.str());
      }
    }

    //Disconnect m_arrow
    m_arrow->m_signal_item_updated.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnItemUpdated,this,boost::lambda::_1)
    );
    /*
    Not sure if I need this high detailed granulity

    m_arrow->m_signal_base_changed.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnBaseChanged,this,boost::lambda::_1)
    );
    m_arrow->m_signal_font_changed.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnFontChanged,this,boost::lambda::_1)
    );
    m_arrow->m_signal_padding_changed.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnPaddingChanged,this,boost::lambda::_1)
    );
    m_arrow->m_signal_text_changed.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnTextChanged,this,boost::lambda::_1)
    );
    m_arrow->m_signal_text_pen_changed.disconnect(
      boost::bind(&ribi::QtQuadBezierArrowDialog::OnTextPenChanged,this,boost::lambda::_1)
    );
  */
  }

  //Replace m_arrow by the new one
  m_arrow = arrow;

  assert(m_arrow->GetFocusPen() == focus_pen_after);
  assert(m_arrow->GetFromItem() == from_item_after);
  assert(m_arrow->GetMidItem() == mid_item_after);
  assert(m_arrow->GetPen() == pen_after);
  assert(m_arrow->GetToItem() == to_item_after);
  assert(m_arrow->HasHead() == has_head_after);
  assert(m_arrow->HasTail() == has_tail_after);

  m_arrow->m_signal_item_updated.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnItemUpdated,this,boost::lambda::_1)
  );

  /* Not sure if I need this high detailed granulity

  m_arrow->m_signal_base_changed.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnBaseChanged,this,boost::lambda::_1)
  );
  m_arrow->m_signal_font_changed.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnFontChanged,this,boost::lambda::_1)
  );
  m_arrow->m_signal_padding_changed.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnPaddingChanged,this,boost::lambda::_1)
  );
  m_arrow->m_signal_text_changed.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnTextChanged,this,boost::lambda::_1)
  );
  m_arrow->m_signal_text_pen_changed.connect(
    boost::bind(&ribi::QtQuadBezierArrowDialog::OnTextPenChanged,this,boost::lambda::_1)
  );
  */

  //Emit everything that has changed
  if (
     focus_pen_changed
  || from_item_changed
  || has_head_changed
  || mid_item_changed
  || pen_changed
  || has_tail_changed
  || to_item_changed
  )
  {
     m_arrow->m_signal_item_updated(m_arrow.get());
  }

  /* Not sure if I need this high detailed granulity
  if (focus_pen_changed)
  {
    m_arrow->m_signal_base_changed(m_arrow.get());
  }
  if (from_item_changed)
  {
    m_arrow->m_signal_font_changed(m_arrow.get());
  }
  if (has_head_changed)
  {
    m_arrow->m_signal_padding_changed(m_arrow.get());
  }
  if (mid_item_changed)
  {
    m_arrow->m_signal_text_changed(m_arrow.get());
  }
  if (pen_changed)
  {
    m_arrow->m_signal_text_pen_changed(m_arrow.get());
  }
  */
  assert( arrow ==  m_arrow);
  assert(*arrow == *m_arrow);
}

void ribi::QtQuadBezierArrowDialog::SetUiMidX(const double x) noexcept
{
  ui->box_mid_x->setValue(x);
}

void ribi::QtQuadBezierArrowDialog::SetUiMidY(const double y) noexcept
{
  ui->box_mid_y->setValue(y);
}


#ifndef NDEBUG
void ribi::QtQuadBezierArrowDialog::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  {
    const boost::shared_ptr<QGraphicsItem> from{new QGraphicsSimpleTextItem};
    const bool tail{false};
    const boost::shared_ptr<QGraphicsItem> mid{new QGraphicsSimpleTextItem};
    const bool head{true};
    const boost::shared_ptr<QGraphicsItem> to{new QGraphicsSimpleTextItem};
    const boost::shared_ptr<QtQuadBezierArrowItem> arrow{
      new QtQuadBezierArrowItem(from.get(),tail,mid.get(),head,to.get())
    };
    assert(arrow);
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);
  const bool verbose{false};
  const boost::shared_ptr<QGraphicsItem> from{new QGraphicsSimpleTextItem};
  const bool tail{false};
  const boost::shared_ptr<QGraphicsItem> mid{new QGraphicsSimpleTextItem};
  const bool head{true};
  const boost::shared_ptr<QGraphicsItem> to{new QGraphicsSimpleTextItem};
  const boost::shared_ptr<QtQuadBezierArrowItem> arrow{
    new QtQuadBezierArrowItem(from.get(),tail,mid.get(),head,to.get())
  };
  QtQuadBezierArrowDialog d;
  d.SetArrow(arrow);
  if (verbose) { TRACE("Get/SetUiMidX must be symmetric"); }
  {
    const double old_x{d.GetUiMidX()};
    const double new_x{old_x + 10.0};
    d.SetUiMidX(new_x);
    assert(std::abs(d.GetUiMidX() - new_x) < 2.0);
  }
  if (verbose) { TRACE("Get/SetUiMidY must be symmetric"); }
  {
    const double old_y{d.GetUiMidY()};
    const double new_y{old_y + 10.0};
    d.SetUiMidY(new_y);
    assert(std::abs(d.GetUiMidY() - new_y) < 2.0);
  }

  /*
  assert(std::abs(ui->box_focus_pen_width->value() - m_arrow->GetFocusPen().widthF()) < 2.0);
  assert(std::abs(ui->box_from_x->value() - m_arrow->GetFromItem()->x()) < 2.0);
  assert(std::abs(ui->box_from_y->value() - m_arrow->GetFromItem()->y()) < 2.0);
  assert(ui->box_has_head->isChecked() == m_arrow->HasHead());
  assert(ui->box_has_tail->isChecked() == m_arrow->HasTail());
  assert(std::abs(ui->box_mid_x->value() - m_arrow->GetMidItem()->x()) < 2.0);
  assert(std::abs(ui->box_mid_y->value() - m_arrow->GetMidItem()->y()) < 2.0);
  assert(std::abs(ui->box_normal_pen_width->value() - m_arrow->GetPen().widthF()) < 2.0);
  assert(std::abs(ui->box_to_x->value() - m_arrow->GetToItem()->x()) < 2.0);
  assert(std::abs(ui->box_to_y->value() - m_arrow->GetToItem()->y()) < 2.0);
  */

}
#endif

 

 

 

 

 

./CppQtQuadBezierArrowItem/qtquadbezierarrowitem.h

 

//---------------------------------------------------------------------------
/*
QtQuadBezierArrowItem, an quadratic Bezier arrow QGraphicsItem
Copyright (C) 2012-2015 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
//From http://www.richelbilderbeek.nl/CppQtQuadBezierArrowItem.htm
//---------------------------------------------------------------------------
#ifndef QTQUADBEZIERARROWITEM_H
#define QTQUADBEZIERARROWITEM_H

#include <string>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <QGraphicsPathItem>
#include <QPen>
#pragma GCC diagnostic pop

namespace ribi {

///The QtQuadBezierArrowItem is a QGraphicsItem that
///follows the three QGraphicsItem positions
///If mid is nullptr, the line will be straight
///The QGraphicsItems supplied will not be deleted by QtQuadBezierArrowItem
struct QtQuadBezierArrowItem : public QGraphicsItem
{
  QtQuadBezierArrowItem(
    QGraphicsItem* const from,
    const bool tail,
    QGraphicsItem* const mid,
    const bool head,
    QGraphicsItem* const to,
    QGraphicsItem* parent = 0
  ) noexcept;

  virtual ~QtQuadBezierArrowItem() noexcept {}

  ///The rectangle that containg the item, used for rough calculations like
  ///collision detection
  QRectF boundingRect() const override final;

  ///Get the QPen used to indicate that the arrow has focus
  const QPen& GetFocusPen() const noexcept { return m_focus_pen; }

  ///Get the item where the arrow originates from
  ///(would the arrow and tail heads not be reversible)
  ///Use the specific GetFrom* member functions to modify this item
  const QGraphicsItem* GetFromItem() const noexcept { return m_from; }
  double GetFromX() const noexcept { return m_from->x(); }
  double GetFromY() const noexcept { return m_from->y(); }

  ///Obtain the head point of the arrow, on the edge of the rectangle m_from
  QPointF GetHead() const noexcept;

  ///Get the item where the arrow pass through in the middle
  ///Use the specific GetMid* member functions to modify this item
  const QGraphicsItem* GetMidItem() const noexcept { return m_mid; }
  double GetMidX() const noexcept { return m_mid->x(); }
  double GetMidY() const noexcept { return m_mid->y(); }

  ///Get the QPen used to draw a regular, non-focused, arrow
  const QPen& GetPen() const noexcept { return m_pen; }

  ///Obtain the tail point of the arrow, on the edge of the rectangle m_from
  QPointF GetTail() const noexcept;

  ///Get the item where the arrow points to
  ///(would the arrow and tail heads not be reversible)
  ///Use the specific GetTo* member functions to modify this item
  const QGraphicsItem* GetToItem() const noexcept { return m_to; }
  double GetToX() const noexcept { return m_to->x(); }
  double GetToY() const noexcept { return m_to->y(); }

  ///Obtain the version of this class
  static std::string GetVersion() noexcept;

  ///Obtain the version history of this class
  static std::vector<std::string> GetVersionHistory() noexcept;

  ///Is there an arrow at the 'to' point (x2,y2)?
  bool HasHead() const noexcept { return m_head; }

  ///Is there an arrow at the 'from' point (x1,y1)?
  bool HasTail() const noexcept { return m_tail; }

  ///Respond to key press
  void keyPressEvent(QKeyEvent *event) noexcept override final;

  ///Respond to mouse press
  void mousePressEvent(QGraphicsSceneMouseEvent *event) noexcept override final;

  ///Paint a QtQuadBezierArrowItem
  void paint(QPainter* painter, const QStyleOptionGraphicsItem *, QWidget *) noexcept override final;

  ///Set if the arrow has a point at the head
  void SetHasHead(const bool has_head) noexcept;

  ///Set if the arrow has a point at the tail
  void SetHasTail(const bool has_tail) noexcept;

  ///Set the pen used to show focus
  void SetFocusPen(const QPen& pen) noexcept;

  ///Set the position of the from item
  void SetFromPos(const QPointF& pos) noexcept;
  void SetFromPos(const double x, const double y) noexcept { SetFromPos(QPointF(x,y)); }
  void SetFromX(const double& x) noexcept { SetFromPos(x,GetFromY()); }
  void SetFromY(const double& y) noexcept { SetFromPos(GetFromX(),y); }

  ///Set the position of the middle item
  void SetMidPos(const QPointF& pos) noexcept;
  void SetMidPos(const double x, const double y) noexcept { SetMidPos(QPointF(x,y)); }
  void SetMidX(const double& x) noexcept { SetMidPos(x,GetMidY()); }
  void SetMidY(const double& y) noexcept { SetMidPos(GetMidX(),y); }

  ///Set the regular pen used to draw the arrow
  void SetPen(const QPen& pen) noexcept;

  void SetShowBoundingRect(const bool show_bounding_rect) noexcept;

  ///Set the position of the from item
  void SetToPos(const QPointF& pos) noexcept;
  void SetToPos(const double x, const double y) noexcept { SetToPos(QPointF(x,y)); }
  void SetToX(const double& x) noexcept { SetToPos(x,GetToY()); }
  void SetToY(const double& y) noexcept { SetToPos(GetToX(),y); }

  ///More precise shape compared to boundingRect
  ///In this example, it is redefined to ease selecting those thin lines
  QPainterPath shape() const noexcept override final;

  ///Emitted when the item has called
  boost::signals2::signal<void(const QtQuadBezierArrowItem*)> m_signal_item_updated;

  protected:
  ///Change the cursor when the user moves the mouse cursor in the bounding rectangle
  ///
  void hoverEnterEvent(QGraphicsSceneHoverEvent *event) noexcept override final;
  void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) noexcept override final;
  void hoverMoveEvent(QGraphicsSceneHoverEvent *event) noexcept override final;

  private:
  ///The extra width given to the line for easier clicking
  static const double m_click_easy_width;

  ///The arrow used for indicating focus
  QPen m_focus_pen;

  ///The item where the arrow originates from
  QGraphicsItem* const m_from;

  ///Show arrow at head
  bool m_head;

  ///The item where the arrow pass through in the middle
  QGraphicsItem* const m_mid;

  ///The regular pen
  QPen m_pen;

  ///Show the bounding rectangle, used in debugging
  bool m_show_bounding_rect;

  ///Show arrow at tail
  bool m_tail;

  ///The item where the arrow points to
  ///(would the arrow and tail heads not be reversible)
  QGraphicsItem* const m_to;

  ///Obtain point 'beyond'
  QPointF GetBeyond() const noexcept;

  ///Obtain point 'center'
  /*

  F
   \
    \
  C  M  B
    /
   /
  T

  F = From
  T = To
  M = Mid
  C = Center
  B = Beyond

  */
  QPointF GetCenter() const noexcept;

  QPointF pos() const = delete;
  void setPos(const QPointF&) = delete;
  void setX(const double&) = delete;
  void setY(const double&) = delete;

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif

  friend std::ostream& operator<<(std::ostream& os, const QtQuadBezierArrowItem& arrow) noexcept;
};

std::ostream& operator<<(std::ostream& os, const QtQuadBezierArrowItem& arrow) noexcept;
bool operator==(const QtQuadBezierArrowItem& lhs, const QtQuadBezierArrowItem& rhs) noexcept;

} //~namespace ribi

#endif // QTQUADBEZIERARROWITEM_H

 

 

 

 

 

./CppQtQuadBezierArrowItem/qtquadbezierarrowitem.cpp

 

//---------------------------------------------------------------------------
/*
QtQuadBezierArrowItem, an quadratic Bezier arrow QGraphicsItem
Copyright (C) 2012-2015 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
//From http://www.richelbilderbeek.nl/CppQtQuadBezierArrowItem.htm
//---------------------------------------------------------------------------
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include "qtquadbezierarrowitem.h"

#include <cassert>
#include <cmath>

#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry.hpp>
#include <boost/math/constants/constants.hpp>

#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QPainter>

#include "geometry.h"
#include "testtimer.h"
#include "trace.h"
#pragma GCC diagnostic pop

template <class T>
bool operator==(
  const boost::geometry::model::d2::point_xy<T>& a,
  const boost::geometry::model::d2::point_xy<T>& b)
{
  return boost::geometry::equals(a,b);
  //return a.x() == b.x() && a.y() == b.y();
}

template <class T>
bool operator!=(
  const boost::geometry::model::d2::point_xy<T>& a,
  const boost::geometry::model::d2::point_xy<T>& b)
{
  return !(a == b);
}

template <class T>
bool operator==(
  const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T> > a,
  const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T> > b)
{
  return boost::geometry::equals(a,b);
  //return (a[0] == b[0] && a[1] == b[1])
  //  ||   (a[0] == b[1] && a[1] == b[0]);
}

template <class T>
bool operator!=(
  const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T> > a,
  const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T> > b)
{
  return !(a==b);
}

///Obtain the zero or one intersections between two finite lines
//From http://www.richelbilderbeek.nl/CppGetLineLineIntersections.htm
template <class T>
const std::vector<
  boost::geometry::model::d2::point_xy<T>
>
GetLineLineIntersections(
  const boost::geometry::model::linestring<
    boost::geometry::model::d2::point_xy<T>
  > line1,
  const boost::geometry::model::linestring<
    boost::geometry::model::d2::point_xy<T>
  > line2)
{
  typedef boost::geometry::model::d2::point_xy<T> Point;
  std::vector<Point> points;
  boost::geometry::intersection(line1,line2,points);

  assert((points.empty() || points.size() == 1 || points.size() == 2)
         && "0: The lines are parallel and not on top of each other"
         && "1: The lines are crossing"
         && "2: The lines are on top of each other"); //edit claudio_04122014
  return points;
}

//Helper function to create a const line without a temporary std::vector
template <class T>
const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T>
>
CreateLine(const std::vector<boost::geometry::model::d2::point_xy<T> >& v)
{
  return boost::geometry::model::linestring<
    boost::geometry::model::d2::point_xy<T>
  >(std::begin(v),std::end(v));
}

///Obtain the zero, one or two intersections between a line and a rectanle
//From http://www.richelbilderbeek.nl/CppGetLineRectIntersections.htm
template <class T>
const std::vector<
  boost::geometry::model::d2::point_xy<T>
>
GetLineRectIntersections(
  const boost::geometry::model::linestring<
    boost::geometry::model::d2::point_xy<T>
  > line,
  const boost::geometry::model::box<
    boost::geometry::model::d2::point_xy<T>
  > rect)
{
  typedef boost::geometry::model::d2::point_xy<T> Point;
  typedef boost::geometry::model::linestring<Point> Line;
  //typedef boost::geometry::model::box<Point> Rect;

  const Point p0 = Point(rect.min_corner().x(), rect.min_corner().y());
  const Point p1 = Point(rect.max_corner().x(), rect.min_corner().y());
  const Point p2 = Point(rect.min_corner().x(), rect.max_corner().y());
  const Point p3 = Point(rect.max_corner().x(), rect.max_corner().y());
  assert(p0 != p1); assert(p0 != p2); assert(p0 != p3);
  assert(p1 != p0); assert(p1 != p2); assert(p1 != p3);
  assert(p2 != p0); assert(p2 != p1); assert(p2 != p3);
  assert(p3 != p0); assert(p3 != p1); assert(p3 != p2);

  const std::vector<Line> rect_sides
    =
    {
      CreateLine(std::vector<Point>( {p0,p1} )),
      CreateLine(std::vector<Point>( {p0,p2} )),
      CreateLine(std::vector<Point>( {p1,p3} )),
      CreateLine(std::vector<Point>( {p2,p3} ))
    };
  std::vector<Point> points;
  for (const auto side: rect_sides)
  {
    const std::vector<Point> v = GetLineLineIntersections(line,side);
    std::copy(v.begin(),v.end(),std::back_inserter(points));
  }

  //claudio edit_05122014
  //the vector points must be sorted before deleting the duplicates
  //because std::unique works on consecutive elements
  std::sort( points.begin(),points.end(),
    [](const Point& lhs, const Point& rhs)
    {
      return lhs.x() == rhs.x() && lhs.y() == rhs.y();
    }
  );

  //Remove doublures
  //Put 'typename' before 'std::vector<Point>::iteratortype' to prevent getting the error below:
  //error: need 'typename' before 'std::vector<boost::geometry::model::d2::point_xy<T> >::iterator'
  //  because 'std::vector<boost::geometry::model::d2::point_xy<T> >' is a dependent scope
  typename std::vector<Point>::iterator new_end = std::unique( points.begin(),points.end(),
    [](const Point& lhs, const Point& rhs)
    {
      return lhs.x() == rhs.x() && lhs.y() == rhs.y();
    }
  );

  points.erase(new_end,points.end());

  assert(points.size() <= 2
         && "0: The line does not cross the rectangle"
         && "1: The line crosses one edge or one corner of the rectangle"
         && "2: The line is on top of one edge or crosses two edges of the rectangle"
         ); // edit claudio_04122014

  return points;
}

const double ribi::QtQuadBezierArrowItem::m_click_easy_width = 10.0;

ribi::QtQuadBezierArrowItem::QtQuadBezierArrowItem(
  QGraphicsItem* const from,
  const bool tail,
  QGraphicsItem* const mid,
  const bool head,
  QGraphicsItem* const to,
  QGraphicsItem* parent
) noexcept
  : QGraphicsItem(parent),
    m_signal_item_updated{},
    m_focus_pen{QPen(Qt::DashLine)},
    m_from{from},
    m_head{head},
    m_mid{mid},
    m_pen{QPen(QColor(0,0,0))},
    m_show_bounding_rect{true},
    m_tail{tail},
    m_to{to}
{
  #ifndef NDEBUG
  Test();
  #endif

  assert(from);
  assert(to);
  assert((mid || !mid) && "No mid results in a straight arrow");
  assert(from != to);
  assert(from != mid);
  assert(mid != to);
  this->setFlags(
      QGraphicsItem::ItemIsFocusable
    | QGraphicsItem::ItemIsSelectable
  );

  assert(!(flags() & QGraphicsItem::ItemIsMovable) );
  assert( (flags() & QGraphicsItem::ItemIsSelectable) );

  this->setAcceptHoverEvents(true);

  //Put this arrow item under the rect
  if (mid) { setZValue(mid->zValue() - 1.0); }
}

QRectF ribi::QtQuadBezierArrowItem::boundingRect() const
{
  return shape().boundingRect();
}

QPointF ribi::QtQuadBezierArrowItem::GetBeyond() const noexcept
{
  const QPointF center = GetCenter();
  const double dx_mid_center = GetMidItem() ? (GetMidItem()->pos().x() - center.x()) : 0.0;
  const double dy_mid_center = GetMidItem() ? (GetMidItem()->pos().y() - center.y()) : 0.0;
  const QPointF beyond(center.x() + dx_mid_center + dx_mid_center, center.y() + dy_mid_center + dy_mid_center);
  return beyond;
}

QPointF ribi::QtQuadBezierArrowItem::GetCenter() const noexcept
{
  const QPointF center(GetToItem()->pos() + GetFromItem()->pos() / 2.0);
  return center;
}

QPointF ribi::QtQuadBezierArrowItem::GetHead() const noexcept
{
  typedef boost::geometry::model::d2::point_xy<double> Point;
  typedef boost::geometry::model::linestring<Point> Line;
  typedef boost::geometry::model::box<Point> Rect;

  //const bool debug = true;


  const QPointF beyond = GetBeyond();

  const Line line_head = CreateLine(
    std::vector<Point>(
      {
        Point(beyond.x(),beyond.y()),
        Point(m_to->pos().x(),m_to->pos().y()),
      }
    )
  );


  const QRectF qr_to = m_to->boundingRect().translated(m_to->pos());

  const Rect r_to(
    Point(qr_to.topLeft().x()    ,qr_to.topLeft().y()    ),
    Point(qr_to.bottomRight().x(),qr_to.bottomRight().y())
    );


  std::vector<Point> p_head_end = GetLineRectIntersections(line_head,r_to);
  if (p_head_end.size() == 1)
  {
    return QPointF(p_head_end[0].x(),p_head_end[0].y());
  }
  if (p_head_end.empty())
  {
    p_head_end.push_back(Point(m_to->pos().x(),m_to->pos().y()));
    //Yes,it happens, when the line does not leave the rectangle
    //this happens when the two node rectanges overlap
    assert(!p_head_end.empty());
    assert(p_head_end.size() == 1); ///BUG? claudio does not think this is a bug:
                                    ///one element is added two lines above
    return QPointF(p_head_end[0].x(),p_head_end[0].y());
  }
  else
  {
    assert(p_head_end.size() == 2);
    //Choose point closest to beyond
    const double d1 = Geometry().GetDistance(beyond.x(),beyond.y(),p_head_end[0].x(),p_head_end[0].y());
    const double d2 = Geometry().GetDistance(beyond.x(),beyond.y(),p_head_end[1].x(),p_head_end[1].y());
    if (d1 <= d2)
    {
      return QPointF(p_head_end[0].x(),p_head_end[0].y());
    }
    else
    {
      return QPointF(p_head_end[1].x(),p_head_end[1].y());
    }
  }
}

QPointF ribi::QtQuadBezierArrowItem::GetTail() const noexcept
{

  typedef boost::geometry::model::d2::point_xy<double> Point;
  typedef boost::geometry::model::linestring<Point> Line;
  typedef boost::geometry::model::box<Point> Rect;

  const QPointF beyond = GetBeyond();

  const Line line_tail = CreateLine(
    std::vector<Point>(
      {
        Point(m_from->pos().x(),m_from->pos().y()),
        Point(beyond.x(),beyond.y()),
      }
    )
  );

  const QRectF qr_from = m_from->boundingRect().translated(m_from->pos());

  const Rect r_from(
    Point(qr_from.topLeft().x()    ,qr_from.topLeft().y()    ),
    Point(qr_from.bottomRight().x(),qr_from.bottomRight().y())
    );

  std::vector<Point> p_tail_end = GetLineRectIntersections(line_tail,r_from);
  if (p_tail_end.size() == 1)
  {
    return QPointF(p_tail_end[0].x(),p_tail_end[0].y());
  }
  if (p_tail_end.empty())
  {
    //Yes,it happens, when the line does not leave the rectangle
    //this happens when the two node rectanges overlap
    p_tail_end.push_back(Point(m_from->pos().x(),m_from->pos().y()));
    assert(!p_tail_end.empty());
    assert(p_tail_end.size() == 1);
    return QPointF(p_tail_end[0].x(),p_tail_end[0].y());
  }
  else
  {
    assert(p_tail_end.size() == 2);
    //Choose point closest to beyond
    const double d1 = Geometry().GetDistance(beyond.x(),beyond.y(),p_tail_end[0].x(),p_tail_end[0].y());
    const double d2 = Geometry().GetDistance(beyond.x(),beyond.y(),p_tail_end[1].x(),p_tail_end[1].y());
    if (d1 <= d2)
    {
      return QPointF(p_tail_end[0].x(),p_tail_end[0].y());
    }
    else
    {
      return QPointF(p_tail_end[1].x(),p_tail_end[1].y());
    }
  }
}

std::string ribi::QtQuadBezierArrowItem::GetVersion() noexcept
{
  return "1.5";
}

std::vector<std::string> ribi::QtQuadBezierArrowItem::GetVersionHistory() noexcept
{
  return {
    "2012-12-07: version 1.0: initial version",
    "2012-12-13: version 1.1: respond to focus",
    "2012-12-29: version 1.2: fixed bug in GetHead and GetTail that occurs when GetLineRectIntersections returns two points",
    "2013-01-01: version 1.3: added QGraphicsItem getters",
    "2013-07-10: version 1.4: setting arrow heads emits a notification signal",
    "2014-03-18: version 1.5: allow arrow to be straight"
  };
}

void ribi::QtQuadBezierArrowItem::hoverEnterEvent(QGraphicsSceneHoverEvent *) noexcept
{
  this->setCursor(QCursor(Qt::PointingHandCursor));
}

void ribi::QtQuadBezierArrowItem::hoverMoveEvent(QGraphicsSceneHoverEvent *) noexcept
{
  this->setCursor(QCursor(Qt::PointingHandCursor));
}

void ribi::QtQuadBezierArrowItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) noexcept
{
  this->setCursor(QCursor(Qt::PointingHandCursor));
}

void ribi::QtQuadBezierArrowItem::keyPressEvent(QKeyEvent *event) noexcept
{
  switch (event->key())
  {
    case Qt::Key_F1:
    case Qt::Key_1:
    case Qt::Key_T:
    case Qt::Key_Minus:
      SetHasTail(!HasTail());
      return;
    case Qt::Key_F2:
    case Qt::Key_2:
    case Qt::Key_H:
    case Qt::Key_Plus:
      SetHasHead(!HasHead());
      return;
    default:
      break;
  }
  QGraphicsItem::keyPressEvent(event);
}

void ribi::QtQuadBezierArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) noexcept
{
  if (event->modifiers() & Qt::ShiftModifier)
  {
    if ((event->pos() - this->m_from->pos()).manhattanLength() < 10.0)
    {
      SetHasTail(!HasTail());
    }
    else if ((event->pos() - this->m_to->pos()).manhattanLength() < 10.0)
    {
      SetHasHead(!HasHead());
    }
  }
  QGraphicsItem::mousePressEvent(event);
}


void ribi::QtQuadBezierArrowItem::paint(QPainter* painter, const QStyleOptionGraphicsItem *, QWidget *) noexcept
{
  painter->setRenderHint(QPainter::Antialiasing);

  if (this->isSelected() || this->hasFocus())
  {
    painter->setPen(m_focus_pen);
  }
  else
  {
    painter->setPen(m_pen);
  }
  //Line must go _though_ mid pos, instead of using it as a virtual hinge point
  //Solution:
  // - define point 'center' as the middle between from and to
  // - define point 'beyond' as the mirror point of 'center', using mid_pos as a mirror

  const QPointF p_end_head{GetHead()};
  const QPointF p_end_tail{GetTail()};

  const QPointF p_center((p_end_tail + p_end_head) / 2.0);
  const double dx_mid_center = GetMidItem() ? (GetMidItem()->pos().x() - p_center.x()) : 0.0;
  const double dy_mid_center = GetMidItem() ? (GetMidItem()->pos().y() - p_center.y()) : 0.0;
  const QPointF p_beyond(p_center.x() + dx_mid_center + dx_mid_center, p_center.y() + dy_mid_center + dy_mid_center);

  QPainterPath curve;
  curve.moveTo(p_end_tail);
  curve.quadTo(p_beyond,p_end_head);
  painter->drawPath(curve);

  if (GetMidItem())
  {
    painter->drawEllipse(GetMidItem()->pos(),1,1);
  }

  {
    const double sz = 10.0; //pixels
    if (m_tail)
    {
      //The angle from midpoint to tail
      //Thanks goes out to Toine van den Bogaart and Theo van den Bogaart for being happy to help with the math
      const double pi{boost::math::constants::pi<double>()};
      //#define USE_RICHEL_EDIT_20141204
      #ifdef USE_RICHEL_EDIT_20141204
      const double dx{p_beyond.x() - m_from->pos().x()};
      const double dy{p_beyond.y() - m_from->pos().y()};
      const double arrowangle{0.1*pi};
      double angle1{0.5*pi + arrowangle - Geometry().GetAngleClockScreen(-dx,-dy)};
      double angle2{0.5*pi + arrowangle - Geometry().GetAngleClockScreen(-dx,-dy)};
      #endif // USE_RICHEL_EDIT_20141204
      #define USE_CLAUDIO_EDIT_20141205
      #ifdef USE_CLAUDIO_EDIT_20141205
      const double dx{p_end_tail.x() - p_beyond.x()};
      const double dy{p_end_tail.y() - p_beyond.y()};
      double angle1{0.6*pi - Geometry().GetAngleClockScreen(-dx,-dy)};
      double angle2{0.4*pi - Geometry().GetAngleClockScreen(-dx,-dy)};
      #endif // USE_CLAUDIO_EDIT_20141205
      //#define USE_RICHEL_EDIT_20150209
      #ifdef USE_RICHEL_EDIT_20150209
      const double dx{p_end_tail.x() - p_beyond.x()};
      const double dy{p_end_tail.y() - p_beyond.y()};
      double angle1{0.5*pi - Geometry().GetAngleClockScreen(-dx,-dy)};
      double angle2{0.5*pi - Geometry().GetAngleClockScreen(-dx,-dy)};
      #endif

      const QPointF p0{p_end_tail.x(),p_end_tail.y()};
      #ifdef USE_RICHEL_2014
      const QPointF p1
        = p0 + QPointF(
           std::cos(angle1) * sz,
           -std::sin(angle1) * sz);
      const QPointF p2
        = p0 + QPointF(
           std::cos(angle2) * sz,
           -std::sin(angle2) * sz);
      #else // USE_RICHEL_2014
      const QPointF p1
        = p0 + QPointF(
           std::sin(angle1) * sz,
           -std::cos(angle1) * sz);
      const QPointF p2
        = p0 + QPointF(
           std::sin(angle2) * sz,
           -std::cos(angle2) * sz);
      #endif // USE_ORIGINAL
      painter->drawPolygon(QPolygonF() << p0 << p1 << p2);
    }
    if (m_head)
    {
      //The angle from midpoint to head
      //Thanks goes out to Toine van den Bogaart and Theo van den Bogaart for being happy to help with the math
      const double pi{boost::math::constants::pi<double>()};
      const double dx{p_end_head.x() - p_beyond.x()};
      const double dy{(p_end_head.y() - p_beyond.y())};
      double angle1{0.6*pi - Geometry().GetAngleClockScreen(-dx,-dy)};
      double angle2{0.4*pi - Geometry().GetAngleClockScreen(-dx,-dy)};

      const QPointF p0(p_end_head.x(),p_end_head.y());
      const QPointF p1
        = p0 + QPointF(
           std::cos(angle1) * sz,
           -std::sin(angle1) * sz);
      const QPointF p2
        = p0 + QPointF(
           std::cos(angle2) * sz,
           -std::sin(angle2) * sz);

      painter->drawPolygon(QPolygonF() << p0 << p1 << p2);
    }
  }

  if (m_show_bounding_rect)
  {
    const QPen prev_pen = painter->pen();
    const QBrush prev_brush = painter->brush();
    painter->setPen(QPen(QColor(0,0,96)));
    painter->setBrush(QBrush(QColor(0,0,255,64)));
    painter->drawRect(this->boundingRect().adjusted(1.0,1.0,-1.0,-1.0));
    painter->setPen(prev_pen);
    painter->setBrush(prev_brush);
  }
}

void ribi::QtQuadBezierArrowItem::SetFocusPen(const QPen& pen) noexcept
{
  if (m_focus_pen != pen)
  {
    m_focus_pen = pen;
    this->update();
    m_signal_item_updated(this);
  }
}

void ribi::QtQuadBezierArrowItem::SetFromPos(const QPointF& pos) noexcept
{
  assert(m_from);
  if (m_from->pos() != pos)
  {
    m_from->setPos(pos);
    this->update();
    m_signal_item_updated(this);
  }
}

void ribi::QtQuadBezierArrowItem::SetHasHead(const bool has_head) noexcept
{

  if (m_head != has_head)
  {
    m_head = has_head;
    this->update();
    m_signal_item_updated(this);
  }
}

void ribi::QtQuadBezierArrowItem::SetHasTail(const bool has_tail) noexcept
{
  if (m_tail != has_tail)
  {
    m_tail = has_tail;
    this->update();
    m_signal_item_updated(this);
  }
}


void ribi::QtQuadBezierArrowItem::SetMidPos(const QPointF& pos) noexcept
{
  assert(m_mid);
  if (m_mid->pos() != pos)
  {
    m_mid->setPos(pos);
    this->update();
    m_signal_item_updated(this);
  }
}


void ribi::QtQuadBezierArrowItem::SetPen(const QPen& pen) noexcept
{
  if (m_pen != pen)
  {
    m_pen = pen;
    this->update();
    m_signal_item_updated(this);
  }
}


void ribi::QtQuadBezierArrowItem::SetToPos(const QPointF& pos) noexcept
{
  assert(m_to);
  if (m_to->pos() != pos)
  {
    m_to->setPos(pos);
    this->update();
    m_signal_item_updated(this);
  }
}


QPainterPath ribi::QtQuadBezierArrowItem::shape() const noexcept
{
  const QPointF p_end_tail = GetHead();
  const QPointF p_end_head = GetTail();

  const QPointF p_center((p_end_tail + p_end_head) / 2.0);
  const double dx_mid_center = GetMidItem() ? (GetMidItem()->pos().x() - p_center.x()) : 0.0;
  const double dy_mid_center = GetMidItem() ? (GetMidItem()->pos().y() - p_center.y()) : 0.0;
  const QPointF p_beyond(p_center.x() + dx_mid_center + dx_mid_center, p_center.y() + dy_mid_center + dy_mid_center);

  QPainterPath curve;
  curve.moveTo(p_end_tail);
  curve.quadTo(p_beyond,p_end_head);

  QPainterPathStroker stroker;
  stroker.setWidth(m_click_easy_width);
  return stroker.createStroke(curve);
}

#ifndef NDEBUG
void ribi::QtQuadBezierArrowItem::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  {
    Geometry();
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);

  /*
  //Render one QtQuadBezierArrowItem
  {
    std::unique_ptr<QGraphicsScene> my_scene{new QGraphicsScene};
    std::vector<QGraphicsRectItem *> rects;
    const int n_items = 3;
    const double ray = 100;
    for (int i=0; i!=n_items; ++i)
    {
      const double pi = boost::math::constants::pi<double>();
      const double angle = 2.0 * pi * (static_cast<double>(i) / static_cast<double>(n_items));
      const double x1 =  std::sin(angle) * ray;
      const double y1 = -std::cos(angle) * ray;
      QGraphicsRectItem * const rect = new QGraphicsRectItem;
      rect->setRect(-8.0,-4.0,16.0,8.0);
      rect->setPos(x1,y1);
      assert(!rect->scene());
      my_scene->addItem(rect);
      rects.push_back(rect);
    }
    for (int i=0; i<n_items-2; i+=3)
    {
      assert(i + 2 < n_items);
      QtQuadBezierArrowItem * const item = new QtQuadBezierArrowItem(
        rects[(i+0) % n_items],
        false,
        rects[(i+1) % n_items],
        true,
        rects[(i+2) % n_items]);
      assert(!item->scene());
      my_scene->addItem(item);
    }
    QGraphicsScene().render(
    //my_scene->
    QGraphicsScene q;
  }
  */
}
#endif

std::ostream& ribi::operator<<(std::ostream& os, const QtQuadBezierArrowItem& arrow) noexcept
{
  os
    << '(' << arrow.m_from->x() << ',' << arrow.m_from->y() << ')'
    << (arrow.m_tail ? '<' : '-') << "-- "
    << '(' << arrow.m_mid->x() << ',' << arrow.m_mid->y() << ')'
    << " --" << (arrow.m_head ? '>' : '-') << ' '
    << '(' << arrow.m_to->x() << ',' << arrow.m_to->y() << ')'
  ;
  return os;
}

bool ribi::operator==(const QtQuadBezierArrowItem& lhs, const QtQuadBezierArrowItem& rhs) noexcept
{
  return
       lhs.GetFocusPen() == rhs.GetFocusPen()
    && lhs.GetFromItem() == rhs.GetFromItem()
    && lhs.HasHead() == rhs.HasHead()
    && lhs.GetMidItem() == rhs.GetMidItem()
    && lhs.GetPen() == rhs.GetPen()
    && lhs.HasTail() == rhs.HasTail()
    && lhs.GetToItem() == rhs.GetToItem()
  ;
}

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict

This page has been created by the tool CodeToHtml