AVIEDIT.C

/**************************************************************************** 
*
* AVIEDIT.C
*
* Sample program using the AVIFile read/write routines
*
**************************************************************************/
/**************************************************************************
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
* PURPOSE.
*
* Copyright 1992 - 1998 Microsoft Corporation. All Rights Reserved.
*
**************************************************************************/

#define STRICT
#define INC_OLE2
#include <windows.h>
#include <shellapi.h>
#include <windowsx.h>
#include <commdlg.h>
#include "muldiv32.h"
#include <vfw.h>

#include "aviedit.h"
#include "audio.h"

#include <limits.h>

#define GlobalSizePtr(lp) GlobalSize(GlobalPtrHandle(lp))
#define LPPAVIFILE PAVIFILE *
typedef BYTE * HPBYTE;
typedef UNALIGNED short * HPSHORT;

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static BOOL gfDefDlgEx = FALSE; //the recursion flag for message crackers

#define BUFSIZE 260
static char gszBuffer[BUFSIZE];
static char gszFileName[BUFSIZE];
static char gszSaveFileName[BUFSIZE];

#define AVI_EDIT_CLASS "edit"
static LPAVISTREAMINFO glpavisi;
static int gnSel;
int gSelectedStream = -1; // Highlight this text area when painting
RECT grcSelectedStream; // where to highlight

char gszFilter[512]; // for AVIBuildFilter - more than one string!
static HINSTANCE ghInstApp;
static HWND ghwndApp;
static HACCEL ghAccel;
static WNDPROC gOldEditProc;
static HWND ghwndEdit;

#define SCROLLRANGE 10000
#define MAXNUMSTREAMS 25

int gcpavi; // # of streams
PAVISTREAM gapavi[MAXNUMSTREAMS]; // the current streams
int gcpaviSel; // num of edit streams
PAVISTREAM gapaviSel[MAXNUMSTREAMS]; // edit streams to put on clipbd
int gStreamTop[MAXNUMSTREAMS+1];// y position of each stream
AVICOMPRESSOPTIONS gaAVIOptions[MAXNUMSTREAMS];// compression options
LPAVICOMPRESSOPTIONS galpAVIOptions[MAXNUMSTREAMS];
PGETFRAME gapgf[MAXNUMSTREAMS]; // data for decompressing video
HDRAWDIB ghdd[MAXNUMSTREAMS]; // drawdib handles
LONG galSelStart[MAXNUMSTREAMS];
LONG galSelLen[MAXNUMSTREAMS];
int giFirstAudio = -1; // 1st audio stream found
int giFirstVideo = -1; // 1st video stream found

#define gfVideoFound (giFirstVideo >= 0)
#define gfAudioFound (giFirstAudio >= 0)

BOOL gfPlaying; // are we currently playing?
LONG glPlayStartTime; // When did we start playing?
LONG glPlayStartPos; // Where were we on the scrollbar?
LONG timeStart; // cached start, end, length
LONG timeEnd;
LONG timeLength;
LONG timehscroll; // how much arrows scroll HORZ bar
int nVertSBLen; // vertical scroll bar
int nVertHeight;
DWORD gdwMicroSecPerPixel = 1000L; // scale for video
WORD gwZoom = 2; // one-half zoom (divide by 4)
HWND ghwndMCI;

// buffer for wave data
LPVOID lpAudio;

// constants for painting
#define VSPACE 8 // some vertical spacing
#define SELECTVSPACE 4 // height of selection line
#define HSPACE 4 // space between frames for video stream
#define TSPACE 10 // space for text area about each stream
#define AUDIOVSPACE 64 // height of an audio stream at X1 zoom

#define HIGHLIGHT (GetSysColor(COLOR_HIGHLIGHT) ? GetSysColor(COLOR_HIGHLIGHT) : GetSysColor(COLOR_ACTIVECAPTION))

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

// Macros to get and set the scroll bar to a given millisecond value in the
// movie. Movie lengths can be DWORDS but we only have 16 bits of resolution.

#define GetScrollTime(hwnd) \
(timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))

#define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
(int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/

LRESULT CALLBACK AppWndProc(HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK NewEditProc(HWND, UINT, WPARAM, LPARAM );
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int ErrMsg (LPSTR sz,...);

BOOL MenuHandler( HWND, int );

void editPaste(HWND hwnd, PAVIFILE pfile);
void FrameVideo(HDC hdc, RECT *rcFrame, HBRUSH hbr);

/*----------------------------------------------------------------------------*\
\*----------------------------------------------------------------------------*/
static int gfWait = 0;

/*----------------------------------------------------------------------------*\
| StartWait() |
| Start a wait operation... put up the hourglass if it's the first call |
\*----------------------------------------------------------------------------*/
void StartWait()
{
if (gfWait++ == 0) {
SetCursor(LoadCursor(NULL,IDC_WAIT));
}
}

/*----------------------------------------------------------------------------*\
| EndWait() |
| Once every one who started a wait is finished, go back to regular cursor |
\*----------------------------------------------------------------------------*/
void EndWait()
{
if (--gfWait == 0) {
SetCursor(LoadCursor(NULL,IDC_ARROW));
InvalidateRect(ghwndApp, NULL, TRUE);
}
}

/*----------------------------------------------------------------------------*\
| WinYield() |
| Code to yield while we're not calling GetMessage. |
| Dispatch all messages. Pressing ESC or closing aborts. |
\*----------------------------------------------------------------------------*/

BOOL WinYield(void)
{
MSG msg;
BOOL fAbort=FALSE;

while(gfWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
fAbort = TRUE;
if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
fAbort = TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return fAbort;
}


/*----------------------------------------------------------------------------*\
| PaintVideo() |
| Draw a video frame in the specified rect |
\*----------------------------------------------------------------------------*/

void PaintVideo(HDC hdc, RECT rcFrame, int iStream, LPBITMAPINFOHEADER lpbi, LONG lCurSamp, LONG lPos)
{
int iLen;
COLORREF nCol;
char szText[BUFSIZE];
RECT rc;

//
// If we have a picture, draw it
//
if (lpbi)
{
//
// use the palette of the first video stream
//
DrawDibDraw(ghdd[iStream], hdc,
rcFrame.left, rcFrame.top,
rcFrame.right - rcFrame.left,
rcFrame.bottom - rcFrame.top,
lpbi, NULL,
0, 0, -1, -1,
(iStream == giFirstVideo) ? 0 :DDF_BACKGROUNDPAL);

iLen = wsprintf(szText, "%ld %ld.%03lds",
lCurSamp, lPos / 1000, lPos % 1000);
}

//
// Before or after the movie (or read error) draw GRAY
//
else {
SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));

PatBlt(hdc,
rcFrame.left, rcFrame.top,
rcFrame.right - rcFrame.left,
rcFrame.bottom - rcFrame.top,
PATCOPY);
iLen = 0;
szText[0] = '\0';
}

//
// print something meaningful under the frame
//
rc.left = rcFrame.left;
rc.right = rcFrame.right - 2 * HSPACE; // don't overlap text areas
rc.top = rcFrame.bottom + HSPACE;
rc.bottom = rc.top + TSPACE + TSPACE; // blank out enough space
nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
ExtTextOut(hdc, rc.left, rc.top, ETO_CLIPPED | ETO_OPAQUE,
&rc, szText, iLen, NULL);
SetBkColor(hdc, nCol);
}


/*----------------------------------------------------------------------------*\
| PaintAudio() |
| Draw some samples of audio inside the given rectangle |
\*----------------------------------------------------------------------------*/

void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
{
PCMWAVEFORMAT wf;
int i;
int x,y;
int w,h;
BYTE b;
HBRUSH hbr;
RECT rc = *prc;
LONG lBytes;
LONG l, lLenOrig = lLen;
LONG lWaveBeginTime = AVIStreamStartTime(pavi);
LONG lWaveEndTime = AVIStreamEndTime(pavi);

//
// We can't draw before the beginning of the stream - adjust
//
if (lStart < lWaveBeginTime) {
lLen -= lWaveBeginTime - lStart;
lStart = lWaveBeginTime;
// right justify the legal samples in the rectangle - don't stretch
rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
}

//
// We can't draw past the end of the stream
//
if (lStart + lLen > lWaveEndTime) {
lLenOrig = lLen;
lLen = max(0, lWaveEndTime - lStart); // maybe nothing to draw!
// left justify the legal samples in the rectangle - don't stretch
rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
}

// Now start working with samples, not time
l = lStart;
lStart = AVIStreamTimeToSample(pavi, lStart);
lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;

//
// Get the format of the wave data
//
l = sizeof(wf);
AVIStreamReadFormat(pavi, lStart, &wf, &l);
if (!l)
return;

w = rc.right - rc.left;
h = rc.bottom - rc.top;

//
// We were starting before the beginning or continuing past the end.
// We're not painting in the whole original rect --- use a dark background
//
if (rc.left > prc->left) {
SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
rc.bottom - rc.top, PATCOPY);
}
if (rc.right < prc->right) {
SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
rc.bottom - rc.top, PATCOPY);
}

#define BACKBRUSH (GetSysColor(COLOR_BTNFACE)) // background
#define MONOBRUSH (GetSysColor(COLOR_BTNSHADOW)) // for mono audio
#define LEFTBRUSH (RGB(0,0,255)) // left channel
#define RIGHTBRUSH (RGB(0,255,0)) // right channel
#define HPOSBRUSH (RGB(255,0,0)) // current position

//
// Paint the background
//
hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
DeleteObject(SelectObject(hdc, hbr));

//
// !!! we can only paint PCM data right now. Sorry!
//
if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
return;

//
// How many bytes are we painting? Alloc some space for them
//
lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;
if (!lpAudio)
lpAudio = GlobalAllocPtr (GHND, lBytes);
else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);
if (!lpAudio)
return;

//
// Read in the wave data
//
AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);
if (l != lLen)
return;

#define MulDiv(a,b,c) (UINT)((DWORD)(UINT)(a) * (DWORD)(UINT)(b) / (UINT)(c))

//
// !!! Flickers less painting it NOW or LATER?
// First show the current position as a bar
//
hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
DeleteObject(SelectObject(hdc, hbr));

//
// Paint monochrome wave data
//
if (wf.wf.nChannels == 1) {

//
// Draw the x-axis
//
hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
y = rc.top + h/2;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

//
// 8 bit data is centred around 0x80
//
if (wf.wBitsPerSample == 8) {
for (x=0; x<w; x++) {

// which byte of audio data belongs at this pixel?
b = *((HPBYTE)lpAudio + muldiv32(x, lLen, w));

if (b > 0x80) {
i = y - MulDiv(b - 0x80, (h / 2), 128);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = y + MulDiv(0x80 - b, (h / 2), 128);
PatBlt(hdc, rc.left + x, y, 1, i - y, PATCOPY);
}
}
}

//
// 16 bit data is centred around 0x00
//
else if (wf.wBitsPerSample == 16) {
for (x=0; x<w; x++) {

// which byte of audio data belongs at this pixel?
// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w));

if (i > 0) {
i = y - (int) ((LONG)i * (h/2) / 32768);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = (int) ((LONG)i * (h/2) / 32768);
PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
}
}
DeleteObject(SelectObject(hdc, hbr));
} // endif mono

//
// Draw stereo waveform data
//
else if (wf.wf.nChannels == 2) {

//
// 8 bit data is centred around 0x80
//
if (wf.wBitsPerSample == 8) {

// Left channel
hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
y = rc.top + h/4;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

for (x=0; x<w; x++) {
b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);

if (b > 0x80) {
i = y - MulDiv(b-0x80,(h/4),128);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = y + MulDiv(0x80-b,(h/4),128);
PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
}
}
DeleteObject(SelectObject(hdc, hbr));

// Right channel
hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
y = rc.top + h * 3 / 4;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

for (x=0; x<w; x++) {
b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);

if (b > 0x80) {
i = y - MulDiv(b-0x80,(h/4),128);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = y + MulDiv(0x80-b,(h/4),128);
PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
}
}
DeleteObject(SelectObject(hdc, hbr));
}

