<commit_before>#pragma once

// `krbn::hid_manager` can be used safely in a multi-threaded environment.

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "dispatcher.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "monitor/service_monitor.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {

// We implement hid_manager without IOHIDManager.
//
// IOHIDManager opens connected device automatically, and we cannot stop it.
// We want to prevent opening device depending the `device_detecting` result.
// Thus, we cannot use IOHIDManager to this purpose.

class hid_manager final : public pqrs::dispatcher::extra::dispatcher_client {
public:
  // Signals (invoked from the shared dispatcher thread)

  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull), boost_utility::signals2_combiner_call_while_true> device_detecting;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_detected;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_removed;

  // Methods

  hid_manager(const hid_manager&) = delete;

  hid_manager(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : dispatcher_client(),
                                                                                usage_pairs_(usage_pairs),
                                                                                refresh_timer_(*this) {
  }

  virtual ~hid_manager(void) {
    detach_from_dispatcher([this] {
      stop();
    });
  }

  void async_start(void) {
    enqueue_to_dispatcher([this] {
      start();
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  // This method is executed in the dispatcher thread.
  void start(void) {
    for (const auto& pair : usage_pairs_) {
      if (auto dictionary = IOServiceMatching(kIOHIDDeviceKey)) {
        cf_utility::set_cfmutabledictionary_value(dictionary,
                                                  CFSTR(kIOHIDDeviceUsagePageKey),
                                                  static_cast<int64_t>(pair.first));
        cf_utility::set_cfmutabledictionary_value(dictionary,
                                                  CFSTR(kIOHIDDeviceUsageKey),
                                                  static_cast<int64_t>(pair.second));

        auto monitor = std::make_shared<monitor::service_monitor::service_monitor>(dictionary);

        monitor->service_detected.connect([this](auto&& services) {
          for (const auto& service : services->get_services()) {
            if (devices_.find(service) == std::end(devices_)) {
              if (auto device = IOHIDDeviceCreate(kCFAllocatorDefault, service)) {
                CFRetain(device);
                devices_[service] = device;

                device_matching_callback(device);

                CFRelease(device);
              }
            }
          }
        });

        monitor->service_removed.connect([this](auto&& services) {
          for (const auto& service : services->get_services()) {
            auto it = devices_.find(service);
            if (it != std::end(devices_)) {
              device_removal_callback(it->second);

              devices_.erase(it);
              CFRelease(it->second);
            }
          }
        });

        monitor->async_start();

        service_monitors_.push_back(monitor);

        CFRelease(dictionary);
      }
    }

    refresh_timer_.start(
        [this] {
          refresh_if_needed();
        },
        std::chrono::milliseconds(5000));

    logger::get_logger().info("hid_manager is started.");
  }

  // This method is executed in the dispatcher thread.
  void stop(void) {
    refresh_timer_.stop();
    service_monitors_.clear();
    hids_.clear();
    registry_entry_ids_.clear();

    for (const auto& pair : devices_) {
      CFRelease(pair.second);
    }
    devices_.clear();
  }

  // This method is executed in the dispatcher thread.
  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      stop();
      start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    CFRetain(device);

    enqueue_to_dispatcher([this, device] {
      if (!device_detecting(device)) {
        goto finish;
      }

      if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
        // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
        // Thus, we have to memory device and registry_entry_id correspondence by myself.
        registry_entry_ids_[device] = *registry_entry_id;

        // Skip if same device is already matched.
        // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          if (std::any_of(std::begin(hids_),
                          std::end(hids_),
                          [&](auto&& h) {
                            return *registry_entry_id == h->get_registry_entry_id();
                          })) {
            // logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
            goto finish;
          }

          hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
          hids_.push_back(hid);
        }

        enqueue_to_dispatcher([this, hid] {
          device_detected(hid);
        });
      }

    finish:
      CFRelease(device);
    });
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    CFRetain(device);

    enqueue_to_dispatcher([this, device] {
      if (auto registry_entry_id = find_registry_entry_id(device)) {
        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          auto it = std::find_if(std::begin(hids_),
                                 std::end(hids_),
                                 [&](auto&& h) {
                                   return *registry_entry_id == h->get_registry_entry_id();
                                 });
          if (it != hids_.end()) {
            hid = *it;
            hids_.erase(it);
          }
        }

        if (hid) {
          hid->set_removed();

          enqueue_to_dispatcher([this, hid] {
            device_removed(hid);
          });
        }

        // There might be multiple devices for one registry_entry_id.
        // (For example, keyboard and mouse combined device.)
        // And there is possibility that removal callback is not called with some device.
        //
        // For example, there are 3 devices for 1 registry_entry_id (4294974284).
        //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
        //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
        //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
        // And device_removal_callback are called only with device1 and device2.
        //
        // We should remove device1, device2 and device3 at the same time in order to deal with this case.

        for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
          if (it->second == *registry_entry_id) {
            it = registry_entry_ids_.erase(it);
          } else {
            std::advance(it, 1);
          }
        }
      }

      CFRelease(device);
    });
  }

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  std::vector<std::shared_ptr<monitor::service_monitor::service_monitor>> service_monitors_;
  pqrs::dispatcher::extra::timer refresh_timer_;

  std::unordered_map<io_service_t, IOHIDDeviceRef> devices_;
  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;

  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
}; // namespace krbn
} // namespace krbn
<commit_msg>use cf_ptr<commit_after>#pragma once

// `krbn::hid_manager` can be used safely in a multi-threaded environment.

#include "boost_utility.hpp"
#include "cf_utility.hpp"
#include "device_detail.hpp"
#include "dispatcher.hpp"
#include "human_interface_device.hpp"
#include "logger.hpp"
#include "monitor/service_monitor.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <unordered_map>

namespace krbn {

// We implement hid_manager without IOHIDManager.
//
// IOHIDManager opens connected device automatically, and we cannot stop it.
// We want to prevent opening device depending the `device_detecting` result.
// Thus, we cannot use IOHIDManager to this purpose.

class hid_manager final : public pqrs::dispatcher::extra::dispatcher_client {
public:
  // Signals (invoked from the shared dispatcher thread)

  // Return false to ignore device.
  boost::signals2::signal<bool(IOHIDDeviceRef _Nonnull), boost_utility::signals2_combiner_call_while_true> device_detecting;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_detected;
  boost::signals2::signal<void(std::weak_ptr<human_interface_device>)> device_removed;

  // Methods

  hid_manager(const hid_manager&) = delete;

  hid_manager(const std::vector<std::pair<hid_usage_page, hid_usage>>& usage_pairs) : dispatcher_client(),
                                                                                usage_pairs_(usage_pairs),
                                                                                refresh_timer_(*this) {
  }

  virtual ~hid_manager(void) {
    detach_from_dispatcher([this] {
      stop();
    });
  }

  void async_start(void) {
    enqueue_to_dispatcher([this] {
      start();
    });
  }

  std::vector<std::shared_ptr<human_interface_device>> copy_hids(void) const {
    std::lock_guard<std::mutex> lock(hids_mutex_);

    return hids_;
  }

private:
  // This method is executed in the dispatcher thread.
  void start(void) {
    for (const auto& pair : usage_pairs_) {
      if (auto dictionary = IOServiceMatching(kIOHIDDeviceKey)) {
        cf_utility::set_cfmutabledictionary_value(dictionary,
                                                  CFSTR(kIOHIDDeviceUsagePageKey),
                                                  static_cast<int64_t>(pair.first));
        cf_utility::set_cfmutabledictionary_value(dictionary,
                                                  CFSTR(kIOHIDDeviceUsageKey),
                                                  static_cast<int64_t>(pair.second));

        auto monitor = std::make_shared<monitor::service_monitor::service_monitor>(dictionary);

        monitor->service_detected.connect([this](auto&& services) {
          for (const auto& service : services->get_services()) {
            if (devices_.find(service) == std::end(devices_)) {
              if (auto device = IOHIDDeviceCreate(kCFAllocatorDefault, service)) {
                devices_[service] = cf_utility::cf_ptr<IOHIDDeviceRef>(device);

                device_matching_callback(device);

                CFRelease(device);
              }
            }
          }
        });

        monitor->service_removed.connect([this](auto&& services) {
          for (const auto& service : services->get_services()) {
            auto it = devices_.find(service);
            if (it != std::end(devices_)) {
              device_removal_callback(*(it->second));

              devices_.erase(it);
            }
          }
        });

        monitor->async_start();

        service_monitors_.push_back(monitor);

        CFRelease(dictionary);
      }
    }

    refresh_timer_.start(
        [this] {
          refresh_if_needed();
        },
        std::chrono::milliseconds(5000));

    logger::get_logger().info("hid_manager is started.");
  }

  // This method is executed in the dispatcher thread.
  void stop(void) {
    refresh_timer_.stop();
    service_monitors_.clear();
    hids_.clear();
    registry_entry_ids_.clear();
    devices_.clear();
  }

  // This method is executed in the dispatcher thread.
  void refresh_if_needed(void) {
    // `device_removal_callback` is sometimes missed
    // and invalid human_interface_devices are remained into hids_.
    // (The problem is likely occur just after wake up from sleep.)
    //
    // We validate the human_interface_device,
    // and then reload devices if there is an invalid human_interface_device.

    bool needs_refresh = false;

    {
      std::lock_guard<std::mutex> lock(hids_mutex_);

      for (const auto& hid : hids_) {
        if (!hid->validate()) {
          logger::get_logger().warn("Refreshing hid_manager since a dangling human_interface_device is found. ({0})",
                                    hid->get_name_for_log());
          needs_refresh = true;
          break;
        }
      }
    }

    if (needs_refresh) {
      stop();
      start();
    }
  }

