mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-15 07:21:16 +00:00
1643 lines
40 KiB
C++
1643 lines
40 KiB
C++
// Aseprite UI Library
|
|
// Copyright (C) 2001-2016 David Capello
|
|
//
|
|
// This file is released under the terms of the MIT license.
|
|
// Read LICENSE.txt for more information.
|
|
|
|
// #define REPORT_EVENTS
|
|
// #define DEBUG_PAINT_EVENTS
|
|
// #define LIMIT_DISPATCH_TIME
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "ui/manager.h"
|
|
|
|
#include "base/scoped_value.h"
|
|
#include "she/display.h"
|
|
#include "she/event.h"
|
|
#include "she/event_queue.h"
|
|
#include "she/scoped_surface_lock.h"
|
|
#include "she/surface.h"
|
|
#include "she/system.h"
|
|
#include "ui/intern.h"
|
|
#include "ui/ui.h"
|
|
|
|
#ifdef DEBUG_PAINT_EVENTS
|
|
#include "base/thread.h"
|
|
#endif
|
|
|
|
#ifdef REPORT_EVENTS
|
|
#include <iostream>
|
|
#endif
|
|
|
|
#include <limits>
|
|
#include <list>
|
|
#include <vector>
|
|
|
|
namespace ui {
|
|
|
|
#define ACCEPT_FOCUS(widget) \
|
|
((((widget)->flags() & (FOCUS_STOP | \
|
|
DISABLED | \
|
|
HIDDEN | \
|
|
DECORATIVE)) == FOCUS_STOP) && \
|
|
((widget)->isVisible()))
|
|
|
|
static const int NFILTERS = (int)(kFirstRegisteredMessage+1);
|
|
|
|
struct Filter
|
|
{
|
|
int message;
|
|
Widget* widget;
|
|
|
|
Filter(int message, Widget* widget)
|
|
: message(message)
|
|
, widget(widget) { }
|
|
};
|
|
|
|
typedef std::list<Message*> Messages;
|
|
typedef std::list<Filter*> Filters;
|
|
|
|
Manager* Manager::m_defaultManager = NULL;
|
|
gfx::Region Manager::m_dirtyRegion;
|
|
|
|
static WidgetsList new_windows; // Windows that we should show
|
|
static WidgetsList mouse_widgets_list; // List of widgets to send mouse events
|
|
static Messages msg_queue; // Messages queue
|
|
static Filters msg_filters[NFILTERS]; // Filters for every enqueued message
|
|
|
|
static Widget* focus_widget; // The widget with the focus
|
|
static Widget* mouse_widget; // The widget with the mouse
|
|
static Widget* capture_widget; // The widget that captures the mouse
|
|
|
|
static bool first_time = true; // true when we don't enter in poll yet
|
|
|
|
/* keyboard focus movement stuff */
|
|
static bool move_focus(Manager* manager, Message* msg);
|
|
static int count_widgets_accept_focus(Widget* widget);
|
|
static bool childs_accept_focus(Widget* widget, bool first);
|
|
static Widget* next_widget(Widget* widget);
|
|
static int cmp_left(Widget* widget, int x, int y);
|
|
static int cmp_right(Widget* widget, int x, int y);
|
|
static int cmp_up(Widget* widget, int x, int y);
|
|
static int cmp_down(Widget* widget, int x, int y);
|
|
|
|
Manager::Manager()
|
|
: Widget(kManagerWidget)
|
|
, m_display(NULL)
|
|
, m_clipboard(NULL)
|
|
, m_eventQueue(NULL)
|
|
, m_lockedWindow(NULL)
|
|
, m_mouseButtons(kButtonNone)
|
|
{
|
|
if (!m_defaultManager) {
|
|
// Empty lists
|
|
ASSERT(msg_queue.empty());
|
|
ASSERT(new_windows.empty());
|
|
mouse_widgets_list.clear();
|
|
|
|
// Reset variables
|
|
focus_widget = NULL;
|
|
mouse_widget = NULL;
|
|
capture_widget = NULL;
|
|
}
|
|
|
|
setBounds(gfx::Rect(0, 0, ui::display_w(), ui::display_h()));
|
|
setVisible(true);
|
|
|
|
m_dirtyRegion = bounds();
|
|
|
|
// Default manager is the first one (and is always visible).
|
|
if (!m_defaultManager)
|
|
m_defaultManager = this;
|
|
}
|
|
|
|
Manager::~Manager()
|
|
{
|
|
// There are some messages in queue? Dispatch everything.
|
|
dispatchMessages();
|
|
collectGarbage();
|
|
|
|
// Finish the main manager.
|
|
if (m_defaultManager == this) {
|
|
// No more cursor
|
|
set_mouse_cursor(kNoCursor);
|
|
|
|
// Destroy timers
|
|
Timer::checkNoTimers();
|
|
|
|
// Destroy filters
|
|
for (int c=0; c<NFILTERS; ++c) {
|
|
for (Filters::iterator it=msg_filters[c].begin(), end=msg_filters[c].end();
|
|
it != end; ++it)
|
|
delete *it;
|
|
msg_filters[c].clear();
|
|
}
|
|
|
|
// No more default manager
|
|
m_defaultManager = NULL;
|
|
|
|
// Shutdown system
|
|
ASSERT(msg_queue.empty());
|
|
ASSERT(new_windows.empty());
|
|
mouse_widgets_list.clear();
|
|
}
|
|
}
|
|
|
|
void Manager::setDisplay(she::Display* display)
|
|
{
|
|
m_display = display;
|
|
m_eventQueue = she::instance()->eventQueue();
|
|
|
|
onNewDisplayConfiguration();
|
|
}
|
|
|
|
void Manager::setClipboard(she::Clipboard* clipboard)
|
|
{
|
|
m_clipboard = clipboard;
|
|
}
|
|
|
|
void Manager::run()
|
|
{
|
|
MessageLoop loop(this);
|
|
|
|
if (first_time) {
|
|
first_time = false;
|
|
|
|
Manager::getDefault()->invalidate();
|
|
set_mouse_cursor(kArrowCursor);
|
|
}
|
|
|
|
while (!children().empty())
|
|
loop.pumpMessages();
|
|
}
|
|
|
|
void Manager::flipDisplay()
|
|
{
|
|
if (!m_display)
|
|
return;
|
|
|
|
OverlayManager* overlays = OverlayManager::instance();
|
|
|
|
update_cursor_overlay();
|
|
|
|
// Draw overlays.
|
|
overlays->captureOverlappedAreas();
|
|
overlays->drawOverlays();
|
|
|
|
// Flip dirty region.
|
|
{
|
|
m_dirtyRegion.createIntersection(
|
|
m_dirtyRegion,
|
|
gfx::Region(gfx::Rect(0, 0, ui::display_w(), ui::display_h())));
|
|
|
|
for (auto& rc : m_dirtyRegion)
|
|
m_display->flip(rc);
|
|
|
|
m_dirtyRegion.clear();
|
|
}
|
|
|
|
overlays->restoreOverlappedAreas();
|
|
}
|
|
|
|
bool Manager::generateMessages()
|
|
{
|
|
// First check: there are windows to manage?
|
|
if (children().empty())
|
|
return false;
|
|
|
|
// New windows to show?
|
|
if (!new_windows.empty()) {
|
|
for (auto window : new_windows) {
|
|
// Relayout
|
|
window->layout();
|
|
|
|
// Dirty the entire window and show it
|
|
window->setVisible(true);
|
|
window->invalidate();
|
|
|
|
// Attract the focus to the magnetic widget...
|
|
// 1) get the magnetic widget
|
|
Widget* magnet = findMagneticWidget(window->window());
|
|
// 2) if magnetic widget exists and it doesn't have the focus
|
|
if (magnet && !magnet->hasFocus())
|
|
setFocus(magnet);
|
|
// 3) if not, put the focus in the first child
|
|
else if (static_cast<Window*>(window)->isWantFocus())
|
|
focusFirstChild(window);
|
|
}
|
|
|
|
new_windows.clear();
|
|
}
|
|
|
|
generateMessagesFromSheEvents();
|
|
|
|
// Generate messages for timers
|
|
Timer::pollTimers();
|
|
|
|
// Generate redraw events.
|
|
flushRedraw();
|
|
|
|
if (!msg_queue.empty())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Manager::generateSetCursorMessage(const gfx::Point& mousePos,
|
|
KeyModifiers modifiers)
|
|
{
|
|
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
|
|
if (dst)
|
|
enqueueMessage(
|
|
newMouseMessage(
|
|
kSetCursorMessage, dst,
|
|
mousePos, _internal_get_mouse_buttons(),
|
|
modifiers));
|
|
else
|
|
set_mouse_cursor(kArrowCursor);
|
|
}
|
|
|
|
static MouseButtons mouse_buttons_from_she_to_ui(const she::Event& sheEvent)
|
|
{
|
|
switch (sheEvent.button()) {
|
|
case she::Event::LeftButton: return kButtonLeft; break;
|
|
case she::Event::RightButton: return kButtonRight; break;
|
|
case she::Event::MiddleButton: return kButtonMiddle; break;
|
|
default: return kButtonNone;
|
|
}
|
|
}
|
|
|
|
void Manager::generateMessagesFromSheEvents()
|
|
{
|
|
she::Event lastMouseMoveEvent;
|
|
|
|
// Events from "she" layer.
|
|
she::Event sheEvent;
|
|
for (;;) {
|
|
// bool canWait = (msg_queue.empty());
|
|
bool canWait = false;
|
|
|
|
m_eventQueue->getEvent(sheEvent, canWait);
|
|
if (sheEvent.type() == she::Event::None)
|
|
break;
|
|
|
|
switch (sheEvent.type()) {
|
|
|
|
case she::Event::CloseDisplay: {
|
|
Message* msg = new Message(kCloseDisplayMessage);
|
|
msg->broadcastToChildren(this);
|
|
enqueueMessage(msg);
|
|
break;
|
|
}
|
|
|
|
case she::Event::ResizeDisplay: {
|
|
Message* msg = new Message(kResizeDisplayMessage);
|
|
msg->broadcastToChildren(this);
|
|
enqueueMessage(msg);
|
|
break;
|
|
}
|
|
|
|
case she::Event::DropFiles: {
|
|
Message* msg = new DropFilesMessage(sheEvent.files());
|
|
msg->addRecipient(this);
|
|
enqueueMessage(msg);
|
|
break;
|
|
}
|
|
|
|
case she::Event::KeyDown:
|
|
case she::Event::KeyUp: {
|
|
Message* msg = new KeyMessage(
|
|
(sheEvent.type() == she::Event::KeyDown ?
|
|
kKeyDownMessage:
|
|
kKeyUpMessage),
|
|
sheEvent.scancode(),
|
|
sheEvent.modifiers(),
|
|
sheEvent.unicodeChar(),
|
|
sheEvent.repeat());
|
|
broadcastKeyMsg(msg);
|
|
enqueueMessage(msg);
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseEnter: {
|
|
set_mouse_cursor(kArrowCursor);
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseLeave: {
|
|
set_mouse_cursor(kOutsideDisplay);
|
|
setMouse(NULL);
|
|
|
|
_internal_no_mouse_position();
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseMove: {
|
|
#ifndef USE_ALLEG4_BACKEND
|
|
_internal_set_mouse_position(sheEvent.position());
|
|
|
|
handleMouseMove(sheEvent.position(), m_mouseButtons,
|
|
sheEvent.modifiers());
|
|
#endif
|
|
lastMouseMoveEvent = sheEvent;
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseDown: {
|
|
MouseButtons pressedButton = mouse_buttons_from_she_to_ui(sheEvent);
|
|
m_mouseButtons = (MouseButtons)((int)m_mouseButtons | (int)pressedButton);
|
|
_internal_set_mouse_buttons(m_mouseButtons);
|
|
|
|
handleMouseDown(sheEvent.position(), pressedButton,
|
|
sheEvent.modifiers());
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseUp: {
|
|
MouseButtons releasedButton = mouse_buttons_from_she_to_ui(sheEvent);
|
|
m_mouseButtons = (MouseButtons)((int)m_mouseButtons & ~(int)releasedButton);
|
|
_internal_set_mouse_buttons(m_mouseButtons);
|
|
|
|
handleMouseUp(sheEvent.position(), releasedButton,
|
|
sheEvent.modifiers());
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseDoubleClick: {
|
|
MouseButtons clickedButton = mouse_buttons_from_she_to_ui(sheEvent);
|
|
handleMouseDoubleClick(sheEvent.position(), clickedButton,
|
|
sheEvent.modifiers());
|
|
break;
|
|
}
|
|
|
|
case she::Event::MouseWheel: {
|
|
handleMouseWheel(sheEvent.position(), m_mouseButtons,
|
|
sheEvent.modifiers(),
|
|
sheEvent.wheelDelta(),
|
|
sheEvent.preciseWheel());
|
|
break;
|
|
}
|
|
|
|
case she::Event::TouchMagnify: {
|
|
_internal_set_mouse_position(sheEvent.position());
|
|
|
|
handleTouchMagnify(sheEvent.position(),
|
|
sheEvent.modifiers(),
|
|
sheEvent.magnification());
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Generate just one kSetCursorMessage for the last mouse position
|
|
if (lastMouseMoveEvent.type() != she::Event::None) {
|
|
sheEvent = lastMouseMoveEvent;
|
|
|
|
#ifdef USE_ALLEG4_BACKEND
|
|
_internal_set_mouse_position(sheEvent.position());
|
|
|
|
handleMouseMove(sheEvent.position(), m_mouseButtons,
|
|
sheEvent.modifiers());
|
|
#endif
|
|
|
|
generateSetCursorMessage(sheEvent.position(),
|
|
sheEvent.modifiers());
|
|
}
|
|
}
|
|
|
|
void Manager::handleMouseMove(const gfx::Point& mousePos,
|
|
MouseButtons mouseButtons,
|
|
KeyModifiers modifiers)
|
|
{
|
|
// Get the list of widgets to send mouse messages.
|
|
mouse_widgets_list.clear();
|
|
broadcastMouseMessage(mouse_widgets_list);
|
|
|
|
// Get the widget under the mouse
|
|
Widget* widget = nullptr;
|
|
for (auto mouseWidget : mouse_widgets_list) {
|
|
widget = mouseWidget->pick(mousePos);
|
|
if (widget)
|
|
break;
|
|
}
|
|
|
|
// Fixup "mouse" flag
|
|
if (widget != mouse_widget) {
|
|
if (!widget)
|
|
freeMouse();
|
|
else
|
|
setMouse(widget);
|
|
}
|
|
|
|
// Send the mouse movement message
|
|
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
|
|
enqueueMessage(
|
|
newMouseMessage(
|
|
kMouseMoveMessage, dst,
|
|
mousePos, mouseButtons, modifiers));
|
|
}
|
|
|
|
void Manager::handleMouseDown(const gfx::Point& mousePos,
|
|
MouseButtons mouseButtons,
|
|
KeyModifiers modifiers)
|
|
{
|
|
handleWindowZOrder();
|
|
|
|
enqueueMessage(
|
|
newMouseMessage(
|
|
kMouseDownMessage,
|
|
(capture_widget ? capture_widget: mouse_widget),
|
|
mousePos, mouseButtons, modifiers));
|
|
}
|
|
|
|
void Manager::handleMouseUp(const gfx::Point& mousePos,
|
|
MouseButtons mouseButtons,
|
|
KeyModifiers modifiers)
|
|
{
|
|
enqueueMessage(
|
|
newMouseMessage(
|
|
kMouseUpMessage,
|
|
(capture_widget ? capture_widget: mouse_widget),
|
|
mousePos, mouseButtons, modifiers));
|
|
}
|
|
|
|
void Manager::handleMouseDoubleClick(const gfx::Point& mousePos,
|
|
MouseButtons mouseButtons,
|
|
KeyModifiers modifiers)
|
|
{
|
|
Widget* dst = (capture_widget ? capture_widget: mouse_widget);
|
|
if (dst) {
|
|
enqueueMessage(
|
|
newMouseMessage(
|
|
kDoubleClickMessage,
|
|
dst, mousePos, mouseButtons, modifiers));
|
|
}
|
|
}
|
|
|
|
void Manager::handleMouseWheel(const gfx::Point& mousePos,
|
|
MouseButtons mouseButtons, KeyModifiers modifiers,
|
|
const gfx::Point& wheelDelta, bool preciseWheel)
|
|
{
|
|
enqueueMessage(newMouseMessage(
|
|
kMouseWheelMessage,
|
|
(capture_widget ? capture_widget: mouse_widget),
|
|
mousePos, mouseButtons, modifiers,
|
|
wheelDelta, preciseWheel));
|
|
}
|
|
|
|
void Manager::handleTouchMagnify(const gfx::Point& mousePos,
|
|
const KeyModifiers modifiers,
|
|
const double magnification)
|
|
{
|
|
Widget* widget = (capture_widget ? capture_widget: mouse_widget);
|
|
if (widget) {
|
|
Message* msg = new TouchMessage(
|
|
kTouchMagnifyMessage,
|
|
modifiers,
|
|
mousePos,
|
|
magnification);
|
|
|
|
msg->addRecipient(widget);
|
|
|
|
enqueueMessage(msg);
|
|
}
|
|
}
|
|
|
|
// Handles Z order: Send the window to top (only when you click in a
|
|
// window that aren't the desktop).
|
|
void Manager::handleWindowZOrder()
|
|
{
|
|
if (capture_widget || !mouse_widget)
|
|
return;
|
|
|
|
// The clicked window
|
|
Window* window = mouse_widget->window();
|
|
Manager* win_manager = (window ? window->manager(): NULL);
|
|
|
|
if ((window) &&
|
|
// We cannot change Z-order of desktop windows
|
|
(!window->isDesktop()) &&
|
|
// We cannot change Z order of foreground windows because a
|
|
// foreground window can launch other background windows
|
|
// which should be kept on top of the foreground one.
|
|
(!window->isForeground()) &&
|
|
// If the window is not already the top window of the manager.
|
|
(window != win_manager->getTopWindow())) {
|
|
base::ScopedValue<Widget*> scoped(m_lockedWindow, window, NULL);
|
|
|
|
// Put it in the top of the list
|
|
win_manager->removeChild(window);
|
|
|
|
if (window->isOnTop())
|
|
win_manager->insertChild(0, window);
|
|
else {
|
|
int pos = (int)win_manager->children().size();
|
|
UI_FOREACH_WIDGET_BACKWARD(win_manager->children(), it) {
|
|
if (static_cast<Window*>(*it)->isOnTop())
|
|
break;
|
|
|
|
--pos;
|
|
}
|
|
win_manager->insertChild(pos, window);
|
|
}
|
|
|
|
window->invalidate();
|
|
}
|
|
|
|
// Put the focus
|
|
setFocus(mouse_widget);
|
|
}
|
|
|
|
void Manager::dispatchMessages()
|
|
{
|
|
pumpQueue();
|
|
flipDisplay();
|
|
}
|
|
|
|
void Manager::addToGarbage(Widget* widget)
|
|
{
|
|
m_garbage.push_back(widget);
|
|
}
|
|
|
|
/**
|
|
* @param msg You can't use the this message after calling this
|
|
* routine. The message will be automatically freed through
|
|
* @ref jmessage_free
|
|
*/
|
|
void Manager::enqueueMessage(Message* msg)
|
|
{
|
|
ASSERT(msg != NULL);
|
|
|
|
#ifdef REPORT_EVENTS
|
|
if (msg->type() == kKeyDownMessage ||
|
|
msg->type() == kKeyUpMessage) {
|
|
int mods = (int)static_cast<KeyMessage*>(msg)->keyModifiers();
|
|
TRACE("Key%s scancode=%d unicode=%d mods=%s%s%s\n",
|
|
(msg->type() == kKeyDownMessage ? "Down": "Up"),
|
|
static_cast<KeyMessage*>(msg)->scancode(),
|
|
static_cast<KeyMessage*>(msg)->unicodeChar(),
|
|
mods & kKeyShiftModifier ? " Shift": "",
|
|
mods & kKeyCtrlModifier ? " Ctrl": "",
|
|
mods & kKeyAltModifier ? " Alt": "");
|
|
}
|
|
#endif
|
|
|
|
// Check if this message must be filtered by some widget before
|
|
int c = msg->type();
|
|
if (c >= kFirstRegisteredMessage)
|
|
c = kFirstRegisteredMessage;
|
|
|
|
if (!msg_filters[c].empty()) { // OK, so are filters to add...
|
|
// Add all the filters in the destination list of the message
|
|
for (Filters::reverse_iterator it=msg_filters[c].rbegin(),
|
|
end=msg_filters[c].rend(); it != end; ++it) {
|
|
Filter* filter = *it;
|
|
if (msg->type() == filter->message)
|
|
msg->prependRecipient(filter->widget);
|
|
}
|
|
}
|
|
|
|
if (msg->hasRecipients())
|
|
msg_queue.push_back(msg);
|
|
else
|
|
delete msg;
|
|
}
|
|
|
|
Window* Manager::getTopWindow()
|
|
{
|
|
return static_cast<Window*>(UI_FIRST_WIDGET(children()));
|
|
}
|
|
|
|
Window* Manager::getForegroundWindow()
|
|
{
|
|
for (auto child : children()) {
|
|
Window* window = static_cast<Window*>(child);
|
|
if (window->isForeground() ||
|
|
window->isDesktop())
|
|
return window;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Widget* Manager::getFocus()
|
|
{
|
|
return focus_widget;
|
|
}
|
|
|
|
Widget* Manager::getMouse()
|
|
{
|
|
return mouse_widget;
|
|
}
|
|
|
|
Widget* Manager::getCapture()
|
|
{
|
|
return capture_widget;
|
|
}
|
|
|
|
void Manager::setFocus(Widget* widget)
|
|
{
|
|
if ((focus_widget != widget)
|
|
&& (!(widget)
|
|
|| (!(widget->hasFlags(DISABLED))
|
|
&& !(widget->hasFlags(HIDDEN))
|
|
&& !(widget->hasFlags(DECORATIVE))
|
|
&& someParentIsFocusStop(widget)))) {
|
|
WidgetsList widget_parents;
|
|
Widget* common_parent = NULL;
|
|
|
|
if (widget)
|
|
widget->getParents(false, widget_parents);
|
|
|
|
// Fetch the focus
|
|
if (focus_widget) {
|
|
WidgetsList focus_parents;
|
|
focus_widget->getParents(true, focus_parents);
|
|
|
|
Message* msg = new Message(kFocusLeaveMessage);
|
|
|
|
for (Widget* parent1 : focus_parents) {
|
|
if (widget) {
|
|
for (Widget* parent2 : widget_parents) {
|
|
if (parent1 == parent2) {
|
|
common_parent = parent1;
|
|
break;
|
|
}
|
|
}
|
|
if (common_parent)
|
|
break;
|
|
}
|
|
|
|
if (parent1->hasFocus()) {
|
|
parent1->disableFlags(HAS_FOCUS);
|
|
msg->addRecipient(parent1);
|
|
}
|
|
}
|
|
|
|
enqueueMessage(msg);
|
|
}
|
|
|
|
// Put the focus
|
|
focus_widget = widget;
|
|
if (widget) {
|
|
WidgetsList::iterator it;
|
|
|
|
if (common_parent) {
|
|
it = std::find(widget_parents.begin(),
|
|
widget_parents.end(),
|
|
common_parent);
|
|
ASSERT(it != widget_parents.end());
|
|
++it;
|
|
}
|
|
else
|
|
it = widget_parents.begin();
|
|
|
|
Message* msg = new Message(kFocusEnterMessage);
|
|
|
|
for (; it != widget_parents.end(); ++it) {
|
|
Widget* w = *it;
|
|
|
|
if (w->hasFlags(FOCUS_STOP)) {
|
|
w->enableFlags(HAS_FOCUS);
|
|
msg->addRecipient(w);
|
|
}
|
|
}
|
|
|
|
enqueueMessage(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Manager::setMouse(Widget* widget)
|
|
{
|
|
#ifdef REPORT_EVENTS
|
|
std::cout << "Manager::setMouse ";
|
|
if (widget) {
|
|
std::cout << typeid(*widget).name();
|
|
if (!widget->getId().empty())
|
|
std::cout << " (" << widget->getId() << ")";
|
|
}
|
|
else {
|
|
std::cout << "null";
|
|
}
|
|
std::cout << std::endl;
|
|
#endif
|
|
|
|
if ((mouse_widget != widget) && (!capture_widget)) {
|
|
WidgetsList widget_parents;
|
|
Widget* common_parent = NULL;
|
|
|
|
if (widget)
|
|
widget->getParents(false, widget_parents);
|
|
|
|
// Fetch the mouse
|
|
if (mouse_widget) {
|
|
WidgetsList mouse_parents;
|
|
mouse_widget->getParents(true, mouse_parents);
|
|
|
|
Message* msg = new Message(kMouseLeaveMessage);
|
|
|
|
for (Widget* parent1 : mouse_parents) {
|
|
if (widget) {
|
|
for (Widget* parent2 : widget_parents) {
|
|
if (parent1 == parent2) {
|
|
common_parent = parent1;
|
|
break;
|
|
}
|
|
}
|
|
if (common_parent)
|
|
break;
|
|
}
|
|
|
|
if (parent1->hasMouse()) {
|
|
parent1->disableFlags(HAS_MOUSE);
|
|
msg->addRecipient(parent1);
|
|
}
|
|
}
|
|
|
|
enqueueMessage(msg);
|
|
}
|
|
|
|
// Put the mouse
|
|
mouse_widget = widget;
|
|
if (widget) {
|
|
WidgetsList::iterator it;
|
|
|
|
if (common_parent) {
|
|
it = std::find(widget_parents.begin(),
|
|
widget_parents.end(),
|
|
common_parent);
|
|
ASSERT(it != widget_parents.end());
|
|
++it;
|
|
}
|
|
else
|
|
it = widget_parents.begin();
|
|
|
|
Message* msg = newMouseMessage(
|
|
kMouseEnterMessage, NULL,
|
|
get_mouse_position(), _internal_get_mouse_buttons(),
|
|
kKeyUninitializedModifier);
|
|
|
|
for (; it != widget_parents.end(); ++it) {
|
|
(*it)->enableFlags(HAS_MOUSE);
|
|
msg->addRecipient(*it);
|
|
}
|
|
|
|
enqueueMessage(msg);
|
|
generateSetCursorMessage(get_mouse_position(),
|
|
kKeyUninitializedModifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Manager::setCapture(Widget* widget)
|
|
{
|
|
widget->enableFlags(HAS_CAPTURE);
|
|
capture_widget = widget;
|
|
|
|
m_display->captureMouse();
|
|
}
|
|
|
|
// Sets the focus to the "magnetic" widget inside the window
|
|
void Manager::attractFocus(Widget* widget)
|
|
{
|
|
// Get the magnetic widget
|
|
Widget* magnet = findMagneticWidget(widget->window());
|
|
|
|
// If magnetic widget exists and it doesn't have the focus
|
|
if (magnet && !magnet->hasFocus())
|
|
setFocus(magnet);
|
|
}
|
|
|
|
void Manager::focusFirstChild(Widget* widget)
|
|
{
|
|
for (Widget* it=widget->window(); it; it=next_widget(it)) {
|
|
if (ACCEPT_FOCUS(it) && !(childs_accept_focus(it, true))) {
|
|
setFocus(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Manager::freeFocus()
|
|
{
|
|
setFocus(NULL);
|
|
}
|
|
|
|
void Manager::freeMouse()
|
|
{
|
|
setMouse(NULL);
|
|
}
|
|
|
|
void Manager::freeCapture()
|
|
{
|
|
if (capture_widget) {
|
|
capture_widget->disableFlags(HAS_CAPTURE);
|
|
capture_widget = NULL;
|
|
|
|
m_display->releaseMouse();
|
|
}
|
|
}
|
|
|
|
void Manager::freeWidget(Widget* widget)
|
|
{
|
|
if (widget->hasFocus() || (widget == focus_widget))
|
|
freeFocus();
|
|
|
|
// We shouldn't free widgets that are locked, it means, widgets that
|
|
// will be re-added soon (e.g. when the stack of windows is
|
|
// temporarily modified).
|
|
if (m_lockedWindow == widget)
|
|
return;
|
|
|
|
// Break any relationship with the GUI manager
|
|
if (widget->hasCapture() || (widget == capture_widget))
|
|
freeCapture();
|
|
|
|
if (widget->hasMouse() || (widget == mouse_widget))
|
|
freeMouse();
|
|
}
|
|
|
|
void Manager::removeMessage(Message* msg)
|
|
{
|
|
auto it = std::find(msg_queue.begin(), msg_queue.end(), msg);
|
|
ASSERT(it != msg_queue.end());
|
|
msg_queue.erase(it);
|
|
}
|
|
|
|
void Manager::removeMessagesFor(Widget* widget)
|
|
{
|
|
for (Message* msg : msg_queue)
|
|
removeWidgetFromRecipients(widget, msg);
|
|
}
|
|
|
|
void Manager::removeMessagesFor(Widget* widget, MessageType type)
|
|
{
|
|
for (Message* msg : msg_queue)
|
|
if (msg->type() == type)
|
|
removeWidgetFromRecipients(widget, msg);
|
|
}
|
|
|
|
void Manager::removeMessagesForTimer(Timer* timer)
|
|
{
|
|
for (auto it=msg_queue.begin(); it != msg_queue.end(); ) {
|
|
Message* msg = *it;
|
|
|
|
if (!msg->isUsed() &&
|
|
msg->type() == kTimerMessage &&
|
|
static_cast<TimerMessage*>(msg)->timer() == timer) {
|
|
delete msg;
|
|
it = msg_queue.erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void Manager::addMessageFilter(int message, Widget* widget)
|
|
{
|
|
int c = message;
|
|
if (c >= kFirstRegisteredMessage)
|
|
c = kFirstRegisteredMessage;
|
|
|
|
msg_filters[c].push_back(new Filter(message, widget));
|
|
}
|
|
|
|
void Manager::removeMessageFilter(int message, Widget* widget)
|
|
{
|
|
int c = message;
|
|
if (c >= kFirstRegisteredMessage)
|
|
c = kFirstRegisteredMessage;
|
|
|
|
for (Filters::iterator it=msg_filters[c].begin(); it != msg_filters[c].end(); ) {
|
|
Filter* filter = *it;
|
|
if (filter->widget == widget) {
|
|
delete filter;
|
|
it = msg_filters[c].erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void Manager::removeMessageFilterFor(Widget* widget)
|
|
{
|
|
for (int c=0; c<NFILTERS; ++c) {
|
|
for (Filters::iterator it=msg_filters[c].begin(); it != msg_filters[c].end(); ) {
|
|
Filter* filter = *it;
|
|
if (filter->widget == widget) {
|
|
delete filter;
|
|
it = msg_filters[c].erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Manager::isFocusMovementKey(Message* msg)
|
|
{
|
|
switch (static_cast<KeyMessage*>(msg)->scancode()) {
|
|
|
|
case kKeyTab:
|
|
case kKeyLeft:
|
|
case kKeyRight:
|
|
case kKeyUp:
|
|
case kKeyDown:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Manager::dirtyRect(const gfx::Rect& bounds)
|
|
{
|
|
m_dirtyRegion.createUnion(m_dirtyRegion, gfx::Region(bounds));
|
|
}
|
|
|
|
/* configures the window for begin the loop */
|
|
void Manager::_openWindow(Window* window)
|
|
{
|
|
// Free all widgets of special states.
|
|
if (window->isWantFocus()) {
|
|
freeCapture();
|
|
freeMouse();
|
|
freeFocus();
|
|
}
|
|
|
|
// Add the window to manager.
|
|
insertChild(0, window);
|
|
|
|
// Broadcast the open message.
|
|
Message* msg = new Message(kOpenMessage);
|
|
msg->addRecipient(window);
|
|
enqueueMessage(msg);
|
|
|
|
// Update the new windows list to show.
|
|
new_windows.push_back(window);
|
|
}
|
|
|
|
void Manager::_closeWindow(Window* window, bool redraw_background)
|
|
{
|
|
if (!hasChild(window))
|
|
return;
|
|
|
|
gfx::Region reg1;
|
|
if (redraw_background)
|
|
window->getRegion(reg1);
|
|
|
|
// Close all windows to this desktop
|
|
if (window->isDesktop()) {
|
|
while (!children().empty()) {
|
|
Window* child = static_cast<Window*>(children().front());
|
|
if (child == window)
|
|
break;
|
|
else {
|
|
gfx::Region reg2;
|
|
window->getRegion(reg2);
|
|
reg1.createUnion(reg1, reg2);
|
|
|
|
_closeWindow(child, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free all widgets of special states.
|
|
if (capture_widget && capture_widget->window() == window)
|
|
freeCapture();
|
|
|
|
if (mouse_widget && mouse_widget->window() == window)
|
|
freeMouse();
|
|
|
|
if (focus_widget && focus_widget->window() == window)
|
|
freeFocus();
|
|
|
|
// Hide window.
|
|
window->setVisible(false);
|
|
|
|
// Close message.
|
|
Message* msg = new Message(kCloseMessage);
|
|
msg->addRecipient(window);
|
|
enqueueMessage(msg);
|
|
|
|
// Update manager list stuff.
|
|
removeChild(window);
|
|
|
|
// Redraw background.
|
|
invalidateRegion(reg1);
|
|
|
|
// Maybe the window is in the "new_windows" list.
|
|
WidgetsList::iterator it =
|
|
std::find(new_windows.begin(), new_windows.end(), window);
|
|
if (it != new_windows.end())
|
|
new_windows.erase(it);
|
|
|
|
// Update mouse widget (as it can be a widget below the
|
|
// recently closed window).
|
|
Widget* widget = pick(ui::get_mouse_position());
|
|
if (widget)
|
|
setMouse(widget);
|
|
}
|
|
|
|
bool Manager::onProcessMessage(Message* msg)
|
|
{
|
|
switch (msg->type()) {
|
|
|
|
case kResizeDisplayMessage:
|
|
onNewDisplayConfiguration();
|
|
break;
|
|
|
|
case kKeyDownMessage:
|
|
case kKeyUpMessage: {
|
|
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
|
|
keymsg->setPropagateToChildren(true);
|
|
keymsg->setPropagateToParent(false);
|
|
|
|
// Continue sending the message to the children of all windows
|
|
// (until a desktop or foreground window).
|
|
Window* win = nullptr;
|
|
for (auto manchild : children()) {
|
|
win = static_cast<Window*>(manchild);
|
|
|
|
// Send to the window.
|
|
for (auto winchild : win->children())
|
|
if (winchild->sendMessage(msg))
|
|
return true;
|
|
|
|
if (win->isForeground() ||
|
|
win->isDesktop())
|
|
break;
|
|
}
|
|
|
|
// Check the focus movement for foreground (non-desktop) windows.
|
|
if (win && win->isForeground()) {
|
|
if (msg->type() == kKeyDownMessage)
|
|
move_focus(this, msg);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
return Widget::onProcessMessage(msg);
|
|
}
|
|
|
|
void Manager::onResize(ResizeEvent& ev)
|
|
{
|
|
gfx::Rect old_pos = bounds();
|
|
gfx::Rect new_pos = ev.bounds();
|
|
setBoundsQuietly(new_pos);
|
|
|
|
// The whole manager area is invalid now.
|
|
m_invalidRegion = gfx::Region(new_pos);
|
|
|
|
int dx = new_pos.x - old_pos.x;
|
|
int dy = new_pos.y - old_pos.y;
|
|
int dw = new_pos.w - old_pos.w;
|
|
int dh = new_pos.h - old_pos.h;
|
|
|
|
for (auto child : children()) {
|
|
Window* window = static_cast<Window*>(child);
|
|
if (window->isDesktop()) {
|
|
window->setBounds(new_pos);
|
|
break;
|
|
}
|
|
|
|
gfx::Rect cpos = window->bounds();
|
|
int cx = cpos.x+cpos.w/2;
|
|
int cy = cpos.y+cpos.h/2;
|
|
|
|
if (cx > old_pos.x+old_pos.w*3/5) {
|
|
cpos.x += dw;
|
|
}
|
|
else if (cx > old_pos.x+old_pos.w*2/5) {
|
|
cpos.x += dw / 2;
|
|
}
|
|
|
|
if (cy > old_pos.y+old_pos.h*3/5) {
|
|
cpos.y += dh;
|
|
}
|
|
else if (cy > old_pos.y+old_pos.h*2/5) {
|
|
cpos.y += dh / 2;
|
|
}
|
|
|
|
cpos.offset(dx, dy);
|
|
window->setBounds(cpos);
|
|
}
|
|
}
|
|
|
|
void Manager::onPaint(PaintEvent& ev)
|
|
{
|
|
theme()->paintDesktop(ev);
|
|
}
|
|
|
|
void Manager::onBroadcastMouseMessage(WidgetsList& targets)
|
|
{
|
|
// Ask to the first window in the "children" list to know how to
|
|
// propagate mouse messages.
|
|
Widget* widget = UI_FIRST_WIDGET(children());
|
|
if (widget)
|
|
widget->broadcastMouseMessage(targets);
|
|
}
|
|
|
|
LayoutIO* Manager::onGetLayoutIO()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void Manager::onNewDisplayConfiguration()
|
|
{
|
|
if (m_display) {
|
|
int w = m_display->width() / m_display->scale();
|
|
int h = m_display->height() / m_display->scale();
|
|
if ((bounds().w != w ||
|
|
bounds().h != h)) {
|
|
setBounds(gfx::Rect(0, 0, w, h));
|
|
}
|
|
}
|
|
|
|
_internal_set_mouse_display(m_display);
|
|
invalidate();
|
|
flushRedraw();
|
|
}
|
|
|
|
void Manager::onSizeHint(SizeHintEvent& ev)
|
|
{
|
|
int w = 0, h = 0;
|
|
|
|
if (!parent()) { // hasn' parent?
|
|
w = bounds().w;
|
|
h = bounds().h;
|
|
}
|
|
else {
|
|
gfx::Rect pos = parent()->childrenBounds();
|
|
|
|
for (auto child : children()) {
|
|
gfx::Rect cpos = child->bounds();
|
|
pos = pos.createUnion(cpos);
|
|
}
|
|
|
|
w = pos.w;
|
|
h = pos.h;
|
|
}
|
|
|
|
ev.setSizeHint(gfx::Size(w, h));
|
|
}
|
|
|
|
void Manager::pumpQueue()
|
|
{
|
|
#ifdef LIMIT_DISPATCH_TIME
|
|
int t = ui::clock();
|
|
#endif
|
|
|
|
Messages::iterator it = msg_queue.begin();
|
|
while (it != msg_queue.end()) {
|
|
#ifdef LIMIT_DISPATCH_TIME
|
|
if (ui::clock()-t > 250)
|
|
break;
|
|
#endif
|
|
|
|
// The message to process
|
|
Message* msg = *it;
|
|
|
|
// Go to next message
|
|
if (msg->isUsed()) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
// This message is in use
|
|
msg->markAsUsed();
|
|
Message* first_msg = msg;
|
|
|
|
// Call Timer::tick() if this is a tick message.
|
|
if (msg->type() == kTimerMessage) {
|
|
ASSERT(static_cast<TimerMessage*>(msg)->timer() != NULL);
|
|
static_cast<TimerMessage*>(msg)->timer()->tick();
|
|
}
|
|
|
|
bool done = false;
|
|
for (auto widget : msg->recipients()) {
|
|
if (!widget)
|
|
continue;
|
|
|
|
#ifdef REPORT_EVENTS
|
|
{
|
|
static char *msg_name[] = {
|
|
"kOpenMessage",
|
|
"kCloseMessage",
|
|
"kCloseDisplayMessage",
|
|
"kResizeDisplayMessage",
|
|
"kPaintMessage",
|
|
"kTimerMessage",
|
|
"kDropFilesMessage",
|
|
"kWinMoveMessage",
|
|
|
|
"kKeyDownMessage",
|
|
"kKeyUpMessage",
|
|
"kFocusEnterMessage",
|
|
"kFocusLeaveMessage",
|
|
|
|
"kMouseDownMessage",
|
|
"kMouseUpMessage",
|
|
"kDoubleClickMessage",
|
|
"kMouseEnterMessage",
|
|
"kMouseLeaveMessage",
|
|
"kMouseMoveMessage",
|
|
"kSetCursorMessage",
|
|
"kMouseWheelMessage",
|
|
"kTouchMagnifyMessage",
|
|
};
|
|
const char* string =
|
|
(msg->type() >= kOpenMessage &&
|
|
msg->type() <= kMouseWheelMessage) ? msg_name[msg->type()]:
|
|
"Unknown";
|
|
|
|
std::cout << "Event " << msg->type() << " (" << string << ") "
|
|
<< "for " << typeid(*widget).name();
|
|
if (!widget->getId().empty())
|
|
std::cout << " (" << widget->getId() << ")";
|
|
std::cout << std::endl;
|
|
}
|
|
#endif
|
|
|
|
// We need to configure the clip region for paint messages
|
|
// before we call Widget::sendMessage().
|
|
if (msg->type() == kPaintMessage) {
|
|
if (widget->hasFlags(HIDDEN))
|
|
continue;
|
|
|
|
PaintMessage* paintMsg = static_cast<PaintMessage*>(msg);
|
|
she::NonDisposableSurface* surface = m_display->getSurface();
|
|
gfx::Rect oldClip = surface->getClipBounds();
|
|
|
|
if (surface->intersectClipRect(paintMsg->rect())) {
|
|
#ifdef REPORT_EVENTS
|
|
std::cout << " - clip("
|
|
<< paintMsg->rect().x << ", "
|
|
<< paintMsg->rect().y << ", "
|
|
<< paintMsg->rect().w << ", "
|
|
<< paintMsg->rect().h << ")"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
#ifdef DEBUG_PAINT_EVENTS
|
|
{
|
|
she::ScopedSurfaceLock lock(surface);
|
|
lock->fillRect(gfx::rgba(0, 0, 255), paintMsg->rect());
|
|
}
|
|
|
|
if (m_display)
|
|
m_display->flip(gfx::Rect(0, 0, display_w(), display_h()));
|
|
|
|
base::this_thread::sleep_for(0.002);
|
|
#endif
|
|
|
|
if (surface) {
|
|
// Call the message handler
|
|
done = widget->sendMessage(msg);
|
|
|
|
// Restore clip region for paint messages.
|
|
surface->setClipBounds(oldClip);
|
|
}
|
|
}
|
|
|
|
// As this kPaintMessage's rectangle was updated, we can
|
|
// remove it from "m_invalidRegion".
|
|
m_invalidRegion -= gfx::Region(paintMsg->rect());
|
|
}
|
|
else {
|
|
// Call the message handler
|
|
done = widget->sendMessage(msg);
|
|
}
|
|
|
|
if (done)
|
|
break;
|
|
}
|
|
|
|
// Remove the message from the msg_queue
|
|
it = msg_queue.erase(it);
|
|
|
|
// Destroy the message
|
|
delete first_msg;
|
|
}
|
|
}
|
|
|
|
void Manager::invalidateDisplayRegion(const gfx::Region& region)
|
|
{
|
|
// TODO intersect with getDrawableRegion()???
|
|
gfx::Region reg1;
|
|
reg1.createIntersection(region, gfx::Region(bounds()));
|
|
|
|
// Redraw windows from top to background.
|
|
bool withDesktop = false;
|
|
for (auto child : children()) {
|
|
ASSERT(dynamic_cast<Window*>(child));
|
|
ASSERT(child->type() == kWindowWidget);
|
|
Window* window = static_cast<Window*>(child);
|
|
|
|
// Invalidate regions of this window
|
|
window->invalidateRegion(reg1);
|
|
|
|
// There is desktop?
|
|
if (window->isDesktop()) {
|
|
withDesktop = true;
|
|
break; // Work done
|
|
}
|
|
|
|
// Clip this window area for the next window.
|
|
gfx::Region reg3;
|
|
window->getRegion(reg3);
|
|
reg1.createSubtraction(reg1, reg3);
|
|
}
|
|
|
|
// Invalidate areas outside windows (only when there are not a
|
|
// desktop window).
|
|
if (!withDesktop)
|
|
Widget::invalidateRegion(reg1);
|
|
}
|
|
|
|
LayoutIO* Manager::getLayoutIO()
|
|
{
|
|
return onGetLayoutIO();
|
|
}
|
|
|
|
void Manager::collectGarbage()
|
|
{
|
|
if (m_garbage.empty())
|
|
return;
|
|
|
|
for (WidgetsList::iterator
|
|
it = m_garbage.begin(),
|
|
end = m_garbage.end(); it != end; ++it) {
|
|
delete *it;
|
|
}
|
|
m_garbage.clear();
|
|
}
|
|
|
|
/**********************************************************************
|
|
Internal routines
|
|
**********************************************************************/
|
|
|
|
// static
|
|
void Manager::removeWidgetFromRecipients(Widget* widget, Message* msg)
|
|
{
|
|
msg->removeRecipient(widget);
|
|
}
|
|
|
|
// static
|
|
bool Manager::someParentIsFocusStop(Widget* widget)
|
|
{
|
|
if (widget->isFocusStop())
|
|
return true;
|
|
|
|
if (widget->parent())
|
|
return someParentIsFocusStop(widget->parent());
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
Widget* Manager::findMagneticWidget(Widget* widget)
|
|
{
|
|
Widget* found;
|
|
|
|
for (auto child : widget->children()) {
|
|
found = findMagneticWidget(child);
|
|
if (found)
|
|
return found;
|
|
}
|
|
|
|
if (widget->isFocusMagnet())
|
|
return widget;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
// static
|
|
Message* Manager::newMouseMessage(
|
|
MessageType type,
|
|
Widget* widget, const gfx::Point& mousePos,
|
|
MouseButtons buttons, KeyModifiers modifiers,
|
|
const gfx::Point& wheelDelta, bool preciseWheel)
|
|
{
|
|
Message* msg = new MouseMessage(
|
|
type, buttons, modifiers, mousePos,
|
|
wheelDelta, preciseWheel);
|
|
|
|
if (widget != NULL)
|
|
msg->addRecipient(widget);
|
|
|
|
return msg;
|
|
}
|
|
|
|
// static
|
|
void Manager::broadcastKeyMsg(Message* msg)
|
|
{
|
|
// Send the message to the widget with capture
|
|
if (capture_widget) {
|
|
msg->addRecipient(capture_widget);
|
|
}
|
|
// Send the msg to the focused widget
|
|
else if (focus_widget) {
|
|
msg->addRecipient(focus_widget);
|
|
}
|
|
// Finally, send the message to the manager, it'll know what to do
|
|
else {
|
|
msg->addRecipient(this);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
Focus Movement
|
|
***********************************************************************/
|
|
|
|
static bool move_focus(Manager* manager, Message* msg)
|
|
{
|
|
int (*cmp)(Widget*, int, int) = NULL;
|
|
Widget* focus = NULL;
|
|
Widget* it;
|
|
bool ret = false;
|
|
Window* window = NULL;
|
|
int c, count;
|
|
|
|
// Who have the focus
|
|
if (focus_widget) {
|
|
window = focus_widget->window();
|
|
}
|
|
else if (!manager->children().empty()) {
|
|
window = manager->getTopWindow();
|
|
}
|
|
|
|
if (!window)
|
|
return false;
|
|
|
|
// How many child-widget want the focus in this widget?
|
|
count = count_widgets_accept_focus(window);
|
|
|
|
// One at least
|
|
if (count > 0) {
|
|
std::vector<Widget*> list(count);
|
|
|
|
c = 0;
|
|
|
|
/* list's 1st element is the focused widget */
|
|
for (it=focus_widget; it; it=next_widget(it)) {
|
|
if (ACCEPT_FOCUS(it) && !(childs_accept_focus(it, true)))
|
|
list[c++] = it;
|
|
}
|
|
|
|
for (it=window; it != focus_widget; it=next_widget(it)) {
|
|
if (ACCEPT_FOCUS(it) && !(childs_accept_focus(it, true)))
|
|
list[c++] = it;
|
|
}
|
|
|
|
// Depending on the pressed key...
|
|
switch (static_cast<KeyMessage*>(msg)->scancode()) {
|
|
|
|
case kKeyTab:
|
|
// Reverse tab
|
|
if ((msg->modifiers() & (kKeyShiftModifier | kKeyCtrlModifier | kKeyAltModifier)) != 0) {
|
|
focus = list[count-1];
|
|
}
|
|
// Normal tab
|
|
else if (count > 1) {
|
|
focus = list[1];
|
|
}
|
|
ret = true;
|
|
break;
|
|
|
|
// Arrow keys
|
|
case kKeyLeft: if (!cmp) cmp = cmp_left;
|
|
case kKeyRight: if (!cmp) cmp = cmp_right;
|
|
case kKeyUp: if (!cmp) cmp = cmp_up;
|
|
case kKeyDown: if (!cmp) cmp = cmp_down;
|
|
|
|
// More than one widget
|
|
if (count > 1) {
|
|
int i, j, x, y;
|
|
|
|
// Position where the focus come
|
|
x = ((focus_widget) ? focus_widget->bounds().x+focus_widget->bounds().x2():
|
|
window->bounds().x+window->bounds().x2())
|
|
/ 2;
|
|
y = ((focus_widget) ? focus_widget->bounds().y+focus_widget->bounds().y2():
|
|
window->bounds().y+window->bounds().y2())
|
|
/ 2;
|
|
|
|
c = focus_widget ? 1: 0;
|
|
|
|
// Rearrange the list
|
|
for (i=c; i<count-1; i++) {
|
|
for (j=i+1; j<count; j++) {
|
|
// Sort the list in ascending order
|
|
if ((*cmp) (list[i], x, y) > (*cmp) (list[j], x, y)) {
|
|
Widget* tmp = list[i];
|
|
list[i] = list[j];
|
|
list[j] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the new widget to put the focus is not in the wrong way.
|
|
if ((*cmp) (list[c], x, y) < std::numeric_limits<int>::max())
|
|
focus = list[c];
|
|
}
|
|
// If only there are one widget, put the focus in this
|
|
else
|
|
focus = list[0];
|
|
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
if ((focus) && (focus != focus_widget))
|
|
Manager::getDefault()->setFocus(focus);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int count_widgets_accept_focus(Widget* widget)
|
|
{
|
|
ASSERT(widget != NULL);
|
|
|
|
int count = 0;
|
|
|
|
for (auto child : widget->children())
|
|
count += count_widgets_accept_focus(child);
|
|
|
|
if ((count == 0) && (ACCEPT_FOCUS(widget)))
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
static bool childs_accept_focus(Widget* widget, bool first)
|
|
{
|
|
for (auto child : widget->children())
|
|
if (childs_accept_focus(child, false))
|
|
return true;
|
|
|
|
return (first ? false: ACCEPT_FOCUS(widget));
|
|
}
|
|
|
|
static Widget* next_widget(Widget* widget)
|
|
{
|
|
if (!widget->children().empty())
|
|
return UI_FIRST_WIDGET(widget->children());
|
|
|
|
while (widget->parent()->type() != kManagerWidget) {
|
|
WidgetsList::const_iterator begin = widget->parent()->children().begin();
|
|
WidgetsList::const_iterator end = widget->parent()->children().end();
|
|
WidgetsList::const_iterator it = std::find(begin, end, widget);
|
|
|
|
ASSERT(it != end);
|
|
|
|
if ((it+1) != end)
|
|
return *(it+1);
|
|
else
|
|
widget = widget->parent();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int cmp_left(Widget* widget, int x, int y)
|
|
{
|
|
int z = x - (widget->bounds().x+widget->bounds().w/2);
|
|
if (z <= 0)
|
|
return std::numeric_limits<int>::max();
|
|
return z + ABS((widget->bounds().y+widget->bounds().h/2) - y) * 8;
|
|
}
|
|
|
|
static int cmp_right(Widget* widget, int x, int y)
|
|
{
|
|
int z = (widget->bounds().x+widget->bounds().w/2) - x;
|
|
if (z <= 0)
|
|
return std::numeric_limits<int>::max();
|
|
return z + ABS((widget->bounds().y+widget->bounds().h/2) - y) * 8;
|
|
}
|
|
|
|
static int cmp_up(Widget* widget, int x, int y)
|
|
{
|
|
int z = y - (widget->bounds().y+widget->bounds().h/2);
|
|
if (z <= 0)
|
|
return std::numeric_limits<int>::max();
|
|
return z + ABS((widget->bounds().x+widget->bounds().w/2) - x) * 8;
|
|
}
|
|
|
|
static int cmp_down(Widget* widget, int x, int y)
|
|
{
|
|
int z = (widget->bounds().y+widget->bounds().h/2) - y;
|
|
if (z <= 0)
|
|
return std::numeric_limits<int>::max();
|
|
return z + ABS((widget->bounds().x+widget->bounds().w/2) - x) * 8;
|
|
}
|
|
|
|
} // namespace ui
|