//
// 16 bit data is centred around 0x00
//
else if (wf.wBitsPerSample == 16) {

// Left channel
hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
y = rc.top + h/4;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

for (x=0; x<w; x++) {

// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2);
if (i > 0) {
i = y - (int) ((LONG)i * (h/4) / 32768);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = (int) ((LONG)i * (h/4) / 32768);
PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
}
DeleteObject(SelectObject(hdc, hbr));

// Right channel
hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
y = rc.top + h * 3 / 4;
PatBlt(hdc, rc.left, y, w, 1, PATCOPY);

for (x=0; x<w; x++) {
// Don't make any assumptions about INT size !
i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
if (i > 0) {
i = y - (int) ((LONG)i * (h/4) / 32768);
PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
}
else {
i = (int) ((LONG)i * (h/4) / 32768);
PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
}
}
DeleteObject(SelectObject(hdc, hbr));
}
} // endif stereo
}

/*----------------------------------------------------------------------------*\
| PaintStuff() |
| Do our painting. Return the height of everything painted so we know how |
| much room it took to set our scrollbars. If fDrawEverything is TRUE, |
| we will not stop drawing just because we know we're drawing outside the |
| window. This is used to determine how much we would want to draw. |
\*----------------------------------------------------------------------------*/

int PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything)
{
int yStreamTop;
char szText[BUFSIZE];
int iFrameWidth, iLen;
LONG lSamp, lCurSamp;
int n;
int nFrames;
LPBITMAPINFOHEADER lpbi = NULL;
LONG l;
LONG lTime;
LONG lSize = 0;
LONG lAudioStart;
LONG lAudioLen;
RECT rcFrame, rcC;
int i;
HBRUSH hbr, hbrOld;
RECT rc;

GetClientRect(hwnd, &rcC);

//
// Look at scrollbars to find current position
//
lTime = GetScrollTime(hwnd);
yStreamTop = -GetScrollPos(hwnd, SB_VERT);

//
// Walk through all streams and draw something
//
for (i=0; i<gcpavi; i++) {
AVISTREAMINFO avis;
LONG lEnd, lEndTime;
COLORREF nCol;

//
// Remember where this stream begins
//
gStreamTop[i] = yStreamTop + GetScrollPos(hwnd, SB_VERT);

//
// Get some info about this stream
//
AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

//
// Highlight the stream name if we're supposed to
//
if (gSelectedStream == MAXNUMSTREAMS+i) {
hbr = CreateSolidBrush(HIGHLIGHT);
hbrOld = SelectObject(hdc, hbr);

PatBlt(hdc, 0, yStreamTop, rcC.right, TSPACE * 2, PATCOPY);
SelectObject(hdc, hbrOld);
DeleteObject(hbr);
}

// First we'll print out the stream name
nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
TextOut(hdc, HSPACE, yStreamTop, avis.szName, strlen(avis.szName));
SetBkColor(hdc, nCol);

// Skip to the next line
yStreamTop += 2*TSPACE;

if (galSelStart[i] == -1)
{
LoadString( ghInstApp, IDS_FORMAT_1, gszBuffer, BUFSIZE );
iLen = wsprintf(szText,
gszBuffer,
i,
(LPSTR) &avis.fccType, // compressor FOURCC
AVIStreamStart(gapavi[i]),
AVIStreamLength(gapavi[i]),
(AVIStreamEndTime(gapavi[i]) -
AVIStreamStartTime(gapavi[i])) / 1000);
}
else
{
LoadString( ghInstApp, IDS_FORMAT_2, gszBuffer, BUFSIZE );
iLen = wsprintf(szText,
gszBuffer,
i,
(LPSTR) &avis.fccType, // compressor FOURCC
AVIStreamStart(gapavi[i]),
AVIStreamLength(gapavi[i]),
(AVIStreamEndTime(gapavi[i]) -
AVIStreamStartTime(gapavi[i])) / 1000,
galSelStart[i], galSelStart[i] + galSelLen[i] - 1);
}
//
// Highlight the stream info if we're supposed to
//
if (gSelectedStream == i) {
hbr = CreateSolidBrush(HIGHLIGHT);
hbrOld = SelectObject(hdc, hbr);

PatBlt(hdc, 0, yStreamTop, rcC.right, TSPACE * 2, PATCOPY);
SelectObject(hdc, hbrOld);
DeleteObject(hbr);
}

nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
TextOut(hdc, HSPACE, yStreamTop, szText, iLen);
SetBkColor(hdc, nCol);

yStreamTop += TSPACE;
//
// Draw a VIDEO stream
//
if (avis.fccType == streamtypeVIDEO) {
if (gapgf[i] == NULL)
continue;

//
// Which frame belongs at this time?
//
lEndTime = AVIStreamEndTime(gapavi[i]);
if (lTime <= lEndTime)
lSamp = AVIStreamTimeToSample(gapavi[i], lTime);
else { // we've scrolled past the end of this stream
lEnd = AVIStreamEnd(gapavi[i]);
lSamp = lEnd + AVIStreamTimeToSample(gapavi[i],
lTime - lEndTime);
}

//
// how wide is each frame to paint?
//
iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
gwZoom / 4 + HSPACE;

//
// how many frames can we fit on each half of the screen
// not counting the one we'll centre?
//
nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
if (nFrames < 0)
nFrames = 0;

//
// Step through all the frames we'll draw
//
for (n = -nFrames; n <= nFrames; n++) {

//
// Each video stream is drawn as a horizontal line of
// frames, very close together.
// The first video stream shows a different frame in
// each square. Thus the scale of time is determined
// by the first video stream.
// Every other video stream shows whatever
// frame belongs at the time corresponding to the mid-
// point of each square.
//
if (i == giFirstVideo) {

//
// by definition, we know what frame we're drawing..
// (lSamp-n), (lSamp-(n-1)), ..., (lSamp), ...,
// (lSamp+n) (lSamp is the one in the centre)
//
lCurSamp = lSamp + n;

//
// what time is it at that frame? This number will
// be printed underneath the frame
//
l = AVIStreamSampleToTime(gapavi[i], lCurSamp);

} else { // NOT the first video stream

//
// What time is it at the left hand of the square
// we'll draw? That's what frame we use.
// Does the rounding with MulDiv32 have better properties
// than muldiv32? It appears to give _slightly_ better performance

l = lTime + MulDiv32(n * (iFrameWidth+HSPACE),
gdwMicroSecPerPixel, 1000);
//if (n<=0) { // calculate the time for a frame left of centre,
// // don't forget the HSPACE offset.
// l = lTime - muldiv32(-n * (iFrameWidth + HSPACE),
// gdwMicroSecPerPixel, 1000);
//
//}
//else { // frame is to right of centre.
// l = lTime + muldiv32(n * (iFrameWidth +HSPACE),
// gdwMicroSecPerPixel, 1000);
//
//}

//
// What frame belongs to that time?
//
lCurSamp = AVIStreamTimeToSample(gapavi[i], l);

//
// Use the exact time of that frame when printing
//
l = AVIStreamSampleToTime(gapavi[i], lCurSamp);
}

// !!!
// Could actually return an LPBI for invalid frames
// so we better force it to NULL.
//
if (gapgf[i] && lCurSamp >= AVIStreamStart(gapavi[i])) // &&
//lCurSamp <= AVIStreamEnd(gapavi[i]))
lpbi = AVIStreamGetFrame(gapgf[i], lCurSamp);
else
lpbi = NULL;

//
// Figure out where to draw this frame
//
rcFrame.left = rcC.right / 2 -
((avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4)
/ 2 + (n * iFrameWidth);
rcFrame.top = yStreamTop + TSPACE;
rcFrame.right = rcFrame.left +
(avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4;
rcFrame.bottom = rcFrame.top +
(avis.rcFrame.bottom - avis.rcFrame.top) * gwZoom / 4;

//
// If this frame is selected, highlight it
//
if (lCurSamp >= galSelStart[i] &&
lCurSamp < galSelStart[i] +
galSelLen[i]) {
hbr = CreateSolidBrush(HIGHLIGHT);
}
else { //not highlighted - but need to clear area around frame
// of selection
hbr = CreateSolidBrush(GetBkColor(hdc));
}

FrameVideo(hdc, &rcFrame, hbr);
DeleteObject (hbr);

//
// draw a border around the centre frame.
//
if (n == 0) {
hbr = CreateSolidBrush(RGB(255,0,0));
InflateRect(&rcFrame, 1, 1);
FrameRect(hdc, &rcFrame, hbr);
InflateRect(&rcFrame, -1, -1);
DeleteObject (hbr);
}

//
// Now draw the video frame in the computed rectangle
//
PaintVideo(hdc, rcFrame, i, lpbi, lCurSamp, l);
}

//
// Print a description of this stream
//
if (lpbi)
AVIStreamSampleSize(gapavi[i], lSamp, &lSize);

//
// Move down to where we can draw the next stream
//
yStreamTop += TSPACE +
(rcFrame.bottom - rcFrame.top) +
TSPACE;
}

//
// Draw an AUDIO stream
//
else if (avis.fccType == streamtypeAUDIO) {

//
// Figure out which samples are visible
//
lAudioStart = lTime - muldiv32(rcC.right / 2,
gdwMicroSecPerPixel, 1000);
lAudioLen = 2 * (lTime - lAudioStart);

// clear the selection area

hbr = CreateSolidBrush(GetBkColor(hdc));
hbrOld = SelectObject(hdc, hbr);
PatBlt(hdc,
0,
yStreamTop + TSPACE - SELECTVSPACE,
rcC.right,
SELECTVSPACE,
PATCOPY);
PatBlt(hdc,
0,
yStreamTop + TSPACE + (AUDIOVSPACE * gwZoom/4),
rcC.right,
SELECTVSPACE,
PATCOPY);
SelectObject(hdc, hbrOld);
DeleteObject (hbr);

//
// We have a selection... Highlight it
//
if (galSelStart[i] != -1) {
LONG lSelSt, lSelLen;

//
// What time is our selection?
//
lSelSt = AVIStreamSampleToTime(gapavi[i], galSelStart[i]);
lSelLen = AVIStreamSampleToTime(gapavi[i], galSelLen[i]);

//
// At what pixels is our selection?
//
if (lSelSt < lTime) { //selecting to the left of the current position
rc.left = rcC.right /2 - (int)
muldiv32((lTime - lSelSt) , 1000, gdwMicroSecPerPixel);
}
else {
rc.left = rcC.right / 2 + (int)
muldiv32(lSelSt - lTime, 1000, gdwMicroSecPerPixel);
}
rc.right = rc.left + (int)
muldiv32(lSelLen, 1000, gdwMicroSecPerPixel);

// Selection starts way past left side of screen
if (lSelSt < lAudioStart)
rc.left = 0;
// Selection is off, screen left
if (lSelSt + lSelLen < lAudioStart)
rc.right = - SELECTVSPACE;

// Selection is off, screen right
if (lSelSt > lAudioStart + lAudioLen)
rc.left = rcC.right + SELECTVSPACE;

// Selection ends past the right side of the screen
if (lSelSt + lSelLen > lAudioStart + lAudioLen)
rc.right = rcC.right;
if (rc.right == rc.left) // draw SOMEthing.
rc.right++;
rc.top = yStreamTop + TSPACE;
rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

//
// Draw some indication
//
hbr = CreateSolidBrush(HIGHLIGHT);
hbrOld = SelectObject(hdc, hbr);
PatBlt(hdc,
rc.left - SELECTVSPACE,
rc.top - SELECTVSPACE,
rc.right - rc.left + 2 * SELECTVSPACE,
SELECTVSPACE,
PATCOPY);
PatBlt(hdc,
rc.left - SELECTVSPACE,
rc.bottom,
rc.right - rc.left + 2 * SELECTVSPACE,
SELECTVSPACE,
PATCOPY);
SelectObject(hdc, hbrOld);
DeleteObject (hbr);
}

//
// Make the rectangle to draw audio into
//
rc.left = rcC.left;
rc.right = rcC.right;
rc.top = yStreamTop + TSPACE;
rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;

//
// Actually paint the audio
//
PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);

//
// Print the time at the centre of the audio stream
//
iLen = wsprintf(szText, "%ld.%03lds", lTime/1000, lTime%1000);
rc.left = (rc.right - rc.left) / 2 - 5 * HSPACE;
rc.right = (rc.right - rc.left) / 2;
rc.top = rc.bottom + HSPACE; rc.bottom = rc.top + VSPACE;
nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
ExtTextOut(hdc, rc.left, rc.top, 0, &rc, szText, iLen, NULL);
SetBkColor(hdc, nCol);

//
// Move down to where we can draw the next stream
//
yStreamTop += TSPACE + AUDIOVSPACE * gwZoom / 4;

}

yStreamTop += TSPACE + TSPACE;

//
// Give up once we're painting below the bottom of the window
//
if (!fDrawEverything && yStreamTop >= rcC.bottom)
break;
}

// The bottom of all the streams;
gStreamTop[gcpavi] = yStreamTop + GetScrollPos(hwnd, SB_VERT);

//
// How many lines did we draw?
//
return yStreamTop + GetScrollPos(hwnd, SB_VERT);
}

/*----------------------------------------------------------------------------*\
| FixScrollBars() |
| When we load a file or zoom changes, we re-set the scrollbars |
\*----------------------------------------------------------------------------*/


void FixScrollbars(HWND hwnd)
{
int nHeight = 0;
RECT rc;
HDC hdc;

//
// Determine how tall our window needs to be to display everything.
//
hdc = GetDC(NULL);
ExcludeClipRect(hdc, 0, 0, 32767, 32767); // don't actually draw
nHeight = PaintStuff(hdc, hwnd, TRUE);
ReleaseDC(NULL, hdc);

//
// Set vertical scrollbar for scrolling the visible area
//
GetClientRect(hwnd, &rc);
nVertHeight = nHeight; // total height in pixels of entire display

//
// We won't fit in the window... need scrollbars
//
if (nHeight > rc.bottom)
{
nVertSBLen = nHeight - rc.bottom;
SetScrollRange(hwnd, SB_VERT, 0, nVertSBLen, TRUE);
SetScrollPos(hwnd, SB_VERT, 0, TRUE);

//
// We will fit in the window! No scrollbars necessary
//
}
else
{
nVertSBLen = 0;
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
}
return;
}



/*----------------------------------------------------------------------------*\
| InitStreams() |
| Initialize the streams of a loaded file -- the compression options, the |
| DrawDIB handles, and the scroll bars. |
| !!! This clears the compression options right now every time it's called! |
\*----------------------------------------------------------------------------*/

void InitStreams(HWND hwnd)
{
AVISTREAMINFO avis;
LONG lTemp;
int i;

//
// Start with bogus times
//
timeStart = 0x7FFFFFFF;
timeEnd = 0;

//
// Walk through and init all streams loaded
//
for (i = 0; i < gcpavi; i++) {

AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

//
// Save and SaveOptions code takes a pointer to our compression opts
//
galpAVIOptions[i] = &gaAVIOptions[i];

//
// clear options structure to zeroes
//
_fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));

//
// Initialize the compression options to some default stuff
// !!! Pick something better
//
galpAVIOptions[i]->fccType = avis.fccType;

switch(avis.fccType) {

case streamtypeVIDEO:
galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
galpAVIOptions[i]->fccHandler = 0;
galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
galpAVIOptions[i]->dwKeyFrameEvery = (DWORD)-1; // Default
galpAVIOptions[i]->dwBytesPerSecond = 0;
galpAVIOptions[i]->dwInterleaveEvery = 1;
break;

case streamtypeAUDIO:
galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
galpAVIOptions[i]->dwInterleaveEvery = 1;
AVIStreamReadFormat(gapavi[i],
AVIStreamStart(gapavi[i]),
NULL,
&lTemp);
galpAVIOptions[i]->cbFormat = lTemp;
if (lTemp)
galpAVIOptions[i]->lpFormat = GlobalAllocPtr(GHND, lTemp);
// Use current format as default format
if (galpAVIOptions[i]->lpFormat)
AVIStreamReadFormat(gapavi[i],
AVIStreamStart(gapavi[i]),
galpAVIOptions[i]->lpFormat,
&lTemp);
break;

default:
break;
}

//
// We're finding the earliest and latest start and end points for
// our scrollbar.
//
timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
timeEnd = max(timeEnd, AVIStreamEndTime(gapavi[i]));

//
// Initialize video streams for getting decompressed frames to display
//
if (avis.fccType == streamtypeVIDEO) {

gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);

if (gapgf[i] == NULL)
continue;

ghdd[i] = DrawDibOpen();
// !!! DrawDibBegin?

if (!gfVideoFound) {
DWORD dw;

//
// Remember the first video stream --- treat it specially
//
giFirstVideo = i;

//
// Set the horizontal scrollbar scale to show every frame
// of the first video stream exactly once
//
dw = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 + HSPACE;

gdwMicroSecPerPixel = muldiv32(1000000,
avis.dwScale,
dw * avis.dwRate);
// Move one frame on the top video screen for each HSCROLL
timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
}

} else if (avis.fccType == streamtypeAUDIO) {

// These aren't used and better be NULL!
gapgf[i] = ghdd[i] = NULL;

//
// If there are no video streams, we base everything on this
// audio stream.
//
if (!gfAudioFound && !gfVideoFound) {

// Show one sample per pixel
gdwMicroSecPerPixel = muldiv32(1000000,
avis.dwScale,
avis.dwRate);
// Move one sample per HSCROLL
// Move at least enough to show movement
timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
}

//
// Remember the first audio stream --- treat it specially
//
if (!gfAudioFound)
giFirstAudio = i;

}

}

