/*****************************************************************************
 * MediaControlView.cpp: beos interface
 *****************************************************************************
 * Copyright (C) 1999, 2000, 2001 VideoLAN
 * $Id: MediaControlView.cpp,v 1.15 2003/02/03 17:18:48 stippi Exp $
 *
 * Authors: Tony Castley <tony@castley.net>
 *          Stephan Aßmus <stippi@yellowbites.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/* System headers */
#include <InterfaceKit.h>
#include <AppKit.h>
#include <String.h>
#include <string.h>

/* VLC headers */
#include <vlc/vlc.h>
#include <vlc/intf.h>

/* BeOS interface headers */
#include "VlcWrapper.h"
#include "Bitmaps.h"
#include "DrawingTidbits.h"
#include "InterfaceWindow.h"
#include "MsgVals.h"
#include "TransportButton.h"
#include "ListViews.h"
#include "MediaControlView.h"

#define BORDER_INSET 6.0
#define MIN_SPACE 4.0
#define SPEAKER_SLIDER_DIST 6.0
#define VOLUME_MIN_WIDTH 70.0
#define DIM_LEVEL 0.4
#define VOLUME_SLIDER_LAYOUT_WEIGHT 2.0
#define SEEK_SLIDER_KNOB_WIDTH 8.0

// slider colors are hardcoded here, because that's just
// what they currently are within those bitmaps
const rgb_color kGreen = (rgb_color){ 152, 203, 152, 255 };
const rgb_color kGreenShadow = (rgb_color){ 102, 152, 102, 255 };
const rgb_color kBackground = (rgb_color){ 216, 216, 216, 255 };
const rgb_color kSeekGreen = (rgb_color){ 171, 221, 161, 255 };
const rgb_color kSeekGreenShadow = (rgb_color){ 144, 186, 136, 255 };
const rgb_color kSeekRed = (rgb_color){ 255, 0, 0, 255 };
const rgb_color kSeekRedLight = (rgb_color){ 255, 152, 152, 255 };
const rgb_color kSeekRedShadow = (rgb_color){ 178, 0, 0, 255 };

const char* kDisabledSeekMessage = "Drop files to play";

enum
{
	MSG_REWIND				= 'rwnd',
	MSG_FORWARD				= 'frwd',
	MSG_SKIP_BACKWARDS		= 'skpb',
	MSG_SKIP_FORWARD		= 'skpf',
};

// constructor
MediaControlView::MediaControlView(BRect frame, intf_thread_t *p_interface)
	: BBox(frame, NULL, B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED,
		   B_PLAIN_BORDER),
      fScrubSem(B_ERROR),
      fCurrentRate(DEFAULT_RATE),
      fCurrentStatus(UNDEF_S),
      fBottomControlHeight(0.0),
      fIsEnabled( true )
{
    p_intf = p_interface;

	BRect frame(0.0, 0.0, 10.0, 10.0);
	
    // Seek Slider
    fSeekSlider = new SeekSlider(frame, "seek slider", this,
                                 0, SEEKSLIDER_RANGE - 1);
    fSeekSlider->SetValue(0);
    fSeekSlider->ResizeToPreferred();
    AddChild( fSeekSlider );

    // Buttons
    // Skip Back
    frame.SetRightBottom(kSkipButtonSize);
	fBottomControlHeight = kRewindBitmapHeight - 1.0;
    fSkipBack = new TransportButton(frame, B_EMPTY_STRING,
                                    kSkipBackBitmapBits,
                                    kPressedSkipBackBitmapBits,
                                    kDisabledSkipBackBitmapBits,
                                    new BMessage(MSG_SKIP_BACKWARDS));
    AddChild( fSkipBack );

	// Play Pause
    frame.SetRightBottom(kPlayButtonSize);
	if (fBottomControlHeight < kPlayPauseBitmapHeight - 1.0)
		fBottomControlHeight = kPlayPauseBitmapHeight - 1.0;
    fPlayPause = new PlayPauseButton(frame, B_EMPTY_STRING,
                                     kPlayButtonBitmapBits,
                                     kPressedPlayButtonBitmapBits,
                                     kDisabledPlayButtonBitmapBits,
                                     kPlayingPlayButtonBitmapBits,
                                     kPressedPlayingPlayButtonBitmapBits,
                                     kPausedPlayButtonBitmapBits,
                                     kPressedPausedPlayButtonBitmapBits,
                                     new BMessage(START_PLAYBACK));

    AddChild( fPlayPause );

    // Skip Foward
    frame.SetRightBottom(kSkipButtonSize);
    fSkipForward = new TransportButton(frame, B_EMPTY_STRING,
                                       kSkipForwardBitmapBits,
                                       kPressedSkipForwardBitmapBits,
                                       kDisabledSkipForwardBitmapBits,
                                       new BMessage(MSG_SKIP_FORWARD));
    AddChild( fSkipForward );

	// Forward
	fForward = new TransportButton(frame, B_EMPTY_STRING,
								   kForwardBitmapBits,
								   kPressedForwardBitmapBits,
								   kDisabledForwardBitmapBits,
								   new BMessage(MSG_FORWARD));
//	AddChild( fForward );

	// Rewind
	fRewind = new TransportButton(frame, B_EMPTY_STRING,
								  kRewindBitmapBits,
								  kPressedRewindBitmapBits,
								  kDisabledRewindBitmapBits,
								  new BMessage(MSG_REWIND));
//	AddChild( fRewind );

    // Stop
    frame.SetRightBottom(kStopButtonSize);
	if (fBottomControlHeight < kStopBitmapHeight - 1.0)
		fBottomControlHeight = kStopBitmapHeight - 1.0;
    fStop = new TransportButton(frame, B_EMPTY_STRING,
                                kStopButtonBitmapBits,
                                kPressedStopButtonBitmapBits,
                                kDisabledStopButtonBitmapBits,
                                new BMessage(STOP_PLAYBACK));
	AddChild( fStop );

	// Mute
    frame.SetRightBottom(kSpeakerButtonSize);
	if (fBottomControlHeight < kSpeakerIconBitmapHeight - 1.0)
		fBottomControlHeight = kSpeakerIconBitmapHeight - 1.0;
    fMute = new TransportButton(frame, B_EMPTY_STRING,
                                kSpeakerIconBits,
                                kPressedSpeakerIconBits,
                                kSpeakerIconBits,
                                new BMessage(VOLUME_MUTE));

	AddChild( fMute );

    // Volume Slider
	fVolumeSlider = new VolumeSlider(BRect(0.0, 0.0, VOLUME_MIN_WIDTH,
										   kVolumeSliderBitmapHeight - 1.0),
									 "volume slider", 1, AOUT_VOLUME_MAX,
									 new BMessage(VOLUME_CHG));
	fVolumeSlider->SetValue(AOUT_VOLUME_DEFAULT);
	AddChild( fVolumeSlider );
	
	// Position Info View
    fPositionInfo = new PositionInfoView(BRect(0.0, 0.0, 10.0, 10.0), "led",
                                         p_intf);
    fPositionInfo->ResizeToPreferred();
    AddChild( fPositionInfo );
}

