The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

588 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_Application.h"
  19. #include "jucer_AppearanceSettings.h"
  20. namespace AppearanceColours
  21. {
  22. struct ColourInfo
  23. {
  24. const char* name;
  25. uint32 colourID;
  26. bool mustBeOpaque;
  27. };
  28. static const ColourInfo colours[] =
  29. {
  30. { "Main Window Bkgd", mainBackgroundColourId, true },
  31. { "Treeview Highlight", treeviewHighlightColourId, false },
  32. { "Code Background", CodeEditorComponent::backgroundColourId, true },
  33. { "Line Number Bkgd", CodeEditorComponent::lineNumberBackgroundId, false },
  34. { "Line Numbers", CodeEditorComponent::lineNumberTextId, false },
  35. { "Plain Text", CodeEditorComponent::defaultTextColourId, false },
  36. { "Selected Text Bkgd", CodeEditorComponent::highlightColourId, false },
  37. { "Caret", CaretComponent::caretColourId, false }
  38. };
  39. }
  40. //==============================================================================
  41. AppearanceSettings::AppearanceSettings()
  42. : settings ("COLOUR_SCHEME")
  43. {
  44. IntrojucerLookAndFeel lf;
  45. for (int i = 0; i < sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0]); ++i)
  46. getColourValue (AppearanceColours::colours[i].name) = lf.findColour (AppearanceColours::colours[i].colourID).toString();
  47. CodeDocument doc;
  48. CPlusPlusCodeTokeniser tokeniser;
  49. CodeEditorComponent editor (doc, &tokeniser);
  50. const CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  51. for (int i = cs.types.size(); --i >= 0;)
  52. {
  53. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  54. getColourValue (t.name) = t.colour.toString();
  55. }
  56. getCodeFontValue() = getDefaultCodeFont().toString();
  57. settings.addListener (this);
  58. }
  59. File AppearanceSettings::getSchemesFolder()
  60. {
  61. File f (getAppProperties().getFile().getSiblingFile ("Schemes"));
  62. f.createDirectory();
  63. return f;
  64. }
  65. void AppearanceSettings::writeDefaultSchemeFile (const String& xmlString, const String& name)
  66. {
  67. const File file (getSchemesFolder().getChildFile (name).withFileExtension (getSchemeFileSuffix()));
  68. if (! file.exists())
  69. {
  70. AppearanceSettings settings;
  71. ScopedPointer<XmlElement> xml (XmlDocument::parse (xmlString));
  72. if (xml != nullptr)
  73. settings.readFromXML (*xml);
  74. settings.writeToFile (file);
  75. }
  76. }
  77. void AppearanceSettings::refreshPresetSchemeList()
  78. {
  79. writeDefaultSchemeFile (String::empty, "Default (Light)");
  80. writeDefaultSchemeFile (BinaryData::dark_scheme_xml, "Default (Dark)");
  81. Array<File> newSchemes;
  82. getSchemesFolder().findChildFiles (newSchemes, File::findFiles, false, String ("*") + getSchemeFileSuffix());
  83. if (newSchemes != presetSchemeFiles)
  84. {
  85. presetSchemeFiles.swapWithArray (newSchemes);
  86. commandManager->commandStatusChanged();
  87. }
  88. }
  89. StringArray AppearanceSettings::getPresetSchemes()
  90. {
  91. StringArray s;
  92. for (int i = 0; i < presetSchemeFiles.size(); ++i)
  93. s.add (presetSchemeFiles.getReference(i).getFileNameWithoutExtension());
  94. return s;
  95. }
  96. void AppearanceSettings::selectPresetScheme (int index)
  97. {
  98. readFromFile (presetSchemeFiles [index]);
  99. }
  100. bool AppearanceSettings::readFromXML (const XmlElement& xml)
  101. {
  102. if (xml.hasTagName (settings.getType().toString()))
  103. {
  104. const ValueTree newSettings (ValueTree::fromXml (xml));
  105. // we'll manually copy across the new properties to the existing tree so that
  106. // any open editors will be kept up to date..
  107. settings.copyPropertiesFrom (newSettings, nullptr);
  108. for (int i = settings.getNumChildren(); --i >= 0;)
  109. {
  110. ValueTree c (settings.getChild (i));
  111. const ValueTree newValue (newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)));
  112. if (newValue.isValid())
  113. c.copyPropertiesFrom (newValue, nullptr);
  114. }
  115. return true;
  116. }
  117. return false;
  118. }
  119. bool AppearanceSettings::readFromFile (const File& file)
  120. {
  121. const ScopedPointer<XmlElement> xml (XmlDocument::parse (file));
  122. return xml != nullptr && readFromXML (*xml);
  123. }
  124. bool AppearanceSettings::writeToFile (const File& file) const
  125. {
  126. const ScopedPointer<XmlElement> xml (settings.createXml());
  127. return xml != nullptr && xml->writeToFile (file, String::empty);
  128. }
  129. Font AppearanceSettings::getDefaultCodeFont()
  130. {
  131. return Font (Font::getDefaultMonospacedFontName(), Font::getDefaultStyle(), 13.0f);
  132. }
  133. StringArray AppearanceSettings::getColourNames() const
  134. {
  135. StringArray s;
  136. for (int i = 0; i < settings.getNumChildren(); ++i)
  137. {
  138. const ValueTree c (settings.getChild(i));
  139. if (c.hasType ("COLOUR"))
  140. s.add (c [Ids::name]);
  141. }
  142. return s;
  143. }
  144. void AppearanceSettings::updateColourScheme()
  145. {
  146. applyToLookAndFeel (LookAndFeel::getDefaultLookAndFeel());
  147. JucerApplication::getApp().mainWindowList.sendLookAndFeelChange();
  148. }
  149. void AppearanceSettings::applyToLookAndFeel (LookAndFeel& lf) const
  150. {
  151. for (int i = 0; i < sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0]); ++i)
  152. {
  153. Colour col;
  154. if (getColour (AppearanceColours::colours[i].name, col))
  155. {
  156. if (AppearanceColours::colours[i].mustBeOpaque)
  157. col = Colours::white.overlaidWith (col);
  158. lf.setColour (AppearanceColours::colours[i].colourID, col);
  159. }
  160. }
  161. lf.setColour (ScrollBar::thumbColourId,
  162. getScrollbarColourForBackground (lf.findColour (mainBackgroundColourId)));
  163. }
  164. void AppearanceSettings::applyToCodeEditor (CodeEditorComponent& editor) const
  165. {
  166. CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  167. for (int i = cs.types.size(); --i >= 0;)
  168. {
  169. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  170. getColour (t.name, t.colour);
  171. }
  172. editor.setColourScheme (cs);
  173. editor.setFont (getCodeFont());
  174. editor.setColour (ScrollBar::thumbColourId,
  175. getScrollbarColourForBackground (editor.findColour (CodeEditorComponent::backgroundColourId)));
  176. }
  177. Font AppearanceSettings::getCodeFont() const
  178. {
  179. const String fontString (settings [Ids::font].toString());
  180. if (fontString.isEmpty())
  181. return Font (Font::getDefaultMonospacedFontName(), 13.0f);
  182. return Font::fromString (fontString);
  183. }
  184. Value AppearanceSettings::getCodeFontValue()
  185. {
  186. return settings.getPropertyAsValue (Ids::font, nullptr);
  187. }
  188. Value AppearanceSettings::getColourValue (const String& colourName)
  189. {
  190. ValueTree c (settings.getChildWithProperty (Ids::name, colourName));
  191. if (! c.isValid())
  192. {
  193. c = ValueTree ("COLOUR");
  194. c.setProperty (Ids::name, colourName, nullptr);
  195. settings.addChild (c, -1, nullptr);
  196. }
  197. return c.getPropertyAsValue (Ids::colour, nullptr);
  198. }
  199. bool AppearanceSettings::getColour (const String& name, Colour& result) const
  200. {
  201. const ValueTree colour (settings.getChildWithProperty (Ids::name, name));
  202. if (colour.isValid())
  203. {
  204. result = Colour::fromString (colour [Ids::colour].toString());
  205. return true;
  206. }
  207. return false;
  208. }
  209. Colour AppearanceSettings::getScrollbarColourForBackground (const Colour& background)
  210. {
  211. return background.contrasting().withAlpha (0.13f);
  212. }
  213. //==============================================================================
  214. struct AppearanceEditor
  215. {
  216. class Window : public DialogWindow
  217. {
  218. public:
  219. Window() : DialogWindow ("Appearance Settings", Colours::darkgrey, true, true)
  220. {
  221. setUsingNativeTitleBar (true);
  222. setContentOwned (new EditorPanel(), false);
  223. setResizable (true, true);
  224. const int width = 350;
  225. setResizeLimits (width, 200, width, 1000);
  226. String windowState (getAppProperties().getValue (getWindowPosName()));
  227. if (windowState.isNotEmpty())
  228. restoreWindowStateFromString (windowState);
  229. else
  230. centreAroundComponent (Component::getCurrentlyFocusedComponent(), width, 500);
  231. setVisible (true);
  232. }
  233. ~Window()
  234. {
  235. getAppProperties().setValue (getWindowPosName(), getWindowStateAsString());
  236. }
  237. void closeButtonPressed()
  238. {
  239. JucerApplication::getApp().appearanceEditorWindow = nullptr;
  240. }
  241. private:
  242. static const char* getWindowPosName() { return "colourSchemeEditorPos"; }
  243. JUCE_DECLARE_NON_COPYABLE (Window);
  244. };
  245. //==============================================================================
  246. class EditorPanel : public Component,
  247. private Button::Listener
  248. {
  249. public:
  250. EditorPanel()
  251. : loadButton ("Load Scheme..."),
  252. saveButton ("Save Scheme...")
  253. {
  254. rebuildProperties();
  255. addAndMakeVisible (&panel);
  256. loadButton.setColour (TextButton::buttonColourId, Colours::darkgrey.withAlpha (0.5f));
  257. saveButton.setColour (TextButton::buttonColourId, Colours::darkgrey.withAlpha (0.5f));
  258. loadButton.setColour (TextButton::textColourOffId, Colours::white);
  259. saveButton.setColour (TextButton::textColourOffId, Colours::white);
  260. addAndMakeVisible (&loadButton);
  261. addAndMakeVisible (&saveButton);
  262. loadButton.addListener (this);
  263. saveButton.addListener (this);
  264. }
  265. void rebuildProperties()
  266. {
  267. AppearanceSettings& scheme = getAppSettings().appearance;
  268. Array <PropertyComponent*> props;
  269. Value fontValue (scheme.getCodeFontValue());
  270. props.add (FontNameValueSource::createProperty ("Code Editor Font", fontValue));
  271. props.add (FontSizeValueSource::createProperty ("Font Size", fontValue));
  272. const StringArray colourNames (scheme.getColourNames());
  273. for (int i = 0; i < colourNames.size(); ++i)
  274. props.add (new ColourPropertyComponent (nullptr, colourNames[i],
  275. scheme.getColourValue (colourNames[i]),
  276. Colours::white, false));
  277. panel.clear();
  278. panel.addProperties (props);
  279. }
  280. void resized()
  281. {
  282. Rectangle<int> r (getLocalBounds());
  283. panel.setBounds (r.removeFromTop (getHeight() - 28).reduced (4, 2));
  284. loadButton.setBounds (r.removeFromLeft (getWidth() / 2).reduced (10, 4));
  285. saveButton.setBounds (r.reduced (10, 3));
  286. }
  287. private:
  288. PropertyPanel panel;
  289. TextButton loadButton, saveButton;
  290. void buttonClicked (Button* b)
  291. {
  292. if (b == &loadButton)
  293. loadScheme();
  294. else
  295. saveScheme();
  296. }
  297. void saveScheme()
  298. {
  299. FileChooser fc ("Select a file in which to save this colour-scheme...",
  300. getAppSettings().appearance.getSchemesFolder()
  301. .getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()),
  302. AppearanceSettings::getSchemeFileWildCard());
  303. if (fc.browseForFileToSave (true))
  304. {
  305. File file (fc.getResult().withFileExtension (AppearanceSettings::getSchemeFileSuffix()));
  306. getAppSettings().appearance.writeToFile (file);
  307. getAppSettings().appearance.refreshPresetSchemeList();
  308. }
  309. }
  310. void loadScheme()
  311. {
  312. FileChooser fc ("Please select a colour-scheme file to load...",
  313. getAppSettings().appearance.getSchemesFolder(),
  314. AppearanceSettings::getSchemeFileWildCard());
  315. if (fc.browseForFileToOpen())
  316. {
  317. if (getAppSettings().appearance.readFromFile (fc.getResult()))
  318. rebuildProperties();
  319. }
  320. }
  321. JUCE_DECLARE_NON_COPYABLE (EditorPanel);
  322. };
  323. //==============================================================================
  324. class FontNameValueSource : public ValueSourceFilter
  325. {
  326. public:
  327. FontNameValueSource (const Value& source) : ValueSourceFilter (source) {}
  328. var getValue() const
  329. {
  330. return Font::fromString (sourceValue.toString()).getTypefaceName();
  331. }
  332. void setValue (const var& newValue)
  333. {
  334. Font font (Font::fromString (sourceValue.toString()));
  335. font.setTypefaceName (newValue.toString());
  336. sourceValue = font.toString();
  337. }
  338. static ChoicePropertyComponent* createProperty (const String& title, const Value& value)
  339. {
  340. StringArray fontNames = getAppSettings().getFontNames();
  341. Array<var> values;
  342. values.add (Font::getDefaultMonospacedFontName());
  343. values.add (var());
  344. for (int i = 0; i < fontNames.size(); ++i)
  345. values.add (fontNames[i]);
  346. StringArray names;
  347. names.add ("<Default Monospaced>");
  348. names.add (String::empty);
  349. names.addArray (getAppSettings().getFontNames());
  350. return new ChoicePropertyComponent (Value (new FontNameValueSource (value)),
  351. title, names, values);
  352. }
  353. };
  354. //==============================================================================
  355. class FontSizeValueSource : public ValueSourceFilter
  356. {
  357. public:
  358. FontSizeValueSource (const Value& source) : ValueSourceFilter (source) {}
  359. var getValue() const
  360. {
  361. return Font::fromString (sourceValue.toString()).getHeight();
  362. }
  363. void setValue (const var& newValue)
  364. {
  365. sourceValue = Font::fromString (sourceValue.toString()).withHeight (newValue).toString();
  366. }
  367. static PropertyComponent* createProperty (const String& title, const Value& value)
  368. {
  369. return new SliderPropertyComponent (Value (new FontSizeValueSource (value)),
  370. title, 5.0, 40.0, 0.1, 0.5);
  371. }
  372. };
  373. };
  374. Component* AppearanceSettings::createEditorWindow()
  375. {
  376. return new AppearanceEditor::Window();
  377. }
  378. //==============================================================================
  379. IntrojucerLookAndFeel::IntrojucerLookAndFeel()
  380. {
  381. setColour (mainBackgroundColourId, Colour::greyLevel (0.8f));
  382. setColour (treeviewHighlightColourId, Colour (0x401111ee));
  383. }
  384. Rectangle<int> IntrojucerLookAndFeel::getPropertyComponentContentPosition (PropertyComponent& component)
  385. {
  386. if (component.findParentComponentOfClass<AppearanceEditor::EditorPanel>() != nullptr)
  387. return component.getLocalBounds().reduced (1, 1).removeFromRight (component.getWidth() / 2);
  388. return LookAndFeel::getPropertyComponentContentPosition (component);
  389. }
  390. int IntrojucerLookAndFeel::getTabButtonOverlap (int tabDepth) { return -1; }
  391. int IntrojucerLookAndFeel::getTabButtonSpaceAroundImage() { return 1; }
  392. int IntrojucerLookAndFeel::getTabButtonBestWidth (TabBarButton& button, int tabDepth) { return 120; }
  393. void IntrojucerLookAndFeel::createTabTextLayout (const TabBarButton& button, const Rectangle<int>& textArea, GlyphArrangement& textLayout)
  394. {
  395. Font font (textArea.getHeight() * 0.5f);
  396. font.setUnderline (button.hasKeyboardFocus (false));
  397. textLayout.addFittedText (font, button.getButtonText().trim(),
  398. (float) textArea.getX(), (float) textArea.getY(), (float) textArea.getWidth(), (float) textArea.getHeight(),
  399. Justification::centred, 1);
  400. }
  401. Colour IntrojucerLookAndFeel::getTabBackgroundColour (TabBarButton& button)
  402. {
  403. const Colour normalBkg (button.getTabBackgroundColour());
  404. Colour bkg (normalBkg.contrasting (0.15f));
  405. if (button.isFrontTab())
  406. bkg = bkg.overlaidWith (Colours::yellow.withAlpha (0.5f));
  407. return bkg;
  408. }
  409. void IntrojucerLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown)
  410. {
  411. const Rectangle<int> activeArea (button.getActiveArea());
  412. const Colour bkg (getTabBackgroundColour (button));
  413. g.setGradientFill (ColourGradient (bkg.brighter (0.1f), 0, (float) activeArea.getY(),
  414. bkg.darker (0.1f), 0, (float) activeArea.getBottom(), false));
  415. g.fillRect (activeArea);
  416. g.setColour (button.getTabBackgroundColour().darker (0.3f));
  417. g.drawRect (activeArea);
  418. GlyphArrangement textLayout;
  419. createTabTextLayout (button, button.getTextArea(), textLayout);
  420. const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f;
  421. g.setColour (bkg.contrasting().withMultipliedAlpha (alpha));
  422. textLayout.draw (g);
  423. }
  424. Rectangle<int> IntrojucerLookAndFeel::getTabButtonExtraComponentBounds (const TabBarButton& button, Rectangle<int>& textArea, Component& comp)
  425. {
  426. GlyphArrangement textLayout;
  427. createTabTextLayout (button, textArea, textLayout);
  428. const int textWidth = (int) textLayout.getBoundingBox (0, -1, false).getWidth();
  429. const int extraSpace = jmax (0, textArea.getWidth() - (textWidth + comp.getWidth())) / 2;
  430. textArea.removeFromRight (extraSpace);
  431. textArea.removeFromLeft (extraSpace);
  432. return textArea.removeFromRight (comp.getWidth());
  433. }
  434. void IntrojucerLookAndFeel::drawStretchableLayoutResizerBar (Graphics& g, int /*w*/, int /*h*/, bool /*isVerticalBar*/, bool isMouseOver, bool isMouseDragging)
  435. {
  436. if (isMouseOver || isMouseDragging)
  437. g.fillAll (Colours::yellow.withAlpha (0.4f));
  438. }
  439. void IntrojucerLookAndFeel::drawScrollbar (Graphics& g, ScrollBar& scrollbar, int x, int y, int width, int height,
  440. bool isScrollbarVertical, int thumbStartPosition, int thumbSize,
  441. bool isMouseOver, bool isMouseDown)
  442. {
  443. Path thumbPath;
  444. if (thumbSize > 0)
  445. {
  446. const float thumbIndent = (isScrollbarVertical ? width : height) * 0.25f;
  447. const float thumbIndentx2 = thumbIndent * 2.0f;
  448. if (isScrollbarVertical)
  449. thumbPath.addRoundedRectangle (x + thumbIndent, thumbStartPosition + thumbIndent,
  450. width - thumbIndentx2, thumbSize - thumbIndentx2, (width - thumbIndentx2) * 0.5f);
  451. else
  452. thumbPath.addRoundedRectangle (thumbStartPosition + thumbIndent, y + thumbIndent,
  453. thumbSize - thumbIndentx2, height - thumbIndentx2, (height - thumbIndentx2) * 0.5f);
  454. }
  455. Colour thumbCol (scrollbar.findColour (ScrollBar::thumbColourId, true));
  456. if (isMouseOver || isMouseDown)
  457. thumbCol = thumbCol.withMultipliedAlpha (2.0f);
  458. g.setColour (thumbCol);
  459. g.fillPath (thumbPath);
  460. g.setColour (thumbCol.contrasting ((isMouseOver || isMouseDown) ? 0.2f : 0.1f));
  461. g.strokePath (thumbPath, PathStrokeType (1.0f));
  462. }