1 // Aseprite UI Library
2 // Copyright (C) 2001-2017  David Capello
3 //
4 // This file is released under the terms of the MIT license.
5 // Read LICENSE.txt for more information.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "gfx/size.h"
12 #include "ui/graphics.h"
13 #include "ui/intern.h"
14 #include "ui/size_hint_event.h"
15 #include "ui/theme.h"
16 #include "ui/ui.h"
17 
18 namespace ui {
19 
20 using namespace gfx;
21 
PopupWindow(const std::string & text,const ClickBehavior clickBehavior,const EnterBehavior enterBehavior,const bool withCloseButton)22 PopupWindow::PopupWindow(const std::string& text,
23                          const ClickBehavior clickBehavior,
24                          const EnterBehavior enterBehavior,
25                          const bool withCloseButton)
26   : Window(text.empty() ? WithoutTitleBar: WithTitleBar, text)
27   , m_clickBehavior(clickBehavior)
28   , m_enterBehavior(enterBehavior)
29   , m_filtering(false)
30   , m_fixed(false)
31 {
32   setSizeable(false);
33   setMoveable(false);
34   setWantFocus(false);
35   setAlign(LEFT | TOP);
36 
37   if (!withCloseButton) {
38     // Remove close button
39     for (auto child : children()) {
40       if (child->type() == kWindowCloseButtonWidget) {
41         delete child;
42         break;
43       }
44     }
45   }
46 
47   initTheme();
48 }
49 
~PopupWindow()50 PopupWindow::~PopupWindow()
51 {
52   stopFilteringMessages();
53 }
54 
setHotRegion(const gfx::Region & region)55 void PopupWindow::setHotRegion(const gfx::Region& region)
56 {
57   startFilteringMessages();
58 
59   m_hotRegion = region;
60 }
61 
setClickBehavior(ClickBehavior behavior)62 void PopupWindow::setClickBehavior(ClickBehavior behavior)
63 {
64   m_clickBehavior = behavior;
65 }
66 
setEnterBehavior(EnterBehavior behavior)67 void PopupWindow::setEnterBehavior(EnterBehavior behavior)
68 {
69   m_enterBehavior = behavior;
70 }
71 
makeFloating()72 void PopupWindow::makeFloating()
73 {
74   stopFilteringMessages();
75   setMoveable(true);
76   m_fixed = false;
77 
78   onMakeFloating();
79 }
80 
makeFixed()81 void PopupWindow::makeFixed()
82 {
83   startFilteringMessages();
84   setMoveable(false);
85   m_fixed = true;
86 
87   onMakeFixed();
88 }
89 
onProcessMessage(Message * msg)90 bool PopupWindow::onProcessMessage(Message* msg)
91 {
92   switch (msg->type()) {
93 
94     // There are cases where startFilteringMessages() is called when a
95     // kCloseMessage for this same PopupWindow is enqueued. Processing
96     // the kOpenMessage we ensure that the popup will be filtering
97     // messages if it's needed when it's visible (as kCloseMessage and
98     // kOpenMessage must be enqueued in the correct order).
99     case kOpenMessage:
100       if (!isMoveable())
101         startFilteringMessages();
102       break;
103 
104     case kCloseMessage:
105       stopFilteringMessages();
106       break;
107 
108     case kMouseLeaveMessage:
109       if (m_hotRegion.isEmpty() && m_fixed)
110         closeWindow(nullptr);
111       break;
112 
113     case kKeyDownMessage:
114       if (m_filtering) {
115         KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
116         KeyScancode scancode = keymsg->scancode();
117 
118         if (scancode == kKeyEsc)
119           closeWindow(nullptr);
120 
121         if (m_enterBehavior == EnterBehavior::CloseOnEnter &&
122             (scancode == kKeyEnter ||
123              scancode == kKeyEnterPad)) {
124           closeWindow(this);
125           return true;
126         }
127 
128         // If the message came from a filter, we don't send it back to
129         // the default Window processing (which will send the message
130         // to the Manager). In this way, the focused children can
131         // process the kKeyDownMessage.
132         if (msg->fromFilter())
133           return false;
134       }
135       break;
136 
137     case kMouseDownMessage:
138       if (m_filtering &&
139           manager()->getTopWindow() == this) {
140         gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
141 
142         switch (m_clickBehavior) {
143 
144           // If the user click outside the window, we have to close
145           // the tooltip window.
146           case ClickBehavior::CloseOnClickInOtherWindow: {
147             Widget* picked = pick(mousePos);
148             if (!picked || picked->window() != this) {
149               closeWindow(NULL);
150             }
151             break;
152           }
153 
154           case ClickBehavior::CloseOnClickOutsideHotRegion:
155             if (!m_hotRegion.contains(mousePos)) {
156               closeWindow(NULL);
157             }
158             break;
159         }
160       }
161       break;
162 
163     case kMouseMoveMessage:
164       if (m_fixed &&
165           !m_hotRegion.isEmpty() &&
166           manager()->getCapture() == NULL) {
167         gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
168 
169         // If the mouse is outside the hot-region we have to close the
170         // window.
171         if (!m_hotRegion.contains(mousePos))
172           closeWindow(NULL);
173       }
174       break;
175 
176   }
177 
178   return Window::onProcessMessage(msg);
179 }
180 
onHitTest(HitTestEvent & ev)181 void PopupWindow::onHitTest(HitTestEvent& ev)
182 {
183   Window::onHitTest(ev);
184 
185   Widget* picked = manager()->pick(ev.point());
186   if (picked) {
187     WidgetType type = picked->type();
188     if (type == kWindowWidget && picked == this) {
189       if (isSizeable() && (ev.hit() == HitTestBorderNW ||
190                            ev.hit() == HitTestBorderN ||
191                            ev.hit() == HitTestBorderNE ||
192                            ev.hit() == HitTestBorderE ||
193                            ev.hit() == HitTestBorderSE ||
194                            ev.hit() == HitTestBorderS ||
195                            ev.hit() == HitTestBorderSW ||
196                            ev.hit() == HitTestBorderW)) {
197         // Use the hit value from Window::onHitTest()
198         return;
199       }
200       else {
201         ev.setHit(isMoveable() ? HitTestCaption: HitTestClient);
202       }
203     }
204     else if (type == kBoxWidget ||
205              type == kLabelWidget ||
206              type == kLinkLabelWidget ||
207              type == kGridWidget ||
208              type == kSeparatorWidget) {
209       ev.setHit(isMoveable() ? HitTestCaption: HitTestClient);
210     }
211   }
212 }
213 
startFilteringMessages()214 void PopupWindow::startFilteringMessages()
215 {
216   if (!m_filtering) {
217     m_filtering = true;
218 
219     Manager* manager = Manager::getDefault();
220     manager->addMessageFilter(kMouseMoveMessage, this);
221     manager->addMessageFilter(kMouseDownMessage, this);
222     manager->addMessageFilter(kKeyDownMessage, this);
223   }
224 }
225 
stopFilteringMessages()226 void PopupWindow::stopFilteringMessages()
227 {
228   if (m_filtering) {
229     m_filtering = false;
230 
231     Manager* manager = Manager::getDefault();
232     manager->removeMessageFilter(kMouseMoveMessage, this);
233     manager->removeMessageFilter(kMouseDownMessage, this);
234     manager->removeMessageFilter(kKeyDownMessage, this);
235   }
236 }
237 
onMakeFloating()238 void PopupWindow::onMakeFloating()
239 {
240   // Do nothing
241 }
242 
onMakeFixed()243 void PopupWindow::onMakeFixed()
244 {
245   // Do nothing
246 }
247 
TransparentPopupWindow(ClickBehavior clickBehavior)248 TransparentPopupWindow::TransparentPopupWindow(ClickBehavior clickBehavior)
249   : PopupWindow("", clickBehavior)
250 {
251   setTransparent(true);
252   initTheme();
253 }
254 
onInitTheme(InitThemeEvent & ev)255 void TransparentPopupWindow::onInitTheme(InitThemeEvent& ev)
256 {
257   PopupWindow::onInitTheme(ev);
258   // TODO fix this, if we use alpha=0 (gfx::ColorNone), we get
259   // "window_face" color as background the transparent popup window.
260   //setBgColor(gfx::ColorNone);
261   setBgColor(gfx::rgba(0, 0, 0, 1));
262 }
263 
264 } // namespace ui
265