/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** portf.cpp: Portfolio handler Primary Author: ****** Review Status: peted has reviewed. Final version not yet approved. ***************************************************************************/ #include "studio.h" #include "commctrl.h" ASSERTNAME /*************************************************************************** FPortDisplayWithIds: Display the portfolio to open or save a file. Portfolio title and filters generated using supplied string ids. Arguments: fni - Output FNI for file selected fOpen - fTrue if open portfolio, else save portfolio. lFilterLabel - id of portfolio filter label lFilterExt - id of portfolio filter extension lTitle - id of portfolio title lpstrDefExt - Ptr to string for default file extension for save if required. pstnDefFileName - Ptr to default extension stn if required. pfniInitialDir - Ptr to initial directory fni if required. grfPrevType - Bits for types of preview required, (eg movie, sound etc) == 0 if no preview cnoWave - Wave cno for audio when portfolio is invoked Returns: TRUE - File selected FALSE - User canceled portfolio. ***************************************************************************/ bool FPortDisplayWithIds(FNI *pfni, bool fOpen, long lFilterLabel, long lFilterExt, long lTitle, LPTSTR lpstrDefExt, PSTN pstnDefFileName, FNI *pfniInitialDir, ulong grfPrevType, CNO cnoWave) { STN stnTitle; STN stnFilterLabel; STN stnFilterExt; int cChLabel, cChExt; SZ szFilter; bool fRet; AssertVarMem(pfni); AssertNilOrPo(pfniInitialDir, 0); AssertNilOrPo(pstnDefFileName, 0); if (!vapp.FGetStnApp(lTitle, &stnTitle)) return fFalse; // Filter string contain non-terminating null characters, so must // build up string from separate label and file extension strings. if (!vapp.FGetStnApp(lFilterLabel, &stnFilterLabel)) return fFalse; if (!vapp.FGetStnApp(lFilterExt, &stnFilterExt)) return fFalse; // Kauai does not like internal null chars in an STN. So build // up the final final string as an SZ. cChLabel = stnFilterLabel.Cch(); cChExt = stnFilterExt.Cch(); stnFilterLabel.GetSz(szFilter); stnFilterExt.GetSz(&szFilter[cChLabel + 1]); szFilter[cChLabel + cChExt + 2] = chNil; // Now display the open or save portfolio as required StopAllMovieSounds(); vapp.EnsureInteractive(); vapp.SetFInPortfolio(fTrue); if(fOpen) { fRet = FPortGetFniOpen(pfni, szFilter, stnTitle.Prgch(), pfniInitialDir, grfPrevType, cnoWave); } else { fRet = FPortGetFniSave(pfni, szFilter, stnTitle.Prgch(), lpstrDefExt, pstnDefFileName, grfPrevType, cnoWave); } // Make sure no portfolio related audio is still playing. vpsndm->StopAll(sqnNil, sclNil); vapp.SetFInPortfolio(fFalse); // Let the script know what the outcome is. vpcex->EnqueueCid(cidPortfolioClosed, 0, 0, fRet); // We must also enqueue a message for the help system here. // Help doesn't get the above cidPortfolioClosed as it has // already been filtered. vpcex->EnqueueCid(cidPortfolioResult, 0, 0, fRet); return fRet; } /*************************************************************************** FPortGetFniOpen: Display the portfolio to open a file. Arguments: pfni - Output FNI for file selected lpstrFilter - String containing files types to filter on lpstrTitle - String containing title of portfolio pfniInitialDir - Ptr to initial directory fni if required. grfPrevType - Bits for types of preview required, (eg movie, sound etc) == 0 if no preview cnoWave - Wave cno for audio when portfolio is invoked Returns: TRUE - File selected FALSE - User canceled portfolio. ***************************************************************************/ bool FPortGetFniOpen(FNI *pfni, LPTSTR lpstrFilter, LPTSTR lpstrTitle, FNI *pfniInitialDir, ulong grfPrevType, CNO cnoWave) { SZ szFile; DLGINFO diPortfolio; OPENFILENAME ofn; STN stn; bool fOKed; STN stnInitialDir; SZ szInitialDir; AssertPo(pfni, 0); AssertNilOrPo(pfniInitialDir, 0); ClearPb(&ofn, size(OPENFILENAME)); ClearPb(&diPortfolio, size(DLGINFO)); szFile[0] = 0; ofn.lStructSize = size(OPENFILENAME); ofn.hwndOwner = vwig.hwndApp; ofn.hInstance = vwig.hinst; ofn.nFilterIndex = 1L; ofn.lpstrCustomFilter = NULL; ofn.lpstrFile = szFile; ofn.nMaxFile = kcchMaxSz; ofn.lpstrFileTitle = NULL; ofn.lpfnHook = OpenHookProc; ofn.lpTemplateName = MAKEINTRESOURCE(dlidPortfolio); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE; // lpstrDefExt is used for appended to the user typed filename is // one is entered without an extension. As the user cannot type // anything during a portfolio open, we don't use the lpstrDefExt here. ofn.lpstrDefExt = NULL; // Initialize internal data for accessing from inside dlg hook proc. diPortfolio.fIsOpen = fTrue; diPortfolio.fDrawnBkgnd = fFalse; diPortfolio.grfPrevType = grfPrevType; diPortfolio.cnoWave = cnoWave; ofn.lCustData = (DWORD)&diPortfolio; ofn.lpstrFilter = lpstrFilter; ofn.lpstrTitle = lpstrTitle; // Get the string for the initial directory if required. if(pfniInitialDir != pvNil) { pfniInitialDir->GetStnPath(&stnInitialDir); stnInitialDir.GetSz(szInitialDir); ofn.lpstrInitialDir = szInitialDir; } else { // Initial directory will be current directory. ofn.lpstrInitialDir = pvNil; } // Now display the portfolio. fOKed = (GetOpenFileName(&ofn) == FALSE ? fFalse : fTrue); if(!fOKed) { // Check if custom common dlg failed to initialize. Don't rely on the returned // error being CDERR_INITIALIZATION, as who knows what error will be returned // in future versions. if(CommDlgExtendedError() != NOERROR) { // User never saw portfolio. Therefore attempt to display // common dlg with no customization. ofn.Flags &= ~(OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE); fOKed = (GetOpenFileName(&ofn) == FALSE ? fFalse : fTrue); } } // If the user selected a file, build up the associated fni. if(fOKed) { stn.SetSz(ofn.lpstrFile); pfni->FBuildFromPath(&stn, 0); } // Update the app window to make sure no parts of the portfolio // window are left on the screen while the file is being opened. // Calling UpdateMarked() does not have the desired effect here. UpdateWindow(vwig.hwndApp); // Report error to user if never displayed the portfolio. if(CommDlgExtendedError() != NOERROR) { PushErc(ercSocPortfolioFailed); } // Only return TRUE if the user selected a file. return(fOKed); } /*************************************************************************** pfGetFniSave: Display the save portfolio. Arguments: pfni - Output FNI for file selected lpstrFilter - String containing files types to filter on lpstrTitle - String containing title of portfolio lpstrDefExt - String containing default extension for filenames typed by user without an extension. pstnDefFileName - Ptr to default extension stn if required. grfPrevType - Bits for types of preview required, (eg movie, sound etc) == 0 if no preview cnoWave - Wave cno for audio when portfolio is invoked Returns: TRUE - File selected FALSE - User canceled portfolio, (or other error). ***************************************************************************/ bool FPortGetFniSave(FNI *pfni, LPTSTR lpstrFilter, LPTSTR lpstrTitle, LPTSTR lpstrDefExt, PSTN pstnDefFileName, ulong grfPrevType, CNO cnoWave) { DLGINFO diPortfolio; OPENFILENAME ofn; bool fOKed; bool tRet; bool fRedisplayPortfolio = fFalse; bool fExplorer = fTrue; STN stnFile, stnErr; FNI fniUserDir; STN stnUserDir; SZ szUserDir; SZ szDefFileName; SZ szFileTitle; AssertPo(pfni, 0); AssertNilOrPo(pstnDefFileName, 0); ClearPb(&ofn, size(OPENFILENAME)); ClearPb(&diPortfolio, size(DLGINFO)); szFileTitle[0] = chNil; ofn.lStructSize = size(OPENFILENAME); ofn.hwndOwner = vwig.hwndApp; ofn.hInstance = vwig.hinst; ofn.nFilterIndex = 1L; ofn.lpstrCustomFilter = NULL; ofn.nMaxFile = kcchMaxSz; ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = kcchMaxSz; ofn.lpfnHook = OpenHookProc; ofn.lpTemplateName = MAKEINTRESOURCE(dlidPortfolio); // Don't use OFN_OVERWRITEPROMPT here, otherwise we can't intercept // the btn press on the Save btn to display our own message. Note, // we don't do that either, because the help topic display mechanism // was not designed for use within a modal dlg box such as the portfolio. // Instead query overwrite after the portfolio has closed. ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE; // Do not allow the save portfolio to change the current directory. ofn.Flags |= OFN_NOCHANGEDIR; // Let the initial dir be the user's dir. vapp.GetFniUser(&fniUserDir); fniUserDir.GetStnPath(&stnUserDir); stnUserDir.GetSz(szUserDir); ofn.lpstrInitialDir = szUserDir; // Initialize internal data for accessing from inside dlg hook proc. diPortfolio.fIsOpen = fFalse; diPortfolio.fDrawnBkgnd = fFalse; diPortfolio.grfPrevType = grfPrevType; diPortfolio.cnoWave = cnoWave; ofn.lCustData = (DWORD)&diPortfolio; ofn.lpstrFilter = lpstrFilter; ofn.lpstrTitle = lpstrTitle; ofn.lpstrDefExt = lpstrDefExt; // Set the the portfolio default save file name if required. if(pstnDefFileName != pvNil) { pstnDefFileName->GetSz(szDefFileName); } else { szDefFileName[0] = chNil; } ofn.lpstrFile = szDefFileName; // We may need to display the portfolio multiple times if the user // selects an existing file, and then says they don't want to overwrite it. do // Display portfolio { fRedisplayPortfolio = fFalse; // Now display the portfolio. fOKed = (GetSaveFileName(&ofn) == FALSE ? fFalse : fTrue); if(!fOKed) { DWORD dwRet; // Check if custom common dlg failed to initialize. Don't rely on the returned // error being CDERR_INITIALIZATION, as who knows what error will be returned // in future versions. if((dwRet = CommDlgExtendedError()) != NOERROR) { if (dwRet == FNERR_INVALIDFILENAME || dwRet == FNERR_BUFFERTOOSMALL) { // Set the the portfolio default save file name if required. if(pstnDefFileName != pvNil) { pstnDefFileName->GetSz(szDefFileName); } else { szDefFileName[0] = chNil; } ofn.lpstrFile = szDefFileName; PushErc(ercSocInvalidFilename); fRedisplayPortfolio = fTrue; } else if (dwRet == CDERR_INITIALIZATION && fExplorer) { // User never saw portfolio. Therefore attempt to display // common dlg with no customization. ofn.Flags &= ~(OFN_EXPLORER | OFN_ENABLEHOOK | OFN_ENABLETEMPLATE); fRedisplayPortfolio = fTrue; fExplorer = fFalse; } else PushErc(ercSocPortfolioFailed); vapp.DisplayErrors(); continue; } } else { // Build stn if user selected a file. stnFile.SetSz(ofn.lpstrFile); // Query any attempt to overwrite an existing file now. if(CchSz(ofn.lpstrFile) != 0) { bool tExists; // We always save the file with the default extension. If the // user used a different extension, add the default on the end. if(ofn.Flags & OFN_EXTENSIONDIFFERENT) { stnFile.FAppendCh('.'); stnFile.FAppendSz(lpstrDefExt); } // Make sure the pfni is built from the appended file name if applicable. pfni->FBuildFromPath(&stnFile, 0); if((tExists = pfni->TExists()) != tNo) { long cch; achar *pch; // File already exists. Query user for overwrite. // The default name supplied to the user will only // be the file name without the path. stnFile.SetSz(ofn.lpstrFileTitle); /* Remove the extension */ cch = stnFile.Cch(); pch = stnFile.Psz() + cch; while (cch--) { pch--; if (*pch == ChLit('.')) { stnFile.Delete(cch); break; } } stnFile.GetSz(ofn.lpstrFile); // Only query if we know for sure the file's there. If // it's not known for sure the file's there, just make the // user pick a new name. if (tExists == tYes) { AssertDo(vapp.FGetStnApp(idsReplaceFile, &stnErr), "String not present"); tRet = vapp.TModal(vapp.PcrmAll(), ktpcQueryOverwrite, &stnErr, bkYesNo, kstidQueryOverwrite, &stnFile); } else { vapp.DisplayErrors(); tRet = tNo; } // Redisplay the portfolio if no overwrite. if(tRet == tNo) { fRedisplayPortfolio = fTrue; // Make sure the app window is updated, otherwise the help topic // may still be displayed while the portfolio is redisplayed. InvalidateRect(vwig.hwndApp, NULL, TRUE); UpdateWindow(vwig.hwndApp); } } } else { // The user OKed the selection, and yet with have a zero length file name. // Something must have gone wrong with the common dialog. Treat this as a // cancel of the portfolio. (Remember we have not set up pfni in this case.) Bug("Portfolio selection of a file with no name"); fOKed = fFalse; } } } while(fRedisplayPortfolio); // If the user selected a file, build up the associated fni. if(!fOKed) { pfni->SetNil(); } // Update the app window to make sure no parts of the portfolio // window are left on the screen while the file is being saved. // Calling UpdateMarked() does not have the desired effect here. UpdateWindow(vwig.hwndApp); // Report error to user if never displayed the portfolio. if(CommDlgExtendedError() != NOERROR) { PushErc(ercSocPortfolioFailed); } // Only return fTrue if the user selected a file. return(fOKed); } /*************************************************************************** OpenHookProc: Hook proc for get open/save common dlg. Arguments: standard dialog proc args. Returns: TRUE - Common dlg will ignore message FALSE - Common dlg will process this message after custom dlg. Note - on win95, hwndCustom is the handle to the custom dlg created as a child of the common dlg. ***************************************************************************/ UINT CALLBACK OpenHookProc(HWND hwndCustom, UINT msg, UINT wParam, LONG lParam) { switch (msg) { case WM_INITDIALOG: { PDLGINFO pdiPortfolio; LONG lwStyle, lwExstyle; OPENFILENAME *lpOfn; HWND hwndDlg; RC rc; WNDPROC lpOtherBtnProc; lpOfn = (OPENFILENAME *)lParam; pdiPortfolio = (PDLGINFO)(lpOfn->lCustData); SetWindowLong(hwndCustom, GWL_USERDATA, (LONG)pdiPortfolio); hwndDlg = GetParent(hwndCustom); // Give ourselves a way to access the custom dlg hwnd // from the common dlg subclass wndproc. SetWindowLong(hwndDlg, GWL_USERDATA, (LONG)hwndCustom); // Hide common dlg controls that we're not interested in here. Use the Common Dialog // Message for hiding the control. The documentation on CDM_HIDECONTROL doesn't really // describe what the command actually does, so explictly disable the OK/Cancel btns here. SendMessage(hwndDlg, CDM_HIDECONTROL, stc2, 0L); // File type label EnableWindow(GetDlgItem(hwndDlg, stc2), FALSE); SendMessage(hwndDlg, CDM_HIDECONTROL, cmb1, 0L); // File type combo box EnableWindow(GetDlgItem(hwndDlg, cmb1), FALSE); SendMessage(hwndDlg, CDM_HIDECONTROL, chx1, 0L); // Open as Read-only check box EnableWindow(GetDlgItem(hwndDlg, chx1), FALSE); // Even though we are hiding the OK/Cancel buttons, do not disable them. // By doing this, we retain some default common dialog behavior. When the // user hits the Enter key the highlighted file will be selected, and when // the user hits the Escape key the portfolio is dismissed. SendMessage(hwndDlg, CDM_HIDECONTROL, IDOK, 0L); // OK btn. SendMessage(hwndDlg, CDM_HIDECONTROL, IDCANCEL, 0L); // Cancel btn. if(pdiPortfolio->fIsOpen) { SendMessage(hwndDlg, CDM_HIDECONTROL, stc3, 0L); // 'File Name' EnableWindow(GetDlgItem(hwndDlg, stc3), FALSE); SendMessage(hwndDlg, CDM_HIDECONTROL, edt1, 0L); // File name edit ctrl EnableWindow(GetDlgItem(hwndDlg, edt1), FALSE); } // If no preview required then hide the preview window. if(pdiPortfolio->grfPrevType == 0) { SendMessage(hwndCustom, CDM_HIDECONTROL, IDC_PREVIEW, 0L); EnableWindow(GetDlgItem(hwndCustom, IDC_PREVIEW), FALSE); } // Give the main common dlg the required style for custom display. // This means remove the caption bar, the system menu and the thick border. // Note, We do not need a frame round the window, as we customize its // background entirely. Therefore we should remove WS_CAPTION, (which is // WS_BORDER | WS_DLGFRAME), and WS_EX_DLGMODALFRAME. However, if we do // this, then when the user navigates around the list box in the dialog, // the app window gets repainted. Presumably this is due to win95 // invalidating an area slightly larger than the dlg without any border. // Therefore maintain the thin border. lwStyle = GetWindowLong( hwndDlg, GWL_STYLE); lwStyle &= ~(WS_DLGFRAME | WS_SYSMENU); SetWindowLong( hwndDlg, GWL_STYLE, lwStyle); lwExstyle = GetWindowLong( hwndDlg, GWL_EXSTYLE); lwExstyle &= ~WS_EX_DLGMODALFRAME; SetWindowLong( hwndDlg, GWL_EXSTYLE, lwExstyle); // IMPORTANT NOTE. Cannot move or size the portfolio here. Otherwise this // confuses win95 when it calculates whether all the controls fit in the // common dlg or not. Instead we must wait for the INITDONE notification. // Note, since the portfolio is full screen now, we don't need to move // the window anyway.. // Subclass the push btns to prevent the background flashing in the default color. lpBtnProc = (WNDPROC)SetWindowLong( GetDlgItem(hwndCustom, IDC_BUTTON1), GWL_WNDPROC, (LONG)SubClassBtnProc); lpOtherBtnProc = (WNDPROC)SetWindowLong( GetDlgItem(hwndCustom, IDC_BUTTON2), GWL_WNDPROC, (LONG)SubClassBtnProc); Assert(lpBtnProc == lpOtherBtnProc, "Custom portfolio buttons (ok/cancel) have different window procs"); lpOtherBtnProc = (WNDPROC)SetWindowLong( GetDlgItem(hwndCustom, IDC_BUTTON3), GWL_WNDPROC, (LONG)SubClassBtnProc); Assert(lpBtnProc == lpOtherBtnProc, "Custom portfolio buttons (ok/home) have different window procs"); // Subclass the preview window to allow custom draw. lpPreviewProc = (WNDPROC)SetWindowLong( GetDlgItem(hwndCustom, IDC_PREVIEW), GWL_WNDPROC, (LONG)SubClassPreviewProc); // Subclass the main common dlg window to stop static control backgrounds being // fill with the current system color. Instead use a color that matches our // custom background bitmap. lpDlgProc = (WNDPROC)SetWindowLong( hwndDlg, GWL_WNDPROC, (LONG)SubClassDlgProc); // For the save portfolio we want the file name control to have focus when displayed. if(!pdiPortfolio->fIsOpen) { SetFocus(GetDlgItem(hwndDlg, edt1)); // Select all the text in the file name edit control. SendMessage(GetDlgItem(hwndDlg, edt1), EM_SETSEL, 0, -1); return(1); } // Allow Windows to set the focus wherever it wants to. return(0); } case WM_COMMAND: { // If the user has clicked on one of our custom buttons, then // simulate a click on one of the hidden common dlg buttons. if (HIWORD(wParam) == BN_CLICKED) { switch (LOWORD(wParam)) { case IDC_BUTTON1: PostMessage(GetParent(hwndCustom), WM_COMMAND, IDOK, 0); return(1); case IDC_BUTTON2: PostMessage(GetParent(hwndCustom), WM_COMMAND, IDCANCEL, 0); return(1); case IDC_BUTTON3: { FNI fniUserDir; STN stnUserDir; SZ szUserDir; SZ szCurFile; HWND hwndDlg = GetParent(hwndCustom); // The user has pressed the Go To User's Home Folder btn. So get a SZ // for the user's home folder. vapp.GetFniUser(&fniUserDir); fniUserDir.GetStnPath(&stnUserDir); stnUserDir.GetSz(szUserDir); // Ideally there would be some CDM_ message here to tell the common dialog // that the current folder is to be changed. Unfortunately there is no such // message. There is also no recommended way of doing this. Note that if we // call SetCurrentDirectory here, then the common dialog is oblivious of the // folder change. // SO, we shall simulate a folder change by the user. Do this by filling // the file name edit control with the target folder, and then sending an // OK message to the common dlg. // This would be enough to change the folder, but we would lose the current // contents of the file name control. Therefore store the control contents // before we overwrite it, and reset it afterwards. Ideally there would be // some message to retrieve the contents of a control, (eg CDM_GETCONTROLTEXT), // but there isn't, so use a regular Windows call for this. // Get the current text for the control. GetDlgItemText(hwndDlg, edt1, szCurFile, sizeof(szCurFile)); // Now change folder! SendMessage(hwndDlg, CDM_SETCONTROLTEXT, edt1, (LPARAM)szUserDir); SendMessage(hwndDlg, WM_COMMAND, IDOK, 0); // Restore the edit control text. SendMessage(hwndDlg, CDM_SETCONTROLTEXT, edt1, (LPARAM)szCurFile); return(1); } default: break; } } break; } case WM_DRAWITEM: { int iDlgId = (int)wParam; DRAWITEMSTRUCT *pDrawItem = (DRAWITEMSTRUCT*)lParam; CNO cnoDisplay = cnoNil; PMBMP pmbmp; // Custom draw the our push btns here. switch (iDlgId) { case IDC_BUTTON1: if(pDrawItem->itemState & ODS_SELECTED) { cnoDisplay = kcnoMbmpPortBtnOkSel; } else { cnoDisplay = kcnoMbmpPortBtnOk; } break; case IDC_BUTTON2: if(pDrawItem->itemState & ODS_SELECTED) { cnoDisplay = kcnoMbmpPortBtnCancelSel; } else { cnoDisplay = kcnoMbmpPortBtnCancel; } break; case IDC_BUTTON3: if(pDrawItem->itemState & ODS_SELECTED) { cnoDisplay = kcnoMbmpPortBtnHomeSel; } else { cnoDisplay = kcnoMbmpPortBtnHome; } break; default: break; } if(cnoDisplay != cnoNil) { // Select the appropriate bitmap to display. if((pmbmp = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, cnoDisplay, MBMP::FReadMbmp))) { PGPT pgpt; HPEN hpen, hpenold; HBRUSH hbr, hbrold; HFONT hfnt, hfntold; // To ensure that we don't return from here with different objects // selected in the supplied hdc, save them here and restore later. // (Note that Kauai will attempt to delete things it finds selected // in the hdc passed to PgptNew). hpen = (HPEN)GetStockObject(NULL_PEN); Assert(hpen != hNil, "Portfolio - draw items GetStockObject(NULL_PEN) failed"); hpenold = (HPEN)SelectObject(pDrawItem->hDC, hpen); hbr = (HBRUSH)GetStockObject(WHITE_BRUSH); Assert(hbr != hNil, "Portfolio - draw items GetStockObject(WHITE_BRUSH) failed"); hbrold = (HBRUSH)SelectObject(pDrawItem->hDC, hbr); hfnt = (HFONT)GetStockObject(SYSTEM_FONT); Assert(hfnt != hNil, "Portfolio - draw items GetStockObject(SYSTEM_FONT) failed"); hfntold = (HFONT)SelectObject(pDrawItem->hDC, hfnt); if ((pgpt = GPT::PgptNew(pDrawItem->hDC)) != pvNil) { GNV gnv(pgpt); PGNV pgnvOff; PGPT pgptOff; RC rcItem(pDrawItem->rcItem); // Must create offscreen dc and blit into that. Then blit that // to dlg on screen. If we blit straight from mbmp to screen, // then screen flashes. (Due to white fillrect). if((pgptOff = GPT::PgptNewOffscreen(&rcItem, 8)) != pvNil) { if((pgnvOff = NewObj GNV(pgptOff)) != pvNil) { pgnvOff->DrawMbmp(pmbmp, rcItem.xpLeft, rcItem.ypTop); gnv.CopyPixels(pgnvOff, &rcItem, &rcItem); GPT::Flush(); ReleasePpo(&pgnvOff); } ReleasePpo(&pgptOff); } ReleasePpo(&pgpt); } // Restore the currently secleted objects back into he supplied hdc. SelectObject(pDrawItem->hDC, hpenold); SelectObject(pDrawItem->hDC, hbrold); SelectObject(pDrawItem->hDC, hfntold); ReleasePpo(&pmbmp); } return(1); } break; } case WM_NOTIFY: { LPOFNOTIFY lpofNotify = (LPOFNOTIFY)lParam; switch (lpofNotify->hdr.code) { case CDN_INITDONE: { // Win95 has finished doing any resizing of the custom dlg and the controls. // So take any special action now to ensure the portfolio still looks good. PDLGINFO pdiPortfolio = (PDLGINFO)GetWindowLong(hwndCustom, GWL_USERDATA); RCS rcsApp; POINT ptBtn; int ypBtn; PMBMP pmbmpBtn; RC rcBmp; RCS rcsAppScreen, rcsPreview; int xOff = 0; int yOff = 0; LONG lStyle; PCRF pcrf; HWND hwndDlg = GetParent(hwndCustom); HWND hwndApp = GetParent(hwndDlg); HWND hwndPreview = GetDlgItem(hwndCustom, IDC_PREVIEW); // Store the current size of the portfolio. We need this later when we stretch // the background bitmap to match how win95 may have stretched the dlg controls. GetClientRect(hwndDlg, &(pdiPortfolio->rcsDlg)); // Get the client area size of the app window. GetClientRect(hwndApp, &rcsApp); // Now move the custom buttons to always be to the right of the preview window. // Make sure we keep the buttons above the bottom of the screen. GetClientRect(hwndPreview, &rcsPreview); ptBtn.x = rcsPreview.right; ptBtn.y = (rcsPreview.top + rcsPreview.bottom) / 2; MapWindowPoints(hwndPreview, hwndCustom, (POINT*)&ptBtn, 1); // First the home button. if((pmbmpBtn = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, kcnoMbmpPortBtnHome, MBMP::FReadMbmp))) { pmbmpBtn->GetRc(&rcBmp); ptBtn.x += (3 * rcBmp.Dxp()) / 4; ypBtn = min(ptBtn.y - (rcBmp.Dyp() / 2), rcsApp.bottom - (2 * rcBmp.Dyp())); SetWindowPos(GetDlgItem(hwndCustom, IDC_BUTTON3), 0, ptBtn.x, ypBtn, rcBmp.Dxp(), rcBmp.Dyp(), SWP_NOZORDER); ReleasePpo(&pmbmpBtn); } // Now the cancel button. if((pmbmpBtn = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, kcnoMbmpPortBtnCancel, MBMP::FReadMbmp))) { pmbmpBtn->GetRc(&rcBmp); ptBtn.x += (5 * rcBmp.Dxp()) / 4; ypBtn = min(ptBtn.y - (rcBmp.Dyp() / 2), rcsApp.bottom - (2 * rcBmp.Dyp())); SetWindowPos(GetDlgItem(hwndCustom, IDC_BUTTON2), 0, ptBtn.x, ypBtn, rcBmp.Dxp(), rcBmp.Dyp(), SWP_NOZORDER); ReleasePpo(&pmbmpBtn); } // Now the ok button. if((pmbmpBtn = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, kcnoMbmpPortBtnOk, MBMP::FReadMbmp))) { pmbmpBtn->GetRc(&rcBmp); ptBtn.x += (5 * rcBmp.Dxp()) / 4; ypBtn = min(ptBtn.y - (rcBmp.Dyp() / 2), rcsApp.bottom - (2 * rcBmp.Dyp())); SetWindowPos(GetDlgItem(hwndCustom, IDC_BUTTON1), 0, ptBtn.x, ypBtn, rcBmp.Dxp(), rcBmp.Dyp(), SWP_NOZORDER); ReleasePpo(&pmbmpBtn); } // Note, win95 may have pushed the portfolio around depending on where the task bar is. // Ensure that the top left corner of the portfolio is in the top left of the client // area of the app window. Note that the common dlg is owned by the app window, but // it is not a child window itself. // Note, if we MapWindowPoints on (0,0) here, then the returned coords are shifted by // the task bar which is just what we don't want. So GetWindowRect instead. GetWindowRect(hwndApp, &rcsAppScreen); // If running in a window, then we must find the offset from the top left of the app // window's corner to its client area. lStyle = GetWindowLong(hwndApp, GWL_STYLE); if(lStyle & WS_CAPTION) { xOff = GetSystemMetrics(SM_CXDLGFRAME); yOff = GetSystemMetrics(SM_CYDLGFRAME); yOff += GetSystemMetrics(SM_CYCAPTION); } // Now move the common dialog itself. Resize the dialog too, to be the same size // as the app window. SetWindowPos(hwndDlg, 0, rcsAppScreen.left + xOff, rcsAppScreen.top + yOff, rcsApp.right, rcsApp.bottom, SWP_NOZORDER); // Custom dialog is a child of the common dlg, so (0, 0) wil position it it the common // dialog's client area. SetWindowPos(hwndCustom, 0, 0, 0, rcsApp.right, rcsApp.bottom, SWP_NOZORDER); // Now play the sound associated with this portfolio is there is one. // Note that we are not currently queueing this sound or terminating // any currently playing sound. if(pdiPortfolio->cnoWave != cnoNil) { // There is a sound for the portfolio, so find it. if((pcrf = ((APP *)vpappb)->PcrmAll()->PcrfFindChunk(kctgWave, pdiPortfolio->cnoWave)) != pvNil) { vpsndm->SiiPlay(pcrf, kctgWave, pdiPortfolio->cnoWave); } } break; } case CDN_SELCHANGE: { HWND hwndPreview = GetDlgItem(hwndCustom, IDC_PREVIEW); // User has changed the file selected, so update the preview window. InvalidateRect(hwndPreview, NULL, FALSE); UpdateWindow(hwndPreview); break; } #ifdef QUERYINPORTFOLIO // Do this if we ever have a way of querying the user with a help topic // from inside the portfolio. case CDN_FILEOK: { PDLGINFO pdiPortfolio = (PDLGINFO)GetWindowLong(hwndCustom, GWL_USERDATA); // User has hit OK or Save. Is the user trying to save over an existing file? if(pdiPortfolio->fIsOpen != fTrue) { // Neither CommDlg_OpenSave_GetFilePath nor CommDlg_OpenSave_GetSpec // always return the string with the default extension already added // if applicable. Therefore get the file name from the returned OFN // structure, as this stores the complete file name. if(CchSz(lpofNotify->lpOFN->lpstrFile) != 0) { FNI fni; STN stnFile, stnErr; bool fHelp, tRet; long lSelect; // Now does the specified file already exist? stnFile.SetSz(lpofNotify->lpOFN->lpstrFile); fni.FBuildFromPath(&stnFile, 0); if(fni.TExists() != tNo) { if(vpappb->TGiveAlertSz( "The selected file already exists.\n\nDo you want to overwrite it?", bkYesNo, cokQuestion) != tYes) { // Move the focus back to the file name edit ctrl. SetFocus(GetDlgItem(GetParent(hwndCustom), edt1)); // Let win95 know that the portfolio is to stay up. SetWindowLong(hwndCustom, DWL_MSGRESULT, 1); return(1); } } } } break; } #endif //QUERYINPORTFOLIO default: { break; } } break; } case WM_ERASEBKGND: // We never want the background painted in the system colors. return(1); case WM_PAINT: { PDLGINFO pdiPortfolio; pdiPortfolio = (PDLGINFO)GetWindowLong(hwndCustom, GWL_USERDATA); // Repaint the entire portfolio. RepaintPortfolio(hwndCustom); // Now the background is drawn, disallow erasing of the background in the // system color before each future repaint. pdiPortfolio->fDrawnBkgnd = fTrue; return(1); } default: break; } return(0); } /*************************************************************************** RepaintPortfolio: Repaint the entire portfolio. Arguments: hwndCustom - Handle to our custom dialog, ie child of the main common dlg. Returns: nothing ***************************************************************************/ void RepaintPortfolio(HWND hwndCustom) { PAINTSTRUCT ps; TEXTMETRIC tmCaption; SZ szCaption; PDLGINFO pdiPortfolio = (PDLGINFO)GetWindowLong(hwndCustom, GWL_USERDATA); PMBMP pmbmp, pmbmpBtn; int iBtn; CNO cnoBack; // Draw the custom background for the common dlg. BeginPaint(hwndCustom, &ps); // Display the open or save portfolio background bitmap as appropriate. if(pdiPortfolio->fIsOpen) { cnoBack = kcnoMbmpPortBackOpen; } else { cnoBack = kcnoMbmpPortBackSave; } // Get the background bitmap first. if((pmbmp = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, cnoBack, MBMP::FReadMbmp))) { PGPT pgpt; HPEN hpen, hpenold; HBRUSH hbr, hbrold; HFONT hfnt, hfntold; // To ensure that we don't return from here with different objects // selected in the supplied hdc, save them here and restore later. // (Note that Kauai will attempt to delete things it finds selected // in the hdc passed to PgptNew). hpen = (HPEN)GetStockObject(NULL_PEN); Assert(hpen != hNil, "Portfolio - draw background GetStockObject(NULL_PEN) failed"); hpenold = (HPEN)SelectObject(ps.hdc, hpen); hbr = (HBRUSH)GetStockObject(WHITE_BRUSH); Assert(hbr != hNil, "Portfolio - draw background GetStockObject(WHITE_BRUSH) failed"); hbrold = (HBRUSH)SelectObject(ps.hdc, hbr); hfnt = (HFONT)GetStockObject(SYSTEM_FONT); Assert(hfnt != hNil, "Portfolio - draw background GetStockObject(SYSTEM_FONT) failed"); hfntold = (HFONT)SelectObject(ps.hdc, hfnt); if ((pgpt = GPT::PgptNew(ps.hdc)) != pvNil) { RC rcDisplay; RCS rcsPort, rcsPreview; GNV gnv(pgpt); PGNV pgnvOff; PGPT pgptOff; HWND hwndPreview; CNO cnoBtn; int iBtnId; // Get the current size of the Portfolio window. GetClientRect(hwndCustom, &rcsPort); rcDisplay = rcsPort; // Must create offscreen dc and blit into that. Then blit that to dlg on screen. // If we blit straight from mbmp to screen, then screen flashes. (Due to white FILLRECT). if((pgptOff = GPT::PgptNewOffscreen(&rcDisplay, 8)) != pvNil) { if((pgnvOff = NewObj GNV(pgptOff)) != pvNil) { RC rcOrgPort(pdiPortfolio->rcsDlg); RC rcClip(ps.rcPaint); // Win95 may initially have streched the portfolio due to font sizing. // All the portfolio controls will also have been stretched. So we must // now stretch the portfolio background bitmap into the size win95 // originally made it, to make the background fit the current size // of the controls. // Note, if the user has scaled down the font then the portfolio bitmap // will not fill the entire portfolio area now. The area outside the portfolio // will be black. While this is not ideal, it will be rare and everything will // still work. pgnvOff->DrawMbmp(pmbmp, &rcOrgPort); // While we have this offscreen dc, paint all our custom display into it now. // First the caption text. Position the text using its size. GetWindowText(GetParent(hwndCustom), szCaption, sizeof(szCaption)); GetTextMetrics(ps.hdc, &tmCaption); pgnvOff->DrawRgch(szCaption, CchSz(szCaption), (tmCaption.tmAveCharWidth * 2), tmCaption.tmHeight / 4, kacrBlack, kacrClear); // If we were to blit the current offscreen dc to the screen, then the // display of the preview window and custom buttons will be overwritten. // Those windows would immediately be repainted to make the portfolio // whole again. However, the windows would flash between blitting the // background bitmap and repainting them. Therefore add the preview // and custom buttons to the offscreen dc now, so that when the dc // is blitted to the screen, these controls are good. // Note, that the preview window alone is repainted when the user selection // changes. Also the custom buttons are redrawn when we get the notification // to redraw them in the dlg hook proc. // Update the preview window now. hwndPreview = GetDlgItem(hwndCustom, IDC_PREVIEW); GetClientRect(hwndPreview, &rcsPreview); MapWindowPoints(hwndPreview, hwndCustom, (POINT*)&rcsPreview, 2); OpenPreview(hwndCustom, pgnvOff, &rcsPreview); // Now the custom buttons. for(iBtn = 0; iBtn < 3; ++iBtn) { switch (iBtn) { case 0: cnoBtn = kcnoMbmpPortBtnOk; iBtnId = IDC_BUTTON1; break; case 1: cnoBtn = kcnoMbmpPortBtnCancel; iBtnId = IDC_BUTTON2; break; case 2: cnoBtn = kcnoMbmpPortBtnHome; iBtnId = IDC_BUTTON3; break; default: continue; } if((pmbmpBtn = (PMBMP)vpapp->PcrmAll()->PbacoFetch(kctgMbmp, cnoBtn, MBMP::FReadMbmp))) { HWND hwndBtn = GetDlgItem(hwndCustom, iBtnId); RCS rcsBtn; RC rcItem; GetClientRect(hwndBtn, &rcsBtn); MapWindowPoints(hwndBtn, hwndCustom, (POINT*)&rcsBtn, 2); rcItem = rcsBtn; pgnvOff->DrawMbmp(pmbmpBtn, rcItem.xpLeft, rcItem.ypTop); ReleasePpo(&pmbmpBtn); } } // Clip the final blit, to the area which actually needs repainting. gnv.ClipRc(&rcClip); // Now finally blit our portfolio image to the screen. gnv.CopyPixels(pgnvOff, &rcDisplay, &rcDisplay); GPT::Flush(); ReleasePpo(&pgnvOff); } ReleasePpo(&pgptOff); } ReleasePpo(&pgpt); } // Restore the currently secleted objects back into he supplied hdc. SelectObject(ps.hdc, hpenold); SelectObject(ps.hdc, hbrold); SelectObject(ps.hdc, hfntold); ReleasePpo(&pmbmp); } EndPaint(hwndCustom, &ps); return; } /*************************************************************************** OpenPreview: Generate display for the preview of a movie. Arguments: hwndCustom - Handle to custom dlg window pgnvOff - Offscreen dc for displaying preview in. prcsPreview - RCS for displaying preview. Returns: nothing. ***************************************************************************/ void OpenPreview(HWND hwndCustom, PGNV pgnvOff, RCS *prcsPreview) { STN stn; PCFL pcfl; PMBMP pmbmp; FNI fni; SZ szFile; ERS ersT; ERS *pers; PDLGINFO pdiPortfolio = (PDLGINFO)GetWindowLong(hwndCustom, GWL_USERDATA); bool fPreviewed = fFalse; RC rcPreview(*prcsPreview); // If no preview is required, then do nothing here. if(pdiPortfolio->grfPrevType == 0) return; // Do not allow default error reporting to take place. This is due // to the fact that currently any queued errors do not appear // until the portfolio has been dismissed. pers = vpers; vpers = &ersT; // Clear the current contents of the preview window. pgnvOff->FillRc(&rcPreview, kacrBlack); // Get the currently selected file name. CommDlg_OpenSave_GetSpec( GetParent(hwndCustom), szFile, sizeof(szFile)); // Note the above call returns the name of the last selected file, even if the // user has since selected a folder! If the user hits the OK btn in this case, // win95 opens the last selected file anyway, so we are consistent here. if(CchSz(szFile) != 0) { // Get an fni for the selected file. stn.SetSz(szFile); fni.FBuildFromPath(&stn, 0); // If the user specified a directory, then don't preview it. if(!fni.FDir()) { // The name specifies a file. How should we preview it? if(pdiPortfolio->grfPrevType & fpfPortPrevMovie) { // Preview it as a movie if we can. if ((pcfl = CFL::PcflOpen(&fni, fcflNil)) != pvNil) { CKI ckiMovie; KID kidScene, kidThumb; BLCK blck; // Get the movie chunk from the open file. if (pcfl->FGetCkiCtg(kctgMvie, 0, &ckiMovie)) { // Now get the scene chunk and details. if (pcfl->FGetKidChidCtg(kctgMvie, ckiMovie.cno, 0, kctgScen, &kidScene)) { // Get the mbmp for the movie thumbnail. if (pcfl->FGetKidChidCtg(kctgScen, kidScene.cki.cno, 0, kctgThumbMbmp, &kidThumb) && pcfl->FFind(kidThumb.cki.ctg, kidThumb.cki.cno, &blck)) { if ((pmbmp = MBMP::PmbmpRead(&blck)) != pvNil) { // Stretch the preview into the preview window. pgnvOff->DrawMbmp(pmbmp, &rcPreview); ReleasePpo(&pmbmp); fPreviewed = fTrue; } } } } ReleasePpo(&pcfl); } } if(!fPreviewed && (pdiPortfolio->grfPrevType & fpfPortPrevTexture)) { // Preview the file as a .bmp file if we can. if ((pmbmp = MBMP::PmbmpReadNative(&fni, 0, 0, 0, fmbmpNil)) != pvNil) { // Stretch the bitmap in the preview window. pgnvOff->DrawMbmp(pmbmp, &rcPreview); ReleasePpo(&pmbmp); fPreviewed = fTrue; } } // Currently portfolio is not required to preview sound files. } } // Restore the default error reporting. vpers = pers; return; } /*************************************************************************** SubClassBtnProc: Subclass proc for custom btn ctrls.. Arguments: standard dialog proc args. Returns: TRUE/FALSE ***************************************************************************/ LRESULT CALLBACK SubClassBtnProc(HWND hwndBtn, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_ERASEBKGND: // We draw the button entirely later, so don't change the screen // at all here, This prevents any flashing while dlg repainted. return(1); default: break; } return(CallWindowProc (lpBtnProc, hwndBtn, msg, wParam, lParam)); } /*************************************************************************** SubClassPreviewProc: Subclass proc for preview window. Arguments: standard dialog proc args. Returns: TRUE/FALSE ***************************************************************************/ LRESULT CALLBACK SubClassPreviewProc(HWND hwndPreview, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; RCS rcsPreview; RC rcPreview; PGNV pgnvOff; PGPT pgpt, pgptOff; HPEN hpen, hpenold; HBRUSH hbr, hbrold; HFONT hfnt, hfntold; // Subclass this window so that when the user selects a file, we can only // invalidate and repaint this window, rather than repainting the entire dlg. BeginPaint(hwndPreview, &ps); // To ensure that we don't return from here with different objects // selected in the supplied hdc, save them here and restore later. // (Note that Kauai will attempt to delete things it finds selected // in the hdc passed to PgptNew). hpen = (HPEN)GetStockObject(NULL_PEN); Assert(hpen != hNil, "Portfolio - draw Preview GetStockObject(NULL_PEN) failed"); hpenold = (HPEN)SelectObject(ps.hdc, hpen); hbr = (HBRUSH)GetStockObject(WHITE_BRUSH); Assert(hbr != hNil, "Portfolio - draw Preview GetStockObject(WHITE_BRUSH) failed"); hbrold = (HBRUSH)SelectObject(ps.hdc, hbr); hfnt = (HFONT)GetStockObject(SYSTEM_FONT); Assert(hfnt != hNil, "Portfolio - draw Preview GetStockObject(SYSTEM_FONT) failed"); hfntold = (HFONT)SelectObject(ps.hdc, hfnt); if ((pgpt = GPT::PgptNew(ps.hdc)) != pvNil) { GNV gnv(pgpt); GetClientRect(hwndPreview, &rcsPreview); rcPreview = rcsPreview; if((pgptOff = GPT::PgptNewOffscreen(&rcPreview, 8)) != pvNil) { if((pgnvOff = NewObj GNV(pgptOff)) != pvNil) { // Get the preview image into our offscreen dc. OpenPreview(GetParent(hwndPreview), pgnvOff, &rcsPreview); // Now update the screen. gnv.CopyPixels(pgnvOff, &rcPreview, &rcPreview); GPT::Flush(); ReleasePpo(&pgnvOff); } ReleasePpo(&pgptOff); } ReleasePpo(&pgpt); } // Restore the currently secleted objects back into he supplied hdc. SelectObject(ps.hdc, hpenold); SelectObject(ps.hdc, hbrold); SelectObject(ps.hdc, hfntold); EndPaint(hwndPreview, &ps); return(0); } default: break; } return(CallWindowProc (lpPreviewProc, hwndPreview, msg, wParam, lParam)); } /*************************************************************************** SubClassDlgProc: Subclass proc for common dlg window.. Arguments: standard dialog proc args. Returns: TRUE/FALSE ***************************************************************************/ LRESULT CALLBACK SubClassDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_HELP: { // Do not invoke any help while the portfolio is displayed. return TRUE; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; // Process this to ensure the common dlg static controls look as good // as possible on our custom portfolio background. We could explictly // do this only for the two static ctrl currently used, // (ie stc3 = File Name, stc4 = Look In), but by taking this general // approach, if any new static ctrls are added in future versions of // win95, they will also look ok. (Except for the fact they won't be // known to the current custom portfolio background). This assumes // that we never want any common dlg static controls to be drawn using // the default system colors, but that's ok due the extent of our // portfolio customization. // Do not overwrite the background when writing the static control text. SetBkMode(hdc, TRANSPARENT); // Note, on win95 that static text always appears black, regardless of // the current system color settings. However, rather than relying on // this always being true, set the text color explicitly here. SetTextColor(hdc, RGB(0, 0, 0)); // Draw the control background in the light gray that matched the custom // background bitmap. Otherwise the background is drawn in the current // system color, which may not match the background at all. return((LONG)GetStockObject(LTGRAY_BRUSH)); } case WM_SYSCOMMAND: { // Is a screen saver trying to start? if (wParam == SC_SCREENSAVE) { // Disable the screen saver if we don't allow them to run. if (!vpapp->FAllowScreenSaver()) return fTrue; } break; } case WM_ERASEBKGND: { // Since the user never sees the common dialog background, ensure that // it can never be erased in the system color. Force a repaint of the // custom dlg now, to prevent the common dlg controls appearing before // the portfolio background. Note that GetDlgItem(hwndDlg, ) // returns zero here, as the Menu part of the custom dlg is zero. HWND hwndCustom = (HWND)GetWindowLong(hwndDlg, GWL_USERDATA); if(hwndCustom != 0) UpdateWindow(hwndCustom); return(1); } default: break; } return(CallWindowProc (lpDlgProc, hwndDlg, msg, wParam, lParam)); }