// destructor
MediaControlView::~MediaControlView()
{
}

// AttachedToWindow
void
MediaControlView::AttachedToWindow()
{
	// we are now a valid BHandler
	fRewind->SetTarget(this);
	fForward->SetTarget(this);
	fSkipBack->SetTarget(this);
	fSkipForward->SetTarget(this);
	fVolumeSlider->SetTarget(Window());

	BRect r(_MinFrame());
	if (BMenuBar* menuBar = Window()->KeyMenuBar())
		r.bottom += menuBar->Bounds().Height();

	Window()->SetSizeLimits(r.Width(), r.Width() * 1.8, r.Height(), r.Height() * 1.3);
	if (!Window()->Bounds().Contains(r))
		Window()->ResizeTo(r.Width(), r.Height());
	else
		FrameResized(Bounds().Width(), Bounds().Height());

	// get pulse message every two frames
	Window()->SetPulseRate(80000);
}

// FrameResized
void
MediaControlView::FrameResized(float width, float height)
{
	BRect r(Bounds());
	// make sure we don't leave dirty pixels
	// (B_FULL_UPDATE_ON_RESIZE == annoying flicker -> this is smarter)
	if (fOldBounds.Width() < r.Width())
		Invalidate(BRect(fOldBounds.right, fOldBounds.top + 1.0,
						 fOldBounds.right, fOldBounds.bottom - 1.0));
	else
		Invalidate(BRect(r.right, r.top + 1.0,
						 r.right, r.bottom - 1.0));
	if (fOldBounds.Height() < r.Height())
		Invalidate(BRect(fOldBounds.left + 1.0, fOldBounds.bottom,
						 fOldBounds.right - 1.0, fOldBounds.bottom));
	else
		Invalidate(BRect(r.left + 1.0, r.bottom,
						 r.right - 1.0, r.bottom));
	// remember for next time
	fOldBounds = r;
	// layout controls
	r.InsetBy(BORDER_INSET, BORDER_INSET);
	_LayoutControls(r);
}

// GetPreferredSize
void
MediaControlView::GetPreferredSize(float* width, float* height)
{
	if (width && height)
	{
		BRect r(_MinFrame());
		*width = r.Width();
		*height = r.Height();
	}
}

// MessageReceived
void
MediaControlView::MessageReceived(BMessage* message)
{
	switch (message->what)
	{
		case MSG_REWIND:
			break;
		case MSG_FORWARD:
			break;
		case MSG_SKIP_BACKWARDS:
			Window()->PostMessage(NAVIGATE_PREV);
			break;
		case MSG_SKIP_FORWARD:
			Window()->PostMessage(NAVIGATE_NEXT);
			break;
		default:
		    BBox::MessageReceived(message);
		    break;
	}
}

// Pulse
void
MediaControlView::Pulse()
{
	InterfaceWindow* window = dynamic_cast<InterfaceWindow*>(Window());
	if (window && window->IsStopped())
			fPlayPause->SetStopped();
}

// SetProgress
void
MediaControlView::SetProgress( float position )
{
	fSeekSlider->SetPosition( position );
}

// SetStatus
void
MediaControlView::SetStatus(int status, int rate)
{
	// we need to set the button status periodically
	// (even if it is the same) to get a blinking button
	fCurrentStatus = status;
    switch( status )
    {
        case PLAYING_S:
        case FORWARD_S:
        case BACKWARD_S:
        case START_S:
            fPlayPause->SetPlaying();
            break;
        case PAUSE_S:
            fPlayPause->SetPaused();
            break;
        case UNDEF_S:
        case NOT_STARTED_S:
        default:
            fPlayPause->SetStopped();
            break;
    }
	if (rate != fCurrentRate)
	{
		fCurrentRate = rate;
	    if ( rate < DEFAULT_RATE )
	    {
	    	// TODO: ...
	    }
	}
}

// SetEnabled
void
MediaControlView::SetEnabled(bool enabled)
{
    if( ( enabled && fIsEnabled ) ||
        ( !enabled && !fIsEnabled ) )
    {
        /* do not redraw if it is not necessary */
        return;
    }
    
	if( LockLooper() )
	{
		fSkipBack->SetEnabled( enabled );
		fPlayPause->SetEnabled( enabled );
		fSkipForward->SetEnabled( enabled );
		fStop->SetEnabled( enabled );
		fMute->SetEnabled( enabled );
		fVolumeSlider->SetEnabled( enabled );
		fSeekSlider->SetEnabled( enabled );
		fRewind->SetEnabled( enabled );
		fForward->SetEnabled( enabled );
		UnlockLooper();
		fIsEnabled = enabled;
	}
}

// SetAudioEnabled
void
MediaControlView::SetAudioEnabled(bool enabled)
{
	fMute->SetEnabled(enabled);
	fVolumeSlider->SetEnabled(enabled);
}

// GetSeekTo
uint32
MediaControlView::GetSeekTo() const
{
	return fSeekSlider->Value();
}

// GetVolume
uint32
MediaControlView::GetVolume() const
{
	return fVolumeSlider->Value();
}

// SetSkippable
void
MediaControlView::SetSkippable(bool backward, bool forward)
{
	fSkipBack->SetEnabled(backward);
	fSkipForward->SetEnabled(forward);
}

// SetMuted
void
MediaControlView::SetMuted(bool mute)
{
	fVolumeSlider->SetMuted(mute);
}

