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 #pragma once
27 
28 
29 //==============================================================================
30 class FileGroupInformationComponent  : public Component,
31                                        private ListBoxModel,
32                                        private ValueTree::Listener
33 {
34 public:
FileGroupInformationComponent(const Project::Item & group)35     FileGroupInformationComponent (const Project::Item& group)
36         : item (group),
37           header (item.getName(), { getIcons().openFolder, Colours::transparentBlack })
38     {
39         list.setHeaderComponent (std::make_unique<ListBoxHeader> (Array<String> { "File", "Binary Resource", "Xcode Resource", "Compile", "Skip PCH", "Compiler Flag Scheme" },
40                                                                   Array<float>  {  0.25f,  0.125f,            0.125f,           0.125f,    0.125f,     0.25f }));
41         list.setModel (this);
42         list.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
43         addAndMakeVisible (list);
44         list.updateContent();
45         list.setRowHeight (30);
46         item.state.addListener (this);
47         lookAndFeelChanged();
48 
49         addAndMakeVisible (header);
50     }
51 
~FileGroupInformationComponent()52     ~FileGroupInformationComponent() override
53     {
54         item.state.removeListener (this);
55     }
56 
57     //==============================================================================
paint(Graphics & g)58     void paint (Graphics& g) override
59     {
60         g.setColour (findColour (secondaryBackgroundColourId));
61         g.fillRect (getLocalBounds().reduced (12, 0));
62     }
63 
resized()64     void resized() override
65     {
66         auto bounds = getLocalBounds().reduced (12, 0);
67 
68         header.setBounds (bounds.removeFromTop (40));
69         list.setBounds (bounds.reduced (10, 4));
70     }
71 
parentSizeChanged()72     void parentSizeChanged() override
73     {
74         setSize (jmax (550, getParentWidth()), getParentHeight());
75     }
76 
getNumRows()77     int getNumRows() override
78     {
79         return item.getNumChildren();
80     }
81 
paintListBoxItem(int rowNumber,Graphics & g,int width,int height,bool)82     void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool /*rowIsSelected*/) override
83     {
84         g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
85                                                     : secondaryWidgetBackgroundColourId));
86         g.fillRect (0, 0, width, height - 1);
87     }
88 
refreshComponentForRow(int rowNumber,bool,Component * existingComponentToUpdate)89     Component* refreshComponentForRow (int rowNumber, bool /*isRowSelected*/, Component* existingComponentToUpdate) override
90     {
91         std::unique_ptr<Component> existing (existingComponentToUpdate);
92 
93         if (rowNumber < getNumRows())
94         {
95             auto child = item.getChild (rowNumber);
96 
97             if (existingComponentToUpdate == nullptr
98                  || dynamic_cast<FileOptionComponent*> (existing.get())->item != child)
99             {
100                 existing.reset();
101                 existing.reset (new FileOptionComponent (child, dynamic_cast<ListBoxHeader*> (list.getHeaderComponent())));
102             }
103         }
104 
105         return existing.release();
106     }
107 
getGroupPath()108     String getGroupPath() const    { return item.getFile().getFullPathName(); }
109 
110     //==============================================================================
valueTreePropertyChanged(ValueTree &,const Identifier &)111     void valueTreePropertyChanged (ValueTree&, const Identifier&) override    { itemChanged(); }
valueTreeChildAdded(ValueTree &,ValueTree &)112     void valueTreeChildAdded (ValueTree&, ValueTree&) override                { itemChanged(); }
valueTreeChildRemoved(ValueTree &,ValueTree &,int)113     void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override         { itemChanged(); }
valueTreeChildOrderChanged(ValueTree &,int,int)114     void valueTreeChildOrderChanged (ValueTree&, int, int) override           { itemChanged(); }
valueTreeParentChanged(ValueTree &)115     void valueTreeParentChanged (ValueTree&) override                         { itemChanged(); }
116 
117 private:
118     Project::Item item;
119     ListBox list;
120     ContentViewHeader header;
121 
itemChanged()122     void itemChanged()
123     {
124         list.updateContent();
125         repaint();
126     }
127 
128     //==============================================================================
129     class FileOptionComponent  : public Component
130     {
131     public:
FileOptionComponent(const Project::Item & fileItem,ListBoxHeader * listBoxHeader)132         FileOptionComponent (const Project::Item& fileItem, ListBoxHeader* listBoxHeader)
133             : item (fileItem),
134               header (listBoxHeader),
135               compilerFlagSchemeSelector (item)
136         {
137             if (item.isFile())
138             {
139                 auto isSourceFile = item.isSourceFile();
140 
141                 if (isSourceFile)
142                 {
143                     addAndMakeVisible (compileButton);
144                     compileButton.getToggleStateValue().referTo (item.getShouldCompileValue());
145                     compileButton.onStateChange = [this] { compileEnablementChanged(); };
146                 }
147 
148                 addAndMakeVisible (binaryResourceButton);
149                 binaryResourceButton.getToggleStateValue().referTo (item.getShouldAddToBinaryResourcesValue());
150 
151                 addAndMakeVisible (xcodeResourceButton);
152                 xcodeResourceButton.getToggleStateValue().referTo (item.getShouldAddToXcodeResourcesValue());
153 
154                 if (isSourceFile)
155                 {
156                     addChildComponent (skipPCHButton);
157                     skipPCHButton.getToggleStateValue().referTo (item.getShouldSkipPCHValue());
158 
159                     addChildComponent (compilerFlagSchemeSelector);
160 
161                     compileEnablementChanged();
162                 }
163             }
164         }
165 
paint(Graphics & g)166         void paint (Graphics& g) override
167         {
168             if (header != nullptr)
169             {
170                 auto textBounds = getLocalBounds().removeFromLeft (roundToInt (header->getProportionAtIndex (0) * (float) getWidth()));
171 
172                 auto iconBounds = textBounds.removeFromLeft (25);
173 
174                 if (item.isImageFile())
175                     iconBounds.reduce (5, 5);
176 
177                 item.getIcon().withColour (findColour (treeIconColourId)).draw (g, iconBounds.toFloat(), item.isIconCrossedOut());
178 
179                 g.setColour (findColour (widgetTextColourId));
180 
181                 g.drawText (item.getName(), textBounds, Justification::centredLeft);
182             }
183         }
184 
resized()185         void resized() override
186         {
187             if (header != nullptr)
188             {
189                 auto bounds = getLocalBounds();
190                 auto width = (float) getWidth();
191 
192                 bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (0) * width));
193 
194                 binaryResourceButton.setBounds       (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (1) * width)));
195                 xcodeResourceButton.setBounds        (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (2) * width)));
196                 compileButton.setBounds              (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (3) * width)));
197                 skipPCHButton.setBounds              (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (4) * width)));
198                 compilerFlagSchemeSelector.setBounds (bounds.removeFromLeft (roundToInt (header->getProportionAtIndex (5) * width)));
199             }
200         }
201 
202         Project::Item item;
203 
204     private:
205         //==============================================================================
206         class CompilerFlagSchemeSelector  : public Component,
207                                             private Value::Listener
208         {
209         public:
CompilerFlagSchemeSelector(Project::Item & it)210             CompilerFlagSchemeSelector (Project::Item& it)
211                 : item (it)
212             {
213                 schemeBox.setTextWhenNothingSelected ("None");
214                 updateCompilerFlagSchemeComboBox();
215                 schemeBox.onChange = [this] { handleComboBoxSelection(); };
216 
217                 addAndMakeVisible (schemeBox);
218                 addChildComponent (newSchemeLabel);
219 
220                 newSchemeLabel.setEditable (true);
221                 newSchemeLabel.setJustificationType (Justification::centredLeft);
222                 newSchemeLabel.onEditorHide = [this]
223                 {
224                     newSchemeLabel.setVisible (false);
225                     schemeBox.setVisible (true);
226 
227                     auto newScheme = newSchemeLabel.getText();
228 
229                     item.project.addCompilerFlagScheme (newScheme);
230 
231                     if (item.getCompilerFlagSchemeString().isEmpty())
232                         item.setCompilerFlagScheme (newScheme);
233 
234                     updateCompilerFlagSchemeComboBox();
235                 };
236 
237                 selectScheme (item.getCompilerFlagSchemeString());
238 
239                 projectCompilerFlagSchemesValue = item.project.getProjectValue (Ids::compilerFlagSchemes);
240                 projectCompilerFlagSchemesValue.addListener (this);
241 
242                 lookAndFeelChanged();
243             }
244 
resized()245             void resized() override
246             {
247                 auto b =  getLocalBounds();
248 
249                 schemeBox.setBounds (b);
250                 newSchemeLabel.setBounds (b);
251             }
252 
253         private:
valueChanged(Value &)254             void valueChanged (Value&) override   { updateCompilerFlagSchemeComboBox(); }
255 
lookAndFeelChanged()256             void lookAndFeelChanged() override
257             {
258                 schemeBox.setColour (ComboBox::outlineColourId, Colours::transparentBlack);
259                 schemeBox.setColour (ComboBox::textColourId,    findColour (defaultTextColourId));
260             }
261 
updateCompilerFlagSchemeComboBox()262             void updateCompilerFlagSchemeComboBox()
263             {
264                 auto itemScheme = item.getCompilerFlagSchemeString();
265                 auto allSchemes = item.project.getCompilerFlagSchemes();
266 
267                 if (itemScheme.isNotEmpty() && ! allSchemes.contains (itemScheme))
268                 {
269                     item.clearCurrentCompilerFlagScheme();
270                     itemScheme = {};
271                 }
272 
273                 schemeBox.clear();
274 
275                 schemeBox.addItemList (allSchemes, 1);
276                 schemeBox.addSeparator();
277                 schemeBox.addItem ("Add a new scheme...", -1);
278                 schemeBox.addItem ("Delete selected scheme", -2);
279                 schemeBox.addItem ("Clear", -3);
280 
281                 selectScheme (itemScheme);
282             }
283 
handleComboBoxSelection()284             void handleComboBoxSelection()
285             {
286                 auto selectedID = schemeBox.getSelectedId();
287 
288                 if (selectedID > 0)
289                 {
290                     item.setCompilerFlagScheme (schemeBox.getItemText (selectedID - 1));
291                 }
292                 else if (selectedID == -1)
293                 {
294                     newSchemeLabel.setText ("NewScheme", dontSendNotification);
295 
296                     schemeBox.setVisible (false);
297                     newSchemeLabel.setVisible (true);
298 
299                     newSchemeLabel.showEditor();
300 
301                     if (auto* ed = newSchemeLabel.getCurrentTextEditor())
302                         ed->setInputRestrictions (64, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_");
303                 }
304                 else if (selectedID == -2)
305                 {
306                     auto currentScheme = item.getCompilerFlagSchemeString();
307 
308                     if (currentScheme.isNotEmpty())
309                     {
310                         item.project.removeCompilerFlagScheme (currentScheme);
311                         item.clearCurrentCompilerFlagScheme();
312                     }
313 
314                     updateCompilerFlagSchemeComboBox();
315                 }
316                 else if (selectedID == -3)
317                 {
318                     schemeBox.setSelectedId (0);
319                     item.clearCurrentCompilerFlagScheme();
320                 }
321             }
322 
selectScheme(const String & schemeToSelect)323             void selectScheme (const String& schemeToSelect)
324             {
325                 if (schemeToSelect.isNotEmpty())
326                 {
327                     for (int i = 0; i < schemeBox.getNumItems(); ++i)
328                     {
329                         if (schemeBox.getItemText (i) == schemeToSelect)
330                         {
331                             schemeBox.setSelectedItemIndex (i);
332                             return;
333                         }
334                     }
335                 }
336 
337                 schemeBox.setSelectedId (0);
338             }
339 
340             Project::Item& item;
341             Value projectCompilerFlagSchemesValue;
342 
343             ComboBox schemeBox;
344             Label newSchemeLabel;
345         };
346 
compileEnablementChanged()347         void compileEnablementChanged()
348         {
349             auto shouldBeCompiled = compileButton.getToggleState();
350 
351             skipPCHButton.setVisible (shouldBeCompiled);
352             compilerFlagSchemeSelector.setVisible (shouldBeCompiled);
353         }
354 
355         //==============================================================================
356         ListBoxHeader* header;
357 
358         ToggleButton compileButton, binaryResourceButton, xcodeResourceButton, skipPCHButton;
359         CompilerFlagSchemeSelector compilerFlagSchemeSelector;
360     };
361 
362     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileGroupInformationComponent)
363 };
364