blob: d90bd4706f480f193c89f92354e3c7753f8a41cc [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/skin/qt/emulator-container.h"
#include "android/skin/qt/emulator-qt-window.h"
#include "android/skin/qt/tool-window.h"
#include <QtCore>
#include <QApplication>
#include <QObject>
#include <QScrollBar>
#include <QStyle>
#include <QStyleFactory>
#if defined(__APPLE__)
#include "android/skin/qt/mac-native-window.h"
#endif
#if defined(_WIN32)
#include "android/skin/qt/windows-native-window.h"
#endif
EmulatorContainer::EmulatorContainer(EmulatorQtWindow* window)
: QScrollArea(), mEmulatorWindow(window) {
setFrameShape(QFrame::NoFrame);
setWidget(window);
// The following hints prevent the minimize/maximize/close buttons from
// appearing.
setWindowFlags(Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::Window);
#ifdef __APPLE__
// Digging into the Qt source code reveals that if the above flags are set
// on OSX, the created window will be given a style mask that removes the
// resize handles from the window. The hint below is the specific
// customization flag that ensures the window will have resize handles.
// So, we add the button for now, then immediately disable it when the
// window is first shown.
setWindowFlags(this->windowFlags() | Qt::WindowMaximizeButtonHint);
// On OS X the native scrollbars disappear when not in use which
// makes the zoomed-in emulator window look unscrollable. Also, due
// to the semi-transparent nature of the scrollbar, it will
// interfere with the main GL window, causing all kinds of ugly
// effects.
QStyle* style = QStyleFactory::create("Fusion");
if (style) {
this->verticalScrollBar()->setStyle(style);
this->horizontalScrollBar()->setStyle(style);
QObject::connect(this, &QObject::destroyed, [style] { delete style; });
}
#endif // __APPLE__
mResizeTimer.setSingleShot(true);
QObject::connect(&mResizeTimer, SIGNAL(timeout()), this,
SLOT(slot_resizeDone()));
}
EmulatorContainer::~EmulatorContainer() {
// This object is owned directly by |window|. Avoid circular
// destructor calls by explicitly unsetting the widget.
takeWidget();
}
bool EmulatorContainer::event(QEvent* e) {
// Ignore MetaCall and UpdateRequest events, and don't snap in zoom mode.
if (mEmulatorWindow->isInZoomMode() || e->type() == QEvent::MetaCall ||
e->type() == QEvent::UpdateRequest) {
return QScrollArea::event(e);
}
// Add to the event buffer, but keep it a reasonable size - a few events,
// such as repaint,
// can occur in between resizes but before the release happens
mEventBuffer.push_back(e->type());
if (mEventBuffer.size() > 8) {
mEventBuffer.removeFirst();
}
// Scan to see if a resize event happened recently
bool foundResize = false;
int i = 0;
for (; i < mEventBuffer.size(); i++) {
if (mEventBuffer[i] == QEvent::Resize) {
foundResize = true;
i++;
break;
}
}
// Determining resize-over is OS specific
// Do so by scanning the remainder of the event buffer for specific
// combinations
if (foundResize) {
#ifdef _WIN32
for (; i < mEventBuffer.size() - 1; i++) {
if (mEventBuffer[i] == QEvent::NonClientAreaMouseButtonRelease) {
mEventBuffer.clear();
mEmulatorWindow->doResize(this->size());
// Kill the resize timer to avoid double resizes.
stopResizeTimer();
break;
}
}
#elif __linux__
for (; i < mEventBuffer.size() - 3; i++) {
if (mEventBuffer[i] == QEvent::WindowActivate &&
mEventBuffer[i + 1] == QEvent::ActivationChange &&
mEventBuffer[i + 2] == QEvent::FocusIn &&
mEventBuffer[i + 3] == QEvent::InputMethodQuery) {
mEventBuffer.clear();
mEmulatorWindow->doResize(this->size());
// Kill the resize timer to avoid double resizes.
stopResizeTimer();
break;
}
}
#elif __APPLE__
if (e->type() == QEvent::NonClientAreaMouseMove ||
e->type() == QEvent::Enter || e->type() == QEvent::Leave) {
mEventBuffer.clear();
mEmulatorWindow->doResize(this->size());
// Kill the resize timer to avoid double resizes.
stopResizeTimer();
}
#endif
}
return QScrollArea::event(e);
}
void EmulatorContainer::changeEvent(QEvent* event) {
// Strictly preventing the maximizing (called "zooming" on OS X) of a
// window is hard - it changes by host, and even by window manager on
// Linux. Therefore, we counteract it by seeing if the window ever
// enters a maximized state, and if it does, immediately undoing that
// maximization.
//
// Note that we *do not* call event->ignore(). Maximizing happens in the
// OS-level window, not Qt's representation of the window. This event
// simply notifies the Qt representation (and us) that the OS-level window
// has changed to a maximized state. We do not want to ignore this state
// change, we just want to counteract the effects it had.
if (event->type() == QEvent::WindowStateChange) {
if (windowState() & Qt::WindowMaximized) {
showNormal();
} else if (windowState() & Qt::WindowMinimized) {
// In case the window was minimized without pressing the toolbar's
// minimize button (which is possible on some window managers),
// remember to hide the toolbar (which will also hide the extended
// window, if it exists).
mEmulatorWindow->toolWindow()->hide();
}
}
}
void EmulatorContainer::closeEvent(QCloseEvent* event) {
mEmulatorWindow->closeEvent(event);
}
void EmulatorContainer::focusInEvent(QFocusEvent* event) {
mEmulatorWindow->toolWindow()->raise();
if (mEmulatorWindow->isInZoomMode()) {
mEmulatorWindow->showZoomIfNotUserHidden();
}
}
void EmulatorContainer::keyPressEvent(QKeyEvent* event) {
mEmulatorWindow->keyPressEvent(event);
}
void EmulatorContainer::keyReleaseEvent(QKeyEvent* event) {
mEmulatorWindow->keyReleaseEvent(event);
}
void EmulatorContainer::moveEvent(QMoveEvent* event) {
QScrollArea::moveEvent(event);
mEmulatorWindow->simulateWindowMoved(event->pos());
mEmulatorWindow->toolWindow()->dockMainWindow();
}
void EmulatorContainer::resizeEvent(QResizeEvent* event) {
QScrollArea::resizeEvent(event);
mEmulatorWindow->toolWindow()->dockMainWindow();
mEmulatorWindow->simulateZoomedWindowResized(this->viewportSize());
// To solve some resizing edge cases on OSX/Windows/KDE, start a short
// timer that will attempt to trigger a resize in case the user's mouse has
// not entered the window again. We use a longer timer on Linux because
// the timer is not needed on non-KDE systems, so we want it to be less
// noticeable.
#ifdef __linux__
mResizeTimer.start(1000);
#else
mResizeTimer.start(500);
#endif
}
void EmulatorContainer::showEvent(QShowEvent* event) {
// Disable to maximize button on OSX. See the comment in the constructor for an
// explanation of why this is necessary.
#ifdef __APPLE__
WId wid = effectiveWinId();
wid = (WId)getNSWindow((void*)wid);
nsWindowHideWindowButtons((void*)wid);
#endif // __APPLE__
// showEvent() gets called when the emulator is minimized because we are
// calling showMinimized(), which *technically* is a show event. We only
// want to re-show the toolbar when we are transitioning to a not-minimized
// state. However, we must do it after changing flags on Linux.
if (!(windowState() & Qt::WindowMinimized)) {
// As seen below in showMinimized(), we need to remove the minimize button on
// Linux when the window is re-shown. We know this show event is from being
// un-minimized because the minimized button flag is present.
#ifdef __linux__
Qt::WindowFlags flags = windowFlags();
if (flags & Qt::WindowMinimizeButtonHint) {
setWindowFlags(flags & ~Qt::WindowMinimizeButtonHint);
// Changing window flags requires re-showing this window to ensure
// the flags are appropriately changed.
showNormal();
// The subwindow won't redraw until the guest screen changes, which
// may not happen for a minute (when the clock changes), so force a
// redraw after re-showing the window.
SkinEvent* event = new SkinEvent();
event->type = kEventForceRedraw;
mEmulatorWindow->queueSkinEvent(event);
}
#endif // __linux__
mEmulatorWindow->toolWindow()->show();
mEmulatorWindow->toolWindow()->dockMainWindow();
}
}
void EmulatorContainer::showMinimized() {
// Some Linux window managers (specifically, Compiz, which is the default
// Ubuntu window manager) will not allow minimizing unless the minimize
// button is actually there! So, we re-add the button, minimize the window,
// and then remove the button when it gets reshown.
#ifdef __linux__
setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint);
#endif // __linux__
QScrollArea::showMinimized();
}
void EmulatorContainer::stopResizeTimer() {
mResizeTimer.stop();
}
QSize EmulatorContainer::viewportSize() const {
QSize output = this->size();
QScrollBar* vertical = this->verticalScrollBar();
output.setWidth(output.width() -
(vertical->isVisible() ? vertical->width() : 0));
QScrollBar* horizontal = this->horizontalScrollBar();
output.setHeight(output.height() -
(horizontal->isVisible() ? horizontal->height() : 0));
return output;
}
void EmulatorContainer::slot_resizeDone() {
if (mEmulatorWindow->isInZoomMode()) {
return;
}
// Windows and Apple have convenient ways of checking global mouse state, so
// we'll only do a resize if no mouse buttons are held down.
#if defined(__APPLE__) || defined(_WIN32)
// A hacky way of determining if the user is still holding down for a
// resize.
// This queries the global event state to see if any mouse buttons are held
// down.
// If there are, then the user must not be done resizing yet.
if (numHeldMouseButtons() == 0) {
mEmulatorWindow->doResize(this->size());
} else {
mResizeTimer.start(500);
}
#endif
// X11 doesn't. Hope that the user isn't still holding down a mouse button, and
// if they are, oh well.
#ifdef __linux__
mEmulatorWindow->doResize(this->size());
#endif
}