// _LayoutControls
void
MediaControlView::_LayoutControls(BRect frame) const
{
	// seek slider
	BRect r(frame);
	// calculate absolutly minimal width
	float minWidth = fSkipBack->Bounds().Width();
//	minWidth += fRewind->Bounds().Width();
	minWidth += fStop->Bounds().Width();
	minWidth += fPlayPause->Bounds().Width();
//	minWidth += fForward->Bounds().Width();
	minWidth += fSkipForward->Bounds().Width();
	minWidth += fMute->Bounds().Width();
	minWidth += VOLUME_MIN_WIDTH;
	
	// layout time slider and info view
    float width, height;
    fPositionInfo->GetBigPreferredSize( &width, &height );
    float ratio = width / height;
    width = r.Height() * ratio;
    if (frame.Width() - minWidth - MIN_SPACE >= width
              && frame.Height() >= height)
    {
        r.right = r.left + width;
        fPositionInfo->SetMode(PositionInfoView::MODE_BIG);
        _LayoutControl(fPositionInfo, r, true, true);
        frame.left = r.right + MIN_SPACE;
        r.left = frame.left;
        r.right = frame.right;
    //    r.bottom = r.top + r.Height() / 2.0 - MIN_SPACE / 2.0;
        r.bottom = r.top + fSeekSlider->Bounds().Height();
        _LayoutControl(fSeekSlider, r, true);
    }
    else
    {
        fPositionInfo->GetPreferredSize( &width, &height );
        fPositionInfo->SetMode(PositionInfoView::MODE_SMALL);
        fPositionInfo->ResizeTo(width, height);
        r.bottom = r.top + r.Height() / 2.0 - MIN_SPACE / 2.0;
        r.right = r.left + fPositionInfo->Bounds().Width();
        _LayoutControl(fPositionInfo, r, true );
        r.left = r.right + MIN_SPACE;
        r.right = frame.right;
        _LayoutControl(fSeekSlider, r, true);
    }
	float currentWidth = frame.Width();
	float space = (currentWidth - minWidth) / 6.0;//8.0;
	// apply weighting
	space = MIN_SPACE + (space - MIN_SPACE) / VOLUME_SLIDER_LAYOUT_WEIGHT;
	// layout controls with "space" inbetween
	r.left = frame.left;
	r.top = r.bottom + MIN_SPACE + 1.0;
	r.bottom = frame.bottom;
	// skip back
	r.right = r.left + fSkipBack->Bounds().Width();
	_LayoutControl(fSkipBack, r);
	// rewind
//	r.left = r.right + space;
//	r.right = r.left + fRewind->Bounds().Width();
//	_LayoutControl(fRewind, r);
	// stop
	r.left = r.right + space;
	r.right = r.left + fStop->Bounds().Width();
	_LayoutControl(fStop, r);
	// play/pause
	r.left = r.right + space;
	r.right = r.left + fPlayPause->Bounds().Width();
	_LayoutControl(fPlayPause, r);
	// forward
//	r.left = r.right + space;
//	r.right = r.left + fForward->Bounds().Width();
//	_LayoutControl(fForward, r);
	// skip forward
	r.left = r.right + space;
	r.right = r.left + fSkipForward->Bounds().Width();
	_LayoutControl(fSkipForward, r);
	// speaker icon
	r.left = r.right + space + space;
	r.right = r.left + fMute->Bounds().Width();
	_LayoutControl(fMute, r);
	// volume slider
	r.left = r.right + SPEAKER_SLIDER_DIST; // keep speaker icon and volume slider attached
	r.right = frame.right;
	_LayoutControl(fVolumeSlider, r, true);
}

// _MinFrame
BRect           
MediaControlView::_MinFrame() const
{
	// add up width of controls along bottom (seek slider will likely adopt)
	float minWidth = 2 * BORDER_INSET;
	minWidth += fSkipBack->Bounds().Width() + MIN_SPACE;
//	minWidth += fRewind->Bounds().Width() + MIN_SPACE;
	minWidth += fStop->Bounds().Width() + MIN_SPACE;
	minWidth += fPlayPause->Bounds().Width() + MIN_SPACE;
//	minWidth += fForward->Bounds().Width() + MIN_SPACE;
	minWidth += fSkipForward->Bounds().Width() + MIN_SPACE + MIN_SPACE;
	minWidth += fMute->Bounds().Width() + SPEAKER_SLIDER_DIST;
	minWidth += VOLUME_MIN_WIDTH;

	// add up height of seek slider and heighest control on bottom
	float minHeight = 2 * BORDER_INSET;
	minHeight += fSeekSlider->Bounds().Height() + MIN_SPACE + MIN_SPACE / 2.0;
	minHeight += fBottomControlHeight;
	return BRect(0.0, 0.0, minWidth - 1.0, minHeight - 1.0);
}

// _LayoutControl
void
MediaControlView::_LayoutControl(BView* view, BRect frame,
                                 bool resizeWidth, bool resizeHeight) const
{
    if (!resizeHeight)
	    // center vertically
	    frame.top = (frame.top + frame.bottom) / 2.0 - view->Bounds().Height() / 2.0;
	if (!resizeWidth)
	    //center horizontally
		frame.left = (frame.left + frame.right) / 2.0 - view->Bounds().Width() / 2.0;
	view->MoveTo(frame.LeftTop());
	float width = resizeWidth ? frame.Width() : view->Bounds().Width();
	float height = resizeHeight ? frame.Height() : view->Bounds().Height();
    if (resizeWidth || resizeHeight)
        view->ResizeTo(width, height);
}



/*****************************************************************************
 * SeekSlider
 *****************************************************************************/