  boost::optional<registry_entry_id> find_registry_entry_id(IOHIDDeviceRef _Nonnull device) {
    auto it = registry_entry_ids_.find(device);
    if (it != std::end(registry_entry_ids_)) {
      return it->second;
    }
    return boost::none;
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    CFRetain(device);

    enqueue_to_dispatcher([this, device] {
      if (!device_detecting(device)) {
        goto finish;
      }

      if (auto registry_entry_id = iokit_utility::find_registry_entry_id(device)) {
        // iokit_utility::find_registry_entry_id will be failed in device_removal_callback at least on macOS 10.13.
        // Thus, we have to memory device and registry_entry_id correspondence by myself.
        registry_entry_ids_[device] = *registry_entry_id;

        // Skip if same device is already matched.
        // (Multiple usage device (e.g. usage::pointer and usage::mouse) will be matched twice.)

        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          if (std::any_of(std::begin(hids_),
                          std::end(hids_),
                          [&](auto&& h) {
                            return *registry_entry_id == h->get_registry_entry_id();
                          })) {
            // logger::get_logger().info("registry_entry_id:{0} already exists", static_cast<uint64_t>(*registry_entry_id));
            goto finish;
          }

          hid = std::make_shared<human_interface_device>(device, *registry_entry_id);
          hids_.push_back(hid);
        }

        enqueue_to_dispatcher([this, hid] {
          device_detected(hid);
        });
      }

    finish:
      CFRelease(device);
    });
  }

  static void static_device_removal_callback(void* _Nullable context,
                                             IOReturn result,
                                             void* _Nullable sender,
                                             IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<hid_manager*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    CFRetain(device);

    enqueue_to_dispatcher([this, device] {
      if (auto registry_entry_id = find_registry_entry_id(device)) {
        std::shared_ptr<human_interface_device> hid;

        {
          std::lock_guard<std::mutex> lock(hids_mutex_);

          auto it = std::find_if(std::begin(hids_),
                                 std::end(hids_),
                                 [&](auto&& h) {
                                   return *registry_entry_id == h->get_registry_entry_id();
                                 });
          if (it != hids_.end()) {
            hid = *it;
            hids_.erase(it);
          }
        }

        if (hid) {
          hid->set_removed();

          enqueue_to_dispatcher([this, hid] {
            device_removed(hid);
          });
        }

        // There might be multiple devices for one registry_entry_id.
        // (For example, keyboard and mouse combined device.)
        // And there is possibility that removal callback is not called with some device.
        //
        // For example, there are 3 devices for 1 registry_entry_id (4294974284).
        //   - device1 { device:0x7fb9d64078a0 => registry_entry_id:4294974284 }
        //   - device2 { device:0x7fb9d8301390 => registry_entry_id:4294974284 }
        //   - device3 { device:0x7fb9d830a630 => registry_entry_id:4294974284 }
        // And device_removal_callback are called only with device1 and device2.
        //
        // We should remove device1, device2 and device3 at the same time in order to deal with this case.

        for (auto it = std::begin(registry_entry_ids_); it != std::end(registry_entry_ids_);) {
          if (it->second == *registry_entry_id) {
            it = registry_entry_ids_.erase(it);
          } else {
            std::advance(it, 1);
          }
        }
      }

      CFRelease(device);
    });
  }

  std::vector<std::pair<hid_usage_page, hid_usage>> usage_pairs_;

  std::vector<std::shared_ptr<monitor::service_monitor::service_monitor>> service_monitors_;
  pqrs::dispatcher::extra::timer refresh_timer_;

  std::unordered_map<io_service_t, cf_utility::cf_ptr<IOHIDDeviceRef>> devices_;
  std::unordered_map<IOHIDDeviceRef, registry_entry_id> registry_entry_ids_;

  std::vector<std::shared_ptr<human_interface_device>> hids_;
  mutable std::mutex hids_mutex_;
}; // namespace krbn
} // namespace krbn
<|endoftext|>


<commit_before>#include "plot.h"
#include "curvedata.h"
#include "signaldata.h"
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_marker.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_directpainter.h>
#include <qwt_curve_fitter.h>
#include <qwt_painter.h>
#include <qwt_scale_engine.h>
#include <qwt_scale_draw.h>
#include <qwt_plot_zoomer.h>
#include <qwt_plot_panner.h>
#include <qwt_plot_magnifier.h>
#include <qwt_text.h>

#include <qevent.h>

#include <cassert>

class MyPanner : public QObject
{
public:

  QPoint mInitialPos;
  bool mEnabled;
  int mMouseButton;
  int mKeyboardButton;
  Plot* mPlot;

  MyPanner(Plot* plot) : QObject(plot)
  {
    mEnabled = false;
    mMouseButton = Qt::LeftButton;
    mKeyboardButton = Qt::NoButton;
    mPlot = plot;
    mPlot->canvas()->installEventFilter(this);
  }

  bool eventFilter( QObject *object, QEvent *event )
  {
    switch ( event->type() )
    {
      case QEvent::MouseButtonPress:
      {
        widgetMousePressEvent( ( QMouseEvent * )event );
        break;
      }
      case QEvent::MouseMove:
      {
        widgetMouseMoveEvent( ( QMouseEvent * )event );
        break;
      }
      case QEvent::MouseButtonRelease:
      {
        widgetMouseReleaseEvent( ( QMouseEvent * )event );
        break;
      }
    }

    return false;
  }

  void moveCanvas( int dx, int dy )
  {
    if ( dx == 0 && dy == 0 )
        return;

    if (!mPlot->isStopped())
      dx = 0;

    for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
    {
      const QwtScaleMap map = mPlot->canvasMap( axis );
      const double p1 = map.transform( mPlot->axisScaleDiv( axis )->lowerBound() );
      const double p2 = map.transform( mPlot->axisScaleDiv( axis )->upperBound() );

      double d1, d2;
      if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop )
      {
        d1 = map.invTransform( p1 - dx );
        d2 = map.invTransform( p2 - dx );
      }
      else
      {
        d1 = map.invTransform( p1 - dy );
        d2 = map.invTransform( p2 - dy );
      }
      mPlot->setAxisScale( axis, d1, d2 );
    }

    mPlot->flagAxisSyncRequired();
    mPlot->replot();
  }

  void widgetMousePressEvent( QMouseEvent *mouseEvent )
  {
    if (mouseEvent->button() != mMouseButton)
    {
      return;
    }

    if ((mouseEvent->modifiers() & Qt::KeyboardModifierMask) != (mKeyboardButton & Qt::KeyboardModifierMask))
    {
      return;
    }

    mInitialPos = mouseEvent->pos();
    mEnabled = true;
  }

  void widgetMouseMoveEvent( QMouseEvent *mouseEvent )
  {
    if (!mEnabled)
      return;

    QPoint pos = mouseEvent->pos();
    if (pos != mInitialPos)
    {
      moveCanvas(pos.x() - mInitialPos.x(), pos.y() - mInitialPos.y());
      mInitialPos = mouseEvent->pos();
    }
  }

  void widgetMouseReleaseEvent( QMouseEvent *mouseEvent )
  {
    mEnabled = false;
  }

};

class MyScaleDraw : public QwtScaleDraw
{
  virtual QwtText label(double value) const
  {
    return QString::number(value, 'f', 2);
  }
};

class MyZoomer: public QwtPlotZoomer
{
public:

    Plot* mPlot;

    MyZoomer(Plot *plot) : QwtPlotZoomer(plot->canvas())
    {
      mPlot = plot;
      setTrackerMode(AlwaysOn);
    }

    virtual QwtText trackerTextF(const QPointF &pos) const
    {
      QColor bg(Qt::white);
      bg.setAlpha(200);

      QwtText text = QwtPlotZoomer::trackerTextF(pos);
      text.setBackgroundBrush( QBrush( bg ));
      return text;
    }

protected:

    virtual void rescale()
    {
      mPlot->flagAxisSyncRequired();
      QwtPlotZoomer::rescale();
    }

};

class MyMagnifier: public QwtPlotMagnifier
{
public:
    Plot* mPlot;

    MyMagnifier(Plot* plot) : QwtPlotMagnifier(plot->canvas())
    {
      mPlot = plot;
    }

protected:

    // Normally, a value < 1.0 zooms in, a value > 1.0 zooms out.
    // This function is overloaded to invert the magnification direction.
    virtual void rescale( double factor )
    {
      factor = qAbs( factor );
      factor = (1-factor) + 1;

      mPlot->flagAxisSyncRequired();
      this->QwtPlotMagnifier::rescale(factor);
    }

};

Plot::Plot(QWidget *parent):
  QwtPlot(parent),
  d_origin(0),
  d_grid(0),
  mStopped(true),
  mAxisSyncRequired(false),
  mColorMode(0),
  mTimeWindow(10.0)
{
  setAutoReplot(false);

  // The backing store is important, when working with widget
  // overlays ( f.e rubberbands for zooming ).
  // Here we don't have them and the internal
  // backing store of QWidget is good enough.

  canvas()->setPaintAttribute(QwtPlotCanvas::BackingStore, false);

#if defined(Q_WS_X11)
  // Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent
  // works on X11. This has a nice effect on the performance.

  canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true);

  // Disabling the backing store of Qt improves the performance
  // for the direct painter even more, but the canvas becomes
  // a native window of the window system, receiving paint events
  // for resize and expose operations. Those might be expensive
  // when there are many points and the backing store of
  // the canvas is disabled. So in this application
  // we better don't both backing stores.

  if ( canvas()->testPaintAttribute( QwtPlotCanvas::BackingStore ) )
  {
    canvas()->setAttribute(Qt::WA_PaintOnScreen, true);
    canvas()->setAttribute(Qt::WA_NoSystemBackground, true);
  }

#endif

  plotLayout()->setAlignCanvasToScales(true);

  setAxisAutoScale(QwtPlot::xBottom, false);
  setAxisAutoScale(QwtPlot::yLeft, false);

  setAxisTitle(QwtPlot::xBottom, "Time [s]");
  setAxisScale(QwtPlot::xBottom, 0, 10);
  setAxisScale(QwtPlot::yLeft, -4.0, 4.0);

  setAxisScaleDraw(QwtPlot::xBottom, new MyScaleDraw);

  initBackground();

  QwtPlotZoomer* zoomer = new MyZoomer(this);
  zoomer->setMousePattern(QwtEventPattern::QwtEventPattern::MouseSelect1,
      Qt::LeftButton, Qt::ShiftModifier);
  // zoomer->setMousePattern(QwtEventPattern::MouseSelect3,
  //     Qt::RightButton);

  MyPanner *panner = new MyPanner(this);

  // zoom in/out with the wheel
  mMagnifier = new MyMagnifier(this);
  mMagnifier->setMouseButton(Qt::MiddleButton);

  const QColor c(Qt::darkBlue);
  zoomer->setRubberBandPen(c);
  zoomer->setTrackerPen(c);

  this->setMinimumHeight(200);
}

