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 FileChooserDialogBox::ContentComponent  : public Component
30 {
31 public:
ContentComponent(const String & name,const String & desc,FileBrowserComponent & chooser)32     ContentComponent (const String& name, const String& desc, FileBrowserComponent& chooser)
33         : Component (name),
34           chooserComponent (chooser),
35           okButton (chooser.getActionVerb()),
36           cancelButton (TRANS ("Cancel")),
37           newFolderButton (TRANS ("New Folder")),
38           instructions (desc)
39     {
40         addAndMakeVisible (chooserComponent);
41 
42         addAndMakeVisible (okButton);
43         okButton.addShortcut (KeyPress (KeyPress::returnKey));
44 
45         addAndMakeVisible (cancelButton);
46         cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
47 
48         addChildComponent (newFolderButton);
49 
50         setInterceptsMouseClicks (false, true);
51     }
52 
paint(Graphics & g)53     void paint (Graphics& g) override
54     {
55         text.draw (g, getLocalBounds().reduced (6)
56                         .removeFromTop ((int) text.getHeight()).toFloat());
57     }
58 
resized()59     void resized() override
60     {
61         const int buttonHeight = 26;
62 
63         auto area = getLocalBounds();
64 
65         text.createLayout (getLookAndFeel().createFileChooserHeaderText (getName(), instructions),
66                            (float) getWidth() - 12.0f);
67 
68         area.removeFromTop (roundToInt (text.getHeight()) + 10);
69 
70         chooserComponent.setBounds (area.removeFromTop (area.getHeight() - buttonHeight - 20));
71         auto buttonArea = area.reduced (16, 10);
72 
73         okButton.changeWidthToFitText (buttonHeight);
74         okButton.setBounds (buttonArea.removeFromRight (okButton.getWidth() + 16));
75 
76         buttonArea.removeFromRight (16);
77 
78         cancelButton.changeWidthToFitText (buttonHeight);
79         cancelButton.setBounds (buttonArea.removeFromRight (cancelButton.getWidth()));
80 
81         newFolderButton.changeWidthToFitText (buttonHeight);
82         newFolderButton.setBounds (buttonArea.removeFromLeft (newFolderButton.getWidth()));
83     }
84 
85     FileBrowserComponent& chooserComponent;
86     TextButton okButton, cancelButton, newFolderButton;
87     String instructions;
88     TextLayout text;
89 };
90 
91 //==============================================================================
FileChooserDialogBox(const String & name,const String & instructions,FileBrowserComponent & chooserComponent,bool shouldWarn,Colour backgroundColour,Component * parentComp)92 FileChooserDialogBox::FileChooserDialogBox (const String& name,
93                                             const String& instructions,
94                                             FileBrowserComponent& chooserComponent,
95                                             bool shouldWarn,
96                                             Colour backgroundColour,
97                                             Component* parentComp)
98     : ResizableWindow (name, backgroundColour, parentComp == nullptr),
99       warnAboutOverwritingExistingFiles (shouldWarn)
100 {
101     content = new ContentComponent (name, instructions, chooserComponent);
102     setContentOwned (content, false);
103 
104     setResizable (true, true);
105     setResizeLimits (300, 300, 1200, 1000);
106 
107     content->okButton.onClick        = [this] { okButtonPressed(); };
108     content->cancelButton.onClick    = [this] { closeButtonPressed(); };
109     content->newFolderButton.onClick = [this] { createNewFolder(); };
110 
111     content->chooserComponent.addListener (this);
112 
113     FileChooserDialogBox::selectionChanged();
114 
115     if (parentComp != nullptr)
116         parentComp->addAndMakeVisible (this);
117     else
118         setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
119 }
120 
~FileChooserDialogBox()121 FileChooserDialogBox::~FileChooserDialogBox()
122 {
123     content->chooserComponent.removeListener (this);
124 }
125 
126 //==============================================================================
127 #if JUCE_MODAL_LOOPS_PERMITTED
show(int w,int h)128 bool FileChooserDialogBox::show (int w, int h)
129 {
130     return showAt (-1, -1, w, h);
131 }
132 
showAt(int x,int y,int w,int h)133 bool FileChooserDialogBox::showAt (int x, int y, int w, int h)
134 {
135     if (w <= 0)  w = getDefaultWidth();
136     if (h <= 0)  h = 500;
137 
138     if (x < 0 || y < 0)
139         centreWithSize (w, h);
140     else
141         setBounds (x, y, w, h);
142 
143     const bool ok = (runModalLoop() != 0);
144     setVisible (false);
145     return ok;
146 }
147 #endif
148 
centreWithDefaultSize(Component * componentToCentreAround)149 void FileChooserDialogBox::centreWithDefaultSize (Component* componentToCentreAround)
150 {
151     centreAroundComponent (componentToCentreAround, getDefaultWidth(), 500);
152 }
153 
getDefaultWidth() const154 int FileChooserDialogBox::getDefaultWidth() const
155 {
156     if (auto* previewComp = content->chooserComponent.getPreviewComponent())
157         return 400 + previewComp->getWidth();
158 
159     return 600;
160 }
161 
162 //==============================================================================
closeButtonPressed()163 void FileChooserDialogBox::closeButtonPressed()
164 {
165     setVisible (false);
166 }
167 
selectionChanged()168 void FileChooserDialogBox::selectionChanged()
169 {
170     content->okButton.setEnabled (content->chooserComponent.currentFileIsValid());
171 
172     content->newFolderButton.setVisible (content->chooserComponent.isSaveMode()
173                                           && content->chooserComponent.getRoot().isDirectory());
174 }
175 
fileDoubleClicked(const File &)176 void FileChooserDialogBox::fileDoubleClicked (const File&)
177 {
178     selectionChanged();
179     content->okButton.triggerClick();
180 }
181 
fileClicked(const File &,const MouseEvent &)182 void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) {}
browserRootChanged(const File &)183 void FileChooserDialogBox::browserRootChanged (const File&) {}
184 
okToOverwriteFileCallback(int result,FileChooserDialogBox * box)185 void FileChooserDialogBox::okToOverwriteFileCallback (int result, FileChooserDialogBox* box)
186 {
187     if (result != 0 && box != nullptr)
188         box->exitModalState (1);
189 }
190 
okButtonPressed()191 void FileChooserDialogBox::okButtonPressed()
192 {
193     if (warnAboutOverwritingExistingFiles
194          && content->chooserComponent.isSaveMode()
195          && content->chooserComponent.getSelectedFile(0).exists())
196     {
197         AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
198                                       TRANS("File already exists"),
199                                       TRANS("There's already a file called: FLNM")
200                                          .replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName())
201                                         + "\n\n"
202                                         + TRANS("Are you sure you want to overwrite it?"),
203                                       TRANS("Overwrite"),
204                                       TRANS("Cancel"),
205                                       this,
206                                       ModalCallbackFunction::forComponent (okToOverwriteFileCallback, this));
207     }
208     else
209     {
210         exitModalState (1);
211     }
212 }
213 
createNewFolderCallback(int result,FileChooserDialogBox * box,Component::SafePointer<AlertWindow> alert)214 void FileChooserDialogBox::createNewFolderCallback (int result, FileChooserDialogBox* box,
215                                                     Component::SafePointer<AlertWindow> alert)
216 {
217     if (result != 0 && alert != nullptr && box != nullptr)
218     {
219         alert->setVisible (false);
220         box->createNewFolderConfirmed (alert->getTextEditorContents ("Folder Name"));
221     }
222 }
223 
createNewFolder()224 void FileChooserDialogBox::createNewFolder()
225 {
226     auto parent = content->chooserComponent.getRoot();
227 
228     if (parent.isDirectory())
229     {
230         auto* aw = new AlertWindow (TRANS("New Folder"),
231                                     TRANS("Please enter the name for the folder"),
232                                     AlertWindow::NoIcon, this);
233 
234         aw->addTextEditor ("Folder Name", String(), String(), false);
235         aw->addButton (TRANS("Create Folder"), 1, KeyPress (KeyPress::returnKey));
236         aw->addButton (TRANS("Cancel"),        0, KeyPress (KeyPress::escapeKey));
237 
238         aw->enterModalState (true,
239                              ModalCallbackFunction::forComponent (createNewFolderCallback, this,
240                                                                   Component::SafePointer<AlertWindow> (aw)),
241                              true);
242     }
243 }
244 
createNewFolderConfirmed(const String & nameFromDialog)245 void FileChooserDialogBox::createNewFolderConfirmed (const String& nameFromDialog)
246 {
247     auto name = File::createLegalFileName (nameFromDialog);
248 
249     if (! name.isEmpty())
250     {
251         auto parent = content->chooserComponent.getRoot();
252 
253         if (! parent.getChildFile (name).createDirectory())
254             AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
255                                               TRANS ("New Folder"),
256                                               TRANS ("Couldn't create the folder!"));
257 
258         content->chooserComponent.refresh();
259     }
260 }
261 
262 } // namespace juce
263