SeekSlider::SeekSlider(BRect frame, const char* name, MediaControlView *owner,
					   int32 minValue, int32 maxValue)
	: BControl(frame, name, NULL, NULL, B_FOLLOW_NONE,
			   B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
	  fOwner(owner),
	  fTracking(false),
	  fMinValue(minValue),
	  fMaxValue(maxValue)
{
	BFont font(be_plain_font);
	font.SetSize(9.0);
	SetFont(&font);
}

SeekSlider::~SeekSlider()
{
	_EndSeek();
}

/*****************************************************************************
 * VolumeSlider::AttachedToWindow
 *****************************************************************************/
void
SeekSlider::AttachedToWindow()
{
	BControl::AttachedToWindow();
	SetViewColor(B_TRANSPARENT_32_BIT);
}

/*****************************************************************************
 * VolumeSlider::Draw
 *****************************************************************************/
void
SeekSlider::Draw(BRect updateRect)
{
	BRect r(Bounds());
	float knobWidth2 = SEEK_SLIDER_KNOB_WIDTH / 2.0;
	float sliderStart = (r.left + knobWidth2);
	float sliderEnd = (r.right - knobWidth2);
	float knobPos = sliderStart
					+ floorf((sliderEnd - sliderStart - 1.0) * (Value() - fMinValue)
					/ (fMaxValue - fMinValue) + 0.5);
	// draw both sides (the original from Be doesn't seem
	// to make a difference for enabled/disabled state)
//	DrawBitmapAsync(fLeftSideBits, r.LeftTop());
//	DrawBitmapAsync(fRightSideBits, BPoint(sliderEnd + 1.0, r.top));
	// colors for the slider area between the two bitmaps
	rgb_color background = kBackground;//ui_color(B_PANEL_BACKGROUND_COLOR);
	rgb_color shadow = tint_color(background, B_DARKEN_2_TINT);
	rgb_color softShadow = tint_color(background, B_DARKEN_1_TINT);
	rgb_color darkShadow = tint_color(background, B_DARKEN_4_TINT);
	rgb_color midShadow = tint_color(background, B_DARKEN_3_TINT);
	rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT);
	rgb_color softLight = tint_color(background, B_LIGHTEN_1_TINT);
	rgb_color green = kSeekGreen;
	rgb_color greenShadow = kSeekGreenShadow;
	rgb_color black = kBlack;
	rgb_color dotGrey = midShadow;
	rgb_color dotGreen = greenShadow;
	// draw frame
	_StrokeFrame(r, softShadow, softShadow, softLight, softLight);
	r.InsetBy(1.0, 1.0);
	_StrokeFrame(r, black, black, light, light);
	if (IsEnabled())
	{
		r.InsetBy(1.0, 1.0);
		// inner shadow
		_StrokeFrame(r, greenShadow, greenShadow, green, green);
		r.top++;
		r.left++;
		_StrokeFrame(r, greenShadow, greenShadow, green, green);
		// inside area
		r.InsetBy(1.0, 1.0);
		SetHighColor(green);
		FillRect(r);
		// dots
		int32 dotCount = (int32)(r.Width() / 6.0);
		BPoint dotPos;
		dotPos.y = r.top + 2.0;
		SetHighColor(dotGreen);
		for (int32 i = 0; i < dotCount; i++)
		{
			dotPos.x = sliderStart + i * 6.0 + 5.0;
			StrokeLine(dotPos, BPoint(dotPos.x, dotPos.y + 6.0));
		}
		// slider handle
		r.top -= 4.0;
		r.bottom += 3.0;
		r.left = knobPos - knobWidth2;
		r.right = knobPos + knobWidth2;
		// black outline
		float handleBottomSize = 2.0;
		float handleArrowSize = 6.0;
		BeginLineArray(10);
			// upper handle
			AddLine(BPoint(r.left, r.top + handleBottomSize),
					BPoint(r.left, r.top), black);
			AddLine(BPoint(r.left + 1.0, r.top),
					BPoint(r.right, r.top), black);
			AddLine(BPoint(r.right, r.top + 1.0),
					BPoint(r.right, r.top + handleBottomSize), black);
			AddLine(BPoint(r.right - 1.0, r.top + handleBottomSize + 1.0),
					BPoint(knobPos, r.top + handleArrowSize), black);
			AddLine(BPoint(knobPos - 1.0, r.top + handleArrowSize - 1.0),
					BPoint(r.left + 1.0, r.top + handleBottomSize + 1.0), black);
			// lower handle
			AddLine(BPoint(r.left, r.bottom),
					BPoint(r.left, r.bottom - handleBottomSize), black);
			AddLine(BPoint(r.left + 1.0, r.bottom - handleBottomSize - 1.0),
					BPoint(knobPos, r.bottom - handleArrowSize), black);
			AddLine(BPoint(knobPos + 1.0, r.bottom - handleArrowSize + 1.0),
					BPoint(r.right, r.bottom - handleBottomSize), black);
			AddLine(BPoint(r.right, r.bottom - handleBottomSize + 1.0),
					BPoint(r.right, r.bottom), black);
			AddLine(BPoint(r.right - 1.0, r.bottom),
					BPoint(r.left + 1.0, r.bottom), black);
		EndLineArray();
		// inner red light and shadow lines
		r.InsetBy(1.0, 1.0);
		handleBottomSize--;
		handleArrowSize -= 2.0;
		BeginLineArray(10);
			// upper handle
			AddLine(BPoint(r.left, r.top + handleBottomSize),
					BPoint(r.left, r.top), kSeekRedLight);
			AddLine(BPoint(r.left + 1.0, r.top),
					BPoint(r.right, r.top), kSeekRedLight);
			AddLine(BPoint(r.right, r.top + 1.0),
					BPoint(r.right, r.top + handleBottomSize), kSeekRedShadow);
			AddLine(BPoint(r.right - 1.0, r.top + handleBottomSize + 1.0),
					BPoint(knobPos, r.top + handleArrowSize), kSeekRedShadow);
			AddLine(BPoint(knobPos - 1.0, r.top + handleArrowSize - 1.0),
					BPoint(r.left + 1.0, r.top + handleBottomSize + 1.0), kSeekRedLight);
			// lower handle
			AddLine(BPoint(r.left, r.bottom),
					BPoint(r.left, r.bottom - handleBottomSize), kSeekRedLight);
			AddLine(BPoint(r.left + 1.0, r.bottom - handleBottomSize - 1.0),
					BPoint(knobPos, r.bottom - handleArrowSize), kSeekRedLight);
			AddLine(BPoint(knobPos + 1.0, r.bottom - handleArrowSize + 1.0),
					BPoint(r.right, r.bottom - handleBottomSize), kSeekRedShadow);
			AddLine(BPoint(r.right, r.bottom - handleBottomSize + 1.0),
					BPoint(r.right, r.bottom), kSeekRedShadow);
			AddLine(BPoint(r.right - 1.0, r.bottom),
					BPoint(r.left + 1.0, r.bottom), kSeekRedShadow);
		EndLineArray();
		// fill rest of handles with red
		SetHighColor(kSeekRed);
		r.InsetBy(1.0, 1.0);
		handleArrowSize -= 2.0;
		BPoint arrow[3];
		// upper handle arrow
		arrow[0].x = r.left;
		arrow[0].y = r.top;
		arrow[1].x = r.right;
		arrow[1].y = r.top;
		arrow[2].x = knobPos;
		arrow[2].y = r.top + handleArrowSize;
		FillPolygon(arrow, 3);
		// lower handle arrow
		arrow[0].x = r.left;
		arrow[0].y = r.bottom;
		arrow[1].x = r.right;
		arrow[1].y = r.bottom;
		arrow[2].x = knobPos;
		arrow[2].y = r.bottom - handleArrowSize;
		FillPolygon(arrow, 3);
	}
	else
	{
		r.InsetBy(1.0, 1.0);
		_StrokeFrame(r, darkShadow, darkShadow, darkShadow, darkShadow);
		r.InsetBy(1.0, 1.0);
		_StrokeFrame(r, darkShadow, darkShadow, darkShadow, darkShadow);
		r.InsetBy(1.0, 1.0);
		SetHighColor(darkShadow);
		SetLowColor(shadow);
		// stripes
		float width = floorf(StringWidth(kDisabledSeekMessage));
		float textPos = r.left + r.Width() / 2.0 - width / 2.0;
		pattern stripes = {{ 0xc7, 0x8f, 0x1f, 0x3e, 0x7c, 0xf8, 0xf1, 0xe3 }};
		BRect stripesRect(r);
		stripesRect.right = textPos - 5.0;
		FillRect(stripesRect, stripes);
		stripesRect.left = textPos + width + 3.0;
		stripesRect.right = r.right;
		FillRect(stripesRect, stripes);
		// info text
		r.left = textPos - 4.0;
		r.right = textPos + width + 2.0;
		FillRect(r);
		SetHighColor(shadow);
		SetLowColor(darkShadow);
		font_height fh;
		GetFontHeight(&fh);
		DrawString(kDisabledSeekMessage, BPoint(textPos, r.top + ceilf(fh.ascent) - 1.0));
	}
}

