void SeqPhraseMatrixView::ShowProperties(BPoint where)
{
    track_id		trackId = TrackId(where);
    AmPhraseEvent*	pe = NULL;
    SeqSongWinPropertiesI*		win = dynamic_cast<SeqSongWinPropertiesI*>(Window() );
    _SeqPhraseToolTarget*	target;
    if (win && trackId && (target = new _SeqPhraseToolTarget(win, this, &mMtc)) ) {
        // READ SONG BLOCK
#ifdef AM_TRACE_LOCKS
        printf("SeqPhraseMatrixView::ShowProperties() read lock\n");
        fflush(stdout);
#endif
        const AmSong*	song = mSongRef.ReadLock();
        if (song) {
            const AmTrack*	track = song->Track(trackId);
            if (track) {
                pe = target->PhraseEventAt(track, where);
                if (pe) pe->IncRefs();
            }
        }
        mSongRef.ReadUnlock(song);
        // END READ SONG BLOCK
        delete target;
    }
    /* Done this way so I get out of the read lock as soon as
     * possible (and because opening the properties window causes
     * a read lock, and I don't want them nested).
     */
    if (pe) {
        ShowPropertiesWin(pe);
        pe->DecRefs();
    }
}
void AmTrackDataView::DrawPhrase(	BRect clip,
									BView* view,
									track_id trackId,
									const AmPhraseEvent& topPhrase,
									AmPhraseEvent* pe,
									AmTime start,
									AmTime end,
									int32 properties,
									AmSelectionsI* selections)
{
	ArpASSERT(pe && pe->Phrase());
	AmNode*		n = pe->Phrase()->HeadNode();
	if (!n) return;
	AmRange		eventRange = topPhrase.EventRange( n->Event() );
	while (n && eventRange.start <= end) {
		if (eventRange.end >= start) {
			if (mTarget->IsInteresting( n->Event() )) {
				if (selections && selections->IncludesEvent(trackId, &topPhrase, n->Event() ) )
					DrawEvent(view, topPhrase, n->Event(), eventRange, ARPEVENT_SELECTED);
				else
					DrawEvent(view, topPhrase, n->Event(), eventRange, properties);
			} else if (n->Event()->Type() == n->Event()->PHRASE_TYPE) {
				AmPhraseEvent*	pe2 = dynamic_cast<AmPhraseEvent*>( n->Event() );
				if (pe2) DrawPhrase(clip, view, trackId, topPhrase, pe2, start, end, properties, selections);
			}
		}
		n = n->next;
		if (n) eventRange = topPhrase.EventRange( n->Event() );
	}
}
/* This method finds the first event that STARTS after my left edge.
 * events that are overhanging into my left border are ignored.  This
 * is an arbitrary decision, but basically it makes sense to me to do
 * it this way because this method is used to select the first visible
 * event, if there isn't already one.  And if I did it the other way,
 * then that overhanging note might actually be measures away, so as users
 * clicked the right arrow the actual events being selected would be far
 * away.
 */
