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