/*****************************************************************************
 * SeekSlider::MouseDown
 *****************************************************************************/
void
SeekSlider::MouseDown(BPoint where)
{
	if (IsEnabled() && Bounds().Contains(where))
	{
		SetValue(_ValueFor(where.x));
		fTracking = true;
		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
		_BeginSeek();
	}
}

/*****************************************************************************
 * SeekSlider::MouseMoved
 *****************************************************************************/
void
SeekSlider::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
{
	if (fTracking)
	{
		SetValue(_ValueFor(where.x));
		_Seek();
	}
}

/*****************************************************************************
 * SeekSlider::MouseUp
 *****************************************************************************/
void
SeekSlider::MouseUp(BPoint where)
{
	if (fTracking)
	{
		fTracking = false;
		_EndSeek();
	}
}

/*****************************************************************************
 * SeekSlider::ResizeToPreferred
 *****************************************************************************/
void
SeekSlider::ResizeToPreferred()
{
	float width = 15.0 + StringWidth(kDisabledSeekMessage) + 15.0;
	ResizeTo(width, 17.0);
}

/*****************************************************************************
 * SeekSlider::SetPosition
 *****************************************************************************/
void
SeekSlider::SetPosition(float position)
{
	if ( LockLooper() )
	{
		SetValue(fMinValue + (int32)floorf((fMaxValue - fMinValue) * position + 0.5));
		UnlockLooper();
	}
}

/*****************************************************************************
 * SeekSlider::_ValueFor
 *****************************************************************************/
int32
SeekSlider::_ValueFor(float xPos) const
{
	BRect r(Bounds());
	float knobWidth2 = SEEK_SLIDER_KNOB_WIDTH / 2.0;
	float sliderStart = (r.left + knobWidth2);
	float sliderEnd = (r.right - knobWidth2);
	int32 value =  fMinValue + (int32)(((xPos - sliderStart) * (fMaxValue - fMinValue))
				  / (sliderEnd - sliderStart - 1.0));
	if (value < fMinValue)
		value = fMinValue;
	if (value > fMaxValue)
		value = fMaxValue;
	return value;
}

/*****************************************************************************
 * SeekSlider::_StrokeFrame
 *****************************************************************************/
void
SeekSlider::_StrokeFrame(BRect r, rgb_color left, rgb_color top,
						 rgb_color right, rgb_color bottom)
{
	BeginLineArray(4);
		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), left);
		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), top);
		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), right);
		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), bottom);
	EndLineArray();
}

/*****************************************************************************
 * SeekSlider::_BeginSeek
 *****************************************************************************/
void
SeekSlider::_BeginSeek()
{
	fOwner->fScrubSem = create_sem(0, "Vlc::fScrubSem");
	if (fOwner->fScrubSem >= B_OK)
		release_sem(fOwner->fScrubSem);
}

/*****************************************************************************
 * SeekSlider::_Seek
 *****************************************************************************/
void
SeekSlider::_Seek()
{
	if (fOwner->fScrubSem >= B_OK)
		delete_sem(fOwner->fScrubSem);
	fOwner->fScrubSem = create_sem(0, "Vlc::fScrubSem");
	if (fOwner->fScrubSem >= B_OK)
		release_sem(fOwner->fScrubSem);
}

/*****************************************************************************
 * SeekSlider::_EndSeek
 *****************************************************************************/
void
SeekSlider::_EndSeek()
{
	if (fOwner->fScrubSem >= B_OK)
		delete_sem(fOwner->fScrubSem);
	fOwner->fScrubSem = B_ERROR;
}


/*****************************************************************************
 * VolumeSlider
 *****************************************************************************/
