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.

321 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../jucedemo_headers.h"
  18. //==============================================================================
  19. /**
  20. This class shows how to implement a TableListBoxModel to show in a TableListBox.
  21. */
  22. class TableDemoComponent : public Component,
  23. public TableListBoxModel
  24. {
  25. public:
  26. //==============================================================================
  27. TableDemoComponent()
  28. : font (14.0f)
  29. {
  30. // Load some data from an embedded XML file..
  31. loadData();
  32. // Create our table component and add it to this component..
  33. addAndMakeVisible (&table);
  34. table.setModel (this);
  35. // give it a border
  36. table.setColour (ListBox::outlineColourId, Colours::grey);
  37. table.setOutlineThickness (1);
  38. // Add some columns to the table header, based on the column list in our database..
  39. forEachXmlChildElement (*columnList, columnXml)
  40. {
  41. table.getHeader().addColumn (columnXml->getStringAttribute ("name"),
  42. columnXml->getIntAttribute ("columnId"),
  43. columnXml->getIntAttribute ("width"),
  44. 50, 400,
  45. TableHeaderComponent::defaultFlags);
  46. }
  47. // we could now change some initial settings..
  48. table.getHeader().setSortColumnId (1, true); // sort forwards by the ID column
  49. table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it
  50. // un-comment this line to have a go of stretch-to-fit mode
  51. // table.getHeader().setStretchToFitActive (true);
  52. table.setMultipleSelectionEnabled (true);
  53. }
  54. ~TableDemoComponent()
  55. {
  56. }
  57. //==============================================================================
  58. // This is overloaded from TableListBoxModel, and must return the total number of rows in our table
  59. int getNumRows()
  60. {
  61. return numRows;
  62. }
  63. // This is overloaded from TableListBoxModel, and should fill in the background of the whole row
  64. void paintRowBackground (Graphics& g, int /*rowNumber*/, int /*width*/, int /*height*/, bool rowIsSelected)
  65. {
  66. if (rowIsSelected)
  67. g.fillAll (Colours::lightblue);
  68. }
  69. // This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
  70. // components.
  71. void paintCell (Graphics& g,
  72. int rowNumber,
  73. int columnId,
  74. int width, int height,
  75. bool /*rowIsSelected*/)
  76. {
  77. g.setColour (Colours::black);
  78. g.setFont (font);
  79. const XmlElement* rowElement = dataList->getChildElement (rowNumber);
  80. if (rowElement != 0)
  81. {
  82. const String text (rowElement->getStringAttribute (getAttributeNameForColumnId (columnId)));
  83. g.drawText (text, 2, 0, width - 4, height, Justification::centredLeft, true);
  84. }
  85. g.setColour (Colours::black.withAlpha (0.2f));
  86. g.fillRect (width - 1, 0, 1, height);
  87. }
  88. // This is overloaded from TableListBoxModel, and tells us that the user has clicked a table header
  89. // to change the sort order.
  90. void sortOrderChanged (int newSortColumnId, bool isForwards)
  91. {
  92. if (newSortColumnId != 0)
  93. {
  94. DemoDataSorter sorter (getAttributeNameForColumnId (newSortColumnId), isForwards);
  95. dataList->sortChildElements (sorter);
  96. table.updateContent();
  97. }
  98. }
  99. // This is overloaded from TableListBoxModel, and must update any custom components that we're using
  100. Component* refreshComponentForCell (int rowNumber, int columnId, bool /*isRowSelected*/,
  101. Component* existingComponentToUpdate)
  102. {
  103. if (columnId == 5) // If it's the ratings column, we'll return our custom component..
  104. {
  105. RatingColumnCustomComponent* ratingsBox = (RatingColumnCustomComponent*) existingComponentToUpdate;
  106. // If an existing component is being passed-in for updating, we'll re-use it, but
  107. // if not, we'll have to create one.
  108. if (ratingsBox == 0)
  109. ratingsBox = new RatingColumnCustomComponent (*this);
  110. ratingsBox->setRowAndColumn (rowNumber, columnId);
  111. return ratingsBox;
  112. }
  113. else
  114. {
  115. // for any other column, just return 0, as we'll be painting these columns directly.
  116. jassert (existingComponentToUpdate == 0);
  117. return 0;
  118. }
  119. }
  120. // This is overloaded from TableListBoxModel, and should choose the best width for the specified
  121. // column.
  122. int getColumnAutoSizeWidth (int columnId)
  123. {
  124. if (columnId == 5)
  125. return 100; // (this is the ratings column, containing a custom component)
  126. int widest = 32;
  127. // find the widest bit of text in this column..
  128. for (int i = getNumRows(); --i >= 0;)
  129. {
  130. const XmlElement* rowElement = dataList->getChildElement (i);
  131. if (rowElement != 0)
  132. {
  133. const String text (rowElement->getStringAttribute (getAttributeNameForColumnId (columnId)));
  134. widest = jmax (widest, font.getStringWidth (text));
  135. }
  136. }
  137. return widest + 8;
  138. }
  139. // A couple of quick methods to set and get the "rating" value when the user
  140. // changes the combo box
  141. int getRating (const int rowNumber) const
  142. {
  143. return dataList->getChildElement (rowNumber)->getIntAttribute ("Rating");
  144. }
  145. void setRating (const int rowNumber, const int newRating)
  146. {
  147. dataList->getChildElement (rowNumber)->setAttribute ("Rating", newRating);
  148. }
  149. //==============================================================================
  150. void resized()
  151. {
  152. // position our table with a gap around its edge
  153. table.setBoundsInset (BorderSize<int> (8));
  154. }
  155. private:
  156. TableListBox table; // the table component itself
  157. Font font;
  158. ScopedPointer<XmlElement> demoData; // This is the XML document loaded from the embedded file "demo table data.xml"
  159. XmlElement* columnList; // A pointer to the sub-node of demoData that contains the list of columns
  160. XmlElement* dataList; // A pointer to the sub-node of demoData that contains the list of data rows
  161. int numRows; // The number of rows of data we've got
  162. //==============================================================================
  163. // This is a custom component containing a combo box, which we're going to put inside
  164. // our table's "rating" column.
  165. class RatingColumnCustomComponent : public Component,
  166. public ComboBoxListener
  167. {
  168. public:
  169. RatingColumnCustomComponent (TableDemoComponent& owner_)
  170. : owner (owner_)
  171. {
  172. // just put a combo box inside this component
  173. addAndMakeVisible (&comboBox);
  174. comboBox.addItem ("fab", 1);
  175. comboBox.addItem ("groovy", 2);
  176. comboBox.addItem ("hep", 3);
  177. comboBox.addItem ("neat", 4);
  178. comboBox.addItem ("wild", 5);
  179. comboBox.addItem ("swingin", 6);
  180. comboBox.addItem ("mad for it", 7);
  181. // when the combo is changed, we'll get a callback.
  182. comboBox.addListener (this);
  183. comboBox.setWantsKeyboardFocus (false);
  184. }
  185. ~RatingColumnCustomComponent()
  186. {
  187. }
  188. void resized()
  189. {
  190. comboBox.setBoundsInset (BorderSize<int> (2));
  191. }
  192. // Our demo code will call this when we may need to update our contents
  193. void setRowAndColumn (const int newRow, const int newColumn)
  194. {
  195. row = newRow;
  196. columnId = newColumn;
  197. comboBox.setSelectedId (owner.getRating (row), dontSendNotification);
  198. }
  199. void comboBoxChanged (ComboBox* /*comboBoxThatHasChanged*/)
  200. {
  201. owner.setRating (row, comboBox.getSelectedId());
  202. }
  203. private:
  204. TableDemoComponent& owner;
  205. ComboBox comboBox;
  206. int row, columnId;
  207. };
  208. //==============================================================================
  209. // A comparator used to sort our data when the user clicks a column header
  210. class DemoDataSorter
  211. {
  212. public:
  213. DemoDataSorter (const String attributeToSort_, bool forwards)
  214. : attributeToSort (attributeToSort_),
  215. direction (forwards ? 1 : -1)
  216. {
  217. }
  218. int compareElements (XmlElement* first, XmlElement* second) const
  219. {
  220. int result = first->getStringAttribute (attributeToSort)
  221. .compareLexicographically (second->getStringAttribute (attributeToSort));
  222. if (result == 0)
  223. result = first->getStringAttribute ("ID")
  224. .compareLexicographically (second->getStringAttribute ("ID"));
  225. return direction * result;
  226. }
  227. private:
  228. String attributeToSort;
  229. int direction;
  230. };
  231. //==============================================================================
  232. // this loads the embedded database XML file into memory
  233. void loadData()
  234. {
  235. XmlDocument dataDoc (String ((const char*) BinaryData::demo_table_data_xml));
  236. demoData = dataDoc.getDocumentElement();
  237. dataList = demoData->getChildByName ("DATA");
  238. columnList = demoData->getChildByName ("COLUMNS");
  239. numRows = dataList->getNumChildElements();
  240. }
  241. // (a utility method to search our XML for the attribute that matches a column ID)
  242. const String getAttributeNameForColumnId (const int columnId) const
  243. {
  244. forEachXmlChildElement (*columnList, columnXml)
  245. {
  246. if (columnXml->getIntAttribute ("columnId") == columnId)
  247. return columnXml->getStringAttribute ("name");
  248. }
  249. return String::empty;
  250. }
  251. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableDemoComponent)
  252. };
  253. //==============================================================================
  254. Component* createTableDemo()
  255. {
  256. return new TableDemoComponent();
  257. }