Plot::~Plot()
{

}

void Plot::addSignal(SignalData* signalData, QColor color)
{
  QwtPlotCurve* d_curve = new QwtPlotCurve();
  d_curve->setStyle(QwtPlotCurve::Dots);

  QPen curvePen(color);
  curvePen.setWidth(0);

  d_curve->setPen(curvePen);

#if 1
  d_curve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
#endif
#if 1
  d_curve->setPaintAttribute(QwtPlotCurve::ClipPolygons, false);
#endif
  d_curve->setData(new CurveData(signalData));
  d_curve->attach(this);

  mSignals[signalData] = d_curve;
}

void Plot::removeSignal(SignalData* signalData)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);
  curve->detach();
  delete curve;
  mSignals.remove(signalData);
}

void Plot::setSignalVisible(SignalData* signalData, bool visible)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);

  if (visible)
  {
    curve->attach(this);
  }
  else
  {
    curve->detach();
  }

  this->replot();
}

void Plot::setSignalColor(SignalData* signalData, QColor color)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);
  curve->setPen(QPen(color));
}

void Plot::setPointSize(double pointSize)
{
  foreach (QwtPlotCurve* curve, mSignals.values())
  {
    curve->setStyle(QwtPlotCurve::Dots);
    QPen curvePen = curve->pen();
    curvePen.setWidth(pointSize);
    curve->setPen(curvePen);
  }
}

void Plot::setBackgroundColor(QString color)
{
  if (color == "White")
  {
    mColorMode = 0;
  }
  else if (color == "Black")
  {
    mColorMode = 1;
  }

  this->initBackground();
}

void Plot::initBackground()
{
  if (!d_grid)
  {
    d_grid = new QwtPlotGrid();
    d_grid->enableX(false);
    d_grid->enableXMin(false);
    d_grid->enableY(true);
    d_grid->enableYMin(false);
    d_grid->attach(this);
  }

  if (!d_origin)
  {
    d_origin = new QwtPlotMarker();
    d_origin->setLineStyle(QwtPlotMarker::HLine);
    d_origin->setValue(0.0, 0.0);
    d_origin->attach(this);
  }

  QColor backgroundColor = Qt::white;
  QColor gridColor = Qt::gray;
  if (mColorMode == 1)
  {
    backgroundColor = Qt::black;
    gridColor = QColor(100, 100, 100);
  }

  QPalette pal = canvas()->palette();
  pal.setBrush(QPalette::Window, QBrush(backgroundColor));
  canvas()->setPalette(pal);

  d_grid->setPen(QPen(gridColor, 0.0, Qt::DotLine));
  d_origin->setLinePen(QPen(gridColor, 0.0, Qt::DashLine));
}

void Plot::start()
{
  mStopped = false;
  mMagnifier->setAxisEnabled(QwtPlot::xBottom, mStopped);
}

void Plot::stop()
{
  mStopped = true;
  mMagnifier->setAxisEnabled(QwtPlot::xBottom, mStopped);
}

bool Plot::isStopped()
{
  return mStopped;
}

void Plot::replot()
{
  this->updateTicks();

  // Lock the signal data objects, then plot the data and unlock.
  QList<SignalData*> signalDataList = mSignals.keys();
  foreach (SignalData* signalData, signalDataList)
  {
    signalData->lock();
  }

  QwtPlot::replot();

  foreach (SignalData* signalData, signalDataList)
  {
    signalData->unlock();
  }
}

void Plot::flagAxisSyncRequired()
{
  mAxisSyncRequired = true;
}

double Plot::timeWindow()
{
  return mTimeWindow;
}

void Plot::setTimeWindow(double interval)
{
  if ( interval > 0.0 && interval != mTimeWindow)
  {
    mTimeWindow = interval;
    QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);
    xinterval.setMinValue(xinterval.maxValue() - interval);
    this->setAxisScale(QwtPlot::xBottom, xinterval.minValue(), xinterval.maxValue());
    this->replot();
  }
}

void Plot::setYScale(double scale)
{
  setAxisScale(QwtPlot::yLeft, -scale, scale);
  this->replot();
}

void Plot::setEndTime(double endTime)
{
  QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);
  if (xinterval.maxValue() == endTime)
  {
    return;
  }

  xinterval = QwtInterval(endTime - xinterval.width(), endTime);
  this->setAxisScale(QwtPlot::xBottom, xinterval.minValue(), xinterval.maxValue());
}

void Plot::updateTicks()
{
  this->updateAxes();
  QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);

  // when playing, always use the requested time window
  // when paused, the user may zoom in/out, changing the time window
  if (!this->isStopped())
  {
    xinterval.setMinValue(xinterval.maxValue() - mTimeWindow);
  }

  QwtScaleEngine* engine = axisScaleEngine(QwtPlot::xBottom);
  QwtScaleDiv scaleDiv = engine->divideScale(xinterval.minValue(), xinterval.maxValue(), 0, 0);

  QList<double> majorTicks;
  double majorStep = scaleDiv.range() / 5.0;
  for (int i = 0; i <= 5; ++i)
  {
    majorTicks << scaleDiv.lowerBound() + i*majorStep;
  }
  majorTicks.back() = scaleDiv.upperBound();

  QList<double> minorTicks;
  double minorStep = scaleDiv.range() / 25.0;
  for (int i = 0; i <= 25; ++i)
  {
    minorTicks << scaleDiv.lowerBound() + i*minorStep;
  }
  minorTicks.back() = scaleDiv.upperBound();

  scaleDiv.setTicks(QwtScaleDiv::MajorTick, majorTicks);
  scaleDiv.setTicks(QwtScaleDiv::MinorTick, minorTicks);
  setAxisScaleDiv(QwtPlot::xBottom, scaleDiv);

  if (mAxisSyncRequired)
  {
    emit this->syncXAxisScale(xinterval.minValue(), xinterval.maxValue());
    mAxisSyncRequired = false;
  }
}
<commit_msg>signal scope: disable zoomer middle mouse click, it was clobbering the xaxis scale<commit_after>#include "plot.h"
#include "curvedata.h"
#include "signaldata.h"
#include <qwt_plot_grid.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_marker.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_directpainter.h>
#include <qwt_curve_fitter.h>
#include <qwt_painter.h>
#include <qwt_scale_engine.h>
#include <qwt_scale_draw.h>
#include <qwt_plot_zoomer.h>
#include <qwt_plot_panner.h>
#include <qwt_plot_magnifier.h>
#include <qwt_text.h>

#include <qevent.h>

#include <cassert>

class MyPanner : public QObject
{
public:

  QPoint mInitialPos;
  bool mEnabled;
  int mMouseButton;
  int mKeyboardButton;
  Plot* mPlot;

  MyPanner(Plot* plot) : QObject(plot)
  {
    mEnabled = false;
    mMouseButton = Qt::LeftButton;
    mKeyboardButton = Qt::NoButton;
    mPlot = plot;
    mPlot->canvas()->installEventFilter(this);
  }

  bool eventFilter( QObject *object, QEvent *event )
  {
    switch ( event->type() )
    {
      case QEvent::MouseButtonPress:
      {
        widgetMousePressEvent( ( QMouseEvent * )event );
        break;
      }
      case QEvent::MouseMove:
      {
        widgetMouseMoveEvent( ( QMouseEvent * )event );
        break;
      }
      case QEvent::MouseButtonRelease:
      {
        widgetMouseReleaseEvent( ( QMouseEvent * )event );
        break;
      }
    }

    return false;
  }

  void moveCanvas( int dx, int dy )
  {
    if ( dx == 0 && dy == 0 )
        return;

    if (!mPlot->isStopped())
      dx = 0;

    for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
    {
      const QwtScaleMap map = mPlot->canvasMap( axis );
      const double p1 = map.transform( mPlot->axisScaleDiv( axis )->lowerBound() );
      const double p2 = map.transform( mPlot->axisScaleDiv( axis )->upperBound() );

      double d1, d2;
      if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop )
      {
        d1 = map.invTransform( p1 - dx );
        d2 = map.invTransform( p2 - dx );
      }
      else
      {
        d1 = map.invTransform( p1 - dy );
        d2 = map.invTransform( p2 - dy );
      }
      mPlot->setAxisScale( axis, d1, d2 );
    }

    mPlot->flagAxisSyncRequired();
    mPlot->replot();
  }

  void widgetMousePressEvent( QMouseEvent *mouseEvent )
  {
    if (mouseEvent->button() != mMouseButton)
    {
      return;
    }

    if ((mouseEvent->modifiers() & Qt::KeyboardModifierMask) != (mKeyboardButton & Qt::KeyboardModifierMask))
    {
      return;
    }

    mInitialPos = mouseEvent->pos();
    mEnabled = true;
  }

  void widgetMouseMoveEvent( QMouseEvent *mouseEvent )
  {
    if (!mEnabled)
      return;

    QPoint pos = mouseEvent->pos();
    if (pos != mInitialPos)
    {
      moveCanvas(pos.x() - mInitialPos.x(), pos.y() - mInitialPos.y());
      mInitialPos = mouseEvent->pos();
    }
  }

  void widgetMouseReleaseEvent( QMouseEvent *mouseEvent )
  {
    mEnabled = false;
  }

};

class MyScaleDraw : public QwtScaleDraw
{
  virtual QwtText label(double value) const
  {
    return QString::number(value, 'f', 2);
  }
};

class MyZoomer: public QwtPlotZoomer
{
public:

    Plot* mPlot;

    MyZoomer(Plot *plot) : QwtPlotZoomer(plot->canvas())
    {
      mPlot = plot;
      setTrackerMode(AlwaysOn);
    }

    virtual QwtText trackerTextF(const QPointF &pos) const
    {
      QColor bg(Qt::white);
      bg.setAlpha(200);

      QwtText text = QwtPlotZoomer::trackerTextF(pos);
      text.setBackgroundBrush( QBrush( bg ));
      return text;
    }

protected:

    virtual void rescale()
    {
      mPlot->flagAxisSyncRequired();
      QwtPlotZoomer::rescale();
    }

};

class MyMagnifier: public QwtPlotMagnifier
{
public:
    Plot* mPlot;

    MyMagnifier(Plot* plot) : QwtPlotMagnifier(plot->canvas())
    {
      mPlot = plot;
    }

protected:

