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.

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