timeLength = timeEnd - timeStart;

if (timeLength == 0)
timeLength = 1;

// Make sure HSCROLL scrolls enough to be noticeable.
timehscroll = max(timehscroll, timeLength / SCROLLRANGE + 2);

SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
SetScrollTime(hwnd, timeStart);

FixScrollbars(hwnd);
}


/*----------------------------------------------------------------------------*\
| FixWindowTitle() |
| Update the window title to reflect what's loaded |
\*----------------------------------------------------------------------------*/

void FixWindowTitle(HWND hwnd)
{
char szTitle[2*BUFSIZE];

LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );

wsprintf(szTitle, "%s %s", (LPSTR)gszBuffer, (LPSTR)gszFileName);

SetWindowText( hwnd, szTitle );

InvalidateRect(hwnd, NULL, TRUE);
}

/*----------------------------------------------------------------------------*\
| FreeDrawStuff() |
| Free up the resources associated with DrawDIB. |
\*----------------------------------------------------------------------------*/

void FreeDrawStuff(HWND hwnd)
{
int i;

// Make sure we're not playing!
aviaudioStop();

for (i = 0; i < gcpavi; i++) {
if (gapgf[i]) {
AVIStreamGetFrameClose(gapgf[i]);
gapgf[i] = NULL;
}
if (ghdd[i]) {
DrawDibClose(ghdd[i]);
ghdd[i] = 0;
}
}
SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
giFirstVideo = giFirstAudio = -1;
}


/*----------------------------------------------------------------------------*\
| NukeAVIStream() |
| Get rid of a stream in our array and compact it. |
\*----------------------------------------------------------------------------*/

void NukeAVIStream(int i)
{
int j;

//
// Make sure it's a real stream number
//
if (i < 0 || i >=gcpavi)
return;

//
// Free all the resources associated with this stream
//
AVIStreamRelease(gapavi[i]);
if (galpAVIOptions[i]->lpFormat) {
GlobalFreePtr(galpAVIOptions[i]->lpFormat);
}
if (gapgf[i]) {
AVIStreamGetFrameClose(gapgf[i]);
gapgf[i] = NULL;
}
if (ghdd[i]) {
DrawDibClose(ghdd[i]);
ghdd[i] = 0;
}

//
// Compact the arrays of junk
//
for (j = i; j < gcpavi - 1; j++) {
gapavi[j] = gapavi[j+1];
galpAVIOptions[j] = galpAVIOptions[j+1];
gapgf[j] = gapgf[j+1];
ghdd[j] = ghdd[j+1];
}

gcpavi--;
}

/*----------------------------------------------------------------------------*\
| FreeAVI() |
| Free the resources associated with an open file. |
\*----------------------------------------------------------------------------*/

void FreeAvi(HWND hwnd)
{
int i;

FreeDrawStuff(hwnd);

AVISaveOptionsFree(gcpavi, galpAVIOptions);

for (i = 0; i < gcpavi; i++) {
AVIStreamRelease(gapavi[i]);
}

// Good a place as any to make sure audio data gets freed
if (lpAudio)
GlobalFreePtr(lpAudio);
lpAudio = NULL;

gcpavi = 0;
}

/*----------------------------------------------------------------------------*\
| InsertAVIFile() |
| Put a new AVI file into our internal structures. |
\*----------------------------------------------------------------------------*/

void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPSTR lpszFile)
{
int i;
PAVISTREAM pavi;

for (i = gcpavi; i <= MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i - gcpavi) != AVIERR_OK)
break;
if (i == MAXNUMSTREAMS)
{
AVIStreamRelease(pavi);
LoadString( ghInstApp, IDS_MAXSTREAMS, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
break;
}
if (CreateEditableStream(&gapavi[i], pavi) != AVIERR_OK) {
AVIStreamRelease(pavi);
break;
}
AVIStreamRelease(pavi);
galSelStart[i] = galSelLen[i] = -1;
}

AVIFileRelease(pfile);

if (gcpavi == i && i != MAXNUMSTREAMS)
{

LoadString( ghInstApp, IDS_NOOPEN, gszBuffer, BUFSIZE );

ErrMsg(gszBuffer, lpszFile);
return;
}

FreeDrawStuff(hwnd);
gcpavi = i;
InitStreams(hwnd);
FixWindowTitle(hwnd);
}


/*----------------------------------------------------------------------------*\
| InitAVI() |
| Open up a file through the AVIFile handlers. |
\*----------------------------------------------------------------------------*/


void InitAvi(HWND hwnd, LPSTR szFile, int nMenu)
{
HRESULT hr;
PAVIFILE pfile;

hr = AVIFileOpen(&pfile, szFile, 0, 0L);

if (hr != 0)
{
LoadString( ghInstApp, IDS_NOOPEN, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer, szFile);
return;
}

//
// If we're opening something new, close other open files, otherwise
// just close the draw stuff so we'll merge streams with the new file
//
if (nMenu == MENU_OPEN)
FreeAvi(hwnd);

InsertAVIFile(pfile, hwnd, szFile);
}

