Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / MFC
Article

Nullsoft Winamp Plug-in With Bitmapped UI, Docking and Restrictive Resizing

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
9 Jun 2000 294.9K   4.9K   111   45
An article discussing a Plug-in for Nullsoft Winamp which looks and behaves like the Winamp UI.

Sample Image - WinampWnd.jpg

Introduction

This article presents a general purpose plug-in for Nullsoft Winamp which looks and behaves like Winamp. I assume that everyone is familiar with Winamp, the MP3 player designed by the excellent Justin Frankel and his team at Nullsoft. If not, go and check it out at http://www.winamp.com.

As you may or may not be aware, Winamp contains the facility to extend its features by writing 'Plug-ins' (Win32 DLLs). I have written a few plug-ins for Winamp but they were mostly based around plain vanilla CDialogs. I had seen one or two other plug-ins which had windows which looked like the Winamp 'skinnable' (bitmapped) window. I therefore decided my plug-ins would look far better if my windows were also bitmapped.

The Winamp user interface has loads of other cool features besides bitmapping, all of which are surprisingly simple to implement once you know how. I worked out how to do it, and now I am releasing the source code so that everyone can do it.

The plug-in presented here shows the following:-

  • Bitmapped Windows
  • 'Skinable' Windows
  • Docking to the Winamp Main Window
  • 'Tracking' the Winamp Main Window
  • Restrictive Resizing

Bitmapped Windows

So, here is the hard part of the plug-in; the bitmapped windows. Bitmapped windows have been knocking about for years now but not too many people use them. The CWinampWnd presented here uses an extension to the CBitmap class, called CBitmapEx, which includes the following extensions:-

  • Can load a bitmap from resources or from a file on disk.
  • Contains functions to report the dimensions of the bitmap.
  • Can draw itself given a CWnd pointer and some sizing information.

The source bitmap (shown below) is loaded from resources or disk as mentioned above.

As you can see, the bitmap comprises a series of smaller bitmaps which are combined to make the window. This bitmap is a copy of the Winamp Playlist Editor bitmap with some small alterations. The layout of the bitmap is managed by the CImageMap class which simply has an array of CRects which contain the co-ordinates of each of the smaller bitmaps. The following shows the constructor for the CImageMap class where the co-ordinates are set.

CImageMap::CImageMap()
{
    m_ImageMap[0].SetRect(0,   0, 25, 20);      //TitleBar Left Corner
    m_ImageMap[1].SetRect(26,  0, 126, 20);     //TitleBar Middle
    m_ImageMap[2].SetRect(127, 0, 152, 20);     //TitleBar Fill
    m_ImageMap[3].SetRect(153, 0, 178, 20);     //TitleBar Right Corner
    m_ImageMap[4].SetRect(0,   21, 25, 41);     //TitleBar Left Corner
    m_ImageMap[5].SetRect(26,  21, 126, 41);    //TitleBar Middle
    m_ImageMap[6].SetRect(127, 21, 152, 41);    //TitleBar Fill
    m_ImageMap[7].SetRect(153, 21, 178, 41);    //TitleBar Right Corner
    m_ImageMap[8].SetRect(0, 72, 125, 110);     //Bottom Left Corner
    m_ImageMap[9].SetRect(126, 72, 276, 110);   //Bottom Right Corner
    m_ImageMap[10].SetRect(179, 0, 204, 38);    //Bottom Filler
    m_ImageMap[11].SetRect(0, 42, 25, 71);      //Left Edge
    m_ImageMap[12].SetRect(26, 42, 51, 71);     //Right Edge
    m_ImageMap[13].SetRect(52, 42, 61, 51);     //Close Button
    m_ImageMap[14].SetRect(62, 42, 71, 51);     //Minimize Button
    m_ImageMap[15].SetRect(52, 53, 60, 71);     //Scrollbar
    m_ImageMap[16].SetRect(61, 53, 69, 71);     //Scrollbar Pressed
    m_ImageMap[17].SetRect(72, 42, 97, 56);     //Shade Left Corner
    m_ImageMap[18].SetRect(99, 42, 149, 56);    //Shade Right Corner Active
    m_ImageMap[19].SetRect(72, 57, 97, 71);     //Shade Middle
    m_ImageMap[20].SetRect(99, 57, 149, 71);    //Shade Right Corner Inactive
    m_ImageMap[21].SetRect(150, 42, 158, 51);   //Maximize Button
}

Each CRect is then addressable by using the following constants:-