VolumeSlider::VolumeSlider(BRect frame, const char* name, int32 minValue, int32 maxValue,
						   BMessage* message, BHandler* target)
	: BControl(frame, name, NULL, message, B_FOLLOW_NONE,
			   B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
	  fLeftSideBits(NULL),
	  fRightSideBits(NULL),
	  fKnobBits(NULL),
	  fTracking(false),
	  fMuted(false),
	  fMinValue(minValue),
	  fMaxValue(maxValue)
{
	SetTarget(target);

	// create bitmaps
	BRect r(BPoint(0.0, 0.0), kVolumeSliderBitmapSize);
	fLeftSideBits = new BBitmap(r, B_CMAP8);
	fRightSideBits = new BBitmap(r, B_CMAP8);
	r.Set(0.0, 0.0, kVolumeSliderKnobBitmapSize.x, kVolumeSliderKnobBitmapSize.y);
	fKnobBits = new BBitmap(r, B_CMAP8);

	_MakeBitmaps();
}

/*****************************************************************************
 * VolumeSlider destructor
 *****************************************************************************/
VolumeSlider::~VolumeSlider()
{
	delete fLeftSideBits;
	delete fRightSideBits;
	delete fKnobBits;
}

/*****************************************************************************
 * VolumeSlider::AttachedToWindow
 *****************************************************************************/
void
VolumeSlider::AttachedToWindow()
{
	BControl::AttachedToWindow();
	SetViewColor(B_TRANSPARENT_32_BIT);
}

/*****************************************************************************
 * VolumeSlider::SetValue
 *****************************************************************************/
void
VolumeSlider::SetValue(int32 value)
{
	if (value != Value())
	{
		BControl::SetValue(value);
		Invoke();
	}
}

/*****************************************************************************
 * VolumeSlider::SetEnabled
 *****************************************************************************/
void
VolumeSlider::SetEnabled(bool enable)
{
	if (enable != IsEnabled())
	{
		BControl::SetEnabled(enable);
		_MakeBitmaps();
		Invalidate();
	}
}

/*****************************************************************************
 * VolumeSlider::Draw
 *****************************************************************************/
void
VolumeSlider::Draw(BRect updateRect)
{
	if (IsValid())
	{
		BRect r(Bounds());
		float sliderSideWidth = kVolumeSliderBitmapWidth;
		float sliderStart = (r.left + sliderSideWidth);
		float sliderEnd = (r.right - sliderSideWidth);
		float knobPos = sliderStart
						+ (sliderEnd - sliderStart - 1.0) * (Value() - fMinValue)
						/ (fMaxValue - fMinValue);
		// draw both sides (the original from Be doesn't seem
		// to make a difference for enabled/disabled state)
		DrawBitmapAsync(fLeftSideBits, r.LeftTop());
		DrawBitmapAsync(fRightSideBits, BPoint(sliderEnd + 1.0, r.top));
		// colors for the slider area between the two bitmaps
		rgb_color background = kBackground;//ui_color(B_PANEL_BACKGROUND_COLOR);
		rgb_color shadow = tint_color(background, B_DARKEN_2_TINT);
		rgb_color softShadow = tint_color(background, B_DARKEN_1_TINT);
		rgb_color darkShadow = tint_color(background, B_DARKEN_4_TINT);
		rgb_color midShadow = tint_color(background, B_DARKEN_3_TINT);
		rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT);
		rgb_color softLight = tint_color(background, B_LIGHTEN_1_TINT);
		rgb_color green = kGreen;
		rgb_color greenShadow = kGreenShadow;
		rgb_color black = kBlack;
		rgb_color dotGrey = midShadow;
		rgb_color dotGreen = greenShadow;
		// make dimmed version of colors if we're disabled
		if (!IsEnabled())
		{
			shadow = (rgb_color){ 200, 200, 200, 255 };
			softShadow = dimmed_color_cmap8(softShadow, background, DIM_LEVEL);
			darkShadow = dimmed_color_cmap8(darkShadow, background, DIM_LEVEL);
			midShadow = shadow;
			light = dimmed_color_cmap8(light, background, DIM_LEVEL);
			softLight = dimmed_color_cmap8(softLight, background, DIM_LEVEL);
			green = dimmed_color_cmap8(green, background, DIM_LEVEL);
			greenShadow = dimmed_color_cmap8(greenShadow, background, DIM_LEVEL);
			black = dimmed_color_cmap8(black, background, DIM_LEVEL);
			dotGreen = dotGrey;
		}
		else if (fMuted)
		{
			green = tint_color(kBackground, B_DARKEN_3_TINT);
			greenShadow = tint_color(kBackground, B_DARKEN_4_TINT);
			dotGreen = greenShadow;
		}
		// draw slider edges between bitmaps
		BeginLineArray(7);
			AddLine(BPoint(sliderStart, r.top),
					BPoint(sliderEnd, r.top), softShadow);
			AddLine(BPoint(sliderStart, r.bottom),
					BPoint(sliderEnd, r.bottom), softLight);
			r.InsetBy(0.0, 1.0);
			AddLine(BPoint(sliderStart, r.top),
					BPoint(sliderEnd, r.top), black);
			AddLine(BPoint(sliderStart, r.bottom),
					BPoint(sliderEnd, r.bottom), light);
			r.top++;
			AddLine(BPoint(sliderStart, r.top),
					BPoint(knobPos, r.top), greenShadow);
			AddLine(BPoint(knobPos, r.top),
					BPoint(sliderEnd, r.top), midShadow);
			r.top++;
			AddLine(BPoint(sliderStart, r.top),
					BPoint(knobPos, r.top), greenShadow);
		EndLineArray();
		// fill rest inside of slider
		r.InsetBy(0.0, 1.0);
		r.left = sliderStart;
		r.right = knobPos;
		SetHighColor(green);
		FillRect(r, B_SOLID_HIGH);
		r.left = knobPos + 1.0;
		r.right = sliderEnd;
		r.top -= 1.0;
		SetHighColor(shadow);
		FillRect(r, B_SOLID_HIGH);
		// draw little dots inside
		int32 dotCount = (int32)((sliderEnd - sliderStart) / 5.0);
		BPoint dotPos;
		dotPos.y = r.top + 4.0;
		for (int32 i = 0; i < dotCount; i++)
		{
			dotPos.x = sliderStart + i * 5.0 + 4.0;
			SetHighColor(dotPos.x < knobPos ? dotGreen : dotGrey);
			StrokeLine(dotPos, BPoint(dotPos.x, dotPos.y + 1.0));
		}
		// draw knob
		r.top -= 1.0;
		SetDrawingMode(B_OP_OVER); // part of knob is transparent
		DrawBitmapAsync(fKnobBits, BPoint(knobPos - kVolumeSliderKnobWidth / 2, r.top));
	}
}

/*****************************************************************************
 * VolumeSlider::MouseDown
 *****************************************************************************/
void
VolumeSlider::MouseDown(BPoint where)
{
	if (Bounds().Contains(where) && IsEnabled())
	{
		fTracking = true;
		SetValue(_ValueFor(where.x));
		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
	}
}

/*****************************************************************************
 * VolumeSlider::MouseMoved
 *****************************************************************************/
void
VolumeSlider::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
{
	if (fTracking)
		SetValue(_ValueFor(where.x));
}

/*****************************************************************************
 * VolumeSlider::MouseUp
 *****************************************************************************/
void
VolumeSlider::MouseUp(BPoint where)
{
	fTracking = false;
}


/*****************************************************************************
 * VolumeSlider::IsValid
 *****************************************************************************/
bool
VolumeSlider::IsValid() const
{
	return (fLeftSideBits && fLeftSideBits->IsValid()
			&& fRightSideBits && fRightSideBits->IsValid()
			&& fKnobBits && fKnobBits->IsValid());
}

/*****************************************************************************
 * VolumeSlider::SetMuted
 *****************************************************************************/
void
VolumeSlider::SetMuted(bool mute)
{
	if (mute != fMuted)
	{
		fMuted = mute;
		_MakeBitmaps();
		Invalidate();
	}
}

/*****************************************************************************
 * VolumeSlider::_MakeBitmaps
 *****************************************************************************/