/*----------------------------------------------------------------------------*\
| DropAvi() |
| Allow a drag/drop on AVIEdit. |
\*----------------------------------------------------------------------------*/
void DropAvi(HWND hwnd, HDROP hDrop)
{
char szPath[BUFSIZE];
UINT nDropped, n;
PAVIFILE pfile;
HRESULT hr;

// Get number of files dropped
nDropped = DragQueryFile(hDrop,0xFFFF,NULL,0);

if (nDropped) {
SetActiveWindow(hwnd);

// If we wanted to, we could simulate a click at the position
// the drop took place....

for (n = 0; n < nDropped; n++) {
// Get the file that was dropped....
DragQueryFile(hDrop, n, szPath, BUFSIZE);

hr = AVIFileOpen(&pfile, szPath, 0, 0L);

if (hr == 0) {
// ... and paste it in.
editPaste(hwnd, pfile);
}
}
}
DragFinish(hDrop); /* Delete structure alocated */
}

/*----------------------------------------------------------------------------*\
| AppInit( hInst, hPrev) |
| |
| Description: |
| This is called when the application is first loaded into |
| memory. It performs all initialization that doesn't need to be done |
| once per instance. |
| |
| Arguments: |
| hInstance instance handle of current instance |
| hPrev instance handle of previous instance |
| |
| Returns: |
| TRUE if successful, FALSE if not |
| |
\*----------------------------------------------------------------------------*/

BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw, LPSTR szCmdLine)
{
WNDCLASS cls;
WORD wVer;

/* first let's make sure we are running on 1.1 */
wVer = HIWORD(VideoForWindowsVersion());
if (wVer < 0x010a)
{
char szTitle[BUFSIZE];
/* oops, we are too old, blow out of here */
MessageBeep(MB_ICONHAND);
LoadString( ghInstApp, IDS_APPERR, szTitle, BUFSIZE );
LoadString( ghInstApp, IDS_OLDVFW, gszBuffer, BUFSIZE );

MessageBox(NULL, gszBuffer, szTitle, MB_OK|MB_ICONSTOP);
return FALSE;
}

//
// Save instance handle for DialogBoxs
//
ghInstApp = hInst;

ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

//
// Did we get passed a filename on the command line? We'll open it at create
// time.
//
if (szCmdLine && szCmdLine[0])
lstrcpy(gszFileName, szCmdLine);
else
lstrcpy( gszFileName, "" );

lstrcpy( gszSaveFileName, "" );

if (!hPrev) {
/*
* Register a class for the main application window
*/
cls.hCursor = LoadCursor(NULL,IDC_ARROW);
cls.hIcon = LoadIcon(hInst,MAKEINTATOM(ID_APP));
cls.lpszMenuName = MAKEINTATOM(ID_APP);
cls.lpszClassName = MAKEINTATOM(ID_APP);
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
cls.hInstance = hInst;
cls.style = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW |
CS_DBLCLKS;
cls.lpfnWndProc = (WNDPROC)AppWndProc;
cls.cbWndExtra = 0;
cls.cbClsExtra = 0;

if (!RegisterClass(&cls))
return FALSE;
}

//
// Must be called before using any of the AVIFile routines
//
AVIFileInit();

LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );
//
// Create our main application window
//
ghwndApp = CreateWindow (
MAKEINTATOM(ID_APP), // Class name
gszBuffer, // Caption
WS_OVERLAPPEDWINDOW, // Style bits
CW_USEDEFAULT, 0, // Position
320,300, // Size
(HWND)NULL, // Parent window (no parent)
(HMENU)NULL, // use class menu
hInst, // handle to window instance
(LPSTR)NULL // no params to pass on
);
ShowWindow(ghwndApp,sw);

return TRUE;
}

/*----------------------------------------------------------------------------*\
| WinMain( hInst, hPrev, lpszCmdLine, cmdShow ) |
| |
| Description: |
| The main procedure for the App. After initializing, it just goes |
| into a message-processing loop until it gets a WM_QUIT message |
| (meaning the app was closed). If the preview is playing it adjusts |
| the scrollbar appropriately. |
| |
| Arguments: |
| hInst instance handle of this instance of the app |
| hPrev instance handle of previous instance, NULL if first |
| szCmdLine ->null-terminated command line |
| cmdShow specifies how the window is initially displayed |
| |
| Returns: |
| The exit code as specified in the WM_QUIT message. |
| |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
MSG msg;

//
// Call our initialization procedure
//
if (!AppInit(hInst, hPrev, sw, szCmdLine))
return FALSE;

/*
* Polling messages from event queue
*/
for (;;)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
return msg.wParam;

if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
continue;

TranslateMessage(&msg);
DispatchMessage(&msg);
}

//
// If we have no messages to dispatch, we do our background task...
// If we're playing a file, we set the scroll bar to show the video
// frames corresponding with the current playing audio sample
//
if (gfPlaying) {
LONG l;

//
// Use the audio clock to tell how long we've been playing. To
// maintain sync, it's important we use this clock.
//
l = aviaudioTime(); // returns -1 if no audio playing

//
// If we can't use the audio clock to tell us how long we've been
// playing, calculate it ourself
//
if (l == -1)
l = timeGetTime() - glPlayStartTime + glPlayStartPos;

if (l != (LONG)GetScrollTime(ghwndApp)) {
if (l < timeStart) // make sure number isn't out of bounds
l = timeStart;
if (l > timeEnd) // looks like we're all done!
FORWARD_WM_COMMAND(ghwndApp, MENU_STOP, NULL, 0, SendMessage);

SetScrollTime(ghwndApp, l);
InvalidateRect(ghwndApp, NULL, FALSE);
UpdateWindow(ghwndApp);

continue;
}
}

WaitMessage();
}

/* NOT REACHED */
}

/*----------------------------------------------------------------------------*\
| SelectStream() |
| |
| Selects a portion of a stream i: |
| |
| i == -1 means clear all selections |
| start == -1 means clear that individual stream's selection |
| fAdd == TRUE means extend that stream's selection to include the new |
| range (will otherwise just replace the selection) |
| fAll == TRUE means select this range in every stream, not just i |
\*----------------------------------------------------------------------------*/

void SelectStream(HWND hwnd, int i, LONG start, LONG length, BOOL fAdd, BOOL fAll)
{
int n, j;
LONG mystart, mylength;
RECT rc;

//
// Clear all selections
//
if (i == -1) {
for (n = 0; n < gcpavi; n++)
galSelStart[n] = galSelLen[n] = -1;

} else if (i >= 0 && i < gcpavi) { // valid stream number

//
// We've been told to clear this selection
//
if (start == -1 || length == -1)
galSelStart[i] = galSelLen[i] = -1;

//
// Is this a valid selection range?
//
if (start >=AVIStreamStart(gapavi[i]) &&
start < AVIStreamEnd(gapavi[i]) &&
length >= 1) {

//
// Do we select the same range in every stream or just one?
//
for (j = (fAll ? 0 : i); j < (fAll ? gcpavi : i+1); j++) {

//
// Translate for each stream the equivalent region to select
//
if (j == i) {
mystart = start; mylength = length;
} else {
mystart = AVIStreamSampleToSample(gapavi[j],
gapavi[i], start);
mylength = max(1, AVIStreamSampleToSample(gapavi[j],
gapavi[i], length)); // at least 1
// !!! Better invalidate this entire stream since we're not
// sure what part needs repainting.
GetClientRect(hwnd, &rc);
rc.top = gStreamTop[j] - GetScrollPos(hwnd, SB_VERT);
rc.bottom = gStreamTop[j+1] - GetScrollPos(hwnd, SB_VERT);
InvalidateRect(hwnd, &rc, FALSE);
}

//
// Verify we got good selection values
//
if (mystart < AVIStreamStart(gapavi[j])) {
mylength -= AVIStreamStart(gapavi[j]) - mystart;
mystart = AVIStreamStart(gapavi[j]);
}
if (mystart + mylength > AVIStreamEnd(gapavi[j]))
mylength -= mystart + mylength - AVIStreamEnd(gapavi[j]);
if (mylength <= 0)
mystart = -1;
if (mystart == -1) // nothing to select in this stream
continue;

//
// Reset selection to new values
//
if (!fAdd || galSelStart[j] == -1) {
galSelStart[j] = mystart;
galSelLen[j] = mylength;

//
// extend selection to include this new range
//
} else {
if (mystart < galSelStart[j]) {
galSelLen[j] += galSelStart[j] - mystart;
galSelStart[j] = mystart;
}
if (mystart + mylength > galSelStart[j] + galSelLen[j])
galSelLen[j] = mystart + mylength - galSelStart[j];
}
}
}
}
}

/*----------------------------------------------------------------------------*\
| EditStreamName() |
| |
| We need to edit the name of a stream. Create the edit box. |
\*----------------------------------------------------------------------------*/

void EditStreamName(HWND hwndParent)

{
AVISTREAMINFO avis;

// Get the stream info so we can get the name
AVIStreamInfo(gapavi[gSelectedStream-MAXNUMSTREAMS], &avis, sizeof(avis));

// Create the window;
InflateRect(&grcSelectedStream, 0, 2);
ghwndEdit = CreateWindow(AVI_EDIT_CLASS, NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
grcSelectedStream.left,
grcSelectedStream.top,
grcSelectedStream.right - grcSelectedStream.left,
grcSelectedStream.bottom - grcSelectedStream.top,
hwndParent, (HMENU)1, ghInstApp, NULL);

// Subclass the window so we can trap <cr> hits.
gOldEditProc = (WNDPROC)GetWindowLong(ghwndEdit, GWL_WNDPROC);
SetWindowLong(ghwndEdit, GWL_WNDPROC, (long)NewEditProc);

// Set the initial text of the edit window, give focus to the
// window and select the text.
SetWindowText(ghwndEdit, avis.szName);
SetFocus(ghwndEdit);
Edit_SetSel(ghwndEdit, 0, lstrlen(avis.szName));
}

/*----------------------------------------------------------------------------*\
| EditDone() |
| |
| Done with an edit. See if we take the changes. |
\*----------------------------------------------------------------------------*/

void EditDone(HWND hwndParent, BOOL bAcceptChange)
{
// Update the stream name if we're supposed to.
if (bAcceptChange) {
char szBuff[BUFSIZE];
int n;

// Get the edited name and put into the stream header
n = GetWindowText(ghwndEdit, szBuff, BUFSIZE);
szBuff[n] = '\0';
EditStreamSetName(gapavi[gSelectedStream-MAXNUMSTREAMS], szBuff);
}

// Turn the selection off.
gSelectedStream = -1;

// Nuke the edit window.
SetWindowLong(ghwndEdit, GWL_WNDPROC, (long)gOldEditProc);
DestroyWindow(ghwndEdit);
ghwndEdit = NULL;

// Paint where window used to be.
InvalidateRect(hwndParent, &grcSelectedStream, TRUE);
UpdateWindow(hwndParent);

// Give the parent the focus back.
SetFocus(hwndParent);
}

