1 /*
2  * Copyright (C) 2002 - David W. Durham
3  *
4  * This file is part of ReZound, an audio editing application.
5  *
6  * ReZound is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * ReZound is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19  */
20 
21 #include "FXRezWaveView.h"
22 
23 #include "FXWaveScrollArea.h"
24 #include "FXPopupHint.h"
25 
26 #include "CSoundFileManager.h"
27 #include "CSoundWindow.h"
28 
29 #include "../backend/CActionParameters.h"
30 #include "../backend/Edits/CCueAction.h"
31 #include "../backend/Edits/CSelectionEdit.h"
32 
33 #include <string>
34 #include <algorithm>
35 
36 #include <istring>
37 
38 #include "settings.h"
39 
40 // --- declaration of FXWaveRuler -----------------------------------------------
41 
42 class FXWaveRuler : public FXComposite
43 {
44 	FXDECLARE(FXWaveRuler)
45 public:
46 	FXWaveRuler(FXComposite *p,FXRezWaveView *parent,CLoadedSound *_loadedSound);
47 	virtual ~FXWaveRuler();
48 
49 	virtual void create();
50 
51 	virtual fx_bool canFocus() const;
52 
53 
54 	size_t getClickedCue(FXint x,FXint y);
55 
56 	// events I get by message
57 
58 	long onPaint(FXObject *object,FXSelector sel,void *ptr);
59 
60 	long onPopupMenu(FXObject *object,FXSelector sel,void *ptr);
61 
62 	long onFindStartPosition(FXObject *object,FXSelector sel,void *ptr);
63 	long onFindStopPosition(FXObject *object,FXSelector sel,void *ptr);
64 
65 	long onSetPositionToCue(FXObject *object,FXSelector sel,void *ptr);
66 	long onAddCue(FXObject *object,FXSelector sel,void *ptr);
67 	long onRemoveCue(FXObject *object,FXSelector sel,void *ptr);
68 	long onEditCue(FXObject *object,FXSelector sel,void *ptr);
69 	long onShowCueList(FXObject *object,FXSelector sel,void *ptr);
70 
71 	long onLeftBtnPress(FXObject *object,FXSelector sel,void *ptr);
72 	long onMouseMove(FXObject *object,FXSelector sel,void *ptr);
73 	long onLeftBtnRelease(FXObject *object,FXSelector sel,void *ptr);
74 
75 	long onFocusIn(FXObject* sender,FXSelector sel,void* ptr);
76 	long onFocusOut(FXObject* sender,FXSelector sel,void* ptr);
77 
78 	long onKeyPress(FXObject *object,FXSelector sel,void *ptr);
79 
80 	// methods get information about and for altering which cue is focused
81 	void focusNextCue();
82 	void focusPrevCue();
83 	void focusFirstCue();
84 	void focusLastCue();
85 
86 	enum
87 	{
88 		ID_FIND_START_POSITION=FXComposite::ID_LAST,
89 		ID_FIND_STOP_POSITION,
90 
91 		ID_SET_START_POSITION,
92 		ID_SET_STOP_POSITION,
93 
94 		ID_ADD_CUE,
95 		ID_ADD_CUE_AT_START_POSITION,
96 		ID_ADD_CUE_AT_STOP_POSITION,
97 		ID_REMOVE_CUE,
98 		ID_EDIT_CUE,
99 
100 		ID_SHOW_CUE_LIST,
101 
102 		ID_POPUP_MENU,
103 
104 		ID_LAST
105 	};
106 
107 protected:
FXWaveRuler()108 	FXWaveRuler() {}
109 
110 private:
111 	FXRezWaveView *parent;
112 
113 	CLoadedSound *loadedSound;
114 	CSound *sound;
115 
116 	FXFont *font;
117 
118 	#define NIL_CUE_INDEX (0xffffffff)
119 	size_t cueClicked; // the index of the cue clicked on holding the value between the click event and the menu item event
120 	int cueClickedOffset; // used when dragging cues to know how far from the middle a cue was clicked
121 	sample_pos_t origCueClickedTime;
122 	sample_pos_t origStartPosition; // start position before drag cue start (for undo purposes)
123 	sample_pos_t origStopPosition; // stop position before drag cue start (for undo purposes)
124 	sample_pos_t addCueTime; // the time in the audio where the mouse was clicked to add a cue if that's what they choose
125 
126 	size_t focusedCueIndex; // NIL_CUE_INDEX if none focused
127 	sample_pos_t focusedCueTime;
128 
129 	FXPopupHint *dragCueHint;
130 
131 	bool draggingSelectionToo; // true when a cue to be dragged was on the start or stop position
132 
133 	CSelectionEditPositionFactory *selectionEditPositionFactory;
134 	CMoveCueActionFactory *moveCueActionFactory;
135 	CRemoveCueActionFactory *removeCueActionFactory;
136 };
137 
138 
139 
140 
141 // --- FXRezWaveView ---------------------------------------------------------
142 
143 FXDEFMAP(FXRezWaveView) FXRezWaveViewMap[]=
144 {
145 	//Message_Type					ID				Message_Handler
146 	//FXMAPFUNC(SEL_TIMEOUT,			FXWindow::ID_AUTOSCROLL,	FXRezWaveView::onAutoScroll),
147 };
148 
FXIMPLEMENT(FXRezWaveView,FXPacker,FXRezWaveViewMap,ARRAYNUMBER (FXRezWaveViewMap))149 FXIMPLEMENT(FXRezWaveView,FXPacker,FXRezWaveViewMap,ARRAYNUMBER(FXRezWaveViewMap))
150 
151 FXRezWaveView::FXRezWaveView(FXComposite* p,CLoadedSound *_loadedSound) :
152 	FXPacker(p,FRAME_NONE|LAYOUT_FILL_X|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0, 0,0),
153 
154 	rulerPanel(new FXWaveRuler(this,this,_loadedSound)),
155 	waveScrollArea(new FXWaveScrollArea(this,_loadedSound))
156 {
157 	enable();
158 }
159 
~FXRezWaveView()160 FXRezWaveView::~FXRezWaveView()
161 {
162 }
163 
setHorzZoom(double v,FXWaveCanvas::HorzRecenterTypes horzRecenterType)164 void FXRezWaveView::setHorzZoom(double v,FXWaveCanvas::HorzRecenterTypes horzRecenterType)
165 {
166 	waveScrollArea->setHorzZoom(v,horzRecenterType);
167 	updateRuler();
168 }
169 
getHorzZoom() const170 double FXRezWaveView::getHorzZoom() const
171 {
172 	return waveScrollArea->getHorzZoom();
173 }
174 
setVertZoom(float v)175 void FXRezWaveView::setVertZoom(float v)
176 {
177 	waveScrollArea->setVertZoom(v);
178 }
179 
getVertZoom() const180 float FXRezWaveView::getVertZoom() const
181 {
182 	return waveScrollArea->getVertZoom();
183 }
184 
setHorzOffset(const sample_pos_t offset)185 void FXRezWaveView::setHorzOffset(const sample_pos_t offset)
186 {
187 	waveScrollArea->setHorzOffset(offset);
188 }
189 
getHorzOffset() const190 sample_pos_t FXRezWaveView::getHorzOffset() const
191 {
192 	return waveScrollArea->getHorzOffset();
193 }
194 
setVertOffset(const int offset)195 void FXRezWaveView::setVertOffset(const int offset)
196 {
197 	waveScrollArea->setVertOffset(offset);
198 }
199 
getVertOffset() const200 int FXRezWaveView::getVertOffset() const
201 {
202 	return waveScrollArea->getVertOffset();
203 }
204 
205 
drawPlayPosition(sample_pos_t dataPosition,bool justErasing,bool scrollToMakeVisible)206 void FXRezWaveView::drawPlayPosition(sample_pos_t dataPosition,bool justErasing,bool scrollToMakeVisible)
207 {
208 	waveScrollArea->drawPlayPosition(dataPosition,justErasing,scrollToMakeVisible);
209 }
210 
redraw()211 void FXRezWaveView::redraw()
212 {
213 	update();
214 	waveScrollArea->redraw();
215 	rulerPanel->update();
216 }
217 
centerStartPos()218 void FXRezWaveView::centerStartPos()
219 {
220 	waveScrollArea->centerStartPos();
221 	rulerPanel->update();
222 }
223 
centerStopPos()224 void FXRezWaveView::centerStopPos()
225 {
226 	waveScrollArea->centerStopPos();
227 	rulerPanel->update();
228 }
229 
showAmount(double seconds,sample_pos_t pos,int marginPixels)230 void FXRezWaveView::showAmount(double seconds,sample_pos_t pos,int marginPixels)
231 {
232 	waveScrollArea->showAmount(seconds,pos,marginPixels);
233 }
234 
updateFromSelectionChange(FXWaveCanvas::LastChangedPositions lastChangedPosition)235 void FXRezWaveView::updateFromSelectionChange(FXWaveCanvas::LastChangedPositions lastChangedPosition)
236 {
237 	waveScrollArea->updateFromSelectionChange(lastChangedPosition);
238 }
239 
updateFromEdit(bool undoing)240 void FXRezWaveView::updateFromEdit(bool undoing)
241 {
242 	waveScrollArea->updateFromEdit(undoing);
243 	update();
244 	rulerPanel->update();
245 }
246 
updateRuler()247 void FXRezWaveView::updateRuler()
248 {
249 	rulerPanel->update();
250 }
251 
updateRulerFromScroll(int deltaX,FXEvent * event)252 void FXRezWaveView::updateRulerFromScroll(int deltaX,FXEvent *event)
253 {
254 	FXEvent e(*event);
255 	e.last_x+=deltaX;
256 	rulerPanel->onMouseMove(NULL,0,&e);
257 }
258 
getSamplePosForScreenX(FXint X) const259 sample_pos_t FXRezWaveView::getSamplePosForScreenX(FXint X) const
260 {
261 	return waveScrollArea->getSamplePosForScreenX(X);
262 }
263 
getWaveSize(int & top,int & height)264 void FXRezWaveView::getWaveSize(int &top,int &height)
265 {
266 	top=waveScrollArea->getY();
267 	height=waveScrollArea->getHeight()-waveScrollArea->horizontalScrollBar()->getHeight();
268 }
269 
270 
271 
272 
273 
274 
275 // --- FXWaveRuler -------------------------------------------------------
276 
277 #include "../backend/CLoadedSound.h"
278 #include "../backend/CSound.h"
279 #include "../backend/CSoundPlayerChannel.h"
280 
281 /*
282  * ??? Split this widget into a base class and a derived class so that the base class could be easily
283  * used above other wave renderings the challenge will be eliminating the need for relying on parent
284  * in the base class
285  */
286 
287 FXDEFMAP(FXWaveRuler) FXWaveRulerMap[]=
288 {
289 	//Message_Type				ID						Message_Handler
290 	FXMAPFUNC(SEL_RIGHTBUTTONPRESS,		0,						FXWaveRuler::onPopupMenu),
291 
292 	FXMAPFUNC(SEL_PAINT,			0,						FXWaveRuler::onPaint),
293 
294 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_FIND_START_POSITION,		FXWaveRuler::onFindStartPosition),
295 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_FIND_STOP_POSITION,		FXWaveRuler::onFindStopPosition),
296 
297 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_SET_START_POSITION,		FXWaveRuler::onSetPositionToCue),
298 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_SET_STOP_POSITION,		FXWaveRuler::onSetPositionToCue),
299 
300 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_ADD_CUE,			FXWaveRuler::onAddCue),
301 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_ADD_CUE_AT_START_POSITION,	FXWaveRuler::onAddCue),
302 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_ADD_CUE_AT_STOP_POSITION,	FXWaveRuler::onAddCue),
303 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_REMOVE_CUE,			FXWaveRuler::onRemoveCue),
304 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_EDIT_CUE,			FXWaveRuler::onEditCue),
305 
306 	FXMAPFUNC(SEL_COMMAND,			FXWaveRuler::ID_SHOW_CUE_LIST,			FXWaveRuler::onShowCueList),
307 
308 	// these handle drag, mouse click for focus, and mouse double click for edit cue(s)
309 	FXMAPFUNC(SEL_LEFTBUTTONPRESS,		0,						FXWaveRuler::onLeftBtnPress),
310 	FXMAPFUNC(SEL_MOTION,			0,						FXWaveRuler::onMouseMove),
311 	FXMAPFUNC(SEL_LEFTBUTTONRELEASE,	0,						FXWaveRuler::onLeftBtnRelease),
312 
313 	FXMAPFUNC(SEL_KEYPRESS,			0,						FXWaveRuler::onKeyPress),
314 
315 
316 	FXMAPFUNC(SEL_FOCUSIN,			0,						FXWaveRuler::onFocusIn),
317 	FXMAPFUNC(SEL_FOCUSOUT,			0,						FXWaveRuler::onFocusOut),
318 
319 };
320 
FXIMPLEMENT(FXWaveRuler,FXComposite,FXWaveRulerMap,ARRAYNUMBER (FXWaveRulerMap))321 FXIMPLEMENT(FXWaveRuler,FXComposite,FXWaveRulerMap,ARRAYNUMBER(FXWaveRulerMap))
322 
323 
324 FXWaveRuler::FXWaveRuler(FXComposite *p,FXRezWaveView *_parent,CLoadedSound *_loadedSound) :
325 	FXComposite(p,FRAME_NONE | LAYOUT_FILL_X | LAYOUT_FIX_HEIGHT, 0,0,0,13+9),
326 
327 	parent(_parent),
328 
329 	loadedSound(_loadedSound),
330 	sound(_loadedSound->sound),
331 
332 	font(getApp()->getNormalFont()),
333 
334 	dragCueHint(new FXPopupHint(p->getApp())),
335 
336 	selectionEditPositionFactory(NULL),
337 	moveCueActionFactory(NULL),
338 	removeCueActionFactory(NULL)
339 {
340 	enable();
341 	flags|=FLAG_SHOWN; // I have to do this, or it will not show up.. like height is 0 or something
342 
343 	FXFontDesc d;
344 	font->getFontDesc(d);
345 	d.weight=FONTWEIGHT_LIGHT;
346 	d.size=65;
347 	font=new FXFont(getApp(),d);
348 
349 	cueClicked=NIL_CUE_INDEX;
350 
351 	focusedCueIndex=NIL_CUE_INDEX;
352 	focusNextCue(); // to make it focus the first one if there is one
353 
354 	dragCueHint->create();
355 
356 	selectionEditPositionFactory=new CSelectionEditPositionFactory;
357 	moveCueActionFactory=new CMoveCueActionFactory;
358 	removeCueActionFactory=new CRemoveCueActionFactory;
359 }
360 
~FXWaveRuler()361 FXWaveRuler::~FXWaveRuler()
362 {
363 	delete font;
364 }
365 
create()366 void FXWaveRuler::create()
367 {
368 	FXComposite::create();
369 	font->create();
370 }
371 
canFocus() const372 fx_bool FXWaveRuler::canFocus() const
373 {
374 	return 1;
375 }
376 
377 #define CUE_Y ((height-1)-(1+CUE_RADIUS))
378 
onPaint(FXObject * object,FXSelector sel,void * ptr)379 long FXWaveRuler::onPaint(FXObject *object,FXSelector sel,void *ptr)
380 {
381 	FXComposite::onPaint(object,sel,ptr);
382 
383 	FXEvent *ev=(FXEvent*)ptr;
384 	FXDCWindow dc(this,ev);
385 
386 	#define LABEL_TICK_FREQ 80
387 	/*
388 	 * Here, we draw the ruler
389 	 * 	We want to always put 1 labled position every LABELED_TICK_FREQ pixels
390 	 * 	And draw ruler ticks also
391 	 */
392 
393 	FXint lastX=parent->waveScrollArea->getCanvasWidth();
394 
395 	const FXint left=0;
396 	const FXint right=left+lastX;
397 
398 	FXint s=ev->rect.x;
399 	s-=LABEL_TICK_FREQ-1;
400 
401 	FXint e=s+ev->rect.w;
402 	e+=LABEL_TICK_FREQ-1;
403 
404 	if(e>lastX)
405 		e=lastX;
406 
407 	dc.setForeground(FXRGB(20,20,20));
408 	dc.compat_setFont(font);
409 	for(FXint x=s;x<=e;x++)
410 	{
411 		if((x%LABEL_TICK_FREQ)==0)
412 		{
413 			dc.drawLine(x,0,x,10);
414 
415 			const string time=sound->getTimePosition(parent->waveScrollArea->getSamplePosForScreenX(x));
416 
417 			dc.drawText(x+2,12,time.c_str(),time.length());
418 		}
419 		else if(((x+(LABEL_TICK_FREQ/2))%(LABEL_TICK_FREQ))==0)
420 			dc.drawLine(x,0,x,4); // half way tick between labled ticks
421 		else if((x%(LABEL_TICK_FREQ/10))==0)
422 			dc.drawLine(x,0,x,2); // small tenth ticks
423 	}
424 
425 	// render cues
426 	const size_t cueCount=sound->getCueCount();
427 	for(size_t t=0;t<cueCount;t++)
428 	{
429 		// ??? I could figure out the min and max screen-visible time and make sure that is in range before testing every cue
430 		FXint cueXPosition=parent->waveScrollArea->getCueScreenX(t);
431 
432 		if(cueXPosition!=CUE_OFF_SCREEN)
433 		{
434 			dc.setForeground(FXRGB(0,0,0));
435 			const string cueName=sound->getCueName(t);
436 			dc.drawText(cueXPosition+CUE_RADIUS+1,height-1,cueName.data(),cueName.size());
437 
438 			dc.drawArc(cueXPosition-CUE_RADIUS,CUE_Y-CUE_RADIUS,CUE_RADIUS*2,CUE_RADIUS*2,0*64,360*64);
439 
440 			dc.setForeground(sound->isCueAnchored(t) ? FXRGB(0,0,255) : FXRGB(255,0,0));
441 			dc.drawLine(cueXPosition-1,height-4,cueXPosition-1,CUE_Y-1);
442 			dc.drawLine(cueXPosition,height-1,cueXPosition,CUE_Y);
443 			dc.drawLine(cueXPosition+1,height-4,cueXPosition+1,CUE_Y-1);
444 
445 			if(hasFocus() && focusedCueIndex!=NIL_CUE_INDEX && focusedCueTime==sound->getCueTime(t))
446 			{	// draw focus rectangle
447 				const int textWidth=font->getTextWidth(cueName.data(),cueName.size());
448 				const int textHeight=font->getTextHeight(cueName.data(),cueName.size());
449 
450 				const int x=cueXPosition-CUE_RADIUS;
451 				const int y=min(height-1-textHeight,CUE_Y-CUE_RADIUS);
452 				const int w=CUE_RADIUS*2+1+textWidth;
453 				const int h=height-y;
454 				dc.drawFocusRectangle(x-1,y+1,w+2,h-1);
455 			}
456 		}
457 	}
458 
459 	return 1;
460 }
461 
getClickedCue(FXint x,FXint y)462 size_t FXWaveRuler::getClickedCue(FXint x,FXint y)
463 {
464 	const size_t cueCount=sound->getCueCount();
465 	size_t cueClicked=NIL_CUE_INDEX;
466 	if(cueCount>0)
467 	{
468 		// go in reverse order so that the more recent one created can be removed first
469 		// I mean, if you accidently create one, then want to remove it, and it's on top
470 		// of another one then it needs to find that more recent one first
471 		for(size_t t=cueCount;t>=1;t--)
472 		{
473 			FXint X=parent->waveScrollArea->getCueScreenX(t-1);
474 			if(X!=CUE_OFF_SCREEN)
475 			{
476 				FXint Y=CUE_Y;
477 
478 				// check distance from clicked point to the cue's position
479 				if( ((x-X)*(x-X))+((y-Y)*(y-Y)) <= ((CUE_RADIUS+1)*(CUE_RADIUS+1)) )
480 				{
481 					cueClickedOffset=x-X;
482 					cueClicked=t-1;
483 					break;
484 				}
485 			}
486 		}
487 	}
488 	return(cueClicked);
489 }
490 
onPopupMenu(FXObject * object,FXSelector sel,void * ptr)491 long FXWaveRuler::onPopupMenu(FXObject *object,FXSelector sel,void *ptr)
492 {
493 	if(!underCursor())
494 		return(1);
495 
496 	// ??? if the parent->target is NULL, I should pop up the windows
497 
498 	FXEvent *event=(FXEvent*)ptr;
499 
500 	// see if any cue was clicked on
501 	cueClicked=getClickedCue(event->win_x,event->win_y); // setting data member for onSetPositionToCue's sake
502 
503 	if(cueClicked<sound->getCueCount())
504 	{
505 		FXMenuPane cueMenu(this);
506 			// ??? make sure that these get deleted when cueMenu is deleted
507 
508 			const sample_fpos_t cueTime=sound->getCueTime(cueClicked);
509 
510 			if(cueTime<loadedSound->channel->getStartPosition())
511 				new FXMenuCommand(&cueMenu,_("Set Start Position to Cue"),NULL,this,ID_SET_START_POSITION);
512 			else if(cueTime>loadedSound->channel->getStopPosition())
513 				new FXMenuCommand(&cueMenu,_("Set Stop Position to Cue"),NULL,this,ID_SET_STOP_POSITION);
514 			else
515 			{
516 				new FXMenuCommand(&cueMenu,_("Set Start Position to Cue"),NULL,this,ID_SET_START_POSITION);
517 				new FXMenuCommand(&cueMenu,_("Set Stop Position to Cue"),NULL,this,ID_SET_STOP_POSITION);
518 			}
519 
520 			new FXMenuSeparator(&cueMenu);
521 			new FXMenuCommand(&cueMenu,_("&Edit Cue"),NULL,this,ID_EDIT_CUE);
522 			new FXMenuCommand(&cueMenu,_("&Remove Cue"),NULL,this,ID_REMOVE_CUE);
523 			new FXMenuSeparator(&cueMenu);
524 			new FXMenuCommand(&cueMenu,_("Show Cues &List"),NULL,this,ID_SHOW_CUE_LIST);
525 
526 		cueMenu.create();
527 		cueMenu.popup(NULL,event->root_x,event->root_y);
528 		getApp()->runModalWhileShown(&cueMenu);
529 	}
530 	else
531 	{
532 		addCueTime=  parent->waveScrollArea->getSamplePosForScreenX(event->win_x);
533 
534 		FXMenuPane gotoMenu(this);
535 			// ??? make sure that these get deleted when gotoMenu is deleted
536 			new FXMenuCommand(&gotoMenu,_("Center Start Position"),NULL,this,ID_FIND_START_POSITION);
537 			new FXMenuCommand(&gotoMenu,_("Center Stop Position"),NULL,this,ID_FIND_STOP_POSITION);
538 			new FXMenuSeparator(&gotoMenu);
539 			new FXMenuCommand(&gotoMenu,_("Add Cue at This Position"),NULL,this,ID_ADD_CUE);
540 			new FXMenuCommand(&gotoMenu,_("Add Cue at Start Position"),NULL,this,ID_ADD_CUE_AT_START_POSITION);
541 			new FXMenuCommand(&gotoMenu,_("Add Cue at Stop Position"),NULL,this,ID_ADD_CUE_AT_STOP_POSITION);
542 			new FXMenuSeparator(&gotoMenu);
543 			new FXMenuCommand(&gotoMenu,_("Show Cues &List"),NULL,this,ID_SHOW_CUE_LIST);
544 
545 		gotoMenu.create();
546 		gotoMenu.popup(NULL,event->root_x,event->root_y);
547 		getApp()->runModalWhileShown(&gotoMenu);
548 	}
549 
550 	cueClicked=NIL_CUE_INDEX;
551 	return 0;
552 }
553 
onFindStartPosition(FXObject * object,FXSelector sel,void * ptr)554 long FXWaveRuler::onFindStartPosition(FXObject *object,FXSelector sel,void *ptr)
555 {
556 	FXWaveScrollArea *wsa=parent->waveScrollArea;
557 	wsa->centerStartPos();
558 	return 1;
559 }
560 
onFindStopPosition(FXObject * object,FXSelector sel,void * ptr)561 long FXWaveRuler::onFindStopPosition(FXObject *object,FXSelector sel,void *ptr)
562 {
563 	FXWaveScrollArea *wsa=parent->waveScrollArea;
564 	wsa->centerStopPos();
565 	return 1;
566 }
567 
onSetPositionToCue(FXObject * object,FXSelector sel,void * ptr)568 long FXWaveRuler::onSetPositionToCue(FXObject *object,FXSelector sel,void *ptr)
569 {
570 	switch(FXSELID(sel))
571 	{
572 	case ID_SET_START_POSITION:
573 		loadedSound->channel->setStartPosition((sample_pos_t)sound->getCueTime(cueClicked));
574 		break;
575 
576 	case ID_SET_STOP_POSITION:
577 		loadedSound->channel->setStopPosition((sample_pos_t)sound->getCueTime(cueClicked));
578 		break;
579 	}
580 
581 	parent->waveScrollArea->updateFromSelectionChange();
582 
583 	return 1;
584 }
585 
onAddCue(FXObject * object,FXSelector sel,void * ptr)586 long FXWaveRuler::onAddCue(FXObject *object,FXSelector sel,void *ptr)
587 {
588 	switch(FXSELID(sel))
589 	{
590 	case ID_ADD_CUE:
591 		if(parent->target)
592 			parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_ADD_CUE),&addCueTime);
593 		break;
594 	case ID_ADD_CUE_AT_START_POSITION:
595 		if(parent->target)
596 			parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_ADD_CUE_AT_START_POSITION),NULL);
597 		break;
598 	case ID_ADD_CUE_AT_STOP_POSITION:
599 		if(parent->target)
600 			parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_ADD_CUE_AT_STOP_POSITION),NULL);
601 		break;
602 	}
603 	update();
604 
605 	return 1;
606 }
607 
onRemoveCue(FXObject * object,FXSelector sel,void * ptr)608 long FXWaveRuler::onRemoveCue(FXObject *object,FXSelector sel,void *ptr)
609 {
610 	if(parent->target)
611 		parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_REMOVE_CUE),&cueClicked);
612 	update();
613 	return 1;
614 }
615 
onEditCue(FXObject * object,FXSelector sel,void * ptr)616 long FXWaveRuler::onEditCue(FXObject *object,FXSelector sel,void *ptr)
617 {
618 	if(parent->target)
619 		parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_EDIT_CUE),&cueClicked);
620 	update();
621 	return 1;
622 }
623 
onShowCueList(FXObject * object,FXSelector sel,void * ptr)624 long FXWaveRuler::onShowCueList(FXObject *object,FXSelector sel,void *ptr)
625 {
626 	if(parent->target)
627 		parent->target->handle(parent,MKUINT(parent->message,FXRezWaveView::SEL_SHOW_CUE_LIST),NULL);
628 	update();
629 	return 1;
630 }
631 
632 /* ??? when I have keyboard event handling for doing something with the focused cue,  make it so that if you press esc while dragging a cue that it moves back to the original location */
onLeftBtnPress(FXObject * object,FXSelector sel,void * ptr)633 long FXWaveRuler::onLeftBtnPress(FXObject *object,FXSelector sel,void *ptr)
634 {
635 	handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
636 
637 	FXEvent* event=(FXEvent*)ptr;
638 	if(event->click_count==1)
639 	{
640 		cueClicked=getClickedCue(event->win_x,event->win_y); // setting data member
641 		if(cueClicked<sound->getCueCount())
642 		{
643 			origCueClickedTime=sound->getCueTime(cueClicked);
644 
645 			origStartPosition=loadedSound->channel->getStartPosition();
646 			origStopPosition=loadedSound->channel->getStopPosition();
647 
648 			draggingSelectionToo=
649 				!(event->state&SHIFTMASK) &&
650 				(origCueClickedTime==loadedSound->channel->getStartPosition() ||
651 				origCueClickedTime==loadedSound->channel->getStopPosition());
652 
653 			dragCueHint->setText(sound->getTimePosition(origCueClickedTime,gSoundFileManager->getSoundWindow(loadedSound)->getZoomDecimalPlaces()).c_str());
654 			dragCueHint->show();
655 			dragCueHint->autoplace();
656 
657 			// set focused cue
658 			focusedCueIndex=cueClicked;
659 			focusedCueTime=sound->getCueTime(focusedCueIndex);
660 			update();
661 		}
662 	}
663 	return 0;
664 }
665 
onMouseMove(FXObject * object,FXSelector sel,void * ptr)666 long FXWaveRuler::onMouseMove(FXObject *object,FXSelector sel,void *ptr)
667 {
668 	FXEvent* event=(FXEvent*)ptr;
669 	if(event->state&LEFTBUTTONMASK && cueClicked<sound->getCueCount())
670 	{	// dragging a cue around
671 
672 		const string cueName=sound->getCueName(cueClicked);
673 		const sample_pos_t cueTime=sound->getCueTime(cueClicked);
674 		const FXint cueTextWidth=font->getTextWidth(cueName.data(),cueName.length());
675 
676 		// last_x is where the cue is on the screen now (possibly after autoscrolling)
677 		const FXint last_x=parent->waveScrollArea->getCueScreenX(cueClicked);
678 
679 		// erase cue at old position
680 		update(last_x-CUE_RADIUS-1,0,CUE_RADIUS*2+1+cueTextWidth+2,getHeight());
681 
682 		// erase vertical cue line at old position
683 		if(gDrawVerticalCuePositions)
684 			parent->waveScrollArea->redraw(last_x,1);
685 
686 		if(object==NULL) // simulated event from auto-scrolling
687 		{
688 			// get where the dragCueHint appears on the wave view canvas
689 			FXint hint_win_x,hint_win_y;
690 			dragCueHint->getParent()->translateCoordinatesTo(hint_win_x,hint_win_y,this,dragCueHint->getX(),dragCueHint->getY());
691 
692 			// redraw what was under the dragCueHint (necessary when auto-scrolling)
693 			parent->waveScrollArea->redraw(hint_win_x+(last_x-event->win_x),dragCueHint->getWidth()+2); /* +2 I guess because of some border? I dunno exactly, but it fixed the prob; except I did still see the problem once when dragging a cue way off to the side and wiggling it around sometimes going back over the window letting it autoscroll ??? */
694 		}
695 
696 
697 		sample_pos_t newTime=parent->waveScrollArea->getSamplePosForScreenX(event->win_x-cueClickedOffset);
698 
699 		if(draggingSelectionToo)
700 		{
701 			const sample_pos_t start=loadedSound->channel->getStartPosition();
702 			const sample_pos_t stop=loadedSound->channel->getStopPosition();
703 
704 			if(cueTime==start)
705 			{	// move start position with cue drag
706 				if(newTime>stop)
707 				{ // dragged cue past stop position
708 					loadedSound->channel->setStopPosition(newTime);
709 					loadedSound->channel->setStartPosition(stop);
710 					parent->updateFromSelectionChange(FXWaveCanvas::lcpStop);
711 				}
712 				else
713 				{
714 					loadedSound->channel->setStartPosition(newTime);
715 					parent->updateFromSelectionChange(FXWaveCanvas::lcpStart);
716 				}
717 
718 			}
719 			else if(cueTime==stop)
720 			{	// move stop position with cue drag
721 				if(newTime<start)
722 				{ // dragged cue before start position
723 					loadedSound->channel->setStartPosition(newTime);
724 					loadedSound->channel->setStopPosition(start);
725 					parent->updateFromSelectionChange(FXWaveCanvas::lcpStart);
726 				}
727 				else
728 				{
729 					loadedSound->channel->setStopPosition(newTime);
730 					parent->updateFromSelectionChange(FXWaveCanvas::lcpStop);
731 				}
732 
733 			}
734 		}
735 
736 		// update cue position
737 		sound->setCueTime(cueClicked,newTime);
738 		if(cueClicked==focusedCueIndex)
739 			focusedCueTime=newTime;
740 
741 		// draw cue at new position
742 		update((event->win_x-cueClickedOffset)-CUE_RADIUS-1,0,CUE_RADIUS*2+1+cueTextWidth+2,getHeight());
743 
744 		// draw vertical cue line at new position
745 		if(gDrawVerticalCuePositions)
746 			parent->waveScrollArea->redraw(event->win_x-cueClickedOffset,1);
747 
748 		dragCueHint->setText(sound->getTimePosition(newTime,gSoundFileManager->getSoundWindow(loadedSound)->getZoomDecimalPlaces()).c_str());
749 		dragCueHint->autoplace();
750 
751 		// have to call canvas->repaint() on real mouse moves because if while autoscrolling a real (that is, object!=NULL) mouse move event occurs, then the window may or may not have been blitted leftward or rightward yet and we will erase the vertical cue position at the wrong position (or something like that, I really had a hard time figuring out the problem that shows up if you omit this call to repaint() )
752 		if(object && gDrawVerticalCuePositions)
753 			parent->waveScrollArea->canvas->repaint();
754 
755 		if(event->win_x<0 || event->win_x>=width)
756 		{ // scroll parent window leftwards or rightwards if mouse is beyond the window edges
757 #if REZ_FOX_VERSION>=10125
758 			parent->waveScrollArea->startAutoScroll(event);
759 #else
760 			parent->waveScrollArea->startAutoScroll(event->win_x,event->win_y);
761 #endif
762 		}
763 
764 	}
765 	return 0;
766 }
767 
onLeftBtnRelease(FXObject * object,FXSelector sel,void * ptr)768 long FXWaveRuler::onLeftBtnRelease(FXObject *object,FXSelector sel,void *ptr)
769 {
770 	FXComposite::handle(object,sel,ptr); // if FXComposite ever starts sending SEL_DOUBLECLICKED events, then the event will happen twice because I'm doing what's below.. detect the version and remove this handler
771 
772 	FXEvent* event=(FXEvent*)ptr;
773 	if(event->click_count==1) //	<-- single click
774 	{
775 		if(cueClicked<sound->getCueCount() && sound->getCueTime(cueClicked)!=origCueClickedTime)
776 		{	// was dragging a cue around
777 			const sample_pos_t newCueTime=sound->getCueTime(cueClicked);
778 
779 			// set back to the orig position
780 			sound->setCueTime(cueClicked,origCueClickedTime);
781 
782 			// set cue to new position except use an AAction object so it goes on the undo stack
783 			CActionParameters actionParameters(NULL);
784 			actionParameters.setValue<unsigned>("index",cueClicked);
785 			actionParameters.setValue<sample_pos_t>("position",newCueTime);
786 			actionParameters.setValue<sample_pos_t>("restoreStartPosition",origStartPosition);
787 			actionParameters.setValue<sample_pos_t>("restoreStopPosition",origStopPosition);
788 			moveCueActionFactory->performAction(loadedSound,&actionParameters,false);
789 		}
790 
791 		dragCueHint->hide();
792 		parent->waveScrollArea->stopAutoScroll();
793 	}
794 	else if(event->click_count==2) //	<-- double click
795 	{
796 		cueClicked=getClickedCue(event->win_x,event->win_y); // setting data member
797 		if(cueClicked<sound->getCueCount())
798 			return onEditCue(object,sel,(void *)cueClicked);
799 		else
800 		{ // clicking not on a cue.. change selection to be that which is between the two nearest cues (or to start or end if there isn't any near on that side)
801 			size_t index;
802 			sample_pos_t startPos=0;
803 			sample_pos_t stopPos=sound->getLength()-1;
804 			sample_pos_t clickedPos=parent->waveScrollArea->getSamplePosForScreenX(event->win_x);
805 
806 			if(sound->findPrevCueInTime(clickedPos,index))
807 				startPos=sound->getCueTime(index);
808 
809 			if(sound->findNextCueInTime(clickedPos,index))
810 				stopPos=sound->getCueTime(index)-1; // select from the position AT the start cue to one less sample prior to the next cue
811 
812 			if(stopPos<startPos) // safety
813 				stopPos=startPos;
814 
815 			CActionParameters actionParameters(NULL);
816 			selectionEditPositionFactory->selectStart=startPos;
817 			selectionEditPositionFactory->selectStop=stopPos;
818 			selectionEditPositionFactory->performAction(loadedSound,&actionParameters,false);
819 			gSoundFileManager->getActiveWindow()->updateFromEdit(); // would prefer to call this for the very window that owns us, but I suppose the user couldn't have clicked it if it wasn't active
820 		}
821 	}
822 
823 	cueClicked=NIL_CUE_INDEX;
824 	return 0;
825 }
826 
onFocusIn(FXObject * sender,FXSelector sel,void * ptr)827 long FXWaveRuler::onFocusIn(FXObject* sender,FXSelector sel,void* ptr){
828 	FXComposite::onFocusIn(sender,sel,ptr);
829 	update();
830 	return 1;
831 }
832 
onFocusOut(FXObject * sender,FXSelector sel,void * ptr)833 long FXWaveRuler::onFocusOut(FXObject* sender,FXSelector sel,void* ptr){
834 	FXComposite::onFocusOut(sender,sel,ptr);
835 	update();
836 	return 1;
837 }
838 
839 /* ???
840  * If possible, make plus and minus nudge the cue to the left and right.. add acceleration
841  * if possible and make the undo action pertain the to last time the key was release for
842  * some given time I guess (because I dont want every little nudge to be undoable)
843  */
844 
onKeyPress(FXObject * object,FXSelector sel,void * ptr)845 long FXWaveRuler::onKeyPress(FXObject *object,FXSelector sel,void *ptr)
846 {
847 	FXEvent* event=(FXEvent*)ptr;
848 	if(event->code==KEY_Left || event->code==KEY_KP_Left)
849 	{
850 		focusPrevCue();
851 	}
852 	else if(event->code==KEY_Right || event->code==KEY_KP_Right)
853 	{
854 		focusNextCue();
855 	}
856 	else if(event->code==KEY_Home || event->code==KEY_KP_Home)
857 	{
858 		focusFirstCue();
859 	}
860 	else if(event->code==KEY_End || event->code==KEY_KP_End)
861 	{
862 		focusLastCue();
863 	}
864 	else if(event->code==KEY_Delete || event->code==KEY_KP_Delete)
865 	{ // delete a cue
866 		if(focusedCueIndex>=sound->getCueCount())
867 			return 0;
868 
869 		const size_t removeCueIndex=focusedCueIndex;
870 
871 		focusNextCue();
872 
873 		CActionParameters actionParameters(NULL);
874 		actionParameters.setValue<unsigned>("index",removeCueIndex);
875 		removeCueActionFactory->performAction(loadedSound,&actionParameters,false);
876 		if(removeCueIndex<focusedCueIndex)
877 			focusedCueIndex--; // decrement since we just remove one below it
878 
879 		if(focusedCueIndex>=sound->getCueCount())
880 			focusLastCue(); // find last cue if we just deleted what was the last cue
881 
882 		if(focusedCueIndex!=NIL_CUE_INDEX)
883 			parent->waveScrollArea->centerTime(focusedCueTime);
884 		parent->waveScrollArea->redraw();
885 		update();
886 		return 1; // handled
887 	}
888 	else if(event->code==KEY_Return)
889 	{ // edit a cue
890 		if(focusedCueIndex>=sound->getCueCount())
891 			return 0;
892 		else
893 			return onEditCue(object,sel,(void *)focusedCueIndex);
894 	}
895 	else
896 	{
897 		if(event->code==KEY_Escape && event->state&LEFTBUTTONMASK && cueClicked<sound->getCueCount())
898 		{ // ESC pressed for cancelling a drag
899 			sound->setCueTime(cueClicked,origCueClickedTime);
900 			dragCueHint->hide();
901 			parent->waveScrollArea->stopAutoScroll();
902 			cueClicked=NIL_CUE_INDEX;
903 			update();
904 			parent->waveScrollArea->redraw();
905 			return 1;
906 		}
907 
908 		return 0;
909 	}
910 
911 	if(focusedCueIndex!=NIL_CUE_INDEX)
912 	{
913 		parent->waveScrollArea->centerTime(focusedCueTime);
914 		update();
915 		return 1; // handled
916 	}
917 	else
918 		return 0;
919 }
920 
focusNextCue()921 void FXWaveRuler::focusNextCue()
922 {
923 	if(focusedCueIndex==NIL_CUE_INDEX)
924 	{
925 		sample_pos_t dummy;
926 		if(!sound->findNearestCue(0,focusedCueIndex,dummy))
927 			focusedCueIndex=NIL_CUE_INDEX;
928 		else
929 			focusedCueTime=sound->getCueTime(focusedCueIndex);
930 	}
931 	else
932 	{
933 		if(sound->findNextCue(focusedCueTime,focusedCueIndex))
934 			focusedCueTime=sound->getCueTime(focusedCueIndex);
935 		else
936 			focusedCueIndex=NIL_CUE_INDEX;
937 	}
938 }
939 
focusPrevCue()940 void FXWaveRuler::focusPrevCue()
941 {
942 	if(focusedCueIndex==NIL_CUE_INDEX)
943 		focusNextCue(); // would implement the same thing here, so just call it
944 	else
945 	{
946 		if(sound->findPrevCue(focusedCueTime,focusedCueIndex))
947 			focusedCueTime=sound->getCueTime(focusedCueIndex);
948 		else
949 			focusedCueIndex=NIL_CUE_INDEX;
950 	}
951 }
952 
focusFirstCue()953 void FXWaveRuler::focusFirstCue()
954 {
955 	sample_pos_t dummy;
956 	if(!sound->findNearestCue(0,focusedCueIndex,dummy))
957 		focusedCueIndex=NIL_CUE_INDEX;
958 	else
959 		focusedCueTime=sound->getCueTime(focusedCueIndex);
960 }
961 
focusLastCue()962 void FXWaveRuler::focusLastCue()
963 {
964 	sample_pos_t dummy;
965 	if(!sound->findNearestCue(sound->getLength()-1,focusedCueIndex,dummy))
966 		focusedCueIndex=NIL_CUE_INDEX;
967 	else
968 		focusedCueTime=sound->getCueTime(focusedCueIndex);
969 }
970 
971