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.

430 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceLibraryCode/JuceHeader.h"
  20. #include "VoicePurchases.h"
  21. //==============================================================================
  22. class InAppPurchaseApplication : public JUCEApplication,
  23. private AsyncUpdater
  24. {
  25. public:
  26. //==============================================================================
  27. InAppPurchaseApplication() : voicePurchases (*this) {}
  28. //==============================================================================
  29. const String getApplicationName() override { return ProjectInfo::projectName; }
  30. const String getApplicationVersion() override { return ProjectInfo::versionString; }
  31. bool moreThanOneInstanceAllowed() override { return false; }
  32. //==============================================================================
  33. void initialise (const String&) override
  34. {
  35. Desktop::getInstance().getDefaultLookAndFeel().setUsingNativeAlertWindows (true);
  36. dm.addAudioCallback (&player);
  37. dm.initialiseWithDefaultDevices (0, 2);
  38. mainWindow = new MainWindow;
  39. }
  40. void shutdown() override
  41. {
  42. mainWindow = nullptr;
  43. dm.closeAudioDevice();
  44. dm.removeAudioCallback (&player);
  45. }
  46. static InAppPurchaseApplication* getInstance()
  47. {
  48. return static_cast<InAppPurchaseApplication*> (JUCEApplication::getInstance());
  49. }
  50. //==============================================================================
  51. SoundPlayer& getPlayer()
  52. {
  53. return player;
  54. }
  55. VoicePurchases& getPurchases()
  56. {
  57. return voicePurchases;
  58. }
  59. //==============================================================================
  60. class MainContentComponent : public Component, private Button::Listener
  61. {
  62. public:
  63. MainContentComponent()
  64. {
  65. setOpaque (true);
  66. phraseListBox.setRowHeight (33);
  67. phraseListBox.selectRow (0);
  68. phraseListBox.updateContent();
  69. voiceListBox.setRowHeight (66);
  70. voiceListBox.selectRow (0);
  71. voiceListBox.updateContent();
  72. voiceListBox.getViewport()->setScrollOnDragEnabled (true);
  73. addAndMakeVisible (phraseLabel);
  74. addAndMakeVisible (phraseListBox);
  75. addAndMakeVisible (playStopButton);
  76. addAndMakeVisible (voiceLabel);
  77. addAndMakeVisible (voiceListBox);
  78. playStopButton.addListener (this);
  79. #if JUCE_ANDROID || JUCE_IOS
  80. auto screenBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  81. setSize (screenBounds.getWidth(), screenBounds.getHeight());
  82. #else
  83. setSize (800, 600);
  84. #endif
  85. }
  86. void updateDisplay()
  87. {
  88. voiceListBox.updateContent();
  89. voiceListBox.setEnabled (! getInstance()->getPurchases().isPurchaseInProgress());
  90. voiceListBox.repaint();
  91. }
  92. private:
  93. //==============================================================================
  94. class PhraseModel : public ListBoxModel
  95. {
  96. public:
  97. PhraseModel() {}
  98. int getNumRows() override { return phrases.size(); }
  99. void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
  100. {
  101. Rectangle<int> r (0, 0, w, h);
  102. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  103. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  104. g.fillRect (r);
  105. g.setColour (lf.findColour (ListBox::textColourId));
  106. g.setFont (18);
  107. String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
  108. g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
  109. }
  110. private:
  111. static StringArray phrases;
  112. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
  113. };
  114. //==============================================================================
  115. class VoiceModel : public ListBoxModel
  116. {
  117. public:
  118. //==============================================================================
  119. class VoiceRow : public Component,
  120. private Button::Listener,
  121. private Timer
  122. {
  123. public:
  124. VoiceRow() : voices (getInstance()->getPurchases().getVoiceNames())
  125. {
  126. addAndMakeVisible (nameLabel);
  127. addAndMakeVisible (purchaseButton);
  128. addAndMakeVisible (priceLabel);
  129. purchaseButton.addListener (this);
  130. setSize (600, 33);
  131. }
  132. void paint (Graphics& g) override
  133. {
  134. auto r = getLocalBounds().reduced (4);
  135. {
  136. auto voiceIconBounds = r.removeFromLeft (r.getHeight());
  137. g.setColour (Colours::black);
  138. g.drawRect (voiceIconBounds);
  139. voiceIconBounds.reduce (1, 1);
  140. g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
  141. g.fillRect (voiceIconBounds);
  142. g.drawImage (avatar, voiceIconBounds.toFloat());
  143. if (! hasBeenPurchased)
  144. {
  145. g.setColour (Colours::white.withAlpha (0.8f));
  146. g.fillRect (voiceIconBounds);
  147. if (purchaseInProgress)
  148. getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
  149. voiceIconBounds.getX(), voiceIconBounds.getY(),
  150. voiceIconBounds.getWidth(), voiceIconBounds.getHeight());
  151. }
  152. }
  153. }
  154. void resized() override
  155. {
  156. auto r = getLocalBounds().reduced (4 + 8, 4);
  157. auto h = r.getHeight();
  158. auto w = static_cast<int> (h * 1.5);
  159. r.removeFromLeft (h);
  160. purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
  161. nameLabel.setBounds (r.removeFromTop (18));
  162. priceLabel.setBounds (r.removeFromTop (18));
  163. }
  164. void update (int rowNumber, bool rowIsSelected)
  165. {
  166. isSelected = rowIsSelected;
  167. rowSelected = rowNumber;
  168. if (isPositiveAndBelow (rowNumber, voices.size()))
  169. {
  170. auto imageResourceName = voices[rowNumber] + "_png";
  171. nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
  172. auto purchase = getInstance()->getPurchases().getPurchase (rowNumber);
  173. hasBeenPurchased = purchase.isPurchased;
  174. purchaseInProgress = purchase.purchaseInProgress;
  175. if (purchaseInProgress)
  176. startTimer (1000 / 50);
  177. else
  178. stopTimer();
  179. nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
  180. nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  181. priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
  182. priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
  183. priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
  184. if (rowNumber == 0)
  185. {
  186. purchaseButton.setButtonText ("Internal");
  187. purchaseButton.setEnabled (false);
  188. }
  189. else
  190. {
  191. purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
  192. purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
  193. }
  194. setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
  195. int rawSize;
  196. if (auto* rawData = BinaryData::getNamedResource (imageResourceName.toRawUTF8(), rawSize))
  197. {
  198. MemoryInputStream imageData (rawData, static_cast<size_t> (rawSize), false);
  199. avatar = PNGImageFormat().decodeImage (imageData);
  200. }
  201. }
  202. }
  203. private:
  204. //==============================================================================
  205. void buttonClicked (Button*) override
  206. {
  207. if (rowSelected >= 0)
  208. {
  209. if (! hasBeenPurchased)
  210. {
  211. getInstance()->getPurchases().purchaseVoice (rowSelected);
  212. purchaseInProgress = true;
  213. startTimer (1000 / 50);
  214. }
  215. }
  216. }
  217. void timerCallback() override { repaint(); }
  218. //==============================================================================
  219. bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
  220. int rowSelected = -1;
  221. Image avatar;
  222. StringArray voices;
  223. Label nameLabel, priceLabel;
  224. TextButton purchaseButton {"Purchase"};
  225. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
  226. };
  227. //==============================================================================
  228. VoiceModel() : voiceProducts (getInstance()->getPurchases().getVoiceNames()) {}
  229. int getNumRows() override { return voiceProducts.size(); }
  230. Component* refreshComponentForRow (int row, bool selected, Component* existing) override
  231. {
  232. if (isPositiveAndBelow (row, voiceProducts.size()))
  233. {
  234. if (existing == nullptr)
  235. existing = new VoiceRow;
  236. if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
  237. voiceRow->update (row, selected);
  238. return existing;
  239. }
  240. return nullptr;
  241. }
  242. void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
  243. {
  244. auto r = Rectangle<int> (0, 0, w, h).reduced (4);
  245. auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
  246. g.setColour (lf.findColour (isSelected ? TextEditor::highlightColourId : ListBox::backgroundColourId));
  247. g.fillRect (r);
  248. }
  249. private:
  250. StringArray voiceProducts;
  251. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
  252. };
  253. //==============================================================================
  254. void resized() override
  255. {
  256. auto r = getLocalBounds().reduced (20);
  257. {
  258. auto phraseArea = r.removeFromTop (r.getHeight() / 2);
  259. phraseLabel.setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
  260. playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
  261. phraseListBox.setBounds (phraseArea);
  262. }
  263. {
  264. auto voiceArea = r;
  265. voiceLabel.setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
  266. voiceListBox.setBounds (voiceArea);
  267. }
  268. }
  269. void paint (Graphics& g) override
  270. {
  271. g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
  272. .findColour (ResizableWindow::backgroundColourId));
  273. }
  274. //==============================================================================
  275. void buttonClicked (Button*) override
  276. {
  277. MemoryOutputStream resourceName;
  278. auto idx = voiceListBox.getSelectedRow();
  279. if (isPositiveAndBelow (idx, soundNames.size()))
  280. {
  281. resourceName << soundNames[idx] << phraseListBox.getSelectedRow() << "_ogg";
  282. int numBytes;
  283. if (auto* data = BinaryData::getNamedResource (resourceName.toString().toRawUTF8(), numBytes))
  284. getInstance()->getPlayer().play (data, static_cast<size_t> (numBytes));
  285. }
  286. }
  287. //==============================================================================
  288. StringArray soundNames = getInstance()->getPurchases().getVoiceNames();
  289. PhraseModel phraseModel;
  290. Label phraseLabel {"phraseLabel", NEEDS_TRANS ("Phrases:")};
  291. ListBox phraseListBox { "phraseListBox", &phraseModel };
  292. TextButton playStopButton {"Play"};
  293. VoiceModel voiceModel;
  294. Label voiceLabel {"voiceLabel", NEEDS_TRANS ("Voices:")};
  295. ListBox voiceListBox { "voiceListBox", &voiceModel };
  296. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
  297. };
  298. //==============================================================================
  299. class MainWindow : public DocumentWindow
  300. {
  301. public:
  302. MainWindow() : DocumentWindow (ProjectInfo::projectName,
  303. Desktop::getInstance().getDefaultLookAndFeel()
  304. .findColour (ResizableWindow::backgroundColourId),
  305. DocumentWindow::allButtons)
  306. {
  307. setUsingNativeTitleBar (true);
  308. setContentOwned (new MainContentComponent(), true);
  309. #if JUCE_ANDROID || JUCE_IOS
  310. setFullScreen (true);
  311. #else
  312. centreWithSize (getWidth(), getHeight());
  313. #endif
  314. setVisible (true);
  315. }
  316. void closeButtonPressed() override
  317. {
  318. JUCEApplication::getInstance()->systemRequestedQuit();
  319. }
  320. private:
  321. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
  322. };
  323. private:
  324. //==============================================================================
  325. void handleAsyncUpdate() override
  326. {
  327. if (auto* contentComponent = dynamic_cast<MainContentComponent*> (mainWindow->getContentComponent()))
  328. contentComponent->updateDisplay();
  329. }
  330. //==============================================================================
  331. VoicePurchases voicePurchases;
  332. AudioDeviceManager dm;
  333. SoundPlayer player;
  334. ScopedPointer<MainWindow> mainWindow;
  335. ScopedPointer<AlertWindow> alertWindow;
  336. };
  337. StringArray InAppPurchaseApplication::MainContentComponent::PhraseModel::phrases {"I love JUCE!", "The five dimensions of touch", "Make it fast!"};
  338. //==============================================================================
  339. // This macro generates the main() routine that launches the app.
  340. START_JUCE_APPLICATION (InAppPurchaseApplication)