/*----------------------------------------------------------------------------*\
| NewEditProc() |
| |
| Our own home-rolled window proc for the edit window giving |
| notification when <cr> is hit. |
| |
| We trap WM_CHAR, because WM_CHAR(wParam='\r') causes a beep, which |
| we don't want. |
\*----------------------------------------------------------------------------*/
#define _ANSI_R (TCHAR)'\r'
LRESULT CALLBACK NewEditProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch (msg) {
case WM_CHAR:
// Trap a keydown for <cr>
if (wParam == _ANSI_R) {
// Send message to parent giving ID 2 (which doesn't
// exist)
FORWARD_WM_COMMAND(GetParent(hwnd), 2, NULL, 0, PostMessage);
return 0L;
}

// Need normal handling of characters otherwise
goto callDWP;

case WM_KEYUP:
// Trap a keydown for <Esc>. This is how we get out without
// making a modification.
if (wParam == VK_ESCAPE) {
// Send message to parent giving ID 3 (which doesn't
// exist)
FORWARD_WM_COMMAND(GetParent(hwnd), 3, NULL, 0, PostMessage);
return 0L;
}

// We want to fall through so what should happen on keydown
// does.

default:
callDWP:
// Just call the old window proc
return CallWindowProc(gOldEditProc, hwnd, msg, wParam, lParam);
}
}

// ******************************************************************************
//
// Message Handler for WM_CREATE
//
// ******************************************************************************

BOOL App_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
DragAcceptFiles(hwnd, TRUE);

if (gszFileName[0])
InitAvi(hwnd, gszFileName, MENU_OPEN);

return TRUE;
}

// ******************************************************************************
//
// Message Handler for WM_COMMAND
//
// ******************************************************************************

void App_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
if (id == 2)
EditDone(hwnd, TRUE);
// See if we are terminating editting.
else if (id == 3)
EditDone(hwnd, FALSE);
else
MenuHandler( hwnd, id );
}

// ******************************************************************************
//
// Message Handler for WM_DROPFILES
//
// ******************************************************************************
//
void App_OnDropFiles(HWND hwnd, HDROP hdrop)
{
DropAvi(hwnd, hdrop);

return;
}

// ******************************************************************************
//
// Message Handler for WM_INITMENU
//
// ******************************************************************************
void App_OnInitMenu(HWND hwnd, HMENU hMenu)
{
int i;
BOOL f;
PAVIFILE pf;

f = gcpavi > 0;
EnableMenuItem(hMenu, MENU_SAVEAS, f ? MF_ENABLED : MF_GRAYED);
EnableMenuItem(hMenu, MENU_OPTIONS,f ? MF_ENABLED : MF_GRAYED);

EnableMenuItem(hMenu, MENU_CLOSE, f ? MF_ENABLED : MF_GRAYED);
EnableMenuItem(hMenu, MENU_MERGE, f ? MF_ENABLED : MF_GRAYED);

EnableMenuItem(hMenu, MENU_SETINFO, f ? MF_ENABLED : MF_GRAYED);

// !!! Why not provide UNDO while I'm at it?
// Enable CUT/COPY/DELETE if there's something selected in a stream
f = FALSE;
for (i=0; i<gcpavi; i++)
if (galSelStart[i] != -1)
f = TRUE;

EnableMenuItem(hMenu, MENU_COPY, f ? MF_ENABLED : MF_GRAYED);
EnableMenuItem(hMenu, MENU_CUT, f ? MF_ENABLED : MF_GRAYED);
EnableMenuItem(hMenu, MENU_DELETE, f ? MF_ENABLED : MF_GRAYED);

// If we haven't an edit window, we need to setup the "Name'
//
if (ghwndEdit == NULL)
{
LoadString( ghInstApp, IDS_NAME, gszBuffer, BUFSIZE );
ModifyMenu(hMenu, MENU_NAME, MF_BYCOMMAND | MF_STRING, MENU_NAME, gszBuffer);
EnableMenuItem(hMenu, MENU_NAME,
(gSelectedStream >= MAXNUMSTREAMS) ? MF_ENABLED : MF_GRAYED);
}
else
{
LoadString( ghInstApp, IDS_ABORTNAME, gszBuffer, BUFSIZE );
ModifyMenu(hMenu, MENU_NAME, MF_BYCOMMAND | MF_STRING,MENU_NAME, gszBuffer);
EnableMenuItem(hMenu, MENU_NAME,MF_ENABLED);

} 

// See if there's anything to paste....
f = FALSE;
AVIGetFromClipboard(&pf);

if (pf)
{
f = TRUE;
AVIFileRelease(pf);
}

EnableMenuItem(hMenu, MENU_PASTE, f ? MF_ENABLED : MF_GRAYED);

f = gfAudioFound | gfVideoFound;
EnableMenuItem(hMenu, MENU_PREVIEW, (f & !gfPlaying) ? MF_ENABLED : MF_GRAYED);
EnableMenuItem(hMenu, MENU_STOP, (f & gfPlaying) ? MF_ENABLED : MF_GRAYED);

CheckMenuItem(hMenu, MENU_ZOOMQUARTER, (gwZoom == 1) ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_ZOOMHALF, (gwZoom == 2) ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_ZOOM1, (gwZoom == 4) ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_ZOOM2, (gwZoom == 8) ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_ZOOM4, (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);


return;
}
// ******************************************************************************
//
// Message Handler for WM_SIZE
//
// ******************************************************************************

void App_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
RECT rc;

GetClientRect(hwnd, &rc);
//
// There is not enough vertical room to show all streams. Scrollbars
// are required.
//
if (nVertHeight > rc.bottom)
{
nVertSBLen = nVertHeight - rc.bottom;
SetScrollRange(hwnd, SB_VERT, 0, nVertSBLen, TRUE);
}
else// Everything fits vertically. No scrollbar necessary.
{
nVertSBLen = 0;
SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
}
return;
}
// ******************************************************************************
//
// Message Handler for WM_DESTROY
//
// ******************************************************************************
//
void App_OnDestroy(HWND hwnd)
{
AVIClearClipboard();
FreeAvi(hwnd); // close all open streams
AVIFileExit(); // shuts down the AVIFile system
PostQuitMessage(0);

return;
}
// ******************************************************************************
//
// Message Handler for WM_ENDSESSION
//
// ******************************************************************************
//
void App_OnEndSession(HWND hwnd, BOOL fEnding)
{
if (fEnding)
{
if (GetClipboardOwner() == hwnd)
{
if (OpenClipboard(hwnd))
{
EmptyClipboard();
CloseClipboard();
}
}
FreeAvi(hwnd);
}
return;
}
// ******************************************************************************
//
// Message Handler for WM_PALETTECHANGED
//
// ******************************************************************************
//
void App_OnPaletteChanged(HWND hwnd, HWND hwndPaletteChange)
{
// It came from us. Ignore it
if (hwndPaletteChange == hwnd)
return;

//if needed, insert any calls - such as RealizePallete - below

return;
}
// ******************************************************************************
//
// Message Handler for WM_KEYDOWN
//
// ******************************************************************************
//
void App_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
if(fDown)
{
switch (vk)
{
case VK_UP:
FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEUP, 0, PostMessage);
break;
case VK_DOWN:
FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEDOWN, 0, PostMessage);
break;
case VK_PRIOR:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_PAGEUP, 0, PostMessage);
break;
case VK_NEXT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_PAGEDOWN, 0, PostMessage);
break;
case VK_HOME:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_THUMBPOSITION, 0, PostMessage);
break;
case VK_END:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_THUMBPOSITION, 0x7FFF, PostMessage);
break;
case VK_LEFT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEUP, 0, PostMessage);
break;
case VK_RIGHT:
FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEDOWN, 0, PostMessage);
break;
}
}
return;
}
// ******************************************************************************
//
// Message Handler for WM_HSCROLL
//
// ******************************************************************************
//
void App_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
LONG lScrollTime;

lScrollTime = GetScrollTime(hwnd);

switch (code)
{
case SB_LINEDOWN:
lScrollTime += timehscroll;
break;
case SB_LINEUP:
lScrollTime -= timehscroll;
break;
case SB_PAGEDOWN:
lScrollTime += timeLength/10;
break;
case SB_PAGEUP:
lScrollTime -= timeLength/10;
break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
lScrollTime = pos;
lScrollTime = timeStart + muldiv32(lScrollTime, timeLength, SCROLLRANGE);
break;
}

if (lScrollTime < timeStart)
lScrollTime = timeStart;

if (lScrollTime > timeEnd)
lScrollTime = timeEnd;

if (lScrollTime == (LONG)GetScrollTime(hwnd))
return;

SetScrollTime(hwnd, lScrollTime);
InvalidateRect(hwnd, NULL, TRUE);
UpdateWindow(hwnd);

return;
}
// ******************************************************************************
//
// Message Handler for WM_VSCROLL
//
// ******************************************************************************
//
void App_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
int nScrollPos;
RECT rc;

nScrollPos = GetScrollPos(hwnd, SB_VERT);

GetClientRect(hwnd, &rc);

switch (code)
{
case SB_LINEDOWN:
nScrollPos += 10;
break;
case SB_LINEUP:
nScrollPos -= 10;
break;
case SB_PAGEDOWN:
nScrollPos += rc.bottom;
break;
case SB_PAGEUP:
nScrollPos -= rc.bottom;
break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
nScrollPos = pos;
break;
}

if (nScrollPos < 0)
nScrollPos = 0;

if (nScrollPos > nVertSBLen)
nScrollPos = nVertSBLen;

if (nScrollPos == GetScrollPos(hwnd, SB_VERT))
return;

SetScrollPos(hwnd, SB_VERT, nScrollPos, TRUE);
InvalidateRect(hwnd, NULL, TRUE);
UpdateWindow(hwnd);

return;
}
// ******************************************************************************
//
// Message Handler for WM_RBUTTONDOWN
//
// ******************************************************************************
//
void App_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
RECT rc;
int i;
// Invalidate any stream that has something selected. It needs
// to redraw now.
if (gSelectedStream >= MAXNUMSTREAMS) {
if (ghwndEdit)
EditDone(hwnd, FALSE);
else {
InvalidateRect(hwnd, &grcSelectedStream, TRUE); // needs to erase
gSelectedStream = -1;
}
}
else {
GetClientRect(hwnd, &rc);
for (i = 0; i < gcpavi; i++) {
if (galSelStart[i] != -1) {
rc.top = gStreamTop[i] - GetScrollPos(hwnd, SB_VERT);
rc.bottom =gStreamTop[i+1] -GetScrollPos(hwnd, SB_VERT);
InvalidateRect(hwnd, &rc, TRUE); // needs to erase
}
}

// Deselect everything
SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
}
return;
}
// ******************************************************************************
//
// Message Handler for WM_LBUTTONDOWN
//
// ******************************************************************************
//
void App_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
int yTop, yBottom = 0, iFrameWidth, nFrames;
int i,j,n;
AVISTREAMINFO avis;
BOOL fShift = FALSE;
BOOL fCtrl = FALSE;
RECT rc, rcC;
LONG l, lTime, lSamp, lCurSamp;

if( fDoubleClick ) {
// See if we get to edit a stream name
if (gSelectedStream >= MAXNUMSTREAMS)
EditStreamName(hwnd);

return;
}

GetClientRect(hwnd, &rc);
rcC = rc;
yBottom = -GetScrollPos(hwnd, SB_VERT); // offset for scrollbar