#define TITLEBAR_LEFT_CORNER   0
#define TITLEBAR_MIDDLE        1
#define TITLEBAR_FILL          2
#define TITLEBAR_RIGHT_CORNER  3
.... etc

So, to draw say the left corner of the titlebar onto the screen, we simply use this code:-

m_Interface.Draw(FALSE, this, 0, 0, m_map.OffSet(TITLEBAR_LEFT_CORNER));

Where m_Interface is a CBitmapEx and m_map is an instance of the CImageMap class.

All aspects of the CWinampWnd are drawn this way in the DrawInterface() function which is called from OnPaint().

'Skinnable' Windows

As an extension to the bitmapped window, instead of simply reading the window bitmap from resources, the user can optionally place a bitmap in the same directory as the plug-in. The plug-in will attempt to read the file from disk before loading the internal bitmap. This allows users to create their own designs for the window appearance based on the template bitmap used in the resources. These are called 'skins'.

Docking

Wouldn't it be cool if you could get your plug-in window to 'snap' to the Winamp Main Window like the other windows do? Well, now you can.

The trick behind this is to do a custom dragging routine. When the title bar is left clicked on, a WM_LBUTTONDOWN message is received on the plug-in window. We then use SetCapture() to watch where the mouse is moved to, so we get WM_MOUSEMOVE messages as the mouse is moved around. When the edges of the window are within the Snapping distance (usually 10 pixels) of the Winamp Main Window, we move the window, using SetWindowPos, so that the plug-in window is touching the Winamp Main Window. If we are outside all of the Snapping regions, we just move the plug-in window with the mouse.

The code is very simple, but very tedious to replicate for all four sides of the Winamp Main Window. Luckily enough, you don't have to.

Tracking the Winamp Main Window

Docking the plug-in window to the Winamp Window is a straightforward, if a little monotonous, task. The docking works fine until you move the Winamp Window, then problems occur. As the plug-in window isn't aware of where the Winamp Main Window is, it will be left behind when the Winamp Window is moved. We therefore need some way to 'track' the Winamp Window such that if we are docked to it, the plug-in window will move with it when the Main Window is dragged.

One way to do this would be to call GetParent()->GetWindowRect() on a timer. This would allow the plug-in to keep track of where the Winamp Window is. However, this is a very CPU-intensive way of tracking Winamp. A better way to do it is to subclass the Winamp Main Window using SetWindowLong.

pOrigProc = (WNDPROC)SetWindowLong(plugin.hwndParent, 
                       GWL_WNDPROC, (LONG)HookWinampWnd);

The callback function now receives all of the Winamp Main Window's messages before Winamp does. This allows us to be informed when Winamp is moving by trapping the WM_MOVE message. When this message is encountered, we call a TrackWindow in the CWinampWnd class to track the Main Window.

LRESULT CALLBACK HookWinampWnd(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
    switch(uMsg) 
    { 
        case WM_MOVE:
            m_MainWnd.TrackWindow((int)(short) LOWORD(lParam),
                                     (int)(short) HIWORD(lParam));
            break;
        default:
            break;
    }

    // Call Winamp Window Proc
    return CallWindowProc(pOrigProc, hwnd, uMsg, wParam, lParam); 
}

Now, when Winamp is dragged, we get pulled along with it. Very cool.

Restrictive Resizing

Restrictive Resizing (I made this phrase up but it sounds technical enough), refers to allowing the window to be resized only in fixed size increments. This effect can be seen in the Winamp Playlist Editor. Grab the triangular shaped bit in the lower right corner and you will see the window can be resized but only in measured 'chunks'. The window also has a minimum size so that buttons and such are not lost off the window.

This is implemented in a very similar way to the docking code shown above. When the user clicks on the Resizing area of the window, SetCapture() is called and the mouse is tracked. When WM_MOUSEMOVE is called, the position of the mouse is read and compared with the position of the mouse when the resizing was initiated. If the mouse has moved more than the allocated size for the resize increment, the window is resized by this increment. If the mouse has not been moved far enough, the window remains the same size. The window is only resized if the mouse has moved beyond the next resize increment and the window is larger than its minimum size.

Again, very simple, but a neat addition to any user interface.

About the Demo

The demo shows a window derived from CWinampWnd which simply displays the current song name and playing time. It is very simple but demonstrates how to use the CWinampWnd class and I guess you can extend it to do whatever you wish. The Winamp IPC mechanism is very flexible so a lot more information can be gathered and displayed using it. Apologies in advance for the quality of the skin bitmap which is included with the demo. I have simply butchered the Winamp 'Playlist' skin and I have to admit that I am not much of an artist!