    // Normally, a value < 1.0 zooms in, a value > 1.0 zooms out.
    // This function is overloaded to invert the magnification direction.
    virtual void rescale( double factor )
    {
      factor = qAbs( factor );
      factor = (1-factor) + 1;

      mPlot->flagAxisSyncRequired();
      this->QwtPlotMagnifier::rescale(factor);
    }

};

Plot::Plot(QWidget *parent):
  QwtPlot(parent),
  d_origin(0),
  d_grid(0),
  mStopped(true),
  mAxisSyncRequired(false),
  mColorMode(0),
  mTimeWindow(10.0)
{
  setAutoReplot(false);

  // The backing store is important, when working with widget
  // overlays ( f.e rubberbands for zooming ).
  // Here we don't have them and the internal
  // backing store of QWidget is good enough.

  canvas()->setPaintAttribute(QwtPlotCanvas::BackingStore, false);

#if defined(Q_WS_X11)
  // Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent
  // works on X11. This has a nice effect on the performance.

  canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true);

  // Disabling the backing store of Qt improves the performance
  // for the direct painter even more, but the canvas becomes
  // a native window of the window system, receiving paint events
  // for resize and expose operations. Those might be expensive
  // when there are many points and the backing store of
  // the canvas is disabled. So in this application
  // we better don't both backing stores.

  if ( canvas()->testPaintAttribute( QwtPlotCanvas::BackingStore ) )
  {
    canvas()->setAttribute(Qt::WA_PaintOnScreen, true);
    canvas()->setAttribute(Qt::WA_NoSystemBackground, true);
  }

#endif

  plotLayout()->setAlignCanvasToScales(true);

  setAxisAutoScale(QwtPlot::xBottom, false);
  setAxisAutoScale(QwtPlot::yLeft, false);

  setAxisTitle(QwtPlot::xBottom, "Time [s]");
  setAxisScale(QwtPlot::xBottom, 0, 10);
  setAxisScale(QwtPlot::yLeft, -4.0, 4.0);

  setAxisScaleDraw(QwtPlot::xBottom, new MyScaleDraw);

  initBackground();

  QwtPlotZoomer* zoomer = new MyZoomer(this);
  zoomer->setMousePattern(QwtEventPattern::QwtEventPattern::MouseSelect1,
      Qt::LeftButton, Qt::ShiftModifier);
  // disable MouseSelect3 action of the zoomer
  zoomer->setMousePattern(QwtEventPattern::MouseSelect3, 0);

  MyPanner *panner = new MyPanner(this);

  // zoom in/out with the wheel
  mMagnifier = new MyMagnifier(this);
  mMagnifier->setMouseButton(Qt::MiddleButton);

  const QColor c(Qt::darkBlue);
  zoomer->setRubberBandPen(c);
  zoomer->setTrackerPen(c);

  this->setMinimumHeight(200);
}

Plot::~Plot()
{

}

void Plot::addSignal(SignalData* signalData, QColor color)
{
  QwtPlotCurve* d_curve = new QwtPlotCurve();
  d_curve->setStyle(QwtPlotCurve::Dots);

  QPen curvePen(color);
  curvePen.setWidth(0);

  d_curve->setPen(curvePen);

#if 1
  d_curve->setRenderHint(QwtPlotItem::RenderAntialiased, true);
#endif
#if 1
  d_curve->setPaintAttribute(QwtPlotCurve::ClipPolygons, false);
#endif
  d_curve->setData(new CurveData(signalData));
  d_curve->attach(this);

  mSignals[signalData] = d_curve;
}

void Plot::removeSignal(SignalData* signalData)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);
  curve->detach();
  delete curve;
  mSignals.remove(signalData);
}

void Plot::setSignalVisible(SignalData* signalData, bool visible)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);

  if (visible)
  {
    curve->attach(this);
  }
  else
  {
    curve->detach();
  }

  this->replot();
}

void Plot::setSignalColor(SignalData* signalData, QColor color)
{
  if (!signalData)
  {
    return;
  }

  QwtPlotCurve* curve = mSignals.value(signalData);
  assert(curve);
  curve->setPen(QPen(color));
}

void Plot::setPointSize(double pointSize)
{
  foreach (QwtPlotCurve* curve, mSignals.values())
  {
    curve->setStyle(QwtPlotCurve::Dots);
    QPen curvePen = curve->pen();
    curvePen.setWidth(pointSize);
    curve->setPen(curvePen);
  }
}

void Plot::setBackgroundColor(QString color)
{
  if (color == "White")
  {
    mColorMode = 0;
  }
  else if (color == "Black")
  {
    mColorMode = 1;
  }

  this->initBackground();
}

void Plot::initBackground()
{
  if (!d_grid)
  {
    d_grid = new QwtPlotGrid();
    d_grid->enableX(false);
    d_grid->enableXMin(false);
    d_grid->enableY(true);
    d_grid->enableYMin(false);
    d_grid->attach(this);
  }

  if (!d_origin)
  {
    d_origin = new QwtPlotMarker();
    d_origin->setLineStyle(QwtPlotMarker::HLine);
    d_origin->setValue(0.0, 0.0);
    d_origin->attach(this);
  }

  QColor backgroundColor = Qt::white;
  QColor gridColor = Qt::gray;
  if (mColorMode == 1)
  {
    backgroundColor = Qt::black;
    gridColor = QColor(100, 100, 100);
  }

  QPalette pal = canvas()->palette();
  pal.setBrush(QPalette::Window, QBrush(backgroundColor));
  canvas()->setPalette(pal);

  d_grid->setPen(QPen(gridColor, 0.0, Qt::DotLine));
  d_origin->setLinePen(QPen(gridColor, 0.0, Qt::DashLine));
}

void Plot::start()
{
  mStopped = false;
  mMagnifier->setAxisEnabled(QwtPlot::xBottom, mStopped);
}

void Plot::stop()
{
  mStopped = true;
  mMagnifier->setAxisEnabled(QwtPlot::xBottom, mStopped);
}

bool Plot::isStopped()
{
  return mStopped;
}

void Plot::replot()
{
  this->updateTicks();

  // Lock the signal data objects, then plot the data and unlock.
  QList<SignalData*> signalDataList = mSignals.keys();
  foreach (SignalData* signalData, signalDataList)
  {
    signalData->lock();
  }

  QwtPlot::replot();

  foreach (SignalData* signalData, signalDataList)
  {
    signalData->unlock();
  }
}

void Plot::flagAxisSyncRequired()
{
  mAxisSyncRequired = true;
}

double Plot::timeWindow()
{
  return mTimeWindow;
}

void Plot::setTimeWindow(double interval)
{
  if ( interval > 0.0 && interval != mTimeWindow)
  {
    mTimeWindow = interval;
    QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);
    xinterval.setMinValue(xinterval.maxValue() - interval);
    this->setAxisScale(QwtPlot::xBottom, xinterval.minValue(), xinterval.maxValue());
    this->replot();
  }
}

void Plot::setYScale(double scale)
{
  setAxisScale(QwtPlot::yLeft, -scale, scale);
  this->replot();
}

void Plot::setEndTime(double endTime)
{
  QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);
  if (xinterval.maxValue() == endTime)
  {
    return;
  }

  xinterval = QwtInterval(endTime - xinterval.width(), endTime);
  this->setAxisScale(QwtPlot::xBottom, xinterval.minValue(), xinterval.maxValue());
}

void Plot::updateTicks()
{
  this->updateAxes();
  QwtInterval xinterval = this->axisInterval(QwtPlot::xBottom);

  // when playing, always use the requested time window
  // when paused, the user may zoom in/out, changing the time window
  if (!this->isStopped())
  {
    xinterval.setMinValue(xinterval.maxValue() - mTimeWindow);
  }

  QwtScaleEngine* engine = axisScaleEngine(QwtPlot::xBottom);
  QwtScaleDiv scaleDiv = engine->divideScale(xinterval.minValue(), xinterval.maxValue(), 0, 0);

  QList<double> majorTicks;
  double majorStep = scaleDiv.range() / 5.0;
  for (int i = 0; i <= 5; ++i)
  {
    majorTicks << scaleDiv.lowerBound() + i*majorStep;
  }
  majorTicks.back() = scaleDiv.upperBound();

  QList<double> minorTicks;
  double minorStep = scaleDiv.range() / 25.0;
  for (int i = 0; i <= 25; ++i)
  {
    minorTicks << scaleDiv.lowerBound() + i*minorStep;
  }
  minorTicks.back() = scaleDiv.upperBound();

  scaleDiv.setTicks(QwtScaleDiv::MajorTick, majorTicks);
  scaleDiv.setTicks(QwtScaleDiv::MinorTick, minorTicks);
  setAxisScaleDiv(QwtPlot::xBottom, scaleDiv);

  if (mAxisSyncRequired)
  {
    emit this->syncXAxisScale(xinterval.minValue(), xinterval.maxValue());
    mAxisSyncRequired = false;
  }
}
<|endoftext|>


<commit_before>#include "Toasts.hpp"

#include "Application.hpp"
#include "common/DownloadManager.hpp"
#include "common/NetworkRequest.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "singletons/Paths.hpp"
#include "util/StreamLink.hpp"
#include "widgets/helper/CommonTexts.hpp"

#ifdef Q_OS_WIN

#    include <wintoastlib.h>

#endif

#include <QDesktopServices>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>

#include <cstdlib>