// If we currently have a stream name selected, we must
// deselect it.
if (gSelectedStream >= MAXNUMSTREAMS) {
// Erase the select marks
if (ghwndEdit)
EditDone(hwnd, FALSE);
else
InvalidateRect(hwnd, &grcSelectedStream, TRUE);

// Stream no longer selected.
gSelectedStream = -1;
}
else {
//
// Otherwise, if the shift key isn't down,
// we deselect everything first
//
fShift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
fCtrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (!fShift) {
// Invalidate any stream that has something selected. It needs
// to redraw now.
for (j = 0; j < gcpavi; j++) {
if (galSelStart[j] != -1) {
rc.top = gStreamTop[j] - GetScrollPos(hwnd, SB_VERT);
rc.bottom =gStreamTop[j+1] -GetScrollPos(hwnd, SB_VERT);
InvalidateRect(hwnd, &rc, TRUE); // needs to erase
}
}
// Deselect everything
SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
}
}


//
// Walk the streams and find out where we clicked
//
for (i=0; i<gcpavi; i++) {
AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

// See if they clicked on the name
yTop = gStreamTop[i] - GetScrollPos(hwnd, SB_VERT);
yBottom = yTop + (2 * TSPACE); // !!! size of stream header

if (y >= yTop && y < yBottom) {

gSelectedStream = MAXNUMSTREAMS+i; // which stream's header it is
rc.top = yTop;
rc.bottom = yBottom;
grcSelectedStream = rc; // Invalidate this on button up

InvalidateRect(hwnd, &rc, FALSE); // repaint whole strip

// No need to be here anymore
return;
}


//*******************************************************************
// See if they clicked on the information Header
yTop = yBottom;
yBottom = yTop + (2 * TSPACE); // !!! size of stream header

//
// If they've clicked on the header - select the whole stream
//
if (y >= yTop && y < yBottom) {

// Maybe select everything if Ctrl is held down
SelectStream(hwnd, i, AVIStreamStart(gapavi[i]),
AVIStreamEnd(gapavi[i]), FALSE, fCtrl);

// Tell paint code to highlight the text area. Invalidate
// the whole stream area. When they let go of the mouse,
// redraw the text area only.
gSelectedStream = i; // which stream's header it is
rc.top = yTop;
rc.bottom = yBottom;
grcSelectedStream = rc; // Invalidate this on button up
// Now get the area of the whole stream
rc.bottom = gStreamTop[i + 1] - GetScrollPos(hwnd, SB_VERT);
InvalidateRect(hwnd, &rc, FALSE); // repaint whole strip

// Time to go
return;
}

// *******************************************************************

//
// Now get the area of the stream data, and...
//
yTop = yBottom;
yBottom = gStreamTop[i + 1] - GetScrollPos(hwnd, SB_VERT);

//
// ... see if we clicked on a video frame, or...
//
if (avis.fccType == streamtypeVIDEO) {
if (gapgf[i] == NULL)
continue;

//
// We're in the vertical range of the strip of video
//
if (y >= yTop && y < yBottom) {

rc.top = yTop; rc.bottom = yBottom;
// Time at the centre of the strip
lTime = GetScrollTime(hwnd);
// What frame should appear in the centre? Times that
// are too big will all return the last frame, so we
// need to calculate the hypothetical frame number
if (lTime <= AVIStreamEndTime(gapavi[i])) {
lSamp = AVIStreamTimeToSample(gapavi[i], lTime);
}
else {
lSamp = AVIStreamEnd(gapavi[i]) +
AVIStreamTimeToSample(gapavi[i],
lTime - AVIStreamEndTime(gapavi[i]));
}
// How wide is each frame?
iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
gwZoom / 4 + HSPACE; // !!! hacky constant
// How many frames on each half of centre?
nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
if (nFrames < 0)
nFrames = 0; // at least show *something*

//
// Walk all frames and find which one we're on top of
//
for (n = -nFrames; n <= nFrames; n++) {

rc.left = rcC.right / 2 -
(avis.rcFrame.right * gwZoom / 4) / 2 +
(n * iFrameWidth);
rc.right = rc.left + iFrameWidth;

//
// We're on top of this frame!
//
if (x >= rc.left && x < rc.right) {
//
// For the top video stream, it's easy to tell
// which frame we're on... each frame is
// displayed in order.
//
if (i == giFirstVideo)
SelectStream(hwnd, i, lSamp + n, 1, fShift, fCtrl);


//
// For other video streams, we need to calculate
// the time of the spot we're on, and see which
// frame is associated with it, because who
// knows what scale we're using for time.
//
else {
l = lTime + MulDiv32(n * iFrameWidth, gdwMicroSecPerPixel, 1000);
lCurSamp = AVIStreamTimeToSample( gapavi[i], l);
SelectStream(hwnd, i, lCurSamp, 1, fShift, fCtrl);
}

//
// Invalidate what we'll be highlighting.
// This includes the text area above. If
// we're adding to a selection, invalidate the
// whole strip because other frames might
// become selected by this, and the text
// changes.
//
InflateRect(&rc, HSPACE / 2, VSPACE / 2);
if (fShift) { // could select more than this
rc.left = 0;
rc.right = rcC.right;
}
InvalidateRect(hwnd, &rc, FALSE);
// Now invalidate the text area
rc.bottom = rc.top;
rc.top -= (2 * TSPACE); // !!! text changes
rc.left = 0;
rc.right = rcC.right;
// If we've got selection text already, erase
InvalidateRect(hwnd, &rc, fShift);
return;
}
}//end of inner frame walk for statement
}
}

// ********************************************************************

//
// ... see if we clicked on an audio section
//
else if (avis.fccType == streamtypeAUDIO) {

//
// We clicked inside the wave!
//
if (y >= yTop && y < yBottom) {

//
// Get the time we clicked on, and it's sample number
//
GetClientRect(hwnd, &rcC);
lTime = GetScrollTime(hwnd);
if (x < (rcC.right/2)) { //button down to the left of current position
l = lTime - muldiv32( (rcC.right / 2) - x, gdwMicroSecPerPixel, 1000);
}
else {
l = lTime + muldiv32(x - rcC.right / 2, gdwMicroSecPerPixel, 1000);
}
lCurSamp = AVIStreamTimeToSample(gapavi[i], l);

SelectStream(hwnd, i, lCurSamp, 1, fShift, fCtrl);

// Invalidate what we'll need to repaint to show it
rc.left = x - 2; // may not be exact right pixel
rc.right = x + 3;
rc.top = yTop;
rc.bottom = yBottom;
InflateRect(&rc, 0, VSPACE / 2);
// If we're adding to a selection, we better invalidate
// the whole strip.
if (fShift) {
rc.left = 0;
rc.right = rcC.right;
}
InvalidateRect(hwnd, &rc, FALSE);
// Now invalidate the header text
rc.bottom = rc.top;
rc.top -= (2 * TSPACE); // !!! text changes
rc.left = 0;
rc.right = rcC.right;
// If we've got selection text already, erase
InvalidateRect(hwnd, &rc, fShift);
}
}
}//end of stream walk for statement


return;
}
// ****************************************************************************
//
// Message Handler for WM_LBUTTONUP
//
// ****************************************************************************
//
void App_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
{
//
// If we're selecting a whole stream, stop highlighting the text area
//
if ((gSelectedStream >= 0) && (gSelectedStream < MAXNUMSTREAMS))
{
InvalidateRect(hwnd, &grcSelectedStream, TRUE);
gSelectedStream = -1;
}
return;
}
/*----------------------------------------------------------------------------*\
| AppWndProc( hwnd, uiMessage, wParam, lParam ) |
| |
| Description: |
| The window proc for the app's main (tiled) window. This processes all |
| of the parent window's messages. |
| |
| Arguments: |
| hwnd window handle for the window |
| uiMessage message number |
| wParam message-dependent |
| lParam message-dependent |
| |
| Returns: |
| 0 if processed, nonzero if ignored |
| |
\*----------------------------------------------------------------------------*/
LRESULT CALLBACK AppWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
BOOL f;
HDC hdc;

switch (msg) {

//
// If we passed a command line filename, open it
//
case WM_CREATE:
return HANDLE_WM_CREATE(hwnd, wParam, lParam, App_OnCreate);

case WM_COMMAND:
HANDLE_WM_COMMAND(hwnd, wParam, lParam, App_OnCommand);
break;

case WM_DROPFILES:
HANDLE_WM_DROPFILES(hwnd, wParam, lParam, App_OnDropFiles);
break;

case WM_INITMENU:
HANDLE_WM_INITMENU(hwnd, wParam, lParam, App_OnInitMenu);
break;

//
// During a wait state (eg saving) don't let us choose any menus
//
case WM_NCHITTEST:

if (gfWait)
{
// Let windows tell us where the cursor is
lParam = DefWindowProc(hwnd,msg,wParam,lParam);

// If it's over a menu, pretend it's in the client (force
// hourglass)
if (lParam == HTMENU)
lParam = HTCLIENT;

return lParam;
}
break;
//
// Set vertical scrollbar for scrolling streams
//
case WM_SIZE:
HANDLE_WM_SIZE(hwnd, wParam, lParam, App_OnSize);
break;

//
// During a wait state, show an hourglass over our client area
// !!! Is this necessary?
//
case WM_SETCURSOR:
if (gfWait && LOWORD(lParam) == HTCLIENT)
{
SetCursor(LoadCursor(NULL, IDC_WAIT));
return TRUE;
}
break;

//
// We're out of here!
//
case WM_DESTROY:
HANDLE_WM_DESTROY(hwnd, wParam, lParam, App_OnDestroy);\
break;

case WM_ENDSESSION:
HANDLE_WM_ENDSESSION(hwnd, wParam, lParam, App_OnEndSession);
break;

//
// Don't let us close ourselves in a wait state (eg saving)
//
case WM_CLOSE:
if (gfWait)
return 0;
break;

//
// Block keyboard access to menus if waiting
//
case WM_SYSCOMMAND:
switch (wParam & 0xFFF0) {
case SC_KEYMENU:
if (gfWait)
return 0;
break;
}
break;

case WM_PALETTECHANGED:
HANDLE_WM_PALETTECHANGED(hwnd, wParam, lParam, App_OnPaletteChanged);
break;

case WM_QUERYNEWPALETTE:

if (gfVideoFound) {
hdc = GetDC(hwnd);
//
// Realize the palette of the first video stream
//
if (f = DrawDibRealize(ghdd[giFirstVideo], hdc, FALSE))
InvalidateRect(hwnd,NULL,TRUE);

ReleaseDC(hwnd,hdc);
return f;
}
break;

case WM_ERASEBKGND:
break;

case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);

PaintStuff(hdc, hwnd, FALSE);

EndPaint(hwnd,&ps);
break;

//
// handle the keyboard interface
//
case WM_KEYDOWN:
HANDLE_WM_KEYDOWN(hwnd, wParam, lParam, App_OnKey);
break;

case WM_HSCROLL:
HANDLE_WM_HSCROLL(hwnd, wParam, lParam, App_OnHScroll);
break;

case WM_VSCROLL:
HANDLE_WM_VSCROLL(hwnd, wParam, lParam, App_OnVScroll);
break;

//
// Deselect everything
//
case WM_RBUTTONDOWN:
HANDLE_WM_RBUTTONDOWN(hwnd, wParam, lParam, App_OnRButtonDown);
break;

//
// Select something
//
case WM_LBUTTONDOWN:
HANDLE_WM_LBUTTONDOWN(hwnd, wParam, lParam, App_OnLButtonDown);
break;

case WM_LBUTTONDBLCLK:
HANDLE_WM_LBUTTONDBLCLK(hwnd, wParam, lParam, App_OnLButtonDown);
break;

case WM_LBUTTONUP:
HANDLE_WM_LBUTTONUP(hwnd, wParam, lParam, App_OnLButtonUp);
break;