void
VolumeSlider::_MakeBitmaps()
{
	if (IsValid())
	{
		// left side of slider
		memcpy(fLeftSideBits->Bits(), kVolumeSliderLeftBitmapBits,
			   fLeftSideBits->BitsLength());
		// right side of slider
		memcpy(fRightSideBits->Bits(), kVolumeSliderRightBits,
			   fRightSideBits->BitsLength());
		// slider knob
		int32 length = fKnobBits->BitsLength();
		memcpy(fKnobBits->Bits(), kVolumeSliderKnobBits, length);
		uint8* bits = (uint8*)fKnobBits->Bits();
		// black was used in the knob to represent transparency
		// use screen to get index for the "transarent" color used in the bitmap
		BScreen screen(B_MAIN_SCREEN_ID);
		uint8 blackIndex = screen.IndexForColor(kBlack);
		// replace black index with transparent index
		for (int32 i = 0; i < length; i++)
			if (bits[i] == blackIndex)
				bits[i] = B_TRANSPARENT_MAGIC_CMAP8;

		if (!IsEnabled())
		{
			// make ghosted versions of the bitmaps
			dim_bitmap(fLeftSideBits, kBackground, DIM_LEVEL);
			dim_bitmap(fRightSideBits, kBackground, DIM_LEVEL);
			dim_bitmap(fKnobBits, kBackground, DIM_LEVEL);
		}
		else if (fMuted)
		{
			// replace green color (and shadow) in left slider side
			bits = (uint8*)fLeftSideBits->Bits();
			length = fLeftSideBits->BitsLength();
			uint8 greenIndex = screen.IndexForColor(kGreen);
			uint8 greenShadowIndex = screen.IndexForColor(kGreenShadow);
			rgb_color shadow = tint_color(kBackground, B_DARKEN_3_TINT);
			rgb_color midShadow = tint_color(kBackground, B_DARKEN_4_TINT);
			uint8 replaceIndex = screen.IndexForColor(shadow);
			uint8 replaceShadowIndex = screen.IndexForColor(midShadow);
			for (int32 i = 0; i < length; i++)
			{
				if (bits[i] == greenIndex)
					bits[i] = replaceIndex;
				else if (bits[i] == greenShadowIndex)
					bits[i] = replaceShadowIndex;
			}
		}
	}
}

/*****************************************************************************
 * VolumeSlider::_ValueFor
 *****************************************************************************/
int32
VolumeSlider::_ValueFor(float xPos) const
{
	BRect r(Bounds());
	float sliderStart = (r.left + kVolumeSliderBitmapWidth);
	float sliderEnd = (r.right - kVolumeSliderBitmapWidth);
	int32 value =  fMinValue + (int32)(((xPos - sliderStart) * (fMaxValue - fMinValue))
				  / (sliderEnd - sliderStart - 1.0));
	if (value < fMinValue)
		value = fMinValue;
	if (value > fMaxValue)
		value = fMaxValue;
	return value;
}

/*****************************************************************************
 * PositionInfoView::PositionInfoView
 *****************************************************************************/
PositionInfoView::PositionInfoView( BRect frame, const char* name,
                                    intf_thread_t * p_interface )
	: BView( frame, name, B_FOLLOW_NONE,
			 B_WILL_DRAW | B_PULSE_NEEDED | B_FULL_UPDATE_ON_RESIZE ),
	  fMode( MODE_SMALL ),
	  fCurrentFileIndex( -1 ),
	  fCurrentFileSize( -1 ),
	  fCurrentTitleIndex( -1 ),
	  fCurrentTitleSize( -1 ),
	  fCurrentChapterIndex( -1 ),
	  fCurrentChapterSize( -1 ),
	  fSeconds( -1 ),
	  fTimeString( "-:--:--" ),
	  fLastPulseUpdate( system_time() ),
	  fStackedWidthCache( 0.0 ),
	  fStackedHeightCache( 0.0 )
{
    p_intf = p_interface;

	SetViewColor( B_TRANSPARENT_32_BIT );
	SetLowColor( kBlack );
	SetHighColor( 0, 255, 0, 255 );
	SetFontSize( 11.0 );
}

/*****************************************************************************
 * PositionInfoView::~PositionInfoView
 *****************************************************************************/
PositionInfoView::~PositionInfoView()
{
}

/*****************************************************************************
 * PositionInfoView::Draw
 *****************************************************************************/
void
PositionInfoView::Draw( BRect updateRect )
{
	rgb_color background = ui_color( B_PANEL_BACKGROUND_COLOR );
	rgb_color shadow = tint_color( background, B_DARKEN_1_TINT );
	rgb_color darkShadow = tint_color( background, B_DARKEN_4_TINT );
	rgb_color light = tint_color( background, B_LIGHTEN_MAX_TINT );
	rgb_color softLight = tint_color( background, B_LIGHTEN_1_TINT );
	// frame
	BRect r( Bounds() );
	BeginLineArray( 8 );
		AddLine( BPoint( r.left, r.bottom ),
				 BPoint( r.left, r.top ), shadow );
		AddLine( BPoint( r.left + 1.0, r.top ),
				 BPoint( r.right, r.top ), shadow );
		AddLine( BPoint( r.right, r.top + 1.0 ),
				 BPoint( r.right, r.bottom ), softLight );
		AddLine( BPoint( r.right - 1.0, r.bottom ),
				 BPoint( r.left + 1.0, r.bottom ), softLight );
		r.InsetBy( 1.0, 1.0 );
		AddLine( BPoint( r.left, r.bottom ),
				 BPoint( r.left, r.top ), darkShadow );
		AddLine( BPoint( r.left + 1.0, r.top ),
				 BPoint( r.right, r.top ), darkShadow );
		AddLine( BPoint( r.right, r.top + 1.0 ),
				 BPoint( r.right, r.bottom ), light );
		AddLine( BPoint( r.right - 1.0, r.bottom ),
				 BPoint( r.left + 1.0, r.bottom ), light );
	EndLineArray();
	// background
	r.InsetBy( 1.0, 1.0 );
	FillRect( r, B_SOLID_LOW );
	// contents
	font_height fh;
	GetFontHeight( &fh );
	switch ( fMode )
	{
		case MODE_SMALL:
		{
			float width = StringWidth( fTimeString.String() );
			DrawString( fTimeString.String(),
						BPoint( r.left + r.Width() / 2.0 - width / 2.0,
								r.top + r.Height() / 2.0 + fh.ascent / 2.0 - 1.0 ) );
			break;
		}
		case MODE_BIG:
		{
			BFont font;
			GetFont( &font );
			BFont smallFont = font;
			BFont bigFont = font;
			BFont tinyFont = font;
			smallFont.SetSize( r.Height() / 5.0 );
			bigFont.SetSize( r.Height() / 3.0 );
			tinyFont.SetSize( r.Height() / 7.0 );
			float timeHeight = r.Height() / 2.5;
			float height = ( r.Height() - timeHeight ) / 3.0;
			SetFont( &tinyFont );
			SetHighColor( 0, 180, 0, 255 );
			DrawString( "File", BPoint( r.left + 3.0, r.top + height ) );
			DrawString( "Title", BPoint( r.left + 3.0, r.top + 2.0 * height ) );
			DrawString( "Chapter", BPoint( r.left + 3.0, r.top + 3.0 * height ) );
			SetFont( &smallFont );
			BString helper;
			SetHighColor( 0, 255, 0, 255 );
			// file
			_MakeString( helper, fCurrentFileIndex, fCurrentFileSize );
			float width = StringWidth( helper.String() );
			DrawString( helper.String(), BPoint( r.right - 3.0 - width, r.top + height ) );
			// title
			_MakeString( helper, fCurrentTitleIndex, fCurrentTitleSize );
			width = StringWidth( helper.String() );
			DrawString( helper.String(), BPoint( r.right - 3.0 - width, r.top + 2.0 * height ) );
			// chapter
			_MakeString( helper, fCurrentChapterIndex, fCurrentChapterSize );
			width = StringWidth( helper.String() );
			DrawString( helper.String(), BPoint( r.right - 3.0 - width, r.top + 3.0 * height ) );
			// time
			SetFont( &bigFont );
			width = StringWidth( fTimeString.String() );
			DrawString( fTimeString.String(),
						BPoint( r.left + r.Width() / 2.0 - width / 2.0,
								r.bottom - 3.0 ) );
			break;
		}
	}
}

