RealTierEditor.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /* RealTierEditor.cpp
  2. *
  3. * Copyright (C) 1992-2011,2012,2013,2014,2015,2016,2017 Paul Boersma
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. * See the GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "RealTierEditor.h"
  19. #include "EditorM.h"
  20. Thing_implement (RealTierEditor, TimeSoundEditor, 0);
  21. #define SOUND_HEIGHT 0.382
  22. /********** MENU COMMANDS **********/
  23. static void menu_cb_removePoints (RealTierEditor me, EDITOR_ARGS_DIRECT) {
  24. Editor_save (me, U"Remove point(s)");
  25. RealTier tier = (RealTier) my data;
  26. if (my startSelection == my endSelection)
  27. AnyTier_removePointNear (tier->asAnyTier(), my startSelection);
  28. else
  29. AnyTier_removePointsBetween (tier->asAnyTier(), my startSelection, my endSelection);
  30. RealTierEditor_updateScaling (me);
  31. FunctionEditor_redraw (me);
  32. Editor_broadcastDataChanged (me);
  33. }
  34. static void menu_cb_addPointAtCursor (RealTierEditor me, EDITOR_ARGS_DIRECT) {
  35. if (isdefined (my v_minimumLegalValue ()) && my ycursor < my v_minimumLegalValue ())
  36. Melder_throw (U"Cannot add a point below ", my v_minimumLegalValue (), my v_rightTickUnits (), U".");
  37. if (isdefined (my v_maximumLegalValue ()) && my ycursor > my v_maximumLegalValue ())
  38. Melder_throw (U"Cannot add a point above ", my v_maximumLegalValue (), my v_rightTickUnits (), U".");
  39. Editor_save (me, U"Add point");
  40. RealTier_addPoint ((RealTier) my data, 0.5 * (my startSelection + my endSelection), my ycursor);
  41. RealTierEditor_updateScaling (me);
  42. FunctionEditor_redraw (me);
  43. Editor_broadcastDataChanged (me);
  44. }
  45. static void menu_cb_addPointAt (RealTierEditor me, EDITOR_ARGS_FORM) {
  46. EDITOR_FORM (U"Add point", nullptr)
  47. REAL (time, U"Time (s)", U"0.0")
  48. REAL (desiredValue, my v_quantityText (), U"0.0")
  49. EDITOR_OK
  50. SET_REAL (time, 0.5 * (my startSelection + my endSelection))
  51. SET_REAL (desiredValue, my ycursor)
  52. EDITOR_DO
  53. if (isdefined (my v_minimumLegalValue ()) && desiredValue < my v_minimumLegalValue ())
  54. Melder_throw (U"Cannot add a point below ", my v_minimumLegalValue (), my v_rightTickUnits (), U".");
  55. if (isdefined (my v_maximumLegalValue ()) && desiredValue > my v_maximumLegalValue ())
  56. Melder_throw (U"Cannot add a point above ", my v_maximumLegalValue (), my v_rightTickUnits (), U".");
  57. Editor_save (me, U"Add point");
  58. RealTier_addPoint ((RealTier) my data, time, desiredValue);
  59. RealTierEditor_updateScaling (me);
  60. FunctionEditor_redraw (me);
  61. Editor_broadcastDataChanged (me);
  62. EDITOR_END
  63. }
  64. static void menu_cb_setRange (RealTierEditor me, EDITOR_ARGS_FORM) {
  65. EDITOR_FORM (my v_setRangeTitle (), nullptr)
  66. REAL (ymin, my v_yminText (), my v_defaultYminText ())
  67. REAL (ymax, my v_ymaxText (), my v_defaultYmaxText ())
  68. EDITOR_OK
  69. SET_REAL (ymin, my ymin)
  70. SET_REAL (ymax, my ymax)
  71. EDITOR_DO
  72. my ymin = ymin;
  73. my ymax = ymax;
  74. if (my ymax <= my ymin) RealTierEditor_updateScaling (me);
  75. FunctionEditor_redraw (me);
  76. EDITOR_END
  77. }
  78. void structRealTierEditor :: v_createMenuItems_view (EditorMenu menu) {
  79. RealTierEditor_Parent :: v_createMenuItems_view (menu);
  80. EditorMenu_addCommand (menu, U"-- view/realtier --", 0, 0);
  81. EditorMenu_addCommand (menu, v_setRangeTitle (), 0, menu_cb_setRange);
  82. }
  83. void structRealTierEditor :: v_createMenus () {
  84. RealTierEditor_Parent :: v_createMenus ();
  85. EditorMenu menu = Editor_addMenu (this, U"Point", 0);
  86. EditorMenu_addCommand (menu, U"Add point at cursor", 'T', menu_cb_addPointAtCursor);
  87. EditorMenu_addCommand (menu, U"Add point at...", 0, menu_cb_addPointAt);
  88. EditorMenu_addCommand (menu, U"-- remove point --", 0, nullptr);
  89. EditorMenu_addCommand (menu, U"Remove point(s)", GuiMenu_OPTION | 'T', menu_cb_removePoints);
  90. }
  91. void RealTierEditor_updateScaling (RealTierEditor me) {
  92. RealTier data = (RealTier) my data;
  93. if (data -> points.size == 0) {
  94. my ymin = my v_defaultYmin ();
  95. my ymax = my v_defaultYmax ();
  96. } else {
  97. double ymin = RealTier_getMinimumValue (data);
  98. double ymax = RealTier_getMaximumValue (data);
  99. double range = ymax - ymin;
  100. if (range == 0.0) {
  101. ymin -= 1.0;
  102. ymax += 1.0;
  103. } else {
  104. ymin -= 0.2 * range;
  105. ymax += 0.2 * range;
  106. }
  107. if (isdefined (my v_minimumLegalValue()) && ymin < my v_minimumLegalValue ())
  108. ymin = my v_minimumLegalValue ();
  109. if (isdefined (my v_maximumLegalValue ()) && ymin > my v_maximumLegalValue ())
  110. ymin = my v_maximumLegalValue ();
  111. if (isdefined (my v_minimumLegalValue ()) && ymax < my v_minimumLegalValue ())
  112. ymax = my v_minimumLegalValue ();
  113. if (isdefined (my v_maximumLegalValue ()) && ymax > my v_maximumLegalValue ())
  114. ymax = my v_maximumLegalValue ();
  115. if (ymin >= ymax) {
  116. if (isdefined (my v_minimumLegalValue ()) && isdefined (my v_maximumLegalValue ())) {
  117. ymin = my v_minimumLegalValue ();
  118. ymax = my v_maximumLegalValue ();
  119. } else if (isdefined (my v_minimumLegalValue ())) {
  120. ymin = my v_minimumLegalValue ();
  121. ymax = ymin + 1.0;
  122. } else {
  123. Melder_assert (isdefined (my v_maximumLegalValue ()));
  124. ymax = my v_maximumLegalValue ();
  125. ymin = ymax - 1.0;
  126. }
  127. }
  128. if (ymin < my ymin || my ymin < 0.0) my ymin = ymin;
  129. if (ymax > my ymax) my ymax = ymax;
  130. if (my ycursor <= my ymin || my ycursor >= my ymax)
  131. my ycursor = 0.382 * my ymin + 0.618 * my ymax;
  132. }
  133. }
  134. void structRealTierEditor :: v_dataChanged () {
  135. RealTierEditor_updateScaling (this);
  136. RealTierEditor_Parent :: v_dataChanged ();
  137. }
  138. /********** DRAWING AREA **********/
  139. void structRealTierEditor :: v_draw () {
  140. RealTier data = (RealTier) our data;
  141. integer n = data -> points.size;
  142. trace (U"structRealTierEditor :: v_draw ", n);
  143. Graphics_Viewport viewport;
  144. if (our d_sound.data) {
  145. viewport = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 1.0 - SOUND_HEIGHT, 1.0);
  146. Graphics_setColour (our graphics.get(), Graphics_WHITE);
  147. Graphics_setWindow (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  148. Graphics_fillRectangle (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  149. TimeSoundEditor_drawSound (this, -1.0, 1.0);
  150. Graphics_resetViewport (our graphics.get(), viewport);
  151. Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 0.0, 1.0 - SOUND_HEIGHT);
  152. }
  153. Graphics_setColour (our graphics.get(), Graphics_WHITE);
  154. Graphics_setWindow (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  155. Graphics_fillRectangle (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  156. Graphics_setWindow (our graphics.get(), our startWindow, our endWindow, our ymin, our ymax);
  157. Graphics_setColour (our graphics.get(), Graphics_RED);
  158. Graphics_line (our graphics.get(), our startWindow, ycursor, our endWindow, our ycursor);
  159. Graphics_setTextAlignment (our graphics.get(), Graphics_RIGHT, Graphics_HALF);
  160. Graphics_text (our graphics.get(), our startWindow, our ycursor, Melder_float (Melder_half (our ycursor)));
  161. Graphics_setColour (our graphics.get(), Graphics_BLUE);
  162. Graphics_setTextAlignment (our graphics.get(), Graphics_LEFT, Graphics_TOP);
  163. Graphics_text (our graphics.get(), our endWindow, our ymax, Melder_float (Melder_half (ymax)), our v_rightTickUnits ());
  164. Graphics_setTextAlignment (our graphics.get(), Graphics_LEFT, Graphics_HALF);
  165. Graphics_text (our graphics.get(), our endWindow, our ymin, Melder_float (Melder_half (our ymin)), our v_rightTickUnits ());
  166. integer ifirstSelected = AnyTier_timeToHighIndex (data->asAnyTier(), our startSelection);
  167. integer ilastSelected = AnyTier_timeToLowIndex (data->asAnyTier(), our endSelection);
  168. trace (U"structRealTierEditor :: v_draw: selected from ", our startSelection, U" ",
  169. ifirstSelected, U" to ", our endSelection, U" ", ilastSelected);
  170. integer imin = AnyTier_timeToHighIndex (data->asAnyTier(), our startWindow);
  171. integer imax = AnyTier_timeToLowIndex (data->asAnyTier(), our endWindow);
  172. Graphics_setLineWidth (our graphics.get(), 2.0);
  173. if (n == 0) {
  174. Graphics_setTextAlignment (our graphics.get(), Graphics_CENTRE, Graphics_HALF);
  175. Graphics_text (our graphics.get(), 0.5 * (our startWindow + our endWindow),
  176. 0.5 * (our ymin + our ymax), U"(no points)");
  177. } else if (imax < imin) {
  178. double yleft = RealTier_getValueAtTime (data, our startWindow);
  179. double yright = RealTier_getValueAtTime (data, our endWindow);
  180. Graphics_line (our graphics.get(), our startWindow, yleft, our endWindow, yright);
  181. } else for (integer i = imin; i <= imax; i ++) {
  182. RealPoint point = data -> points.at [i];
  183. double t = point -> number, y = point -> value;
  184. if (i >= ifirstSelected && i <= ilastSelected)
  185. Graphics_setColour (our graphics.get(), Graphics_RED);
  186. Graphics_fillCircle_mm (our graphics.get(), t, y, 3.0);
  187. Graphics_setColour (our graphics.get(), Graphics_BLUE);
  188. if (i == 1)
  189. Graphics_line (our graphics.get(), our startWindow, y, t, y);
  190. else if (i == imin)
  191. Graphics_line (our graphics.get(), t, y, our startWindow, RealTier_getValueAtTime (data, our startWindow));
  192. if (i == n)
  193. Graphics_line (our graphics.get(), t, y, our endWindow, y);
  194. else if (i == imax)
  195. Graphics_line (our graphics.get(), t, y, our endWindow, RealTier_getValueAtTime (data, our endWindow));
  196. else {
  197. RealPoint pointRight = data -> points.at [i + 1];
  198. Graphics_line (our graphics.get(), t, y, pointRight -> number, pointRight -> value);
  199. }
  200. }
  201. Graphics_setLineWidth (our graphics.get(), 1.0);
  202. Graphics_setColour (our graphics.get(), Graphics_BLACK);
  203. our v_updateMenuItems_file ();
  204. }
  205. static void drawWhileDragging (RealTierEditor me, double /* xWC */, double /* yWC */, integer first, integer last, double dt, double dy) {
  206. RealTier data = (RealTier) my data;
  207. /*
  208. * Draw all selected points as magenta empty circles, if inside the window.
  209. */
  210. for (integer i = first; i <= last; i ++) {
  211. RealPoint point = data -> points.at [i];
  212. double t = point -> number + dt, y = point -> value + dy;
  213. if (t >= my startWindow && t <= my endWindow)
  214. Graphics_circle_mm (my graphics.get(), t, y, 3);
  215. }
  216. if (last == first) {
  217. /*
  218. * Draw a crosshair with time and y.
  219. */
  220. RealPoint point = data -> points.at [first];
  221. double t = point -> number + dt, y = point -> value + dy;
  222. Graphics_line (my graphics.get(), t, my ymin, t, my ymax - Graphics_dyMMtoWC (my graphics.get(), 4.0));
  223. Graphics_setTextAlignment (my graphics.get(), kGraphics_horizontalAlignment::CENTRE, Graphics_TOP);
  224. Graphics_text (my graphics.get(), t, my ymax, Melder_fixed (t, 6));
  225. Graphics_line (my graphics.get(), my startWindow, y, my endWindow, y);
  226. Graphics_setTextAlignment (my graphics.get(), Graphics_LEFT, Graphics_BOTTOM);
  227. Graphics_text (my graphics.get(), my startWindow, y, Melder_fixed (y, 6));
  228. }
  229. }
  230. bool structRealTierEditor :: v_click (double xWC, double yWC, bool shiftKeyPressed) {
  231. RealTier pitch = (RealTier) our data;
  232. double dt = 0.0, df = 0.0;
  233. Graphics_Viewport viewport;
  234. /*
  235. * Perform the default action: move cursor.
  236. */
  237. //our startSelection = our endSelection = xWC;
  238. if (our d_sound.data) {
  239. if (yWC < 1.0 - SOUND_HEIGHT) { // clicked in tier area?
  240. yWC /= 1.0 - SOUND_HEIGHT;
  241. our ycursor = (1.0 - yWC) * our ymin + yWC * our ymax;
  242. viewport = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 0.0, 1.0 - SOUND_HEIGHT);
  243. } else {
  244. return our RealTierEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  245. }
  246. } else {
  247. our ycursor = (1.0 - yWC) * our ymin + yWC * our ymax;
  248. }
  249. Graphics_setWindow (our graphics.get(), our startWindow, our endWindow, our ymin, our ymax);
  250. yWC = our ycursor;
  251. /*
  252. * Clicked on a point?
  253. */
  254. integer inearestPoint = AnyTier_timeToNearestIndex (pitch->asAnyTier(), xWC);
  255. if (inearestPoint == 0) return our RealTierEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  256. RealPoint nearestPoint = pitch -> points.at [inearestPoint];
  257. if (Graphics_distanceWCtoMM (our graphics.get(), xWC, yWC, nearestPoint -> number, nearestPoint -> value) > 1.5) {
  258. if (our d_sound.data) Graphics_resetViewport (our graphics.get(), viewport);
  259. return our RealTierEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  260. }
  261. /*
  262. * Clicked on a selected point?
  263. */
  264. bool draggingSelection = shiftKeyPressed &&
  265. nearestPoint -> number > our startSelection && nearestPoint -> number < our endSelection;
  266. integer ifirstSelected, ilastSelected;
  267. if (draggingSelection) {
  268. ifirstSelected = AnyTier_timeToHighIndex (pitch->asAnyTier(), our startSelection);
  269. ilastSelected = AnyTier_timeToLowIndex (pitch->asAnyTier(), our endSelection);
  270. Editor_save (this, U"Drag points");
  271. } else {
  272. ifirstSelected = ilastSelected = inearestPoint;
  273. Editor_save (this, U"Drag point");
  274. }
  275. /*
  276. * Drag.
  277. */
  278. Graphics_xorOn (our graphics.get(), Graphics_MAROON);
  279. drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df); // draw at old position
  280. while (Graphics_mouseStillDown (our graphics.get())) {
  281. double xWC_new, yWC_new;
  282. Graphics_getMouseLocation (our graphics.get(), & xWC_new, & yWC_new);
  283. if (xWC_new != xWC || yWC_new != yWC) {
  284. drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df); // undraw at old position
  285. dt += xWC_new - xWC, df += yWC_new - yWC;
  286. xWC = xWC_new, yWC = yWC_new;
  287. drawWhileDragging (this, xWC, yWC, ifirstSelected, ilastSelected, dt, df); // draw at new position
  288. }
  289. }
  290. Graphics_xorOff (our graphics.get());
  291. /*
  292. * Dragged inside window?
  293. */
  294. if (xWC < our startWindow || xWC > our endWindow) return 1;
  295. /*
  296. * Points not dragged past neighbours?
  297. */
  298. {
  299. double newTime = pitch -> points.at [ifirstSelected] -> number + dt;
  300. if (newTime < our tmin) return 1; // outside domain
  301. if (ifirstSelected > 1 && newTime <= pitch -> points.at [ifirstSelected - 1] -> number)
  302. return 1; // past left neighbour
  303. newTime = pitch -> points.at [ilastSelected] -> number + dt;
  304. if (newTime > our tmax) return 1; // outside domain
  305. if (ilastSelected < pitch -> points.size && newTime >= pitch -> points.at [ilastSelected + 1] -> number)
  306. return FunctionEditor_UPDATE_NEEDED; // past right neighbour
  307. }
  308. /*
  309. * Drop.
  310. */
  311. for (int i = ifirstSelected; i <= ilastSelected; i ++) {
  312. RealPoint point = pitch -> points.at [i];
  313. point -> number += dt;
  314. point -> value += df;
  315. if (isdefined (v_minimumLegalValue ()) && point -> value < v_minimumLegalValue ())
  316. point -> value = v_minimumLegalValue ();
  317. if (isdefined (v_maximumLegalValue ()) && point -> value > v_maximumLegalValue ())
  318. point -> value = v_maximumLegalValue ();
  319. }
  320. /*
  321. * Make sure that the same points are still selected (a problem with Undo...).
  322. */
  323. if (draggingSelection) our startSelection += dt, our endSelection += dt;
  324. if (ifirstSelected == ilastSelected) {
  325. /*
  326. * Move crosshair to only selected pitch point.
  327. */
  328. RealPoint point = pitch -> points.at [ifirstSelected];
  329. our startSelection = our endSelection = point -> number;
  330. our ycursor = point -> value;
  331. } else {
  332. /*
  333. * Move crosshair to mouse location.
  334. */
  335. /*our cursor += dt;*/
  336. our ycursor += df;
  337. if (isdefined (v_minimumLegalValue ()) && our ycursor < v_minimumLegalValue ())
  338. our ycursor = v_minimumLegalValue ();
  339. if (isdefined (v_maximumLegalValue ()) && our ycursor > v_maximumLegalValue ())
  340. our ycursor = v_maximumLegalValue ();
  341. }
  342. Editor_broadcastDataChanged (this);
  343. RealTierEditor_updateScaling (this);
  344. return FunctionEditor_UPDATE_NEEDED;
  345. }
  346. void structRealTierEditor :: v_play (double a_tmin, double a_tmax) {
  347. if (our d_sound.data)
  348. Sound_playPart (our d_sound.data, a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  349. }
  350. void RealTierEditor_init (RealTierEditor me, conststring32 title, RealTier data, Sound sound, bool ownSound) {
  351. Melder_assert (data);
  352. Melder_assert (Thing_isa (data, classRealTier));
  353. TimeSoundEditor_init (me, title, data, sound, ownSound);
  354. my ymin = -1.0;
  355. RealTierEditor_updateScaling (me);
  356. my ycursor = 0.382 * my ymin + 0.618 * my ymax;
  357. }
  358. /* End of file RealTierEditor.cpp */