//
// Wave driver wants to tell us something. Pass it on.
//
case MM_WOM_OPEN:
case MM_WOM_DONE:
case MM_WOM_CLOSE:
aviaudioMessage(hwnd, msg, wParam, lParam);
break;
}
return DefWindowProc(hwnd,msg,wParam,lParam);
}

/*----------------------------------------------------------------------------*\
| SaveCallback() |
| |
| Our save callback that prints our progress in our window title bar |
\*----------------------------------------------------------------------------*/
BOOL CALLBACK SaveCallback(int iProgress)
{
char szText[3*BUFSIZE];
char szFormat[BUFSIZE];

LoadString( ghInstApp, IDS_APPNAME, gszBuffer, BUFSIZE );
LoadString( ghInstApp, IDS_SAVEFORMAT, szFormat, BUFSIZE );

wsprintf(szText, szFormat, (LPSTR) gszBuffer, (LPSTR) gszSaveFileName, iProgress);

SetWindowText(ghwndApp, szText);

//
// Give ourselves a chance to abort
//
return WinYield();
}


/*----------------------------------------------------------------------------*\
| GetDlgItemLong() |
| |
| Get a long integer from a dialog item. |
\*----------------------------------------------------------------------------*/

DWORD GetDlgItemLong (HWND hwnd, int idCtl, LPINT lpfOK, BOOL fSigned)
{
LONG l;
char ch;
BOOL fNegative = FALSE;
char szBuf[64];
LPSTR pbuf = szBuf;
BOOL fOk;


fOk = FALSE;
if (lpfOK)
*lpfOK = FALSE;

if (!GetDlgItemText(hwnd, idCtl, (LPSTR)szBuf, sizeof(szBuf)-1))
return(0);

while (*pbuf == ' ') pbuf++;

if (fSigned && *pbuf == '-')
{
pbuf++;
fNegative = TRUE;
}

l = 0;
while ((ch = *pbuf++) >= '0' && ch <= '9')
{
fOk = TRUE;
if (l > (DWORD)(ULONG_MAX/10))
return(0);
l = (l * 10) + ch - '0';
if (fSigned && l > (DWORD)(ULONG_MAX/2))
return(0);
}

if (fNegative)
l = -l;

if (lpfOK)
*lpfOK = (ch == 0 && fOk);

return(l);
}

/*----------------------------------------------------------------------------*\
| SetDlgItemLong() |
| |
| Put a long integer into a dialog item. |
\*----------------------------------------------------------------------------*/

void SetDlgItemLong (HWND hwnd, int idCtl, DWORD dwValue, BOOL fSigned )
{
char szBuf[64];

wsprintf (szBuf, fSigned ? "%ld" : "%lu", dwValue);
SetDlgItemText(hwnd, idCtl, szBuf);
}


/*----------------------------------------------------------------------------*\
| DoDataExchange() |
| |
| Exchange data between our internal buffer and dialog controls |
| fDir = TRUE for DialogBox->Buffer |
| = FALSE for Buffer->DialogBox |
\*----------------------------------------------------------------------------*/

void DoDataExchange (HWND hwnd, LPAVISTREAMINFO lpinfo, BOOL fDir)
{
if (fDir) {
lpinfo->wPriority = (WORD)GetDlgItemInt (hwnd, IDC_PRIORITY, NULL, FALSE);
lpinfo->wLanguage = (WORD)GetDlgItemInt (hwnd, IDC_LANGUAGE, NULL, FALSE);
lpinfo->dwScale = GetDlgItemLong(hwnd, IDC_SCALE, NULL, FALSE);
lpinfo->dwRate = GetDlgItemLong(hwnd, IDC_RATE, NULL, FALSE);
lpinfo->dwStart = GetDlgItemLong(hwnd, IDC_START, NULL, FALSE);
lpinfo->dwQuality = GetDlgItemLong(hwnd, IDC_QUALITY, NULL, FALSE);
lpinfo->rcFrame.top = GetDlgItemInt (hwnd, IDC_FRAMETOP, NULL, TRUE);
lpinfo->rcFrame.bottom = GetDlgItemInt (hwnd, IDC_FRAMEBOTTOM, NULL, TRUE);
lpinfo->rcFrame.left = GetDlgItemInt (hwnd, IDC_FRAMELEFT, NULL, TRUE);
lpinfo->rcFrame.right = GetDlgItemInt (hwnd, IDC_FRAMERIGHT, NULL, TRUE);
GetDlgItemText(hwnd, IDC_NAME, lpinfo->szName, sizeof(lpinfo->szName)-1);
}
else
{
SetDlgItemInt (hwnd, IDC_PRIORITY, lpinfo->wPriority, FALSE);
SetDlgItemInt (hwnd, IDC_LANGUAGE, lpinfo->wLanguage, FALSE);
SetDlgItemLong(hwnd, IDC_SCALE, lpinfo->dwScale, FALSE);
SetDlgItemLong(hwnd, IDC_RATE, lpinfo->dwRate, FALSE);
SetDlgItemLong(hwnd, IDC_START, lpinfo->dwStart, FALSE);
SetDlgItemLong(hwnd, IDC_QUALITY, lpinfo->dwQuality, FALSE);
SetDlgItemLong(hwnd, IDC_FRAMETOP, lpinfo->rcFrame.top, TRUE);
SetDlgItemLong(hwnd, IDC_FRAMEBOTTOM, lpinfo->rcFrame.bottom, TRUE);
SetDlgItemLong(hwnd, IDC_FRAMELEFT, lpinfo->rcFrame.left, TRUE);
SetDlgItemLong(hwnd, IDC_FRAMERIGHT, lpinfo->rcFrame.right, TRUE);
SetDlgItemText(hwnd, IDC_NAME, lpinfo->szName);
}
}

// *****************************************************************************
//
// FUNCTION: Dlg_DefProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Handles default messages for all dialog boxes
//
//
static LRESULT Dlg_DefProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
return DefDlgProcEx( hDlg, message, wParam, lParam, &gfDefDlgEx );
}
//
// *****************************************************************************
//
// FUNCTION: InfoDlg_OnInitDialog(HWND, HWND, LPARAM)
//
// PURPOSE: Handles initialization for dialog box
//
//
static BOOL InfoDlg_OnInitDialog(HWND hDlg, HWND hwndFocus, LPARAM lParam)
{
int i;
glpavisi = (LPAVISTREAMINFO)
GlobalAllocPtr (GHND, sizeof(AVISTREAMINFO)*gcpavi);

for ( i = 0; i < gcpavi; ++i)
{
AVIStreamInfo(gapavi[i], &glpavisi[i], sizeof(AVISTREAMINFO));
SendDlgItemMessage(hDlg, IDC_STREAMS, CB_ADDSTRING, 0, (LPARAM)(LPSTR)glpavisi[i].szName);
}
SendDlgItemMessage(hDlg, IDC_STREAMS, CB_SETCURSEL, 0, 0);
gnSel = 0;
DoDataExchange(hDlg, &glpavisi[0], FALSE);

return (FALSE);
}
//
// *****************************************************************************
//
// FUNCTION: InfoDlg_OnCommand(HWND, HWND, UINT)
//
// PURPOSE: Handles the child controls for dialog box
//
// DIALOGBOX ID'S
//
// IDOK - ok button
// IDCANCEL - cancel button
// IDC_STREAMS - the dropdown listbox
//
static void InfoDlg_OnCommand(HWND hDlg, int control_source, HWND control_handle, UINT control_action )
{

switch (control_source)
{
case IDC_STREAMS:
{
if (control_action != CBN_SELCHANGE)
break;
if (gnSel != CB_ERR)
DoDataExchange (hDlg, &glpavisi[gnSel], TRUE);
gnSel = (int)SendDlgItemMessage(hDlg, IDC_STREAMS, CB_GETCURSEL, 0, 0);
if (gnSel != CB_ERR)
DoDataExchange(hDlg, &glpavisi[gnSel], FALSE);
}
break;

case IDOK:
case IDCANCEL:
{
BOOL fOk = (control_source == IDOK);
int i,ix;

if (fOk)
{
ix = (int)SendDlgItemMessage(hDlg, IDC_STREAMS, CB_GETCURSEL, 0, 0);
if (ix != CB_ERR)
DoDataExchange(hDlg, &glpavisi[ix], TRUE);

for (i = 0; i < gcpavi; ++i)
EditStreamSetInfo(gapavi[i], &glpavisi[i], sizeof(AVISTREAMINFO));
}
GlobalFreePtr(glpavisi);
EndDialog(hDlg, fOk);
}
break;

}
return;
}
//
// *****************************************************************************
//
// FUNCTION: SetInfoNewDlgProc(HWND, UINT, WPARAM, LPARAM)

// 
// PURPOSE: Processes messages for dialog box using message crackers
//
// MESSAGES:
//
// WM_INITDIALOG - initialize dialog box
// WM_COMMAND - process user input
//
// *****************************************************************************
//
static LRESULT SetInfoNewDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
HANDLE_MSG( hDlg, WM_INITDIALOG, InfoDlg_OnInitDialog );
HANDLE_MSG( hDlg, WM_COMMAND, InfoDlg_OnCommand );
default:
return Dlg_DefProc( hDlg, message, wParam, lParam );
}
}
//
//
// *****************************************************************************
//
// FUNCTION: SetInfoDlgProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for info dialog box
//
//
BOOL CALLBACK SetInfoDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
CheckDefDlgRecursion( &gfDefDlgEx );
return SetDlgMsgResult( hDlg, message, SetInfoNewDlgProc( hDlg, message, wParam, lParam ) );
}


/*----------------------------------------------------------------------------*\
| SetStreamInfo() |
| |
| Bring up the dialog to allow setting the stream info |
\*----------------------------------------------------------------------------*/

BOOL SetStreamInfo (HWND hwnd)
{
return(DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_STREAMINFO), hwnd, (DLGPROC)SetInfoDlgProc));
}



/*----------------------------------------------------------------------------*\
| editPaste() |
| |
| PASTE the streams in this PFILE into our movie using the following logic: |
| |
| Take a stream from the clipboard. If you can find a similar type stream |
| in the app with a selection, paste it in before the selection. If no |
| such stream exists, add it to the end. |
\*----------------------------------------------------------------------------*/

