//////////////////////////////////////////////////////////////////////////////// // Plainamp, Open source Winamp core // // Copyright © 2005 Sebastian Pipping // // --> 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 ); }