namespace chatterino {

std::map<ToastReaction, QString> Toasts::reactionToString = {
    {ToastReaction::OpenInBrowser, OPEN_IN_BROWSER},
    {ToastReaction::OpenInPlayer, OPEN_PLAYER_IN_BROWSER},
    {ToastReaction::OpenInStreamlink, OPEN_IN_STREAMLINK},
    {ToastReaction::DontOpen, DONT_OPEN}};

bool Toasts::isEnabled()
{
#ifdef Q_OS_WIN
    return WinToastLib::WinToast::isCompatible() &&
           getSettings()->notificationToast &&
           !(isInStreamerMode() &&
             getSettings()->streamerModeSuppressLiveNotifications);
#else
    return false;
#endif
}

QString Toasts::findStringFromReaction(const ToastReaction &reaction)
{
    auto iterator = Toasts::reactionToString.find(reaction);
    if (iterator != Toasts::reactionToString.end())
    {
        return iterator->second;
    }
    else
    {
        return DONT_OPEN;
    }
}

QString Toasts::findStringFromReaction(
    const pajlada::Settings::Setting<int> &value)
{
    int i = static_cast<int>(value);
    return Toasts::findStringFromReaction(static_cast<ToastReaction>(i));
}

void Toasts::sendChannelNotification(const QString &channelName, Platform p)
{
#ifdef Q_OS_WIN
    auto sendChannelNotification = [this, channelName, p] {
        this->sendWindowsNotification(channelName, p);
    };
#else
    auto sendChannelNotification = [] {
        // Unimplemented for OSX and Linux
    };
#endif
    // Fetch user profile avatar
    if (p == Platform::Twitch)
    {
        QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" +
                             channelName + ".png");
        if (check_file.exists() && check_file.isFile())
        {
            sendChannelNotification();
        }
        else
        {
            getHelix()->getUserByName(
                channelName,
                [channelName, sendChannelNotification](const auto &user) {
                    DownloadManager *manager = new DownloadManager();
                    manager->setFile(user.profileImageUrl, channelName);
                    manager->connect(manager,
                                     &DownloadManager::downloadComplete,
                                     sendChannelNotification);
                },
                [] {
                    // on failure
                });
        }
    }
}

#ifdef Q_OS_WIN

class CustomHandler : public WinToastLib::IWinToastHandler
{
private:
    QString channelName_;
    Platform platform_;

public:
    CustomHandler(QString channelName, Platform p)
        : channelName_(channelName)
        , platform_(p)
    {
    }
    void toastActivated() const
    {
        QString link;
        auto toastReaction =
            static_cast<ToastReaction>(getSettings()->openFromToast.getValue());

        switch (toastReaction)
        {
            case ToastReaction::OpenInBrowser:
                if (platform_ == Platform::Twitch)
                {
                    link = "http://www.twitch.tv/" + channelName_;
                }
                QDesktopServices::openUrl(QUrl(link));
                break;
            case ToastReaction::OpenInPlayer:
                if (platform_ == Platform::Twitch)
                {
                    link =
                        "https://player.twitch.tv/?parent=twitch.tv&channel=" +
                        channelName_;
                }
                QDesktopServices::openUrl(QUrl(link));
                break;
            case ToastReaction::OpenInStreamlink: {
                openStreamlinkForChannel(channelName_);
                break;
            }
                // the fourth and last option is "don't open"
                // in this case obviously nothing should happen
        }
    }

    void toastActivated(int actionIndex) const
    {
    }

    void toastFailed() const
    {
    }

    void toastDismissed(WinToastDismissalReason state) const
    {
    }
};

void Toasts::sendWindowsNotification(const QString &channelName, Platform p)
{
    WinToastLib::WinToastTemplate templ = WinToastLib::WinToastTemplate(
        WinToastLib::WinToastTemplate::ImageAndText03);
    QString str = channelName + " is live!";
    std::string utf8_text = str.toUtf8().constData();
    std::wstring widestr = std::wstring(utf8_text.begin(), utf8_text.end());

    templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
    if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
        ToastReaction::DontOpen)
    {
        QString mode =
            Toasts::findStringFromReaction(getSettings()->openFromToast);
        mode = mode.toLower();

        templ.setTextField(L"Click here to " + mode.toStdWString(),
                           WinToastLib::WinToastTemplate::SecondLine);
    }

    QString Path;
    if (p == Platform::Twitch)
    {
        Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName +
               ".png";
    }
    std::string temp_Utf8 = Path.toUtf8().constData();
    std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end());
    templ.setImagePath(imagePath);
    if (getSettings()->notificationPlaySound)
    {
        templ.setAudioOption(
            WinToastLib::WinToastTemplate::AudioOption::Silent);
    }
    WinToastLib::WinToast::instance()->setAppName(L"Chatterino2");
    int mbstowcs(wchar_t * aumi_version, const char *CHATTERINO_VERSION,
                 size_t size);
    std::string(CHATTERINO_VERSION);
    std::wstring aumi_version =
        std::wstring(CHATTERINO_VERSION.begin(), CHATTERINO_VERSION.end());
    WinToastLib::WinToast::instance()->setAppUserModelId(
        WinToastLib::WinToast::configureAUMI(L"", L"Chatterino 2", L"",
                                             aumi_version));
    WinToastLib::WinToast::instance()->setShortcutPolicy(
        WinToastLib::WinToast::SHORTCUT_POLICY_IGNORE);
    WinToastLib::WinToast::instance()->initialize();
    WinToastLib::WinToast::instance()->showToast(
        templ, new CustomHandler(channelName, p));
}

#endif

}  // namespace chatterino
<commit_msg>Fix windows toast notifications opening as http (#4005)<commit_after>#include "Toasts.hpp"

#include "Application.hpp"
#include "common/DownloadManager.hpp"
#include "common/NetworkRequest.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "singletons/Paths.hpp"
#include "util/StreamLink.hpp"
#include "widgets/helper/CommonTexts.hpp"

#ifdef Q_OS_WIN

#    include <wintoastlib.h>

#endif

#include <QDesktopServices>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>

#include <cstdlib>

namespace chatterino {

std::map<ToastReaction, QString> Toasts::reactionToString = {
    {ToastReaction::OpenInBrowser, OPEN_IN_BROWSER},
    {ToastReaction::OpenInPlayer, OPEN_PLAYER_IN_BROWSER},
    {ToastReaction::OpenInStreamlink, OPEN_IN_STREAMLINK},
    {ToastReaction::DontOpen, DONT_OPEN}};

bool Toasts::isEnabled()
{
#ifdef Q_OS_WIN
    return WinToastLib::WinToast::isCompatible() &&
           getSettings()->notificationToast &&
           !(isInStreamerMode() &&
             getSettings()->streamerModeSuppressLiveNotifications);
#else
    return false;
#endif
}

QString Toasts::findStringFromReaction(const ToastReaction &reaction)
{
    auto iterator = Toasts::reactionToString.find(reaction);
    if (iterator != Toasts::reactionToString.end())
    {
        return iterator->second;
    }
    else
    {
        return DONT_OPEN;
    }
}

QString Toasts::findStringFromReaction(
    const pajlada::Settings::Setting<int> &value)
{
    int i = static_cast<int>(value);
    return Toasts::findStringFromReaction(static_cast<ToastReaction>(i));
}

void Toasts::sendChannelNotification(const QString &channelName, Platform p)
{
#ifdef Q_OS_WIN
    auto sendChannelNotification = [this, channelName, p] {
        this->sendWindowsNotification(channelName, p);
    };
#else
    auto sendChannelNotification = [] {
        // Unimplemented for OSX and Linux
    };
#endif
    // Fetch user profile avatar
    if (p == Platform::Twitch)
    {
        QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" +
                             channelName + ".png");
        if (check_file.exists() && check_file.isFile())
        {
            sendChannelNotification();
        }
        else
        {
            getHelix()->getUserByName(
                channelName,
                [channelName, sendChannelNotification](const auto &user) {
                    DownloadManager *manager = new DownloadManager();
                    manager->setFile(user.profileImageUrl, channelName);
                    manager->connect(manager,
                                     &DownloadManager::downloadComplete,
                                     sendChannelNotification);
                },
                [] {
                    // on failure
                });
        }
    }
}

#ifdef Q_OS_WIN

class CustomHandler : public WinToastLib::IWinToastHandler
{
private:
    QString channelName_;
    Platform platform_;

public:
    CustomHandler(QString channelName, Platform p)
        : channelName_(channelName)
        , platform_(p)
    {
    }
    void toastActivated() const
    {
        QString link;
        auto toastReaction =
            static_cast<ToastReaction>(getSettings()->openFromToast.getValue());

        switch (toastReaction)
        {
            case ToastReaction::OpenInBrowser:
                if (platform_ == Platform::Twitch)
                {
                    link = "https://www.twitch.tv/" + channelName_;
                }
                QDesktopServices::openUrl(QUrl(link));
                break;
            case ToastReaction::OpenInPlayer:
                if (platform_ == Platform::Twitch)
                {
                    link =
                        "https://player.twitch.tv/?parent=twitch.tv&channel=" +
                        channelName_;
                }
                QDesktopServices::openUrl(QUrl(link));
                break;
            case ToastReaction::OpenInStreamlink: {
                openStreamlinkForChannel(channelName_);
                break;
            }
                // the fourth and last option is "don't open"
                // in this case obviously nothing should happen
        }
    }

    void toastActivated(int actionIndex) const
    {
    }

    void toastFailed() const
    {
    }

    void toastDismissed(WinToastDismissalReason state) const
    {
    }
};

void Toasts::sendWindowsNotification(const QString &channelName, Platform p)
{
    WinToastLib::WinToastTemplate templ = WinToastLib::WinToastTemplate(
        WinToastLib::WinToastTemplate::ImageAndText03);
    QString str = channelName + " is live!";
    std::string utf8_text = str.toUtf8().constData();
    std::wstring widestr = std::wstring(utf8_text.begin(), utf8_text.end());

    templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
    if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
        ToastReaction::DontOpen)
    {
        QString mode =
            Toasts::findStringFromReaction(getSettings()->openFromToast);
        mode = mode.toLower();

        templ.setTextField(L"Click here to " + mode.toStdWString(),
                           WinToastLib::WinToastTemplate::SecondLine);
    }

    QString Path;
    if (p == Platform::Twitch)
    {
        Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName +
               ".png";
    }
    std::string temp_Utf8 = Path.toUtf8().constData();
    std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end());
    templ.setImagePath(imagePath);
    if (getSettings()->notificationPlaySound)
    {
        templ.setAudioOption(
            WinToastLib::WinToastTemplate::AudioOption::Silent);
    }
    WinToastLib::WinToast::instance()->setAppName(L"Chatterino2");
    int mbstowcs(wchar_t * aumi_version, const char *CHATTERINO_VERSION,
                 size_t size);
    std::string(CHATTERINO_VERSION);
    std::wstring aumi_version =
        std::wstring(CHATTERINO_VERSION.begin(), CHATTERINO_VERSION.end());
    WinToastLib::WinToast::instance()->setAppUserModelId(
        WinToastLib::WinToast::configureAUMI(L"", L"Chatterino 2", L"",
                                             aumi_version));
    WinToastLib::WinToast::instance()->setShortcutPolicy(
        WinToastLib::WinToast::SHORTCUT_POLICY_IGNORE);
    WinToastLib::WinToast::instance()->initialize();
    WinToastLib::WinToast::instance()->showToast(
        templ, new CustomHandler(channelName, p));
}

