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