1 | // Aseprite UI Library |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | // #define REPORT_SIGNALS |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "ui/widget.h" |
15 | |
16 | #include "base/memory.h" |
17 | #include "base/string.h" |
18 | #include "base/utf8_decode.h" |
19 | #include "os/font.h" |
20 | #include "os/surface.h" |
21 | #include "os/system.h" |
22 | #include "os/window.h" |
23 | #include "ui/app_state.h" |
24 | #include "ui/init_theme_event.h" |
25 | #include "ui/intern.h" |
26 | #include "ui/layout_io.h" |
27 | #include "ui/load_layout_event.h" |
28 | #include "ui/manager.h" |
29 | #include "ui/message.h" |
30 | #include "ui/move_region.h" |
31 | #include "ui/paint_event.h" |
32 | #include "ui/resize_event.h" |
33 | #include "ui/save_layout_event.h" |
34 | #include "ui/size_hint_event.h" |
35 | #include "ui/system.h" |
36 | #include "ui/theme.h" |
37 | #include "ui/view.h" |
38 | #include "ui/window.h" |
39 | |
40 | #include <algorithm> |
41 | #include <cctype> |
42 | #include <cstdarg> |
43 | #include <cstdio> |
44 | #include <cstring> |
45 | #include <limits> |
46 | #include <queue> |
47 | #include <sstream> |
48 | |
49 | namespace ui { |
50 | |
51 | using namespace gfx; |
52 | |
53 | WidgetType register_widget_type() |
54 | { |
55 | static int type = (int)kFirstUserWidget; |
56 | return (WidgetType)type++; |
57 | } |
58 | |
59 | Widget::Widget(WidgetType type) |
60 | : m_type(type) |
61 | , m_flags(0) |
62 | , m_theme(get_theme()) |
63 | , m_style(nullptr) |
64 | , m_font(nullptr) |
65 | , m_bgColor(gfx::ColorNone) |
66 | , m_bounds(0, 0, 0, 0) |
67 | , m_parent(nullptr) |
68 | , m_parentIndex(-1) |
69 | , m_sizeHint(nullptr) |
70 | , m_mnemonic(0) |
71 | , m_minSize(0, 0) |
72 | , m_maxSize(std::numeric_limits<int>::max(), |
73 | std::numeric_limits<int>::max()) |
74 | , m_childSpacing(0) |
75 | { |
76 | details::addWidget(this); |
77 | } |
78 | |
79 | Widget::~Widget() |
80 | { |
81 | // First, we remove children (so children's ~Widget() can access to |
82 | // the manager()). |
83 | while (!m_children.empty()) |
84 | delete m_children.front(); |
85 | |
86 | // Break relationship with the manager. This cannot be before |
87 | // deleting children, if we delete children after releasing the |
88 | // parent, a children deletion could generate a kMouseLeaveMessage |
89 | // for the parent that will be deleted too. |
90 | Manager* manager = this->manager(); |
91 | ASSERT(manager); |
92 | if (manager) { |
93 | manager->freeWidget(this); |
94 | manager->removeMessagesFor(this); |
95 | manager->removeMessageFilterFor(this); |
96 | } |
97 | |
98 | // Remove this widget from parent. |
99 | if (m_parent) |
100 | m_parent->removeChild(this); |
101 | |
102 | // Delete fixed size hint if it isn't nullptr |
103 | delete m_sizeHint; |
104 | |
105 | // Low level free |
106 | details::removeWidget(this); |
107 | } |
108 | |
109 | void Widget::deferDelete() |
110 | { |
111 | if (auto man = manager()) |
112 | man->addToGarbage(this); |
113 | else { |
114 | ASSERT(false); |
115 | } |
116 | } |
117 | |
118 | void Widget::initTheme() |
119 | { |
120 | InitThemeEvent ev(this, m_theme); |
121 | onInitTheme(ev); |
122 | } |
123 | |
124 | int Widget::textInt() const |
125 | { |
126 | return onGetTextInt(); |
127 | } |
128 | |
129 | double Widget::textDouble() const |
130 | { |
131 | return onGetTextDouble(); |
132 | } |
133 | |
134 | void Widget::setText(const std::string& text) |
135 | { |
136 | setTextQuiet(text); |
137 | onSetText(); |
138 | } |
139 | |
140 | void Widget::setTextf(const char *format, ...) |
141 | { |
142 | // formatted string |
143 | if (format) { |
144 | va_list ap; |
145 | va_start(ap, format); |
146 | char buf[4096]; |
147 | vsprintf(buf, format, ap); |
148 | va_end(ap); |
149 | |
150 | setText(buf); |
151 | } |
152 | // empty string |
153 | else { |
154 | setText("" ); |
155 | } |
156 | } |
157 | |
158 | void Widget::setTextQuiet(const std::string& text) |
159 | { |
160 | assert_ui_thread(); |
161 | |
162 | m_text = text; |
163 | enableFlags(HAS_TEXT); |
164 | } |
165 | |
166 | os::Font* Widget::font() const |
167 | { |
168 | if (!m_font && m_theme) |
169 | m_font = AddRef(m_theme->getWidgetFont(this)); |
170 | return m_font.get(); |
171 | } |
172 | |
173 | void Widget::setBgColor(gfx::Color color) |
174 | { |
175 | assert_ui_thread(); |
176 | |
177 | m_bgColor = color; |
178 | onSetBgColor(); |
179 | |
180 | #ifdef _DEBUG |
181 | if (m_style) { |
182 | LOG(WARNING, "UI: %s: Warning setting bgColor to a widget with style (%s)\n" , |
183 | typeid(*this).name(), m_style->id().c_str()); |
184 | } |
185 | #endif |
186 | } |
187 | |
188 | void Widget::setTheme(Theme* theme) |
189 | { |
190 | assert_ui_thread(); |
191 | |
192 | m_theme = theme; |
193 | m_font = nullptr; |
194 | |
195 | for (auto child : children()) |
196 | child->setTheme(theme); |
197 | } |
198 | |
199 | void Widget::setStyle(Style* style) |
200 | { |
201 | assert_ui_thread(); |
202 | |
203 | m_style = style; |
204 | m_border = m_theme->calcBorder(this, style); |
205 | m_bgColor = m_theme->calcBgColor(this, style); |
206 | if (style->font()) |
207 | m_font = AddRef(style->font()); |
208 | } |
209 | |
210 | // =============================================================== |
211 | // COMMON PROPERTIES |
212 | // =============================================================== |
213 | |
214 | void Widget::setVisible(bool state) |
215 | { |
216 | assert_ui_thread(); |
217 | |
218 | if (state) { |
219 | if (hasFlags(HIDDEN)) { |
220 | disableFlags(HIDDEN); |
221 | invalidate(); |
222 | |
223 | onVisible(true); |
224 | } |
225 | } |
226 | else { |
227 | if (!hasFlags(HIDDEN)) { |
228 | if (auto man = manager()) |
229 | man->freeWidget(this); // Free from manager |
230 | enableFlags(HIDDEN); |
231 | |
232 | onVisible(false); |
233 | } |
234 | } |
235 | } |
236 | |
237 | void Widget::setEnabled(bool state) |
238 | { |
239 | assert_ui_thread(); |
240 | |
241 | if (state) { |
242 | if (hasFlags(DISABLED)) { |
243 | disableFlags(DISABLED); |
244 | invalidate(); |
245 | |
246 | onEnable(true); |
247 | } |
248 | } |
249 | else { |
250 | if (!hasFlags(DISABLED)) { |
251 | if (auto man = manager()) |
252 | man->freeWidget(this); // Free from the manager |
253 | |
254 | enableFlags(DISABLED); |
255 | invalidate(); |
256 | |
257 | onEnable(false); |
258 | } |
259 | } |
260 | } |
261 | |
262 | void Widget::setSelected(bool state) |
263 | { |
264 | assert_ui_thread(); |
265 | |
266 | if (state) { |
267 | if (!hasFlags(SELECTED)) { |
268 | enableFlags(SELECTED); |
269 | invalidate(); |
270 | |
271 | onSelect(true); |
272 | } |
273 | } |
274 | else { |
275 | if (hasFlags(SELECTED)) { |
276 | disableFlags(SELECTED); |
277 | invalidate(); |
278 | |
279 | onSelect(false); |
280 | } |
281 | } |
282 | } |
283 | |
284 | void Widget::setExpansive(bool state) |
285 | { |
286 | assert_ui_thread(); |
287 | |
288 | if (state) |
289 | enableFlags(EXPANSIVE); |
290 | else |
291 | disableFlags(EXPANSIVE); |
292 | } |
293 | |
294 | void Widget::setDecorative(bool state) |
295 | { |
296 | assert_ui_thread(); |
297 | |
298 | if (state) |
299 | enableFlags(DECORATIVE); |
300 | else |
301 | disableFlags(DECORATIVE); |
302 | } |
303 | |
304 | void Widget::setFocusStop(bool state) |
305 | { |
306 | assert_ui_thread(); |
307 | |
308 | if (state) |
309 | enableFlags(FOCUS_STOP); |
310 | else |
311 | disableFlags(FOCUS_STOP); |
312 | } |
313 | |
314 | void Widget::setFocusMagnet(bool state) |
315 | { |
316 | assert_ui_thread(); |
317 | |
318 | if (state) |
319 | enableFlags(FOCUS_MAGNET); |
320 | else |
321 | disableFlags(FOCUS_MAGNET); |
322 | } |
323 | |
324 | bool Widget::isVisible() const |
325 | { |
326 | assert_ui_thread(); |
327 | |
328 | const Widget* widget = this; |
329 | const Widget* lastWidget = nullptr; |
330 | |
331 | do { |
332 | if (widget->hasFlags(HIDDEN)) |
333 | return false; |
334 | |
335 | lastWidget = widget; |
336 | widget = widget->m_parent; |
337 | } while (widget); |
338 | |
339 | // The widget is visible if it's inside a visible manager |
340 | return (lastWidget ? lastWidget->type() == kManagerWidget: false); |
341 | } |
342 | |
343 | bool Widget::isEnabled() const |
344 | { |
345 | assert_ui_thread(); |
346 | |
347 | const Widget* widget = this; |
348 | |
349 | do { |
350 | if (widget->hasFlags(DISABLED)) |
351 | return false; |
352 | |
353 | widget = widget->m_parent; |
354 | } while (widget); |
355 | |
356 | return true; |
357 | } |
358 | |
359 | bool Widget::isSelected() const |
360 | { |
361 | assert_ui_thread(); |
362 | return hasFlags(SELECTED); |
363 | } |
364 | |
365 | bool Widget::isExpansive() const |
366 | { |
367 | assert_ui_thread(); |
368 | return hasFlags(EXPANSIVE); |
369 | } |
370 | |
371 | bool Widget::isDecorative() const |
372 | { |
373 | assert_ui_thread(); |
374 | return hasFlags(DECORATIVE); |
375 | } |
376 | |
377 | bool Widget::isFocusStop() const |
378 | { |
379 | assert_ui_thread(); |
380 | return hasFlags(FOCUS_STOP); |
381 | } |
382 | |
383 | bool Widget::isFocusMagnet() const |
384 | { |
385 | assert_ui_thread(); |
386 | return hasFlags(FOCUS_MAGNET); |
387 | } |
388 | |
389 | // =============================================================== |
390 | // PARENTS & CHILDREN |
391 | // =============================================================== |
392 | |
393 | Window* Widget::window() const |
394 | { |
395 | assert_ui_thread(); |
396 | |
397 | const Widget* widget = this; |
398 | |
399 | while (widget) { |
400 | if (widget->type() == kWindowWidget) |
401 | return static_cast<Window*>(const_cast<Widget*>(widget)); |
402 | |
403 | widget = widget->m_parent; |
404 | } |
405 | |
406 | return nullptr; |
407 | } |
408 | |
409 | Manager* Widget::manager() const |
410 | { |
411 | assert_ui_thread(); |
412 | |
413 | const Widget* widget = this; |
414 | |
415 | while (widget) { |
416 | if (widget->type() == kManagerWidget) |
417 | return static_cast<Manager*>(const_cast<Widget*>(widget)); |
418 | |
419 | widget = widget->m_parent; |
420 | } |
421 | |
422 | return Manager::getDefault(); |
423 | } |
424 | |
425 | Display* Widget::display() const |
426 | { |
427 | if (Window* win = this->window()) |
428 | return win->display(); |
429 | else |
430 | return manager()->display(); |
431 | } |
432 | |
433 | int Widget::getChildIndex(Widget* child) |
434 | { |
435 | if (!child) |
436 | return -1; |
437 | |
438 | #ifdef _DEBUG |
439 | ASSERT(child->parent() == this); |
440 | auto it = std::find(m_children.begin(), m_children.end(), child); |
441 | ASSERT(it != m_children.end()); |
442 | ASSERT(child->parentIndex() == (it - m_children.begin())); |
443 | #endif |
444 | |
445 | return child->parentIndex(); |
446 | } |
447 | |
448 | Widget* Widget::nextSibling() |
449 | { |
450 | assert_ui_thread(); |
451 | |
452 | if (!m_parent) |
453 | return nullptr; |
454 | |
455 | auto begin = m_parent->m_children.begin(); |
456 | auto end = m_parent->m_children.end(); |
457 | auto it = begin + m_parentIndex; |
458 | |
459 | if (++it == end) |
460 | return nullptr; |
461 | |
462 | return *it; |
463 | } |
464 | |
465 | Widget* Widget::previousSibling() |
466 | { |
467 | assert_ui_thread(); |
468 | |
469 | if (!m_parent) |
470 | return nullptr; |
471 | |
472 | auto begin = m_parent->m_children.begin(); |
473 | auto it = begin + m_parentIndex; |
474 | |
475 | if (it == begin) |
476 | return nullptr; |
477 | |
478 | return *(--it); |
479 | } |
480 | |
481 | Widget* Widget::pick(const gfx::Point& pt, |
482 | const bool checkParentsVisibility) const |
483 | { |
484 | assert_ui_thread(); |
485 | |
486 | const Widget* inside, *picked = nullptr; |
487 | |
488 | // isVisible() checks visibility of widget's parent. |
489 | if (((checkParentsVisibility && isVisible()) || |
490 | (!checkParentsVisibility && !hasFlags(HIDDEN))) && |
491 | (bounds().contains(pt))) { |
492 | picked = this; |
493 | |
494 | for (Widget* child : m_children) { |
495 | inside = child->pick(pt, false); |
496 | if (inside) { |
497 | picked = inside; |
498 | break; |
499 | } |
500 | } |
501 | } |
502 | |
503 | return const_cast<Widget*>(picked); |
504 | } |
505 | |
506 | Widget* Widget::pickFromScreenPos(const gfx::Point& screenPos) const |
507 | { |
508 | return pick(display()->nativeWindow()->pointFromScreen(screenPos)); |
509 | } |
510 | |
511 | bool Widget::hasChild(Widget* child) |
512 | { |
513 | ASSERT_VALID_WIDGET(child); |
514 | assert_ui_thread(); |
515 | |
516 | return std::find(m_children.begin(), m_children.end(), child) != m_children.end(); |
517 | } |
518 | |
519 | bool Widget::hasAncestor(Widget* ancestor) |
520 | { |
521 | for (Widget* widget=m_parent; widget; widget=widget->m_parent) { |
522 | if (widget == ancestor) |
523 | return true; |
524 | } |
525 | return false; |
526 | } |
527 | |
528 | Widget* Widget::findChild(const char* id) const |
529 | { |
530 | for (auto child : m_children) { |
531 | if (child->id() == id) |
532 | return child; |
533 | } |
534 | |
535 | for (auto child : m_children) { |
536 | auto subchild = child->findChild(id); |
537 | if (subchild) |
538 | return subchild; |
539 | } |
540 | |
541 | return nullptr; |
542 | } |
543 | |
544 | Widget* Widget::findSibling(const char* id) const |
545 | { |
546 | return window()->findChild(id); |
547 | } |
548 | |
549 | void Widget::addChild(Widget* child) |
550 | { |
551 | ASSERT_VALID_WIDGET(this); |
552 | ASSERT_VALID_WIDGET(child); |
553 | |
554 | // Remove the widget from its current parent |
555 | if (child->m_parent) |
556 | child->m_parent->removeChild(child); |
557 | |
558 | int i = int(m_children.size()); |
559 | m_children.push_back(child); |
560 | child->m_parent = this; |
561 | child->m_parentIndex = i; |
562 | } |
563 | |
564 | void Widget::removeChild(const WidgetsList::iterator& it) |
565 | { |
566 | Widget* child = nullptr; |
567 | |
568 | ASSERT(it != m_children.end()); |
569 | if (it != m_children.end()) { |
570 | child = *it; |
571 | |
572 | auto it2 = m_children.erase(it); |
573 | for (auto end=m_children.end(); it2!=end; ++it2) |
574 | --(*it2)->m_parentIndex; |
575 | |
576 | ASSERT(child); |
577 | if (!child) |
578 | return; |
579 | } |
580 | else |
581 | return; |
582 | |
583 | // Free child from manager |
584 | if (auto man = manager()) |
585 | man->freeWidget(child); |
586 | |
587 | child->m_parent = nullptr; |
588 | child->m_parentIndex = -1; |
589 | } |
590 | |
591 | void Widget::removeChild(Widget* child) |
592 | { |
593 | ASSERT_VALID_WIDGET(this); |
594 | ASSERT_VALID_WIDGET(child); |
595 | ASSERT(child->parent() == this); |
596 | if (child->parent() == this) |
597 | removeChild(m_children.begin() + child->m_parentIndex); |
598 | } |
599 | |
600 | void Widget::removeAllChildren() |
601 | { |
602 | while (!m_children.empty()) |
603 | removeChild(--m_children.end()); |
604 | } |
605 | |
606 | void Widget::replaceChild(Widget* oldChild, Widget* newChild) |
607 | { |
608 | ASSERT_VALID_WIDGET(oldChild); |
609 | ASSERT_VALID_WIDGET(newChild); |
610 | |
611 | #if _DEBUG |
612 | { |
613 | auto it = std::find(m_children.begin(), m_children.end(), oldChild); |
614 | ASSERT(it != m_children.end()); |
615 | ASSERT(oldChild->m_parentIndex == (it - m_children.begin())); |
616 | } |
617 | #endif |
618 | |
619 | if (oldChild->parent() != this) { |
620 | ASSERT(false); |
621 | return; |
622 | } |
623 | int index = oldChild->m_parentIndex; |
624 | |
625 | removeChild(oldChild); |
626 | |
627 | auto it = m_children.begin() + index; |
628 | it = m_children.insert(it, newChild); |
629 | for (auto end=m_children.end(); it!=end; ++it) |
630 | ++(*it)->m_parentIndex; |
631 | |
632 | newChild->m_parent = this; |
633 | newChild->m_parentIndex = index; |
634 | } |
635 | |
636 | void Widget::insertChild(int index, Widget* child) |
637 | { |
638 | ASSERT_VALID_WIDGET(this); |
639 | ASSERT_VALID_WIDGET(child); |
640 | |
641 | index = std::clamp(index, 0, int(m_children.size())); |
642 | |
643 | auto it = m_children.begin() + index; |
644 | it = m_children.insert(it, child); |
645 | ++it; |
646 | for (auto end=m_children.end(); it!=end; ++it) |
647 | ++(*it)->m_parentIndex; |
648 | |
649 | child->m_parent = this; |
650 | child->m_parentIndex = index; |
651 | } |
652 | |
653 | void Widget::moveChildTo(Widget* thisChild, Widget* toThisPosition) |
654 | { |
655 | ASSERT(thisChild->parent() == this); |
656 | ASSERT(toThisPosition->parent() == this); |
657 | |
658 | const int from = thisChild->m_parentIndex; |
659 | const int to = toThisPosition->m_parentIndex; |
660 | |
661 | auto it = m_children.begin() + from; |
662 | it = m_children.erase(it); |
663 | auto end = m_children.end(); |
664 | for (; it!=end; ++it) |
665 | --(*it)->m_parentIndex; |
666 | |
667 | it = m_children.begin() + to; |
668 | it = m_children.insert(it, thisChild); |
669 | thisChild->m_parentIndex = to; |
670 | for (++it, end=m_children.end(); it!=end; ++it) |
671 | ++(*it)->m_parentIndex; |
672 | } |
673 | |
674 | // =============================================================== |
675 | // LAYOUT & CONSTRAINT |
676 | // =============================================================== |
677 | |
678 | void Widget::layout() |
679 | { |
680 | setBounds(bounds()); |
681 | invalidate(); |
682 | } |
683 | |
684 | void Widget::loadLayout() |
685 | { |
686 | if (!m_id.empty()) { |
687 | auto man = manager(); |
688 | LayoutIO* io = (man ? man->getLayoutIO(): nullptr); |
689 | if (io) { |
690 | std::string layout = io->loadLayout(this); |
691 | if (!layout.empty()) { |
692 | std::stringstream s(layout); |
693 | LoadLayoutEvent ev(this, s); |
694 | onLoadLayout(ev); |
695 | } |
696 | } |
697 | } |
698 | |
699 | // Do for all children |
700 | for (auto child : m_children) |
701 | child->loadLayout(); |
702 | } |
703 | |
704 | void Widget::saveLayout() |
705 | { |
706 | if (!m_id.empty()) { |
707 | auto man = manager(); |
708 | LayoutIO* io = (man ? man->getLayoutIO(): nullptr); |
709 | if (io) { |
710 | std::stringstream s; |
711 | SaveLayoutEvent ev(this, s); |
712 | onSaveLayout(ev); |
713 | |
714 | std::string layout = s.str(); |
715 | if (!layout.empty()) |
716 | io->saveLayout(this, layout); |
717 | } |
718 | } |
719 | |
720 | // Do for all children |
721 | for (auto child : m_children) |
722 | child->saveLayout(); |
723 | } |
724 | |
725 | void Widget::setDecorativeWidgetBounds() |
726 | { |
727 | onSetDecorativeWidgetBounds(); |
728 | } |
729 | |
730 | // =============================================================== |
731 | // POSITION & GEOMETRY |
732 | // =============================================================== |
733 | |
734 | Rect Widget::childrenBounds() const |
735 | { |
736 | return Rect(m_bounds.x + border().left(), |
737 | m_bounds.y + border().top(), |
738 | m_bounds.w - border().width(), |
739 | m_bounds.h - border().height()); |
740 | } |
741 | |
742 | Rect Widget::clientChildrenBounds() const |
743 | { |
744 | return Rect(border().left(), |
745 | border().top(), |
746 | m_bounds.w - border().width(), |
747 | m_bounds.h - border().height()); |
748 | } |
749 | |
750 | gfx::Rect Widget::boundsOnScreen() const |
751 | { |
752 | gfx::Rect rc = bounds(); |
753 | os::Window* nativeWindow = display()->nativeWindow(); |
754 | rc = gfx::Rect( |
755 | nativeWindow->pointToScreen(rc.origin()), |
756 | nativeWindow->pointToScreen(rc.point2())); |
757 | return rc; |
758 | } |
759 | |
760 | void Widget::setBounds(const Rect& rc) |
761 | { |
762 | // Don't generate onResize() events if the app is being closed |
763 | if (is_app_state_closing()) |
764 | return; |
765 | |
766 | ResizeEvent ev(this, rc); |
767 | onResize(ev); |
768 | } |
769 | |
770 | void Widget::setBoundsQuietly(const gfx::Rect& rc) |
771 | { |
772 | if (m_bounds != rc) { |
773 | m_bounds = rc; |
774 | |
775 | // Remove all paint messages for this widget. |
776 | if (Manager* man = manager()) |
777 | man->removeMessagesFor(this, kPaintMessage); |
778 | } |
779 | |
780 | // TODO Test moving this inside the if (m_bounds != rc) { ... } |
781 | // block, so the widget is invalidted only when the bounds are |
782 | // really changed. |
783 | invalidate(); |
784 | } |
785 | |
786 | void Widget::setBorder(const Border& br) |
787 | { |
788 | m_border = br; |
789 | |
790 | #ifdef _DEBUG |
791 | if (m_style) { |
792 | LOG(WARNING, "UI: %s: Warning setting border to a widget with style (%s)\n" , |
793 | typeid(*this).name(), m_style->id().c_str()); |
794 | } |
795 | #endif |
796 | } |
797 | |
798 | void Widget::setChildSpacing(int childSpacing) |
799 | { |
800 | m_childSpacing = childSpacing; |
801 | |
802 | #ifdef _DEBUG |
803 | if (m_style) { |
804 | LOG(WARNING, "UI: %s: Warning setting child spacing to a widget with style (%s)\n" , |
805 | typeid(*this).name(), m_style->id().c_str()); |
806 | } |
807 | #endif |
808 | } |
809 | |
810 | void Widget::noBorderNoChildSpacing() |
811 | { |
812 | m_border = gfx::Border(0, 0, 0, 0); |
813 | m_childSpacing = 0; |
814 | |
815 | #ifdef _DEBUG |
816 | if (m_style) { |
817 | LOG(WARNING, "UI: %s: Warning setting no border to a widget with style (%s)\n" , |
818 | typeid(*this).name(), m_style->id().c_str()); |
819 | } |
820 | #endif |
821 | } |
822 | |
823 | void Widget::getRegion(gfx::Region& region) |
824 | { |
825 | if (type() == kWindowWidget) |
826 | theme()->getWindowMask(this, region); |
827 | else |
828 | region = bounds(); |
829 | } |
830 | |
831 | void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags) |
832 | { |
833 | Window* window = this->window(); |
834 | Display* display = this->display(); |
835 | |
836 | getRegion(region); |
837 | |
838 | // Cut the top windows areas |
839 | if (flags & kCutTopWindows) { |
840 | const auto& uiWindows = display->getWindows(); |
841 | |
842 | // Reverse iterator |
843 | auto it = std::find(uiWindows.rbegin(), |
844 | uiWindows.rend(), window); |
845 | |
846 | if (!uiWindows.empty() && |
847 | window != uiWindows.front() && |
848 | it != uiWindows.rend()) { |
849 | // Subtract the rectangles of each window |
850 | for (++it; it != uiWindows.rend(); ++it) { |
851 | if (!(*it)->isVisible()) |
852 | continue; |
853 | |
854 | Region reg1; |
855 | (*it)->getRegion(reg1); |
856 | region -= reg1; |
857 | } |
858 | } |
859 | } |
860 | |
861 | // Clip the areas where are children |
862 | if (!(flags & kUseChildArea) && !children().empty()) { |
863 | Region reg1; |
864 | Region reg2(childrenBounds()); |
865 | |
866 | for (auto child : children()) { |
867 | if (child->isVisible()) { |
868 | Region reg3; |
869 | child->getRegion(reg3); |
870 | |
871 | if (child->hasFlags(DECORATIVE)) { |
872 | reg1 = bounds(); |
873 | reg1.createIntersection(reg1, reg3); |
874 | } |
875 | else { |
876 | reg1.createIntersection(reg2, reg3); |
877 | } |
878 | region -= reg1; |
879 | } |
880 | } |
881 | } |
882 | |
883 | // Intersect with the parent area |
884 | if (!hasFlags(DECORATIVE)) { |
885 | Widget* p = this->parent(); |
886 | while (p && p->type() != kManagerWidget) { |
887 | region &= Region(p->childrenBounds()); |
888 | p = p->parent(); |
889 | } |
890 | } |
891 | else { |
892 | Widget* p = parent(); |
893 | if (p) { |
894 | region &= Region(p->bounds()); |
895 | } |
896 | } |
897 | |
898 | // Limit to the displayable area |
899 | View* view = View::getView(display->containedWidget()); |
900 | Rect cpos; |
901 | if (view) |
902 | cpos = static_cast<View*>(view)->viewportBounds(); |
903 | else |
904 | cpos = display->containedWidget()->bounds(); |
905 | |
906 | region &= Region(cpos); |
907 | } |
908 | |
909 | int Widget::textWidth() const |
910 | { |
911 | return Graphics::measureUITextLength(text().c_str(), font()); |
912 | } |
913 | |
914 | int Widget::textHeight() const |
915 | { |
916 | return font()->height(); |
917 | } |
918 | |
919 | void Widget::getTextIconInfo( |
920 | gfx::Rect* box, |
921 | gfx::Rect* text, |
922 | gfx::Rect* icon, |
923 | int icon_align, int icon_w, int icon_h) |
924 | { |
925 | #define SETRECT(r) \ |
926 | if (r) { \ |
927 | r->x = r##_x; \ |
928 | r->y = r##_y; \ |
929 | r->w = r##_w; \ |
930 | r->h = r##_h; \ |
931 | } |
932 | |
933 | gfx::Rect bounds = clientBounds(); |
934 | int box_x, box_y, box_w, box_h, icon_x, icon_y; |
935 | int text_x, text_y, text_w, text_h; |
936 | |
937 | text_x = text_y = 0; |
938 | |
939 | // Size of the text |
940 | if (hasText()) { |
941 | text_w = textWidth(); |
942 | text_h = textHeight(); |
943 | } |
944 | else { |
945 | text_w = text_h = 0; |
946 | } |
947 | |
948 | // Box size |
949 | if (icon_align & CENTER) { // With the icon in the center |
950 | if (icon_align & MIDDLE) { // With the icon inside the text |
951 | box_w = std::max(icon_w, text_w); |
952 | box_h = std::max(icon_h, text_h); |
953 | } |
954 | // With the icon in the top or bottom |
955 | else { |
956 | box_w = std::max(icon_w, text_w); |
957 | box_h = icon_h + (hasText() ? childSpacing(): 0) + text_h; |
958 | } |
959 | } |
960 | // With the icon in left or right that doesn't care by now |
961 | else { |
962 | box_w = icon_w + (hasText() ? childSpacing(): 0) + text_w; |
963 | box_h = std::max(icon_h, text_h); |
964 | } |
965 | |
966 | // Box position |
967 | if (align() & RIGHT) |
968 | box_x = bounds.x2() - box_w - border().right(); |
969 | else if (align() & CENTER) |
970 | box_x = (bounds.x+bounds.x2())/2 - box_w/2; |
971 | else |
972 | box_x = bounds.x + border().left(); |
973 | |
974 | if (align() & BOTTOM) |
975 | box_y = bounds.y2() - box_h - border().bottom(); |
976 | else if (align() & MIDDLE) |
977 | box_y = (bounds.y+bounds.y2())/2 - box_h/2; |
978 | else |
979 | box_y = bounds.y + border().top(); |
980 | |
981 | // With text |
982 | if (hasText()) { |
983 | // Text/icon X position |
984 | if (icon_align & RIGHT) { |
985 | text_x = box_x; |
986 | icon_x = box_x + box_w - icon_w; |
987 | } |
988 | else if (icon_align & CENTER) { |
989 | text_x = box_x + box_w/2 - text_w/2; |
990 | icon_x = box_x + box_w/2 - icon_w/2; |
991 | } |
992 | else { |
993 | text_x = box_x + box_w - text_w; |
994 | icon_x = box_x; |
995 | } |
996 | |
997 | // Text Y position |
998 | if (icon_align & BOTTOM) { |
999 | text_y = box_y; |
1000 | icon_y = box_y + box_h - icon_h; |
1001 | } |
1002 | else if (icon_align & MIDDLE) { |
1003 | text_y = box_y + box_h/2 - text_h/2; |
1004 | icon_y = box_y + box_h/2 - icon_h/2; |
1005 | } |
1006 | else { |
1007 | text_y = box_y + box_h - text_h; |
1008 | icon_y = box_y; |
1009 | } |
1010 | } |
1011 | // Without text |
1012 | else { |
1013 | // Icon X/Y position |
1014 | icon_x = box_x; |
1015 | icon_y = box_y; |
1016 | } |
1017 | |
1018 | SETRECT(box); |
1019 | SETRECT(text); |
1020 | SETRECT(icon); |
1021 | } |
1022 | |
1023 | void Widget::setMinSize(const gfx::Size& sz) |
1024 | { |
1025 | ASSERT(sz.w <= m_maxSize.w); |
1026 | ASSERT(sz.h <= m_maxSize.h); |
1027 | m_minSize = sz; |
1028 | } |
1029 | |
1030 | void Widget::setMaxSize(const gfx::Size& sz) |
1031 | { |
1032 | ASSERT(sz.w >= m_minSize.w); |
1033 | ASSERT(sz.h >= m_minSize.h); |
1034 | m_maxSize = sz; |
1035 | } |
1036 | |
1037 | void Widget::resetMinSize() |
1038 | { |
1039 | m_minSize = gfx::Size(0, 0); |
1040 | } |
1041 | |
1042 | void Widget::resetMaxSize() |
1043 | { |
1044 | m_maxSize = gfx::Size(std::numeric_limits<int>::max(), |
1045 | std::numeric_limits<int>::max()); |
1046 | } |
1047 | |
1048 | void Widget::flushRedraw() |
1049 | { |
1050 | std::queue<Widget*> processing; |
1051 | Message* msg; |
1052 | |
1053 | if (hasFlags(DIRTY)) { |
1054 | disableFlags(DIRTY); |
1055 | processing.push(this); |
1056 | } |
1057 | |
1058 | Manager* manager = this->manager(); |
1059 | ASSERT(manager); |
1060 | if (!manager) |
1061 | return; |
1062 | |
1063 | while (!processing.empty()) { |
1064 | Widget* widget = processing.front(); |
1065 | processing.pop(); |
1066 | |
1067 | ASSERT_VALID_WIDGET(widget); |
1068 | |
1069 | // If the widget is hidden |
1070 | if (!widget->isVisible()) |
1071 | continue; |
1072 | |
1073 | for (auto child : widget->children()) { |
1074 | if (child->hasFlags(DIRTY)) { |
1075 | child->disableFlags(DIRTY); |
1076 | processing.push(child); |
1077 | } |
1078 | } |
1079 | |
1080 | if (!widget->m_updateRegion.isEmpty()) { |
1081 | // Intersect m_updateRegion with drawable area. |
1082 | { |
1083 | Region drawable; |
1084 | widget->getDrawableRegion(drawable, kCutTopWindows); |
1085 | widget->m_updateRegion &= drawable; |
1086 | } |
1087 | |
1088 | std::size_t c, nrects = widget->m_updateRegion.size(); |
1089 | Region::const_iterator it = widget->m_updateRegion.begin(); |
1090 | |
1091 | // Draw the widget |
1092 | Display* display = widget->display(); |
1093 | int count = nrects-1; |
1094 | for (c=0; c<nrects; ++c, ++it, --count) { |
1095 | // Create the draw message |
1096 | msg = new PaintMessage(count, *it); |
1097 | msg->setDisplay(display); |
1098 | msg->setRecipient(widget); |
1099 | |
1100 | // Enqueue the draw message |
1101 | manager->enqueueMessage(msg); |
1102 | } |
1103 | |
1104 | display->addInvalidRegion(widget->m_updateRegion); |
1105 | widget->m_updateRegion.clear(); |
1106 | } |
1107 | } |
1108 | } |
1109 | |
1110 | void Widget::paint(Graphics* graphics, |
1111 | const gfx::Region& drawRegion, |
1112 | const bool isBg) |
1113 | { |
1114 | if (drawRegion.isEmpty()) |
1115 | return; |
1116 | |
1117 | std::queue<Widget*> processing; |
1118 | processing.push(this); |
1119 | |
1120 | Display* display = this->display(); |
1121 | |
1122 | while (!processing.empty()) { |
1123 | Widget* widget = processing.front(); |
1124 | processing.pop(); |
1125 | |
1126 | ASSERT_VALID_WIDGET(widget); |
1127 | |
1128 | // If the widget is hidden |
1129 | if (!widget->isVisible()) |
1130 | continue; |
1131 | |
1132 | for (auto child : widget->children()) |
1133 | processing.push(child); |
1134 | |
1135 | // Intersect drawRegion with widget's drawable region. |
1136 | Region region; |
1137 | widget->getDrawableRegion(region, kCutTopWindows); |
1138 | region.createIntersection(region, drawRegion); |
1139 | |
1140 | Graphics graphics2( |
1141 | display, |
1142 | base::AddRef(graphics->getInternalSurface()), |
1143 | widget->bounds().x, |
1144 | widget->bounds().y); |
1145 | graphics2.setFont(AddRef(widget->font())); |
1146 | |
1147 | for (const gfx::Rect& rc : region) { |
1148 | IntersectClip clip(&graphics2, |
1149 | Rect(rc).offset( |
1150 | -widget->bounds().x, |
1151 | -widget->bounds().y)); |
1152 | widget->paintEvent(&graphics2, isBg); |
1153 | } |
1154 | } |
1155 | } |
1156 | |
1157 | bool Widget::paintEvent(Graphics* graphics, |
1158 | const bool isBg) |
1159 | { |
1160 | // For transparent widgets we have to draw the parent first. |
1161 | if (isTransparent()) { |
1162 | #if _DEBUG |
1163 | // In debug mode we can fill the area with Red so we know if the |
1164 | // we are drawing the parent correctly. |
1165 | if (window() && !window()->ownDisplay()) |
1166 | graphics->fillRect(gfx::rgba(255, 0, 0), clientBounds()); |
1167 | #endif |
1168 | |
1169 | enableFlags(HIDDEN); |
1170 | |
1171 | Widget* parentWidget = nullptr; |
1172 | if (type() == kWindowWidget) { |
1173 | parentWidget = display()->containedWidget(); |
1174 | |
1175 | if (get_multiple_displays()) { |
1176 | // Draw the desktop window as the background, not the manager |
1177 | // (as the manager contains all windows as children and will |
1178 | // try to draw other foreground windows here). |
1179 | if (parentWidget->type() == kManagerWidget) { |
1180 | parentWidget = static_cast<Manager*>(parentWidget)->getDesktopWindow(); |
1181 | } |
1182 | // If this ui::Window has it's own native window (os::Window), |
1183 | // we'll fill a transparent rectangle because we already have |
1184 | // a transparent native window. |
1185 | else if (static_cast<Window*>(this)->ownDisplay()) { |
1186 | parentWidget = nullptr; |
1187 | } |
1188 | } |
1189 | } |
1190 | else { |
1191 | parentWidget = parent(); |
1192 | } |
1193 | if (parentWidget) { |
1194 | gfx::Region rgn(parentWidget->bounds()); |
1195 | rgn &= gfx::Region( |
1196 | graphics->getClipBounds().offset( |
1197 | graphics->getInternalDeltaX(), |
1198 | graphics->getInternalDeltaY())); |
1199 | parentWidget->paint(graphics, rgn, true); |
1200 | } |
1201 | else { |
1202 | // Clear surface with transparent color |
1203 | os::Paint p; |
1204 | p.color(gfx::rgba(0, 0, 0, 0)); |
1205 | p.blendMode(os::BlendMode::Src); |
1206 | graphics->drawRect(clientBounds(), p); |
1207 | } |
1208 | |
1209 | disableFlags(HIDDEN); |
1210 | } |
1211 | |
1212 | PaintEvent ev(this, graphics); |
1213 | ev.setTransparentBg(isBg); |
1214 | onPaint(ev); // Fire onPaint event |
1215 | return ev.isPainted(); |
1216 | } |
1217 | |
1218 | bool Widget::isDoubleBuffered() const |
1219 | { |
1220 | return hasFlags(DOUBLE_BUFFERED); |
1221 | } |
1222 | |
1223 | void Widget::setDoubleBuffered(bool doubleBuffered) |
1224 | { |
1225 | enableFlags(DOUBLE_BUFFERED); |
1226 | } |
1227 | |
1228 | bool Widget::isTransparent() const |
1229 | { |
1230 | return hasFlags(TRANSPARENT); |
1231 | } |
1232 | |
1233 | void Widget::setTransparent(bool transparent) |
1234 | { |
1235 | enableFlags(TRANSPARENT); |
1236 | } |
1237 | |
1238 | void Widget::invalidate() |
1239 | { |
1240 | assert_ui_thread(); |
1241 | if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets |
1242 | onInvalidateRegion(Region(bounds())); |
1243 | } |
1244 | |
1245 | void Widget::invalidateRect(const gfx::Rect& rect) |
1246 | { |
1247 | assert_ui_thread(); |
1248 | if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets |
1249 | onInvalidateRegion(Region(rect)); |
1250 | } |
1251 | |
1252 | void Widget::invalidateRegion(const Region& region) |
1253 | { |
1254 | assert_ui_thread(); |
1255 | if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets |
1256 | onInvalidateRegion(region); |
1257 | } |
1258 | |
1259 | class DeleteGraphicsAndSurface { |
1260 | public: |
1261 | DeleteGraphicsAndSurface(const gfx::Rect& clip, |
1262 | const os::SurfaceRef& surface, |
1263 | os::SurfaceRef& dst) |
1264 | : m_pt(clip.origin()) |
1265 | , m_surface(surface) |
1266 | , m_dst(dst) { |
1267 | } |
1268 | |
1269 | void operator()(Graphics* graphics) { |
1270 | { |
1271 | os::SurfaceLock lockSrc(m_surface.get()); |
1272 | os::SurfaceLock lockDst(m_dst.get()); |
1273 | m_surface->blitTo( |
1274 | m_dst.get(), 0, 0, m_pt.x, m_pt.y, |
1275 | m_surface->width(), m_surface->height()); |
1276 | } |
1277 | m_surface.reset(); |
1278 | delete graphics; |
1279 | } |
1280 | |
1281 | private: |
1282 | gfx::Point m_pt; |
1283 | os::SurfaceRef m_surface; |
1284 | os::SurfaceRef m_dst; |
1285 | }; |
1286 | |
1287 | GraphicsPtr Widget::getGraphics(const gfx::Rect& clip) |
1288 | { |
1289 | GraphicsPtr graphics; |
1290 | Display* display = this->display(); |
1291 | os::SurfaceRef dstSurface = AddRef(display->surface()); |
1292 | |
1293 | // In case of double-buffering, we need to create the temporary |
1294 | // buffer only if the default surface is the screen. |
1295 | if (isDoubleBuffered() && dstSurface->isDirectToScreen()) { |
1296 | os::SurfaceRef surface = |
1297 | os::instance()->makeSurface(clip.w, clip.h); |
1298 | graphics.reset(new Graphics(display, surface, -clip.x, -clip.y), |
1299 | DeleteGraphicsAndSurface(clip, surface, |
1300 | dstSurface)); |
1301 | } |
1302 | // In other case, we can draw directly onto the screen. |
1303 | else { |
1304 | graphics.reset(new Graphics(display, dstSurface, bounds().x, bounds().y)); |
1305 | } |
1306 | |
1307 | graphics->setFont(AddRef(font())); |
1308 | return graphics; |
1309 | } |
1310 | |
1311 | // =============================================================== |
1312 | // GUI MANAGER |
1313 | // =============================================================== |
1314 | |
1315 | bool Widget::sendMessage(Message* msg) |
1316 | { |
1317 | ASSERT(msg); |
1318 | return onProcessMessage(msg); |
1319 | } |
1320 | |
1321 | void Widget::closeWindow() |
1322 | { |
1323 | if (Window* w = window()) |
1324 | w->closeWindow(this); |
1325 | } |
1326 | |
1327 | void Widget::broadcastMouseMessage(const gfx::Point& screenPos, |
1328 | WidgetsList& targets) |
1329 | { |
1330 | onBroadcastMouseMessage(screenPos, targets); |
1331 | } |
1332 | |
1333 | // =============================================================== |
1334 | // SIZE & POSITION |
1335 | // =============================================================== |
1336 | |
1337 | /** |
1338 | Returns the preferred size of the Widget. |
1339 | |
1340 | It checks if the preferred size is static (it means when it was |
1341 | set through #setSizeHint before) or if it is dynamic (this is |
1342 | the default and is when the #onSizeHint is used to determined |
1343 | the preferred size). |
1344 | |
1345 | In another words, if you do not use #setSizeHint to set a |
1346 | <em>static preferred size</em> for the widget then #onSizeHint |
1347 | will be used to calculate it. |
1348 | |
1349 | @see setSizeHint, onSizeHint, #sizeHint(const Size &) |
1350 | */ |
1351 | Size Widget::sizeHint() |
1352 | { |
1353 | if (m_sizeHint) |
1354 | return *m_sizeHint; |
1355 | else { |
1356 | SizeHintEvent ev(this, Size(0, 0)); |
1357 | onSizeHint(ev); |
1358 | |
1359 | Size sz(ev.sizeHint()); |
1360 | sz.w = std::clamp(sz.w, m_minSize.w, m_maxSize.w); |
1361 | sz.h = std::clamp(sz.h, m_minSize.h, m_maxSize.h); |
1362 | return sz; |
1363 | } |
1364 | } |
1365 | |
1366 | /** |
1367 | Returns the preferred size trying to fit in the specified size. |
1368 | Remember that if you use #setSizeHint this routine will |
1369 | return the static size which you specified manually. |
1370 | |
1371 | @param fitIn |
1372 | This can have both attributes (width and height) in |
1373 | zero, which means that it'll behave same as #sizeHint(). |
1374 | If the width is great than zero the #onSizeHint will try to |
1375 | fit in that width (this is useful to fit Label or Edit controls |
1376 | in a specified width and calculate the height it could occupy). |
1377 | |
1378 | @see sizeHint |
1379 | */ |
1380 | Size Widget::sizeHint(const Size& fitIn) |
1381 | { |
1382 | if (m_sizeHint) |
1383 | return *m_sizeHint; |
1384 | else { |
1385 | SizeHintEvent ev(this, fitIn); |
1386 | onSizeHint(ev); |
1387 | |
1388 | Size sz(ev.sizeHint()); |
1389 | sz.w = std::clamp(sz.w, m_minSize.w, m_maxSize.w); |
1390 | sz.h = std::clamp(sz.h, m_minSize.h, m_maxSize.h); |
1391 | return sz; |
1392 | } |
1393 | } |
1394 | |
1395 | /** |
1396 | Sets a fixed preferred size specified by the user. |
1397 | Widget::sizeHint() will return this value if it's setted. |
1398 | */ |
1399 | void Widget::setSizeHint(const Size& fixedSize) |
1400 | { |
1401 | delete m_sizeHint; |
1402 | m_sizeHint = new Size(fixedSize); |
1403 | } |
1404 | |
1405 | void Widget::setSizeHint(int fixedWidth, int fixedHeight) |
1406 | { |
1407 | setSizeHint(Size(fixedWidth, fixedHeight)); |
1408 | } |
1409 | |
1410 | void Widget::resetSizeHint() |
1411 | { |
1412 | if (m_sizeHint) { |
1413 | delete m_sizeHint; |
1414 | m_sizeHint = nullptr; |
1415 | } |
1416 | } |
1417 | |
1418 | // =============================================================== |
1419 | // FOCUS & MOUSE |
1420 | // =============================================================== |
1421 | |
1422 | void Widget::requestFocus() |
1423 | { |
1424 | if (auto man = manager()) |
1425 | man->setFocus(this); |
1426 | } |
1427 | |
1428 | void Widget::releaseFocus() |
1429 | { |
1430 | if (hasFocus()) { |
1431 | if (auto man = manager()) |
1432 | man->freeFocus(); |
1433 | } |
1434 | } |
1435 | |
1436 | // Captures the mouse to send all the future mouse messsages to the |
1437 | // specified widget (included the kMouseMoveMessage and kSetCursorMessage). |
1438 | void Widget::captureMouse() |
1439 | { |
1440 | if (auto man = manager()) { |
1441 | if (!man->getCapture()) { |
1442 | man->setCapture(this); |
1443 | } |
1444 | } |
1445 | } |
1446 | |
1447 | // Releases the capture of the mouse events. |
1448 | void Widget::releaseMouse() |
1449 | { |
1450 | if (auto man = manager()) { |
1451 | if (man->getCapture() == this) { |
1452 | man->freeCapture(); |
1453 | } |
1454 | } |
1455 | } |
1456 | |
1457 | bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type) |
1458 | { |
1459 | if (hasCapture()) { |
1460 | const gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(mouseMsg->position()); |
1461 | auto man = manager(); |
1462 | Widget* pick = (man ? man->pickFromScreenPos(screenPos): nullptr); |
1463 | if (pick && pick != this && pick->type() == widget_type) { |
1464 | releaseMouse(); |
1465 | |
1466 | MouseMessage* mouseMsg2 = new MouseMessage( |
1467 | kMouseDownMessage, |
1468 | *mouseMsg, |
1469 | mouseMsg->positionForDisplay(pick->display())); |
1470 | mouseMsg2->setDisplay(pick->display()); |
1471 | mouseMsg2->setRecipient(pick); |
1472 | man->enqueueMessage(mouseMsg2); |
1473 | return true; |
1474 | } |
1475 | } |
1476 | return false; |
1477 | } |
1478 | |
1479 | bool Widget::hasMouseOver() const |
1480 | { |
1481 | return (this == pickFromScreenPos(get_mouse_position())); |
1482 | } |
1483 | |
1484 | gfx::Point Widget::mousePosInDisplay() const |
1485 | { |
1486 | return display()->nativeWindow()->pointFromScreen(get_mouse_position()); |
1487 | } |
1488 | |
1489 | void Widget::setMnemonic(int mnemonic) |
1490 | { |
1491 | m_mnemonic = mnemonic; |
1492 | } |
1493 | |
1494 | void Widget::processMnemonicFromText(int escapeChar) |
1495 | { |
1496 | // Avoid calling setText() when the widget doesn't have the HAS_TEXT flag |
1497 | if (!hasText()) |
1498 | return; |
1499 | |
1500 | std::wstring newText; // wstring is used to properly push_back() multibyte chars |
1501 | if (!m_text.empty()) |
1502 | newText.reserve(m_text.size()); |
1503 | |
1504 | base::utf8_decode decode(m_text); |
1505 | while (int chr = decode.next()) { |
1506 | if (chr == escapeChar) { |
1507 | chr = decode.next(); |
1508 | if (!chr) { |
1509 | break; // Ill-formed string (it ends with escape character) |
1510 | } |
1511 | else if (chr != escapeChar) { |
1512 | setMnemonic(chr); |
1513 | } |
1514 | } |
1515 | newText.push_back(chr); |
1516 | } |
1517 | |
1518 | setText(base::to_utf8(newText)); |
1519 | } |
1520 | |
1521 | bool Widget::isMnemonicPressed(const KeyMessage* keyMsg) const |
1522 | { |
1523 | int chr = std::tolower(mnemonic()); |
1524 | return |
1525 | ((chr) && |
1526 | ((chr == std::tolower(keyMsg->unicodeChar())) || |
1527 | (chr >= 'a' && chr <= 'z' && keyMsg->scancode() == (kKeyA + chr - 'a')) || |
1528 | (chr >= '0' && chr <= '9' && keyMsg->scancode() == (kKey0 + chr - '0')))); |
1529 | } |
1530 | |
1531 | bool Widget::onProcessMessage(Message* msg) |
1532 | { |
1533 | ASSERT(msg != nullptr); |
1534 | |
1535 | switch (msg->type()) { |
1536 | |
1537 | case kOpenMessage: |
1538 | case kCloseMessage: |
1539 | case kWinMoveMessage: |
1540 | // Broadcast the message to the children. |
1541 | for (auto child : m_children) |
1542 | child->sendMessage(msg); |
1543 | break; |
1544 | |
1545 | case kPaintMessage: { |
1546 | const PaintMessage* ptmsg = static_cast<const PaintMessage*>(msg); |
1547 | ASSERT(ptmsg->rect().w > 0); |
1548 | ASSERT(ptmsg->rect().h > 0); |
1549 | |
1550 | GraphicsPtr graphics = getGraphics(toClient(ptmsg->rect())); |
1551 | return paintEvent(graphics.get(), false); |
1552 | } |
1553 | |
1554 | case kDoubleClickMessage: { |
1555 | // Convert double clicks into mouse down |
1556 | MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg); |
1557 | MouseMessage mouseMsg2(kMouseDownMessage, |
1558 | *mouseMsg, |
1559 | mouseMsg->position()); |
1560 | mouseMsg2.setDisplay(mouseMsg->display()); |
1561 | sendMessage(&mouseMsg2); |
1562 | break; |
1563 | } |
1564 | |
1565 | case kMouseDownMessage: |
1566 | case kMouseUpMessage: |
1567 | case kMouseMoveMessage: |
1568 | case kMouseWheelMessage: |
1569 | // Propagate the message to the parent. |
1570 | if (parent()) |
1571 | return parent()->sendMessage(msg); |
1572 | else |
1573 | break; |
1574 | |
1575 | case kSetCursorMessage: |
1576 | // Propagate the message to the parent. |
1577 | if (parent()) |
1578 | return parent()->sendMessage(msg); |
1579 | else { |
1580 | set_mouse_cursor(kArrowCursor); |
1581 | return true; |
1582 | } |
1583 | break; |
1584 | |
1585 | } |
1586 | |
1587 | // Broadcast the message to the children. |
1588 | if (msg->propagateToChildren()) { |
1589 | for (auto child : m_children) |
1590 | if (child->sendMessage(msg)) |
1591 | return true; |
1592 | } |
1593 | |
1594 | // Propagate the message to the parent. |
1595 | if (msg->propagateToParent() && parent() && |
1596 | msg->commonAncestor() != parent()) { |
1597 | return parent()->sendMessage(msg); |
1598 | } |
1599 | |
1600 | return false; |
1601 | } |
1602 | |
1603 | // =============================================================== |
1604 | // EVENTS |
1605 | // =============================================================== |
1606 | |
1607 | void Widget::onInvalidateRegion(const Region& region) |
1608 | { |
1609 | if (!isVisible() || region.contains(bounds()) == Region::Out) |
1610 | return; |
1611 | |
1612 | Region reg1; |
1613 | reg1.createUnion(m_updateRegion, region); |
1614 | { |
1615 | Region reg2; |
1616 | getDrawableRegion(reg2, kCutTopWindows); |
1617 | m_updateRegion.createIntersection(reg1, reg2); |
1618 | } |
1619 | reg1.createSubtraction(region, m_updateRegion); |
1620 | |
1621 | setDirtyFlag(); |
1622 | |
1623 | for (auto child : m_children) |
1624 | child->invalidateRegion(reg1); |
1625 | } |
1626 | |
1627 | void Widget::onSizeHint(SizeHintEvent& ev) |
1628 | { |
1629 | if (m_style) { |
1630 | ev.setSizeHint(m_theme->calcSizeHint(this, style())); |
1631 | } |
1632 | else { |
1633 | ev.setSizeHint(m_minSize); |
1634 | } |
1635 | } |
1636 | |
1637 | void Widget::onLoadLayout(LoadLayoutEvent& ev) |
1638 | { |
1639 | // Do nothing |
1640 | } |
1641 | |
1642 | void Widget::onSaveLayout(SaveLayoutEvent& ev) |
1643 | { |
1644 | // Do nothing |
1645 | } |
1646 | |
1647 | void Widget::onResize(ResizeEvent& ev) |
1648 | { |
1649 | setBoundsQuietly(ev.bounds()); |
1650 | |
1651 | // Set all the children to the same "cpos". |
1652 | gfx::Rect cpos = childrenBounds(); |
1653 | for (auto child : m_children) |
1654 | child->setBounds(cpos); |
1655 | } |
1656 | |
1657 | void Widget::onPaint(PaintEvent& ev) |
1658 | { |
1659 | if (m_style) |
1660 | m_theme->paintWidget(ev.graphics(), this, style(), |
1661 | clientBounds()); |
1662 | } |
1663 | |
1664 | void Widget::onBroadcastMouseMessage(const gfx::Point& screenPos, |
1665 | WidgetsList& targets) |
1666 | { |
1667 | // Do nothing |
1668 | } |
1669 | |
1670 | void Widget::onInitTheme(InitThemeEvent& ev) |
1671 | { |
1672 | for (auto child : children()) |
1673 | child->initTheme(); |
1674 | |
1675 | if (m_theme) { |
1676 | m_theme->initWidget(this); |
1677 | |
1678 | if (!hasFlags(INITIALIZED)) |
1679 | enableFlags(INITIALIZED); |
1680 | |
1681 | InitTheme(); |
1682 | } |
1683 | } |
1684 | |
1685 | void Widget::onSetDecorativeWidgetBounds() |
1686 | { |
1687 | if (m_theme) |
1688 | m_theme->setDecorativeWidgetBounds(this); |
1689 | } |
1690 | |
1691 | void Widget::onVisible(bool visible) |
1692 | { |
1693 | // Do nothing |
1694 | } |
1695 | |
1696 | void Widget::onEnable(bool enabled) |
1697 | { |
1698 | // Do nothing |
1699 | } |
1700 | |
1701 | void Widget::onSelect(bool selected) |
1702 | { |
1703 | // Do nothing |
1704 | } |
1705 | |
1706 | void Widget::onSetText() |
1707 | { |
1708 | invalidate(); |
1709 | } |
1710 | |
1711 | void Widget::onSetBgColor() |
1712 | { |
1713 | invalidate(); |
1714 | } |
1715 | |
1716 | int Widget::onGetTextInt() const |
1717 | { |
1718 | return std::strtol(m_text.c_str(), nullptr, 10); |
1719 | } |
1720 | |
1721 | double Widget::onGetTextDouble() const |
1722 | { |
1723 | return std::strtod(m_text.c_str(), nullptr); |
1724 | } |
1725 | |
1726 | void Widget::offsetWidgets(int dx, int dy) |
1727 | { |
1728 | if (dx == 0 && dy == 0) |
1729 | return; |
1730 | |
1731 | m_updateRegion.offset(dx, dy); |
1732 | m_bounds.offset(dx, dy); |
1733 | |
1734 | // Remove all paint messages for this widget. |
1735 | if (auto man = manager()) |
1736 | man->removeMessagesFor(this, kPaintMessage); |
1737 | |
1738 | for (auto child : m_children) |
1739 | child->offsetWidgets(dx, dy); |
1740 | } |
1741 | |
1742 | void Widget::setDirtyFlag() |
1743 | { |
1744 | Widget* widget = this; |
1745 | while (widget) { |
1746 | widget->enableFlags(DIRTY); |
1747 | widget = widget->parent(); |
1748 | } |
1749 | } |
1750 | |
1751 | } // namespace ui |
1752 | |