Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
9 Jun 2000 1  
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