1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 class DocumentWindow::ButtonListenerProxy  : public Button::Listener
30 {
31 public:
ButtonListenerProxy(DocumentWindow & w)32     ButtonListenerProxy (DocumentWindow& w) : owner (w) {}
33 
buttonClicked(Button * button)34     void buttonClicked (Button* button) override
35     {
36         if      (button == owner.getMinimiseButton())  owner.minimiseButtonPressed();
37         else if (button == owner.getMaximiseButton())  owner.maximiseButtonPressed();
38         else if (button == owner.getCloseButton())     owner.closeButtonPressed();
39     }
40 
41 private:
42     DocumentWindow& owner;
43 
44     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonListenerProxy)
45 };
46 
47 //==============================================================================
DocumentWindow(const String & title,Colour backgroundColour,int requiredButtons_,bool addToDesktop_)48 DocumentWindow::DocumentWindow (const String& title,
49                                 Colour backgroundColour,
50                                 int requiredButtons_,
51                                 bool addToDesktop_)
52     : ResizableWindow (title, backgroundColour, addToDesktop_),
53       requiredButtons (requiredButtons_),
54      #if JUCE_MAC
55       positionTitleBarButtonsOnLeft (true)
56      #else
57       positionTitleBarButtonsOnLeft (false)
58      #endif
59 {
60     setResizeLimits (128, 128, 32768, 32768);
61 
62     DocumentWindow::lookAndFeelChanged();
63 }
64 
~DocumentWindow()65 DocumentWindow::~DocumentWindow()
66 {
67     // Don't delete or remove the resizer components yourself! They're managed by the
68     // DocumentWindow, and you should leave them alone! You may have deleted them
69     // accidentally by careless use of deleteAllChildren()..?
70     jassert (menuBar == nullptr || getIndexOfChildComponent (menuBar.get()) >= 0);
71     jassert (titleBarButtons[0] == nullptr || getIndexOfChildComponent (titleBarButtons[0].get()) >= 0);
72     jassert (titleBarButtons[1] == nullptr || getIndexOfChildComponent (titleBarButtons[1].get()) >= 0);
73     jassert (titleBarButtons[2] == nullptr || getIndexOfChildComponent (titleBarButtons[2].get()) >= 0);
74 
75     for (auto& b : titleBarButtons)
76         b.reset();
77 
78     menuBar.reset();
79 }
80 
81 //==============================================================================
repaintTitleBar()82 void DocumentWindow::repaintTitleBar()
83 {
84     repaint (getTitleBarArea());
85 }
86 
setName(const String & newName)87 void DocumentWindow::setName (const String& newName)
88 {
89     if (newName != getName())
90     {
91         Component::setName (newName);
92         repaintTitleBar();
93     }
94 }
95 
setIcon(const Image & imageToUse)96 void DocumentWindow::setIcon (const Image& imageToUse)
97 {
98     titleBarIcon = imageToUse;
99     repaintTitleBar();
100 }
101 
setTitleBarHeight(const int newHeight)102 void DocumentWindow::setTitleBarHeight (const int newHeight)
103 {
104     titleBarHeight = newHeight;
105     resized();
106     repaintTitleBar();
107 }
108 
setTitleBarButtonsRequired(const int buttons,const bool onLeft)109 void DocumentWindow::setTitleBarButtonsRequired (const int buttons, const bool onLeft)
110 {
111     requiredButtons = buttons;
112     positionTitleBarButtonsOnLeft = onLeft;
113     lookAndFeelChanged();
114 }
115 
setTitleBarTextCentred(const bool textShouldBeCentred)116 void DocumentWindow::setTitleBarTextCentred (const bool textShouldBeCentred)
117 {
118     drawTitleTextCentred = textShouldBeCentred;
119     repaintTitleBar();
120 }
121 
122 //==============================================================================
setMenuBar(MenuBarModel * newMenuBarModel,const int newMenuBarHeight)123 void DocumentWindow::setMenuBar (MenuBarModel* newMenuBarModel, const int newMenuBarHeight)
124 {
125     if (menuBarModel != newMenuBarModel)
126     {
127         menuBar.reset();
128 
129         menuBarModel = newMenuBarModel;
130         menuBarHeight = newMenuBarHeight > 0 ? newMenuBarHeight
131                                              : getLookAndFeel().getDefaultMenuBarHeight();
132 
133         if (menuBarModel != nullptr)
134             setMenuBarComponent (new MenuBarComponent (menuBarModel));
135 
136         resized();
137     }
138 }
139 
getMenuBarComponent() const140 Component* DocumentWindow::getMenuBarComponent() const noexcept
141 {
142     return menuBar.get();
143 }
144 
setMenuBarComponent(Component * newMenuBarComponent)145 void DocumentWindow::setMenuBarComponent (Component* newMenuBarComponent)
146 {
147     menuBar.reset (newMenuBarComponent);
148     Component::addAndMakeVisible (menuBar.get()); // (call the superclass method directly to avoid the assertion in ResizableWindow)
149 
150     if (menuBar != nullptr)
151         menuBar->setEnabled (isActiveWindow());
152 
153     resized();
154 }
155 
156 //==============================================================================
closeButtonPressed()157 void DocumentWindow::closeButtonPressed()
158 {
159     /*  If you've got a close button, you have to override this method to get
160         rid of your window!
161 
162         If the window is just a pop-up, you should override this method and make
163         it delete the window in whatever way is appropriate for your app. E.g. you
164         might just want to call "delete this".
165 
166         If your app is centred around this window such that the whole app should quit when
167         the window is closed, then you will probably want to use this method as an opportunity
168         to call JUCEApplicationBase::quit(), and leave the window to be deleted later by your
169         JUCEApplicationBase::shutdown() method. (Doing it this way means that your window will
170         still get cleaned-up if the app is quit by some other means (e.g. a cmd-Q on the mac
171         or closing it via the taskbar icon on Windows).
172     */
173     jassertfalse;
174 }
175 
minimiseButtonPressed()176 void DocumentWindow::minimiseButtonPressed()
177 {
178     setMinimised (true);
179 }
180 
maximiseButtonPressed()181 void DocumentWindow::maximiseButtonPressed()
182 {
183     setFullScreen (! isFullScreen());
184 }
185 
186 //==============================================================================
paint(Graphics & g)187 void DocumentWindow::paint (Graphics& g)
188 {
189     ResizableWindow::paint (g);
190 
191     auto titleBarArea = getTitleBarArea();
192     g.reduceClipRegion (titleBarArea);
193     g.setOrigin (titleBarArea.getPosition());
194 
195     int titleSpaceX1 = 6;
196     int titleSpaceX2 = titleBarArea.getWidth() - 6;
197 
198     for (auto& b : titleBarButtons)
199     {
200         if (b != nullptr)
201         {
202             if (positionTitleBarButtonsOnLeft)
203                 titleSpaceX1 = jmax (titleSpaceX1, b->getRight() + (getWidth() - b->getRight()) / 8);
204             else
205                 titleSpaceX2 = jmin (titleSpaceX2, b->getX() - (b->getX() / 8));
206         }
207     }
208 
209     getLookAndFeel().drawDocumentWindowTitleBar (*this, g,
210                                                  titleBarArea.getWidth(),
211                                                  titleBarArea.getHeight(),
212                                                  titleSpaceX1,
213                                                  jmax (1, titleSpaceX2 - titleSpaceX1),
214                                                  titleBarIcon.isValid() ? &titleBarIcon : nullptr,
215                                                  ! drawTitleTextCentred);
216 }
217 
resized()218 void DocumentWindow::resized()
219 {
220     ResizableWindow::resized();
221 
222     if (auto* b = getMaximiseButton())
223         b->setToggleState (isFullScreen(), dontSendNotification);
224 
225     auto titleBarArea = getTitleBarArea();
226 
227     getLookAndFeel()
228         .positionDocumentWindowButtons (*this,
229                                         titleBarArea.getX(), titleBarArea.getY(),
230                                         titleBarArea.getWidth(), titleBarArea.getHeight(),
231                                         titleBarButtons[0].get(),
232                                         titleBarButtons[1].get(),
233                                         titleBarButtons[2].get(),
234                                         positionTitleBarButtonsOnLeft);
235 
236     if (menuBar != nullptr)
237         menuBar->setBounds (titleBarArea.getX(), titleBarArea.getBottom(),
238                             titleBarArea.getWidth(), menuBarHeight);
239 }
240 
getBorderThickness()241 BorderSize<int> DocumentWindow::getBorderThickness()
242 {
243     return ResizableWindow::getBorderThickness();
244 }
245 
getContentComponentBorder()246 BorderSize<int> DocumentWindow::getContentComponentBorder()
247 {
248     auto border = getBorderThickness();
249 
250     if (! isKioskMode())
251         border.setTop (border.getTop()
252                         + (isUsingNativeTitleBar() ? 0 : titleBarHeight)
253                         + (menuBar != nullptr ? menuBarHeight : 0));
254 
255     return border;
256 }
257 
getTitleBarHeight() const258 int DocumentWindow::getTitleBarHeight() const
259 {
260     return isUsingNativeTitleBar() ? 0 : jmin (titleBarHeight, getHeight() - 4);
261 }
262 
getTitleBarArea()263 Rectangle<int> DocumentWindow::getTitleBarArea()
264 {
265     if (isKioskMode())
266         return {};
267 
268     auto border = getBorderThickness();
269     return { border.getLeft(), border.getTop(), getWidth() - border.getLeftAndRight(), getTitleBarHeight() };
270 }
271 
getCloseButton() const272 Button* DocumentWindow::getCloseButton()    const noexcept  { return titleBarButtons[2].get(); }
getMinimiseButton() const273 Button* DocumentWindow::getMinimiseButton() const noexcept  { return titleBarButtons[0].get(); }
getMaximiseButton() const274 Button* DocumentWindow::getMaximiseButton() const noexcept  { return titleBarButtons[1].get(); }
275 
getDesktopWindowStyleFlags() const276 int DocumentWindow::getDesktopWindowStyleFlags() const
277 {
278     auto styleFlags = ResizableWindow::getDesktopWindowStyleFlags();
279 
280     if ((requiredButtons & minimiseButton) != 0)  styleFlags |= ComponentPeer::windowHasMinimiseButton;
281     if ((requiredButtons & maximiseButton) != 0)  styleFlags |= ComponentPeer::windowHasMaximiseButton;
282     if ((requiredButtons & closeButton)    != 0)  styleFlags |= ComponentPeer::windowHasCloseButton;
283 
284     return styleFlags;
285 }
286 
lookAndFeelChanged()287 void DocumentWindow::lookAndFeelChanged()
288 {
289     for (auto& b : titleBarButtons)
290         b.reset();
291 
292     if (! isUsingNativeTitleBar())
293     {
294         auto& lf = getLookAndFeel();
295 
296         if ((requiredButtons & minimiseButton) != 0)  titleBarButtons[0].reset (lf.createDocumentWindowButton (minimiseButton));
297         if ((requiredButtons & maximiseButton) != 0)  titleBarButtons[1].reset (lf.createDocumentWindowButton (maximiseButton));
298         if ((requiredButtons & closeButton)    != 0)  titleBarButtons[2].reset (lf.createDocumentWindowButton (closeButton));
299 
300         for (auto& b : titleBarButtons)
301         {
302             if (b != nullptr)
303             {
304                 if (buttonListener == nullptr)
305                     buttonListener.reset (new ButtonListenerProxy (*this));
306 
307                 b->addListener (buttonListener.get());
308                 b->setWantsKeyboardFocus (false);
309 
310                 // (call the Component method directly to avoid the assertion in ResizableWindow)
311                 Component::addAndMakeVisible (b.get());
312             }
313         }
314 
315         if (auto* b = getCloseButton())
316         {
317            #if JUCE_MAC
318             b->addShortcut (KeyPress ('w', ModifierKeys::commandModifier, 0));
319            #else
320             b->addShortcut (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0));
321            #endif
322         }
323     }
324 
325     activeWindowStatusChanged();
326 
327     ResizableWindow::lookAndFeelChanged();
328 }
329 
parentHierarchyChanged()330 void DocumentWindow::parentHierarchyChanged()
331 {
332     lookAndFeelChanged();
333 }
334 
activeWindowStatusChanged()335 void DocumentWindow::activeWindowStatusChanged()
336 {
337     ResizableWindow::activeWindowStatusChanged();
338     bool isActive = isActiveWindow();
339 
340     for (auto& b : titleBarButtons)
341         if (b != nullptr)
342             b->setEnabled (isActive);
343 
344     if (menuBar != nullptr)
345         menuBar->setEnabled (isActive);
346 }
347 
mouseDoubleClick(const MouseEvent & e)348 void DocumentWindow::mouseDoubleClick (const MouseEvent& e)
349 {
350     if (getTitleBarArea().contains (e.x, e.y))
351         if (auto* maximise = getMaximiseButton())
352             maximise->triggerClick();
353 }
354 
userTriedToCloseWindow()355 void DocumentWindow::userTriedToCloseWindow()
356 {
357     closeButtonPressed();
358 }
359 
360 } // namespace juce
361