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.

468 lines
15KB

  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. { "Code Background", CodeEditorComponent::backgroundColourId, true },
  31. { "Line Number Bkgd", CodeEditorComponent::lineNumberBackgroundId, false },
  32. { "Line Numbers", CodeEditorComponent::lineNumberTextId, false },
  33. { "Plain Text", CodeEditorComponent::defaultTextColourId, false },
  34. { "Selected Text Bkgd", CodeEditorComponent::highlightColourId, false },
  35. { "Caret", CaretComponent::caretColourId, false },
  36. { "Main Window Bkgd", mainBackgroundColourId, true },
  37. { "Project Panel Bkgd", projectPanelBackgroundColourId, true },
  38. { "Treeview Highlight", treeviewHighlightColourId, false }
  39. };
  40. }
  41. //==============================================================================
  42. AppearanceSettings::AppearanceSettings()
  43. : settings ("COLOUR_SCHEME")
  44. {
  45. IntrojucerLookAndFeel lf;
  46. for (int i = 0; i < sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0]); ++i)
  47. getColourValue (AppearanceColours::colours[i].name) = lf.findColour (AppearanceColours::colours[i].colourID).toString();
  48. CodeDocument doc;
  49. CPlusPlusCodeTokeniser tokeniser;
  50. CodeEditorComponent editor (doc, &tokeniser);
  51. const CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  52. for (int i = cs.types.size(); --i >= 0;)
  53. {
  54. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  55. getColourValue (t.name) = t.colour.toString();
  56. }
  57. Font f (editor.getFont());
  58. f.setTypefaceName (f.getTypeface()->getName());
  59. getCodeFontValue() = f.toString();
  60. settings.addListener (this);
  61. }
  62. File AppearanceSettings::getSchemesFolder()
  63. {
  64. File f (getAppProperties().getFile().getSiblingFile ("Colour Schemes"));
  65. f.createDirectory();
  66. return f;
  67. }
  68. void AppearanceSettings::refreshPresetSchemeList()
  69. {
  70. const File defaultSchemeFile (getSchemesFolder().getChildFile ("Default").withFileExtension (getSchemeFileSuffix()));
  71. if (! defaultSchemeFile.exists())
  72. AppearanceSettings().writeToFile (defaultSchemeFile);
  73. Array<File> newSchemes;
  74. getSchemesFolder().findChildFiles (newSchemes, File::findFiles, false, String ("*") + getSchemeFileSuffix());
  75. if (newSchemes != presetSchemeFiles)
  76. {
  77. presetSchemeFiles.swapWithArray (newSchemes);
  78. commandManager->commandStatusChanged();
  79. }
  80. }
  81. StringArray AppearanceSettings::getPresetSchemes()
  82. {
  83. StringArray s;
  84. for (int i = 0; i < presetSchemeFiles.size(); ++i)
  85. s.add (presetSchemeFiles.getReference(i).getFileNameWithoutExtension());
  86. return s;
  87. }
  88. void AppearanceSettings::selectPresetScheme (int index)
  89. {
  90. readFromFile (presetSchemeFiles [index]);
  91. }
  92. bool AppearanceSettings::readFromXML (const XmlElement& xml)
  93. {
  94. if (xml.hasTagName (settings.getType().toString()))
  95. {
  96. ValueTree newSettings (ValueTree::fromXml (xml));
  97. for (int i = settings.getNumChildren(); --i >= 0;)
  98. {
  99. const ValueTree c (settings.getChild (i));
  100. if (! newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)).isValid())
  101. newSettings.addChild (c.createCopy(), 0, nullptr);
  102. }
  103. settings = newSettings;
  104. return true;
  105. }
  106. return false;
  107. }
  108. bool AppearanceSettings::readFromFile (const File& file)
  109. {
  110. const ScopedPointer<XmlElement> xml (XmlDocument::parse (file));
  111. return xml != nullptr && readFromXML (*xml);
  112. }
  113. bool AppearanceSettings::writeToFile (const File& file) const
  114. {
  115. const ScopedPointer<XmlElement> xml (settings.createXml());
  116. return xml != nullptr && xml->writeToFile (file, String::empty);
  117. }
  118. StringArray AppearanceSettings::getColourNames() const
  119. {
  120. StringArray s;
  121. for (int i = 0; i < settings.getNumChildren(); ++i)
  122. {
  123. const ValueTree c (settings.getChild(i));
  124. if (c.hasType ("COLOUR"))
  125. s.add (c [Ids::name]);
  126. }
  127. return s;
  128. }
  129. void AppearanceSettings::updateColourScheme()
  130. {
  131. applyToLookAndFeel (LookAndFeel::getDefaultLookAndFeel());
  132. JucerApplication::getApp().mainWindowList.sendLookAndFeelChange();
  133. }
  134. void AppearanceSettings::applyToLookAndFeel (LookAndFeel& lf) const
  135. {
  136. for (int i = 0; i < sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0]); ++i)
  137. {
  138. Colour col;
  139. if (getColour (AppearanceColours::colours[i].name, col))
  140. {
  141. if (AppearanceColours::colours[i].mustBeOpaque)
  142. col = Colours::white.overlaidWith (col);
  143. lf.setColour (AppearanceColours::colours[i].colourID, col);
  144. }
  145. }
  146. }
  147. void AppearanceSettings::applyToCodeEditor (CodeEditorComponent& editor) const
  148. {
  149. CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
  150. for (int i = cs.types.size(); --i >= 0;)
  151. {
  152. CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
  153. getColour (t.name, t.colour);
  154. }
  155. editor.setColourScheme (cs);
  156. editor.setFont (getCodeFont());
  157. }
  158. Font AppearanceSettings::getCodeFont() const
  159. {
  160. const String fontString (settings [Ids::font].toString());
  161. if (fontString.isEmpty())
  162. {
  163. #if JUCE_MAC
  164. Font font (13.0f);
  165. font.setTypefaceName ("Menlo");
  166. #else
  167. Font font (10.0f);
  168. font.setTypefaceName (Font::getDefaultMonospacedFontName());
  169. #endif
  170. return font;
  171. }
  172. return Font::fromString (fontString);
  173. }
  174. Value AppearanceSettings::getCodeFontValue()
  175. {
  176. return settings.getPropertyAsValue (Ids::font, nullptr);
  177. }
  178. Value AppearanceSettings::getColourValue (const String& colourName)
  179. {
  180. ValueTree c (settings.getChildWithProperty (Ids::name, colourName));
  181. if (! c.isValid())
  182. {
  183. c = ValueTree ("COLOUR");
  184. c.setProperty (Ids::name, colourName, nullptr);
  185. settings.addChild (c, -1, nullptr);
  186. }
  187. return c.getPropertyAsValue (Ids::colour, nullptr);
  188. }
  189. bool AppearanceSettings::getColour (const String& name, Colour& result) const
  190. {
  191. const ValueTree colour (settings.getChildWithProperty (Ids::name, name));
  192. if (colour.isValid())
  193. {
  194. result = Colour::fromString (colour [Ids::colour].toString());
  195. return true;
  196. }
  197. return false;
  198. }
  199. //==============================================================================
  200. struct AppearanceEditor
  201. {
  202. class Window : public DialogWindow
  203. {
  204. public:
  205. Window() : DialogWindow ("Appearance Settings", Colours::black, true, true)
  206. {
  207. setUsingNativeTitleBar (true);
  208. setContentOwned (new EditorPanel(), false);
  209. setResizable (true, true);
  210. const int width = 350;
  211. setResizeLimits (width, 200, width, 1000);
  212. String windowState (getAppProperties().getValue (getWindowPosName()));
  213. if (windowState.isNotEmpty())
  214. restoreWindowStateFromString (windowState);
  215. else
  216. centreAroundComponent (Component::getCurrentlyFocusedComponent(), width, 500);
  217. setVisible (true);
  218. }
  219. ~Window()
  220. {
  221. getAppProperties().setValue (getWindowPosName(), getWindowStateAsString());
  222. }
  223. void closeButtonPressed()
  224. {
  225. JucerApplication::getApp().appearanceEditorWindow = nullptr;
  226. }
  227. private:
  228. static const char* getWindowPosName() { return "colourSchemeEditorPos"; }
  229. JUCE_DECLARE_NON_COPYABLE (Window);
  230. };
  231. //==============================================================================
  232. class EditorPanel : public Component,
  233. private Button::Listener
  234. {
  235. public:
  236. EditorPanel()
  237. : loadButton ("Load Scheme..."),
  238. saveButton ("Save Scheme...")
  239. {
  240. setOpaque (true);
  241. rebuildProperties();
  242. addAndMakeVisible (&panel);
  243. loadButton.setColour (TextButton::buttonColourId, Colours::grey);
  244. saveButton.setColour (TextButton::buttonColourId, Colours::grey);
  245. addAndMakeVisible (&loadButton);
  246. addAndMakeVisible (&saveButton);
  247. loadButton.addListener (this);
  248. saveButton.addListener (this);
  249. }
  250. void rebuildProperties()
  251. {
  252. AppearanceSettings& scheme = getAppSettings().appearance;
  253. Array <PropertyComponent*> props;
  254. Value fontValue (scheme.getCodeFontValue());
  255. props.add (FontNameValueSource::createProperty ("Code Editor Font", fontValue));
  256. props.add (FontSizeValueSource::createProperty ("Font Size", fontValue));
  257. const StringArray colourNames (scheme.getColourNames());
  258. for (int i = 0; i < colourNames.size(); ++i)
  259. props.add (new ColourPropertyComponent (nullptr, colourNames[i],
  260. scheme.getColourValue (colourNames[i]),
  261. Colours::white, false));
  262. panel.clear();
  263. panel.addProperties (props);
  264. }
  265. void paint (Graphics& g)
  266. {
  267. g.fillAll (Colours::black);
  268. }
  269. void resized()
  270. {
  271. Rectangle<int> r (getLocalBounds());
  272. panel.setBounds (r.removeFromTop (getHeight() - 26).reduced (4, 3));
  273. loadButton.setBounds (r.removeFromLeft (getWidth() / 2).reduced (10, 3));
  274. saveButton.setBounds (r.reduced (10, 3));
  275. }
  276. private:
  277. PropertyPanel panel;
  278. TextButton loadButton, saveButton;
  279. void buttonClicked (Button* b)
  280. {
  281. if (b == &loadButton)
  282. loadScheme();
  283. else
  284. saveScheme();
  285. }
  286. void saveScheme()
  287. {
  288. FileChooser fc ("Select a file in which to save this colour-scheme...",
  289. getAppSettings().appearance.getSchemesFolder().getNonexistentChildFile ("Scheme", ".editorscheme"),
  290. "*.editorscheme");
  291. if (fc.browseForFileToSave (true))
  292. {
  293. File file (fc.getResult().withFileExtension (".editorscheme"));
  294. getAppSettings().appearance.writeToFile (file);
  295. getAppSettings().appearance.refreshPresetSchemeList();
  296. }
  297. }
  298. void loadScheme()
  299. {
  300. FileChooser fc ("Please select a colour-scheme file to load...",
  301. getAppSettings().appearance.getSchemesFolder(),
  302. "*.editorscheme");
  303. if (fc.browseForFileToOpen())
  304. {
  305. if (getAppSettings().appearance.readFromFile (fc.getResult()))
  306. rebuildProperties();
  307. }
  308. }
  309. JUCE_DECLARE_NON_COPYABLE (EditorPanel);
  310. };
  311. //==============================================================================
  312. class FontNameValueSource : public ValueSourceFilter
  313. {
  314. public:
  315. FontNameValueSource (const Value& source) : ValueSourceFilter (source) {}
  316. var getValue() const
  317. {
  318. return Font::fromString (sourceValue.toString()).getTypefaceName();
  319. }
  320. void setValue (const var& newValue)
  321. {
  322. Font font (Font::fromString (sourceValue.toString()));
  323. font.setTypefaceName (newValue.toString());
  324. sourceValue = font.toString();
  325. }
  326. static ChoicePropertyComponent* createProperty (const String& title, const Value& value)
  327. {
  328. const StringArray& fontNames = getAppSettings().getFontNames();
  329. Array<var> values;
  330. for (int i = 0; i < fontNames.size(); ++i)
  331. values.add (fontNames[i]);
  332. return new ChoicePropertyComponent (Value (new FontNameValueSource (value)),
  333. title, fontNames, values);
  334. }
  335. };
  336. //==============================================================================
  337. class FontSizeValueSource : public ValueSourceFilter
  338. {
  339. public:
  340. FontSizeValueSource (const Value& source) : ValueSourceFilter (source) {}
  341. var getValue() const
  342. {
  343. return Font::fromString (sourceValue.toString()).getHeight();
  344. }
  345. void setValue (const var& newValue)
  346. {
  347. sourceValue = Font::fromString (sourceValue.toString()).withHeight (newValue).toString();
  348. }
  349. static PropertyComponent* createProperty (const String& title, const Value& value)
  350. {
  351. return new SliderPropertyComponent (Value (new FontSizeValueSource (value)),
  352. title, 5.0, 40.0, 0.1, 0.5);
  353. }
  354. };
  355. };
  356. Component* AppearanceSettings::createEditorWindow()
  357. {
  358. return new AppearanceEditor::Window();
  359. }
  360. //==============================================================================
  361. IntrojucerLookAndFeel::IntrojucerLookAndFeel()
  362. {
  363. setColour (mainBackgroundColourId, Colour::greyLevel (0.8f));
  364. setColour (projectPanelBackgroundColourId, Colour::greyLevel (0.93f));
  365. setColour (treeviewHighlightColourId, Colour (0x401111ee));
  366. }
  367. Rectangle<int> IntrojucerLookAndFeel::getPropertyComponentContentPosition (PropertyComponent& component)
  368. {
  369. if (component.findParentComponentOfClass<AppearanceEditor::EditorPanel>() != nullptr)
  370. return component.getLocalBounds().reduced (1, 1).removeFromRight (component.getWidth() / 2);
  371. return LookAndFeel::getPropertyComponentContentPosition (component);
  372. }