#endif

}  // namespace chatterino
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////////
//
//  File:  $Source: /usr/sci/projects/Nektar/cvs/Nektar++/libs/SpatialDomains/EdgeComponent.cpp,v $
//
//  For more information, please see: http://www.nektar.info/
//
//  The MIT License
//
//  Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
//  Department of Aeronautics, Imperial College London (UK), and Scientific
//  Computing and Imaging Institute, University of Utah (USA).
//
//  License for the specific language governing rights and limitations under
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included
//  in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//  Description:
//
//
////////////////////////////////////////////////////////////////////////////////
#include "pchSpatialDomains.h"

#include <SpatialDomains/EdgeComponent.h>

namespace Nektar
{
    namespace SpatialDomains
    {
        EdgeComponent::EdgeComponent()
        {
        }

        EdgeComponent::EdgeComponent(int id, const int coordim):
        Geometry1D(coordim)
        {

            const StdRegions::BasisKey B(StdRegions::eModified_A, 2,
                StdRegions::eLobatto, 3,0,0);
            m_eid = id;

            m_xmap = new StdRegions::StdSegExp * [m_coordim];

            for(int i = 0; i < m_coordim; ++i)
            {
                m_xmap[i] = NULL;// new StdRegions::StdSegExp(B);
            }
        }

        EdgeComponent::EdgeComponent(int id, const int coordim,
            VertexComponentSharedPtr vertex[]):
        Geometry1D(coordim),
        m_vertex(2) //always have two vertices per edge
        {
            m_eid = id;

            if (coordim > 0)
            {
                const StdRegions::BasisKey B(StdRegions::eModified_A, 2,
                    StdRegions::eLobatto, 3,0,0);

                m_xmap = new StdRegions::StdSegExp * [m_coordim];

                for(int i = 0; i < m_coordim; ++i)
                {
                    m_xmap[i] = NULL; //new StdRegions::StdSegExp(B);
                }
            }

            m_vertex[0] = vertex[0];
            m_vertex[1] = vertex[1];
        }

        EdgeComponent::EdgeComponent(int id, const int coordim,
            const int order, const int nquad):
        Geometry1D(coordim)
        {

            const StdRegions::BasisKey B(StdRegions::eModified_A, order,
                StdRegions::eLobatto, nquad,0,0);

            m_xmap = new StdRegions::StdSegExp * [m_coordim];

            for(int i = 0; i < m_coordim; ++i)
            {
                m_xmap[i] = new StdRegions::StdSegExp(B);
            }

            m_state = eNotFilled;
        }

        EdgeComponent::~EdgeComponent()
        {
            if(m_xmap[0])
            {
                for(int i =0; i < m_coordim; ++i)
                {
                    //delete m_xmap[i];
                }

                delete[] m_xmap;

                m_xmap[0] = (StdRegions::StdSegExp *) NULL;
            }
        }

        EdgeComponent::EdgeComponent(const EdgeComponent &T)
        {

            m_eid = T.m_eid;

            std::list<CompToElmt>::const_iterator def;
            for(def = T.m_elmtmap.begin(); def != T.m_elmtmap.end(); def++)
            {
                m_elmtmap.push_back(*def);
            }
        }

        /** given local collapsed coordinate Lcoord return the value of
        physical coordinate in direction i **/

        double EdgeComponent::GetCoord(const int i, const double *Lcoord)
        {

            ASSERTL1(m_state == ePtsFilled, "Goemetry is not in physical space");

            return m_xmap[i]->Evaluate(Lcoord);
        }

        void EdgeComponent::AddElmtConnected(int gvo_id, int locid)
        {
            CompToElmt ee(gvo_id,locid);
            m_elmtmap.push_back(ee);
        }

        int EdgeComponent::NumElmtConnected() const
        {
            return int(m_elmtmap.size());
        }

        bool EdgeComponent::IsElmtConnected(int gvo_id, int locid) const
        {
            std::list<CompToElmt>::const_iterator def;
            CompToElmt ee(gvo_id,locid);

            def = find(m_elmtmap.begin(),m_elmtmap.end(),ee);

            // Found the element connectivity object in the list
            if(def != m_elmtmap.end())
            {
                return(true);
            }

            return(false);
        }

        /// \brief Get the orientation of edge1.
        ///
        /// If edge1 is connected to edge2 in the same direction
        /// as the points comprising edge1 then it is forward, otherwise it is backward.  For example, assume
        /// edge1 is comprised of points 1 and 2, and edge2 is comprised of points 2 and 3, then edge
        /// is forward.  If edge1 is comprised of points 2 and 1 and edge2 is comprised of points
        /// 3 and 2, then edge1 is backward.

        StdRegions::EdgeOrientation EdgeComponent::GetEdgeOrientation(const EdgeComponent &edge1, const EdgeComponent &edge2)
        {
            StdRegions::EdgeOrientation returnval = StdRegions::eForwards;

            /// Backward direction.  Vertex 0 is connected to edge 2.
            if ((*edge1.GetVertex(0) == *edge2.GetVertex(0)) || (*edge1.GetVertex(0) == *edge2.GetVertex(1)))
            {
                returnval = StdRegions::eBackwards;
            }
            // Not forward either, then we have a problem.
            else if ((*edge1.GetVertex(1) != *edge2.GetVertex(0)) && (*edge1.GetVertex(1) != *edge2.GetVertex(1)))
            {
                std::ostrstream errstrm;
                errstrm << "Connected edges do not share a vertex. Edges ";
                errstrm << edge1.GetEid() << ", " << edge2.GetEid();
                ASSERTL0(false, errstrm.str());
            }

            return returnval;
        }
    }; //end of namespace
}; //end of namespace

/**
*    $Log: EdgeComponent.cpp,v $
*    Revision 1.4  2006/05/16 20:12:59  jfrazier
*    Minor fixes to correct bugs.
*
*    Revision 1.3  2006/05/09 13:37:01  jfrazier
*    Removed duplicate definition of shared vertex pointer.
*
*    Revision 1.2  2006/05/06 20:36:16  sherwin
*    Modifications to get LocalRegions/Project1D working
*
*    Revision 1.1  2006/05/04 18:58:59  kirby
*    *** empty log message ***
*
*    Revision 1.14  2006/04/11 23:18:11  jfrazier
*    Completed MeshGraph2D for tri's and quads.  Not thoroughly tested.
*
*    Revision 1.13  2006/04/09 02:08:34  jfrazier
*    Added precompiled header.
*
*    Revision 1.12  2006/04/04 23:12:37  jfrazier
*    More updates to readers.  Still work to do on MeshGraph2D to store tris and quads.
*
*    Revision 1.11  2006/03/25 00:58:28  jfrazier
*    Many changes dealing with fundamental structure and reading/writing.
*
*    Revision 1.10  2006/03/12 14:20:42  sherwin
*
*    First compiling version of SpatialDomains and associated modifications
*
*    Revision 1.9  2006/03/12 11:06:39  sherwin
*
*    First complete copy of code standard code but still not compile tested
*
*    Revision 1.8  2006/03/12 07:42:02  sherwin
*
*    Updated member names and StdRegions call. Still has not been compiled
*
**/
<commit_msg>Added #pragma to show where problems currently exist.<commit_after>////////////////////////////////////////////////////////////////////////////////
//
//  File:  $Source: /usr/sci/projects/Nektar/cvs/Nektar++/libs/SpatialDomains/EdgeComponent.cpp,v $
//
//  For more information, please see: http://www.nektar.info/
//
//  The MIT License
//
//  Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
//  Department of Aeronautics, Imperial College London (UK), and Scientific
//  Computing and Imaging Institute, University of Utah (USA).
//
//  License for the specific language governing rights and limitations under
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included
//  in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//  Description:
//
//
////////////////////////////////////////////////////////////////////////////////
#include "pchSpatialDomains.h"

#include <SpatialDomains/EdgeComponent.h>

namespace Nektar
{
    namespace SpatialDomains
    {
        EdgeComponent::EdgeComponent()
        {
        }

        EdgeComponent::EdgeComponent(int id, const int coordim):
        Geometry1D(coordim)
        {

            const StdRegions::BasisKey B(StdRegions::eModified_A, 2,
                StdRegions::eLobatto, 3,0,0);
            m_eid = id;

            m_xmap = new StdRegions::StdSegExp * [m_coordim];

            for(int i = 0; i < m_coordim; ++i)
            {
                m_xmap[i] = NULL;// new StdRegions::StdSegExp(B);
            }
        }

        EdgeComponent::EdgeComponent(int id, const int coordim,
            VertexComponentSharedPtr vertex[]):
        Geometry1D(coordim),
        m_vertex(2) //always have two vertices per edge
        {
            m_eid = id;

            if (coordim > 0)
            {
                const StdRegions::BasisKey B(StdRegions::eModified_A, 2,
                    StdRegions::eLobatto, 3,0,0);

                m_xmap = new StdRegions::StdSegExp * [m_coordim];

                for(int i = 0; i < m_coordim; ++i)
                {
#pragma message("Fix this! " __FILE__)
                    m_xmap[i] = NULL; //new StdRegions::StdSegExp(B);
                }
            }

            m_vertex[0] = vertex[0];
            m_vertex[1] = vertex[1];
        }

        EdgeComponent::EdgeComponent(int id, const int coordim,
            const int order, const int nquad):
        Geometry1D(coordim)
        {

            const StdRegions::BasisKey B(StdRegions::eModified_A, order,
                StdRegions::eLobatto, nquad,0,0);

            m_xmap = new StdRegions::StdSegExp * [m_coordim];

            for(int i = 0; i < m_coordim; ++i)
            {
                m_xmap[i] = new StdRegions::StdSegExp(B);
            }

            m_state = eNotFilled;
        }

