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.

577 lines
20KB

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