AmEvent* AmTrackDataView::FirstEvent(const AmTrack* track, AmPhraseEvent** answer) const
{
	ArpASSERT(mTarget);
	const AmPhrase&		phrase = track->Phrases();
	AmTime				time = mTarget->TimeConverter().PixelToTick( Bounds().left );
	AmNode*				phraseNode = phrase.FindNode( time, BACKWARDS_SEARCH );
	if( !phraseNode ) return 0;
	AmPhraseEvent*		pe;
	while (phraseNode != 0) {
		if( (phraseNode->Event()->Type() == phraseNode->Event()->PHRASE_TYPE)
				&& (pe = dynamic_cast<AmPhraseEvent*>( phraseNode->Event() ))
				&& pe->Phrase() ) {
			AmNode*		eventNode = pe->Phrase()->FindNode( time, FORWARDS_SEARCH );
			while( eventNode ) {
				if( mTarget->IsInteresting( eventNode->Event() ) ) {
					*answer = pe;
					return eventNode->Event();
				}
				eventNode = eventNode->next;
			}
		}
		phraseNode = phraseNode->next;
	}
	return 0;
}
void AmTrackDataView::SelectRightEvent()
{
#if 0
	ArpASSERT(mTarget);
	AmSelectionsI*		newSelections = NULL;
	// READ SONG BLOCK
	#ifdef AM_TRACE_LOCKS
	printf("AmTrackDataView::SelectRightEvent() read lock\n"); fflush(stdout);
	#endif
	const AmSong*	song = mSongRef.ReadLock();
	const AmTrack*	track = song ? song->Track(mTrackWinProps.OrderedTrackAt(0)) : NULL;
	if (track) {
		AmSelectionsI*	selections = mTrackWinProps.Selections();
		/* If there are no selections, select the first event
		 * starting on or after the left edge of the window.
		 */
		if (!selections || selections->CountEvents() < 1) {
			newSelections = SelectFirstEvent(track);
		/* If there are already events, select the next event
		 * over from the end of the last selected event.  And if
		 * that event isn't visible, select the first visible event.
		 */
		} else {
			/* Find the event to move right from.
			 */
			AmEvent*		event = 0;
			AmPhraseEvent*	container = 0;
			AmEvent*		iteratingEvent;
			AmPhraseEvent*	iteratingContainer;
			for (uint32 k = 0; selections->EventAt(k, &iteratingContainer, &iteratingEvent) == B_OK; k++) {
				if (!event || (iteratingContainer->EventRange(iteratingEvent).start > container->EventRange(event).start) ) {
					event = iteratingEvent;
					container = iteratingContainer;
				}
			}
			if (!event) newSelections = SelectFirstEvent(track);
			else {
				/* Select the next event over.  This has a lot of problems,
				 * like not dealing with operlapping phrases.
				 */
				if (NextRightEvent(track, event, container, &iteratingEvent, &iteratingContainer) == B_OK)
					newSelections = SelectEvent(iteratingEvent, iteratingContainer);
				else newSelections = SelectFirstEvent(track);
			}
		}

	}
	mSongRef.ReadUnlock(song);
	// END READ SONG BLOCK
	if (newSelections) mTrackWinProps.SetSelections(newSelections);
#endif
}
static void control_report(const AmPhrase* phrase, _AmControlEntry& entry)
{
	if (!phrase) return;
	AmNode*		node = phrase->HeadNode();
	while (node) {
		if (node->Event() ) {
			if (node->Event()->Type() == node->Event()->PHRASE_TYPE) {
				AmPhraseEvent*	pe = dynamic_cast<AmPhraseEvent*>(node->Event() );
				if (pe) control_report(pe->Phrase(), entry);
			} else if (node->Event()->Type() == node->Event()->CONTROLCHANGE_TYPE) {
				AmControlChange*	cc = dynamic_cast<AmControlChange*>(node->Event() );
				if (cc) entry.AddControl(cc->ControlNumber() );
			}
		}
		node = node->next;
	}
}
static void program_report(const AmPhrase* phrase, _AmProgramEntry& entry)
{
	if (!phrase) return;
	AmNode*		node = phrase->HeadNode();
	while (node) {
		if ( node->Event() ) {
			if (node->Event()->Type() == node->Event()->PHRASE_TYPE) {
				AmPhraseEvent*	pe = dynamic_cast<AmPhraseEvent*>( node->Event() );
				if (pe) program_report(pe->Phrase(), entry);
			} else if (node->Event()->Type() == node->Event()->PROGRAMCHANGE_TYPE) {
				AmProgramChange*	pc = dynamic_cast<AmProgramChange*>( node->Event() );
				if (pc) entry.AddProgram( pc->ProgramNumber() );
			}
		}
		node = node->next;
	}
}
static void _control_report(const AmPhrase* phrase, uint8* active)
{
	if (!phrase) return;
	AmNode*		node = phrase->HeadNode();
	while (node) {
		if (node->Event() ) {
			if (node->Event()->Type() == node->Event()->PHRASE_TYPE) {
				AmPhraseEvent*	pe = dynamic_cast<AmPhraseEvent*>(node->Event() );
				if (pe) _control_report(pe->Phrase(), active);
			} else if (node->Event()->Type() == node->Event()->CONTROLCHANGE_TYPE) {
				AmControlChange*	cc = dynamic_cast<AmControlChange*>(node->Event() );
				if (cc) active[cc->ControlNumber()] = 1;
			}
		}
		node = node->next;
	}
}
void _AmControlTarget::GetMoveValues(	const AmPhraseEvent& topPhrase,
										const AmEvent* event,
										AmTime* x, int32* y) const
{
	ArpASSERT(event);
	*x = topPhrase.EventRange(event).start;
	const AmControlChange*		ccEvent = dynamic_cast<const AmControlChange*>(event);
	if (ccEvent) *y = ccEvent->ControlValue();
	else *y = 0;
}
void AmTrackDataView::DrawTrack(const AmTrack* track,
								BRect clip,
								BView* view,
								int32 properties,
								AmSelectionsI* selections)
{
	AmTime		start = mMtc.PixelToTick(clip.left - 2);
	AmTime		end = mMtc.PixelToTick(clip.right + 2);
	AmNode*		n = track->Phrases().HeadNode();
	while (n && n->Event() && n->StartTime() <= end) {
		if (n->EndTime() >= start) {
			if (n->Event()->Type() == n->Event()->PHRASE_TYPE) {
				AmPhraseEvent*	pe = dynamic_cast<AmPhraseEvent*>( n->Event() );
				if (pe && pe->Phrase() ) {
					mEventColor = pe->Phrase()->Color(AmPhrase::FOREGROUND_C);
					mix_in(mEventColor, AmPrefs().Color(AM_DATA_BG_C), 0.75, mLowEventColor);
					DrawPhrase(clip, view, track->Id(), *pe, pe, start, end, properties, selections);
				}
			}
		}
		n = n->next;
	}
}
void _AmControlTarget::SetMove(	AmPhraseEvent& topPhrase,
								AmEvent* event,
								AmTime originalX, int32 originalY,
								AmTime deltaX, int32 deltaY,
								uint32 flags)
{
	ArpASSERT(event);
	AmControlChange*		ccEvent = dynamic_cast<AmControlChange*>(event);
	if (!ccEvent) return;
//	if (flags&TRANSFORM_X) {

	AmTime			newStart = originalX + deltaX;
	if (newStart < 0) newStart = 0;
	if (newStart != topPhrase.EventRange(event).start)
		topPhrase.SetEventStartTime(ccEvent, newStart);

//	if (flags&TRANSFORM_Y) {
		int32			newVal = originalY + deltaY;
		if (newVal > 127) newVal = 127;
		else if (newVal < 0) newVal = 0;
		ccEvent->SetControlValue(newVal);
//	}
}