Blob Blame Raw


#include <QPaintEvent>
#include <QDesktopWidget>
#include <QApplication>
#include <QMetaObject>
#include <QCursor>
#include <QPainter>

#include "toonzqt/screenboard.h"

using namespace DVGui;

#include <QPalette>

//***********************************************************************************
//    Local namespace
//***********************************************************************************

namespace
{

class MouseTrackerDrawing : public ScreenBoard::Drawing
{
public:
	bool acceptScreenEvents(const QRect &rect) const
	{
		return rect.contains(QCursor::pos());
	}
	void paintEvent(QWidget *widget, QPaintEvent *pe)
	{
//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 : 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)
	{
		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)
	{
		m_mouseOnScreen = false;

		ScreenBoard *screenBoard = ScreenBoard::instance();
		if (screenBoard->m_grabbing)
			screenBoard->ensureMouseOnAScreen();
	}

	void enterEvent(QEvent *e)
	{
		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, screensCount = desktop->numScreens();

	//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(desktop->numScreens());

	//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 = desktop->screenGeometry(i);

		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.)
	}
}