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.

700 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 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. package com.juce;
  19. import android.app.Activity;
  20. import android.app.AlertDialog;
  21. import android.content.DialogInterface;
  22. import android.content.Context;
  23. import android.content.Intent;
  24. import android.content.res.Configuration;
  25. import android.net.Uri;
  26. import android.os.Bundle;
  27. import android.view.*;
  28. import android.view.inputmethod.BaseInputConnection;
  29. import android.view.inputmethod.EditorInfo;
  30. import android.view.inputmethod.InputConnection;
  31. import android.view.inputmethod.InputMethodManager;
  32. import android.graphics.*;
  33. import android.opengl.*;
  34. import android.text.ClipboardManager;
  35. import android.text.InputType;
  36. import java.io.BufferedInputStream;
  37. import java.io.IOException;
  38. import java.io.InputStream;
  39. import java.io.OutputStream;
  40. import java.net.URL;
  41. import java.net.HttpURLConnection;
  42. import javax.microedition.khronos.egl.EGLConfig;
  43. import javax.microedition.khronos.opengles.GL10;
  44. import android.media.MediaScannerConnection;
  45. import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  46. //==============================================================================
  47. public final class JuceAppActivity extends Activity
  48. {
  49. //==============================================================================
  50. static
  51. {
  52. System.loadLibrary ("juce_jni");
  53. }
  54. @Override
  55. public final void onCreate (Bundle savedInstanceState)
  56. {
  57. super.onCreate (savedInstanceState);
  58. viewHolder = new ViewHolder (this);
  59. setContentView (viewHolder);
  60. setVolumeControlStream (AudioManager.STREAM_MUSIC);
  61. }
  62. @Override
  63. protected final void onDestroy()
  64. {
  65. quitApp();
  66. super.onDestroy();
  67. }
  68. @Override
  69. protected final void onPause()
  70. {
  71. if (viewHolder != null)
  72. viewHolder.onPause();
  73. suspendApp();
  74. super.onPause();
  75. }
  76. @Override
  77. protected final void onResume()
  78. {
  79. super.onResume();
  80. if (viewHolder != null)
  81. viewHolder.onResume();
  82. resumeApp();
  83. }
  84. @Override
  85. public void onConfigurationChanged (Configuration cfg)
  86. {
  87. super.onConfigurationChanged (cfg);
  88. setContentView (viewHolder);
  89. }
  90. private void callAppLauncher()
  91. {
  92. launchApp (getApplicationInfo().publicSourceDir,
  93. getApplicationInfo().dataDir);
  94. }
  95. //==============================================================================
  96. private native void launchApp (String appFile, String appDataDir);
  97. private native void quitApp();
  98. private native void suspendApp();
  99. private native void resumeApp();
  100. private native void setScreenSize (int screenWidth, int screenHeight);
  101. //==============================================================================
  102. public native void deliverMessage (long value);
  103. private android.os.Handler messageHandler = new android.os.Handler();
  104. public final void postMessage (long value)
  105. {
  106. messageHandler.post (new MessageCallback (value));
  107. }
  108. private final class MessageCallback implements Runnable
  109. {
  110. public MessageCallback (long value_) { value = value_; }
  111. public final void run() { deliverMessage (value); }
  112. private long value;
  113. }
  114. //==============================================================================
  115. private ViewHolder viewHolder;
  116. public final ComponentPeerView createNewView (boolean opaque)
  117. {
  118. ComponentPeerView v = new ComponentPeerView (this, opaque);
  119. viewHolder.addView (v);
  120. return v;
  121. }
  122. public final void deleteView (ComponentPeerView view)
  123. {
  124. ViewGroup group = (ViewGroup) (view.getParent());
  125. if (group != null)
  126. group.removeView (view);
  127. }
  128. final class ViewHolder extends ViewGroup
  129. {
  130. public ViewHolder (Context context)
  131. {
  132. super (context);
  133. setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
  134. setFocusable (false);
  135. }
  136. protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
  137. {
  138. setScreenSize (getWidth(), getHeight());
  139. if (isFirstResize)
  140. {
  141. isFirstResize = false;
  142. callAppLauncher();
  143. }
  144. }
  145. public final void onPause()
  146. {
  147. for (int i = getChildCount(); --i >= 0;)
  148. {
  149. View v = getChildAt (i);
  150. if (v instanceof ComponentPeerView)
  151. ((ComponentPeerView) v).onPause();
  152. }
  153. }
  154. public final void onResume()
  155. {
  156. for (int i = getChildCount(); --i >= 0;)
  157. {
  158. View v = getChildAt (i);
  159. if (v instanceof ComponentPeerView)
  160. ((ComponentPeerView) v).onResume();
  161. }
  162. }
  163. private boolean isFirstResize = true;
  164. }
  165. public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
  166. {
  167. canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
  168. }
  169. //==============================================================================
  170. public final String getClipboardContent()
  171. {
  172. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  173. return clipboard.getText().toString();
  174. }
  175. public final void setClipboardContent (String newText)
  176. {
  177. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  178. clipboard.setText (newText);
  179. }
  180. //==============================================================================
  181. public final void showMessageBox (String title, String message, final long callback)
  182. {
  183. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  184. builder.setTitle (title)
  185. .setMessage (message)
  186. .setCancelable (true)
  187. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  188. {
  189. public void onClick (DialogInterface dialog, int id)
  190. {
  191. dialog.cancel();
  192. JuceAppActivity.this.alertDismissed (callback, 0);
  193. }
  194. });
  195. builder.create().show();
  196. }
  197. public final void showOkCancelBox (String title, String message, final long callback)
  198. {
  199. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  200. builder.setTitle (title)
  201. .setMessage (message)
  202. .setCancelable (true)
  203. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  204. {
  205. public void onClick (DialogInterface dialog, int id)
  206. {
  207. dialog.cancel();
  208. JuceAppActivity.this.alertDismissed (callback, 1);
  209. }
  210. })
  211. .setNegativeButton ("Cancel", new DialogInterface.OnClickListener()
  212. {
  213. public void onClick (DialogInterface dialog, int id)
  214. {
  215. dialog.cancel();
  216. JuceAppActivity.this.alertDismissed (callback, 0);
  217. }
  218. });
  219. builder.create().show();
  220. }
  221. public final void showYesNoCancelBox (String title, String message, final long callback)
  222. {
  223. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  224. builder.setTitle (title)
  225. .setMessage (message)
  226. .setCancelable (true)
  227. .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
  228. {
  229. public void onClick (DialogInterface dialog, int id)
  230. {
  231. dialog.cancel();
  232. JuceAppActivity.this.alertDismissed (callback, 1);
  233. }
  234. })
  235. .setNegativeButton ("No", new DialogInterface.OnClickListener()
  236. {
  237. public void onClick (DialogInterface dialog, int id)
  238. {
  239. dialog.cancel();
  240. JuceAppActivity.this.alertDismissed (callback, 2);
  241. }
  242. })
  243. .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
  244. {
  245. public void onClick (DialogInterface dialog, int id)
  246. {
  247. dialog.cancel();
  248. JuceAppActivity.this.alertDismissed (callback, 0);
  249. }
  250. });
  251. builder.create().show();
  252. }
  253. public native void alertDismissed (long callback, int id);
  254. //==============================================================================
  255. public final class ComponentPeerView extends ViewGroup
  256. implements View.OnFocusChangeListener
  257. {
  258. public ComponentPeerView (Context context, boolean opaque_)
  259. {
  260. super (context);
  261. setWillNotDraw (false);
  262. opaque = opaque_;
  263. setFocusable (true);
  264. setFocusableInTouchMode (true);
  265. setOnFocusChangeListener (this);
  266. requestFocus();
  267. }
  268. //==============================================================================
  269. private native void handlePaint (Canvas canvas);
  270. @Override
  271. public void draw (Canvas canvas)
  272. {
  273. super.draw (canvas);
  274. handlePaint (canvas);
  275. }
  276. @Override
  277. public boolean isOpaque()
  278. {
  279. return opaque;
  280. }
  281. private boolean opaque;
  282. //==============================================================================
  283. private native void handleMouseDown (int index, float x, float y, long time);
  284. private native void handleMouseDrag (int index, float x, float y, long time);
  285. private native void handleMouseUp (int index, float x, float y, long time);
  286. @Override
  287. public boolean onTouchEvent (MotionEvent event)
  288. {
  289. int action = event.getAction();
  290. long time = event.getEventTime();
  291. switch (action & MotionEvent.ACTION_MASK)
  292. {
  293. case MotionEvent.ACTION_DOWN:
  294. handleMouseDown (event.getPointerId(0), event.getX(), event.getY(), time);
  295. return true;
  296. case MotionEvent.ACTION_CANCEL:
  297. case MotionEvent.ACTION_UP:
  298. handleMouseUp (event.getPointerId(0), event.getX(), event.getY(), time);
  299. return true;
  300. case MotionEvent.ACTION_MOVE:
  301. {
  302. int n = event.getPointerCount();
  303. for (int i = 0; i < n; ++i)
  304. handleMouseDrag (event.getPointerId(i), event.getX(i), event.getY(i), time);
  305. return true;
  306. }
  307. case MotionEvent.ACTION_POINTER_UP:
  308. {
  309. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  310. handleMouseUp (event.getPointerId(i), event.getX(i), event.getY(i), time);
  311. return true;
  312. }
  313. case MotionEvent.ACTION_POINTER_DOWN:
  314. {
  315. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  316. handleMouseDown (event.getPointerId(i), event.getX(i), event.getY(i), time);
  317. return true;
  318. }
  319. default:
  320. break;
  321. }
  322. return false;
  323. }
  324. //==============================================================================
  325. private native void handleKeyDown (int keycode, int textchar);
  326. private native void handleKeyUp (int keycode, int textchar);
  327. public void showKeyboard (boolean shouldShow)
  328. {
  329. InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
  330. if (imm != null)
  331. {
  332. if (shouldShow)
  333. imm.showSoftInput (this, InputMethodManager.SHOW_FORCED);
  334. else
  335. imm.hideSoftInputFromWindow (getWindowToken(), 0);
  336. }
  337. }
  338. @Override
  339. public boolean onKeyDown (int keyCode, KeyEvent event)
  340. {
  341. handleKeyDown (keyCode, event.getUnicodeChar());
  342. return true;
  343. }
  344. @Override
  345. public boolean onKeyUp (int keyCode, KeyEvent event)
  346. {
  347. handleKeyUp (keyCode, event.getUnicodeChar());
  348. return true;
  349. }
  350. // this is here to make keyboard entry work on a Galaxy Tab2 10.1
  351. @Override
  352. public InputConnection onCreateInputConnection (EditorInfo outAttrs)
  353. {
  354. outAttrs.actionLabel = "";
  355. outAttrs.hintText = "";
  356. outAttrs.initialCapsMode = 0;
  357. outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
  358. outAttrs.label = "";
  359. outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
  360. outAttrs.inputType = InputType.TYPE_NULL;
  361. return new BaseInputConnection (this, false);
  362. }
  363. //==============================================================================
  364. @Override
  365. protected void onSizeChanged (int w, int h, int oldw, int oldh)
  366. {
  367. super.onSizeChanged (w, h, oldw, oldh);
  368. viewSizeChanged();
  369. }
  370. @Override
  371. protected void onLayout (boolean changed, int left, int top, int right, int bottom)
  372. {
  373. for (int i = getChildCount(); --i >= 0;)
  374. requestTransparentRegion (getChildAt (i));
  375. }
  376. private native void viewSizeChanged();
  377. @Override
  378. public void onFocusChange (View v, boolean hasFocus)
  379. {
  380. if (v == this)
  381. focusChanged (hasFocus);
  382. }
  383. private native void focusChanged (boolean hasFocus);
  384. public void setViewName (String newName) {}
  385. public boolean isVisible() { return getVisibility() == VISIBLE; }
  386. public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
  387. public boolean containsPoint (int x, int y)
  388. {
  389. return true; //xxx needs to check overlapping views
  390. }
  391. public final void onPause()
  392. {
  393. for (int i = getChildCount(); --i >= 0;)
  394. {
  395. View v = getChildAt (i);
  396. if (v instanceof OpenGLView)
  397. ((OpenGLView) v).onPause();
  398. }
  399. }
  400. public final void onResume()
  401. {
  402. for (int i = getChildCount(); --i >= 0;)
  403. {
  404. View v = getChildAt (i);
  405. if (v instanceof OpenGLView)
  406. ((OpenGLView) v).onResume();
  407. }
  408. }
  409. public OpenGLView createGLView()
  410. {
  411. OpenGLView glView = new OpenGLView (getContext());
  412. addView (glView);
  413. return glView;
  414. }
  415. }
  416. //==============================================================================
  417. public final class OpenGLView extends GLSurfaceView
  418. implements GLSurfaceView.Renderer
  419. {
  420. OpenGLView (Context context)
  421. {
  422. super (context);
  423. setEGLContextClientVersion (2);
  424. setRenderer (this);
  425. setRenderMode (RENDERMODE_WHEN_DIRTY);
  426. }
  427. @Override
  428. public void onSurfaceCreated (GL10 unused, EGLConfig config)
  429. {
  430. contextCreated();
  431. }
  432. @Override
  433. public void onSurfaceChanged (GL10 unused, int width, int height)
  434. {
  435. contextChangedSize();
  436. }
  437. @Override
  438. public void onDrawFrame (GL10 unused)
  439. {
  440. render();
  441. }
  442. private native void contextCreated();
  443. private native void contextChangedSize();
  444. private native void render();
  445. }
  446. //==============================================================================
  447. public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds)
  448. {
  449. Path p = new Path();
  450. paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p);
  451. RectF boundsF = new RectF();
  452. p.computeBounds (boundsF, true);
  453. matrix.mapRect (boundsF);
  454. boundsF.roundOut (bounds);
  455. bounds.left--;
  456. bounds.right++;
  457. final int w = bounds.width();
  458. final int h = Math.max (1, bounds.height());
  459. Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
  460. Canvas c = new Canvas (bm);
  461. matrix.postTranslate (-bounds.left, -bounds.top);
  462. c.setMatrix (matrix);
  463. c.drawPath (p, paint);
  464. final int sizeNeeded = w * h;
  465. if (cachedRenderArray.length < sizeNeeded)
  466. cachedRenderArray = new int [sizeNeeded];
  467. bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
  468. bm.recycle();
  469. return cachedRenderArray;
  470. }
  471. private int[] cachedRenderArray = new int [256];
  472. //==============================================================================
  473. public static class HTTPStream
  474. {
  475. public HTTPStream (HttpURLConnection connection_) throws IOException
  476. {
  477. connection = connection_;
  478. inputStream = new BufferedInputStream (connection.getInputStream());
  479. }
  480. public final void release()
  481. {
  482. try
  483. {
  484. inputStream.close();
  485. }
  486. catch (IOException e)
  487. {}
  488. connection.disconnect();
  489. }
  490. public final int read (byte[] buffer, int numBytes)
  491. {
  492. int num = 0;
  493. try
  494. {
  495. num = inputStream.read (buffer, 0, numBytes);
  496. }
  497. catch (IOException e)
  498. {}
  499. if (num > 0)
  500. position += num;
  501. return num;
  502. }
  503. public final long getPosition() { return position; }
  504. public final long getTotalLength() { return -1; }
  505. public final boolean isExhausted() { return false; }
  506. public final boolean setPosition (long newPos) { return false; }
  507. private HttpURLConnection connection;
  508. private InputStream inputStream;
  509. private long position;
  510. }
  511. public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
  512. String headers, int timeOutMs,
  513. java.lang.StringBuffer responseHeaders)
  514. {
  515. try
  516. {
  517. HttpURLConnection connection = (HttpURLConnection) (new URL (address).openConnection());
  518. if (connection != null)
  519. {
  520. try
  521. {
  522. if (isPost)
  523. {
  524. connection.setConnectTimeout (timeOutMs);
  525. connection.setDoOutput (true);
  526. connection.setChunkedStreamingMode (0);
  527. OutputStream out = connection.getOutputStream();
  528. out.write (postData);
  529. out.flush();
  530. }
  531. return new HTTPStream (connection);
  532. }
  533. catch (Throwable e)
  534. {
  535. connection.disconnect();
  536. }
  537. }
  538. }
  539. catch (Throwable e)
  540. {}
  541. return null;
  542. }
  543. public final void launchURL (String url)
  544. {
  545. startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
  546. }
  547. public static final String getLocaleValue (boolean isRegion)
  548. {
  549. java.util.Locale locale = java.util.Locale.getDefault();
  550. return isRegion ? locale.getDisplayCountry (java.util.Locale.US)
  551. : locale.getDisplayLanguage (java.util.Locale.US);
  552. }
  553. //==============================================================================
  554. private final class SingleMediaScanner implements MediaScannerConnectionClient
  555. {
  556. public SingleMediaScanner (Context context, String filename)
  557. {
  558. file = filename;
  559. msc = new MediaScannerConnection (context, this);
  560. msc.connect();
  561. }
  562. @Override
  563. public void onMediaScannerConnected()
  564. {
  565. msc.scanFile (file, null);
  566. }
  567. @Override
  568. public void onScanCompleted (String path, Uri uri)
  569. {
  570. msc.disconnect();
  571. }
  572. private MediaScannerConnection msc;
  573. private String file;
  574. }
  575. public final void scanFile (String filename)
  576. {
  577. new SingleMediaScanner (this, filename);
  578. }
  579. }