/*****************************************************************************
 * PositionInfoView::ResizeToPreferred
 *****************************************************************************/
void
PositionInfoView::ResizeToPreferred()
{
	float width, height;
	GetPreferredSize( &width, &height );
	ResizeTo( width, height );
}

/*****************************************************************************
 * PositionInfoView::GetPreferredSize
 *****************************************************************************/
void
PositionInfoView::GetPreferredSize( float* width, float* height )
{
	if ( width && height )
	{
		*width = 5.0 + ceilf( StringWidth( "0:00:00" ) ) + 5.0;
		font_height fh;
		GetFontHeight( &fh );
		*height = 3.0 + ceilf( fh.ascent ) + 3.0;
		fStackedWidthCache = *width * 1.2;
		fStackedHeightCache = *height * 2.7;
	}
}

/*****************************************************************************
 * PositionInfoView::Pulse
 *****************************************************************************/
void
PositionInfoView::Pulse()
{
	// allow for Pulse frequency to be higher, MediaControlView needs it
	bigtime_t now = system_time();
	if ( now - fLastPulseUpdate > 900000 )
	{
		int32 index, size;
		p_intf->p_sys->p_wrapper->GetPlaylistInfo( index, size );
		SetFile( index + 1, size );
		p_intf->p_sys->p_wrapper->TitleInfo( index, size );
		SetTitle( index, size );
		p_intf->p_sys->p_wrapper->ChapterInfo( index, size );
		SetChapter( index, size );
		SetTime( p_intf->p_sys->p_wrapper->GetTimeAsString() );
		fLastPulseUpdate = now;
	}
}

/*****************************************************************************
 * PositionInfoView::GetBigPreferredSize
 *****************************************************************************/
void
PositionInfoView::GetBigPreferredSize( float* width, float* height )
{
	if ( width && height )
	{
		*width = fStackedWidthCache;
		*height = fStackedHeightCache;
	}
}

/*****************************************************************************
 * PositionInfoView::SetMode
 *****************************************************************************/
void
PositionInfoView::SetMode( uint32 mode )
{
	if ( fMode != mode )
	{
		fMode = mode;
		_InvalidateContents();
	}
}

/*****************************************************************************
 * PositionInfoView::SetFile
 *****************************************************************************/
void
PositionInfoView::SetFile( int32 index, int32 size )
{
	if ( fCurrentFileIndex != index || fCurrentFileSize != size )
	{
		fCurrentFileIndex = index;
		fCurrentFileSize = size;
		_InvalidateContents();
	}
}

/*****************************************************************************
 * PositionInfoView::SetTitle
 *****************************************************************************/
void
PositionInfoView::SetTitle( int32 index, int32 size )
{
	if ( fCurrentTitleIndex != index || fCurrentFileSize != size )
	{
		fCurrentTitleIndex = index;
		fCurrentTitleSize = size;
		_InvalidateContents();
	}
}

/*****************************************************************************
 * PositionInfoView::SetChapter
 *****************************************************************************/
void
PositionInfoView::SetChapter( int32 index, int32 size )
{
	if ( fCurrentChapterIndex != index || fCurrentFileSize != size )
	{
		fCurrentChapterIndex = index;
		fCurrentChapterSize = size;
		_InvalidateContents();
	}
}

/*****************************************************************************
 * PositionInfoView::SetTime
 *****************************************************************************/
void
PositionInfoView::SetTime( int32 seconds )
{
	if ( fSeconds != seconds )
	{
		if ( seconds >= 0 )
		{
			int32 minutes = seconds / 60;
			int32 hours = minutes / 60;
			seconds -= minutes * 60 - hours * 60 * 60;
			minutes -= hours * 60;
			fTimeString.SetTo( "" );
			fTimeString << hours << ":" << minutes << ":" << seconds;
		}
		else
			fTimeString.SetTo( "-:--:--" );

		fSeconds = seconds;
		_InvalidateContents();
	}
}

/*****************************************************************************
 * PositionInfoView::SetTime
 *****************************************************************************/
void
PositionInfoView::SetTime( const char* string )
{
	fTimeString.SetTo( string );
	_InvalidateContents();
}

/*****************************************************************************
 * PositionInfoView::_InvalidateContents
 *****************************************************************************/
void
PositionInfoView::_InvalidateContents( uint32 which )
{
	BRect r( Bounds() );
	r.InsetBy( 2.0, 2.0 );
	Invalidate( r );
}

/*****************************************************************************
 * PositionInfoView::_InvalidateContents
 *****************************************************************************/
void
PositionInfoView::_MakeString( BString& into, int32 index, int32 maxIndex ) const
{
	into = "";
	if ( index >= 0 && maxIndex >= 0 )
		into << index;
	else
		into << "-";
	into << "/";
	if ( maxIndex >= 0 )
		into << maxIndex;
	else
		into << "-";
}