        EdgeComponent::~EdgeComponent()
        {
            if(m_xmap[0])
            {
                for(int i =0; i < m_coordim; ++i)
                {
  #pragma message("Fix this! " __FILE__)
                  //delete m_xmap[i];
                }

                delete[] m_xmap;

                m_xmap[0] = (StdRegions::StdSegExp *) NULL;
            }
        }

        EdgeComponent::EdgeComponent(const EdgeComponent &T)
        {

            m_eid = T.m_eid;

            std::list<CompToElmt>::const_iterator def;
            for(def = T.m_elmtmap.begin(); def != T.m_elmtmap.end(); def++)
            {
                m_elmtmap.push_back(*def);
            }
        }

        /** given local collapsed coordinate Lcoord return the value of
        physical coordinate in direction i **/

        double EdgeComponent::GetCoord(const int i, const double *Lcoord)
        {

            ASSERTL1(m_state == ePtsFilled, "Goemetry is not in physical space");

            return m_xmap[i]->Evaluate(Lcoord);
        }

        void EdgeComponent::AddElmtConnected(int gvo_id, int locid)
        {
            CompToElmt ee(gvo_id,locid);
            m_elmtmap.push_back(ee);
        }

        int EdgeComponent::NumElmtConnected() const
        {
            return int(m_elmtmap.size());
        }

        bool EdgeComponent::IsElmtConnected(int gvo_id, int locid) const
        {
            std::list<CompToElmt>::const_iterator def;
            CompToElmt ee(gvo_id,locid);

            def = find(m_elmtmap.begin(),m_elmtmap.end(),ee);

            // Found the element connectivity object in the list
            if(def != m_elmtmap.end())
            {
                return(true);
            }

            return(false);
        }

        /// \brief Get the orientation of edge1.
        ///
        /// If edge1 is connected to edge2 in the same direction
        /// as the points comprising edge1 then it is forward, otherwise it is backward.  For example, assume
        /// edge1 is comprised of points 1 and 2, and edge2 is comprised of points 2 and 3, then edge
        /// is forward.  If edge1 is comprised of points 2 and 1 and edge2 is comprised of points
        /// 3 and 2, then edge1 is backward.

        StdRegions::EdgeOrientation EdgeComponent::GetEdgeOrientation(const EdgeComponent &edge1, const EdgeComponent &edge2)
        {
            StdRegions::EdgeOrientation returnval = StdRegions::eForwards;

            /// Backward direction.  Vertex 0 is connected to edge 2.
            if ((*edge1.GetVertex(0) == *edge2.GetVertex(0)) || (*edge1.GetVertex(0) == *edge2.GetVertex(1)))
            {
                returnval = StdRegions::eBackwards;
            }
            // Not forward either, then we have a problem.
            else if ((*edge1.GetVertex(1) != *edge2.GetVertex(0)) && (*edge1.GetVertex(1) != *edge2.GetVertex(1)))
            {
                std::ostrstream errstrm;
                errstrm << "Connected edges do not share a vertex. Edges ";
                errstrm << edge1.GetEid() << ", " << edge2.GetEid();
                ASSERTL0(false, errstrm.str());
            }

            return returnval;
        }
    }; //end of namespace
}; //end of namespace

/**
*    $Log: EdgeComponent.cpp,v $
*    Revision 1.5  2006/05/23 19:56:33  jfrazier
*    These build and run, but the expansion pieces are commented out
*    because they would not run.
*
*    Revision 1.4  2006/05/16 20:12:59  jfrazier
*    Minor fixes to correct bugs.
*
*    Revision 1.3  2006/05/09 13:37:01  jfrazier
*    Removed duplicate definition of shared vertex pointer.
*
*    Revision 1.2  2006/05/06 20:36:16  sherwin
*    Modifications to get LocalRegions/Project1D working
*
*    Revision 1.1  2006/05/04 18:58:59  kirby
*    *** empty log message ***
*
*    Revision 1.14  2006/04/11 23:18:11  jfrazier
*    Completed MeshGraph2D for tri's and quads.  Not thoroughly tested.
*
*    Revision 1.13  2006/04/09 02:08:34  jfrazier
*    Added precompiled header.
*
*    Revision 1.12  2006/04/04 23:12:37  jfrazier
*    More updates to readers.  Still work to do on MeshGraph2D to store tris and quads.
*
*    Revision 1.11  2006/03/25 00:58:28  jfrazier
*    Many changes dealing with fundamental structure and reading/writing.
*
*    Revision 1.10  2006/03/12 14:20:42  sherwin
*
*    First compiling version of SpatialDomains and associated modifications
*
*    Revision 1.9  2006/03/12 11:06:39  sherwin
*
*    First complete copy of code standard code but still not compile tested
*
*    Revision 1.8  2006/03/12 07:42:02  sherwin
*
*    Updated member names and StdRegions call. Still has not been compiled
*
**/
<|endoftext|>


<commit_before>/**
 * @file   SafetyPrepareQueue.hpp
 * @brief  SafetyPrepareQueue class prototype.
 * @author zer0
 * @date   2016-08-04
 */

#ifndef __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__
#define __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__

// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif

#include <libtbag/config.h>
#include <libtbag/Noncopyable.hpp>
#include <libtbag/Exception.hpp>
#include <libtbag/id/generator/TimeId.hpp>

#include <mutex>
#include <memory>
#include <queue>
#include <map>
#include <type_traits>

// -------------------
NAMESPACE_LIBTBAG_OPEN
// -------------------

namespace container {

/**
 * SafetyPrepareQueue class prototype.
 *
 * @author zer0
 * @date   2016-08-04
 *
 * @remarks
 *  @code
 *      *<---(push)--- USER_WRITE / [PREPARE] <----(prepare)----*
 *      |                                                       |
 *   [ACTIVE] --(pop)-> [READING] / USER_READ --(readEnd)--> [REMOVE] <== (NEW DATA if EMPTY)
 *  @endcode
 */
template <typename ValueType
        , typename MutexType = std::mutex>
class SafetyPrepareQueue : public Noncopyable
{

public:
    using Key   = id::Id;
    using Value = ValueType;
    using Mutex = MutexType;
    using Guard = std::lock_guard<Mutex>;

public:
    enum class PacketState : int
    {
        ACTIVE,
        READING,
        REMOVE,
        PREPARE,
    };

    /**
     * Packet class prototype.
     *
     * @author zer0
     * @date   2016-08-04
     */
    class Packet
    {
    public:
        friend SafetyPrepareQueue;

    protected:
        PacketState  _state;
        Key          _id;
        Value        _value;

    public:
        Packet() : _state(PacketState::PREPARE), _id(id::generator::genTimeId()), _value()
        {
            // EMPTY.
        }

        Packet(Packet const & obj) = default;
        Packet(Packet && obj) = default;

        ~Packet() = default;

        Packet & operator =(Packet const & obj) = default;
        Packet & operator =(Packet && obj) = default;

    public:
        inline PacketState getState() const noexcept
        { return _state; }
        inline Key getId() const noexcept
        { return _id;    }

    public:
        inline Value const & at() const throw (IllegalStateException)
        {
            if (_state != PacketState::READING) {
                throw IllegalStateException();
            }
            return _value;
        }

        inline Value & at() throw (IllegalStateException)
        {
            if (_state != PacketState::PREPARE) {
                throw IllegalStateException();
            }
            return _value;
        }
    };

    /**
     * PreparePacket class prototype.
     *
     * @author zer0
     * @date   2016-08-05
     */
    struct PreparePacket : public Noncopyable
    {
    private:
        SafetyPrepareQueue & _queue;
        Packet & _packet;

    public:
        PreparePacket(SafetyPrepareQueue & queue, Packet & packet)
                : _queue(queue), _packet(packet)
        {
            // EMPTY.
        }

        ~PreparePacket()
        {
            _queue.push(_packet);
        }

    public:
        inline Value & at()
        { return _packet.at(); }
    };

    /**
     * ReadablePacket class prototype.
     *
     * @author zer0
     * @date   2016-08-05
     */
    struct ReadablePacket : public Noncopyable
    {
    private:
        SafetyPrepareQueue & _queue;
        Packet const & _packet;

    public:
        ReadablePacket(SafetyPrepareQueue & queue, Packet const & packet)
                : _queue(queue), _packet(packet)
        {
            // EMPTY.
        }

        ~ReadablePacket()
        {
            _queue.readEnd(_packet);
        }

    public:
        inline Value const & at() const
        { return _packet.at(); }
    };

    using Prepare  = std::shared_ptr<PreparePacket>;
    using Readable = std::shared_ptr<ReadablePacket>;

    using Queue         = std::queue<Packet>;
    using PacketMap     = std::map<Key, Packet>;
    using PacketMapPair = typename PacketMap::value_type;

private:
    mutable Mutex _mutex;
    Queue         _active_queue;
    PacketMap     _reading_map;
    PacketMap     _remove_map;

public:
    SafetyPrepareQueue()
    {
        // EMPTY.
    }

    ~SafetyPrepareQueue()
    {
        clear();
    }

public:
    void clear()
    {
        Guard guard(_mutex);
        while (_active_queue.empty() == false) {
            _active_queue.pop();
        }
        _reading_map.clear();
        _remove_map.clear();
    }

private:
    static inline bool compareExchangeState(PacketState & state, PacketState expected, PacketState desired) noexcept
    {
        if (state == expected) {
            state = desired;
            return true;
        }
        return false;
    }

    static inline bool tryActiveToReading(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::ACTIVE, PacketState::READING); }
    static inline bool tryReadingToRemove(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::READING, PacketState::REMOVE); }
    static inline bool tryRemoveToPrepare(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::REMOVE, PacketState::PREPARE); }
    static inline bool tryPrepareToActive(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::PREPARE, PacketState::ACTIVE); }