void editPaste(HWND hwnd, PAVIFILE pfile)
{
int i, j, nVideo = 0, nAudio = 0, nStream;
LONG l;
PAVISTREAM pavi;
AVISTREAMINFO avisClip, avis;

FreeDrawStuff(hwnd);

for (i=0; i<MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i) != AVIERR_OK)
break;
AVIStreamInfo(pavi, &avisClip, sizeof(avisClip));
nStream = (avisClip.fccType == streamtypeVIDEO) ? nVideo : nAudio;
for (j=nStream; j<gcpavi; j++) {
AVIStreamInfo(gapavi[j], &avis, sizeof(avis));
if (avis.fccType == avisClip.fccType && galSelStart[j] != -1) {
l = AVIStreamLength(pavi);
if (EditStreamPaste(gapavi[j], &galSelStart[j], &l,
pavi, AVIStreamStart(pavi), AVIStreamLength(pavi)) !=
AVIERR_OK)
{
LoadString( ghInstApp, IDS_NOPASTE, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
}
galSelLen[j] = AVIStreamLength(pavi);
break;
}
}
if (j == gcpavi) {
galSelStart[j] = AVIStreamStart(pavi);
galSelLen[j] = AVIStreamLength(pavi);
if (CreateEditableStream(&gapavi[j], pavi) != AVIERR_OK)
{
LoadString( ghInstApp, IDS_PASTEERROR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
AVIStreamRelease(pavi);
break;
}
AVIStreamRelease(pavi);
gcpavi++;
}
if (avisClip.fccType == streamtypeVIDEO)
nVideo = ++j;
else
nAudio = ++j;
}
AVIFileRelease(pfile);
InitStreams(hwnd);
FixWindowTitle(hwnd);
}
/*----------------------------------------------------------------------------*\
| MenuHandler() |
| |
| Process all of our Menu messages. |
\*----------------------------------------------------------------------------*/
BOOL MenuHandler( HWND hwnd, int nMenuID )
{
OPENFILENAME ofn;

switch(nMenuID)
{
case MENU_ABOUT:
//
// Display an informative dialog box.
//
DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, (DLGPROC)AboutDlgProc);
break;
//
// We want out of here!
//
case MENU_EXIT:
PostMessage(hwnd,WM_CLOSE,0,0L);
break;

//
// Set the compression options for each stream - pass an array of
// streams and an array of compression options structures
//
case MENU_OPTIONS:
AVISaveOptions(hwnd,
ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE |
ICMF_CHOOSE_PREVIEW,
gcpavi, gapavi, galpAVIOptions);
break;

//
// Save all the open streams into a file
//
case MENU_SAVEAS:

gszSaveFileName[0] = 0;

//
// prompt user for file to save
//
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.hInstance = NULL;
AVIBuildFilter(gszFilter, sizeof(gszFilter), TRUE);
ofn.lpstrFilter = gszFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = gszSaveFileName;
ofn.nMaxFile = sizeof(gszSaveFileName);
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
LoadString( ghInstApp, IDS_SAVETITLE, gszBuffer, BUFSIZE );
ofn.lpstrTitle = gszBuffer;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
LoadString( ghInstApp, IDS_DEFEXT, gszBuffer, BUFSIZE );
ofn.lpstrDefExt = gszBuffer;
ofn.lCustData = 0;
ofn.lpfnHook = NULL;
ofn.lpTemplateName = NULL;

//
// If we get a filename, save it
//
if (GetSaveFileName(&ofn))
{
DWORD fccHandler[MAXNUMSTREAMS];
int i;
HRESULT hr;

StartWait();

for (i = 0; i < gcpavi; i++)
fccHandler[i] = galpAVIOptions[i]->fccHandler;

hr = AVISaveV(gszSaveFileName,
NULL,
(AVISAVECALLBACK) SaveCallback,
gcpavi,
gapavi,
galpAVIOptions);
if (hr != AVIERR_OK) {
switch (hr) {
case AVIERR_FILEOPEN:
LoadString( ghInstApp, IDS_ERROVERWRITE, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
break;
default:
LoadString( ghInstApp, IDS_SAVEERROR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
}
}
// Now put the video compressors back that we stole
for (i = 0; i < gcpavi; i++)
galpAVIOptions[i]->fccHandler = fccHandler[i];

EndWait();
FixWindowTitle(hwnd);
}
break;

//
// Close everything
//
case MENU_CLOSE:
FreeAvi(hwnd);
gszFileName[0] = '\0';
FixWindowTitle(hwnd);
break;

//
// Open a new file, or merge streams with a new file
//
case MENU_OPEN:
case MENU_MERGE:
gszFileName[0] = 0;

//
// prompt user for file to open
//
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.hInstance = NULL;
if (nMenuID == MENU_MERGE)
LoadString( ghInstApp, IDS_MERGETITLE, gszBuffer, BUFSIZE );
else
LoadString( ghInstApp, IDS_OPENTITLE, gszBuffer, BUFSIZE );
ofn.lpstrTitle = gszBuffer;
AVIBuildFilter(gszFilter, sizeof(gszFilter), FALSE);
ofn.lpstrFilter = gszFilter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = gszFileName;
ofn.nMaxFile = sizeof(gszFileName);
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST |OFN_HIDEREADONLY;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = NULL;
ofn.lCustData = 0;
ofn.lpfnHook = NULL;
ofn.lpTemplateName = NULL;

//
// If we've got a filename, go open it
//
if (GetOpenFileNamePreview(&ofn))
InitAvi(hwnd, gszFileName, nMenuID);

break;

case MENU_ZOOMQUARTER:
gwZoom = 1;
FixScrollbars(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;

case MENU_ZOOMHALF:
gwZoom = 2;
FixScrollbars(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;

case MENU_ZOOM1:
gwZoom = 4;
FixScrollbars(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;

case MENU_ZOOM2:
gwZoom = 8;
FixScrollbars(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;

case MENU_ZOOM4:
gwZoom = 16;
FixScrollbars(hwnd);
InvalidateRect(hwnd, NULL, TRUE);
break;

//
// Simulate playing the file. We just play the 1st audio stream and let
// our main message loop scroll the video by whenever it's bored.
//
case MENU_PREVIEW:
if (gfAudioFound)
aviaudioPlay(hwnd,
gapavi[giFirstAudio],
AVIStreamTimeToSample(gapavi[giFirstAudio], GetScrollTime(hwnd)),
AVIStreamEnd(gapavi[giFirstAudio]),
FALSE);
gfPlaying = TRUE;
glPlayStartTime = timeGetTime();
glPlayStartPos = GetScrollTime(hwnd);
break;

//
// Stop the play preview
//
case MENU_STOP:
if (gfAudioFound)
aviaudioStop();
gfPlaying = FALSE;
break;

case MENU_SETINFO:
if (SetStreamInfo(hwnd))
{
FreeDrawStuff(hwnd); // !!! in order to call InitStreams
InitStreams(hwnd); // !!! Nukes COMP options
InvalidateRect(hwnd, NULL, TRUE);
}
break;

case MENU_CUT:
case MENU_COPY:
case MENU_DELETE:
{
PAVIFILE pf;
int i;

//
// Walk our list of selections and make streams out of each section
//
gcpaviSel = 0;
for (i = 0; i < gcpavi; i++) {
if (galSelStart[i] != -1) {
// !!! What if the start and length change?
if (nMenuID == MENU_COPY) {
if (EditStreamCopy(gapavi[i], &galSelStart[i],
&galSelLen[i], &gapaviSel[gcpaviSel++]) != 0) {
--gcpaviSel;
LoadString( ghInstApp, IDS_STRCPYERR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
}
} else {
if (EditStreamCut(gapavi[i], &galSelStart[i],
&galSelLen[i], &gapaviSel[gcpaviSel++]) != 0) {
--gcpaviSel;
LoadString( ghInstApp, IDS_STRCUTERR, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
}
}
}
}


for (i = gcpavi - 1; i >= 0; i--) {
// Check to see if any stream is entirely gone now....
if (AVIStreamLength(gapavi[i]) == 0) {
NukeAVIStream(i);
}
}

//
// Put the selected stuff up on the clipboard
//
if (gcpaviSel && nMenuID != MENU_DELETE) {
PAVISTREAM gapaviTemp[MAXNUMSTREAMS];
int i;

//
// Clone the edited streams, so that if the user does
// more editing, the thing on the clipboard won't
// suddenly change....
//
for (i = 0; i < gcpaviSel; i++) {
gapaviTemp[i] = NULL;
// !!! error check
EditStreamClone(gapaviSel[i], &gapaviTemp[i]);
}

AVIMakeFileFromStreams(&pf, gcpaviSel, gapaviTemp);
if (AVIPutFileOnClipboard(pf) != AVIERR_OK)
{
LoadString( ghInstApp, IDS_NOCLIP, gszBuffer, BUFSIZE );
ErrMsg(gszBuffer);
}
for (i = 0; i < gcpaviSel; i++) {
AVIStreamRelease(gapaviTemp[i]);
}

AVIFileRelease(pf);
}

for (i = 0; i < gcpaviSel; i++)
AVIStreamRelease(gapaviSel[i]);

//
// If we cut out the selections, then they don't exist anymore.
//
if (gcpaviSel && (nMenuID == MENU_DELETE || nMenuID == MENU_CUT)) {
SelectStream(hwnd, -1, -1, -1, FALSE, FALSE);
}

//
// We just changed the world!
//
FreeDrawStuff(hwnd); // !!! in order to call InitStreams
InitStreams(hwnd); // !!! Nukes COMP options
InvalidateRect(hwnd, NULL, TRUE);

break;
}

case MENU_PASTE:
{
PAVIFILE pf;

AVIGetFromClipboard(&pf);

if (pf) {
editPaste(hwnd, pf);
}
break;
}
case MENU_NAME:
if (ghwndEdit == NULL)
EditStreamName(hwnd);
else
EditDone(hwnd, FALSE);


break;
}
return TRUE;
}
/*-----------------------------------------------------------------------------
* FrameVideo()
*
* Puts a border around a video frame, the size of a selection
*/

void FrameVideo(HDC hdc, RECT *rcFrame, HBRUSH hbr)
{
RECT rcTop,rcBottom,rcLeft,rcRight;
HBRUSH hbrOld;

// Calculate 4 rectangles, which 'frame' rcFrame.
rcTop.left = rcFrame->left - HSPACE/2;
rcTop.top = rcFrame->top - SELECTVSPACE;
rcTop.right = rcFrame->right + HSPACE/2;
rcTop.bottom = rcFrame->top;
rcLeft.left = rcFrame->left - HSPACE/2;
rcLeft.top = rcFrame->top - SELECTVSPACE;
rcLeft.right = rcFrame->left;
rcLeft.bottom = rcFrame->bottom + SELECTVSPACE;
rcRight.left = rcFrame->right;
rcRight.top = rcFrame->top - SELECTVSPACE;
rcRight.right = rcFrame->right + HSPACE/2;
rcRight.bottom = rcFrame->bottom + SELECTVSPACE;
rcBottom.left = rcFrame->left - HSPACE/2;
rcBottom.top = rcFrame->bottom;
rcBottom.right = rcFrame->right + HSPACE/2;
rcBottom.bottom = rcFrame->bottom + SELECTVSPACE;

// Now put each rectangle on screen
hbrOld = SelectObject(hdc, hbr);
PatBlt(hdc, rcTop.left, rcTop.top, rcTop.right - rcTop.left, rcTop.bottom - rcTop.top, PATCOPY);
PatBlt(hdc, rcLeft.left, rcLeft.top, rcLeft.right - rcLeft.left, rcLeft.bottom - rcLeft.top, PATCOPY);
PatBlt(hdc, rcRight.left, rcRight.top, rcRight.right - rcRight.left, rcRight.bottom - rcRight.top, PATCOPY);
PatBlt(hdc, rcBottom.left, rcBottom.top, rcBottom.right - rcBottom.left, rcBottom.bottom - rcBottom.top, PATCOPY);
SelectObject(hdc, hbrOld);

}

/*----------------------------------------------------------------------------*\
| ErrMsg() |
| |
| Opens a Message box with a error message in it. The user can |
| select the OK button to continue |
\*----------------------------------------------------------------------------*/

int ErrMsg (LPSTR sz,...)
{
static char szOutput[4*BUFSIZE];

va_list va;

va_start(va, sz);
wvsprintf (szOutput,sz,va); /* Format the string */
va_end(va);
MessageBox(NULL,szOutput,NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
return FALSE;
}


/* AboutDlgProc()
*
* Dialog Procedure for the "about" dialog box.
*
*/

BOOL CALLBACK AboutDlgProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
switch (msg) {
case WM_COMMAND:
EndDialog(hwnd, TRUE);
return TRUE;
case WM_INITDIALOG:
return TRUE;
}
return FALSE;
}