You will of course need Winamp to run the demo, and if you haven't got it, you should get it. It is an excellent example of original independent programming (well, it used to be independent).

Update: 6th June 2000

Fixed a memory leak spotted by Magnus Johansson. Thanks Magnus!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
James is currently working as a Software Engineer providing large scale Warehouse Management Systems and Airport Baggage Handling Systems. He is a Windows specialist but nowadays spends about 65% of his time fighting with VI in a vain attempt to get his UNIX C code to compile. He has been programming in C/C++ for 6 years and Visual C++/MFC for 4 years.

In his spare time James plays a variety of musical instruments including guitar and piano with varying degrees of success. He has been told he spends too much time and money in the pub but doesn't everyone have their own stool at the bar?

James is originally from Nottingham (no Robin Hood jokes please) but is now based in sunny Manchester, UK.

The attached photo shows James in his favourite position, drinking beer with a hand growing out of his neck.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Minhazul13-Jan-13 23:41
Minhazul13-Jan-13 23:41 
QuestionWinAmp "WindowShade" Pin
this that19-Feb-07 11:46
this that19-Feb-07 11:46 
QuestionQuestion about subclassing winamp with modern skin? Pin
zhangdeming20-Aug-06 23:15
zhangdeming20-Aug-06 23:15 
GeneralTransparent Edges Pin
vyjesh3-Jan-05 18:31
vyjesh3-Jan-05 18:31 
Generala problem when using this Demo as DLL in My application Pin
linberd8-Jan-04 16:13
linberd8-Jan-04 16:13 
GeneralRe: a problem when using this Demo as DLL in My application Pin
James Spibey8-Jan-04 21:50
James Spibey8-Jan-04 21:50 
Hi,

This article is about creating a Winamp plugin and therefore requires Winamp to run. If you wish to use the skinned features of the Gen_Cp.dll in your own application, simply derive your own class from CWinampWnd and remove all the code which is specific to Winamp.

Cheers

James
Generalregd general plugin for winamp Pin
karteek19-Oct-03 21:13
karteek19-Oct-03 21:13 
Generalregd general plugin for winamp Pin
karteek27-Sep-03 22:06
karteek27-Sep-03 22:06 
Generalcreating general plugins in VC++ for winamp Pin
karteek23-Sep-03 7:44
karteek23-Sep-03 7:44 
GeneralRe: creating general plugins in VC++ for winamp Pin
David Crow23-Sep-03 8:58
David Crow23-Sep-03 8:58 
GeneralRe: creating general plugins in VC++ for winamp Pin
karteek23-Sep-03 19:20
karteek23-Sep-03 19:20 
Generalcreating general plugin for winamp Pin
Anonymous17-Sep-03 5:56
Anonymous17-Sep-03 5:56 
GeneralRe: creating general plugin for winamp Pin
James Spibey17-Sep-03 6:47
James Spibey17-Sep-03 6:47 
Generalsuggested improvements Pin
gb1151-Apr-03 2:12
gb1151-Apr-03 2:12 
QuestionHow to use above demo ? Pin
pdtrung20-Feb-03 19:16
pdtrung20-Feb-03 19:16 
Questionhowto create a winamp-like application? Pin
Syl18-Nov-02 20:50
Syl18-Nov-02 20:50 
AnswerRe: howto create a winamp-like application? Pin
Christian Graus18-Nov-02 21:35
protectorChristian Graus18-Nov-02 21:35 
AnswerRe: howto create a winamp-like application? Pin
porfessorDavid20-Feb-06 19:20
porfessorDavid20-Feb-06 19:20 
Generalapp that makes all your windows snap Pin
Ivan Heckman28-Jun-02 12:16
Ivan Heckman28-Jun-02 12:16 
Generalwindow flickering Pin
13-May-02 6:17
suss13-May-02 6:17 
GeneralRe: window flickering Pin
22-Jun-02 21:39
suss22-Jun-02 21:39 
GeneralRe: window flickering Pin
Dmitry Kostenko6-Nov-02 5:40
Dmitry Kostenko6-Nov-02 5:40 
GeneralWinamp - DSP/Effect plugin - Subclassing - Crash! Pin
3-Apr-02 8:12
suss3-Apr-02 8:12 
GeneralRe: Winamp - DSP/Effect plugin - Subclassing - Crash! Pin
zamoeba14-Jul-05 10:21
zamoeba14-Jul-05 10:21 
GeneralPositioning bug Pin
26-Oct-01 10:58
suss26-Oct-01 10:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.