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