1 /*
2 * The ManaPlus Client
3 * Copyright (C) 2004-2009 The Mana World Development Team
4 * Copyright (C) 2009-2010 The Mana Developers
5 * Copyright (C) 2011-2019 The ManaPlus Developers
6 * Copyright (C) 2019-2021 Andrei Karas
7 *
8 * This file is part of The ManaPlus Client.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /* _______ __ __ __ ______ __ __ _______ __ __
25 * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\
26 * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27 * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / /
28 * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / /
29 * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30 * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31 *
32 * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33 *
34 *
35 * Per Larsson a.k.a finalman
36 * Olof Naessén a.k.a jansem/yakslem
37 *
38 * Visit: http://guichan.sourceforge.net
39 *
40 * License: (BSD)
41 * Redistribution and use in source and binary forms, with or without
42 * modification, are permitted provided that the following conditions
43 * are met:
44 * 1. Redistributions of source code must retain the above copyright
45 * notice, this list of conditions and the following disclaimer.
46 * 2. Redistributions in binary form must reproduce the above copyright
47 * notice, this list of conditions and the following disclaimer in
48 * the documentation and/or other materials provided with the
49 * distribution.
50 * 3. Neither the name of Guichan nor the names of its contributors may
51 * be used to endorse or promote products derived from this software
52 * without specific prior written permission.
53 *
54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65 */
66
67 #include "gui/widgets/window.h"
68
69 #include "client.h"
70 #include "configuration.h"
71 #ifndef DYECMD
72 #include "dragdrop.h"
73 #else // DYECMD
74 #include "resources/image/image.h"
75 #endif // DYECMD
76 #include "soundmanager.h"
77
78 #include "const/sound.h"
79
80 #include "gui/focushandler.h"
81 #include "gui/gui.h"
82 #include "gui/popupmanager.h"
83 #include "gui/skin.h"
84 #include "gui/viewport.h"
85
86 #include "gui/fonts/font.h"
87
88 #include "gui/popups/popupmenu.h"
89
90 #include "gui/windows/setupwindow.h"
91
92 #include "gui/widgets/containerplacer.h"
93 #include "gui/widgets/layout.h"
94
95 #include "render/renderers.h"
96
97 #include "render/vertexes/imagecollection.h"
98
99 #include "utils/checkutils.h"
100 #include "utils/delete2.h"
101
102 #include "debug.h"
103
104 const int resizeMask = 8 + 4 + 2 + 1;
105
106 int Window::windowInstances = 0;
107 int Window::mouseResize = 0;
108
Window(const std::string & caption,const Modal modal,Window * const parent,std::string skin)109 Window::Window(const std::string &caption,
110 const Modal modal,
111 Window *const parent,
112 std::string skin) :
113 BasicContainer2(nullptr),
114 MouseListener(),
115 WidgetListener(),
116 mCaption(caption),
117 mAlignment(Graphics::CENTER),
118 mPadding(2),
119 mTitleBarHeight(16),
120 mMovable(Move_true),
121 mDragOffsetX(0),
122 mDragOffsetY(0),
123 mMoved(false),
124 mSkin(nullptr),
125 mDefaultX(0),
126 mDefaultY(0),
127 mDefaultWidth(0),
128 mDefaultHeight(0),
129 mCaptionOffsetX(7),
130 mCaptionOffsetY(5),
131 mShowTitle(true),
132 mLastRedraw(true),
133 mGrip(nullptr),
134 mParentWindow(parent),
135 mLayout(nullptr),
136 mCloseRect(),
137 mStickyRect(),
138 mGripRect(),
139 mTextChunk(),
140 mWindowName("window"),
141 mMinWinWidth(100),
142 mMinWinHeight(40),
143 mMaxWinWidth(mainGraphics->mWidth),
144 mMaxWinHeight(mainGraphics->mHeight),
145 mVertexes(new ImageCollection),
146 mCaptionAlign(Graphics::LEFT),
147 mTitlePadding(4),
148 mGripPadding(2),
149 mResizeHandles(-1),
150 mOldResizeHandles(-1),
151 mClosePadding(0),
152 mStickySpacing(0),
153 mStickyPadding(0),
154 mCaptionFont(getFont()),
155 mModal(modal),
156 mCloseWindowButton(false),
157 mDefaultVisible(false),
158 mSaveVisible(false),
159 mStickyButton(false),
160 mSticky(false),
161 mStickyButtonLock(false),
162 mPlayVisibleSound(false),
163 mInit(false),
164 mTextChanged(true),
165 mAllowClose(false)
166 {
167 logger->log("Window::Window(\"%s\")", caption.c_str());
168
169 mWindow = this;
170
171 windowInstances++;
172
173 // mFrameSize = 1;
174 addMouseListener(this);
175
176 setFrameSize(0);
177 setPadding(3);
178 setTitleBarHeight(20);
179
180 if (skin.empty())
181 {
182 reportAlways("Default skin was used for window: %s",
183 caption.c_str())
184 skin = "window.xml";
185 }
186
187 int childPalette = 1;
188 // Loads the skin
189 if (theme != nullptr)
190 {
191 mSkin = theme->load(skin,
192 "window.xml",
193 true,
194 Theme::getThemePath());
195 if (mSkin != nullptr)
196 {
197 setPadding(mSkin->getPadding());
198 if (getOptionBool("titlebarBold", false))
199 mCaptionFont = boldFont;
200 mTitlePadding = mSkin->getTitlePadding();
201 mGripPadding = getOption("resizePadding", 0);
202 mCaptionOffsetX = getOption("captionoffsetx", 0);
203 if (mCaptionOffsetX == 0)
204 mCaptionOffsetX = 7;
205 mCaptionOffsetY = getOption("captionoffsety", 0);
206 if (mCaptionOffsetY == 0)
207 mCaptionOffsetY = 5;
208 mCaptionAlign = static_cast<Graphics::Alignment>(
209 getOption("captionalign", 0));
210 if (mCaptionAlign < Graphics::LEFT
211 || mCaptionAlign > Graphics::RIGHT)
212 {
213 mCaptionAlign = Graphics::LEFT;
214 }
215 setTitleBarHeight(CAST_U32(
216 getOption("titlebarHeight", 0)));
217 if (mTitleBarHeight == 0U)
218 mTitleBarHeight = mCaptionFont->getHeight() + mPadding;
219
220 mTitleBarHeight += getOption("titlebarHeightRelative", 0);
221 setPalette(getOption("palette", 0));
222 childPalette = getOption("childPalette", 0);
223 mShowTitle = getOptionBool("showTitle", true);
224 mClosePadding = getOption("closePadding", 0);
225 mStickySpacing = getOption("stickySpacing", 0);
226 mStickyPadding = getOption("stickyPadding", 0);
227 }
228 }
229
230 // Add this window to the window container
231 if (windowContainer != nullptr)
232 windowContainer->add(this);
233
234 if (mModal == Modal_true)
235 {
236 gui->setCursorType(Cursor::CURSOR_POINTER);
237 requestModalFocus();
238 }
239
240 // Windows are invisible by default
241 setVisible(Visible_false, false);
242
243 addWidgetListener(this);
244 mForegroundColor = getThemeColor(ThemeColorId::WINDOW, 255U);
245 mForegroundColor2 = getThemeColor(ThemeColorId::WINDOW_OUTLINE, 255U);
246 setPalette(childPalette);
247 }
248
postInit()249 void Window::postInit()
250 {
251 if (mInit)
252 {
253 reportAlways("error: Window created with calling postInit() "
254 "more than once: %s",
255 mWindowName.c_str())
256 }
257 mInit = true;
258 }
259
~Window()260 Window::~Window()
261 {
262 logger->log("Window::~Window(\"%s\")", getCaption().c_str());
263
264 if (gui != nullptr)
265 gui->removeDragged(this);
266
267 #ifndef DYECMD
268 if (setupWindow != nullptr)
269 setupWindow->unregisterWindowForReset(this);
270 #endif // DYECMD
271
272 client->windowRemoved(this);
273
274 saveWindowState();
275
276 delete2(mLayout)
277
278 while (!mWidgets.empty())
279 delete mWidgets.front();
280
281 mWidgets.clear();
282
283 removeWidgetListener(this);
284 delete2(mVertexes)
285
286 windowInstances--;
287
288 if (mSkin != nullptr)
289 {
290 if (theme != nullptr)
291 theme->unload(mSkin);
292 mSkin = nullptr;
293 }
294 if (mGrip != nullptr)
295 {
296 mGrip->decRef();
297 mGrip = nullptr;
298 }
299 if (!mInit)
300 {
301 reportAlways("error: Window created without calling postInit(): %s",
302 mWindowName.c_str())
303 }
304 }
305
setWindowContainer(WindowContainer * const wc)306 void Window::setWindowContainer(WindowContainer *const wc)
307 {
308 windowContainer = wc;
309 }
310
draw(Graphics * const graphics)311 void Window::draw(Graphics *const graphics)
312 {
313 if (mSkin == nullptr)
314 return;
315
316 BLOCK_START("Window::draw")
317 bool update = false;
318
319 if (mResizeHandles != mOldResizeHandles)
320 {
321 mRedraw = true;
322 mOldResizeHandles = mResizeHandles;
323 }
324 if (mRedraw)
325 {
326 mLastRedraw = true;
327 mRedraw = false;
328 update = true;
329 mVertexes->clear();
330 graphics->calcWindow(mVertexes,
331 0, 0,
332 mDimension.width,
333 mDimension.height,
334 mSkin->getBorder());
335
336 // Draw Close Button
337 if (mCloseWindowButton)
338 {
339 const Image *const button = mSkin->getCloseImage(
340 mResizeHandles == CLOSE);
341 if (button != nullptr)
342 {
343 graphics->calcTileCollection(mVertexes,
344 button,
345 mCloseRect.x,
346 mCloseRect.y);
347 }
348 }
349 // Draw Sticky Button
350 if (mStickyButton)
351 {
352 const Image *const button = mSkin->getStickyImage(mSticky);
353 if (button != nullptr)
354 {
355 graphics->calcTileCollection(mVertexes,
356 button,
357 mStickyRect.x,
358 mStickyRect.y);
359 }
360 }
361
362 if (mGrip != nullptr)
363 {
364 graphics->calcTileCollection(mVertexes,
365 mGrip,
366 mGripRect.x,
367 mGripRect.y);
368 }
369 graphics->finalize(mVertexes);
370 }
371 else
372 {
373 mLastRedraw = false;
374 }
375 graphics->drawTileCollection(mVertexes);
376
377 // Draw title
378 if (mShowTitle)
379 {
380 int x;
381 switch (mCaptionAlign)
382 {
383 case Graphics::LEFT:
384 default:
385 x = mCaptionOffsetX;
386 break;
387 case Graphics::CENTER:
388 x = mCaptionOffsetX - mCaptionFont->getWidth(mCaption) / 2;
389 break;
390 case Graphics::RIGHT:
391 x = mCaptionOffsetX - mCaptionFont->getWidth(mCaption);
392 break;
393 }
394 if (mTextChanged)
395 {
396 mTextChunk.textFont = mCaptionFont;
397 mTextChunk.deleteImage();
398 mTextChunk.text = mCaption;
399 mTextChunk.color = mForegroundColor;
400 mTextChunk.color2 = mForegroundColor2;
401 mCaptionFont->generate(mTextChunk);
402 mTextChanged = false;
403 }
404
405 const Image *const image = mTextChunk.img;
406 if (image != nullptr)
407 graphics->drawImage(image, x, mCaptionOffsetY);
408 }
409
410 if (update)
411 {
412 graphics->setRedraw(update);
413 drawChildren(graphics);
414 graphics->setRedraw(false);
415 }
416 else
417 {
418 drawChildren(graphics);
419 }
420 BLOCK_END("Window::draw")
421 }
422
safeDraw(Graphics * const graphics)423 void Window::safeDraw(Graphics *const graphics)
424 {
425 if (mSkin == nullptr)
426 return;
427
428 BLOCK_START("Window::safeDraw")
429
430 graphics->drawImageRect(0, 0,
431 mDimension.width,
432 mDimension.height,
433 mSkin->getBorder());
434
435 // Draw Close Button
436 if (mCloseWindowButton)
437 {
438 const Image *const button = mSkin->getCloseImage(
439 mResizeHandles == CLOSE);
440 if (button != nullptr)
441 graphics->drawImage(button, mCloseRect.x, mCloseRect.y);
442 }
443 // Draw Sticky Button
444 if (mStickyButton)
445 {
446 const Image *const button = mSkin->getStickyImage(mSticky);
447 if (button != nullptr)
448 graphics->drawImage(button, mStickyRect.x, mStickyRect.y);
449 }
450
451 if (mGrip != nullptr)
452 graphics->drawImage(mGrip, mGripRect.x, mGripRect.y);
453
454 // Draw title
455 if (mShowTitle)
456 {
457 int x;
458 switch (mCaptionAlign)
459 {
460 case Graphics::LEFT:
461 default:
462 x = mCaptionOffsetX;
463 break;
464 case Graphics::CENTER:
465 x = mCaptionOffsetX - mCaptionFont->getWidth(mCaption) / 2;
466 break;
467 case Graphics::RIGHT:
468 x = mCaptionOffsetX - mCaptionFont->getWidth(mCaption);
469 break;
470 }
471 if (mTextChanged)
472 {
473 mTextChunk.textFont = mCaptionFont;
474 mTextChunk.deleteImage();
475 mTextChunk.text = mCaption;
476 mTextChunk.color = mForegroundColor;
477 mTextChunk.color2 = mForegroundColor2;
478 mCaptionFont->generate(mTextChunk);
479 mTextChanged = false;
480 }
481
482 const Image *const image = mTextChunk.img;
483 if (image != nullptr)
484 graphics->drawImage(image, x, mCaptionOffsetY);
485 }
486
487 safeDrawChildren(graphics);
488
489 BLOCK_END("Window::safeDraw")
490 }
491
setContentSize(int width,int height)492 void Window::setContentSize(int width, int height)
493 {
494 width = width + 2 * mPadding;
495 height = height + mPadding + mTitleBarHeight;
496
497 if (mMinWinWidth > width)
498 width = mMinWinWidth;
499 else if (mMaxWinWidth < width)
500 width = mMaxWinWidth;
501 if (mMinWinHeight > height)
502 height = mMinWinHeight;
503 else if (mMaxWinHeight < height)
504 height = mMaxWinHeight;
505
506 setSize(width, height);
507 }
508
setLocationRelativeTo(const Widget * const widget)509 void Window::setLocationRelativeTo(const Widget *const widget)
510 {
511 if (widget == nullptr)
512 return;
513
514 int wx;
515 int wy;
516 int x;
517 int y;
518
519 widget->getAbsolutePosition(wx, wy);
520 getAbsolutePosition(x, y);
521
522 setPosition(mDimension.x + (wx + (widget->getWidth()
523 - mDimension.width) / 2 - x),
524 mDimension.y + (wy + (widget->getHeight()
525 - mDimension.height) / 2 - y));
526 }
527
setLocationHorisontallyRelativeTo(const Widget * const widget)528 void Window::setLocationHorisontallyRelativeTo(const Widget *const widget)
529 {
530 if (widget == nullptr)
531 return;
532
533 int wx;
534 int wy;
535 int x;
536 int y;
537
538 widget->getAbsolutePosition(wx, wy);
539 getAbsolutePosition(x, y);
540
541 setPosition(mDimension.x + (wx + (widget->getWidth()
542 - mDimension.width) / 2 - x), 0);
543 }
544
setLocationRelativeTo(const ImagePosition::Type & position,int offsetX,int offsetY)545 void Window::setLocationRelativeTo(const ImagePosition::Type &position,
546 int offsetX, int offsetY)
547 {
548 if (position == ImagePosition::UPPER_LEFT)
549 {
550 }
551 else if (position == ImagePosition::UPPER_CENTER)
552 {
553 offsetX += (mainGraphics->mWidth - mDimension.width) / 2;
554 }
555 else if (position == ImagePosition::UPPER_RIGHT)
556 {
557 offsetX += mainGraphics->mWidth - mDimension.width;
558 }
559 else if (position == ImagePosition::LEFT)
560 {
561 offsetY += (mainGraphics->mHeight - mDimension.height) / 2;
562 }
563 else if (position == ImagePosition::CENTER)
564 {
565 offsetX += (mainGraphics->mWidth - mDimension.width) / 2;
566 offsetY += (mainGraphics->mHeight - mDimension.height) / 2;
567 }
568 else if (position == ImagePosition::RIGHT)
569 {
570 offsetX += mainGraphics->mWidth - mDimension.width;
571 offsetY += (mainGraphics->mHeight - mDimension.height) / 2;
572 }
573 else if (position == ImagePosition::LOWER_LEFT)
574 {
575 offsetY += mainGraphics->mHeight - mDimension.height;
576 }
577 else if (position == ImagePosition::LOWER_CENTER)
578 {
579 offsetX += (mainGraphics->mWidth - mDimension.width) / 2;
580 offsetY += mainGraphics->mHeight - mDimension.height;
581 }
582 else if (position == ImagePosition::LOWER_RIGHT)
583 {
584 offsetX += mainGraphics->mWidth - mDimension.width;
585 offsetY += mainGraphics->mHeight - mDimension.height;
586 }
587
588 setPosition(offsetX, offsetY);
589 }
590
setMinWidth(const int width)591 void Window::setMinWidth(const int width)
592 {
593 if (mSkin != nullptr)
594 {
595 mMinWinWidth = width > mSkin->getMinWidth()
596 ? width : mSkin->getMinWidth();
597 }
598 else
599 {
600 mMinWinWidth = width;
601 }
602 }
603
setMinHeight(const int height)604 void Window::setMinHeight(const int height)
605 {
606 if (mSkin != nullptr)
607 {
608 mMinWinHeight = height > mSkin->getMinHeight()
609 ? height : mSkin->getMinHeight();
610 }
611 else
612 {
613 mMinWinHeight = height;
614 }
615 }
616
setMaxWidth(const int width)617 void Window::setMaxWidth(const int width)
618 {
619 mMaxWinWidth = width;
620 }
621
setMaxHeight(const int height)622 void Window::setMaxHeight(const int height)
623 {
624 mMaxWinHeight = height;
625 }
626
setResizable(const bool r)627 void Window::setResizable(const bool r)
628 {
629 if ((mGrip != nullptr) == r)
630 return;
631
632 if (mGrip != nullptr)
633 mGrip->decRef();
634 if (r)
635 {
636 mGrip = Theme::getImageFromThemeXml("resize.xml", "");
637 if (mGrip != nullptr)
638 {
639 mGripRect.x = mDimension.width - mGrip->getWidth() - mGripPadding;
640 mGripRect.y = mDimension.height - mGrip->getHeight()
641 - mGripPadding;
642 }
643 else
644 {
645 mGripRect.x = 0;
646 mGripRect.y = 0;
647 }
648 }
649 else
650 {
651 mGrip = nullptr;
652 }
653 }
654
widgetResized(const Event & event A_UNUSED)655 void Window::widgetResized(const Event &event A_UNUSED)
656 {
657 if (mGrip != nullptr)
658 {
659 mGripRect.x = mDimension.width - mGrip->getWidth() - mGripPadding;
660 mGripRect.y = mDimension.height - mGrip->getHeight() - mGripPadding;
661 }
662
663 if (mLayout != nullptr)
664 {
665 const Rect area = getChildrenArea();
666 int w = area.width;
667 int h = area.height;
668 mLayout->reflow(w, h);
669 }
670 if (mSkin != nullptr)
671 {
672 const bool showClose = mCloseWindowButton
673 && (mSkin->getCloseImage(false) != nullptr);
674 if (showClose)
675 {
676 const Image *const button = mSkin->getCloseImage(false);
677 if (button != nullptr)
678 {
679 const int buttonWidth = button->getWidth();
680 mCloseRect.x = mDimension.width - buttonWidth - mClosePadding;
681 mCloseRect.y = mClosePadding;
682 mCloseRect.width = buttonWidth;
683 mCloseRect.height = button->getHeight();
684 }
685 }
686 if (mStickyButton)
687 {
688 const Image *const button = mSkin->getStickyImage(mSticky);
689 if (button != nullptr)
690 {
691 const int buttonWidth = button->getWidth();
692 int x = mDimension.width - buttonWidth
693 - mStickySpacing - mClosePadding;
694
695 if (showClose)
696 x -= mSkin->getCloseImage(false)->getWidth();
697
698 mStickyRect.x = x;
699 mStickyRect.y = mStickyPadding;
700 mStickyRect.width = buttonWidth;
701 mStickyRect.height = button->getHeight();
702 }
703 }
704 }
705 else
706 {
707 mCloseRect.x = 0;
708 mCloseRect.y = 0;
709 mCloseRect.width = 0;
710 mCloseRect.height = 0;
711 mStickyRect.x = 0;
712 mStickyRect.y = 0;
713 mStickyRect.width = 0;
714 mStickyRect.height = 0;
715 }
716
717 mRedraw = true;
718 }
719
widgetMoved(const Event & event A_UNUSED)720 void Window::widgetMoved(const Event& event A_UNUSED)
721 {
722 mRedraw = true;
723 }
724
widgetHidden(const Event & event A_UNUSED)725 void Window::widgetHidden(const Event &event A_UNUSED)
726 {
727 if (isBatchDrawRenders(openGLMode))
728 mVertexes->clear();
729
730 mTextChunk.deleteImage();
731
732 mTextChanged = true;
733 mRedraw = true;
734
735 if (gui != nullptr)
736 gui->setCursorType(Cursor::CURSOR_POINTER);
737
738 if (mFocusHandler == nullptr)
739 return;
740
741 for (WidgetListConstIterator it = mWidgets.begin();
742 it != mWidgets.end(); ++ it)
743 {
744 if (mFocusHandler->isFocused(*it))
745 mFocusHandler->focusNone();
746 }
747 }
748
setCloseButton(const bool flag)749 void Window::setCloseButton(const bool flag)
750 {
751 mCloseWindowButton = flag;
752 if (flag)
753 mAllowClose = true;
754 }
755
isResizable() const756 bool Window::isResizable() const
757 {
758 return mGrip != nullptr;
759 }
760
setStickyButton(const bool flag)761 void Window::setStickyButton(const bool flag)
762 {
763 mStickyButton = flag;
764 }
765
setSticky(const bool sticky)766 void Window::setSticky(const bool sticky)
767 {
768 mSticky = sticky;
769 mRedraw = true;
770 }
771
setStickyButtonLock(const bool lock)772 void Window::setStickyButtonLock(const bool lock)
773 {
774 mStickyButtonLock = lock;
775 mStickyButton = lock;
776 }
777
setVisible(Visible visible)778 void Window::setVisible(Visible visible)
779 {
780 setVisible(visible, false);
781 }
782
setVisible(const Visible visible,const bool forceSticky)783 void Window::setVisible(const Visible visible, const bool forceSticky)
784 {
785 if (visible == mVisible)
786 return;
787
788 // Check if the window is off screen...
789 if (visible == Visible_true)
790 ensureOnScreen();
791 else
792 mResizeHandles = 0;
793
794 if (mStickyButtonLock)
795 {
796 BasicContainer2::setVisible(visible);
797 }
798 else
799 {
800 BasicContainer2::setVisible(fromBool((!forceSticky && mSticky) ||
801 visible == Visible_true, Visible));
802 }
803 if (visible == Visible_true)
804 {
805 if (mPlayVisibleSound)
806 soundManager.playGuiSound(SOUND_SHOW_WINDOW);
807 if (gui != nullptr)
808 {
809 MouseEvent *const event = reinterpret_cast<MouseEvent*>(
810 gui->createMouseEvent(this));
811 if (event != nullptr)
812 {
813 const int x = event->getX();
814 const int y = event->getY();
815 if (x >= 0 && x <= mDimension.width
816 && y >= 0 && y <= mDimension.height)
817 {
818 mouseMoved(*event);
819 }
820 delete event;
821 }
822 }
823 }
824 else
825 {
826 if (mPlayVisibleSound)
827 soundManager.playGuiSound(SOUND_HIDE_WINDOW);
828 }
829 }
830
scheduleDelete()831 void Window::scheduleDelete()
832 {
833 windowContainer->scheduleDelete(this);
834 }
835
mousePressed(MouseEvent & event)836 void Window::mousePressed(MouseEvent &event)
837 {
838 if (event.isConsumed())
839 return;
840
841 if (event.getSource() == this)
842 {
843 if (getParent() != nullptr)
844 getParent()->moveToTop(this);
845
846 mDragOffsetX = event.getX();
847 mDragOffsetY = event.getY();
848 mMoved = event.getY() <= CAST_S32(mTitleBarHeight);
849 }
850
851 const MouseButtonT button = event.getButton();
852 if (button == MouseButton::LEFT)
853 {
854 const int x = event.getX();
855 const int y = event.getY();
856
857 // Handle close button
858 if (mCloseWindowButton &&
859 mSkin != nullptr &&
860 mCloseRect.isPointInRect(x, y))
861 {
862 mouseResize = 0;
863 mMoved = false;
864 event.consume();
865 close();
866 return;
867 }
868
869 // Handle sticky button
870 if (mStickyButton &&
871 mSkin != nullptr &&
872 mStickyRect.isPointInRect(x, y))
873 {
874 setSticky(!isSticky());
875 mouseResize = 0;
876 mMoved = false;
877 event.consume();
878 return;
879 }
880
881 // Handle window resizing
882 mouseResize = getResizeHandles(event) & resizeMask;
883 if (mouseResize != 0)
884 event.consume();
885 if (canMove())
886 mMoved = (mouseResize == 0);
887 else
888 mMoved = false;
889 }
890 #ifndef DYECMD
891 else if (button == MouseButton::RIGHT)
892 {
893 if (popupMenu != nullptr)
894 {
895 event.consume();
896 popupMenu->showWindowPopup(this);
897 }
898 }
899 #endif // DYECMD
900 }
901
close()902 void Window::close()
903 {
904 setVisible(Visible_false);
905 }
906
mouseReleased(MouseEvent & event A_UNUSED)907 void Window::mouseReleased(MouseEvent &event A_UNUSED)
908 {
909 if ((mGrip != nullptr) && (mouseResize != 0))
910 {
911 mouseResize = 0;
912 if (gui != nullptr)
913 gui->setCursorType(Cursor::CURSOR_POINTER);
914 }
915
916 mMoved = false;
917 }
918
mouseEntered(MouseEvent & event)919 void Window::mouseEntered(MouseEvent &event)
920 {
921 updateResizeHandler(event);
922 }
923
mouseExited(MouseEvent & event A_UNUSED)924 void Window::mouseExited(MouseEvent &event A_UNUSED)
925 {
926 if ((mGrip != nullptr) && (mouseResize == 0) && (gui != nullptr))
927 gui->setCursorType(Cursor::CURSOR_POINTER);
928 }
929
updateResizeHandler(MouseEvent & event)930 void Window::updateResizeHandler(MouseEvent &event)
931 {
932 if (gui == nullptr)
933 return;
934
935 #ifndef DYECMD
936 if (!dragDrop.isEmpty())
937 return;
938 #endif // DYECMD
939
940 mResizeHandles = getResizeHandles(event);
941
942 // Changes the custom mouse cursor based on it's current position.
943 switch (mResizeHandles & resizeMask)
944 {
945 case BOTTOM | RIGHT:
946 case TOP | LEFT:
947 gui->setCursorType(Cursor::CURSOR_RESIZE_DOWN_RIGHT);
948 break;
949 case TOP | RIGHT:
950 case BOTTOM | LEFT:
951 gui->setCursorType(Cursor::CURSOR_RESIZE_DOWN_LEFT);
952 break;
953 case BOTTOM:
954 case TOP:
955 gui->setCursorType(Cursor::CURSOR_RESIZE_DOWN);
956 break;
957 case RIGHT:
958 case LEFT:
959 gui->setCursorType(Cursor::CURSOR_RESIZE_ACROSS);
960 break;
961 default:
962 gui->setCursorType(Cursor::CURSOR_POINTER);
963 break;
964 }
965 }
966
mouseMoved(MouseEvent & event)967 void Window::mouseMoved(MouseEvent &event)
968 {
969 updateResizeHandler(event);
970 if ((popupManager != nullptr) && !event.isConsumed())
971 {
972 PopupManager::hideBeingPopup();
973 PopupManager::hideTextPopup();
974 }
975 }
976
canMove() const977 bool Window::canMove() const
978 {
979 return !mStickyButtonLock || !mSticky;
980 }
981
mouseDragged(MouseEvent & event)982 void Window::mouseDragged(MouseEvent &event)
983 {
984 if (canMove())
985 {
986 if (!event.isConsumed() && event.getSource() == this)
987 {
988 if (isMovable() && mMoved)
989 {
990 setPosition(event.getX() - mDragOffsetX + getX(),
991 event.getY() - mDragOffsetY + getY());
992 }
993
994 event.consume();
995 }
996 }
997 else
998 {
999 if (!event.isConsumed() && event.getSource() == this)
1000 event.consume();
1001 return;
1002 }
1003
1004 // Keep guichan window inside screen when it may be moved
1005 if (isMovable() && mMoved)
1006 {
1007 setPosition(std::min(mainGraphics->mWidth - mDimension.width,
1008 std::max(0, mDimension.x)),
1009 std::min(mainGraphics->mHeight - mDimension.height,
1010 std::max(0, mDimension.y)));
1011 }
1012
1013 if ((mouseResize != 0) && !mMoved)
1014 {
1015 const int dy = event.getY() - mDragOffsetY;
1016 Rect newDim = getDimension();
1017
1018 if ((mouseResize & (TOP | BOTTOM)) != 0)
1019 {
1020 const int newHeight = newDim.height
1021 + ((mouseResize & TOP) != 0 ? -dy : dy);
1022 newDim.height = std::min(mMaxWinHeight,
1023 std::max(mMinWinHeight, newHeight));
1024
1025 if ((mouseResize & TOP) != 0)
1026 newDim.y -= newDim.height - getHeight();
1027 }
1028
1029 if ((mouseResize & (LEFT | RIGHT)) != 0)
1030 {
1031 const int dx = event.getX() - mDragOffsetX;
1032 const int newWidth = newDim.width
1033 + ((mouseResize & LEFT) != 0 ? -dx : dx);
1034 newDim.width = std::min(mMaxWinWidth,
1035 std::max(mMinWinWidth, newWidth));
1036
1037 if ((mouseResize & LEFT) != 0)
1038 newDim.x -= newDim.width - mDimension.width;
1039 }
1040
1041 // Keep guichan window inside screen (supports resizing any side)
1042 if (newDim.x < 0)
1043 {
1044 newDim.width += newDim.x;
1045 newDim.x = 0;
1046 }
1047 if (newDim.y < 0)
1048 {
1049 newDim.height += newDim.y;
1050 newDim.y = 0;
1051 }
1052 if (newDim.x + newDim.width > mainGraphics->mWidth)
1053 newDim.width = mainGraphics->mWidth - newDim.x;
1054 if (newDim.y + newDim.height > mainGraphics->mHeight)
1055 newDim.height = mainGraphics->mHeight - newDim.y;
1056
1057 // Update mouse offset when dragging bottom or right border
1058 if ((mouseResize & BOTTOM) != 0)
1059 mDragOffsetY += newDim.height - mDimension.height;
1060
1061 if ((mouseResize & RIGHT) != 0)
1062 mDragOffsetX += newDim.width - mDimension.width;
1063
1064 // Set the new window and content dimensions
1065 setDimension(newDim);
1066 }
1067 }
1068
setModal(const Modal modal)1069 void Window::setModal(const Modal modal)
1070 {
1071 if (mModal != modal)
1072 {
1073 mModal = modal;
1074 if (mModal == Modal_true)
1075 {
1076 if (gui != nullptr)
1077 gui->setCursorType(Cursor::CURSOR_POINTER);
1078 requestModalFocus();
1079 }
1080 else
1081 {
1082 releaseModalFocus();
1083 }
1084 }
1085 }
1086
loadWindowState()1087 void Window::loadWindowState()
1088 {
1089 const std::string &name = mWindowName;
1090 if (name.empty())
1091 return;
1092
1093 setPosition(config.getValueInt(name + "WinX", mDefaultX),
1094 config.getValueInt(name + "WinY", mDefaultY));
1095
1096 if (mSaveVisible)
1097 {
1098 setVisible(fromBool(config.getValueBool(name
1099 + "Visible", mDefaultVisible), Visible));
1100 }
1101
1102 if (mStickyButton)
1103 {
1104 setSticky(config.getValueBool(name
1105 + "Sticky", isSticky()));
1106 }
1107
1108 if (mGrip != nullptr)
1109 {
1110 int width = config.getValueInt(name + "WinWidth", mDefaultWidth);
1111 int height = config.getValueInt(name + "WinHeight", mDefaultHeight);
1112
1113 if (getMinWidth() > width)
1114 width = getMinWidth();
1115 else if (getMaxWidth() < width)
1116 width = getMaxWidth();
1117 if (getMinHeight() > height)
1118 height = getMinHeight();
1119 else if (getMaxHeight() < height)
1120 height = getMaxHeight();
1121
1122 setSize(width, height);
1123 }
1124 else
1125 {
1126 setSize(mDefaultWidth, mDefaultHeight);
1127 }
1128
1129 // Check if the window is off screen...
1130 ensureOnScreen();
1131
1132 if (viewport != nullptr)
1133 {
1134 int width = mDimension.width;
1135 int height = mDimension.height;
1136
1137 if (mDimension.x + width > viewport->getWidth())
1138 width = viewport->getWidth() - mDimension.x;
1139 if (mDimension.y + height > viewport->getHeight())
1140 height = viewport->getHeight() - mDimension.y;
1141 if (width < 0)
1142 width = 0;
1143 if (height < 0)
1144 height = 0;
1145 setSize(width, height);
1146 }
1147 }
1148
saveWindowState()1149 void Window::saveWindowState()
1150 {
1151 // Saving X, Y and Width and Height for resizables in the config
1152 if (!mWindowName.empty() && mWindowName != "window")
1153 {
1154 config.setValue(mWindowName + "WinX", mDimension.x);
1155 config.setValue(mWindowName + "WinY", mDimension.y);
1156
1157 if (mSaveVisible)
1158 config.setValue(mWindowName + "Visible", isWindowVisible());
1159
1160 if (mStickyButton)
1161 config.setValue(mWindowName + "Sticky", isSticky());
1162
1163 if (mGrip != nullptr)
1164 {
1165 if (getMinWidth() > mDimension.width)
1166 setWidth(getMinWidth());
1167 else if (getMaxWidth() < mDimension.width)
1168 setWidth(getMaxWidth());
1169 if (getMinHeight() > mDimension.height)
1170 setHeight(getMinHeight());
1171 else if (getMaxHeight() < mDimension.height)
1172 setHeight(getMaxHeight());
1173
1174 config.setValue(mWindowName + "WinWidth", mDimension.width);
1175 config.setValue(mWindowName + "WinHeight", mDimension.height);
1176 }
1177 }
1178 }
1179
setDefaultSize(const int defaultX,const int defaultY,int defaultWidth,int defaultHeight)1180 void Window::setDefaultSize(const int defaultX, const int defaultY,
1181 int defaultWidth, int defaultHeight)
1182 {
1183 if (mMinWinWidth > defaultWidth)
1184 defaultWidth = mMinWinWidth;
1185 else if (mMaxWinWidth < defaultWidth)
1186 defaultWidth = mMaxWinWidth;
1187 if (mMinWinHeight > defaultHeight)
1188 defaultHeight = mMinWinHeight;
1189 else if (mMaxWinHeight < defaultHeight)
1190 defaultHeight = mMaxWinHeight;
1191
1192 mDefaultX = defaultX;
1193 mDefaultY = defaultY;
1194 mDefaultWidth = defaultWidth;
1195 mDefaultHeight = defaultHeight;
1196 }
1197
setDefaultSize()1198 void Window::setDefaultSize()
1199 {
1200 mDefaultX = mDimension.x;
1201 mDefaultY = mDimension.y;
1202 mDefaultWidth = mDimension.width;
1203 mDefaultHeight = mDimension.height;
1204 }
1205
setDefaultSize(const int defaultWidth,const int defaultHeight,const ImagePosition::Type & position,const int offsetX,const int offsetY)1206 void Window::setDefaultSize(const int defaultWidth, const int defaultHeight,
1207 const ImagePosition::Type &position,
1208 const int offsetX, const int offsetY)
1209 {
1210 int x = 0;
1211 int y = 0;
1212
1213 if (position == ImagePosition::UPPER_LEFT)
1214 {
1215 }
1216 else if (position == ImagePosition::UPPER_CENTER)
1217 {
1218 x = (mainGraphics->mWidth - defaultWidth) / 2;
1219 }
1220 else if (position == ImagePosition::UPPER_RIGHT)
1221 {
1222 x = mainGraphics->mWidth - defaultWidth;
1223 }
1224 else if (position == ImagePosition::LEFT)
1225 {
1226 y = (mainGraphics->mHeight - defaultHeight) / 2;
1227 }
1228 else if (position == ImagePosition::CENTER)
1229 {
1230 x = (mainGraphics->mWidth - defaultWidth) / 2;
1231 y = (mainGraphics->mHeight - defaultHeight) / 2;
1232 }
1233 else if (position == ImagePosition::RIGHT)
1234 {
1235 x = mainGraphics->mWidth - defaultWidth;
1236 y = (mainGraphics->mHeight - defaultHeight) / 2;
1237 }
1238 else if (position == ImagePosition::LOWER_LEFT)
1239 {
1240 y = mainGraphics->mHeight - defaultHeight;
1241 }
1242 else if (position == ImagePosition::LOWER_CENTER)
1243 {
1244 x = (mainGraphics->mWidth - defaultWidth) / 2;
1245 y = mainGraphics->mHeight - defaultHeight;
1246 }
1247 else if (position == ImagePosition::LOWER_RIGHT)
1248 {
1249 x = mainGraphics->mWidth - defaultWidth;
1250 y = mainGraphics->mHeight - defaultHeight;
1251 }
1252
1253 mDefaultX = x - offsetX;
1254 mDefaultY = y - offsetY;
1255 mDefaultWidth = defaultWidth;
1256 mDefaultHeight = defaultHeight;
1257 }
1258
resetToDefaultSize()1259 void Window::resetToDefaultSize()
1260 {
1261 setPosition(mDefaultX, mDefaultY);
1262 setSize(mDefaultWidth, mDefaultHeight);
1263 saveWindowState();
1264 }
1265
adjustPositionAfterResize(const int oldScreenWidth,const int oldScreenHeight)1266 void Window::adjustPositionAfterResize(const int oldScreenWidth,
1267 const int oldScreenHeight)
1268 {
1269 // If window was aligned to the right or bottom, keep it there
1270 const int rightMargin = oldScreenWidth - (mDimension.x + mDimension.width);
1271 const int bottomMargin = oldScreenHeight
1272 - (mDimension.y + mDimension.height);
1273 if (mDimension.x > 0 && mDimension.x > rightMargin)
1274 mDimension.x = mainGraphics->mWidth - rightMargin - mDimension.width;
1275 if (mDimension.y > 0 && mDimension.y > bottomMargin)
1276 {
1277 mDimension.y = mainGraphics->mHeight
1278 - bottomMargin - mDimension.height;
1279 }
1280
1281 ensureOnScreen();
1282 adjustSizeToScreen();
1283 }
1284
adjustSizeToScreen()1285 void Window::adjustSizeToScreen()
1286 {
1287 if (mGrip == nullptr)
1288 return;
1289
1290 const int screenWidth = mainGraphics->mWidth;
1291 const int screenHeight = mainGraphics->mHeight;
1292 const int oldWidth = mDimension.width;
1293 const int oldHeight = mDimension.height;
1294 if (oldWidth + mDimension.x > screenWidth)
1295 mDimension.x = 0;
1296 if (oldHeight + mDimension.y > screenHeight)
1297 mDimension.x = 0;
1298 if (mDimension.width > screenWidth)
1299 mDimension.width = screenWidth;
1300 if (mDimension.height > screenHeight)
1301 mDimension.height = screenHeight;
1302 if (oldWidth != mDimension.width || oldHeight != mDimension.height)
1303 widgetResized(Event(this));
1304 }
1305
getResizeHandles(const MouseEvent & event)1306 int Window::getResizeHandles(const MouseEvent &event)
1307 {
1308 if (event.getX() < 0 || event.getY() < 0)
1309 return 0;
1310
1311 int resizeHandles = 0;
1312 const unsigned y = event.getY();
1313 const unsigned x = event.getX();
1314 if (mCloseRect.isPointInRect(x, y))
1315 return CLOSE;
1316
1317 if (!mStickyButtonLock || !mSticky)
1318 {
1319 if ((mGrip != nullptr) &&
1320 (y > mTitleBarHeight ||
1321 (CAST_S32(y) < mPadding &&
1322 CAST_S32(mTitleBarHeight) > mPadding)))
1323 {
1324 if (!getWindowArea().isPointInRect(x, y)
1325 && event.getSource() == this)
1326 {
1327 resizeHandles |= (x > mDimension.width - resizeBorderWidth)
1328 ? RIGHT : (x < resizeBorderWidth) ? LEFT : 0;
1329 resizeHandles |= (y > mDimension.height - resizeBorderWidth)
1330 ? BOTTOM : (y < resizeBorderWidth) ? TOP : 0;
1331 }
1332 if (x >= CAST_U32(mGripRect.x)
1333 && y >= CAST_U32(mGripRect.y))
1334 {
1335 mDragOffsetX = x;
1336 mDragOffsetY = y;
1337 resizeHandles |= BOTTOM | RIGHT;
1338 }
1339 }
1340 }
1341
1342 return resizeHandles;
1343 }
1344
isResizeAllowed(const MouseEvent & event) const1345 bool Window::isResizeAllowed(const MouseEvent &event) const
1346 {
1347 const int y = event.getY();
1348
1349 if ((mGrip != nullptr) &&
1350 (y > CAST_S32(mTitleBarHeight) ||
1351 y < mPadding))
1352 {
1353 const int x = event.getX();
1354
1355 if (!getWindowArea().isPointInRect(x, y) && event.getSource() == this)
1356 return true;
1357
1358 if (x >= mGripRect.x && y >= mGripRect.y)
1359 return true;
1360 }
1361
1362 return false;
1363 }
1364
getLayout()1365 Layout &Window::getLayout()
1366 {
1367 if (mLayout == nullptr)
1368 mLayout = new Layout;
1369 return *mLayout;
1370 }
1371
clearLayout()1372 void Window::clearLayout()
1373 {
1374 clear();
1375
1376 // Recreate layout instance when one is present
1377 if (mLayout != nullptr)
1378 {
1379 delete mLayout;
1380 mLayout = new Layout;
1381 }
1382 }
1383
place(const int x,const int y,Widget * const wg,const int w,const int h)1384 LayoutCell &Window::place(const int x, const int y, Widget *const wg,
1385 const int w, const int h)
1386 {
1387 add(wg);
1388 return getLayout().place(wg, x, y, w, h);
1389 }
1390
getPlacer(const int x,const int y)1391 ContainerPlacer Window::getPlacer(const int x, const int y)
1392 {
1393 return ContainerPlacer(this, &getLayout().at(x, y));
1394 }
1395
reflowLayout(int w,int h)1396 void Window::reflowLayout(int w, int h)
1397 {
1398 if (mLayout == nullptr)
1399 return;
1400
1401 mLayout->reflow(w, h);
1402 delete2(mLayout)
1403 setContentSize(w, h);
1404 }
1405
redraw()1406 void Window::redraw()
1407 {
1408 if (mLayout != nullptr)
1409 {
1410 const Rect area = getChildrenArea();
1411 int w = area.width;
1412 int h = area.height;
1413 mLayout->reflow(w, h);
1414 }
1415 }
1416
center()1417 void Window::center()
1418 {
1419 setLocationRelativeTo(getParent());
1420 }
1421
centerHorisontally()1422 void Window::centerHorisontally()
1423 {
1424 setLocationHorisontallyRelativeTo(getParent());
1425 }
1426
ensureOnScreen()1427 void Window::ensureOnScreen()
1428 {
1429 // Skip when a window hasn't got any size initialized yet
1430 if (mDimension.width == 0 && mDimension.height == 0)
1431 return;
1432
1433 // Check the left and bottom screen boundaries
1434 if (mDimension.x + mDimension.width > mainGraphics->mWidth)
1435 mDimension.x = mainGraphics->mWidth - mDimension.width;
1436 if (mDimension.y + mDimension.height > mainGraphics->mHeight)
1437 mDimension.y = mainGraphics->mHeight - mDimension.height;
1438
1439 // But never allow the windows to disappear in to the right and top
1440 if (mDimension.x < 0)
1441 mDimension.x = 0;
1442 if (mDimension.y < 0)
1443 mDimension.y = 0;
1444 }
1445
getWindowArea() const1446 Rect Window::getWindowArea() const
1447 {
1448 return Rect(mPadding,
1449 mPadding,
1450 mDimension.width - mPadding * 2,
1451 mDimension.height - mPadding * 2);
1452 }
1453
getOption(const std::string & name,const int def) const1454 int Window::getOption(const std::string &name, const int def) const
1455 {
1456 if (mSkin != nullptr)
1457 {
1458 const int val = mSkin->getOption(name);
1459 if (val != 0)
1460 return val;
1461 return def;
1462 }
1463 return def;
1464 }
1465
getOptionBool(const std::string & name,const bool def) const1466 bool Window::getOptionBool(const std::string &name, const bool def) const
1467 {
1468 if (mSkin != nullptr)
1469 return mSkin->getOption(name, static_cast<int>(def)) != 0;
1470 return def;
1471 }
1472
getChildrenArea()1473 Rect Window::getChildrenArea()
1474 {
1475 return Rect(mPadding,
1476 mTitleBarHeight,
1477 mDimension.width - mPadding * 2,
1478 mDimension.height - mPadding - mTitleBarHeight);
1479 }
1480
resizeToContent()1481 void Window::resizeToContent()
1482 {
1483 int w = 0;
1484 int h = 0;
1485 for (WidgetListConstIterator it = mWidgets.begin();
1486 it != mWidgets.end(); ++ it)
1487 {
1488 const Widget *const widget = *it;
1489 const int x = widget->getX();
1490 const int y = widget->getY();
1491 const int width = widget->getWidth();
1492 const int height = widget->getHeight();
1493 if (x + width > w)
1494 w = x + width;
1495
1496 if (y + height > h)
1497 h = y + height;
1498 }
1499
1500 setSize(w + 2 * mPadding,
1501 h + mPadding + mTitleBarHeight);
1502 }
1503
1504 #ifdef USE_PROFILER
logic()1505 void Window::logic()
1506 {
1507 BLOCK_START("Window::logic")
1508 logicChildren();
1509 BLOCK_END("Window::logic")
1510 }
1511 #endif // USE_PROFILER
1512