Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
#include <qpaintevent></qpaintevent>
Toshihiro Shimizu 890ddd
#include <qdesktopwidget></qdesktopwidget>
Toshihiro Shimizu 890ddd
#include <qapplication></qapplication>
Toshihiro Shimizu 890ddd
#include <qmetaobject></qmetaobject>
Toshihiro Shimizu 890ddd
#include <qcursor></qcursor>
Toshihiro Shimizu 890ddd
#include <qpainter></qpainter>
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
#include "toonzqt/screenboard.h"
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
using namespace DVGui;
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
#include <qpalette></qpalette>
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
//    Local namespace
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
namespace {
Toshihiro Shimizu 890ddd
Shinya Kitaoka d1f6c4
class MouseTrackerDrawing final : public ScreenBoard::Drawing {
Toshihiro Shimizu 890ddd
public:
Shinya Kitaoka 473e70
  bool acceptScreenEvents(const QRect &rect) const override {
Shinya Kitaoka 120a6e
    return rect.contains(QCursor::pos());
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 473e70
  void paintEvent(QWidget *widget, QPaintEvent *pe) override {
Shinya Kitaoka 120a6e
// Seems that mouse masking is on by default on the drawn regions, when using
Shinya Kitaoka 120a6e
// WA_TranslucentBackground (which is weird). I think it's the Qt 2 autoMask
Shinya Kitaoka 120a6e
// feature
Shinya Kitaoka 120a6e
// that disappeared from Qt 3 on - now it must be managed internally.
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
// So, we have to fill the entire screen region with the most invisible color
Shinya Kitaoka 120a6e
// possible...
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
#ifdef MACOSX
Shinya Kitaoka 120a6e
#define MIN_ALPHA                                                              \
Shinya Kitaoka 120a6e
  13  // Again, very weird. 13 is the minimal alpha that gets converted to
Shinya Kitaoka 120a6e
      // 'mask-opaque' on MAC...
Toshihiro Shimizu 890ddd
#else
Shinya Kitaoka 120a6e
#define MIN_ALPHA 1  // ... whereas 1 is sufficient on Windows. Argh!
Toshihiro Shimizu 890ddd
#endif
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
    QPainter painter(widget);
Shinya Kitaoka 120a6e
    painter.fillRect(0, 0, widget->width(), widget->height(),
Shinya Kitaoka 120a6e
                     QColor(0, 0, 0, MIN_ALPHA));
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
} tracker;
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
}  // namespace
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
//    ScreenWidget implementation
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
Shinya Kitaoka d1f6c4
class ScreenBoard::ScreenWidget final : public QWidget {
Shinya Kitaoka 120a6e
  QList<screenboard::drawing *=""></screenboard::drawing>
Shinya Kitaoka 120a6e
      m_drawings;        //!< Drawings intersecting the screen
Shinya Kitaoka 120a6e
  bool m_mouseOnScreen;  //!< Whether the mouse is inside this screen
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
public:
Shinya Kitaoka 120a6e
  ScreenWidget(QWidget *parent = 0, bool grabbing = false)
Shinya Kitaoka 120a6e
      : QWidget(
Shinya Kitaoka 120a6e
            parent,
Shinya Kitaoka 120a6e
            Qt::Tool |  // Tool does not force focus changes upon shows
Shinya Kitaoka 120a6e
                Qt::FramelessWindowHint |  // No decorations
Shinya Kitaoka 120a6e
                Qt::WindowStaysOnTopHint)  // Must be above all other windows
Shinya Kitaoka 120a6e
  {
Shinya Kitaoka 120a6e
    setAttribute(Qt::WA_TransparentForMouseEvents,
Shinya Kitaoka 120a6e
                 !grabbing);                     // Receives mouse events?
Shinya Kitaoka 120a6e
    setAttribute(Qt::WA_TranslucentBackground);  // Transparent widget
Shinya Kitaoka 120a6e
    setFocusPolicy(Qt::NoFocus);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
    setMouseTracking(true);  // When receiving mouse events
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  const QList<drawing *=""> &drawings() const { return m_drawings; }</drawing>
Shinya Kitaoka 120a6e
  QList<drawing *=""> &drawings() { return m_drawings; }</drawing>
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  bool mouseOnScreen() const { return m_mouseOnScreen; }
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
protected:
Shinya Kitaoka 473e70
  bool event(QEvent *e) override {
Shinya Kitaoka 120a6e
    int i, size = m_drawings.size();
Shinya Kitaoka 120a6e
    if (e->type() == QEvent::Paint) {
Shinya Kitaoka 120a6e
      // Invoke paint events in reversed sorting order
Shinya Kitaoka 120a6e
      for (i = size - 1; i >= 0; --i)
Shinya Kitaoka 120a6e
        m_drawings[i]->paintEvent(this, static_cast<qpaintevent *="">(e));</qpaintevent>
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
    // Invoke other events in straight sorting order
Shinya Kitaoka 120a6e
    for (i = 0; i < size; ++i) m_drawings[i]->event(this, e);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
    return QWidget::event(e);
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 473e70
  void leaveEvent(QEvent *e) override {
Shinya Kitaoka 120a6e
    m_mouseOnScreen = false;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
    ScreenBoard *screenBoard = ScreenBoard::instance();
Shinya Kitaoka 120a6e
    if (screenBoard->m_grabbing) screenBoard->ensureMouseOnAScreen();
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 473e70
  void enterEvent(QEvent *e) override {
Shinya Kitaoka 120a6e
    m_mouseOnScreen                           = true;
Shinya Kitaoka 120a6e
    ScreenBoard::instance()->m_mouseOnAScreen = true;
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
};
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
//    ScreenBoard implementation
Toshihiro Shimizu 890ddd
//***********************************************************************************
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
ScreenBoard::ScreenBoard() : m_grabbing(false) {}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
ScreenBoard *ScreenBoard::instance() {
Shinya Kitaoka 120a6e
  static ScreenBoard theInstance;
Shinya Kitaoka 120a6e
  return &theInstance;
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
/*!
Shinya Kitaoka 120a6e
  Makes the ScreenBoard catch all mouse events (effectively preventing other
Shinya Kitaoka 120a6e
  windows or applications
Shinya Kitaoka 120a6e
  to get them), and delivers them to drawings. An appropriate cursor should be
Shinya Kitaoka 120a6e
  specified to inform the
Toshihiro Shimizu 890ddd
  user that tout-court mouse grabbing takes place.
Toshihiro Shimizu 890ddd
*/
Shinya Kitaoka 120a6e
void ScreenBoard::grabMouse(const QCursor &cursor) {
Shinya Kitaoka 120a6e
  m_grabbing = true;
Shinya Kitaoka 120a6e
  m_cursor   = cursor;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Place a mouse-tracking dummy drawing among drawings
Shinya Kitaoka 120a6e
  m_drawings.push_back(&::tracker);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Make all screen widgets react to mouse events, and show them
Shinya Kitaoka 120a6e
  int i, size = m_screenWidgets.size();
Shinya Kitaoka 120a6e
  for (i = 0; i < size; ++i) {
Shinya Kitaoka 120a6e
    QWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    if (screenWidget) {
Shinya Kitaoka 120a6e
      screenWidget->setAttribute(Qt::WA_TransparentForMouseEvents, false);
Shinya Kitaoka 120a6e
      screenWidget->setCursor(m_cursor);
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
/*!
Shinya Kitaoka 120a6e
  Restores the ScreenBoard to ignore mouse events (the default behaviour) after
Shinya Kitaoka 120a6e
  a
Toshihiro Shimizu 890ddd
  call to grabMouse().
Toshihiro Shimizu 890ddd
*/
Shinya Kitaoka 120a6e
void ScreenBoard::releaseMouse() {
Shinya Kitaoka 120a6e
  // Restore screen widgets to ignore mouse events
Shinya Kitaoka 120a6e
  int i, size = m_screenWidgets.size();
Shinya Kitaoka 120a6e
  for (i = 0; i < size; ++i) {
Shinya Kitaoka 120a6e
    QWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    if (screenWidget) {
Shinya Kitaoka 120a6e
      screenWidget->setAttribute(Qt::WA_TransparentForMouseEvents, true);
Shinya Kitaoka 120a6e
      screenWidget->unsetCursor();
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Remove the mouse-tracking drawing
Shinya Kitaoka 120a6e
  m_drawings.removeAt(m_drawings.indexOf(&::tracker));
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  m_cursor   = QCursor();
Shinya Kitaoka 120a6e
  m_grabbing = false;
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
// Refresh the screen widgets pool, depending on stored drawings
Shinya Kitaoka 120a6e
void ScreenBoard::reallocScreenWidgets() {
Shinya Kitaoka 120a6e
  QDesktopWidget *desktop = QApplication::desktop();
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  int i, screensCount = desktop->numScreens();
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Delete exceeding screens and resize to screensCount
Shinya Kitaoka 120a6e
  for (i = screensCount; i < m_screenWidgets.size(); ++i) {
Shinya Kitaoka 120a6e
    m_screenWidgets[i]->hide();
Shinya Kitaoka 120a6e
    m_screenWidgets[i]->deleteLater();  // Ensures no event about it is pending.
Shinya Kitaoka 120a6e
    // Note that updates may be invoked in event handlers.
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  m_screenWidgets.resize(desktop->numScreens());
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Re-initialize the screen widgets list
Shinya Kitaoka 120a6e
  for (i = 0; i < screensCount; ++i) {
Shinya Kitaoka 120a6e
    ScreenWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    if (screenWidget) screenWidget->drawings().clear();
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Turn on a ScreenWidget for each screen crossed by any drawing
Shinya Kitaoka 120a6e
  int j, drawingsCount = m_drawings.size();
Shinya Kitaoka 120a6e
  for (i = 0; i < screensCount; ++i) {
Shinya Kitaoka 120a6e
    ScreenWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    const QRect &screenGeom    = desktop->screenGeometry(i);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
    for (j = 0; j < drawingsCount; ++j) {
Shinya Kitaoka 120a6e
      Drawing *drawing = m_drawings[j];
Shinya Kitaoka 120a6e
      if (drawing->acceptScreenEvents(screenGeom)) {
Shinya Kitaoka 120a6e
        // Allocate the associated screen widget if necessary
Shinya Kitaoka 120a6e
        if (!screenWidget) {
Shinya Kitaoka 120a6e
          m_screenWidgets[i] = screenWidget = new ScreenWidget(0, m_grabbing);
Shinya Kitaoka 120a6e
          if (m_grabbing) screenWidget->setCursor(m_cursor);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
          screenWidget->setGeometry(screenGeom);
Shinya Kitaoka 120a6e
          screenWidget->show();
Shinya Kitaoka 120a6e
        }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
        // Add the drawing to the widget
Shinya Kitaoka 120a6e
        screenWidget->drawings().push_back(drawing);
Shinya Kitaoka 120a6e
      }
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // Remove screens without drawings
Shinya Kitaoka 120a6e
  for (i = 0; i < screensCount; ++i) {
Shinya Kitaoka 120a6e
    ScreenWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    if (screenWidget && screenWidget->drawings().empty()) {
Shinya Kitaoka 120a6e
      screenWidget->hide();
Shinya Kitaoka 120a6e
      screenWidget->deleteLater();
Shinya Kitaoka 120a6e
      m_screenWidgets[i] = 0;
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
/*!
Toshihiro Shimizu 890ddd
  This function must be called whenever a drawing needs to be refreshed.
Toshihiro Shimizu 890ddd
  It has 2 consequences:
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
    \li The pool of invisible screen widgets that are used to catch Qt events
Toshihiro Shimizu 890ddd
        is refreshed to satisfy the drawings requirements. Screen widgets whose
Toshihiro Shimizu 890ddd
        events are not needed are released from the pool.
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
    \li All screen widgets are updated, propagating paintEvents to their
Shinya Kitaoka 120a6e
  drawings.
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
  Observe that this function awaits return to the main event loop before being
Shinya Kitaoka 120a6e
  processed.
Shinya Kitaoka 120a6e
  This ensures that update calls are processed outside event handlers, and
Shinya Kitaoka 120a6e
  multiple
Toshihiro Shimizu 890ddd
  calls from different drawings gets coalesced into one.
Toshihiro Shimizu 890ddd
*/
Shinya Kitaoka 120a6e
void ScreenBoard::update() {
Shinya Kitaoka 120a6e
  m_updated = false;
Shinya Kitaoka 120a6e
  QMetaObject::invokeMethod(this, "doUpdate", Qt::QueuedConnection);
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
void ScreenBoard::doUpdate() {
Shinya Kitaoka 120a6e
  if (m_updated)  // Weak updates coalescence. It helps.
Shinya Kitaoka 120a6e
    return;
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
  m_updated = true;
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
  reallocScreenWidgets();
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
  // Update all screenWidgets
Shinya Kitaoka 120a6e
  int i, size = m_screenWidgets.size();
Shinya Kitaoka 120a6e
  for (i = 0; i < size; ++i)
Shinya Kitaoka 120a6e
    if (m_screenWidgets[i]) m_screenWidgets[i]->update();
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
void ScreenBoard::ensureMouseOnAScreen() {
Shinya Kitaoka 120a6e
  // Find out if the mouse is on a screen
Shinya Kitaoka 120a6e
  m_mouseOnAScreen = false;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  int i, size = m_screenWidgets.size();
Shinya Kitaoka 120a6e
  for (i = 0; i < size; ++i) {
Shinya Kitaoka 120a6e
    ScreenWidget *screenWidget = m_screenWidgets[i];
Shinya Kitaoka 120a6e
    if (screenWidget) m_mouseOnAScreen |= screenWidget->mouseOnScreen();
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  if (!m_mouseOnAScreen)
Shinya Kitaoka 120a6e
    // Ensure that there is a screen under the mouse cursor.
Shinya Kitaoka 120a6e
    // We need a slot invocation, since this method could be called in an event
Shinya Kitaoka 120a6e
    // handler.
Shinya Kitaoka 120a6e
    QMetaObject::invokeMethod(this, "trackCursor", Qt::QueuedConnection);
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//------------------------------------------------------------------------------
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
void ScreenBoard::trackCursor() {
Shinya Kitaoka 120a6e
  while (!m_mouseOnAScreen) {
Shinya Kitaoka 120a6e
    update();  // Refresh screens pool
Shinya Kitaoka 120a6e
    QCoreApplication::processEvents(
Shinya Kitaoka 120a6e
        QEventLoop::WaitForMoreEvents);  // Process events (-> enterEv.)
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
}