mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-29 20:49:27 +00:00
759f0d820f
This updates the various dialogs to use the new functions. The help system should be fully functional now.
387 lines
15 KiB
C++
387 lines
15 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "SelectFilesDialog.h"
|
|
#include "PathName.h"
|
|
#include "Util.h"
|
|
#include <dlgs.h>
|
|
|
|
|
|
void SelectFilesDialog::OnInitDone()
|
|
{
|
|
// Tweak the controls
|
|
SetControlText(IDOK, L"Accept");
|
|
|
|
// we don't take a "files of type" arg, so there's nothing in this combo box
|
|
HideControl(stc2); // "Files of type"
|
|
HideControl(cmb1); // (file type combo)
|
|
|
|
// Let the subclass do control configuration and data exchange.
|
|
(void) MyDataExchange(false);
|
|
|
|
// Configure a window proc to intercept events. We need to do it this
|
|
// way, rather than using m_ofn.lpfnHook, because the CFileDialog hook
|
|
// does not receive messages intended for the standard controls of the
|
|
// default dialog box. Since our goal is to intercept the "OK" button,
|
|
// we need to set a proc for the whole window.
|
|
CWnd* pParent = GetParent();
|
|
fPrevWndProc = (WNDPROC) ::SetWindowLongPtr(pParent->m_hWnd, GWL_WNDPROC,
|
|
(LONG_PTR) MyWindowProc);
|
|
|
|
// Stuff a pointer to this object into the userdata field.
|
|
::SetWindowLongPtr(pParent->m_hWnd, GWL_USERDATA, (LONG_PTR) this);
|
|
}
|
|
|
|
void SelectFilesDialog::OnFolderChange()
|
|
{
|
|
// We get one of these shortly after OnInitDone. We can't do this in
|
|
// OnInitDone because the dialog isn't ready yet.
|
|
//
|
|
// Ideally we'd just call GetFolderPath(), but for some reason that
|
|
// returns an empty string for places like Desktop or My Documents
|
|
// (which, unlike Computer or Libraries, do have filesystem paths).
|
|
// You actually get the path in the string returned from the multi-select
|
|
// file dialog, but apparently you have to use a semi-hidden method
|
|
// to get at it from OnFolderChange.
|
|
//
|
|
// In other words, par for the course in Windows file dialogs.
|
|
|
|
fCurrentDirectory = L"";
|
|
CWnd* pParent = GetParent();
|
|
LRESULT len = pParent->SendMessage(CDM_GETFOLDERIDLIST, 0, NULL);
|
|
if (len <= 0) {
|
|
LOGW("Can't get folder ID list, len is %d", len);
|
|
} else {
|
|
LPCITEMIDLIST pidlFolder = (LPCITEMIDLIST) CoTaskMemAlloc(len);
|
|
if (pidlFolder == NULL) {
|
|
LOGE("Unable to allocate %d bytes", len);
|
|
return;
|
|
}
|
|
len = pParent->SendMessage(CDM_GETFOLDERIDLIST, len,
|
|
(LPARAM) pidlFolder);
|
|
ASSERT(len > 0); // should fail earlier, or not at all
|
|
|
|
WCHAR buf[MAX_PATH];
|
|
BOOL result = SHGetPathFromIDList(pidlFolder, buf);
|
|
if (result) {
|
|
fCurrentDirectory = buf;
|
|
} else {
|
|
fCurrentDirectory = L"";
|
|
}
|
|
CoTaskMemFree((LPVOID) pidlFolder);
|
|
}
|
|
|
|
LOGD("OnFolderChange: '%ls'", (LPCWSTR) fCurrentDirectory);
|
|
}
|
|
|
|
BOOL SelectFilesDialog::OnFileNameOK()
|
|
{
|
|
// This function provides "custom validation of filenames that are
|
|
// entered into a common file dialog box". We don't need to validate
|
|
// filenames here, but we do require it for another reason: if the user
|
|
// double-clicks a file, the dialog will accept the name and close
|
|
// without our WindowProc getting a WM_COMMAND.
|
|
//
|
|
// This function doesn't get called if we select a file or files and
|
|
// hit the OK button or enter key, because we intercept that before
|
|
// the dialog can get to it. It also doesn't get called if you
|
|
// double-click directory, as directory traversal is handled internally
|
|
// by the dialog.
|
|
//
|
|
// It's possible to click then shift-double-click to select a range,
|
|
// so we can have content in both the list and the text box. This is
|
|
// really just another way to hit "OK".
|
|
|
|
LOGD("OnFileNameOK");
|
|
CFileDialog* pDialog = (CFileDialog*) GetParent();
|
|
FPResult fpr = OKButtonClicked(pDialog);
|
|
switch (fpr) {
|
|
case kFPDone:
|
|
return 0; // success, let the dialog exit
|
|
case kFPPassThru:
|
|
LOGW("WEIRD: got OK_PASSTHRU from double click");
|
|
return 1;
|
|
case kFPNoFiles:
|
|
LOGW("WEIRD: got OK_NOFILES from double click");
|
|
return 1;
|
|
case kFPError:
|
|
return 1;
|
|
default:
|
|
assert(false);
|
|
return 1;
|
|
}
|
|
// not reached
|
|
}
|
|
|
|
/*static*/ LRESULT CALLBACK SelectFilesDialog::MyWindowProc(HWND hwnd,
|
|
UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
SelectFilesDialog* pSFD =
|
|
(SelectFilesDialog*) ::GetWindowLong(hwnd, GWL_USERDATA);
|
|
|
|
if (uMsg == WM_COMMAND) {
|
|
// React to a click on the OK button (also triggered by hitting return
|
|
// in the filename text box).
|
|
if (HIWORD(wParam) == BN_CLICKED) {
|
|
if (LOWORD(wParam) == IDOK) {
|
|
// Obtain a CFileDialog pointer from the window handle. The
|
|
// SelectFilesDialog pointer only gets us to a child window
|
|
// (the one used to implement the templated custom stuff).
|
|
CFileDialog* pDialog = (CFileDialog*) CWnd::FromHandle(hwnd);
|
|
FPResult fpr = pSFD->OKButtonClicked(pDialog);
|
|
switch (fpr) {
|
|
case kFPDone:
|
|
// Success; close the dialog with IDOK as the return.
|
|
LOGD("Calling EndDialog");
|
|
pDialog->EndDialog(IDOK);
|
|
return 0;
|
|
case kFPPassThru:
|
|
LOGD("passing through");
|
|
// User typed a single name that didn't resolve to a
|
|
// simple file. Let the CFileDialog have it so it can
|
|
// do a directory change. (We don't just want to return
|
|
// nonzero -- fall through to call prior window proc.)
|
|
break;
|
|
case kFPNoFiles:
|
|
// No files have been selected. We popped up a message box.
|
|
// Discontinue processing.
|
|
return 0;
|
|
case kFPError:
|
|
// Something failed internally. Don't let the parent dialog
|
|
// continue on.
|
|
return 0;
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
} else if (LOWORD(wParam) == pshHelp) {
|
|
pSFD->HandleHelp();
|
|
return 0; // default handler will post "unable to open help"
|
|
}
|
|
}
|
|
}
|
|
|
|
return ::CallWindowProc(pSFD->fPrevWndProc, hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
SelectFilesDialog::FPResult SelectFilesDialog::OKButtonClicked(CFileDialog* pDialog)
|
|
{
|
|
// There are two not-quite-independent sources of filenames. As
|
|
// ordinary (non-directory) files are selected, they are added to the
|
|
// edit control. The user is free to edit the text in the box. This is
|
|
// then what would be returned to the user of the file dialog. With
|
|
// OFN_FILEMUSTEXIST, the dialog would even confirm that the files exist.
|
|
//
|
|
// It is possible to select a bunch of files, delete the text, type
|
|
// some more names, and hit OK. In that case you would get a completely
|
|
// disjoint set of names in the list control and edit control.
|
|
//
|
|
// Complicating matters somewhat is the "hide extensions for known file
|
|
// types" feature, which strips the filename extensions from the entries
|
|
// in the list control. As the files are selected, the names -- with
|
|
// extensions -- are added to the edit box. So we'd really like to get
|
|
// the names from the edit control.
|
|
//
|
|
// (Win7: Control Panel, Appearance and Personalization, Folder Options,
|
|
// View tab, scroll down, "Hide extensions for known file types" checkbox.)
|
|
//
|
|
// So we need to get the directory names out of the list control, because
|
|
// that's the only place we can find them, and we need to get the file
|
|
// names out of the edit control, because that's the only way to get the
|
|
// full file name (not to mention any names the user typed).
|
|
//
|
|
// We have a special case: we want to be able to type a path into
|
|
// the filename field to go directly there (e.g. "C:\src\test", or a
|
|
// network path like "\\webby\fadden"). If the text edit field contains
|
|
// a single name, and the name isn't a simple file, we want to let the
|
|
// selection dialog exercise its default behavior.
|
|
|
|
LOGV("OKButtonClicked");
|
|
|
|
// reset array in case we had a previous partially-successful attempt
|
|
fFileNameArray.RemoveAll();
|
|
|
|
// add a slash to the directory name, unless it's e.g. "C:\"
|
|
CString curDirSlash = fCurrentDirectory;
|
|
if (curDirSlash.GetAt(curDirSlash.GetLength() - 1) != '\\') {
|
|
curDirSlash += '\\';
|
|
}
|
|
|
|
// Get the contents of the text edit control.
|
|
CString editStr;
|
|
LRESULT editLen = pDialog->SendMessage(CDM_GETSPEC, 0, NULL);
|
|
if (editLen > 0) {
|
|
LPTSTR buf = editStr.GetBuffer(editLen);
|
|
pDialog->SendMessage(CDM_GETSPEC, editLen, (LPARAM) buf);
|
|
editStr.ReleaseBuffer();
|
|
}
|
|
LOGV("textedit has '%ls'", (LPCWSTR) editStr);
|
|
|
|
// Parse the contents into fFileNameArray.
|
|
int fileCount = ParseFileNames(editStr);
|
|
if (fileCount < 0) {
|
|
::MessageBeep(MB_ICONERROR);
|
|
return kFPError;
|
|
}
|
|
|
|
// find the ShellView control
|
|
CWnd* pListWrapper = pDialog->GetDlgItem(lst2);
|
|
if (pListWrapper == NULL) {
|
|
LOGE("Unable to find ShellView (lst2=%d) in file dialog", lst2);
|
|
return kFPError;
|
|
}
|
|
|
|
// get the list control, with voodoo
|
|
CListCtrl* pList = (CListCtrl*) pListWrapper->GetDlgItem(1);
|
|
if (pList == NULL) {
|
|
LOGE("Unable to find list control");
|
|
return kFPError;
|
|
}
|
|
|
|
// Check to see if nothing is selected, or exactly one directory has
|
|
// been found in the text box.
|
|
int listCount = pList->GetSelectedCount();
|
|
LOGD("Found %d selected items", listCount);
|
|
if (listCount + fileCount == 0) {
|
|
MessageBox(L"Please select one or more files and directories.",
|
|
m_ofn.lpstrTitle, MB_OK | MB_ICONWARNING);
|
|
return kFPNoFiles;
|
|
} else if (fileCount == 1 && listCount == 0) {
|
|
CString file(fFileNameArray[0]);
|
|
CString path;
|
|
if (file.Find('\\') == -1) {
|
|
// just the filename, prepend the current dir
|
|
path = curDirSlash + file;
|
|
} else {
|
|
// full (?) path, don't alter it
|
|
path = file;
|
|
}
|
|
DWORD attr = GetFileAttributes(path);
|
|
LOGV("Checking to see if '%ls' is a directory: 0x%08lx",
|
|
(LPCWSTR) path, attr);
|
|
if (attr != INVALID_FILE_ATTRIBUTES &&
|
|
(attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
{
|
|
return kFPPassThru;
|
|
}
|
|
}
|
|
|
|
// Run through the list, looking for directories. We have to prepend the
|
|
// current directory to each entry and check the filesystem.
|
|
int index = pList->GetNextItem(-1, LVNI_SELECTED);
|
|
int dirCount = 0;
|
|
while (listCount--) {
|
|
CString itemText(pList->GetItemText(index, 0));
|
|
CString path(curDirSlash + itemText);
|
|
DWORD attr = GetFileAttributes(path);
|
|
LOGV(" %d: 0x%08lx '%ls'", index, attr, (LPCWSTR) itemText);
|
|
if (attr != INVALID_FILE_ATTRIBUTES &&
|
|
(attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
{
|
|
fFileNameArray.Add(itemText);
|
|
dirCount++;
|
|
}
|
|
|
|
index = pList->GetNextItem(index, LVNI_SELECTED);
|
|
}
|
|
LOGV(" added %d directories", dirCount);
|
|
|
|
// let sub-classes copy data out, and have an opportunity to reject values
|
|
if (!MyDataExchange(true)) {
|
|
LOGW("MyDataExchange failed!");
|
|
return kFPError;
|
|
}
|
|
|
|
return kFPDone;
|
|
}
|
|
|
|
int SelectFilesDialog::ParseFileNames(const CString& str)
|
|
{
|
|
// The filename string can come in two forms. If only one filename was
|
|
// selected, the entire string will be the filename (spaces and all). If
|
|
// more than one file was selected, the individual filenames will be
|
|
// surrounded by double quotes. All of this assumes that the names were
|
|
// put there by the dialog -- if the user just types a bunch of
|
|
// space-separated filenames without quotes, we can't tell that from a
|
|
// single long name with spaces.
|
|
//
|
|
// Double quotes aren't legal in Windows filenames, so we don't have
|
|
// to worry about embedded quotes.
|
|
//
|
|
// So: if the string contains any double quotes, we assume that a series
|
|
// of quoted names follows. Anything outside quotes is accepted as a
|
|
// single space-separated name. If the string does not have any '"',
|
|
// we just grab the whole thing as a single entry.
|
|
//
|
|
// It's possible to see a full path here -- we allow a special case
|
|
// where the user can type a path into the text box to change directories.
|
|
// We only expect to see one of those, though, so it must not be quoted.
|
|
//
|
|
// The dialog seems to leave an extra space at the end of multi-file
|
|
// strings.
|
|
|
|
LOGD("ParseFileNames '%ls'", (LPCWSTR) str);
|
|
if (str.GetLength() == 0) {
|
|
// empty string
|
|
return 0;
|
|
} else if (str.Find('\"') == -1) {
|
|
// no quotes, single filename, possibly with spaces
|
|
fFileNameArray.Add(str);
|
|
return 1;
|
|
} else if (str.Find('\\') != -1) {
|
|
// should not be multiple full/partial paths, string is invalid
|
|
LOGW("Found multiple paths in '%ls'", (LPCWSTR) str);
|
|
return -1;
|
|
}
|
|
|
|
const WCHAR* cp = str;
|
|
const WCHAR* start;
|
|
while (*cp != '\0') {
|
|
// consume whitespace, which should only be spaces
|
|
while (*cp == ' ') {
|
|
cp++;
|
|
}
|
|
|
|
if (*cp == '\0') {
|
|
// reached end of string
|
|
break;
|
|
} else if (*cp == '\"') {
|
|
// grab quoted item
|
|
cp++;
|
|
start = cp;
|
|
while (*cp != '\"' && *cp != '\0') {
|
|
cp++;
|
|
}
|
|
|
|
if (cp == start) {
|
|
// empty quoted string; ignore
|
|
LOGV(" found empty string in '%ls'", (LPCWSTR) str);
|
|
} else {
|
|
CString name(start, cp - start);
|
|
LOGV(" got quoted '%ls'", (LPCWSTR) name);
|
|
fFileNameArray.Add(name);
|
|
}
|
|
if (*cp != '\0') {
|
|
cp++; // advance past closing quote
|
|
} else {
|
|
LOGV(" (missing closing quote)");
|
|
}
|
|
} else {
|
|
// found unquoted characters
|
|
start = cp;
|
|
cp++;
|
|
while (*cp != ' ' && *cp != '\"' && *cp != '\0') {
|
|
cp++;
|
|
}
|
|
CString name(start, cp - start);
|
|
LOGV(" got unquoted '%ls'", (LPCWSTR) name);
|
|
fFileNameArray.Add(name);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|