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 struct Spinner  : public Component,
30                   private Timer
31 {
Spinnerjuce::Spinner32     Spinner()                       { startTimer (1000 / 50); }
timerCallbackjuce::Spinner33     void timerCallback() override   { repaint(); }
34 
paintjuce::Spinner35     void paint (Graphics& g) override
36     {
37         getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight());
38     }
39 };
40 
41 struct OnlineUnlockForm::OverlayComp  : public Component,
42                                         private Thread,
43                                         private Timer,
44                                         private Button::Listener
45 {
OverlayCompjuce::OnlineUnlockForm::OverlayComp46     OverlayComp (OnlineUnlockForm& f, bool hasCancelButton = false)
47         : Thread (String()), form (f)
48     {
49         result.succeeded = false;
50         email = form.emailBox.getText();
51         password = form.passwordBox.getText();
52         addAndMakeVisible (spinner);
53 
54         if (hasCancelButton)
55         {
56             cancelButton.reset (new TextButton (TRANS ("Cancel")));
57             addAndMakeVisible (cancelButton.get());
58             cancelButton->addListener (this);
59         }
60 
61         startThread (4);
62     }
63 
~OverlayCompjuce::OnlineUnlockForm::OverlayComp64     ~OverlayComp() override
65     {
66         stopThread (10000);
67     }
68 
paintjuce::OnlineUnlockForm::OverlayComp69     void paint (Graphics& g) override
70     {
71         g.fillAll (Colours::white.withAlpha (0.97f));
72 
73         g.setColour (Colours::black);
74         g.setFont (15.0f);
75 
76         g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()),
77                           getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)),
78                           Justification::centred, 5);
79     }
80 
resizedjuce::OnlineUnlockForm::OverlayComp81     void resized() override
82     {
83         const int spinnerSize = 40;
84         spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize);
85 
86         if (cancelButton != nullptr)
87             cancelButton->setBounds (getLocalBounds().removeFromBottom (50).reduced (getWidth() / 4, 5));
88     }
89 
runjuce::OnlineUnlockForm::OverlayComp90     void run() override
91     {
92         result = form.status.attemptWebserverUnlock (email, password);
93         startTimer (100);
94     }
95 
timerCallbackjuce::OnlineUnlockForm::OverlayComp96     void timerCallback() override
97     {
98         spinner.setVisible (false);
99         stopTimer();
100 
101         if (result.errorMessage.isNotEmpty())
102         {
103             AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
104                                               TRANS("Registration Failed"),
105                                               result.errorMessage);
106         }
107         else if (result.informativeMessage.isNotEmpty())
108         {
109             AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
110                                               TRANS("Registration Complete!"),
111                                               result.informativeMessage);
112         }
113         else if (result.urlToLaunch.isNotEmpty())
114         {
115             URL url (result.urlToLaunch);
116             url.launchInDefaultBrowser();
117         }
118 
119         // (local copies because we're about to delete this)
120         const bool worked = result.succeeded;
121         OnlineUnlockForm& f = form;
122 
123         delete this;
124 
125         if (worked)
126             f.dismiss();
127     }
128 
buttonClickedjuce::OnlineUnlockForm::OverlayComp129     void buttonClicked (Button* button) override
130     {
131         if (button == cancelButton.get())
132         {
133             form.status.userCancelled();
134 
135             spinner.setVisible (false);
136             stopTimer();
137 
138             delete this;
139         }
140     }
141 
142     OnlineUnlockForm& form;
143     Spinner spinner;
144     OnlineUnlockStatus::UnlockResult result;
145     String email, password;
146 
147     std::unique_ptr<TextButton> cancelButton;
148 
149     JUCE_LEAK_DETECTOR (OnlineUnlockForm::OverlayComp)
150 };
151 
getDefaultPasswordChar()152 static juce_wchar getDefaultPasswordChar() noexcept
153 {
154    #if JUCE_LINUX || JUCE_BSD
155     return 0x2022;
156    #else
157     return 0x25cf;
158    #endif
159 }
160 
OnlineUnlockForm(OnlineUnlockStatus & s,const String & userInstructions,bool hasCancelButton,bool overlayHasCancelButton)161 OnlineUnlockForm::OnlineUnlockForm (OnlineUnlockStatus& s,
162                                     const String& userInstructions,
163                                     bool hasCancelButton,
164                                     bool overlayHasCancelButton)
165     : message (String(), userInstructions),
166       passwordBox (String(), getDefaultPasswordChar()),
167       registerButton (TRANS("Register")),
168       cancelButton (TRANS ("Cancel")),
169       status (s),
170       showOverlayCancelButton (overlayHasCancelButton)
171 {
172     // Please supply a message to tell your users what to do!
173     jassert (userInstructions.isNotEmpty());
174 
175     setOpaque (true);
176 
177     emailBox.setText (status.getUserEmail());
178     message.setJustificationType (Justification::centred);
179 
180     addAndMakeVisible (message);
181     addAndMakeVisible (emailBox);
182     addAndMakeVisible (passwordBox);
183     addAndMakeVisible (registerButton);
184 
185     if (hasCancelButton)
186         addAndMakeVisible (cancelButton);
187 
188     emailBox.setEscapeAndReturnKeysConsumed (false);
189     passwordBox.setEscapeAndReturnKeysConsumed (false);
190 
191     registerButton.addShortcut (KeyPress (KeyPress::returnKey));
192 
193     registerButton.addListener (this);
194     cancelButton.addListener (this);
195 
196     lookAndFeelChanged();
197     setSize (500, 250);
198 }
199 
~OnlineUnlockForm()200 OnlineUnlockForm::~OnlineUnlockForm()
201 {
202     unlockingOverlay.deleteAndZero();
203 }
204 
paint(Graphics & g)205 void OnlineUnlockForm::paint (Graphics& g)
206 {
207     g.fillAll (Colours::lightgrey);
208 }
209 
resized()210 void OnlineUnlockForm::resized()
211 {
212     /* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW!
213        Plugins that create external windows are incredibly annoying for users, and
214        cause all sorts of headaches for hosts. Don't be the person who writes that
215        plugin that irritates everyone with a nagging dialog box every time they scan!
216     */
217     jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr);
218 
219     const int buttonHeight = 22;
220 
221     auto r = getLocalBounds().reduced (10, 20);
222 
223     auto buttonArea = r.removeFromBottom (buttonHeight);
224     registerButton.changeWidthToFitText (buttonHeight);
225     cancelButton.changeWidthToFitText (buttonHeight);
226 
227     const int gap = 20;
228     buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth()
229                                                      + (cancelButton.isVisible() ? gap + cancelButton.getWidth() : 0),
230                                                    buttonHeight);
231     registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth()));
232     buttonArea.removeFromLeft (gap);
233     cancelButton.setBounds (buttonArea);
234 
235     r.removeFromBottom (20);
236 
237     // (force use of a default system font to make sure it has the password blob character)
238     Font font (Font::getDefaultTypefaceForFont (Font (Font::getDefaultSansSerifFontName(),
239                                                       Font::getDefaultStyle(),
240                                                       5.0f)));
241 
242     const int boxHeight = 24;
243     passwordBox.setBounds (r.removeFromBottom (boxHeight));
244     passwordBox.setInputRestrictions (64);
245     passwordBox.setFont (font);
246 
247     r.removeFromBottom (20);
248     emailBox.setBounds (r.removeFromBottom (boxHeight));
249     emailBox.setInputRestrictions (512);
250     emailBox.setFont (font);
251 
252     r.removeFromBottom (20);
253 
254     message.setBounds (r);
255 
256     if (unlockingOverlay != nullptr)
257         unlockingOverlay->setBounds (getLocalBounds());
258 }
259 
lookAndFeelChanged()260 void OnlineUnlockForm::lookAndFeelChanged()
261 {
262     Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f));
263 
264     emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol);
265     passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol);
266 }
267 
showBubbleMessage(const String & text,Component & target)268 void OnlineUnlockForm::showBubbleMessage (const String& text, Component& target)
269 {
270     bubble.reset (new BubbleMessageComponent (500));
271     addChildComponent (bubble.get());
272 
273     AttributedString attString;
274     attString.append (text, Font (16.0f));
275 
276     bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
277                     attString, 500,  // numMillisecondsBeforeRemoving
278                     true,  // removeWhenMouseClicked
279                     false); // deleteSelfAfterUse
280 }
281 
buttonClicked(Button * b)282 void OnlineUnlockForm::buttonClicked (Button* b)
283 {
284     if (b == &registerButton)
285         attemptRegistration();
286     else if (b == &cancelButton)
287         dismiss();
288 }
289 
attemptRegistration()290 void OnlineUnlockForm::attemptRegistration()
291 {
292     if (unlockingOverlay == nullptr)
293     {
294         if (emailBox.getText().trim().length() < 3)
295         {
296             showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox);
297             return;
298         }
299 
300         if (passwordBox.getText().trim().length() < 3)
301         {
302             showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox);
303             return;
304         }
305 
306         status.setUserEmail (emailBox.getText());
307 
308         addAndMakeVisible (unlockingOverlay = new OverlayComp (*this, showOverlayCancelButton));
309         resized();
310         unlockingOverlay->enterModalState();
311     }
312 }
313 
dismiss()314 void OnlineUnlockForm::dismiss()
315 {
316     delete this;
317 }
318 
319 } // namespace juce
320