public:
    Packet & prepare()
    {
        Guard guard(_mutex);

        auto itr = _remove_map.begin();
        auto end = _remove_map.end();

        static_assert(!std::is_const<decltype(itr)>::value, "Iterator is should not be const-type.");

        for (; itr != end; ++itr) {
            // FIND READY DATA.
            if (tryRemoveToPrepare(itr->second._state)) {
                return itr->second;
            }
        }

        Packet new_packet;
        return _remove_map.insert(PacketMapPair(new_packet._id, new_packet)).first->second;
    }

    inline Prepare autoPrepare()
    {
        return Prepare(new PreparePacket(*this, prepare()));
    }

    void push(Packet const & packet) throw (IllegalArgumentException, NotFoundException)
    {
        Guard guard(_mutex);
        auto itr = _remove_map.find(packet.getId());
        if (itr == _remove_map.end()) {
            throw NotFoundException();
        }

        if (tryPrepareToActive(itr->second._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.push(itr->second);
        _remove_map.erase(itr);
    }

    Packet const & pop() throw (IllegalArgumentException)
    {
        Guard guard(_mutex);
        auto packet = _active_queue.front();
        if (tryActiveToReading(packet._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.pop();
        return _reading_map.insert(PacketMapPair(packet._id, packet)).first->second;
    }

    inline Readable autoPop()
    {
        return Readable(new ReadablePacket(*this, pop()));
    }

    bool popAndReadEnd() throw (IllegalArgumentException)
    {
        Guard guard(_mutex);
        auto packet = _active_queue.front();
        if (tryActiveToReading(packet._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.pop();
        return _remove_map.insert(PacketMapPair(packet._id, packet)).second;
    }

    void readEnd(Packet const & packet) throw (IllegalArgumentException, NotFoundException)
    {
        Guard guard(_mutex);
        auto itr = _reading_map.find(packet.getId());
        if (itr == _reading_map.end()) {
            throw NotFoundException();
        }

        if (tryReadingToRemove(itr->second._state) == false) {
            throw IllegalArgumentException();
        }

        _remove_map.insert(PacketMapPair(itr->first, itr->second));
        _reading_map.erase(itr);
    }

public:
    inline std::size_t size() const noexcept
    { return _active_queue.size(); }
    inline std::size_t sizeOfReadingSet() const noexcept
    { return _reading_map.size();  }
    inline std::size_t sizeOfRemoveSet() const noexcept
    { return _remove_map.size();   }

    inline bool empty() const noexcept
    { return _active_queue.empty(); }
    inline bool emptyOfReadingSet() const noexcept
    { return _reading_map.empty();  }
    inline bool emptyOfRemoveSet() const noexcept
    { return _remove_map.empty();   }
};

} // namespace container

// --------------------
NAMESPACE_LIBTBAG_CLOSE
// --------------------

#endif // __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__

<commit_msg>Trivial commit.<commit_after>/**
 * @file   SafetyPrepareQueue.hpp
 * @brief  SafetyPrepareQueue class prototype.
 * @author zer0
 * @date   2016-08-04
 */

#ifndef __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__
#define __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__

// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif

#include <libtbag/config.h>
#include <libtbag/Noncopyable.hpp>
#include <libtbag/Exception.hpp>
#include <libtbag/id/generator/TimeId.hpp>

#include <mutex>
#include <memory>
#include <queue>
#include <map>
#include <type_traits>

// -------------------
NAMESPACE_LIBTBAG_OPEN
// -------------------

namespace container {

/**
 * SafetyPrepareQueue class prototype.
 *
 * @author zer0
 * @date   2016-08-04
 *
 * @remarks
 *  @code
 *      *<---------------(OR/autoPrepare)<--------------------*
 *      *<---(push)--- [USER_WRITE/PREPARE] <----(prepare)----*
 *      |                                                     |
 *   [ACTIVE] --(pop)-> [READING/USER_READ] --(readEnd)--> [REMOVE] <== (NEW DATA if EMPTY)
 *      *----------------->(OR/autoPop)---------------------->*
 *  @endcode
 */
template <typename ValueType
        , typename MutexType = std::mutex>
class SafetyPrepareQueue : public Noncopyable
{

public:
    using Key   = id::Id;
    using Value = ValueType;
    using Mutex = MutexType;
    using Guard = std::lock_guard<Mutex>;

public:
    enum class PacketState : int
    {
        ACTIVE,
        READING,
        REMOVE,
        PREPARE,
    };

    /**
     * Packet class prototype.
     *
     * @author zer0
     * @date   2016-08-04
     */
    class Packet
    {
    public:
        friend SafetyPrepareQueue;

    protected:
        PacketState  _state;
        Key          _id;
        Value        _value;

    public:
        Packet() : _state(PacketState::PREPARE), _id(id::generator::genTimeId()), _value()
        {
            // EMPTY.
        }

        Packet(Packet const & obj) = default;
        Packet(Packet && obj) = default;

        ~Packet() = default;

        Packet & operator =(Packet const & obj) = default;
        Packet & operator =(Packet && obj) = default;

    public:
        inline PacketState getState() const noexcept
        { return _state; }
        inline Key getId() const noexcept
        { return _id;    }

    public:
        inline Value const & at() const throw (IllegalStateException)
        {
            if (_state != PacketState::READING) {
                throw IllegalStateException();
            }
            return _value;
        }

        inline Value & at() throw (IllegalStateException)
        {
            if (_state != PacketState::PREPARE) {
                throw IllegalStateException();
            }
            return _value;
        }
    };

    /**
     * PreparePacket class prototype.
     *
     * @author zer0
     * @date   2016-08-05
     */
    struct PreparePacket : public Noncopyable
    {
    private:
        SafetyPrepareQueue & _queue;
        Packet & _packet;

    public:
        PreparePacket(SafetyPrepareQueue & queue, Packet & packet)
                : _queue(queue), _packet(packet)
        {
            // EMPTY.
        }

        ~PreparePacket()
        {
            _queue.push(_packet);
        }

    public:
        inline Value & at()
        { return _packet.at(); }
    };

    /**
     * ReadablePacket class prototype.
     *
     * @author zer0
     * @date   2016-08-05
     */
    struct ReadablePacket : public Noncopyable
    {
    private:
        SafetyPrepareQueue & _queue;
        Packet const & _packet;

    public:
        ReadablePacket(SafetyPrepareQueue & queue, Packet const & packet)
                : _queue(queue), _packet(packet)
        {
            // EMPTY.
        }

        ~ReadablePacket()
        {
            _queue.readEnd(_packet);
        }

    public:
        inline Value const & at() const
        { return _packet.at(); }
    };

    using Prepare  = std::shared_ptr<PreparePacket>;
    using Readable = std::shared_ptr<ReadablePacket>;

    using Queue         = std::queue<Packet>;
    using PacketMap     = std::map<Key, Packet>;
    using PacketMapPair = typename PacketMap::value_type;

private:
    mutable Mutex _mutex;
    Queue         _active_queue;
    PacketMap     _reading_map;
    PacketMap     _remove_map;

public:
    SafetyPrepareQueue()
    {
        // EMPTY.
    }

    ~SafetyPrepareQueue()
    {
        clear();
    }

public:
    void clear()
    {
        Guard guard(_mutex);
        while (_active_queue.empty() == false) {
            _active_queue.pop();
        }
        _reading_map.clear();
        _remove_map.clear();
    }

private:
    static inline bool compareExchangeState(PacketState & state, PacketState expected, PacketState desired) noexcept
    {
        if (state == expected) {
            state = desired;
            return true;
        }
        return false;
    }

    static inline bool tryActiveToReading(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::ACTIVE, PacketState::READING); }
    static inline bool tryReadingToRemove(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::READING, PacketState::REMOVE); }
    static inline bool tryRemoveToPrepare(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::REMOVE, PacketState::PREPARE); }
    static inline bool tryPrepareToActive(PacketState & state) noexcept
    { return compareExchangeState(state, PacketState::PREPARE, PacketState::ACTIVE); }

public:
    Packet & prepare()
    {
        Guard guard(_mutex);

        auto itr = _remove_map.begin();
        auto end = _remove_map.end();

        static_assert(!std::is_const<decltype(itr)>::value, "Iterator is should not be const-type.");

        for (; itr != end; ++itr) {
            // FIND READY DATA.
            if (tryRemoveToPrepare(itr->second._state)) {
                return itr->second;
            }
        }

        Packet new_packet;
        return _remove_map.insert(PacketMapPair(new_packet._id, new_packet)).first->second;
    }

    inline Prepare autoPrepare()
    {
        return Prepare(new PreparePacket(*this, prepare()));
    }

    void push(Packet const & packet) throw (IllegalArgumentException, NotFoundException)
    {
        Guard guard(_mutex);
        auto itr = _remove_map.find(packet.getId());
        if (itr == _remove_map.end()) {
            throw NotFoundException();
        }

        if (tryPrepareToActive(itr->second._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.push(itr->second);
        _remove_map.erase(itr);
    }

    Packet const & pop() throw (IllegalArgumentException)
    {
        Guard guard(_mutex);
        auto packet = _active_queue.front();
        if (tryActiveToReading(packet._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.pop();
        return _reading_map.insert(PacketMapPair(packet._id, packet)).first->second;
    }

    inline Readable autoPop()
    {
        return Readable(new ReadablePacket(*this, pop()));
    }

    bool popAndReadEnd() throw (IllegalArgumentException)
    {
        Guard guard(_mutex);
        auto packet = _active_queue.front();
        if (tryActiveToReading(packet._state) == false) {
            throw IllegalArgumentException();
        }

        _active_queue.pop();
        return _remove_map.insert(PacketMapPair(packet._id, packet)).second;
    }

    void readEnd(Packet const & packet) throw (IllegalArgumentException, NotFoundException)
    {
        Guard guard(_mutex);
        auto itr = _reading_map.find(packet.getId());
        if (itr == _reading_map.end()) {
            throw NotFoundException();
        }

        if (tryReadingToRemove(itr->second._state) == false) {
            throw IllegalArgumentException();
        }

        _remove_map.insert(PacketMapPair(itr->first, itr->second));
        _reading_map.erase(itr);
    }

public:
    inline std::size_t size() const noexcept
    { return _active_queue.size(); }
    inline std::size_t sizeOfReadingSet() const noexcept
    { return _reading_map.size();  }
    inline std::size_t sizeOfRemoveSet() const noexcept
    { return _remove_map.size();   }

    inline bool empty() const noexcept
    { return _active_queue.empty(); }
    inline bool emptyOfReadingSet() const noexcept
    { return _reading_map.empty();  }
    inline bool emptyOfRemoveSet() const noexcept
    { return _remove_map.empty();   }
};

} // namespace container

// --------------------
NAMESPACE_LIBTBAG_CLOSE
// --------------------

#endif // __INCLUDE_LIBTBAG__LIBTBAG_CONTAINER_SAFETYPREPAREQUEUE_HPP__

<|endoftext|>