/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * File selection dialog, a sub-class of "Open" that allows multiple selection * of both files and directories. * * This is something of a nightmare to work through. The standard Windows * dialog will return multiple selected files, but omits the directories, * leaving the developer to find alternative means of acquiring the complete * list of files. The most popular approach is to dig into the CListView * object (lst2) and peruse the set of selected files from the control itself. * * Complicating this is the existence of three very different dialog * implementations, known as "old style", "explorer" and "vista". Since * we are currently targeting WinXP as a minimum OS level, and I would * prefer not to have multiple implementations, this code targets the * explorer-style dialogs. * * The API follows the standard file dialog multi-select pattern, which * returned a directory name followed by a series of files in that directory. * We simplify things a bit by returning the pathname separately and the * filenames in a string array. * * The current implementation owes a debt to Hojjat Bohlooli's * SelectDialog sample (http://www.codeproject.com/Articles/28015/). * * Other relevant links: * http://www.codeproject.com/dialog/select_all_button.asp * http://www.codeproject.com/dialog/select_all_button.asp * http://www.codeproject.com/dialog/customize_dialog.asp * http://stackoverflow.com/questions/31059/ * * I wish I could say all this nonsense was fixed in the "vista" dialogs, * but it isn't (e.g. http://stackoverflow.com/questions/8269696/ ). */ #ifndef UTIL_SELECTFILESDIALOG_H #define UTIL_SELECTFILESDIALOG_H /* * File selection, based on an "open" common file dialog. */ class SelectFilesDialog : public CFileDialog { public: SelectFilesDialog(const WCHAR* rctmpl, bool showHelp, CWnd* pParentWnd = NULL) : CFileDialog(true, NULL, NULL, OFN_HIDEREADONLY, NULL, pParentWnd, 0, FALSE /*disable Vista style*/), fPrevWndProc(NULL) { // Set flags. We specify ALLOWMULTISELECT but no filename buffer; // we want the multi-select behavior but we don't want to return // the filenames in the filename buf. m_ofn.Flags |= OFN_ENABLETEMPLATE | OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLESIZING | (showHelp ? OFN_SHOWHELP : 0); // Configure use of a template. The dialog template must have // WS_CHILD and WS_CLIPSIBLINGS set, and should have DS_3DLOOK and // DS_CONTROL set as well. m_ofn.lpTemplateName = rctmpl; m_ofn.hInstance = AfxGetInstanceHandle(); } virtual ~SelectFilesDialog(void) {} /* * Gets the directory name where the files live. This is a full path. */ const CString& GetDirectory(void) const { return fCurrentDirectory; } /* * Gets the file name array. This is only valid if the dialog exited * successfully (DoModal returned IDOK). */ const CStringArray& GetFileNames(void) const { return fFileNameArray; } /* * Sets the window title; must be called before DoModal. */ void SetWindowTitle(const WCHAR* title) { fCustomTitle = title; m_ofn.lpstrTitle = (LPCWSTR) fCustomTitle; } /* * Stuffs a single file/directory into the return value fields. This is * (mis-)used by code that treats AddFilesDialog as a way to pass data * around. * * TODO: don't do this -- change the callers to pass data around differently */ void StuffSingleFilename(const CString& directory, const CString& filename) { fCurrentDirectory = directory; fFileNameArray.RemoveAll(); fFileNameArray.Add(filename); } protected: // This is much like DoDataExchange, but ret val replaces pDX->Fail(). // This will be called with saveAndValidate==true during OnInitDialog, // and with false when we've extracted the filenames and are about to // close the dialog. // // Return true on success, false if something failed and we want to keep // the dialog open. virtual bool MyDataExchange(bool saveAndValidate) { return true; } // Handles a click on the "help" button. virtual void HandleHelp() {} private: DECLARE_COPY_AND_OPEQ(SelectFilesDialog) /* * Finishes configuring the file dialog. */ virtual void OnInitDone() override; /* * Tracks changes to the current directory. * * Updates fCurrentDirectory with the path currently being used by the * dialog. For items with no path (e.g. Computer or Libraries), this * will set an empty string. */ virtual void OnFolderChange() override; /* * Custom filename validation; in our case, it's a double-click trap. * * Returns 0 if the name looks good, 1 otherwise. If we return 1, the * dialog will not close. */ virtual BOOL OnFileNameOK() override; /* * Our WindowProc callback function. Watches for the OK button. */ static LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // filename parse result enum FPResult { kFPDone, kFPPassThru, kFPNoFiles, kFPError }; /* * The "OK" (actually "Open" or "Accept") button was clicked. Extract * the filenames from the list control. * * Possible return values: * OK_DONE * We have successfully obtained the list of files and folders. * OK_PASSTHRU * Let the parent dialog process the event. This is done when the * edit box contains a directory name -- we want the dialog to * change to that directory. * OK_NOFILES * No files were selected. Keep the dialog open. * OK_ERROR * Something went wrong. */ FPResult OKButtonClicked(CFileDialog* pDialog); /* * Parses the file name string returned by the dialog. Adds them to * fPathNameArray. Returns the number of names found, or -1 if the * string appears to be invalid. */ int ParseFileNames(const CString& str); /* * Previous WindowProc. Most messages will be forwarded to this. */ WNDPROC fPrevWndProc; CString fCustomTitle; // Directory the dialog is currently accessing. Prepend this to the // entries in fFileNameArray to get full paths. CString fCurrentDirectory; // File names of selected files. CStringArray fFileNameArray; }; #endif /*UTIL_SELECTFILESDIALOG_H*/