dolphin/Branches/MusicMod/Player/Src/PlaylistView.cpp

783 lines
22 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// Plainamp, Open source Winamp core
//
// Copyright © 2005 Sebastian Pipping <webmaster@hartwork.org>
//
// --> http://www.hartwork.org
//
// This source code is released under the GNU General Public License (GPL).
// See GPL.txt for details. Any non-GPL usage is strictly forbidden.
////////////////////////////////////////////////////////////////////////////////
// =======================================================================================
#include "Playlist.h"
#include "Main.h"
#include "Status.h"
#include "Rebar.h"
#include "Playback.h"
#include "Config.h"
#include "Util.h"
WNDPROC WndprocPlaylistBackup = NULL;
LRESULT CALLBACK WndprocPlaylist( HWND hwnd, UINT message, WPARAM wp, LPARAM lp );
// =======================================================================================
// =======================================================================================
// This gives the values from the ini file.
// ConfBool = class
PlaylistControler * playlist = NULL; // extern
bool bPlaylistEntryNumberZeroPadding;
ConfBool cbPlaylistEntryNumberZeroPadding( &bPlaylistEntryNumberZeroPadding, TEXT( "PlaylistEntryNumberZeroPadding" ), CONF_MODE_PUBLIC, true );
int iCurPlaylistPosition;
ConfInt ciCurPlaylistPosition( &iCurPlaylistPosition, TEXT( "CurPlaylistPosition" ), CONF_MODE_INTERNAL, -1 );
bool bInfinitePlaylist;
ConfBool cbInfinitePlaylist( &bInfinitePlaylist, TEXT( "InfinitePlaylist" ), CONF_MODE_PUBLIC, false );
// =======================================================================================
// =======================================================================================
void PlaylistView::Create()
{
#ifndef NOGUI
RECT ClientMain;
GetClientRect( WindowMain, &ClientMain );
const int iClientHeight = ClientMain.bottom - ClientMain.top;
const int iClientWidth = ClientMain.right - ClientMain.left;
const int iPlaylistHeight = iClientHeight - iRebarHeight - iStatusHeight;
//LoadCommonControls();
DWORD dwStyle;
// HWND WindowPlaylist;
BOOL bSuccess = TRUE;
dwStyle = WS_TABSTOP |
WS_CHILD |
WS_BORDER |
WS_VISIBLE |
LVS_AUTOARRANGE | // TODO
LVS_REPORT |
LVS_OWNERDATA |
LVS_NOCOLUMNHEADER ;
WindowPlaylist = CreateWindowEx( WS_EX_CLIENTEDGE, // ex style
WC_LISTVIEW, // class name - defined in commctrl.h
TEXT( "" ), // dummy text
dwStyle, // style
0,
iRebarHeight, // + -2,
iClientWidth,
iPlaylistHeight,
WindowMain, // parent
NULL, // ID
g_hInstance, // instance
NULL); // no extra data
if(!WindowPlaylist) return; // TODO
// This calls PlaylistControler::PlaylistControler()
playlist = new PlaylistControler( WindowPlaylist, bPlaylistEntryNumberZeroPadding, &iCurPlaylistPosition );
#else
HWND WindowPlaylist = NULL;
playlist = new PlaylistControler( WindowPlaylist, bPlaylistEntryNumberZeroPadding, &iCurPlaylistPosition );
#endif
// Exchange window procedure
//WndprocPlaylistBackup = ( WNDPROC )GetWindowLong( WindowPlaylist, GWL_WNDPROC );
//if( WndprocPlaylistBackup != NULL )
//{
// SetWindowLong( WindowPlaylist, GWL_WNDPROC, ( LONG )WndprocPlaylist );
//}
//ListView_SetExtendedListViewStyle( WindowPlaylist, LVS_EX_FULLROWSELECT ); // | LVS_EX_GRIDLINES );
//playlist->Resize( WindowMain );
/*
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp
*
* Remarks:
* If a column is added to a list-view control with index 0 (the leftmost column)
* and with LVCFMT_RIGHT or LVCFMT_CENTER specified, the text is not right-aligned
* or centered. The text in the index 0 column is left-aligned. Therefore if you
* keep inserting columns with index 0, the text in all columns are left-aligned.
* If you want the first column to be right-aligned or centered you can make a dummy
* column, then insert one or more columns with index 1 or higher and specify the
* alignment you require. Finally delete the dummy column.
*/
//LV_COLUMN lvColumn;
//memset( &lvColumn, 0, sizeof( LV_COLUMN ) );
//lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
//
//// Number column (with dummy hack)
//lvColumn.fmt = LVCFMT_LEFT;
//lvColumn.cx = 0;
//lvColumn.pszText = TEXT( "" );
//ListView_InsertColumn( WindowPlaylist, 0, &lvColumn );
//lvColumn.fmt = LVCFMT_RIGHT;
//lvColumn.cx = 120;
//lvColumn.pszText = TEXT( "" );
//ListView_InsertColumn( WindowPlaylist, 1, &lvColumn );
//ListView_DeleteColumn( WindowPlaylist, 0 );
// Entry
//lvColumn.fmt = LVCFMT_LEFT;
//lvColumn.cx = 120;
//lvColumn.pszText = TEXT( "Filename" );
//ListView_InsertColumn(WindowPlaylist, 1, &lvColumn);
/*
stupid test code
SCROLLINFO scrollinfo;
ZeroMemory( &scrollinfo, sizeof( SCROLLINFO ) );
scrollinfo.cbSize = sizeof( SCROLLINFO );
scrollinfo.fMask = 0; // SIF_DISABLENOSCROLL;
if( !GetScrollInfo( WindowPlaylist, SB_VERT, &scrollinfo ) )
{
MessageBox( 0, "ERROR", "", 0 );
}
else
{
MessageBox( 0, "OKAY", "", 0 );
scrollinfo.fMask = SIF_DISABLENOSCROLL;
SetScrollInfo( WindowPlaylist, SB_VERT, &scrollinfo, TRUE );
}
if( !ShowScrollBar( WindowPlaylist, SB_VERT, TRUE ) )
{
MessageBox( 0, "ERROR ShowScrollBar", "", 0 );
}
SCROLLBARINFO scrollbarinfo;
scrollbarinfo.cbSize = sizeof( SCROLLBARINFO );
if( !GetScrollBarInfo( WindowPlaylist, OBJID_VSCROLL, &scrollbarinfo ) )
{
MessageBox( 0, "ERROR GetScrollBarInfo", "", 0 );
}
*/
}
// Dragging
static int iItemHeight = 15;
static int iDragStartY = 0;
static bool bDragging = false;
// Liquid selection
static bool bLiquidSelecting = false;
static int iLastTouched = -1;
// Liquid or range selection
static int iSelAnchor = -1;
LRESULT CALLBACK WndprocPlaylist( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
{
/*
* Click, click and click
*
* [Alt] [Ctrl] [Shift] Action
* -------+---------+---------+-----------------------
* X | X | X |
* X | X | |
* X | | X |
* X | | |
* | X | X | Range selection
* | X | | Toggle selection
* | | X | Range selection
* | | | Single selection
*
*
* Click, hold and move
*
* [Alt] [Ctrl] [Shift] Action
* -------+---------+---------+-----------------------
* X | X | X | Selection move
* X | X | | Selection move
* X | | X | Selection move
* X | | | Selection move
* | X | X |
* | X | |
* | | X |
* | | | Liquid selection
*/
static bool bCapturing = true;
switch( message )
{
/*
case WM_CAPTURECHANGED:
if( bCapturing && ( GetCapture() != WindowPlaylist ) )
{
MessageBox( 0, TEXT( "Capture stolen" ), TEXT( "" ), 0 );
}
break;
*/
case WM_MOUSEMOVE:
if( bLiquidSelecting )
{
LVHITTESTINFO hittest;
memset( &hittest, 0, sizeof( LVHITTESTINFO ) );
hittest.pt.x = LOWORD( lp );
hittest.pt.y = HIWORD( lp );
const int iIndex = ( int )ListView_HitTest( WindowPlaylist, &hittest );
if( iIndex == -1 ) return 0;
if( iIndex == iLastTouched ) return 0;
// Note: Update this as early as possible!
// We cannot be sure this code is
// not called two or three times at the
// same time without losing much speed
// but this at least lowers the chance
const int iLastTouchedBackup = iLastTouched;
iLastTouched = iIndex;
const bool bControl = ( ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0 );
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVIS_FOCUSED );
ListView_SetItemState( WindowPlaylist, iIndex, LVIS_FOCUSED, LVIS_FOCUSED );
// Below anchor?
if( iIndex > iSelAnchor )
{
if( iIndex > iLastTouchedBackup )
{
// iSelAnchor
// ..
// iLastTouchedBackup
// ..
// >> iIndex <<
if( iLastTouchedBackup > iSelAnchor )
{
// Select downwards
for( int i = iLastTouchedBackup + 1; i <= iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
// iLastTouchedBackup
// ..
// iSelAnchor
// ..
// >> iIndex <<
else
{
// Unselect downwards
for( int i = iLastTouchedBackup; i < iSelAnchor; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
// Select downwards
for( int i = iSelAnchor + 1; i <= iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
}
else // iIndex < iLastTouchedBackup
{
// iSelAnchor
// ..
// >> iIndex <<
// ..
// iLastTouchedBackup
// Unselect upwards
for( int i = iLastTouchedBackup; i > iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
// Above anchor?
else if( iIndex < iSelAnchor )
{
if( iIndex < iLastTouchedBackup )
{
// >> iIndex <<
// ..
// iSelAnchor
// ..
// iLastTouchedBackup
if( iIndex < iLastTouchedBackup )
{
// Unselect upwards
for( int i = iLastTouchedBackup; i > iSelAnchor; i-- )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
// Select upwards
for( int i = iSelAnchor - 1; i >= iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
// >> iIndex <<
// ..
// iLastTouchedBackup
// ..
// iSelAnchor
else // iIndex < iLastTouchedBackup
{
// Select upwards
for( int i = iLastTouchedBackup - 1; i >= iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
}
else // iIndex > iLastTouchedBackup
{
// iLastTouchedBackup
// ..
// >> iIndex <<
// ..
// iSelAnchor
// Unselect downwards
for( int i = iLastTouchedBackup; i < iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
// At anchor
else // iIndex == iSelAnchor
{
if( iIndex < iLastTouchedBackup )
{
// iSelAnchor / >> iIndex <<
// ..
// iLastTouchedBackup
// Unselect upwards
for( int i = iLastTouchedBackup; i > iSelAnchor; i-- )
{
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
else // iIndex > iLastTouchedBackup
{
// iLastTouchedBackup
// ..
// iSelAnchor / >> iIndex <<
// Unselect downwards
for( int i = iLastTouchedBackup; i < iSelAnchor; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
}
else if( bDragging )
{
static bool bMoveLock = false;
if( bMoveLock ) return 0;
bMoveLock = true;
const int y = HIWORD( lp );
const int diff = y - iDragStartY;
if( abs( diff ) > iItemHeight / 2 )
{
iDragStartY += ( ( diff > 0 ) ? iItemHeight : -iItemHeight );
playlist->MoveSelected( ( diff > 0 ) ? +1 : -1 );
}
bMoveLock = false;
}
return 0;
case WM_LBUTTONDOWN:
{
static int iLastClicked = -1;
static bool bLastClickNoneOrShift = true;
SetFocus( hwnd ); // TODO monitor focus loss
LVHITTESTINFO hittest;
memset( &hittest, 0, sizeof( LVHITTESTINFO ) );
GetCursorPos( &hittest.pt );
ScreenToClient( hwnd, &hittest.pt );
const int iIndex = ( int )ListView_HitTest( WindowPlaylist, &hittest );
if( iIndex == -1 ) return 0;
const bool bShift = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 );
const bool bControl = ( ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0 );
const bool bAlt = ( ( GetKeyState( VK_MENU ) & 0x8000 ) != 0 );
// [Shift] or [Shift]+[Ctrl]?
if( bShift )
{
if( bAlt ) return 0;
// Last click usable as selection anchor?
if( !bLastClickNoneOrShift )
{
// Treat as normal click
iSelAnchor = iIndex;
iLastClicked = iIndex;
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState( WindowPlaylist, iIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
bLiquidSelecting = true;
bLastClickNoneOrShift = true;
return 0;
}
if( iIndex != iLastClicked )
{
// Below anchor?
if( iIndex > iSelAnchor )
{
// Below last click?
if( iIndex > iLastClicked )
{
// Other side of anchor?
if( iLastClicked < iSelAnchor )
{
// Unselect downwards
for( int i = iLastClicked; i < iSelAnchor; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
// Select downwards
for( int i = iSelAnchor; i <= iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
// Same side of anchor?
else
{
// Select downwards
for( int i = iLastClicked + 1; i <= iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
}
// Above last click?
else // iIndex < iLastClicked
{
// Unselect upwards
for( int i = iLastClicked; i > iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
// Above anchor?
else if( iIndex < iSelAnchor )
{
// Above last clicked?
if( iIndex < iLastClicked )
{
// Other side of anchor?
if( iLastClicked > iSelAnchor )
{
// Unselect upwards
for( int i = iLastClicked; i > iSelAnchor; i-- )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
// Select upwards
for( int i = iSelAnchor; i >= iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
// Same side of anchor?
else
{
// Select upwards
for( int i = iLastClicked - 1; i >= iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, LVIS_SELECTED, LVIS_SELECTED );
}
}
// Below last clicked?
else // iIndex > iLastClicked
{
// Unselect downwards
for( int i = iLastClicked; i < iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
// At anchor
else // iIndex == iSelAnchor
{
if( iLastClicked < iSelAnchor )
{
// Unselect downwards
for( int i = iLastClicked; i < iIndex; i++ )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
else if( iLastClicked > iSelAnchor )
{
// Unselect upwards
for( int i = iLastClicked; i > iIndex; i-- )
ListView_SetItemState( WindowPlaylist, i, 0, LVIS_SELECTED );
}
}
iLastClicked = iIndex;
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVIS_FOCUSED );
ListView_SetItemState( WindowPlaylist, iIndex, LVIS_FOCUSED, LVIS_FOCUSED );
}
bLastClickNoneOrShift = true;
}
// [Ctrl]?
else if( bControl )
{
if( bAlt ) return 0;
iLastTouched = iIndex;
// Toggle selection
const bool bSelected = ( ListView_GetItemState( WindowPlaylist, iIndex, LVIS_SELECTED ) == LVIS_SELECTED );
ListView_SetItemState( WindowPlaylist, iIndex, bSelected ? 0 : LVIS_SELECTED, LVIS_SELECTED );
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVIS_FOCUSED );
ListView_SetItemState( WindowPlaylist, iIndex, LVIS_FOCUSED, LVIS_FOCUSED );
bLastClickNoneOrShift = false;
}
// [Alt]?
else if( bAlt )
{
// Update item height
RECT rc;
GetClientRect( WindowPlaylist, &rc );
const int iItemsPerPage = ListView_GetCountPerPage( WindowPlaylist );
iItemHeight = ( rc.bottom - rc.top ) / iItemsPerPage;
iDragStartY = hittest.pt.y;
bDragging = true;
bCapturing = true;
SetCapture( WindowPlaylist );
}
// No modifiers?
else
{
iSelAnchor = iIndex;
iLastClicked = iIndex;
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState( WindowPlaylist, iIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED );
bLiquidSelecting = true;
bLastClickNoneOrShift = true;
bCapturing = true;
SetCapture( WindowPlaylist );
}
}
return 0;
case WM_LBUTTONUP:
bLiquidSelecting = false;
bDragging = false;
bCapturing = false;
ReleaseCapture();
return 0;
case WM_SYSKEYDOWN:
switch( wp ) // [Alt]+[...]
{
case VK_UP:
playlist->MoveSelected( -1 );
break;
case VK_DOWN:
playlist->MoveSelected( +1 );
break;
}
break;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
case WM_CHAR:
case WM_KEYUP:
// SMALL LETTERS!!!!!!
switch( wp )
{
case 'z':
case 'y':
case 'x':
case 'c':
case 'v':
case 'b':
case 'l':
return 0;
}
break;
case WM_KEYDOWN:
{
const bool bShift = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 );
const bool bControl = ( ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0 );
// Note: [Alt] goes to WM_SYSKEYDOWN instead
// const bool bAlt = ( ( GetKeyState( VK_MENU ) & 0x8000 ) != 0 );
switch( wp )
{
case VK_LEFT:
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_REW5S, 0 );
return 0;
case VK_RIGHT:
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_FFWD5S, 0 );
return 0;
case VK_UP:
if( bInfinitePlaylist )
{
// First item has focus?
if( ListView_GetNextItem( WindowPlaylist, ( UINT )-1, LVNI_FOCUSED ) != 0 ) break;
if( bControl && !bShift )
{
// Move caret only
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVNI_FOCUSED );
ListView_SetItemState( WindowPlaylist, playlist->GetMaxIndex(), LVNI_FOCUSED, LVNI_FOCUSED );
return 0;
}
else
{
// Move Caret and selection
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVNI_FOCUSED );
ListView_SetItemState( WindowPlaylist, playlist->GetMaxIndex() - 1, LVNI_FOCUSED, LVNI_FOCUSED );
wp = VK_DOWN;
}
}
break;
case VK_DOWN:
if( bInfinitePlaylist )
{
// Last item has focus?
if( ListView_GetNextItem( WindowPlaylist, playlist->GetMaxIndex() - 1, LVNI_FOCUSED ) == -1 ) break;
if( bControl && !bShift )
{
// Move caret only
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVNI_FOCUSED );
ListView_SetItemState( WindowPlaylist, 0, LVNI_FOCUSED, LVNI_FOCUSED );
return 0;
}
else
{
// Workaround
ListView_SetItemState( WindowPlaylist, ( UINT )-1, 0, LVNI_FOCUSED );
ListView_SetItemState( WindowPlaylist, 1, LVNI_FOCUSED, LVNI_FOCUSED );
wp = VK_UP;
}
}
break;
case VK_DELETE:
{
if( bShift ) break;
if( bControl )
playlist->RemoveSelected( false );
else
playlist->RemoveSelected( true );
break;
}
case VK_RETURN:
playlist->SetCurIndex( ListView_GetNextItem( WindowPlaylist, ( UINT )-1, LVIS_FOCUSED ) );
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON2, 0 );
return 0;
case 'Y':
case 'Z':
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON1, 0 );
return 0;
case 'X':
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON2, 0 );
return 0;
case 'C':
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON3, 0 );
return 0;
case 'V':
// Todo modifiers pressed? -> fadeout/...
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON4, 0 );
return 0;
case 'B':
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_BUTTON5, 0 );
return 0;
/*
case 'J':
if( bShift || bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, WINAMP_JUMPFILE, 0 );
return 0;
*/
case 'L':
if( bControl ) return 0;
SendMessage( WindowMain, WM_COMMAND, bShift ? WINAMP_FILE_DIR : WINAMP_FILE_PLAY, 0 );
return 0;
}
break;
}
case WM_LBUTTONDBLCLK:
// iCurIndex = Playlist_MouseToIndex();
LVHITTESTINFO hittest;
memset( &hittest, 0, sizeof( LVHITTESTINFO ) );
GetCursorPos( &hittest.pt );
ScreenToClient( hwnd, &hittest.pt );
const int iIndex = ( int )ListView_HitTest( WindowPlaylist, &hittest );
if( iIndex == -1 ) break;
playlist->SetCurIndex( iIndex );
Playback::Play();
Playback::UpdateSeek();
break;
}
return CallWindowProc( WndprocPlaylistBackup, hwnd, message, wp, lp );
}