/////////////////////////////////////////////////////////////////////////////// /* File: details.cpp Description: Contains definition for class DetailsView. This class implements a list view containing quota information about the various accounts in a volume's quota information file. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 05/28/97 Major changes. BrianAu - Added "User Finder". - Added promotion of selected item to front of name resolution queue. - Improved name resolution status reporting through listview. - Moved drag/drop and report generation code from dragdrop.cpp and reptgen.cpp into the DetailsView class. DetailsView now implements IDataObject, IDropSource and IDropTarget instead of deferring implementation to secondary objects. dragdrop.cpp and reptgen.cpp have been dropped from the project. - Added support for CF_HDROP and private import/ export clipboard formats. - Added import/export functionality. 07/28/97 Removed export support for CF_HDROP. Replaced BrianAu with FileContents and FileGroupDescriptor. Import from CF_HDROP is still supported. Added Import Source object hierarchy. */ /////////////////////////////////////////////////////////////////////////////// #include "pch.h" // PCH #pragma hdrstop #include #include #include #include "uihelp.h" #include "uiutils.h" #include "dskquota.h" #include "registry.h" #include "resource.h" #include "shellinc.h" #include "userprop.h" #include "details.h" #include "guidsp.h" #include "mapfile.h" #include "progress.h" #include "yntoall.h" #include "ownerlst.h" #include "ownerdlg.h" #include "adusrdlg.h" // // Constant text strings. // TCHAR c_szWndClassDetailsView[] = TEXT("DetailsView"); // // Bitmap dimension constants. // const UINT BITMAP_WIDTH = 16; const UINT BITMAP_HEIGHT = 16; const UINT LG_BITMAP_WIDTH = 32; const UINT LG_BITMAP_HEIGHT = 32; // // How much to grow the user object list whenever expansion is required. // const INT USER_LIST_GROW_AMT = 100; // // This structure is used to pass the DetailsView object's "this" pointer // in WM_CREATE. // typedef struct WndCreationData { SHORT cbExtra; LPVOID pThis; } WNDCREATE_DATA; typedef UNALIGNED WNDCREATE_DATA *PWNDCREATE_DATA; // // Structure passed to CompareItems callback. // typedef struct comparestruct { DWORD idColumn; DWORD dwDirection; DetailsView *pThis; } COMPARESTRUCT, *PCOMPARESTRUCT; // // Define some names for indexes into the listview's image list. // #define iIMAGELIST_ICON_NOIMAGE (-1) #define iIMAGELIST_ICON_OK 0 #define iIMAGELIST_ICON_WARNING 1 #define iIMAGELIST_ICON_LIMIT 2 // // The 0-based index of the "View" item in the main menu and of the // "Arrange" item in the view menu. // WARNING: If you change menu items, these may need updating. // #define iMENUITEM_VIEW 2 #define iMENUITEM_VIEW_ARRANGE 4 // // Same thing for the "Edit" menu. // #define iMENUITEM_EDIT 1 // // Add/remove from this array to change the columns in the list view. // IMPORTANT: // The ordering of these items is very important (sort of). // Because of a bug in commctrl.h, they don't paint under the bitmap // if it's the only thing in the column (or if it's the bitmap of the primary // item). Also, the behavior of the listview is such that the text in // subitem 0 is always shifted right the width of a small bitmap. When // I had the status column NOT as item 0, there were two display problems. // 1) First, the text in column 0 was always shifted right to allow for the // bitmap we weren't using. This looked funny. // 2) The full-row-select highlight didn't properly paint the background // of the status bitmap. // // By placing the status column as subitem 0, we eliminate problem 1 since // we're using a bitmap in subitem 0 (listview's default behavior). // If we drag the status column out of the leftmost position, they still don't // paint under the bitmap but at least it will work like any other explorer // view. When/if they fix listview, we'll be fixed automatically. // const DV_COLDATA g_rgColumns[] = { { LVCFMT_LEFT | LVCFMT_COL_HAS_IMAGES, 0, IDS_TITLE_COL_STATUS, DetailsView::idCol_Status }, { LVCFMT_LEFT, 0, IDS_TITLE_COL_FOLDER, DetailsView::idCol_Folder }, { LVCFMT_LEFT, 0, IDS_TITLE_COL_USERNAME, DetailsView::idCol_Name }, { LVCFMT_LEFT, 0, IDS_TITLE_COL_LOGONNAME, DetailsView::idCol_LogonName }, { LVCFMT_RIGHT, 0, IDS_TITLE_COL_AMTUSED, DetailsView::idCol_AmtUsed }, { LVCFMT_RIGHT, 0, IDS_TITLE_COL_LIMIT, DetailsView::idCol_Limit }, { LVCFMT_RIGHT, 0, IDS_TITLE_COL_THRESHOLD, DetailsView::idCol_Threshold }, { LVCFMT_RIGHT, 0, IDS_TITLE_COL_PCTUSED, DetailsView::idCol_PctUsed }, }; // // User quota state constants. // used for identifying which icon to display in "Status" column. // const INT iUSERSTATE_OK = 0; const INT iUSERSTATE_WARNING = 1; const INT iUSERSTATE_OVERLIMIT = 2; // // Maximum number of entries allowed in the "Find User" MRU list. // const INT DetailsView::MAX_FINDMRU_ENTRIES = 10; // // Dimensions for the "Find User" combo box in the toolbar. // const INT DetailsView::CX_TOOLBAR_COMBO = 200; const INT DetailsView::CY_TOOLBAR_COMBO = 200; /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DetailsView Description: Class constructor. Arguments: None. Returns: Nothing. Exceptions: OutOfMemory Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/21/97 Ownerdata listview. Added m_UserList. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::DetailsView( VOID ) : m_cRef(0), m_UserList(USER_LIST_GROW_AMT), m_hwndMain(NULL), m_hwndListView(NULL), m_hwndStatusBar(NULL), m_hwndToolBar(NULL), m_hwndListViewToolTip(NULL), m_hwndHeader(NULL), m_hKbdAccel(NULL), m_lpfnLVWndProc(NULL), m_pQuotaControl(NULL), m_pUserFinder(NULL), m_DropSource(MK_LBUTTON), m_DropTarget(MK_LBUTTON), m_pDataObject(NULL), m_pUndoList(NULL), m_ColMap(ARRAYSIZE(g_rgColumns)), m_strAccountUnresolved(g_hInstDll, IDS_USER_ACCOUNT_UNRESOLVED), m_strAccountUnavailable(g_hInstDll, IDS_USER_ACCOUNT_UNAVAILABLE), m_strAccountUnknown(g_hInstDll, IDS_USER_ACCOUNT_UNKNOWN), m_strAccountDeleted(g_hInstDll, IDS_USER_ACCOUNT_DELETED), m_strAccountInvalid(g_hInstDll, IDS_USER_ACCOUNT_INVALID), m_strNoLimit(g_hInstDll, IDS_NO_LIMIT), m_strNotApplicable(g_hInstDll, IDS_NOT_APPLICABLE), m_strStatusOK(g_hInstDll, IDS_STATUS_OK), m_strStatusWarning(g_hInstDll, IDS_STATUS_WARNING), m_strStatusOverlimit(g_hInstDll, IDS_STATUS_OVERLIMIT), m_pIDataObjectOnClipboard(NULL), m_dwEventCookie(0), m_iLastItemHit(-1), m_iLastColSorted(-1), m_fSortDirection(0), m_bMenuActive(FALSE), m_bWaitCursor(FALSE), m_bStopLoadingObjects(FALSE), m_bDestroyingView(FALSE), m_bCritSecInited_AsyncUpdate(FALSE) { DBGTRACE((DM_VIEW, DL_HIGH, TEXT("DetailsView::DetailsView"))); // // Make sure the idCol_XXX constants agree // with the size of g_rgColumns. // DBGASSERT((ARRAYSIZE(g_rgColumns) == DetailsView::idCol_Last)); ZeroMemory(&m_lvsi, sizeof(m_lvsi)); m_ptMouse.x = 0; m_ptMouse.y = 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Initialize Description: Initializes a new details view object. Arguments: idVolume - Ref to a const CVolumeID object containing both the parsable and displayable names for the volume. Returns: TRUE = Success. FALSE = Out of memory or couldn't create thread. Either way, we can't run the view. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 12/06/96 Initial creation. Moved this code out of the ctor. BrianAu 02/25/97 Removed m_hwndPropPage from DetailsView. BrianAu 05/20/97 Added user finder object. BrianAu 06/28/98 Added support for mounted volumes. BrianAu 02/26/02 Moved critsec initialization from ctor. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::Initialize( const CVolumeID& idVolume ) { BOOL bResult = FALSE; HANDLE hThread = NULL; if (!InitializeCriticalSectionAndSpinCount(&m_csAsyncUpdate, 0)) { return FALSE; } m_bCritSecInited_AsyncUpdate = TRUE; try { // // Create the reg parameter objects we'll be using in the UI. // The RegParamTable functions will not add a duplicate entry. // // // Parameter: Preferences // LV_STATE_INFO lvsi; InitLVStateInfo(&lvsi); // // Create a private copy of the file sys object name string. // This can throw OutOfMemory. // m_idVolume = idVolume; if (FAILED(CreateVolumeDisplayName(m_idVolume, &m_strVolumeDisplayName))) { m_strVolumeDisplayName = m_idVolume.ForDisplay(); } // // Read saved state of listview from registry. // Saved info includes window ht/wd, column widths and // toolbar/status bar visibility. Need this info before we start thread. // RegKey keyPref(HKEY_CURRENT_USER, REGSTR_KEY_DISKQUOTA); if (FAILED(keyPref.Open(KEY_READ)) || FAILED(keyPref.GetValue(REGSTR_VAL_PREFERENCES, (LPBYTE)&m_lvsi, sizeof(m_lvsi))) || !DetailsView::IsValidLVStateInfo(&m_lvsi)) { // // Protect us from truly bogus data. If it's bad, or obsolete, // just re-initialize it. // DBGERROR((TEXT("Listview persist state info invalid. Re-initializing."))); DetailsView::InitLVStateInfo(&m_lvsi); } // // Transfer sorting information to member variables. // These can be changed by user-initiated events. // m_iLastColSorted = m_lvsi.iLastColSorted; m_fSortDirection = m_lvsi.fSortDirection; // // Create the user finder object. // This is used to locate users through the toolbar combo box and // the "Find User" dialog. The finder object maintains a MRU list for // both the toolbar and dialog combos. // m_pUserFinder = new Finder(*this, MAX_FINDMRU_ENTRIES); // // Create the data object we use to control data transfers. // m_pDataObject = new DataObject(*this); // // Create a new thread on which to run the details view window. // This is so that the details view will remain alive if the // property page is destroyed. This must be done last in this method // so that if we return FALSE, the caller is assured there is no thread // running loose. If we return FALSE, they'll have to call "delete" // to release any string allocations done above. If we return TRUE, // the caller must not call delete on the object. The object will // destroy itself when the user closes the view window. // hThread = CreateThread(NULL, // No security attributes. 0, // Default stack size. &ThreadProc, this, // Static thread proc needs this. 0, // Not suspended. NULL); if (NULL != hThread) { CloseHandle(hThread); // // Everything succeeded. // bResult = TRUE; } } catch(CAllocException& e) { // // Catch an allocation exception here. // We'll return FALSE indicating initialization failure. // } return bResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::~DetailsView Description: Class destructor. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/21/97 Ownerdata listview. Added m_UserList. BrianAu 05/20/97 Added user finder object. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::~DetailsView( VOID ) { DBGTRACE((DM_VIEW, DL_HIGH, TEXT("DetailsView::~DetailsView"))); // // Destroy the user object list if it still has some objects. // ReleaseObjects(); delete m_pUserFinder; delete m_pUndoList; delete m_pDataObject; if (m_bCritSecInited_AsyncUpdate) { DeleteCriticalSection(&m_csAsyncUpdate); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::QueryInterface Description: Returns an interface pointer to the object's supported interfaces. Arguments: riid - Reference to requested interface ID. ppvOut - Address of interface pointer variable to accept interface ptr. Returns: NO_ERROR - Success. E_NOINTERFACE - Requested interface not supported. E_INVALIDARG - ppvOut argument was NULL. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::QueryInterface( REFIID riid, LPVOID *ppv ) { HRESULT hResult = NO_ERROR; if (NULL != ppv) { *ppv = NULL; if (IID_IUnknown == riid || IID_IDiskQuotaEvents == riid) { *ppv = static_cast(this); } else if (IID_IDataObject == riid) { *ppv = static_cast(this); } else if (IID_IDropSource == riid) { *ppv = static_cast(this); } else if (IID_IDropTarget == riid) { *ppv = static_cast(this); } else hResult = E_NOINTERFACE; if (NULL != *ppv) { ((LPUNKNOWN)*ppv)->AddRef(); } } else hResult = E_INVALIDARG; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::AddRef Description: Increments object reference count. Arguments: None. Returns: New reference count value. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/15/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) DetailsView::AddRef( VOID ) { ULONG cRef = InterlockedIncrement(&m_cRef); DBGPRINT((DM_COM, DL_HIGH, TEXT("DetailsView::AddRef, 0x%08X %d -> %d\n"), this, cRef - 1, cRef )); return cRef; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Release Description: Decrements object reference count. If count drops to 0, object is deleted. Arguments: None. Returns: New reference count value. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/15/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP_(ULONG) DetailsView::Release( VOID ) { ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); DBGPRINT((DM_COM, DL_HIGH, TEXT("DetailsView::Release, 0x%08X %d -> %d\n"), this, cRef + 1, cRef )); if ( 0 == cRef ) { delete this; } return cRef; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::ThreadProc Description: Thread procedure for the details view window. Creates the quota control object and the main window. Then it just sits processing messages until it receives a WM_QUIT message. Arguments: pvParam - Address of DetailsView instance. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 03/22/00 Fixed proc param for ia64. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DWORD DetailsView::ThreadProc( LPVOID pvParam ) { HRESULT hResult = NO_ERROR; DetailsView *pThis = (DetailsView *)pvParam; DBGPRINT((DM_VIEW, DL_HIGH, TEXT("LISTVIEW - New thread %d"), GetCurrentThreadId())); DBGASSERT((NULL != pThis)); // // Need to ensure DLL stays loaded while this thread is active. // InterlockedIncrement(&g_cRefThisDll); // // This will keep the view object alive while the thread is alive. // We call Release when the thread terminates. // pThis->AddRef(); // // Must call OleInitialize() for new thread. // try { if (SUCCEEDED(OleInitialize(NULL))) { // // Create the quota control object. // Why don't we just use the same quota controller as the // volume property page? Good question. // Since we're on a separate thread, we either need a new // object or marshal the IDiskQuotaControl interface. // I chose to create a new object rather than take the // performance hit of the additional marshaling. The quota // controller object is used heavily by the details view. // hResult = CoCreateInstance(CLSID_DiskQuotaControl, NULL, CLSCTX_INPROC_SERVER, IID_IDiskQuotaControl, (LPVOID *)&(pThis->m_pQuotaControl)); if (SUCCEEDED(hResult)) { hResult = pThis->m_pQuotaControl->Initialize(pThis->m_idVolume.ForParsing(), TRUE); // Read-write. if (SUCCEEDED(hResult)) { // // Create the main window. // hResult = pThis->CreateMainWindow(); if (SUCCEEDED(hResult)) { MSG msg; DBGASSERT((NULL != pThis->m_hwndMain)); // // Place a message in the queue that the window has been // created. Now creation of the other controls can procede. // // It is VERY important that once we receive a WM_QUIT message, // no members of the DetailsView instance are referenced. // Posting WM_QUIT is the last thing done by the WM_DESTROY handler. // PostMessage(pThis->m_hwndMain, WM_MAINWINDOW_CREATED, 0, 0); while (0 != GetMessage(&msg, NULL, 0, 0)) { if (NULL == pThis->m_hKbdAccel || !TranslateAccelerator(pThis->m_hwndMain, pThis->m_hKbdAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } } } else { DBGERROR((TEXT("LISTVIEW - OleInitialize failed for thread %d."), GetCurrentThreadId())); } OleUninitialize(); } } catch(CAllocException& e) { DiskQuotaMsgBox(GetDesktopWindow(), IDS_OUTOFMEMORY, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); pThis->CleanupAfterAbnormalTermination(); } DBGPRINT((DM_VIEW, DL_HIGH, TEXT("LISTVIEW - Exit thread %d"), GetCurrentThreadId())); // // Release the view object since it's no longer required. // This will call the destructor. // pThis->Release(); ASSERT( 0 != g_cRefThisDll ); InterlockedDecrement(&g_cRefThisDll); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CleanupAfterAbnormalTermination Description: Perform operations required after the thread has terminated abnormally. This function assumes that the thread's message pump is no longer active. Any operations performed must not generate messages that require processing by the thread. This method does almost the same things as OnDestroy(). Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 12/15/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::CleanupAfterAbnormalTermination( VOID ) { // // Cancel subclassing of the listview control. // if (NULL != m_lpfnLVWndProc) SetWindowLongPtr(m_hwndListView, GWLP_WNDPROC, (INT_PTR)m_lpfnLVWndProc); DisconnectEventSink(); // // NOTE: We can't call ReleaseObjects() because that method // requires an active listview. Our thread is finished and the // window is gone. // if (NULL != m_pQuotaControl) { m_pQuotaControl->Release(); m_pQuotaControl = NULL; } // // If we have a data object on the clipboard, clear the clipboard. // Note that the clipboard holds the reference to the data object. // When we clear the clipboard, the data object will be released. // if (NULL != m_pIDataObjectOnClipboard && S_OK == OleIsCurrentClipboard(m_pIDataObjectOnClipboard)) { OleFlushClipboard(); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnUserNameChanged Description: Called by the event source (SidNameResolver) whenever a disk quota user object's name has changed. The user object's folder and account name strings are updated in the list view. Arguments: pUser - Address of IDiskQuotaUser interface for user object that has a new name. Returns: NO_ERROR - Success. E_INVALIDARG - User object pointer received from event source was invalid. E_FAIL - User not found in listview list. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 12/10/96 Use free-threading OLE apartment model. BrianAu 02/05/98 Changed ListView_RedrawItems to use BrianAu SendMessageTimeout(). */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::OnUserNameChanged( PDISKQUOTA_USER pUser ) { HRESULT hResult = E_FAIL; // // Ensure the DetailsView object stay's alive while the view is updated. // Remember, this code is being run on the SID/Name resolver's thread. // AddRef(); // // We don't want to perform a user-name-changed update if the // view is being or has been destroyed. Likewise, we don't want to // destroy the view window while a user-name-changed update is in // progress. The crit sec m_csAsyncUpdate and the flag m_bDestroyingView // work together to ensure this. // EnterCriticalSection(&m_csAsyncUpdate); if (!m_bDestroyingView) { try { if (NULL != pUser) { INT iItem = -1; if (m_UserList.FindIndex((LPVOID)pUser, &iItem)) { // // Send message to listview to redraw the item // that changed. Use the "timeout" version of // SendMessage because the main window thread // could be blocked waiting for m_csAsyncUpdate // which is now owned by the resolver thread. // If the main thread is blocked (waiting to // process WM_DESTROY), this call will return 0 // after 5 seconds. If this happens, we leave the // CS without generating a window update, releasing // the CS and letting the main window thread continue // with WM_DESTROY processing. // DWORD_PTR dwResult; LRESULT lResult = SendMessageTimeout(m_hwndListView, LVM_REDRAWITEMS, (WPARAM)iItem, (LPARAM)iItem, SMTO_BLOCK, 5000, &dwResult); if (lResult) UpdateWindow(m_hwndListView); else DBGERROR((TEXT("ListView update timed out after 5 seconds"))); hResult = NO_ERROR; } } else hResult = E_INVALIDARG; } catch(CAllocException& e) { // // Catch allocation exceptions and do nothing. // Resolver doesn't care about return value. // Want to ensure that Release() is called no matter what. // hResult = E_OUTOFMEMORY; } } LeaveCriticalSection(&m_csAsyncUpdate); Release(); return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateMainWindow Description: Creates the main window for the details view. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Couldn't create window. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateMainWindow( VOID ) { HRESULT hResult = NO_ERROR; WNDCLASSEX wc; DWORD dwExStyle; LANGID LangID; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_PARENTDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = g_hInstDll; wc.hIcon = LoadIcon(g_hInstDll, MAKEINTRESOURCE(IDI_QUOTA)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = MAKEINTRESOURCE(IDM_LISTVIEW_MENU); wc.lpszClassName = c_szWndClassDetailsView; wc.hIconSm = NULL; RegisterClassEx(&wc); // // Need to pass "this" pointer in WM_CREATE. We'll store "this" // in the window's USERDATA. // WNDCREATE_DATA wcd; wcd.cbExtra = sizeof(WNDCREATE_DATA); wcd.pThis = this; // // Create the window title string. // "Quota Details for My Disk (X:)" // CString strWndTitle(g_hInstDll, IDS_TITLE_MAINWINDOW, (LPCTSTR)m_strVolumeDisplayName); HWND hwndDesktop = GetDesktopWindow(); HDC hdc = GetDC(hwndDesktop); // // Get current screen resolution. // if ((m_lvsi.cxScreen != (WORD)GetDeviceCaps(hdc, HORZRES)) || (m_lvsi.cyScreen != (WORD)GetDeviceCaps(hdc, VERTRES))) { // // Screen resolution has changed since listview state data was // last saved to registry. Use the default window ht/wd. // m_lvsi.cx = 0; m_lvsi.cy = 0; } ReleaseDC(hwndDesktop, hdc); // Check if we are running on BiDi Localized build. we need to create the Main Window // mirrored (WS_EX_LAYOUTRTL). dwExStyle = 0; LangID = GetUserDefaultUILanguage(); if( LangID ) { LCID iLCID; WCHAR wchLCIDFontSignature[16]; iLCID = MAKELCID( LangID , SORT_DEFAULT ); if( GetLocaleInfoW( iLCID , LOCALE_FONTSIGNATURE , (WCHAR *) &wchLCIDFontSignature[0] , (sizeof(wchLCIDFontSignature)/sizeof(WCHAR))) ) { if( wchLCIDFontSignature[7] & (WCHAR)0x0800 ) { dwExStyle = WS_EX_LAYOUTRTL; } } } m_hwndMain = CreateWindowEx(dwExStyle, c_szWndClassDetailsView, strWndTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, m_lvsi.cx ? m_lvsi.cx : CW_USEDEFAULT, m_lvsi.cy ? m_lvsi.cy : CW_USEDEFAULT, hwndDesktop, NULL, g_hInstDll, &wcd); if (NULL != m_hwndMain) { // // Register the main window as an OLE drop target. // RegisterAsDropTarget(TRUE); } else { hResult = E_FAIL; } #if DBG if (FAILED(hResult)) DBGERROR((TEXT("LISTVIEW - Failed creating main window."), hResult)); #endif return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateListView Description: Create the list view control. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed to create listview or load icons. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/21/97 Modified to use virtual listview (LVS_OWNERDATA) BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateListView( VOID ) { HRESULT hResult = NO_ERROR; RECT rc; DBGASSERT((NULL != m_hwndMain)); GetClientRect(m_hwndMain, &rc); m_hwndListView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA, 0, 0, rc.right - rc.left, rc.bottom - rc.top, m_hwndMain, (HMENU)NULL, g_hInstDll, NULL); if (NULL != m_hwndListView) { // // Store "this" ptr so subclass WndProc can access members. // SetWindowLongPtr(m_hwndListView, GWLP_USERDATA, (INT_PTR)this); // // We talk to the header control so save it's handle. // m_hwndHeader = ListView_GetHeader(m_hwndListView); // // Subclass the listview control so we can monitor mouse position. // This is used for listview tooltip management. // m_lpfnLVWndProc = (WNDPROC)GetWindowLongPtr(m_hwndListView, GWLP_WNDPROC); SetWindowLongPtr(m_hwndListView, GWLP_WNDPROC, (INT_PTR)LVSubClassWndProc); // // Enable listview for images in sub-item columns and full-row select. // ListView_SetExtendedListViewStyle(m_hwndListView, LVS_EX_SUBITEMIMAGES | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP); // // Add all columns to the listview. // Adjust for showing/hiding the Folder column. // INT iColId = 0; // Start with 1st col. for (INT iSubItem = 0; iSubItem < (m_lvsi.fShowFolder ? DetailsView::idCol_Last : DetailsView::idCol_Last - 1); iSubItem++) { AddColumn(iSubItem, g_rgColumns[iColId]); iColId++; // // Skip over the Folder column if it's hidden. // if (!m_lvsi.fShowFolder && DetailsView::idCol_Folder == iColId) iColId++; } // // Restore column widths to where the user left them last time // the details view was used. // if (m_lvsi.cb == sizeof(LV_STATE_INFO)) { for (UINT i = 0; i < DetailsView::idCol_Last; i++) { if (0 != m_lvsi.rgcxCol[i]) { ListView_SetColumnWidth(m_hwndListView, i, m_lvsi.rgcxCol[i]); } } } // // Restore the user's last column ordering. // DBGASSERT((Header_GetItemCount(m_hwndHeader) <= ARRAYSIZE(m_lvsi.rgColIndices))); Header_SetOrderArray(m_hwndHeader, Header_GetItemCount(m_hwndHeader), m_lvsi.rgColIndices); // // Check the "Show Folder" menu item to indicate the current visibility state // of the Folder column. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_SHOWFOLDER, MF_BYCOMMAND | (m_lvsi.fShowFolder ? MF_CHECKED : MF_UNCHECKED)); // // Set the sensitivity of the "by Folder" item arrangement menu option. // EnableMenuItem_ArrangeByFolder(m_lvsi.fShowFolder); // // Create and activate the listview tooltip window. // Even though the standard listview has a tooltip window, we need more // control that it provides. i.e.: We need to be able to enable/disable // the tooltip as well as notify the control when a new listview item // has been hit. Therefore, we need our own tooltip window. // if (SUCCEEDED(CreateListViewToolTip())) ActivateListViewToolTip(!m_lvsi.fShowFolder); else DBGERROR((TEXT("LISTVIEW, Failed creating tooltip window."))); // // Add WARNING and ERROR images to the listview's image list. // These are used for the "Status" column. // if (FAILED(hResult = AddImages())) DBGERROR((TEXT("LISTVIEW, Failed adding images to image list."))); } else hResult = E_FAIL; #if DBG if (FAILED(hResult)) DBGERROR((TEXT("LISTVIEW - Failed creating list view with result 0x%08X"), hResult)); #endif return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::RemoveColumn Description: Removes a specified column from the list view. Arguments: iColId - 0-based index of the column in the list view. i.e. idCol_Folder, idCol_Name etc. Returns: NO_ERROR - Success. E_FAIL - Column could not be removed. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/06/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::RemoveColumn( INT iColId ) { INT iSubItem = m_ColMap.IdToSubItem(iColId); if (-1 != iSubItem && ListView_DeleteColumn(m_hwndListView, iSubItem)) { m_ColMap.RemoveId(iSubItem); return NO_ERROR; } return E_FAIL; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::AddColumn Description: Adds a column to the list view. The caller specifies which 0-based position the column is to occupy and a reference to a column descriptor record containing information that defines the column. Arguments: iSubItem - 0-based index of the column in the list view. ColDesc - Reference to column descriptor record. Returns: NO_ERROR - Success. E_FAIL - One or more columns could not be inserted. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/06/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::AddColumn( INT iSubItem, const DV_COLDATA& ColDesc ) { LV_COLUMN col; HRESULT hResult = NO_ERROR; CString strColText(g_hInstDll, ColDesc.idMsgText); col.pszText = strColText; if (0 == ColDesc.cx) { // // No width specified in col desc record. Size column to the title. // HDC hdc = NULL; TEXTMETRIC tm; hdc = GetDC(m_hwndListView); GetTextMetrics(hdc, &tm); ReleaseDC(m_hwndListView, hdc); // // Nothing special about the +2. Without it, we get trailing ellipsis. // col.cx = tm.tmAveCharWidth * (lstrlen(col.pszText) + 2); } else col.cx = ColDesc.cx; // Use width from col descriptor. col.iSubItem = iSubItem; col.fmt = ColDesc.fmt; col.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; if (-1 != ListView_InsertColumn(m_hwndListView, iSubItem, &col)) m_ColMap.InsertId(iSubItem, ColDesc.iColId); else hResult = E_FAIL; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::AddImages Description: Adds icon images to the list view's image lists. These icons are used in the status column to indicate overrun of the quota threshold and limit. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - One or more icons could not be loaded. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/06/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::AddImages( VOID ) { HRESULT hResult = NO_ERROR; HIMAGELIST hSmallImages = NULL; // // Create the image lists for the listview. // hSmallImages = ImageList_Create(BITMAP_WIDTH, BITMAP_HEIGHT, ILC_MASK, 3, 0); // // Note: The order of these icon ID's in this array must match with the // iIMAGELIST_ICON_XXXXX macros defined at the top of this file. // The macro values represent the image indices in the image list. // struct IconDef { LPTSTR szName; HINSTANCE hInstDll; } rgIcons[] = { { MAKEINTRESOURCE(IDI_OKBUBBLE), g_hInstDll }, { IDI_WARNING, NULL }, { MAKEINTRESOURCE(IDI_WARNERR), g_hInstDll } }; for (UINT i = 0; i < ARRAYSIZE(rgIcons) && SUCCEEDED(hResult); i++) { HICON hIcon = LoadIcon(rgIcons[i].hInstDll, rgIcons[i].szName); if (NULL != hIcon) { ImageList_AddIcon(hSmallImages, hIcon); DestroyIcon(hIcon); } else { DBGERROR((TEXT("LISTVIEW - Error loading icon"))); hResult = E_FAIL; } } ImageList_SetBkColor(hSmallImages, CLR_NONE); // Transparent background. ListView_SetImageList(m_hwndListView, hSmallImages, LVSIL_SMALL); return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateListViewToolTip Description: Creates a tooltip window for displaying the user's folder name when the Folder column is hidden. The entire listview is defined as a single tool. We make the tooltip control think each listview item is a separate tool by intercepting WM_MOUSEMOVE, and performing a hit test to determine which listview item is hit. If the cursor has moved over a new item, the tooltip control is sent a WM_MOUSEMOVE(0,0). The next real WM_MOUSEMOVE that we relay to the tooltip makes it think that it is on a new tool. This is required so that tooltips popup and hide appropriately. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed to create tooltip window. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateListViewToolTip( VOID ) { HRESULT hResult = E_FAIL; m_hwndListViewToolTip = CreateWindowEx(0, TOOLTIPS_CLASS, (LPTSTR)NULL, TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hwndListView, (HMENU)NULL, g_hInstDll, NULL); if (NULL != m_hwndListViewToolTip) { TOOLINFO ti; // // Set tooltip timing parameter so that it pops up after // 1/2 second of no-mouse-movement. // SendMessage(m_hwndListViewToolTip, TTM_SETDELAYTIME, TTDT_INITIAL, (LPARAM)500); ti.cbSize = sizeof(TOOLINFO); ti.uFlags = TTF_IDISHWND; ti.hwnd = m_hwndListView; ti.hinst = g_hInstDll; ti.uId = (UINT_PTR)m_hwndListView; // Treat entire LV as a tool. ti.lpszText = LPSTR_TEXTCALLBACK; if (SendMessage(m_hwndListViewToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti)) { hResult = NO_ERROR; } } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateStatusBar Description: Creates the status bar. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed to create status bar. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateStatusBar( VOID ) { HRESULT hResult = NO_ERROR; DBGASSERT((NULL != m_hwndMain)); m_hwndStatusBar = CreateWindow(STATUSCLASSNAME, TEXT(""), WS_VISIBLE | WS_CHILD | WS_BORDER | SBS_SIZEGRIP, 0, 0, 0, 0, m_hwndMain, (HMENU)NULL, g_hInstDll, NULL); if (NULL != m_hwndStatusBar) { // // Show/hide status bar according to registry setting. // if (!m_lvsi.fStatusBar) ShowWindow(m_hwndStatusBar, SW_HIDE); // // Check the menu item to indicate the current status bar state. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_STATUSBAR, MF_BYCOMMAND | (m_lvsi.fStatusBar ? MF_CHECKED : MF_UNCHECKED)); } else { hResult = E_FAIL; DBGERROR((TEXT("LISTVIEW - Failed creating status bar."), hResult)); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateToolBar Description: Creates the tool bar. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed to create status bar. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/26/97 Changed to flat toolbar buttons. BrianAu 05/20/97 Added "Find User" button and combo box. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateToolBar( VOID ) { HRESULT hResult = NO_ERROR; // // Array describing each of the tool bar buttons. // TBBUTTON rgToolBarBtns[] = { { STD_FILENEW, IDM_QUOTA_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { STD_DELETE, IDM_QUOTA_DELETE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { STD_PROPERTIES, IDM_QUOTA_PROPERTIES, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { STD_UNDO, IDM_EDIT_UNDO, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { STD_FIND, IDM_EDIT_FIND, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0} }; DBGASSERT((NULL != m_hwndMain)); m_hwndToolBar = CreateToolbarEx(m_hwndMain, WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, IDC_TOOLBAR, 15, (HINSTANCE)HINST_COMMCTRL, IDB_STD_SMALL_COLOR, (LPCTBBUTTON)rgToolBarBtns, ARRAYSIZE(rgToolBarBtns), 0, 0, 100, 30, sizeof(TBBUTTON)); if (NULL != m_hwndToolBar) { // // FEATURE: I'm creating this combo without the WS_VISIBLE // attribute set. I originally coded this to have a // "find" dropdown combo in the toolbar similar to that // found in MS Dev Studio. Later we decided that this // was unnecessarily redundant with the "find" dialog // and it's dropdown combo. I'm leaving the code for // two reasons. // 1. I don't want to break the existing implementation. // 2. If we decide later to re-enable the feature it will // be easy to reactivate. // // [brianau - 1/20/98] // m_hwndToolbarCombo = CreateWindowEx(0, TEXT("COMBOBOX"), TEXT(""), WS_CHILD | WS_BORDER | CBS_HASSTRINGS | CBS_DROPDOWN | CBS_AUTOHSCROLL, 0, 0, CX_TOOLBAR_COMBO, CY_TOOLBAR_COMBO, m_hwndToolBar, (HMENU)IDC_TOOLBAR_COMBO, g_hInstDll, NULL); if (NULL != m_hwndToolbarCombo) { // // Set the font in the toolbar combo to be the same as that // used in listview. This assumes that the listview // has already been created. // DBGASSERT((NULL != m_hwndListView)); HFONT hfontMain = (HFONT)SendMessage(m_hwndListView, WM_GETFONT, 0, 0); SendMessage(m_hwndToolbarCombo, WM_SETFONT, (WPARAM)hfontMain, 0); // // Initialize the "user finder" object so that it knows // how to communicate with the toolbar combo box. // m_pUserFinder->ConnectToolbarCombo(m_hwndToolbarCombo); // // Retrieve the finder's MRU list contents from the registry and // load the toolbar's combo box. // The check for cMruEntries < MAX_FINDMRU_ENTRIES is to prevent // this loop from running wild if someone trashes the registry entry. // RegKey keyPref(HKEY_CURRENT_USER, REGSTR_KEY_DISKQUOTA); if (SUCCEEDED(keyPref.Open(KEY_READ))) { CArray rgstrMRU; if (SUCCEEDED(keyPref.GetValue(REGSTR_VAL_FINDMRU, &rgstrMRU))) { int n = rgstrMRU.Count(); for (int i = 0; i < n; i++) { SendMessage(m_hwndToolbarCombo, CB_ADDSTRING, 0, (LPARAM)(rgstrMRU[i].Cstr())); } } } } // // Show/hide tool bar according to registry setting. // if (!m_lvsi.fToolBar) ShowWindow(m_hwndToolBar, SW_HIDE); // // Check the menu item to indicate the current tool bar state. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_TOOLBAR, MF_BYCOMMAND | (m_lvsi.fToolBar ? MF_CHECKED : MF_UNCHECKED)); // // Initially, we have nothing in the undo list. // EnableMenuItem_Undo(FALSE); } else { hResult = E_FAIL; DBGERROR((TEXT("LISTVIEW - Failed creating tool bar."), hResult)); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::LoadObjects Description: Loads the user object list with quota record objects. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed enumerating users or adding objects to listview. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::LoadObjects( VOID ) { HRESULT hResult = NO_ERROR; DBGASSERT((NULL != m_pQuotaControl)); // // Use a user enumerator object for obtaining all of the quota users. // IEnumDiskQuotaUsers *pEnumUsers = NULL; hResult = m_pQuotaControl->CreateEnumUsers( NULL, // All entries. 0, // All entries. DISKQUOTA_USERNAME_RESOLVE_ASYNC, // Asynchronous operation. &pEnumUsers); if (SUCCEEDED(hResult)) { IDiskQuotaUser *pUser = NULL; hResult = S_OK; // // m_bStopLoadingObjects is sort of a hack so that we can interrupt // object loading if the user closes the view while loading is in progress. // // This is probably the most speed-critical loop in the disk quota UI. // The faster it is, the less time the user must wait for the listview // to be populated with user objects. // try { // // Go ahead and take a lock on the user list during the entire loading // process. This will let the list locking code in m_UserList.Append // proceded without having to obtain the lock each time. // m_UserList.Lock(); while(!m_bStopLoadingObjects) { DWORD cUsers = 1; hResult = pEnumUsers->Next(cUsers, &pUser, &cUsers); if (S_OK == hResult) { m_UserList.Append(pUser); } else { break; } pUser = NULL; } pEnumUsers->Release(); // Release the enumerator. pEnumUsers = NULL; m_UserList.ReleaseLock(); } catch(CAllocException& e) { // // Clean up before re-throwing exception. // Leave m_UserList in the pre-exception state. // if (NULL != pUser) pUser->Release(); if (NULL != pEnumUsers) pEnumUsers->Release(); m_UserList.ReleaseLock(); m_bStopLoadingObjects = FALSE; hResult = E_OUTOFMEMORY; } } if (S_FALSE == hResult) // Means no-more-users. hResult = NO_ERROR; #if DBG if (FAILED(hResult)) { DBGERROR((TEXT("LISTVIEW - Failed loading objects. Result = 0x%08X"), hResult)); } #endif m_bStopLoadingObjects = FALSE; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::ReleaseObjects Description: Releases all objects from the user object list (listview). Arguments: None. Returns: Always returns NO_ERROR. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/21/97 Ownerdata listview. Added m_UserList. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::ReleaseObjects( VOID ) { // // Destroy the user objects in the list. // PDISKQUOTA_USER pUser = NULL; m_UserList.Lock(); while(m_UserList.RemoveLast((LPVOID *)&pUser)) { if (NULL != pUser) pUser->Release(); } m_UserList.ReleaseLock(); return NO_ERROR; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::SortObjects Description: Sort objects in the list view using a given column as the key. Arguments: idColumn - Number of the column (0-based) to use as the key. dwDirection - 0 = Ascending sort, 1 = Descending sort. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/24/97 Added m_UserList. Ownerdata listview. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::SortObjects( DWORD idColumn, DWORD dwDirection ) { DBGASSERT((idColumn < DetailsView::idCol_Last)); CAutoWaitCursor waitcursor; COMPARESTRUCT cs; cs.idColumn = idColumn; cs.dwDirection = dwDirection; cs.pThis = this; m_UserList.Lock(); m_UserList.Sort(CompareItems, (LPARAM)&cs); InvalidateRect(m_hwndListView, NULL, TRUE); UpdateWindow(m_hwndListView); m_UserList.ReleaseLock(); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CompareItems [static] Description: Compares two items from the details view. Note that it's a static method so there's no "this" pointer. Arguments: lParam1 - Address of first user object. lParam2 - Address of second user object. lParam3 - Address of a COMPARESTRUCT structure. Returns: < 0 = User 1 is "less than" user 2. 0 = Users are "equivalent". > 0 = User 1 is "greater than" user 2. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 09/05/96 Added domain name string. BrianAu 05/19/97 Fixed overflow in difference calculations. BrianAu Changed type of "diff" from INT to __int64. 07/18/97 Use CompareString for name comparisons. BrianAu Need to be locale-sensitive. */ /////////////////////////////////////////////////////////////////////////////// INT DetailsView::CompareItems( LPVOID lParam1, LPVOID lParam2, LPARAM lParam3 ) { INT i[2]; __int64 diff = 0; PDISKQUOTA_USER pUser[2]; LONGLONG llValue[2]; PCOMPARESTRUCT pcs = (PCOMPARESTRUCT)lParam3; DBGASSERT((NULL != pcs)); DetailsView *pThis = pcs->pThis; pUser[0] = (PDISKQUOTA_USER)lParam1; pUser[1] = (PDISKQUOTA_USER)lParam2; i[0] = pcs->dwDirection; // Sort direction (0 = ascending, 1 = descending) i[1] = i[0] ^ 1; // Opposite of i[0]. DBGASSERT((NULL != pUser[0])); DBGASSERT((NULL != pUser[1])); switch(pcs->idColumn) { case DetailsView::idCol_Name: case DetailsView::idCol_LogonName: case DetailsView::idCol_Folder: { DWORD dwAccountStatus[2]; pUser[0]->GetAccountStatus(&dwAccountStatus[0]); pUser[1]->GetAccountStatus(&dwAccountStatus[1]); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus[0] && DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus[1]) { // // Both users have valid logon name strings. // INT iCompareResult; // For CompareString. TCHAR szContainer[2][MAX_DOMAIN]; TCHAR szName[2][MAX_USERNAME]; TCHAR szLogonName[2][MAX_USERNAME]; pUser[0]->GetName(szContainer[0], ARRAYSIZE(szContainer[0]), szLogonName[0], ARRAYSIZE(szLogonName[0]), szName[0], ARRAYSIZE(szName[0])); pUser[1]->GetName(szContainer[1], ARRAYSIZE(szContainer[1]), szLogonName[1], ARRAYSIZE(szLogonName[1]), szName[1], ARRAYSIZE(szName[1])); if (DetailsView::idCol_Folder == pcs->idColumn) { // // Sort by container + logon name. // Use CompareString so we're locale-sensitive. // iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szContainer[ i[0] ], -1, szContainer[ i[1] ], -1); if (CSTR_EQUAL == iCompareResult) { iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szLogonName[ i[0] ], -1, szLogonName[ i[1] ], -1); } } else if (DetailsView::idCol_Name == pcs->idColumn) { // // Sort by display name + container. // Use CompareString so we're locale-sensitive. // iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szName[ i[0] ], -1, szName[ i[1] ], -1); if (CSTR_EQUAL == iCompareResult) { iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szContainer[ i[0] ], -1, szContainer[ i[1] ], -1); } } else if (DetailsView::idCol_LogonName == pcs->idColumn) { // // Sort by logon name + container. // Use CompareString so we're locale-sensitive. // iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szLogonName[ i[0] ], -1, szLogonName[ i[1] ], -1); if (CSTR_EQUAL == iCompareResult) { iCompareResult = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szContainer[ i[0] ], -1, szContainer[ i[1] ], -1); } } // // Convert iCompareResult [1,2,3] to [-1,0,1]. // diff = iCompareResult - 2; } else { // // At least one of the users hasn't been or can't be resolved. // Compare by account status alone. Status values are such // that a resolved name will sort before an unresolved name. // Cast to (INT) is required for proper ordering. // diff = (INT)dwAccountStatus[ i[0] ] - (INT)dwAccountStatus[ i[1] ]; } break; } case DetailsView::idCol_Status: { // // The status image is based on the quota "state" of the user. // This expression effectively compares user records by status image. // diff = (pThis->GetUserQuotaState(pUser[ i[0] ]) - pThis->GetUserQuotaState(pUser[ i[1] ])); break; } case DetailsView::idCol_AmtUsed: { pUser[0]->GetQuotaUsed(&llValue[0]); pUser[1]->GetQuotaUsed(&llValue[1]); diff = llValue[ i[0] ] - llValue[ i[1] ]; break; } case DetailsView::idCol_Limit: { pUser[0]->GetQuotaLimit(&llValue[0]); pUser[1]->GetQuotaLimit(&llValue[1]); if (NOLIMIT == llValue[ i[0] ]) diff = 1; else if (NOLIMIT == llValue[ i[1] ]) diff = -1; else diff = llValue[ i[0] ] - llValue[ i[1] ]; break; } case DetailsView::idCol_Threshold: { pUser[0]->GetQuotaThreshold(&llValue[0]); pUser[1]->GetQuotaThreshold(&llValue[1]); if (NOLIMIT == llValue[ i[0] ]) diff = 1; else if (NOLIMIT == llValue[ i[1] ]) diff = -1; else diff = llValue[ i[0] ] - llValue[ i[1] ]; break; } case DetailsView::idCol_PctUsed: { DWORD dwPct[2]; CalcPctQuotaUsed(pUser[0], &dwPct[0]); CalcPctQuotaUsed(pUser[1], &dwPct[1]); diff = (INT)dwPct[ i[0] ] - (INT)dwPct[ i[1] ]; break; } default: break; } // // Translate return value to -1, 0 or 1. // INT iReturn = 0; if (0 != diff) { if (0 < diff) iReturn = 1; else iReturn = -1; } return iReturn; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::WndProc Description: Window procedure for the details view main window. This method merely dispatches the messages to other methods that do the actual work. These work methods should be declared "inline". Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK DetailsView::WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // // Retrieve the DetailsView object's "this" pointer from the window's // USERDATA. // DetailsView *pThis = (DetailsView *)GetWindowLongPtr(hWnd, GWLP_USERDATA); try { switch(message) { case WM_CREATE: { CREATESTRUCT *pcs = (CREATESTRUCT *)lParam; PWNDCREATE_DATA pCreateData = (PWNDCREATE_DATA)(pcs->lpCreateParams); DBGASSERT((NULL != pCreateData)); pThis = (DetailsView *)(pCreateData->pThis); DBGASSERT((NULL != pThis)); SetWindowLongPtr(hWnd, GWLP_USERDATA, (INT_PTR)pThis); InitCommonControls(); return 0; } case WM_COMMAND: DBGASSERT((NULL != pThis)); pThis->OnCommand(hWnd, message, wParam, lParam); // // Enable the "Undo" menu if the undo list is not empty. // pThis->EnableMenuItem_Undo(0 != pThis->m_pUndoList->Count()); return 0; case WM_CONTEXTMENU: DBGASSERT((NULL != pThis)); pThis->OnContextMenu(hWnd, message, wParam, lParam); return 0; case WM_CLOSE: case WM_ENDSESSION: DestroyWindow(hWnd); return 0; case WM_DESTROY: DBGASSERT((NULL != pThis)); pThis->OnDestroy(hWnd, message, wParam, lParam); return 0; case WM_ADD_USER_TO_DETAILS_VIEW: // This is DSKQUOTA-specific. DBGASSERT((NULL != pThis)); pThis->AddUser((PDISKQUOTA_USER)lParam); return 0; case WM_MAINWINDOW_CREATED: // This is DSKQUOTA-specific. DBGASSERT((NULL != pThis)); pThis->OnMainWindowCreated(hWnd, message, wParam, lParam); return 0; case WM_MENUSELECT: DBGASSERT((NULL != pThis)); pThis->OnMenuSelect(hWnd, message, wParam, lParam); return 0; case WM_NOTIFY: DBGASSERT((NULL != pThis)); pThis->OnNotify(hWnd, message, wParam, lParam); return 0; case WM_SETFOCUS: DBGASSERT((NULL != pThis)); pThis->OnSetFocus(hWnd, message, wParam, lParam); return 0; case WM_SIZE: DBGASSERT((NULL != pThis)); pThis->OnSize(hWnd, message, wParam, lParam); return 0; case WM_SYSCOLORCHANGE: case WM_SETTINGCHANGE: DBGASSERT((NULL != pThis)); pThis->OnSettingChange(hWnd, message, wParam, lParam); return 0; default: break; } } catch(CAllocException& e) { // // Handle out-of-memory errors here. Any other exceptions // can be thrown to caller. Let ThreadProc handle them. // DiskQuotaMsgBox(GetDesktopWindow(), IDS_OUTOFMEMORY, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } return DefWindowProc(hWnd, message, wParam, lParam); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::LVSubClassWndProc Description: Window proc for the sub-classed listview control. This is required so that we can intecept mouse messages and respond to the request for tooltip text. Arguments: Std windows WndProc args. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK DetailsView::LVSubClassWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { DetailsView *pThis = (DetailsView *)GetWindowLongPtr(hWnd, GWLP_USERDATA); switch(message) { case WM_NOTIFY: { // // Only return ToolTip text if TTN_NEEDTEXT is being // sent from our tooltip. Don't respond to the listview's // internal tooltip's request for text. // LV_DISPINFO *pDispInfo = (LV_DISPINFO *)lParam; if (pDispInfo->hdr.hwndFrom == pThis->m_hwndListViewToolTip) { switch(pDispInfo->hdr.code) { case TTN_NEEDTEXT: // // FEATURE: With the removal of the "domain" term from the UI, I // decided we don't need this tooltip any more. // However, I'm making this change in the last hour before // "code complete" and I don't want to break something else. // Therefore I'm just commenting this out and leaving the // subclassing in place. If there's time later, this subclassing // of the listview should be removed. [brianau - 03/19/98] // // pThis->LV_OnTTN_NeedText((TOOLTIPTEXT *)lParam); return 0; default: break; } } } break; case WM_MOUSEMOVE: DBGASSERT((NULL != pThis)); pThis->LV_OnMouseMessages(hWnd, message, wParam, lParam); break; case WM_ADD_USER_TO_DETAILS_VIEW: // This is DSKQUOTA-specific. DBGASSERT((NULL != pThis)); pThis->AddUser((PDISKQUOTA_USER)lParam); break; default: break; } DBGASSERT((NULL != pThis->m_lpfnLVWndProc)); return CallWindowProc(pThis->m_lpfnLVWndProc, hWnd, message, wParam, lParam); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCommand Description: Handler for WM_COMMAND. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 09/06/96 Added "Show Domain" menu option. BrianAu 05/20/97 Added IDM_EDIT_FIND and IDM_EDIT_FIND_LIST. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCommand( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch(LOWORD(wParam)) { case IDM_EDIT_INVERTSELECTION: InvertSelectedItems(); break; case IDM_EDIT_UNDO: OnCmdUndo(); break; case IDM_EDIT_FIND: OnCmdFind(); break; case IDM_EDIT_FIND_LIST: SetFocus(m_hwndToolbarCombo); break; case IDM_EDIT_SELECTALL: SelectAllItems(); break; case IDM_EDIT_COPY: OnCmdEditCopy(); break; case IDM_HELP_ABOUT: OnHelpAbout(hWnd); break; case IDM_HELP_TOPICS: OnHelpTopics(hWnd); break; case IDM_QUOTA_CLOSE: DestroyWindow(m_hwndMain); break; case IDM_QUOTA_DELETE: OnCmdDelete(); FocusOnSomething(); // Needed if DEL key pressed. break; case IDM_QUOTA_NEW: OnCmdNew(); break; case IDM_QUOTA_PROPERTIES: OnCmdProperties(); break; case IDM_QUOTA_IMPORT: OnCmdImport(); break; case IDM_QUOTA_EXPORT: OnCmdExport(); break; case IDM_VIEW_ARRANGE_BYFOLDER: SortObjects(DetailsView::idCol_Folder, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYLIMIT: SortObjects(DetailsView::idCol_Limit, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYNAME: SortObjects(DetailsView::idCol_Name, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYLOGONNAME: SortObjects(DetailsView::idCol_LogonName, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYPERCENT: SortObjects(DetailsView::idCol_PctUsed, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYTHRESHOLD: SortObjects(DetailsView::idCol_Threshold, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYSTATUS: SortObjects(DetailsView::idCol_Status, m_fSortDirection); break; case IDM_VIEW_ARRANGE_BYUSED: SortObjects(DetailsView::idCol_AmtUsed, m_fSortDirection); break; case IDM_VIEW_REFRESH: Refresh(true); break; case IDM_VIEW_STATUSBAR: OnCmdViewStatusBar(); break; case IDM_VIEW_TOOLBAR: OnCmdViewToolBar(); break; case IDM_VIEW_SHOWFOLDER: OnCmdViewShowFolder(); break; // // These are just for development. // // case IDM_CLEAR_CACHE: // m_pQuotaControl->InvalidateSidNameCache(); // break; default: break; } return 0; } LRESULT DetailsView::OnSettingChange( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { HWND rghwnd[] = { m_hwndListView, m_hwndStatusBar, m_hwndToolBar, m_hwndToolbarCombo, m_hwndListViewToolTip, m_hwndHeader }; for (int i = 0; i < ARRAYSIZE(rghwnd); i++) { SendMessage(rghwnd[i], uMsg, wParam, lParam); } return 0; } // // Is an x,y screen position in the LV header control? // BOOL DetailsView::HitTestHeader( int xPos, int yPos ) { RECT rcHdr; POINT pt = { xPos, yPos }; GetWindowRect(m_hwndHeader, &rcHdr); return PtInRect(&rcHdr, pt); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnContextMenu Description: Handler for WM_CONTEXTMENU. Creates and tracks a popup context menu for deleting selected object(s) and showing their properties. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnContextMenu( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { // // Only display menu if the message is from the list view and there's // one or more objects selected in the list view. // if ((HWND)wParam == m_hwndListView && !HitTestHeader(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)) && ListView_GetSelectedCount(m_hwndListView) > 0) { HMENU hMenu = LoadMenu(g_hInstDll, MAKEINTRESOURCE(IDM_CONTEXT_MENU)); if (NULL != hMenu) { HMENU hMenuTrackPopup = GetSubMenu(hMenu, 0); SetMenuDefaultItem(hMenuTrackPopup, IDM_QUOTA_PROPERTIES, MF_BYCOMMAND); if (LPARAM(-1) == lParam) { // // Invoked from keyboard. Place menu at focused item. // POINT pt = { -1, -1 }; int i = ListView_GetNextItem(m_hwndListView, -1, LVNI_FOCUSED); if (i != -1) { ListView_GetItemPosition(m_hwndListView, i, &pt); ClientToScreen(m_hwndListView, &pt); } lParam = MAKELPARAM(pt.x, pt.y); } if (LPARAM(-1) != lParam) { TrackPopupMenu(hMenuTrackPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, hWnd, NULL); } DestroyMenu(hMenu); } } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnDestroy Description: Handler for WM_DESTROY. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnDestroy( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { // // We don't want to destroy the view window while a user-name-changed // update is in progress. Likewise, we don't want to perform a name // update if the view is being (or has been) destroyed. The crit sec // m_csAsyncUpdate and the flag m_bDestroyingView work together to // ensure this. // EnterCriticalSection(&m_csAsyncUpdate); m_bDestroyingView = TRUE; // Destruction of DetailsView in progress. m_bStopLoadingObjects = TRUE; // Will terminate in-progress loading. // // Unregister the main window as an OLE drop target. // if (NULL != hWnd) { RegisterAsDropTarget(FALSE); } // // Cancel subclassing of the listview control. // if (NULL != m_lpfnLVWndProc) SetWindowLongPtr(m_hwndListView, GWLP_WNDPROC, (INT_PTR)m_lpfnLVWndProc); DisconnectEventSink(); if (NULL != m_pQuotaControl) { m_pQuotaControl->Release(); m_pQuotaControl = NULL; } // // Save the view dimensions and column widths to the registry. // We want the user to be able to configure the view and leave it. // SaveViewStateToRegistry(); // // If we have a data object on the clipboard, clear the clipboard. // Note that the clipboard holds the reference to the data object. // When we clear the clipboard, the data object will be released. // if (NULL != m_pIDataObjectOnClipboard && S_OK == OleIsCurrentClipboard(m_pIDataObjectOnClipboard)) { OleFlushClipboard(); } // // All done now. Post a WM_QUIT message to the thread to tell // it to exit. On termination, the thread proc will release // the view object, calling the destructor. // PostMessage(hWnd, WM_QUIT, 0, 0); LeaveCriticalSection(&m_csAsyncUpdate); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::RegisterAsDropTarget Description: Registers or De-Registers the details view window as an OLE drop target. Arguments: bActive - If TRUE, registers as a drop target. If FALSE, un-registers as a drop target. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/28/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::RegisterAsDropTarget( BOOL bActive ) { if (bActive) { // // Register as a drop target. // CoLockObjectExternal(static_cast(this), TRUE, FALSE); RegisterDragDrop(m_hwndMain, static_cast(this)); } else { // // Un-register as a drop target. // RevokeDragDrop(m_hwndMain); CoLockObjectExternal(static_cast(this), FALSE, TRUE); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::SaveViewStateToRegistry Description: Saves the listview height/width and the column widths to the registry. When the listview is created, these values are used to size it so that the user doesn't always have to re-configure the view every time they open it. Also saves the visibility state of the toolbar, statusbar and folder column. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/25/96 Initial creation. BrianAu 05/20/97 Added FindMRU list to persistent reg data. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::SaveViewStateToRegistry( VOID ) { RECT rc; HDC hdc = GetDC(m_hwndMain); RegKey keyPref(HKEY_CURRENT_USER, REGSTR_KEY_DISKQUOTA); if (FAILED(keyPref.Open(KEY_WRITE, true))) { DBGERROR((TEXT("Error opening reg key \"%s\""), REGSTR_KEY_DISKQUOTA)); return; } m_lvsi.cb = sizeof(LV_STATE_INFO); // // Save current screen resolution. // m_lvsi.cxScreen = (WORD)GetDeviceCaps(hdc, HORZRES); m_lvsi.cyScreen = (WORD)GetDeviceCaps(hdc, VERTRES); ReleaseDC(m_hwndMain, hdc); // // Save current listview window size. // GetWindowRect(m_hwndMain, &rc); m_lvsi.cx = rc.right - rc.left; m_lvsi.cy = rc.bottom - rc.top; // // Save listview column widths. // UINT cColumns = Header_GetItemCount(m_hwndHeader); for (UINT i = 0; i < cColumns; i++) { m_lvsi.rgcxCol[i] = ListView_GetColumnWidth(m_hwndListView, i); } // // Save the current order of the columns in the listview. // DBGASSERT(cColumns <= ARRAYSIZE(m_lvsi.rgColIndices)); Header_GetOrderArray(m_hwndHeader, cColumns, m_lvsi.rgColIndices); // // Save column sorting state. // Casts are because we use a WORD bit field in the LVSI structure. // m_lvsi.iLastColSorted = (WORD)(m_iLastColSorted & 0xF); // Uses only lower 4 bits. m_lvsi.fSortDirection = (WORD)m_fSortDirection; // // Write preference data to registry. // keyPref.SetValue(REGSTR_VAL_PREFERENCES, (LPBYTE)&m_lvsi, m_lvsi.cb); // // Save the contents of the Find MRU list. // UINT cNames = (UINT)SendMessage(m_hwndToolbarCombo, CB_GETCOUNT, 0, 0); if (CB_ERR != cNames && 0 < cNames) { CArray rgstrNames(cNames); for (i = 0; i < cNames; i++) { INT cchName = (INT)SendMessage(m_hwndToolbarCombo, CB_GETLBTEXTLEN, i, 0); if (CB_ERR != cchName && 0 < cchName) { CString s; cchName = (INT)SendMessage(m_hwndToolbarCombo, CB_GETLBTEXT, i, (LPARAM)s.GetBuffer(cchName + 1)); s.ReleaseBuffer(); if (CB_ERR != cchName) { rgstrNames[i] = s; } } } keyPref.SetValue(REGSTR_VAL_FINDMRU, rgstrNames); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnMainWindowCreated Description: Handles WM_MAIN_WINDOW_CREATED. This message is posted by ThreadProc after main window creation is complete. It does all the stuff to get the window up and running. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnMainWindowCreated( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { DBGASSERT((NULL != m_hwndMain)); // // The accelerator table is automatically freed by Windows // when the app terminates. // m_hKbdAccel = LoadAccelerators(g_hInstDll, MAKEINTRESOURCE(IDR_KBDACCEL)); CreateListView(); CreateStatusBar(); CreateToolBar(); ConnectEventSink(); ShowWindow(m_hwndMain, SW_SHOWNORMAL); UpdateWindow(m_hwndMain); // // Create the UNDO object. // m_pUndoList = new UndoList(&m_UserList, m_hwndListView); ShowItemCountInStatusBar(); Refresh(); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnMenuSelect Description: Handles WM_MENUSELECT. If a menu item is currently selected AND the status bar is visible, the menu item's description is displayed in the status bar. When the menu is closed, the status bar reverts back to an item count. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnMenuSelect( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { if (0xFFFF == HIWORD(wParam) && NULL == (HMENU)lParam) { // // Menu closed. // m_bMenuActive = FALSE; ShowItemCountInStatusBar(); } else { // // Item selected. // m_bMenuActive = TRUE; ShowMenuTextInStatusBar(LOWORD(wParam)); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnNotify Description: Handles all LVN_XXXXXX list view control notifications. Dispatches specific notifications to other handlers. Arguments: Standard WndProc arguments. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnNotify( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { NMHDR *pnmhdr = (NMHDR *)lParam; switch(pnmhdr->code) { case NM_DBLCLK: case NM_RETURN: OnCmdProperties( ); // // Enable/disable Undo menu item. // EnableMenuItem_Undo(0 != m_pUndoList->Count()); break; case NM_SETFOCUS: FocusOnSomething(); // Something should always be highlighted. break; case LVN_ODFINDITEM: OnLVN_OwnerDataFindItem((NMLVFINDITEM *)lParam); break; case LVN_GETDISPINFO: OnLVN_GetDispInfo((LV_DISPINFO *)lParam); break; case LVN_BEGINDRAG: OnLVN_BeginDrag((NM_LISTVIEW *)lParam); break; case LVN_COLUMNCLICK: OnLVN_ColumnClick((NM_LISTVIEW *)lParam); break; case LVN_ITEMCHANGED: OnLVN_ItemChanged((NM_LISTVIEW *)lParam); break; case TTN_NEEDTEXT: OnTTN_NeedText((TOOLTIPTEXT *)lParam); // // Fall through. // default: break; } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::LV_OnMouseMessages Description: Handles mouse messages for the subclassed listview control. These must be intercepted so that we can... a) Tell the tooltip when we've hit another listview item. and b) Forward all mouse messages to the tooltip window. Arguments: Standard WndProc args. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::LV_OnMouseMessages( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch(message) { case WM_MOUSEMOVE: { // // If we've moved the mouse to another listview item, // make the tooltip window think we're over another tool. // The tooltip window thinks the entire listview is a single // tool but we want to treat each item as a separate tool. // Note that m_ptMouse.x and .y are recorded when the main // window receives WM_MOUSEMOVE. // LV_HITTESTINFO hti; INT iItem = 0; hti.pt.x = m_ptMouse.x = GET_X_LPARAM(lParam); hti.pt.y = m_ptMouse.y = GET_Y_LPARAM(lParam); if (-1 != (iItem = ListView_HitTest(m_hwndListView, &hti))) { if (iItem != m_iLastItemHit) { SendMessage(m_hwndListViewToolTip, WM_MOUSEMOVE, 0, 0); m_iLastItemHit = iItem; } } else { ShowWindow(m_hwndListViewToolTip, SW_HIDE); m_iLastItemHit = iItem; } } // // Fall through. // case WM_LBUTTONUP: case WM_LBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDOWN: { // // Relay all mouse messages to the listview's tooltip control. // MSG msg; msg.hwnd = hWnd; msg.message = message; msg.wParam = wParam; msg.lParam = lParam; SendMessage(m_hwndListViewToolTip, TTM_RELAYEVENT, 0, (LPARAM)&msg); } break; default: break; } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdEditCopy Description: Handles WM_COMMAND, IDM_EDIT_COPY. This is invoked whenever a user selects the Copy menu item or presses Ctrl + C. The method creates a DataObject (same used in drag-drop) and places it on the OLE clipboard. The data is rendered when OLE asks for it via IDataObject::GetData. Arguments: None. Returns: Always 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdEditCopy( VOID ) { HRESULT hResult = NO_ERROR; hResult = QueryInterface(IID_IDataObject, (LPVOID *)&m_pIDataObjectOnClipboard); if (SUCCEEDED(hResult)) { OleSetClipboard(m_pIDataObjectOnClipboard); // // OLE calls AddRef() so we can release the count added in QI. // m_pIDataObjectOnClipboard->Release(); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_BeginDrag Description: Called when the user has selected one or more items in the listview and begins a drag operation. Creates a DropSource object and a DataObject then calls DoDragDrop() to execute the drag-drop operation. Note that we don't store the IDataObject pointer in m_IDataObjectOnClipboard. That member is only for clipboard copy operations, not drag/drop. Arguments: pNm - Address of notification message structure. Returns: TRUE = Succeeded. FALSE = Failed. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_BeginDrag( NM_LISTVIEW *pNm ) { DBGTRACE((DM_DRAGDROP, DL_HIGH, TEXT("DetailsView::OnLVN_BeginDragDrop"))); HRESULT hResult = NO_ERROR; IDataObject *pIDataObject = NULL; IDropSource *pIDropSource = NULL; DBGPRINT((DM_DRAGDROP, DL_HIGH, TEXT("DRAGDROP - Beginning Drag/Drop"))); try { hResult = QueryInterface(IID_IDataObject, (LPVOID *)&pIDataObject); if (SUCCEEDED(hResult)) { hResult = QueryInterface(IID_IDropSource, (LPVOID *)&pIDropSource); if (SUCCEEDED(hResult)) { DWORD dwEffect = 0; // // Unregister our window as a drop target while we're acting as a drop // source. Don't want to drop our own data onto our own window. // RegisterAsDropTarget(FALSE); hResult = DoDragDrop(pIDataObject, pIDropSource, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect); // // FEATURE: Should probably display some error UI here. // The shell doesn't indicate any error if the // destination volume is full or if there's a write // error. The only indication of a failure is that // dwEffect will contain 0. We could display something // like "An error occured while transferring the selected // items." The big problem is that only the shell knows // where the data was stored so only it could delete // the created file. Displaying a message but leaving // the file is also confusing. [brianau 7/29/97] // NT Bug 96282 will fix the shell not deleting the file. // RegisterAsDropTarget(TRUE); DBGPRINT((TEXT("DRAGDROP - Drag/Drop complete.\n\t hResult = 0x%08X Effect = 0x%08X"), hResult, dwEffect)); pIDropSource->Release(); pIDropSource = NULL; } pIDataObject->Release(); pIDataObject = NULL; } } catch(CAllocException& e) { if (NULL != pIDropSource) pIDropSource->Release(); if (NULL != pIDataObject) pIDataObject->Release(); RegisterAsDropTarget(TRUE); throw; } return SUCCEEDED(hResult); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnTTN_NeedText Description: Handles requests for tooltip text for the main window's tool bar buttons. Arguments: pToolTipText - Address of tooltip text notification information. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu 07/09/97 Added cmd/TT cross reference. BrianAu Previously used the tool status text in tooltip. */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnTTN_NeedText( TOOLTIPTEXT *pToolTipText ) { // // Cross-reference tool command IDs with tooltip text IDs. // const struct { UINT idCmd; // Tool cmd ID. UINT idTT; // Tooltip text ID. } CmdTTXRef[] = { { IDM_QUOTA_NEW, IDS_TT_QUOTA_NEW }, { IDM_QUOTA_DELETE, IDS_TT_QUOTA_DELETE }, { IDM_QUOTA_PROPERTIES, IDS_TT_QUOTA_PROPERTIES }, { IDM_EDIT_UNDO, IDS_TT_EDIT_UNDO }, { IDM_EDIT_FIND, IDS_TT_EDIT_FIND } }; INT idTT = -1; for (INT i = 0; i < ARRAYSIZE(CmdTTXRef) && -1 == idTT; i++) { if (CmdTTXRef[i].idCmd == pToolTipText->hdr.idFrom) idTT = CmdTTXRef[i].idTT; } if (-1 != idTT) { m_strDispText.Format(g_hInstDll, idTT); pToolTipText->lpszText = (LPTSTR)m_strDispText; } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::LV_OnTTN_NeedText Description: Handles requests for tooltip text for the listview tooltip window. This is where we get the foldername DOMAIN/USERNAME text for when the "domain name" column is hidden. Arguments: pToolTipText - Address of tooltip text notification information. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu 10/11/96 Added support for draggable columns. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::LV_OnTTN_NeedText( TOOLTIPTEXT *pToolTipText ) { // // Only provide text when the mouse is over the "User Name" column. // if (-1 != m_iLastItemHit) { INT cxMin = 0; INT cxMax = 0; INT cHdrs = Header_GetItemCount(m_hwndHeader); for (INT i = 0; i < cHdrs; i++) { // // Find the left and right X coordinate for the "Name" column. // INT iCol = Header_OrderToIndex(m_hwndHeader, i); INT cxCol = ListView_GetColumnWidth(m_hwndListView, iCol); if (DetailsView::idCol_Name == m_ColMap.SubItemToId(iCol)) { cxMax = cxMin + cxCol; break; } else { cxMin += cxCol; } } // // cxMin now contains left edge of Name column. // cxMax now contains right edge of Name column. // if (m_ptMouse.x >= cxMin && m_ptMouse.x <= cxMax) { PDISKQUOTA_USER pUser = NULL; if (m_UserList.Retrieve((LPVOID *)&pUser, m_iLastItemHit)) { TCHAR szContainer[MAX_DOMAIN] = { TEXT('\0') }; TCHAR szLogonName[MAX_USERNAME] = { TEXT('\0') }; TCHAR szDisplayName[MAX_FULL_USERNAME] = { TEXT('\0') }; pUser->GetName(szContainer, ARRAYSIZE(szContainer), szLogonName, ARRAYSIZE(szLogonName), szDisplayName, ARRAYSIZE(szDisplayName)); if (TEXT('\0') != szContainer[0] && TEXT('\0') != szLogonName[0]) { if (TEXT('\0') != szDisplayName[0]) m_strDispText.Format(g_hInstDll, IDS_FMT_DISPLAY_LOGON_CONTAINER, szDisplayName, szLogonName, szContainer); else m_strDispText.Format(g_hInstDll, IDS_FMT_LOGON_CONTAINER, szLogonName, szContainer); pToolTipText->lpszText = (LPTSTR)m_strDispText; } else { pToolTipText->lpszText = NULL; pToolTipText->szText[0] = TEXT('\0'); } } } } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_OwnerDataFindItem Description: Handles LVN_ODFINDITEM for the listview control. Arguments: pFindInfo - Address of NMLVFINDITEM structure associated with the notification. Returns: 0-based index of found item. -1 if not found. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 02/21/97 Initial creation. Ownerdraw listview. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_OwnerDataFindItem( NMLVFINDITEM *pFindInfo ) { INT iItem = -1; switch(pFindInfo->lvfi.flags) { case LVFI_PARAM: { LPVOID pvUser = NULL; m_UserList.Lock(); INT cUsers = m_UserList.Count(); for (INT i = 0; i < cUsers; i++) { if (m_UserList.Retrieve(&pvUser, i) && pvUser == (LPVOID)pFindInfo->lvfi.lParam) { iItem = i; break; } } m_UserList.ReleaseLock(); break; } default: // // This app only uses lParam for locating items. // break; } return iItem; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_GetDispInfo Description: Handles LVN_GETDISPINFO for the listview control. Arguments: pDispInfo - Address of LV_DISPINFO structure associated with the notification. Returns: Always 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 02/21/97 Initial creation. Ownerdraw listview. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_GetDispInfo( LV_DISPINFO * pDispInfo ) { PDISKQUOTA_USER pUser = NULL; m_UserList.Retrieve((LPVOID *)&pUser, pDispInfo->item.iItem); if (NULL != pUser) { if (LVIF_TEXT & pDispInfo->item.mask) OnLVN_GetDispInfo_Text(pDispInfo, pUser); if ((LVIF_IMAGE & pDispInfo->item.mask) && (m_ColMap.SubItemToId(pDispInfo->item.iSubItem) == DetailsView::idCol_Status)) { OnLVN_GetDispInfo_Image(pDispInfo, pUser); } } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_GetDispInfo_Text Description: Handles LVN_GETDISPINFO - LVIF_TEXT for the listview control. Arguments: pDispInfo - Address of LV_DISPINFO structure associated with the notification. pUser - Address of user object for listview item. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 09/22/96 Added user "full name" support. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_GetDispInfo_Text( LV_DISPINFO *pDispInfo, PDISKQUOTA_USER pUser ) { HRESULT hResult = NO_ERROR; LONGLONG llValue; NUMBERFMT NumFmt; DBGASSERT((NULL != pDispInfo)); DBGASSERT((NULL != pUser)); NumFmt.NumDigits = 0; m_strDispText.Empty(); switch(m_ColMap.SubItemToId(pDispInfo->item.iSubItem)) { case DetailsView::idCol_Status: DBGASSERT((NULL != pUser)); switch(GetUserQuotaState(pUser)) { case iUSERSTATE_OK: m_strDispText = m_strStatusOK; break; case iUSERSTATE_WARNING: m_strDispText = m_strStatusWarning; break; default: DBGASSERT((0)); // // Fall through. // case iUSERSTATE_OVERLIMIT: m_strDispText = m_strStatusOverlimit; break; } break; case DetailsView::idCol_Folder: { DWORD dwAccountStatus = 0; DBGASSERT((NULL != pUser)); pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus) { pUser->GetName(m_strDispText.GetBuffer(MAX_PATH), MAX_PATH, NULL, 0, NULL, 0); } else { // // Non-normal account status. Leave domain column // blank. Account name column will contain status information. // } break; } case DetailsView::idCol_Name: { DWORD dwAccountStatus = 0; DBGASSERT((NULL != pUser)); pUser->GetAccountStatus(&dwAccountStatus); switch(dwAccountStatus) { case DISKQUOTA_USER_ACCOUNT_RESOLVED: pUser->GetName(NULL, 0, NULL, 0, m_strDispText.GetBuffer(MAX_USERNAME), MAX_USERNAME); m_strDispText.ReleaseBuffer(); break; case DISKQUOTA_USER_ACCOUNT_UNRESOLVED: m_strDispText = m_strAccountUnresolved; break; case DISKQUOTA_USER_ACCOUNT_UNKNOWN: m_strDispText = m_strAccountUnknown; break; case DISKQUOTA_USER_ACCOUNT_INVALID: m_strDispText = m_strAccountInvalid; break; case DISKQUOTA_USER_ACCOUNT_DELETED: m_strDispText = m_strAccountDeleted; break; case DISKQUOTA_USER_ACCOUNT_UNAVAILABLE: m_strDispText = m_strAccountUnavailable; break; } break; } case DetailsView::idCol_LogonName: { DBGASSERT((NULL != pUser)); DWORD dwAccountStatus = 0; pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus) { // // If the account SID has been resolved to a name, // display the name. // pUser->GetName(NULL, 0, m_strDispText.GetBuffer(MAX_USERNAME), MAX_USERNAME, NULL, 0); m_strDispText.ReleaseBuffer(); } else { // // If the account SID has NOT been resolved to a name, display // the SID as a string. // BYTE Sid[MAX_SID_LEN]; DWORD cchSidStr = MAX_PATH; if (SUCCEEDED(pUser->GetSid(Sid, ARRAYSIZE(Sid)))) { SidToString(Sid, m_strDispText.GetBuffer(cchSidStr), &cchSidStr); m_strDispText.ReleaseBuffer(); } } break; } case DetailsView::idCol_AmtUsed: pUser->GetQuotaUsed(&llValue); XBytes::FormatByteCountForDisplay(llValue, m_strDispText.GetBuffer(40), 40); break; case DetailsView::idCol_Limit: pUser->GetQuotaLimit(&llValue); if (NOLIMIT == llValue) m_strDispText = m_strNoLimit; else XBytes::FormatByteCountForDisplay(llValue, m_strDispText.GetBuffer(40), 40); break; case DetailsView::idCol_Threshold: pUser->GetQuotaThreshold(&llValue); if (NOLIMIT == llValue) m_strDispText = m_strNoLimit; else XBytes::FormatByteCountForDisplay(llValue, m_strDispText.GetBuffer(40), 40); break; case DetailsView::idCol_PctUsed: { DWORD dwPct = 0; hResult = CalcPctQuotaUsed(pUser, &dwPct); if (SUCCEEDED(hResult)) m_strDispText.Format(TEXT("%1!d!"), dwPct); else m_strDispText = m_strNotApplicable; // Not a number. break; } default: break; } pDispInfo->item.pszText = (LPTSTR)m_strDispText; // Used by all text callbacks. return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_GetDispInfo_Image Description: Handles LVN_GETDISPINFO - LVIF_IMAGE for the listview control. Arguments: pDispInfo - Address of LV_DISPINFO structure associated with the notification. pUser - Address of user object for listview item. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 09/12/96 Added CheckMark icon. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_GetDispInfo_Image( LV_DISPINFO *pDispInfo, PDISKQUOTA_USER pUser ) { switch(GetUserQuotaState(pUser)) { case iUSERSTATE_OK: pDispInfo->item.iImage = iIMAGELIST_ICON_OK; break; case iUSERSTATE_WARNING: pDispInfo->item.iImage = iIMAGELIST_ICON_WARNING; break; default: DBGASSERT((0)); // // Fall through. // case iUSERSTATE_OVERLIMIT: pDispInfo->item.iImage = iIMAGELIST_ICON_LIMIT; break; } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetUserQuotaState Description: Determines which of 3 states the user's quota values place the user in. This is mainly used to determine what icon to display in the "Status" column. It is also used to determine what text to display in the "Status" column in a drag-drop report. Arguments: pUser - Address of user object for listview item. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/10/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT DetailsView::GetUserQuotaState( PDISKQUOTA_USER pUser ) { LONGLONG llUsed; LONGLONG llLimit; INT iState = iUSERSTATE_OK; DBGASSERT((NULL != pUser)); pUser->GetQuotaUsed(&llUsed); pUser->GetQuotaLimit(&llLimit); if (NOLIMIT != llLimit && llUsed > llLimit) { iState = iUSERSTATE_OVERLIMIT; } else { LONGLONG llThreshold; pUser->GetQuotaThreshold(&llThreshold); if (NOLIMIT != llThreshold && llUsed > llThreshold) iState = iUSERSTATE_WARNING; } return iState; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_ColumnClick Description: Handles LVN_COLUMNCLICK list view notifications. This is received when the user selects a column's label. Arguments: pNm - Address of listview notification message structure. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_ColumnClick( NM_LISTVIEW *pNm ) { INT idCol = m_ColMap.SubItemToId(pNm->iSubItem); if (idCol != m_iLastColSorted) { // // New column selected. Reset to ascending sort order. // m_fSortDirection = 0; } else { // // Column selected more than once. Toggle sort order. // m_fSortDirection ^= 1; } SortObjects(idCol, m_fSortDirection); // // Remember what column was selected. // m_iLastColSorted = idCol; return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnLVN_ItemChanged Description: Handles LVN_ITEMCHANGED listview notifications. Updates the selected-item-count in the status bar. Arguments: pNm - Address of listview notification structure. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 05/18/97 Added promotion of user object in name resolution BrianAu queue. */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnLVN_ItemChanged( NM_LISTVIEW *pNm ) { if (LVIS_FOCUSED & pNm->uNewState) { if (!m_bMenuActive) { // // Only update the item count if a menu item is not active. // This method is called WHENEVER an item is updated. This includes // asynchronous notifications following name resolution. Without // this check, a menu's descriptive text can be overwritten while // the user is walking through menu items. // ShowItemCountInStatusBar(); } PDISKQUOTA_USER pUser = NULL; m_UserList.Lock(); m_UserList.Retrieve((LPVOID *)&pUser, pNm->iItem); if (NULL != pUser && NULL != m_pQuotaControl) { DWORD dwAccountStatus = 0; pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_UNRESOLVED == dwAccountStatus) { // // If the user object hasn't been resolved yet, promote it to the // head of the quota controller's SID-Name resolver queue. // This will speed up the name resolution for this user without // performing a blocking operation. // m_pQuotaControl->GiveUserNameResolutionPriority(pUser); } } m_UserList.ReleaseLock(); } else if ((0 == pNm->uNewState) || (LVIS_SELECTED & pNm->uNewState)) { ShowItemCountInStatusBar(); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::SetFocus Description: Called whenever the main window receives focus. Immediately transfers focus to the listview control. The listview in turn ensures that one or more items are highlighted. Arguments: Std WndProc arguments. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnSetFocus( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { if (NULL != m_hwndListView) SetFocus(m_hwndListView); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnSize Description: Handles WM_SIZE message. Arguments: Standard WndProc arguments. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 05/20/97 Added positioning of "Find User" combo in toolbar. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnSize( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { RECT rcMain; RECT rcListView; GetClientRect(hWnd, &rcMain); // How big's the main window? rcListView = rcMain; if (m_lvsi.fToolBar) { // // Adjust toolbar if it's visible. // RECT rcToolBar; INT cyToolBar = 0; SendMessage(m_hwndToolBar, message, wParam, lParam); GetClientRect(m_hwndToolBar, &rcToolBar); cyToolBar = rcToolBar.bottom - rcToolBar.top; rcListView.top += (cyToolBar + 1); // // Position the "Find User" combo box to the immediate right of the // "Find" toolbar button. // This code assumes that the "Find" toolbar button is the right-most // button in the toolbar. // INT cButtons = (INT)SendMessage(m_hwndToolBar, TB_BUTTONCOUNT, 0, 0); if (0 < cButtons) { RECT rcButton; SendMessage(m_hwndToolBar, TB_GETITEMRECT, cButtons - 1, (LPARAM)&rcButton); RECT rcCombo; GetWindowRect(m_hwndToolbarCombo, &rcCombo); SetWindowPos(m_hwndToolbarCombo, NULL, rcButton.right + 1, rcButton.top + 1, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); } } if (m_lvsi.fStatusBar) { // // Adjust status bar if it's visible. // RECT rcStatusBar; INT cyStatusBar = 0; SendMessage(m_hwndStatusBar, message, wParam, lParam); GetClientRect(m_hwndStatusBar, &rcStatusBar); cyStatusBar = rcStatusBar.bottom - rcStatusBar.top; rcListView.bottom -= cyStatusBar; } // // Adjust the listview. Accounts for toolbar and status bar. // MoveWindow(m_hwndListView, 0, rcListView.top, rcListView.right - rcListView.left, rcListView.bottom - rcListView.top, TRUE); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::SelectAllItems Description: Highlights all items in the listview for selection. Arguments: None. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::SelectAllItems( VOID ) { INT cItems = ListView_GetItemCount(m_hwndListView); DWORD dwState = 0; CAutoWaitCursor waitcursor; SetFocus(m_hwndListView); // // This isn't documented but it's the way the shell does it for DefView. // ListView_SetItemState(m_hwndListView, -1, LVIS_SELECTED, LVIS_SELECTED); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::InvertSelection Description: Selects all items that are not selected and unselects all items that are. Arguments: None. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::InvertSelectedItems( VOID ) { INT iItem = -1; CAutoWaitCursor waitcursor; SetFocus(m_hwndListView); while ((iItem = ListView_GetNextItem(m_hwndListView, iItem, 0)) != -1) { DWORD dwState; dwState = ListView_GetItemState(m_hwndListView, iItem, LVIS_SELECTED); dwState ^= LVNI_SELECTED; ListView_SetItemState(m_hwndListView, iItem, dwState, LVIS_SELECTED); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnHelpAbout Description: Handler for "About Windows NT" menu option. Arguments: hWnd - Handle of parent window for "about" dialog. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnHelpAbout( HWND hWnd ) { TCHAR szOpSysName[MAX_PATH]; LoadString(g_hInstDll, IDS_WINDOWS, szOpSysName, ARRAYSIZE(szOpSysName)); ShellAbout(hWnd, szOpSysName, NULL, NULL); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnHelpTopics Description: Handler for "Help Topics" menu option. Arguments: hWnd - Handle of parent window for the help UI. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnHelpTopics( HWND hWnd ) { const char szHtmlHelpFileA[] = "DSKQUOUI.CHM > windefault"; const char szHtmlHelpTopicA[] = "sag_DQtopnode.htm"; HtmlHelpA(hWnd, szHtmlHelpFileA, HH_DISPLAY_TOPIC, (DWORD_PTR)szHtmlHelpTopicA); return 0; } bool DetailsView::SingleSelectionIsAdmin( void ) { bool bResult = false; if (1 == ListView_GetSelectedCount(m_hwndListView)) { INT iItem = ListView_GetNextItem(m_hwndListView, -1, LVNI_SELECTED); if (-1 != iItem) { PDISKQUOTA_USER pUser = NULL; if (m_UserList.Retrieve((LPVOID *)&pUser, iItem) && NULL != pUser) { bResult = !!UserIsAdministrator(pUser); } } } return bResult; } // // NTRAID#NTBUG9-157269-2000/08/08-BrianAu Temporary workaround. // // This workaround was added for bug 157269. However, // the root cause is bug 24904. Once 24904 is fixed, we should // be able to remove this code. Bug 157269 provides a detailed // explanation of the issue. // // Since it's a temporary function and doesn't use any members // of the DetailsView class I chose to not make it a member // of DetailsView. The logic used is taken from // DiskQuotaControl::AddUserSid. // // The Retry and sleep code is a total hack. // bool UserWasReallyDeleted( IDiskQuotaControl *pQuotaControl, IDiskQuotaUser *pIUser ) { bool bDeleted = true; BYTE rgbSid[MAX_SID_LEN]; HRESULT hr = pIUser->GetSid(rgbSid, sizeof(rgbSid)); if (SUCCEEDED(hr)) { IDiskQuotaUser *pIUserTemp; hr = pQuotaControl->FindUserSid(rgbSid, DISKQUOTA_USERNAME_RESOLVE_NONE, &pIUserTemp); if (SUCCEEDED(hr)) { const int MAX_RETRY_COUNT = 1; const DWORD RETRY_WAIT_MS = 100; for (int i = 0; i <= MAX_RETRY_COUNT && bDeleted; i++) { LONGLONG llLimit; LONGLONG llThreshold; LONGLONG llUsed; pIUserTemp->GetQuotaLimit(&llLimit); pIUserTemp->GetQuotaThreshold(&llThreshold); pIUserTemp->GetQuotaUsed(&llUsed); bDeleted = ((MARK4DEL == llLimit) || ( 0 == llLimit && 0 == llThreshold && 0 == llUsed)); if (bDeleted && i < MAX_RETRY_COUNT) { Sleep(RETRY_WAIT_MS); // // Invalidate cached data so next request fetches from // volume, not memory. // pIUserTemp->Invalidate(); } } pIUserTemp->Release(); } } return bDeleted; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdDelete Description: Called whenever the user presses DEL or selects the "Delete" option from the main menu, context menu or toolbar. The method attempts to delete the selected records. Any records that have 1+ bytes charged to them will not be deleted. A message box is displayed informing the user if any selected records have 1+ bytes. Arguments: None. Returns: Nothing. Exceptions: OutOfMemory. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/11/96 Initial creation. BrianAu 03/11/98 Added code for resolving "owned" files. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdDelete( VOID ) { HRESULT hResult = NO_ERROR; // // Make sure they really want to do this. // Early return if they don't. // Don't ask if it's a single selection and the selected user // is BUILTIN/Administrators. This user can't be deleted anyway so // we don't want to ask for confirmation. The deletion attempt will // fail later and we'll display a "can't be deleted" msgbox. // if (!SingleSelectionIsAdmin() && IDNO == DiskQuotaMsgBox(m_hwndListView, IDS_CONFIRM_DELETE_USER, IDS_TITLE_DISK_QUOTA, MB_ICONWARNING | MB_YESNO)) { return 0; } // // Clear any previous undo actions. // Only allow undo for a single delete (single or multi-user) operation. // m_pUndoList->Clear(); CAutoWndEnable autoenable(m_hwndListView); PDISKQUOTA_USER pUser = NULL; INT iItem = -1; INT cItemsToDelete = ListView_GetSelectedCount(m_hwndListView); ProgressDialog dlgProgress(IDD_PROGRESS, IDC_PROGRESS_BAR, IDC_TXT_PROGRESS_DESCRIPTION, IDC_TXT_PROGRESS_FILENAME); if (2 < cItemsToDelete) { // // Create and display a progress dialog if we're deleting more than 2 // user quota records. // if (dlgProgress.Create(g_hInstDll, m_hwndMain)) { autoenable.Enable(false); dlgProgress.ProgressBarInit(0, cItemsToDelete, 1); dlgProgress.SetDescription(MAKEINTRESOURCE(IDS_PROGRESS_DELETING)); dlgProgress.Show(); } } // // Set each user's threshold and limit to -2 (MARK4DEL) and remove the // item from the listview. // A limit of -2 tells the quota system (NTFS) that the record should be // removed from the quota file. However, if the user still has quota // charged, the record will be restored. // CAutoSetRedraw autoredraw(m_hwndListView, false); CArray rgpUsersWithFiles; LONGLONG Threshold; LONGLONG Limit; while(-1 != (iItem = ListView_GetNextItem(m_hwndListView, iItem, LVNI_SELECTED)) && !dlgProgress.UserCancelled()) { if (m_UserList.Retrieve((LPVOID *)&pUser, iItem)) { if (UserIsAdministrator(pUser)) { // // Deletion of the BUILTINS\Administrators quota record is not // allowed. // CString strText(g_hInstDll, IDS_CANT_DELETE_ADMIN_RECORD); DiskQuotaMsgBox(dlgProgress.m_hWnd ? dlgProgress.m_hWnd : m_hwndListView, strText, IDS_TITLE_DISK_QUOTA, MB_ICONWARNING | MB_OK); } else { // // Get threshold and limit values for undo action. // pUser->GetQuotaThreshold(&Threshold); pUser->GetQuotaLimit(&Limit); // // Remove the user from the quota file. // hResult = m_pQuotaControl->DeleteUser(pUser); if (SUCCEEDED(hResult)) { if (!UserWasReallyDeleted(m_pQuotaControl, pUser)) { // // NTRAID#NTBUG9-157269-2000/08/08-BrianAu Temporary workaround. // // This workaround was added for bug 157269. However, // the root cause is bug 24904. Once 24904 is fixed, we should // be able to remove this code. Bug 157269 provides a detailed // explanation of the issue. // pUser->SetQuotaThreshold(Threshold, TRUE); pUser->SetQuotaLimit(Limit, TRUE); } else { pUser->AddRef(); // Giving ptr to UNDO list. try { autoptr ptrUndoDelete = new UndoDelete(pUser, Threshold, Limit); m_pUndoList->Add(ptrUndoDelete); ptrUndoDelete.disown(); } catch(CAllocException& e) { pUser->Release(); // Release from Undo list. EnableWindow(m_hwndMain, TRUE); throw; } ListView_DeleteItem(m_hwndListView, iItem); // // Deletion is successful. Now actually remove the user from // the user list. // m_UserList.Remove((LPVOID *)&pUser, iItem); pUser->Release(); // Release from listview. // // Decrement the search index by 1 since what was index + 1 // is now index. ListView_GetNextItem ignores the item at "index". // iItem--; } dlgProgress.ProgressBarAdvance(); } else if (ERROR_FILE_EXISTS == HRESULT_CODE(hResult)) { // // One more we couldn't delete. // rgpUsersWithFiles.Append(pUser); } } } } if (0 < rgpUsersWithFiles.Count()) { // // Display a dialog listing users selected for deletion and // and the files owned by those users on this volume. From the dialog, // the admin can Delete, Move or Take Ownership of the files. // dlgProgress.SetDescription(MAKEINTRESOURCE(IDS_PROGRESS_SEARCHINGFORFILES)); CFileOwnerDialog dlg(g_hInstDll, dlgProgress.m_hWnd ? dlgProgress.m_hWnd : m_hwndListView, m_idVolume.FSPath(), rgpUsersWithFiles); dlg.Run(); dlgProgress.SetDescription(MAKEINTRESOURCE(IDS_PROGRESS_DELETING)); int cUsers = rgpUsersWithFiles.Count(); int cCannotDelete = 0; for (int i = 0; i < cUsers; i++) { pUser = rgpUsersWithFiles[i]; // // Get threshold and limit values for undo action. // pUser->GetQuotaThreshold(&Threshold); pUser->GetQuotaLimit(&Limit); // // Try to remove the user from the quota file. // hResult = m_pQuotaControl->DeleteUser(pUser); if (SUCCEEDED(hResult)) { if (!UserWasReallyDeleted(m_pQuotaControl, pUser)) { // // NTRAID#NTBUG9-157269-2000/08/08-BrianAu Temporary workaround. // // This workaround was added for bug 157269. However, // the root cause is bug 24904. Once 24904 is fixed, we should // be able to remove this code. Bug 157269 provides a detailed // explanation of the issue. // pUser->SetQuotaThreshold(Threshold, TRUE); pUser->SetQuotaLimit(Limit, TRUE); } else { pUser->AddRef(); // Giving ptr to UNDO list. try { autoptr ptrUndoDelete = new UndoDelete(pUser, Threshold, Limit); m_pUndoList->Add(ptrUndoDelete); ptrUndoDelete.disown(); } catch(CAllocException& e) { pUser->Release(); // Release from Undo list. throw; } iItem = FindUserByObjPtr(pUser); if (-1 != iItem) { ListView_DeleteItem(m_hwndListView, iItem); // // Deletion is successful. Now actually remove the user from // the user list. // m_UserList.Remove((LPVOID *)&pUser, iItem); pUser->Release(); // Release from listview. } } dlgProgress.ProgressBarAdvance(); } else if (ERROR_FILE_EXISTS == HRESULT_CODE(hResult)) { cCannotDelete++; } } if (0 < cCannotDelete) { // // One or more records could not be deleted because they have // disk space charged to them. // CString strText; if (1 == cCannotDelete) strText.Format(g_hInstDll, IDS_CANNOT_DELETE_USER); else strText.Format(g_hInstDll, IDS_CANNOT_DELETE_USERS, cCannotDelete); DiskQuotaMsgBox(m_hwndListView, strText, IDS_TITLE_DISK_QUOTA, MB_ICONINFORMATION | MB_OK); } } ShowItemCountInStatusBar(); if (FAILED(hResult) && ERROR_FILE_EXISTS != HRESULT_CODE(hResult)) { // // Something bad happened. // FEATURE: Do we need to discriminate between a general failure and // a quota file write error? // DiskQuotaMsgBox(m_hwndListView, IDS_ERROR_DELETE_USER, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdUndo Description: Called whenever the user presses Ctrl + Z or selects the "Undo" option from the main menu, context menu or toolbar. The method invokes the current undo list to "undo" its actions. Arguments: None. Returns: Always 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/01/96 Initial creation. BrianAu 02/26/97 Added call to update status bar. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdUndo( VOID ) { if (NULL != m_pUndoList) { CAutoWaitCursor waitcursor; m_pUndoList->Undo(); ShowItemCountInStatusBar(); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdFind Description: Called whenever the user presses Ctrl + F or selects the "Find" option from the main menu, context menu or toolbar. The method invokes the "Find User" dialog. Arguments: None. Returns: Always 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdFind( VOID ) { if (NULL != m_pUserFinder) { m_pUserFinder->InvokeFindDialog(m_hwndListView); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdProperties Description: Displays a properties dialog for one or more selected objects. Invoked when the user selects a "Properties" menu option, dbl clicks a selection or presses Return for a selection. Arguments: None. Returns: Always returns 0. Exceptions: OutOfMemory. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 09/10/96 Added passing of LVSelection to prop sheet ctor. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdProperties( VOID ) { LVSelection lvs(m_hwndListView); INT iItem = -1; // // Fill in arrays of user pointers and item indices. // while(-1 != (iItem = ListView_GetNextItem(m_hwndListView, iItem, LVNI_SELECTED))) { LPVOID pvUser = 0; if (m_UserList.Retrieve(&pvUser, iItem)) { // // Add user object pointer and item index to the selection object. // We'll use this container to communicate the selected items to the // property sheet object. // This can throw OutOfMemory. // lvs.Add((PDISKQUOTA_USER)pvUser, iItem); } } if (0 < lvs.Count()) { // // Create and run the property sheet. It's modal. // There's a condition where the user can select in the listview and // nothing is actually selected (i.e. select below the last item). // Therefore, we need the (0 < count) check. // m_pQuotaControl->AddRef(); UserPropSheet ups(m_pQuotaControl, m_idVolume, m_hwndListView, lvs, *m_pUndoList); ups.Run(); } return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdNew Description: Displays a properties dialog for adding a new user to the quota information file. Invoked when the user selects the "New" menu option. Arguments: None. Returns: Always returns 0. Exceptions: OutOfMemory. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/27/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdNew( VOID ) { // // Create and run the AddUser dialog. // Note that it first launches the DS Object Picker dialog. // m_pQuotaControl->AddRef(); AddUserDialog dlg(m_pQuotaControl, m_idVolume, g_hInstDll, m_hwndListView, m_hwndListView, *m_pUndoList); dlg.Run(); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CreateVolumeDisplayName [static] Description: Obtains the display name used by the shell for a given volume. Arguments: pszDrive - Address of string containing drive name (i.e. "C:\"). pstrDisplayName - Address of CString object to receive the display name. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 06/30/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CreateVolumeDisplayName( const CVolumeID& idVolume, // [in] - "C:\" or "\\?\Volume{ }\", the shell won't // understand it. Just use the default display name provided by the // CVolumeID object. // *pstrDisplayName = idVolume.ForDisplay(); } else { // // It's a normal volume. Get the display name the shell uses. // com_autoptr ptrDesktop; // // Bind to the desktop folder. // hr = SHGetDesktopFolder(ptrDesktop.getaddr()); if (SUCCEEDED(hr)) { sh_autoptr ptrIdlDrives; hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, ptrIdlDrives.getaddr()); if (SUCCEEDED(hr)) { // // Bind to the "Drives" folder. // com_autoptr ptrDrives; hr = ptrDesktop->BindToObject(ptrIdlDrives, NULL, IID_IShellFolder, (LPVOID *)ptrDrives.getaddr()); if (SUCCEEDED(hr)) { com_autoptr ptrEnum; // // Enumerate all of the non-folder objects in the drives folder. // hr = ptrDrives->EnumObjects(NULL, SHCONTF_NONFOLDERS, ptrEnum.getaddr()); if (SUCCEEDED(hr)) { sh_autoptr ptrIdlItem; ULONG ulFetched = 0; LPCTSTR pszDrive = idVolume.ForParsing(); // // For each item in the drives folder... // while(S_OK == ptrEnum->Next(1, ptrIdlItem.getaddr(), &ulFetched)) { STRRET strretName; // // Get the non-display name form; i.e. "G:\" // hr = ptrDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_FORPARSING, &strretName); if (SUCCEEDED(hr)) { TCHAR szName[MAX_PATH]; StrRetToBuf(&strretName, ptrIdlItem, szName, ARRAYSIZE(szName)); if (TEXT(':') == szName[1] && *pszDrive == szName[0]) { // // Get the display name form; i.e. "My Disk (G:)" // hr = ptrDrives->GetDisplayNameOf(ptrIdlItem, SHGDN_NORMAL, &strretName); if (SUCCEEDED(hr)) { StrRetToBuf(&strretName, ptrIdlItem, pstrDisplayName->GetBuffer(MAX_PATH), MAX_PATH); pstrDisplayName->ReleaseBuffer(); } break; } } } } } } } } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdImport Description: Called when the user selects the "Import" option on the "Quota" menu. Presents the "Open File" common dialog to get the name for the file containing the import information. Then passes the path off to an Importer object to do the actual import. Arguments: None. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdImport( VOID ) { HRESULT hResult = NO_ERROR; TCHAR szFileName[MAX_PATH] = { TEXT('\0') }; TCHAR szTitle[80] = { TEXT('\0') }; LoadString(g_hInstDll, IDS_DLGTITLE_IMPORT, szTitle, ARRAYSIZE(szTitle)); OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = m_hwndMain; ofn.hInstance = g_hInstDll; ofn.lpstrFile = szFileName; ofn.lpstrTitle = szTitle; ofn.nMaxFile = ARRAYSIZE(szFileName); ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; // // Get name of import file from user and import the files. // if (GetOpenFileName(&ofn)) { Importer importer(*this); hResult = importer.Import(ofn.lpstrFile); } return SUCCEEDED(hResult); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdExport Description: Called when the user selects the "Export" option on the "Quota" menu or listview context menu. Presents the "Save File" common dialog to get the name for the output file. It then creates the doc file, the stream within the doc file and then calls the DetailsView's IDataObject implementation to render the data on the stream. Arguments: None. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdExport( VOID ) { HRESULT hResult = NO_ERROR; IDataObject *pIDataObject = NULL; try { hResult = QueryInterface(IID_IDataObject, (LPVOID *)&pIDataObject); if (SUCCEEDED(hResult)) { FORMATETC fmt; DataObject::SetFormatEtc(fmt, DataObject::m_CF_NtDiskQuotaExport, TYMED_ISTREAM); hResult = pIDataObject->QueryGetData(&fmt); if (SUCCEEDED(hResult)) { TCHAR szFileName[MAX_PATH] = { TEXT('\0') }; TCHAR szTitle[80] = { TEXT('\0') }; LoadString(g_hInstDll, IDS_DLGTITLE_EXPORT, szTitle, ARRAYSIZE(szTitle)); OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = m_hwndMain; ofn.hInstance = g_hInstDll; ofn.lpstrFile = szFileName; ofn.lpstrTitle = szTitle; ofn.nMaxFile = ARRAYSIZE(szFileName); ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; // // Get output file name from user. // if (GetSaveFileName(&ofn)) { DWORD grfMode = STGM_DIRECT | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE; IStorage *pStg; // // Create the output doc file. // hResult = StgCreateDocfile(ofn.lpstrFile, grfMode, 0, &pStg); if (SUCCEEDED(hResult)) { // // Create the stream in the doc file. // IStream *pStm; hResult = pStg->CreateStream(DataObject::SZ_EXPORT_STREAM_NAME, grfMode, 0, 0, &pStm); if (SUCCEEDED(hResult)) { CStgMedium medium; // // Render the quota information onto the file stream. // hResult = pIDataObject->GetData(&fmt, &medium); if (SUCCEEDED(hResult)) { const LARGE_INTEGER ofsBegin = {0, 0}; ULARGE_INTEGER ofsOriginal = {0, 0}; hResult = medium.pstm->Seek(ofsBegin, STREAM_SEEK_CUR, &ofsOriginal); if (SUCCEEDED(hResult)) { hResult = medium.pstm->Seek(ofsBegin, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hResult)) { ULARGE_INTEGER cb = {0xFFFFFFFF, 0xFFFFFFFF}; medium.pstm->CopyTo(pStm, cb, NULL, NULL); const LARGE_INTEGER ofs = { ofsOriginal.LowPart, (LONG)ofsOriginal.HighPart }; medium.pstm->Seek(ofs, STREAM_SEEK_SET, NULL); } } } pStm->Release(); } pStg->Release(); } if (FAILED(hResult)) { UINT iMsg = IDS_EXPORT_STREAM_FAILED; switch(hResult) { case STG_E_ACCESSDENIED: iMsg = IDS_EXPORT_STREAM_NOACCESS; break; case E_OUTOFMEMORY: case STG_E_INSUFFICIENTMEMORY: iMsg = IDS_EXPORT_STREAM_OUTOFMEMORY; break; case STG_E_INVALIDNAME: iMsg = IDS_EXPORT_STREAM_INVALIDNAME; break; case STG_E_TOOMANYOPENFILES: iMsg = IDS_EXPORT_STREAM_TOOMANYFILES; break; default: break; } DiskQuotaMsgBox(m_hwndMain, iMsg, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } } } else { DBGERROR((TEXT("Export: Error 0x%08X returned from QueryGetData."), hResult)); } pIDataObject->Release(); pIDataObject = NULL; } else { DBGERROR((TEXT("Export: Error 0x%08X getting IDataObject."), hResult)); } } catch(CAllocException& e) { if (NULL != pIDataObject) pIDataObject->Release(); throw; } return SUCCEEDED(hResult); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdViewStatusBar Description: Toggles the visibility of the status bar. Invoked when the user selects the "Status Bar" menu option. Arguments: None. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdViewStatusBar( VOID ) { RECT rc; m_lvsi.fStatusBar ^= TRUE; ShowWindow(m_hwndStatusBar, m_lvsi.fStatusBar ? SW_SHOW : SW_HIDE); // // Adjust the main window. // GetWindowRect(m_hwndMain, &rc); OnSize(m_hwndMain, WM_SIZE, SIZE_RESTORED, MAKELONG(rc.right-rc.left,rc.bottom-rc.top)); // // Check the menu item to indicate the current status bar state. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_STATUSBAR, MF_BYCOMMAND | (m_lvsi.fStatusBar ? MF_CHECKED : MF_UNCHECKED)); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdViewToolBar Description: Toggles the visibility of the tool bar. Invoked when the user selects the "Tool Bar" menu option. Arguments: None. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdViewToolBar( VOID ) { RECT rc; m_lvsi.fToolBar ^= TRUE; ShowWindow(m_hwndToolBar, m_lvsi.fToolBar ? SW_SHOW : SW_HIDE); // // Adjust the main window. // GetWindowRect(m_hwndMain, &rc); OnSize(m_hwndMain, WM_SIZE, SIZE_RESTORED, MAKELONG(rc.right-rc.left,rc.bottom-rc.top)); // // Check the menu item to indicate the current tool bar state. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_TOOLBAR, MF_BYCOMMAND | (m_lvsi.fToolBar ? MF_CHECKED : MF_UNCHECKED)); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::OnCmdViewShowFolder Description: Toggles the visibility of the Domain Name column. Invoked when the user selects the "Show Domain" menu option. Arguments: None. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/06/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::OnCmdViewShowFolder( VOID ) { m_lvsi.fShowFolder ^= TRUE; if (m_lvsi.fShowFolder) { // // Insert the folder column and DEACTIVATE listview tooltip. // Always add at index 1 then shift it to position 0. // User can drag it elsewhere if they like. // Because of the listview's icon painting behavior, we only let the // "status" column be index 0. // AddColumn(1, g_rgColumns[DetailsView::idCol_Folder]); INT cCols = Header_GetItemCount(m_hwndHeader); INT rgColIndicies[DetailsView::idCol_Last]; INT iTemp = 0; DBGASSERT((DetailsView::idCol_Last >= cCols)); Header_GetOrderArray(m_hwndHeader, cCols, rgColIndicies); // // Swap the column we just added with column 0. // iTemp = rgColIndicies[0]; rgColIndicies[0] = rgColIndicies[1]; rgColIndicies[1] = iTemp; Header_SetOrderArray(m_hwndHeader, cCols, rgColIndicies); ActivateListViewToolTip(FALSE); } else { // // Remove the folder column and ACTIVATE listview tooltip. // With the column hidden, users can view a user's folder by hovering // over the user's name. // ActivateListViewToolTip(TRUE); RemoveColumn(DetailsView::idCol_Folder); } // // Check/Uncheck the "Show Folder" menu item. // CheckMenuItem(GetMenu(m_hwndMain), IDM_VIEW_SHOWFOLDER, MF_BYCOMMAND | (m_lvsi.fShowFolder ? MF_CHECKED : MF_UNCHECKED)); // // If the folder column is hidden, the "Arrange by Folder" menu option // is disabled. // EnableMenuItem_ArrangeByFolder(m_lvsi.fShowFolder); // // I haven't found a way to do this without unloading and reloading the // objects following the new column configuration. // Refresh(); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::EnableMenuItem_ArrangeByFolder Description: Sets the sensitivity of the "by Folder" menu item in the "Arrange Items" submenu. Arguments: bEnable - TRUE = Enable menu item, FALSE = Disable and gray item. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::EnableMenuItem_ArrangeByFolder( BOOL bEnable ) { HMENU hMainMenu = GetMenu(m_hwndMain); HMENU hViewMenu = GetSubMenu(hMainMenu, iMENUITEM_VIEW); HMENU hViewArrangeMenu = GetSubMenu(hViewMenu, iMENUITEM_VIEW_ARRANGE); // // If these assert, it probably means somebody's changed the // menus so that the iMENUITEM_XXXXX constants are no longer correct. // DBGASSERT((NULL != hViewMenu)); DBGASSERT((NULL != hViewArrangeMenu)); EnableMenuItem(hViewArrangeMenu, IDM_VIEW_ARRANGE_BYFOLDER, MF_BYCOMMAND | (bEnable ? MF_ENABLED : MF_GRAYED)); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::EnableMenuItem_Undo Description: Sets the sensitivity of the "Undo" menu item in the "Edit" submenu. Arguments: bEnable - TRUE = Enable menu item, FALSE = Disable and gray item. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu 10/22/96 Replaced Assert() with nested if's. BrianAu Tester hit assert via WM_COMMAND when closing details view. */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::EnableMenuItem_Undo( BOOL bEnable ) { HMENU hMainMenu = GetMenu(m_hwndMain); if (NULL != hMainMenu) { HMENU hEditMenu = GetSubMenu(hMainMenu, iMENUITEM_EDIT); if (NULL != hEditMenu) { EnableMenuItem(hEditMenu, IDM_EDIT_UNDO, MF_BYCOMMAND | (bEnable ? MF_ENABLED : MF_GRAYED)); SendMessage(m_hwndToolBar, TB_ENABLEBUTTON, IDM_EDIT_UNDO, MAKELONG(bEnable, 0)); } } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::ShowItemCountInStatusBar Description: Displays the current count of selected items in the status bar. This is what is displayed in the status bar when a menu item is not currently selected. Arguments: None. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 12/16/96 Appended "incorrect data" warning to status bar BrianAu text when quotas are disabled on system. */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::ShowItemCountInStatusBar( VOID ) { DWORD cTotalItems = m_UserList.Count(); DWORD cSelectedItems = ListView_GetSelectedCount(m_hwndListView); CString strText(g_hInstDll, IDS_STATUSBAR_ITEMCOUNT, cTotalItems, cSelectedItems); SendMessage(m_hwndStatusBar, SB_SETTEXT, 0, (LPARAM)((LPCTSTR)strText)); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::ShowMenuTextInStatusBar Description: Displays the description of the currently-selected menu item in the status bar. Arguments: None. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::ShowMenuTextInStatusBar( DWORD idMenuOption ) { TCHAR szText[MAX_PATH]; LoadString(g_hInstDll, idMenuOption, szText, ARRAYSIZE(szText)); SendMessage(m_hwndStatusBar, SB_SETTEXT, 0, (LPARAM)szText); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Refresh Description: Refreshes the view by re-loading the objects. Arguments: bInvalidateCache - true == invalidate all entries in the SID-name cache. Default is false. Returns: Always returns 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu 02/21/97 Ownerdata listview. BrianAu 10/10/98 Added bInvalidateCache argument. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT DetailsView::Refresh( bool bInvalidateCache // optional. default is false. ) { CAutoWaitCursor waitcursor; if (bInvalidateCache) m_pQuotaControl->InvalidateSidNameCache(); InvalidateRect(m_hwndListView, NULL, TRUE); CAutoSetRedraw autoredraw(m_hwndListView, false); ReleaseObjects(); autoredraw.Set(true); LoadObjects(); ListView_SetItemCountEx(m_hwndListView, m_UserList.Count(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL); SortObjects(m_iLastColSorted, m_fSortDirection); ShowItemCountInStatusBar(); FocusOnSomething(); return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::FocusOnSomething Description: Ensures that one or more listview items have the focus highlighting. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::FocusOnSomething( VOID ) { INT iFocus; iFocus = ListView_GetNextItem(m_hwndListView, -1, LVNI_FOCUSED); if (-1 == iFocus) iFocus = 0; ListView_SetItemState(m_hwndListView, iFocus, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED) } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::CalcPctQuotaUsed Description: Calculates the percent quota used for a given user. The value returned is an integer. Arguments: pUser - Address of IDiskQuotaUser interface for user object. pdwPct - Address of DWORD to receive the percentage value. If the method returns div-by-zero, this value is set to ~0. This lets a caller sort erroneous values from valid values. The (~0 - 1) return value is used so that NOLIMIT users are grouped separate from 0 limit users when sorted on % used. Both are using 0% of their quota but it looks better if they are each grouped separately. Returns: NO_ERROR - Success. STATUS_INTEGER_DIVIDE_BY_ZERO - The user's quota limit was 0. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::CalcPctQuotaUsed( PDISKQUOTA_USER pUser, LPDWORD pdwPct ) { LONGLONG llUsed; LONGLONG llLimit; HRESULT hResult = E_FAIL; DBGASSERT((NULL != pUser)); DBGASSERT((NULL != pdwPct)); pUser->GetQuotaUsed(&llUsed); pUser->GetQuotaLimit(&llLimit); if (NOLIMIT == llLimit) { *pdwPct = (DWORD)~0 - 1; // No quota limit for user. } else if (0 < llLimit) { *pdwPct = (INT)((llUsed * 100) / llLimit); hResult = NO_ERROR; } else { // // Limit is 0. Would produce div-by-zero. // *pdwPct = (DWORD)~0; } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::AddUser Description: Adds a user object to the listview. Note that this is used for adding a single user object such as in an "add user" operation. The method LoadObjects is used to load the whole listview. It's more efficient than calling this for each user. Arguments: pUser - Address of user's IDiskQuotaUser interface. Returns: TRUE - User was added. FALSE - User wasn't added. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/30/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::AddUser( PDISKQUOTA_USER pUser ) { BOOL bResult = FALSE; LV_ITEM item; item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE; item.state = 0; item.stateMask = 0; item.iSubItem = 0; item.pszText = LPSTR_TEXTCALLBACK; item.iImage = I_IMAGECALLBACK; item.iItem = 0; m_UserList.Insert((LPVOID)pUser); if (-1 != ListView_InsertItem(m_hwndListView, &item)) { bResult = TRUE; } return bResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::FindUserByName Description: Locate a specified user in the listview by account name. Name comparison is case-insensitive. Arguments: pszUserName - Account name for user. ppIUser [optional] - Address of IDiskQuotaUser pointer variable to receive the address of the user object. Returns: -1 = account name not found. Otherwise, returns index of item in listview. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/23/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT DetailsView::FindUserByName( LPCTSTR pszLogonName, PDISKQUOTA_USER *ppIUser // [optional] ) { INT iItem = -1; m_UserList.Lock(); try { INT cUsers = m_UserList.Count(); PDISKQUOTA_USER pUser = NULL; // // Find the user that matches pszUserName. // for (INT i = 0; i < cUsers && -1 == iItem; i++) { if (m_UserList.Retrieve((LPVOID *)&pUser, i)) { // // Get name from listview item. // if (NULL != pUser) { TCHAR szLogonName[MAX_USERNAME]; pUser->GetName(NULL, 0, szLogonName, ARRAYSIZE(szLogonName), NULL, 0); if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szLogonName, -1, pszLogonName, -1)) { iItem = i; if (NULL != ppIUser) *ppIUser = pUser; } } } } } catch(CAllocException& e) { m_UserList.ReleaseLock(); throw; } m_UserList.ReleaseLock(); return iItem; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::FindUserBySid Description: Locate a specified user in the listview by SID. Arguments: pSid - Address of buffer containing key SID. ppIUser [optional] - Address of IDiskQuotaUser pointer variable to receive the address of the user object. Returns: -1 = Record not found. Otherwise, returns index of item in listview. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/23/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT DetailsView::FindUserBySid( LPBYTE pbSid, PDISKQUOTA_USER *ppIUser // [optional] ) { INT iItem = -1; m_UserList.Lock(); try { INT cUsers = m_UserList.Count(); PDISKQUOTA_USER pUser = NULL; // // Find the user that matches pszUserName. // for (INT i = 0; i < cUsers && -1 == iItem; i++) { if (m_UserList.Retrieve((LPVOID *)&pUser, i)) { // // Get SID from listview item. // if (NULL != pUser) { BYTE Sid[MAX_SID_LEN]; pUser->GetSid((LPBYTE)&Sid, ARRAYSIZE(Sid)); if (EqualSid((LPBYTE)&Sid, pbSid)) { iItem = i; if (NULL != ppIUser) *ppIUser = pUser; } } } } } catch(CAllocException& e) { m_UserList.ReleaseLock(); throw; } m_UserList.ReleaseLock(); return iItem; } // // Locate a user in the listview based on it's object pointer. // INT DetailsView::FindUserByObjPtr( PDISKQUOTA_USER pUserKey ) { INT iItem = -1; m_UserList.Lock(); try { INT cUsers = m_UserList.Count(); PDISKQUOTA_USER pUser = NULL; // // Find the user that matches pszUserName. // for (INT i = 0; i < cUsers && -1 == iItem; i++) { if (m_UserList.Retrieve((LPVOID *)&pUser, i)) { if (NULL != pUser && pUserKey == pUser) { iItem = i; } } } } catch(CAllocException& e) { m_UserList.ReleaseLock(); throw; } m_UserList.ReleaseLock(); return iItem; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GotoUserName Description: Locate a specified user in the listview. If found, highlight the row. The search is case-insensitive. This function was originally designed to work with the "Find User" feature so that when a record is located, it is made visible in the view and highlighted. Arguments: pszUserName - Account name for user. Returns: TRUE = Record found. FALSE = Not found. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::GotoUserName( LPCTSTR pszUserName ) { INT iUser = FindUserByName(pszUserName); if (-1 != iUser) { // // Found a match (case-insensitive). // // Select the item specified by the user. // Note that we leave any selected items selected. // Users may use the find feature to select a set of // non-contiguous quota records in the listview. // ListView_EnsureVisible(m_hwndListView, iUser, FALSE); ListView_SetItemState(m_hwndListView, iUser, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); } return (-1 != iUser); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetConnectionPoint Description: Retrieves the IDiskQuotaEvents connection point from the quota control object. This is the connection point through which the asynchronous user name change events are delivered as names are resolved by the network DC. Arguments: None. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// IConnectionPoint * DetailsView::GetConnectionPoint( VOID ) { HRESULT hResult = NO_ERROR; IConnectionPoint *pCP = NULL; if (NULL != m_pQuotaControl) { IConnectionPointContainer *pCPC = NULL; hResult = m_pQuotaControl->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&pCPC); if (SUCCEEDED(hResult)) { hResult = pCPC->FindConnectionPoint(IID_IDiskQuotaEvents, &pCP); pCPC->Release(); if (FAILED(hResult)) pCP = NULL; } } return pCP; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::ConnectEventSink Description: Connects the event sink (DetailsView) from the quota controller's IDiskQuotaEvents connection point object. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::ConnectEventSink( VOID ) { HRESULT hResult = NO_ERROR; IConnectionPoint *pConnPt = GetConnectionPoint(); if (NULL != pConnPt) { hResult = pConnPt->Advise((LPUNKNOWN)static_cast(this), &m_dwEventCookie); pConnPt->Release(); DBGPRINT((DM_VIEW, DL_MID, TEXT("LISTVIEW - Connected event sink. Cookie = %d"), m_dwEventCookie)); } else hResult = E_FAIL; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DisconnectEventSink Description: Disconnects the event sink (DetailsView) from the quota controller's IDiskQuotaEvents connection point object. Arguments: None. Returns: NO_ERROR - Success. E_FAIL - Failed. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 08/20/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DisconnectEventSink( VOID ) { HRESULT hResult = NO_ERROR; DBGPRINT((DM_VIEW, DL_MID, TEXT("LISTVIEW - Disconnecting event sink. Cookie = %d"), m_dwEventCookie)); if (0 != m_dwEventCookie) { IConnectionPoint *pConnPt = GetConnectionPoint(); if (NULL != pConnPt) { hResult = pConnPt->Unadvise(m_dwEventCookie); if (SUCCEEDED(hResult)) { m_dwEventCookie = 0; } pConnPt->Release(); } else hResult = E_FAIL; } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::InitLVStateInfo Description: Initializes an LV_STATE_INFO structure to default values. NOTE: This method is declared static so that it can be called without a DetailsView object (not needed). If you want to change any listview state defaults, this is the place to do it. Arguments: plvsi - Address of an LV_STATE_INFO structure to be initialized. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::InitLVStateInfo( PLV_STATE_INFO plvsi ) { ZeroMemory(plvsi, sizeof(*plvsi)); plvsi->cb = sizeof(*plvsi); plvsi->wVersion = wLV_STATE_INFO_VERSION; plvsi->fToolBar = 1; // Default to toolbar visible. plvsi->fStatusBar = 1; // Default to statusbar visible. plvsi->iLastColSorted = 0; // Default to sort first col. plvsi->fSortDirection = 1; // Default to ascending sort. for (UINT i = 0; i < DetailsView::idCol_Last; i++) plvsi->rgColIndices[i] = i; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::IsValidLVStateInfo Description: Validates the contents of a LV_STATE_INFO structure. NOTE: This method is declared static so that it can be called without a DetailsView object (not needed). Arguments: plvsi - Address of an LV_STATE_INFO structure to be validated. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 12/10/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::IsValidLVStateInfo( PLV_STATE_INFO plvsi ) { BOOL bResult = FALSE; INT i = 0; // // Validate structure size member. // if (plvsi->cb != sizeof(LV_STATE_INFO)) goto info_invalid; // // Validate version. // if (wLV_STATE_INFO_VERSION != plvsi->wVersion) goto info_invalid; // // Validate iLastSorted member. // if (!(plvsi->iLastColSorted >= 0 && plvsi->iLastColSorted < DetailsView::idCol_Last)) goto info_invalid; // // Validate each of the column index members. Used for ordering columns. // for (i = 0; i < DetailsView::idCol_Last; i++) { if (!(plvsi->rgColIndices[i] >= 0 && plvsi->rgColIndices[i] < DetailsView::idCol_Last)) goto info_invalid; } bResult = TRUE; info_invalid: return bResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetColumnIds Description: Retrieves a list of IDs for the visible columns in the list. A client can use this list to request report items from the GetReportXXXXX methods below. Arguments: prgColIds - Pointer to an array of INTs to receive the column IDs. cColIds - Size of the destination array. Returns: Number of IDs written to destination array. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// UINT DetailsView::GetColumnIds( INT *prgColIds, INT cColIds ) { INT cHdrCols = Header_GetItemCount(m_hwndHeader); INT rgiSubItem[DetailsView::idCol_Last]; INT i = 0; if (Header_GetOrderArray(m_hwndHeader, cHdrCols, rgiSubItem)) { for (i = 0; i < cHdrCols && i < cColIds; i++) { *(prgColIds + i) = m_ColMap.SubItemToId(rgiSubItem[i]); } } return i; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetNextSelectedItemIndex Description: Retrieves the index of a selected item. The search starts with the index supplied in the iRow argument. Therefore, the following loop will find all selected items: INT iItem = -1; while(1) { iItem = GetNextSelectedItemIndex(iItem); if (-1 == iItem) break; // // Do something with item. // } Arguments: iRow - Row where to start search. The row itself is exluded from the search. -1 starts search from the head of the listview. Returns: 0-based index of next selected item if found. -1 if no more selected items. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/28/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT DetailsView::GetNextSelectedItemIndex(INT iRow) { return ListView_GetNextItem(m_hwndListView, iRow, LVNI_ALL | LVNI_SELECTED); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportItem Description: Retrieve a data item for a drag-drop/clipboard report. This method is patterned after the GetDispInfo_XXX methods but is taylored to placing data on a Stream object. Arguments: iRow - Row where to begin search for next selected item in listview. For the first call, specify -1 to begin the search with the first item. Subsequent calls should specify the value returned from the previous call to GetReportItem. iColId - Item's column ID (idCol_Folder, idCol_Name etc). pItem - Address of an LV_REPORT_ITEM structure. This structure is used to return the data to the caller and also to specify the desired format for numeric values. Some report formats want all data in text format (i.e. CF_TEXT) while other binary formats want numeric data in numeric format (i.e. XlTable). Returns: TRUE = Retrieved row/col data. FALSE = Invalid row or column index. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::GetReportItem( UINT iRow, UINT iColId, PLV_REPORT_ITEM pItem ) { DBGASSERT((NULL != pItem)); BOOL bResult = FALSE; PDISKQUOTA_USER pUser = NULL; if (m_UserList.Retrieve((LPVOID *)&pUser, iRow)) { LONGLONG llValue; DBGASSERT((NULL != pUser)); bResult = TRUE; switch(iColId) { case DetailsView::idCol_Folder: { DWORD dwAccountStatus = 0; pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus) { pUser->GetName(pItem->pszText, pItem->cchMaxText, NULL, 0, NULL, 0); } else { // // Folder name not resolved. User name column will // contain status text. // lstrcpyn(pItem->pszText, TEXT(""), pItem->cchMaxText); } pItem->fType = LVRI_TEXT; break; } case DetailsView::idCol_Name: { DWORD dwAccountStatus = 0; CString strNameText; pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus) { // // User's name has been resolved. // pItem->fType = LVRI_TEXT; pUser->GetName(NULL, 0, NULL, 0, strNameText.GetBuffer(MAX_USERNAME), MAX_USERNAME); strNameText.ReleaseBuffer(); } else { // // User's name not resolved. Use a status message. // switch(dwAccountStatus) { case DISKQUOTA_USER_ACCOUNT_UNRESOLVED: strNameText = m_strAccountUnresolved; break; case DISKQUOTA_USER_ACCOUNT_UNKNOWN: strNameText = m_strAccountUnknown; break; case DISKQUOTA_USER_ACCOUNT_INVALID: strNameText = m_strAccountInvalid; break; case DISKQUOTA_USER_ACCOUNT_DELETED: strNameText = m_strAccountDeleted; break; case DISKQUOTA_USER_ACCOUNT_UNAVAILABLE: strNameText = m_strAccountUnavailable; break; } } lstrcpyn(pItem->pszText, strNameText, pItem->cchMaxText); break; } case DetailsView::idCol_LogonName: { DBGASSERT((NULL != pUser)); DWORD dwAccountStatus = 0; CString strNameText; pUser->GetAccountStatus(&dwAccountStatus); if (DISKQUOTA_USER_ACCOUNT_RESOLVED == dwAccountStatus) { // // User's name has been resolved. // pItem->fType = LVRI_TEXT; pUser->GetName(NULL, 0, strNameText.GetBuffer(MAX_USERNAME), MAX_USERNAME, NULL, 0); strNameText.ReleaseBuffer(); } lstrcpyn(pItem->pszText, strNameText, pItem->cchMaxText); break; } case DetailsView::idCol_Status: // // Return a text string to represent the icon shown // in the "Status" column. // DBGASSERT((NULL != pUser)); switch(GetUserQuotaState(pUser)) { case iUSERSTATE_OK: lstrcpyn(pItem->pszText, m_strStatusOK, pItem->cchMaxText); break; case iUSERSTATE_WARNING: lstrcpyn(pItem->pszText, m_strStatusWarning, pItem->cchMaxText); break; default: DBGASSERT((0)); // // Fall through. // case iUSERSTATE_OVERLIMIT: lstrcpyn(pItem->pszText, m_strStatusOverlimit, pItem->cchMaxText); break; } pItem->fType = LVRI_TEXT; break; // // For the following numeric columns, first get the data then // jump to fmt_byte_count to format it as requested. Note that // all numeric values are expressed in megabytes. This is so they // all have the same units to help with ordering in a spreadsheet. // Otherwise, sorting would not be possible. This is also why we // include the "(MB)" in the report column titles. // case DetailsView::idCol_AmtUsed: pUser->GetQuotaUsed(&llValue); goto fmt_byte_count; case DetailsView::idCol_Threshold: pUser->GetQuotaThreshold(&llValue); goto fmt_byte_count; case DetailsView::idCol_Limit: pUser->GetQuotaLimit(&llValue); fmt_byte_count: // // Format the byte count for the requested data type (text vs. numeric). // switch(pItem->fType) { case LVRI_NUMBER: pItem->fType = LVRI_REAL; if (NOLIMIT == llValue) pItem->dblValue = -1.0; // Indicates to caller "No Limit". else pItem->dblValue = XBytes::ConvertFromBytes(llValue, XBytes::e_Mega); break; case LVRI_TEXT: if (NOLIMIT == llValue) lstrcpyn(pItem->pszText, m_strNoLimit, pItem->cchMaxText); else XBytes::FormatByteCountForDisplay(llValue, pItem->pszText, pItem->cchMaxText, XBytes::e_Mega); // // Fall through. // default: break; } break; case DetailsView::idCol_PctUsed: { HRESULT hResult = CalcPctQuotaUsed(pUser, &pItem->dwValue); // // Format the percent value for the requested data type (text vs. numeric). // If a percentage can't be calculated (0 denominator), return -2 as an // INT value or "N/A" as a text value. // switch(pItem->fType) { case LVRI_NUMBER: pItem->fType = LVRI_INT; if (FAILED(hResult)) pItem->dwValue = (DWORD)-2; // Indicates to caller "N/A". break; case LVRI_TEXT: if (FAILED(hResult)) lstrcpyn(pItem->pszText, m_strNotApplicable, pItem->cchMaxText); else wnsprintf(pItem->pszText, pItem->cchMaxText, TEXT("%d"), pItem->dwValue); default: break; } break; } default: bResult = FALSE; break; } } return bResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportTitle Description: Retrieves a title for a report. Uses the listview window title. Arguments: pszDest - Address of destination character buffer. cchDest - Size of destination buffer in characters. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::GetReportTitle( LPTSTR pszDest, UINT cchDest ) { // // This is simple. Just use the details view title. // FEATURE: Could be enhanced to include the date/time but that will // require localization considerations. // GetWindowText(m_hwndMain, pszDest, cchDest); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportColHeader Description: Retrieves a title for a report column. Note that the titles may differ from those used in the listview. Specifically for the numeric columns. In the listview, numeric column entries include units (bytes, KB, MB etc.). In a report, these numeric values are all expressed in MB. Therefore, the units must be included in the title string. Arguments: iColId - ID of column requested (idCol_Folder, idCol_Name etc.) pszDest - Address of destination character buffer. cchDest - Size of destination buffer in characters. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::GetReportColHeader( UINT iColId, LPTSTR pszDest, UINT cchDest ) { // // WARNING: The order of these must match that of the idCol_XXX enumeration // constants in DetailsView. // UINT rgTitles[] = { IDS_REPORT_HEADER_STATUS, IDS_REPORT_HEADER_FOLDER, IDS_REPORT_HEADER_USERNAME, IDS_REPORT_HEADER_LOGONNAME, IDS_REPORT_HEADER_AMTUSED, IDS_REPORT_HEADER_LIMIT, IDS_REPORT_HEADER_THRESHOLD, IDS_REPORT_HEADER_PCTUSED }; DBGASSERT((NULL != pszDest)); DBGASSERT((1 < cchDest)); CString strHeader(TEXT("...")); if (iColId < ARRAYSIZE(rgTitles)) { strHeader.Format(g_hInstDll, rgTitles[iColId]); } else { DBGERROR((TEXT("LISTVIEW - Invalid idCol (%d) on header request"), iColId)); } lstrcpyn(pszDest, strHeader, cchDest); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportRowCount Description: Retrieves the number of data rows in the listview. Arguments: None. Returns: Number of rows in the listview. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/08/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// UINT DetailsView::GetReportRowCount(VOID) { return ListView_GetSelectedCount(m_hwndListView); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportBinaryRecordSize Description: Retrieves the number of bytes in a record formatted as binary data. This should be called before GetReportBinaryRecord to determine how to size the destination buffer. Arguments: iRow - 0-based index of the row in question. Returns: Number of bytes required to store the record. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// UINT DetailsView::GetReportBinaryRecordSize( UINT iRow ) { INT cbRecord = 0; PDISKQUOTA_USER pUser = NULL; DBGASSERT((iRow < m_UserList.Count())); if (m_UserList.Retrieve((LPVOID *)&pUser, iRow)) { if (NULL != pUser) { pUser->GetSidLength((LPDWORD)&cbRecord); // Length of SID field. cbRecord += sizeof(DWORD) + // Sid-Length field. sizeof(LONGLONG) + // Quota used field. sizeof(LONGLONG) + // Quota threshold field. sizeof(LONGLONG); // Quota limit field. } } return cbRecord; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetReportBinaryRecord Description: Retrieves the information for a single row in the details view formatted as binary data. The format of the returned record is as follows: +------------+---------------------------------------+ | cbSid (32) | SID (variable length) | +------------+------------+-------------+------------+ | Quota Used (64) | Quota Threshold (64) | +------------+------------+-------------+------------+ | Quota Limit (64) | +------------+------------+ (*) The size of each field (bits) is shown in parentheses. Arguments: iRow - 0-based index of the row in question. pbRecord - Address of destination buffer. cbRecord - Number of bytes in destination buffer. Returns: TRUE = Destination buffer was sufficiently large. FALSE = Destination buffer too small or record was invalid. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::GetReportBinaryRecord( UINT iRow, LPBYTE pbRecord, UINT cbRecord ) { // // Create "PMF" (pointer to member function) as a type of pointer // to the IDiskQuotaUser::GetQuotaXXXXXX functions. This allows us // to build an array of function pointers and reduce the amount of // code required. // typedef HRESULT(_stdcall IDiskQuotaUser::*PMF)(PLONGLONG); PDISKQUOTA_USER pUser = NULL; BOOL bResult = FALSE; DBGASSERT((iRow < m_UserList.Count())); DBGASSERT((NULL != pbRecord)); if (m_UserList.Retrieve((LPVOID *)&pUser, iRow)) { DWORD cbSid = 0; if (NULL != pUser && cbRecord >= sizeof(cbSid)) { // // Store the SID-length value first in the record. // pUser->GetSidLength((LPDWORD)&cbSid); *((LPDWORD)pbRecord) = cbSid; pbRecord += sizeof(cbSid); cbRecord -= sizeof(cbSid); // // Store the SID value next. // if (cbRecord >= cbSid && SUCCEEDED(pUser->GetSid(pbRecord, cbRecord))) { pbRecord += cbSid; cbRecord -= cbSid; // // An array of member function pointers. Each function // retrieves a LONGLONG value from the quota user object. // This places the redundant code in a loop. // // The value order is Quota Used, Quota Threshold, Quota Limit. // PMF rgpfnQuotaValue[] = { &IDiskQuotaUser::GetQuotaUsed, &IDiskQuotaUser::GetQuotaThreshold, &IDiskQuotaUser::GetQuotaLimit }; for (INT i = 0; i < ARRAYSIZE(rgpfnQuotaValue); i++) { bResult = TRUE; if (cbRecord >= sizeof(LONGLONG)) { // // First copy to a stack LONGLONG as it is guaranteed // to be aligned. Then byte-copy the value to the // output buffer. // LONGLONG llValue; (pUser->*(rgpfnQuotaValue[i]))(&llValue); CopyMemory(pbRecord, &llValue, sizeof(llValue)); pbRecord += sizeof(LONGLONG); cbRecord -= sizeof(LONGLONG); } else { // // Insufficient buffer. // bResult = FALSE; break; } } } } } return bResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GiveFeedback Description: Implementation for IDropSource::GiveFeedback. Arguments: See IDropSource::GiveFeedback in SDK. Returns: Always returns DRAGDROP_S_USEDEFAULTS. We don't have any special cursors for drag/drop. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::GiveFeedback( DWORD dwEffect ) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DropSource::GiveFeedback"))); return DRAGDROP_S_USEDEFAULTCURSORS; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::QueryContinueDrag Description: Implementation for IDropSource::QueryContinueDrag Arguments: See IDropSource::QueryContinueDrag in SDK. Returns: DRAGDROP_S_CANCEL = User pressed ESC during drag. DRAGDROP_S_DROP = User releases left mouse button. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::QueryContinueDrag( BOOL fEscapePressed, DWORD grfKeyState ) { HRESULT hResult = S_OK; DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DropSource::QueryContinueDrag"))); if (fEscapePressed) hResult = DRAGDROP_S_CANCEL; else if (!(m_DropSource.m_grfKeyState & grfKeyState)) hResult = DRAGDROP_S_DROP; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DragEnter Description: Implementation for IDropTarget::DragEnter Arguments: See IDropTarget::DragEnter in SDK. Returns: See IDropTarget::DragEnter in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DragEnter( IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ) { BOOL bWillAcceptDrop = FALSE; HRESULT hResult = NO_ERROR; IEnumFORMATETC *pEnum = NULL; *pdwEffect = DROPEFFECT_NONE; // // Enumerate formats supported by our data object. // hResult = pDataObject->EnumFormatEtc(DATADIR_GET, &pEnum); if (SUCCEEDED(hResult)) { ULONG ulFetched = 0; FORMATETC fmt; // // Search the formats until we find an acceptable match. // We only accept our private export format along with // CF_HDROP in stream and HGLOBAL media types. // while(!bWillAcceptDrop && S_OK == pEnum->Next(1, &fmt, &ulFetched)) { if (fmt.cfFormat == DataObject::m_CF_NtDiskQuotaExport || fmt.cfFormat == CF_HDROP) { if (fmt.tymed & (TYMED_HGLOBAL | TYMED_ISTREAM)) { bWillAcceptDrop = TRUE; } } } pEnum->Release(); } if (SUCCEEDED(hResult)) { hResult = NO_ERROR; if (bWillAcceptDrop) { *pdwEffect = (grfKeyState & MK_CONTROL ? DROPEFFECT_COPY : DROPEFFECT_MOVE); m_DropTarget.m_pIDataObject = pDataObject; m_DropTarget.m_pIDataObject->AddRef(); } } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DragOver Description: Implementation for IDropTarget::DragOver Arguments: See IDropTarget::DragOver in SDK. Returns: See IDropTarget::DragOver in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ) { if (NULL != m_DropTarget.m_pIDataObject) { *pdwEffect = (grfKeyState & MK_CONTROL ? DROPEFFECT_COPY : DROPEFFECT_MOVE); } else { *pdwEffect = DROPEFFECT_NONE; } return NO_ERROR; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DragLeave Description: Implementation for IDropTarget::DragLeave Arguments: See IDropTarget::DragLeave in SDK. Returns: See IDropTarget::DragLeave in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DragLeave( VOID ) { if (NULL != m_DropTarget.m_pIDataObject) { m_DropTarget.m_pIDataObject->Release(); } return NO_ERROR; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Drop Description: Implementation for IDropTarget::Drop Arguments: See IDropTarget::Drop in SDK. Returns: See IDropTarget::Drop in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Drop( IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect ) { HRESULT hResult = E_FAIL; *pdwEffect = DROPEFFECT_NONE; if (NULL != m_DropTarget.m_pIDataObject) { DragLeave(); // // Import the quota data from the data object. // Importer importer(*this); hResult = importer.Import(pDataObject); if (SUCCEEDED(hResult)) { if (grfKeyState & MK_CONTROL) { *pdwEffect = DROPEFFECT_COPY; } } } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetData Description: Implementation of IDataObject::GetData Arguments: See IDataObject::GetData in SDK. Returns: See IDataObject::GetData in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::GetData( FORMATETC *pFormatEtc, STGMEDIUM *pMedium ) { HRESULT hResult = E_INVALIDARG; #if DBG TCHAR szCFName[MAX_PATH] = { TEXT('\0') }; GetClipboardFormatName(pFormatEtc->cfFormat, szCFName, ARRAYSIZE(szCFName)); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::GetData\nDVA = %d CF = %d (%s) tymed = %d"), pFormatEtc->dwAspect, pFormatEtc->cfFormat, szCFName, pFormatEtc->tymed)); #endif // DEBUG if (NULL != pFormatEtc && NULL != pMedium) { // // See if we support the requested format. // hResult = m_pDataObject->IsFormatSupported(pFormatEtc); if (SUCCEEDED(hResult)) { // // Yep, we support it. Render the data. // hResult = m_pDataObject->RenderData(pFormatEtc, pMedium); } } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetDataHere Description: Implementation of IDataObject::GetDataHere Arguments: See IDataObject::GetData in SDK. Returns: E_NOTIMPL Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::GetDataHere( FORMATETC *pFormatEtc, STGMEDIUM *pMedium ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::GetDataHere"))); return E_NOTIMPL; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::QueryGetData Description: Implementation of IDataObject::QueryGetData Arguments: See IDataObject::QueryGetData in SDK. Returns: See IDataObject::QueryGetData in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::QueryGetData( FORMATETC *pFormatEtc ) { HRESULT hResult = E_UNEXPECTED; #if DBG TCHAR szCFName[MAX_PATH] = { TEXT('\0') }; GetClipboardFormatName(pFormatEtc->cfFormat, szCFName, ARRAYSIZE(szCFName)); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::QueryGetData\nDVA = %d CF = %d (%s) tymed = %d"), pFormatEtc->dwAspect, pFormatEtc->cfFormat, szCFName, pFormatEtc->tymed)); #endif // DEBUG if (NULL != pFormatEtc) { hResult = m_pDataObject->IsFormatSupported(pFormatEtc); } else hResult = E_INVALIDARG; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::GetCanonicalFormatEtc Description: Implementation of IDataObject::GetCanonicalFormatEtc Arguments: See IDataObject::GetCanonicalFormatEtc in SDK. Returns: See IDataObject::GetCanonicalFormatEtc in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::GetCanonicalFormatEtc( FORMATETC *pFormatEtcIn, FORMATETC *pFormatEtcOut ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::GetCanonicalFormatEtc"))); HRESULT hResult = E_INVALIDARG; if (NULL != pFormatEtcIn && NULL != pFormatEtcOut) { CopyMemory(pFormatEtcOut, pFormatEtcIn, sizeof(*pFormatEtcOut)); pFormatEtcOut->ptd = NULL; hResult = DATA_S_SAMEFORMATETC; } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::SetData Description: Implementation of IDataObject::SetData Arguments: See IDataObject::SetData in SDK. Returns: E_NOTIMPL. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::SetData( FORMATETC *pFormatEtc, STGMEDIUM *pMedium, BOOL fRelease ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::SetData"))); return E_NOTIMPL; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::EnumFormatEtc Description: Implementation of IDataObject::EnumFormatEtc Arguments: See IDataObject::EnumFormatEtc in SDK. Returns: See IDataObject::GetCanonicalFormatEtc in SDK. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::EnumFormatEtc( DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::EnumFormatEtc"))); HRESULT hResult = E_FAIL; EnumFORMATETC *pEnum = NULL; switch(dwDirection) { case DATADIR_GET: try { pEnum = new EnumFORMATETC(m_pDataObject->m_cFormats, m_pDataObject->m_rgFormats); hResult = pEnum->QueryInterface(IID_IEnumFORMATETC, (LPVOID *)ppenumFormatEtc); } catch(CAllocException& e) { *ppenumFormatEtc = NULL; hResult = E_OUTOFMEMORY; } break; case DATADIR_SET: // // SetData not implemented. // default: *ppenumFormatEtc = NULL; break; } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DAdvise Description: Implementation of IDataObject::DAdvise Arguments: See IDataObject::DAdvise in SDK. Returns: E_NOTIMPL Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::DAdvise( FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::DAdvise"))); return E_NOTIMPL; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DUnadvise Description: Implementation of IDataObject::DUnadvise Arguments: See IDataObject::DUnadvise in SDK. Returns: E_NOTIMPL Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::DUnadvise( DWORD dwConnection ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::DUnadvise"))); return E_NOTIMPL; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::EnumDAdvise Description: Implementation of IDataObject::EnumDAdvise Arguments: See IDataObject::EnumDAdvise in SDK. Returns: E_NOTIMPL Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// STDMETHODIMP DetailsView::EnumDAdvise( IEnumSTATDATA **ppenumAdvise ) { DBGTRACE((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - DataObject::EnumDAdvise"))); return E_NOTIMPL; } // // Number of clipboard formats supported by our data object. // Change this if you add/remove clipboard formats. There's an assert // in the DataObject ctor to ensure this. // const INT DetailsView::DataObject::CF_FORMATS_SUPPORTED = 14; // // Name of data stream in import/export and dragdrop streams. // LPCWSTR DetailsView::DataObject::SZ_EXPORT_STREAM_NAME = L"NT DISKQUOTA IMPORTEXPORT"; LPCTSTR DetailsView::DataObject::SZ_EXPORT_CF_NAME = TEXT("NT DISKQUTOA IMPORTEXPORT"); // // The version of export data produced by this module. This value // is written into the stream immediately following the GUID. If the // format of the export stream is changed, this value should be incremented. // const DWORD DetailsView::DataObject::EXPORT_STREAM_VERSION = 1; CLIPFORMAT DetailsView::DataObject::m_CF_Csv = 0; // Comma-separated fields format. CLIPFORMAT DetailsView::DataObject::m_CF_RichText = 0; // RTF format. CLIPFORMAT DetailsView::DataObject::m_CF_NtDiskQuotaExport = 0; // Internal fmt for import/export. CLIPFORMAT DetailsView::DataObject::m_CF_FileGroupDescriptor = 0; CLIPFORMAT DetailsView::DataObject::m_CF_FileContents = 0; /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::DataObject Description: Constructor for implementation of IDataObject. Arguments: DV - Reference to details view object that contains the data object. Returns: Nothing Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::DataObject::DataObject( DetailsView& DV ) : m_pStg(NULL), m_pStm(NULL), m_rgFormats(NULL), m_cFormats(CF_FORMATS_SUPPORTED), m_DV(DV) { DBGTRACE((DM_VIEW, DL_HIGH, TEXT("DetailsView::DataObject::DataObject"))); DBGPRINT((DM_VIEW, DL_HIGH, TEXT("\tthis = 0x%08X"), this)); // // Get additional clipboard formats we support. // if (0 == m_CF_Csv) { m_CF_Csv = (CLIPFORMAT)RegisterClipboardFormat(TEXT("Csv")); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - Csv CF = %d"), m_CF_Csv)); } if (0 == m_CF_RichText) { m_CF_RichText = (CLIPFORMAT)RegisterClipboardFormat(TEXT("Rich Text Format")); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - RTF CF = %d"), m_CF_RichText)); } if (0 == m_CF_NtDiskQuotaExport) { m_CF_NtDiskQuotaExport = (CLIPFORMAT)RegisterClipboardFormat(DataObject::SZ_EXPORT_CF_NAME); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - NtDiskQuotaExport = %d"), m_CF_NtDiskQuotaExport)); } if (0 == m_CF_FileGroupDescriptor) { m_CF_FileGroupDescriptor = (CLIPFORMAT)RegisterClipboardFormat(TEXT("FileGroupDescriptorW")); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - FileGroupDescriptorW = %d"), m_CF_FileGroupDescriptor)); } if (0 == m_CF_FileContents) { m_CF_FileContents = (CLIPFORMAT)RegisterClipboardFormat(TEXT("FileContents")); DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - FileContents = %d"), m_CF_FileContents)); } // // Create the array to hold the FORMATETC structures that describe the // formats we support. // m_rgFormats = new FORMATETC[m_cFormats]; // // Specify all formats and media we support. // Place the richest formats first in the array. // These are used to initialize the format enumerator when it // is requested. // UINT iFmt = 0; SetFormatEtc(m_rgFormats[iFmt++], m_CF_FileGroupDescriptor, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], m_CF_FileGroupDescriptor, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], m_CF_FileContents, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], m_CF_FileContents, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], m_CF_NtDiskQuotaExport, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], m_CF_NtDiskQuotaExport, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], m_CF_RichText, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], m_CF_RichText, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], m_CF_Csv, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], m_CF_Csv, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], CF_UNICODETEXT, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], CF_UNICODETEXT, TYMED_HGLOBAL); SetFormatEtc(m_rgFormats[iFmt++], CF_TEXT, TYMED_ISTREAM); SetFormatEtc(m_rgFormats[iFmt++], CF_TEXT, TYMED_HGLOBAL); // // If you hit this, you need to adjust CF_FORMATS_SUPPORTED to match // the number of SetFormatEtc statements above. // Otherwise, you just overwrote the m_rgFormats[] allocation. // DBGASSERT((iFmt == m_cFormats)); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::~DataObject Description: Destructor for implementation of IDataObject. Arguments: None. Returns: Nothing Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::DataObject::~DataObject( VOID ) { DBGTRACE((DM_VIEW, DL_HIGH, TEXT("DetailsView::DataObject::~DataObject"))); DBGPRINT((DM_VIEW, DL_HIGH, TEXT("\tthis = 0x%08X"), this)); delete[] m_rgFormats; if (NULL != m_pStg) m_pStg->Release(); // // NOTE: m_pStm is released by the data object's recipient // through ReleaseStgMedium. // } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::IsFormatSupported Description: Determines if a given format is supported by our implementation. Arguments: pFormatEtc - Address of FORMATETC structure containing request info. Returns: NO_ERROR - Supported. DV_E_TYMED - Medium type not supported. DV_E_FORMATETC - Clipboard format not supported. DV_E_DVASPECT - Device aspect not supported. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/10/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DataObject::IsFormatSupported( FORMATETC *pFormatEtc ) { DBGASSERT((NULL != pFormatEtc)); HRESULT hResult = E_FAIL; if (DVASPECT_CONTENT == pFormatEtc->dwAspect) { if (CF_TEXT == pFormatEtc->cfFormat || CF_UNICODETEXT == pFormatEtc->cfFormat || m_CF_RichText == pFormatEtc->cfFormat || m_CF_Csv == pFormatEtc->cfFormat || m_CF_NtDiskQuotaExport == pFormatEtc->cfFormat || m_CF_FileGroupDescriptor == pFormatEtc->cfFormat || m_CF_FileContents == pFormatEtc->cfFormat) { if (pFormatEtc->tymed & (TYMED_ISTREAM | TYMED_HGLOBAL)) { hResult = NO_ERROR; } else { hResult = DV_E_TYMED; } } else hResult = DV_E_FORMATETC; } else hResult = DV_E_DVASPECT; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::CreateRenderStream Description: Creates the OLE stream on which the data is to be rendered. Arguments: tymed - Desired medium type. ppStm - Address of IStream pointer variable to receive the stream ptr. Returns: NO_ERROR - Success. E_INVALIDARG - Invalid medium type. E_OUTOFMEMORY - Insufficient memory. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 07/30/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DataObject::CreateRenderStream( DWORD tymed, IStream **ppStm ) { HRESULT hResult = NOERROR; // // Create the Stream. // if (TYMED_ISTREAM & tymed) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - CreateRenderStream for ISTREAM"))); hResult = CreateStreamOnHGlobal(NULL, // Block of 0 bytes. TRUE, // Delete on release. ppStm); } else if (TYMED_HGLOBAL & tymed) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DRAGDROP - CreateRenderStream for HGLOBAL"))); hResult = CreateStreamOnHGlobal(NULL, // Block of 0 bytes. TRUE, // Delete on release. ppStm); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::RenderData [private] Description: Renders the data in the Details View onto the provided stream using the requested clipboard format. Arguments: pStm - Pointer to output stream. cf - Desired clipboard format. Returns: NO_ERROR - Success. E_FAIL - General failure. STG_E_WRITEFAULT - Media write error. STG_E_MEDIUMFULL - Insufficient space on medium. E_ACCESSDENIED - Write access denied. E_OUTOFMEMORY - Insufficient memory. E_UNEXPECTED - Unexpected exception. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 07/30/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DataObject::RenderData( IStream *pStm, CLIPFORMAT cf ) { HRESULT hResult = NOERROR; Renderer *pRenderer = NULL; DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("DetailsView::DataObject::RenderData on stream"))); try { // // Create the properly-typed rendering object for the requested format. // switch(cf) { case CF_TEXT: DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is CF_TEXT"))); pRenderer = new Renderer_TEXT(m_DV); break; case CF_UNICODETEXT: DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is CF_UNICODETEXT"))); pRenderer = new Renderer_UNICODETEXT(m_DV); break; default: if (m_CF_RichText == cf) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is RTF"))); pRenderer = new Renderer_RTF(m_DV); } else if (m_CF_Csv == cf) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is Csv"))); pRenderer = new Renderer_Csv(m_DV); } else if (m_CF_NtDiskQuotaExport == cf) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is Windows NT Disk Quota ImportExport Format"))); pRenderer = new Renderer_Export(m_DV); } else if (m_CF_FileGroupDescriptor == cf) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is FileGroupDescriptor"))); pRenderer = new Renderer_FileGroupDescriptor(m_DV); } else if (m_CF_FileContents == cf) { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Format is FileContents"))); pRenderer = new Renderer_FileContents(m_DV); } else { DBGPRINT((DM_DRAGDROP, DL_MID, TEXT("Unknown CF format (%d) requested"), cf)); } break; } if (NULL != pRenderer) { m_pStm->AddRef(); // Giving stream to renderer. // Will be released when renderer is destroyed. // // Render the information onto the stream. // This can throw FileError exceptions if we run out of disk // space or there's a disk write error. // pRenderer->Render(m_pStm); } } catch(CFileException& fe) { switch(fe.Reason()) { case CFileException::write: DBGERROR((TEXT("FileWrite error"))); hResult = E_FAIL; break; case CFileException::device: DBGERROR((TEXT("Disk error"))); hResult = STG_E_WRITEFAULT; break; case CFileException::diskfull: DBGERROR((TEXT("Disk Full error"))); hResult = STG_E_MEDIUMFULL; break; case CFileException::access: DBGERROR((TEXT("Access Denied error"))); hResult = E_ACCESSDENIED; break; default: DBGERROR((TEXT("Other error"))); hResult = E_FAIL; break; } } catch(CAllocException& e) { DBGERROR((TEXT("Insufficient memory"))); hResult = E_OUTOFMEMORY; } delete pRenderer; return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::RenderData [public] Description: Renders the data in the Details View onto the requested medium using the requested format. Arguments: pFormatEtc - Address of FORMATETC structure containing request info. pMedium - Address of STGMEDIUM structure containing requested medium info. Returns: NO_ERROR - Success. Can return many other OLE drag/drop error codes. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/10/96 Initial creation. BrianAu 07/30/97 Reworked. Moved some code into CreateRenderStream BrianAu and CreateAndRunRenderer. Makes the function more understandable. */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::DataObject::RenderData( FORMATETC *pFormatEtc, STGMEDIUM *pMedium ) { DBGASSERT((NULL != pFormatEtc)); DBGASSERT((NULL != pMedium)); DBGASSERT((SUCCEEDED(IsFormatSupported(pFormatEtc)))); HRESULT hResult = NOERROR; Renderer *pRenderer = NULL; DBGPRINT((DM_DRAGDROP, DL_HIGH, TEXT("DetailsView::DataObject::RenderData on medium"))); // // Create the stream we'll render the data onto. // hResult = CreateRenderStream(pFormatEtc->tymed, &m_pStm); if (SUCCEEDED(hResult)) { // // Render the data on the stream. // hResult = RenderData(m_pStm, pFormatEtc->cfFormat); if (SUCCEEDED(hResult)) { // // If we've made it here, we have a valid drag-drop report on m_pStm. // Now set up the stg medium to transfer the rendering. // if (TYMED_ISTREAM & pFormatEtc->tymed) { pMedium->pstm = m_pStm; pMedium->tymed = TYMED_ISTREAM; pMedium->pUnkForRelease = NULL; // Target will free the Stream. } else if (TYMED_HGLOBAL & pFormatEtc->tymed) { pMedium->tymed = TYMED_HGLOBAL; pMedium->pUnkForRelease = NULL; // Target will free the mem. hResult = GetHGlobalFromStream(m_pStm, &pMedium->hGlobal); } else { // // Call to CreateRenderStream() should have failed if we // hit this. // DBGASSERT((0)); } } if (FAILED(hResult)) { DBGERROR((TEXT("DRAGDROP - Error 0x%08X rendering data"), hResult)); // // Something failed after the stream was created. // The DetailsView::DataObject dtor DOES NOT release it. // It assumes success and assumes the recipient will release it. // Release the stream. // m_pStm->Release(); // // These two statements are redundant since pMedium contains a union. // I didn't want any more if(STREAM) else if (HGLOBAL) logic. In case // there's ever a change in structure, this will ensure both possible // medium types are null'd out. // pMedium->pstm = NULL; pMedium->hGlobal = NULL; } } else { DBGERROR((TEXT("DRAGDROP - Error 0x%08X creating stream"), hResult);) } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::SetFormatEtc [static] Description: Helper function to set the members of a FORMATETC structure. Uses defaults for least used members. Arguments: See SDK description of FORMATETC. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::SetFormatEtc( FORMATETC& fe, CLIPFORMAT cfFormat, DWORD tymed, DWORD dwAspect, DVTARGETDEVICE *ptd, LONG lindex ) { fe.cfFormat = cfFormat; fe.dwAspect = dwAspect; fe.ptd = ptd; fe.tymed = tymed; fe.lindex = lindex; }; /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::WideToAnsi Description: Helper function to convert a wide character string to ANSI. The caller must delete the return buffer. Arguments: pszTextW - UNICODE string to convert. Returns: Address of ANSI string. Caller must delete this. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LPSTR DetailsView::DataObject::WideToAnsi( LPCWSTR pszTextW ) { DBGASSERT((NULL != pszTextW)); INT cchTextA = WideCharToMultiByte(CP_ACP, 0, pszTextW, -1, NULL, 0, NULL, NULL); LPSTR pszTextA = new CHAR[cchTextA]; WideCharToMultiByte(CP_ACP, 0, pszTextW, -1, pszTextA, cchTextA, NULL, NULL); return pszTextA; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::Renderer::Render Description: Render the selected items in the listview on a stream. Calls virtual functions defined by derived classes to produce the required format. Arguments: pStm - Address of IStream on which to write output. Assumes that this pointer has been AddRef'd by the caller. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer::Render( IStream *pStm ) { HRESULT hResult = NO_ERROR; WCHAR szText[MAX_PATH]; INT rgColIds[DetailsView::idCol_Last]; INT cCols = m_DV.GetColumnIds(rgColIds, ARRAYSIZE(rgColIds)); INT cRows = m_DV.GetReportRowCount(); INT i, j; INT iRow = -1; DBGASSERT((NULL != pStm)); m_Stm.SetStream(pStm); // // Start the report. // Begin(cRows, cCols); // // Add the report title. // m_DV.GetReportTitle(szText, ARRAYSIZE(szText)); AddTitle(szText); // // Add the report column headers. // BeginHeaders(); for (i = 0; i < cCols; i++) { m_DV.GetReportColHeader(rgColIds[i], szText, ARRAYSIZE(szText)); AddHeader(szText); AddHeaderSep(); } EndHeaders(); // // Add the report row/col data. // for (i = 0; i < cRows; i++) { iRow = m_DV.GetNextSelectedItemIndex(iRow); DBGASSERT((-1 != iRow)); BeginRow(); for (j = 0; j < cCols; j++) { AddRowColData(iRow, rgColIds[j]); AddRowColSep(); } EndRow(); } // // Terminate the report. // End(); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::Renderer::Stream::Stream Description: Constructor for the renderer's private stream object. The object is used to encapsulate stream write operations in overloaded type-sensitive member functions. Arguments: pStm - Address of IStream associated with the object. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::DataObject::Renderer::Stream::Stream( IStream *pStm ) : m_pStm(pStm) { #ifdef CLIPBOARD_DEBUG_OUTPUT m_pStgDbgOut = NULL; m_pStmDbgOut = NULL; StgCreateDocfile(TEXT("\\DskquotaClipboard.Out"), STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &m_pStgDbgOut); if (NULL != m_pStgDbgOut) { m_pStgDbgOut->CreateStream(TEXT("Clipboard Data"), STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStmDbgOut); } #endif // CLIPBOARD_DEBUG_OUTPUT } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::Renderer::Stream::~Stream Description: Destructor for the renderer's private stream object. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::DataObject::Renderer::Stream::~Stream(VOID) { if (NULL != m_pStm) m_pStm->Release(); #ifdef CLIPBOARD_DEBUG_OUTPUT if (NULL != m_pStmDbgOut) m_pStmDbgOut->Release(); if (NULL != m_pStgDbgOut) m_pStgDbgOut->Release(); #endif // CLIPBOARD_DEBUG_OUTPUT } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::Renderer::Stream::SetStream Description: Associates an IStream pointer with the stream object. Releases an existing pointer if one was already assigned. Arguments: pStm - Address of new IStream to associate with stream object. Caller must AddRef IStream pointer before passing to this function. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer::Stream::SetStream( IStream *pStm ) { DBGASSERT((NULL != pStm)); if (NULL != m_pStm) m_pStm->Release(); m_pStm = pStm; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::DataObject::Renderer::Stream::Write Description: Set of overloaded functions to handle the writing of various types of data to the stream. Arguments: pbData - Address of BYTE buffer for source data. cbData - Number of bytes in pbData[] pszTextA - Ansi text string for source data. pszTextW - Wide character text string for source data. bData - Byte to write to stream. chDataW - Wide character to write to stream. chDataA - Ansi character to write to stream. dwData - DWORD-type data to write to stream. dblData - double-type data to write to stream. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer::Stream::Write( LPBYTE pbData, UINT cbData ) { DBGASSERT((NULL != pbData)); ULONG cbWritten = 0; HRESULT hr; hr = m_pStm->Write(pbData, cbData, &cbWritten); if (S_OK != hr) { DBGERROR((TEXT("Error 0x%08X writing to output stream."), hr)); CFileException::reason reason = CFileException::write; switch(hr) { case STG_E_ACCESSDENIED: reason = CFileException::access; break; case STG_E_MEDIUMFULL: reason = CFileException::diskfull; break; case STG_E_WRITEFAULT: reason = CFileException::device; break; default: // // Use default value. // break; } throw CFileException(reason, TEXT(""), 0); } #ifdef CLIPBOARD_DEBUG_OUTPUT cbWritten = 0; if (S_OK != m_pStmDbgOut->Write(pbData, cbData, &cbWritten)) throw CFileException(CFileException::write, TEXT(""), 0); #endif // CLIPBOARD_DEBUG_OUTPUT } VOID DetailsView::DataObject::Renderer::Stream::Write( LPCWSTR pszTextW ) { Write((LPBYTE)pszTextW, lstrlenW(pszTextW) * sizeof(WCHAR)); } VOID DetailsView::DataObject::Renderer::Stream::Write( LPCSTR pszTextA ) { Write((LPBYTE)pszTextA, lstrlenA(pszTextA) * sizeof(CHAR)); } VOID DetailsView::DataObject::Renderer::Stream::Write( BYTE bData ) { Write((LPBYTE)&bData, sizeof(bData)); } VOID DetailsView::DataObject::Renderer::Stream::Write( WCHAR chDataW ) { Write((LPBYTE)&chDataW, sizeof(chDataW)); } VOID DetailsView::DataObject::Renderer::Stream::Write( CHAR chDataA ) { Write((LPBYTE)&chDataA, sizeof(chDataA)); } VOID DetailsView::DataObject::Renderer::Stream::Write( DWORD dwData ) { Write((LPBYTE)&dwData, sizeof(dwData)); } VOID DetailsView::DataObject::Renderer::Stream::Write( double dblData ) { Write((LPBYTE)&dblData, sizeof(dblData)); } /////////////////////////////////////////////////////////////////////////////// // // The following section of code contains the different implementations of // the virtual rendering functions that make each type of rendering object // unique. Since they're pretty self-explanatory, I haven't commented each // function. It should be obvious as to what they do. // I have separated each rendering-type section with a banner comment for // readability. [brianau] // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // CF_UNICODETEXT /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer_UNICODETEXT::AddTitle( LPCTSTR pszTitle ) { m_Stm.Write(pszTitle); m_Stm.Write(L'\r'); m_Stm.Write(L'\n'); m_Stm.Write(L'\r'); m_Stm.Write(L'\n'); } VOID DetailsView::DataObject::Renderer_UNICODETEXT::AddRowColData( INT iRow, INT idCol ) { WCHAR szText[MAX_PATH]; LV_REPORT_ITEM item; szText[0] = 0; item.fType = LVRI_TEXT; // Want text data. item.pszText = szText; item.cchMaxText = ARRAYSIZE(szText); m_DV.GetReportItem(iRow, idCol, &item); m_Stm.Write(szText); } /////////////////////////////////////////////////////////////////////////////// // CF_TEXT /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer_TEXT::AddTitle( LPCWSTR pszTitleW ) { array_autoptr ptrTitleA(DataObject::WideToAnsi(pszTitleW)); m_Stm.Write(ptrTitleA); m_Stm.Write('\r'); m_Stm.Write('\n'); m_Stm.Write('\r'); m_Stm.Write('\n'); } VOID DetailsView::DataObject::Renderer_TEXT::AddHeader( LPCWSTR pszHeaderW ) { array_autoptr ptrHeaderA(DataObject::WideToAnsi(pszHeaderW)); m_Stm.Write(ptrHeaderA); } VOID DetailsView::DataObject::Renderer_TEXT::AddRowColData( INT iRow, INT idCol ) { WCHAR szTextW[MAX_PATH]; LV_REPORT_ITEM item; szTextW[0] = 0; item.fType = LVRI_TEXT; // Want text data. item.pszText = szTextW; item.cchMaxText = ARRAYSIZE(szTextW); m_DV.GetReportItem(iRow, idCol, &item); array_autoptr ptrTextA(DataObject::WideToAnsi(szTextW)); m_Stm.Write(ptrTextA); } /////////////////////////////////////////////////////////////////////////////// // RTF (Rich Text) /////////////////////////////////////////////////////////////////////////////// static const INT TWIPS_PER_PT = 20; static const INT PTS_PER_INCH = 72; static const INT TWIPS_PER_INCH = PTS_PER_INCH * TWIPS_PER_PT; static const INT COL_WIDTH_TWIPS = TWIPS_PER_INCH * 5 / 4; // 1 1/4 inches. // // Converts all single backslashes to double backslashes. // Literal backslashes in RTF must be "\\". // Caller must delete[] the returned buffer. // LPSTR DetailsView::DataObject::Renderer_RTF::DoubleBackslashes( LPSTR pszSrc ) { DBGASSERT((NULL != pszSrc)); // // Create new string for output. Size must be double. Every char // could be '\'. // LPSTR pszFormatted = new CHAR[(lstrlenA(pszSrc) * 2) + 1]; LPSTR pszDest = pszFormatted; while('\0' != *pszSrc) { if ('\\' == *pszSrc) *pszDest++ = *pszSrc; *pszDest++ = *pszSrc++; } *pszDest = *pszSrc; // Pick up NUL terminator. return pszFormatted; } VOID DetailsView::DataObject::Renderer_RTF::Begin( INT cRows, INT cCols ) { m_cCols = cCols; m_Stm.Write("{\\rtf1 \\sect\\sectd\\lndscpsxn \\par\\pard\\plain "); } VOID DetailsView::DataObject::Renderer_RTF::AddTitle( LPCWSTR pszTitleW ) { array_autoptr ptrTempA(DataObject::WideToAnsi(pszTitleW)); array_autoptr ptrTitleA(DoubleBackslashes(ptrTempA)); // cvt '\' to "\\" m_Stm.Write(ptrTitleA); } VOID DetailsView::DataObject::Renderer_RTF::BeginHeaders( VOID ) { m_Stm.Write(" \\par \\par "); // Hdr preceded by empty row. BeginHeaderOrRow(); // Add stuff common to hdr and data rows. m_Stm.Write(" \\trhdr "); // Hdr at top of each page. AddCellDefs(); // Cell size definitions. } VOID DetailsView::DataObject::Renderer_RTF::AddCellDefs( VOID ) { char szText[80]; INT cxTwips = 0; for (INT i = 0; i < m_cCols; i++) { cxTwips += COL_WIDTH_TWIPS; wnsprintfA(szText, ARRAYSIZE(szText), "\\cellx%d", cxTwips); m_Stm.Write(szText); } m_Stm.Write(' '); } // // Stuff common to both header row and data rows. // VOID DetailsView::DataObject::Renderer_RTF::BeginHeaderOrRow( VOID ) { m_Stm.Write("\\trowd \\pard \\intbl "); } VOID DetailsView::DataObject::Renderer_RTF::AddHeader( LPCWSTR pszHeaderW ) { array_autoptr ptrHeaderA(DataObject::WideToAnsi(pszHeaderW)); // // No need to convert '\' to "\\". No // backslashes in our header text. // m_Stm.Write(ptrHeaderA); } VOID DetailsView::DataObject::Renderer_RTF::AddRowColData( INT iRow, INT idCol ) { WCHAR szTextW[MAX_PATH]; LV_REPORT_ITEM item; szTextW[0] = 0; item.fType = LVRI_TEXT; // Want text data. item.pszText = szTextW; item.cchMaxText = ARRAYSIZE(szTextW); m_DV.GetReportItem(iRow, idCol, &item); array_autoptr ptrTempA(DataObject::WideToAnsi(szTextW)); array_autoptr ptrTextA(DoubleBackslashes(ptrTempA)); // cvt '\' to "\\" m_Stm.Write(ptrTextA); } /////////////////////////////////////////////////////////////////////////////// // Private import/export format /////////////////////////////////////////////////////////////////////////////// // // Assumes that caller AddRef'd IStream pointer. // VOID DetailsView::DataObject::Renderer_Export::Render( IStream *pStm ) { HRESULT hResult = NO_ERROR; INT cRows = m_DV.GetReportRowCount(); INT iRow = -1; DBGASSERT((NULL != pStm)); m_Stm.SetStream(pStm); Begin(cRows, 0); // // Add the export data records. // for (INT i = 0; i < cRows; i++) { iRow = m_DV.GetNextSelectedItemIndex(iRow); DBGASSERT((-1 != iRow)); AddBinaryRecord(iRow); } // // Terminate the report. // End(); } VOID DetailsView::DataObject::Renderer_Export::Begin( INT cRows, INT cCols ) { // // The stream header contains a GUID as a unique identifier followed // by a version number. // m_Stm.Write((LPBYTE)&GUID_NtDiskQuotaStream, sizeof(GUID_NtDiskQuotaStream)); m_Stm.Write(DataObject::EXPORT_STREAM_VERSION); m_Stm.Write((DWORD)cRows); } VOID DetailsView::DataObject::Renderer_Export::AddBinaryRecord( INT iRow ) { INT cbRecord = m_DV.GetReportBinaryRecordSize(iRow); array_autoptr ptrRecord(new BYTE[cbRecord]); if (NULL != ptrRecord.get()) { if (m_DV.GetReportBinaryRecord(iRow, ptrRecord, cbRecord)) { m_Stm.Write(ptrRecord, cbRecord); } } } /////////////////////////////////////////////////////////////////////////////// // CF "FileGroupDescriptor" // // /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::DataObject::Renderer_FileGroupDescriptor::Begin( INT cRows, INT cCols ) { // // Build a name for the file we'll create. // // Vol label? Filename // ---------- ------------------------------------------------------- // Yes "Disk Quota Settings for Volume 'VOL_LABEL'" // No "Disk Quota Settings for Unlabeled Volume SN 8AB1-DE23" // // The serial-number format is gross but without a label, we don't have // any other distinguishing feature for the volume. I'd use the // display name from the CVolumeID object but in the mounted volume // case, it contains backslashes and a colon; both invalid buried in // a filename. // TCHAR szLabel[MAX_VOL_LABEL] = { TEXT('\0') }; DWORD dwSerialNumber = 0; GetVolumeInformation(m_DV.GetVolumeID().ForParsing(), szLabel, ARRAYSIZE(szLabel), &dwSerialNumber, NULL, NULL, NULL, 0); CString strFileName; if (TEXT('\0') != szLabel[0]) { // // Volume has a label. // strFileName.Format(g_hInstDll, IDS_EXPORT_STREAM_FILENAME_TEMPLATE, szLabel); } else { // // No volume label. // strFileName.Format(g_hInstDll, IDS_EXPORT_STREAM_FILENAME_TEMPLATE_VOLSN, HIWORD(dwSerialNumber), LOWORD(dwSerialNumber)); } // // Create a file group descriptor containing the name we want the // shell to use for the file. The descriptor contains one file // description. That description just contains the file name. // All other members are initialized to 0. // FILEGROUPDESCRIPTORW desc; ZeroMemory(&desc, sizeof(desc)); desc.cItems = 1; lstrcpyn(desc.fgd[0].cFileName, strFileName, ARRAYSIZE(desc.fgd[0].cFileName)); // // Write the file group descriptor to the renderer's stream. // m_Stm.Write((LPBYTE)&desc, sizeof(desc)); } /////////////////////////////////////////////////////////////////////////////// /* Function: LVSelection::Add Description: Add a user pointer and listview item index to a listview selection object. This object is used to transfer the notion of a "selection" to some function. Arguments: pUser - Address of IDiskQuotaUser interface for a selected user object. iItem - Index of selected item in the listview. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/10/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID LVSelection::Add( PDISKQUOTA_USER pUser, INT iItem ) { DBGASSERT((NULL != pUser)); ListEntry entry; entry.pUser = pUser; entry.iItem = iItem; m_List.Append((LPVOID)&entry); } /////////////////////////////////////////////////////////////////////////////// /* Function: LVSelection::Retrieve Description: Retrieves a user pointer and listview item index from a listview selection object. Arguments: i - Index of item. Use the Count() method to determine how many items are in the selection object. ppUser - Address of an interface pointer variable to receive the IDiskQuotaUser interface for the user object at index 'i'. piItem - Address of integer variable to receive the Listview item index of the object at index 'i'. Returns: TRUE = Returned information is valid. FALSE = Couldn't retrieve entry 'i'. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 09/10/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL LVSelection::Retrieve( INT i, PDISKQUOTA_USER *ppUser, INT *piItem ) { ListEntry entry; if (m_List.Retrieve((LPVOID)&entry, i)) { if (NULL != ppUser) *ppUser = entry.pUser; if (NULL != piItem) *piItem = entry.iItem; return TRUE; } return FALSE; } /////////////////////////////////////////////////////////////////////////////// /* Function: ColumnMap::ColumnMap Function: ColumnMap::~ColumnMap Description: Constructor and Destructor. Creates/Destroys a column map. The column map is used to map column ID's (known to the Details View) to listview subitem indices. It is needed to support the addition and deletion of the folder name column. Arguments: cMapSize - Number of entries in the map. Should be the max number of columns possible in the listview. Returns: Nothing. Exceptions: OutOfMemory. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// ColumnMap::ColumnMap( UINT cMapSize ) : m_pMap(NULL), m_cMapSize(cMapSize) { // // Can throw OutOfMemory. // m_pMap = new INT[m_cMapSize]; FillMemory(m_pMap, m_cMapSize * sizeof(m_pMap[0]), (BYTE)-1); } ColumnMap::~ColumnMap( VOID ) { if (NULL != m_pMap) delete[] m_pMap; } /////////////////////////////////////////////////////////////////////////////// /* Function: ColumnMap::SubItemToId Description: Returns a column ID given a listview subitem index. Arguments: iSubItem - 0-based subitem index of the item to be mapped. Returns: Column ID corresponding to subitem. -1 if the subitem is invalid. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT ColumnMap::SubItemToId( INT iSubItem ) const { DBGASSERT((iSubItem >= 0 && iSubItem < (INT)m_cMapSize)); return *(m_pMap + iSubItem); } /////////////////////////////////////////////////////////////////////////////// /* Function: ColumnMap::IdToSubItem Description: Returns a listview subitem index given a column ID. Arguments: iColId - ID of column. i.e. idCol_Name, idCol_Folder etc. Returns: Listview subitem index. -1 if the column is not currently visible. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/09/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT ColumnMap::IdToSubItem( INT iColId ) const { for (INT i = 0; i < (INT)m_cMapSize; i++) { if (SubItemToId(i) == iColId) return i; } return -1; } /////////////////////////////////////////////////////////////////////////////// /* Function: ColumnMap::RemoveId Description: Removes a mapping for a given listview subitem index. Arguments: iSubItem - 0-based subitem index of the item to be removed. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/09/96 Initial creation. BrianAu 11/30/96 Fixed off-by-one error. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID ColumnMap::RemoveId( INT iSubItem ) { DBGASSERT((iSubItem >= 0 && iSubItem < (INT)m_cMapSize)); for (INT i = iSubItem; i < (INT)m_cMapSize - 1; i++) *(m_pMap + i) = *(m_pMap + i + 1); *(m_pMap + m_cMapSize - 1) = -1; } /////////////////////////////////////////////////////////////////////////////// /* Function: ColumnMap::InsertId Description: Adds a mapping for a given listview subitem index. The mapping is added at the iSubItem location in the map. All subsequent item mappings are shifted down one place. This is analogous to inserting a column into the listview. Arguments: iSubItem - 0-based subitem index of the item to be removed. iColId - ID of column. i.e. idCol_Name, idCol_Folder etc. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 10/09/96 Initial creation. BrianAu 11/30/96 Fixed off-by-one error. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID ColumnMap::InsertId( INT iSubItem, INT iColId ) { DBGASSERT((iSubItem >= 0 && iSubItem < (INT)m_cMapSize)); for (INT i = m_cMapSize-1; i > iSubItem; i--) *(m_pMap + i) = *(m_pMap + i - 1); *(m_pMap + iSubItem) = iColId; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::Finder Description: Constructs a user finder object. The user finder coordinates the activities of finding an item in the details listview. Arguments: DetailsView - Reference to the details view object. cMaxMru - Maximum entries allowed in the most-recently-used list. This list is maintained in the dropdown combo box in the view's toolbar. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// DetailsView::Finder::Finder( DetailsView& DetailsView, INT cMaxMru ) : m_DetailsView(DetailsView), m_hwndToolbarCombo(NULL), m_cMaxComboEntries(cMaxMru), m_pfnOldToolbarComboWndProc(NULL) { // // Nothing more to do. // } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::ConnectToolbarCombo Description: Connects the finder object to the combo box in the view's toolbar. This is necessary because the finder object coordinates the contents of the toolbar combo box with the contents of the combo box in the "Find User" dialog. When you enter a name in one of the boxes, it is automatically added to the other so they appear to be in sync. Also subclasses the edit control within the combo box. This is required so that we can intercept VK_RETURN and find the record when the user presses [Return]. Also adds the toolbar combo box as a "tool" to the toolbar. This is so we can get a tooltip for the combo. Arguments: hwndToolbarCombo - Hwnd of combo box in view's toolbar. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::Finder::ConnectToolbarCombo( HWND hwndToolbarCombo ) { m_hwndToolbarCombo = hwndToolbarCombo; // // Add the combo box to the toolbar's list of "tools". // This will allow us to get a tooltip for the combo box. // This code assumes that the combo is a child of the toolbar. // HWND hwndToolbar = GetParent(hwndToolbarCombo); HWND hwndMain = GetParent(hwndToolbar); HWND hwndTooltip = (HWND)SendMessage(hwndToolbar, TB_GETTOOLTIPS, 0, 0); if (NULL != hwndTooltip) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND | TTF_CENTERTIP | TTF_SUBCLASS; ti.lpszText = (LPTSTR)IDS_TOOLBAR_COMBO; ti.hwnd = hwndMain; ti.uId = (UINT_PTR)hwndToolbarCombo; ti.hinst = g_hInstDll; SendMessage(hwndTooltip, TTM_ADDTOOL, 0, (LPARAM)&ti); } // // Subclass the combo box so we can intercept VK_ENTER. // This is done so we can respond to VK_ENTER. Normally combo boxes // don't respond to this keystroke. // HWND hwndComboEdit = NULL; // // The combo box has two children... an edit control and a listbox control. // Find the edit control. // for (HWND hwndChild = GetTopWindow(m_hwndToolbarCombo); hwndChild != NULL; hwndChild = GetNextWindow(hwndChild, GW_HWNDNEXT)) { TCHAR szClassName[20] = { TEXT('\0') }; GetClassName(hwndChild, szClassName, ARRAYSIZE(szClassName)); if (0 == lstrcmpi(szClassName, TEXT("Edit"))) { hwndComboEdit = hwndChild; break; } } if (NULL != hwndComboEdit) { // // Store the address of the Finder object in the combo box's // userdata. This is so the subclass WndProc (a static function) // can access the finder object. // SetWindowLongPtr(hwndComboEdit, GWLP_USERDATA, (INT_PTR)this); // // Subclass the combo box's edit control. // m_pfnOldToolbarComboWndProc = (WNDPROC)GetWindowLongPtr(hwndComboEdit, GWLP_WNDPROC); SetWindowLongPtr(hwndComboEdit, GWLP_WNDPROC, (INT_PTR)DetailsView::Finder::ToolbarComboSubClassWndProc); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::InvokeFindDialog Description: Display the "Find User" dialog. Arguments: hwndParent - Parent for the dialog. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::Finder::InvokeFindDialog( HWND hwndParent ) { DialogBoxParam(g_hInstDll, MAKEINTRESOURCE(IDD_FINDUSER), hwndParent, DetailsView::Finder::DlgProc, (LPARAM)this); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::DlgProc Description: DlgProc for the "Find User" dialog. Arguments: Standard DlgProc arguments. Returns: Standard DlgProc return values. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// INT_PTR CALLBACK DetailsView::Finder::DlgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { // // Get the finder object's "this" ptr from the window's userdata. // Finder *pThis = (Finder *)GetWindowLongPtr(hwnd, DWLP_USER); switch(uMsg) { case WM_INITDIALOG: { // // Save the "this" ptr in the window's userdata. // pThis = (Finder *)lParam; SetWindowLongPtr(hwnd, DWLP_USER, (INT_PTR)pThis); // // Set the height of the combo in the dialog. // Not sure why, but DevStudio's dialog editor won't let me // do this. Use the same height value we use for the combo // in the toolbar. It's the same contents so the height // should be the same. // HWND hwndCombo = GetDlgItem(hwnd, IDC_CMB_FINDUSER); RECT rcCombo; GetClientRect(hwndCombo, &rcCombo); SetWindowPos(hwndCombo, NULL, 0, 0, rcCombo.right - rcCombo.left, CY_TOOLBAR_COMBO, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); // // Fill the dialog's combo with entries from the toolbar // combo. The toolbar's combo box contains the MRU for finding users. // pThis->FillDialogCombo(pThis->m_hwndToolbarCombo, GetDlgItem(hwnd, IDC_CMB_FINDUSER)); return 1; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: // // User pressed OK button or [Enter]. // DBGASSERT((NULL != pThis)); if (!pThis->UserNameEntered(GetDlgItem(hwnd, IDC_CMB_FINDUSER))) { // // Record not found so don't close dialog. // UserNameEntered() will display UI to tell the user // that the name wasn't found. Leave the dialog open // so user can retry with a new name. // break; } // // Fall through... // case IDCANCEL: // // User pressed Cancel button or [ESC]. // EndDialog(hwnd, 0); break; default: break; } break; }; return 0; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::FillDialogCombo Description: Fill the combo box in the dialog with the contents from a second combo box. Arguments: hwndComboSrc - Hwnd of source combo containing text strings. hwndComboDest - Hwnd of combo where strings will be copied to. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::Finder::FillDialogCombo( HWND hwndComboSrc, HWND hwndComboDest ) { // // Clear out the destination combo. // SendMessage(hwndComboDest, CB_RESETCONTENT, 0, 0); // // Copy all contents of the source combo to the destination combo. // INT cItems = (INT)SendMessage(hwndComboSrc, CB_GETCOUNT, 0, 0); if (CB_ERR != cItems) { for (INT i = 0; i < cItems; i++) { LPTSTR pszName = NULL; INT cchName = (INT)SendMessage(hwndComboSrc, CB_GETLBTEXTLEN, i, 0); pszName = new TCHAR[cchName + 1]; if (NULL != pszName) { // // Remove item from the source combo at index [i] and append // it to the destination combo. // SendMessage(hwndComboSrc, CB_GETLBTEXT, i, (LPARAM)pszName); SendMessage(hwndComboDest, CB_ADDSTRING, 0, (LPARAM)pszName); delete[] pszName; } } } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::ToolbarComboSubclassWndProc Description: Subclass window procedure for the "Edit" control that is part of the combo contained in the view's toolbar. We subclass this control so that we can intercept VK_RETURN and handle it. The standard combo box just beeps when you press [Enter] in it's edit control. Arguments: Standard WndProc arguments. Returns: Standard WndProc return values. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK DetailsView::Finder::ToolbarComboSubClassWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { // // Get finder object's "this" ptr from window's userdata. // Finder *pThis = (Finder *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch(uMsg) { case WM_CHAR: switch(wParam) { case VK_RETURN: { // // Tell the finder that a user name was entered in the // combo box. Pass the hwnd of the combo from which the // name was entered. Since this message is for the // subclassed edit control (child of the combo), the // parent is the combo box itself. // DBGASSERT((NULL != pThis)); HWND hwndCombo = GetParent(hwnd); if (pThis->UserNameEntered(hwndCombo)) { // // Record found in view. // Set focus to the main view. // If not found, focus should just stay with the combo // so user can enter another name. // HWND hwndToolbar = GetParent(hwndCombo); SetFocus(GetParent(hwndToolbar)); } else { // // Not found in listview. Focus remains in the combo box // so user can try again with a new name. // SetFocus(hwndCombo); } // // Swallow up the VK_RETURN. // Otherwise, the combo box control beeps. // return 0; } case VK_ESCAPE: { // // Set focus to the main window which will set focus to the // listview. This gives the keyboard-only user a way to // get back out of the combo box. // HWND hwndCombo = GetParent(hwnd); HWND hwndToolbar = GetParent(hwndCombo); SetFocus(GetParent(hwndToolbar)); // // Swallow VK_ESCAPE so combo box doesn't beep. // return 0; } } break; default: break; } return CallWindowProc(pThis->m_pfnOldToolbarComboWndProc, hwnd, uMsg, wParam, lParam); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::AddNameToCombo Description: Add a name string to one of the Find User combo boxes. If the item already exists in the list, it is moved to the top of the list. If the item is not in the list, it is added at the top of the list. If the addition of the new item causes the list's entry count to exceed a specified maximum value, the last item in the list is removed. Arguments: hwndCombo - Hwnd for the combo box to which the name is added. pszName - Address of name string to add. cMaxEntries - Maximum number of entries allowed in combo box. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// VOID DetailsView::Finder::AddNameToCombo( HWND hwndCombo, LPCTSTR pszName, INT cMaxEntries ) { if (NULL != pszName && TEXT('\0') != *pszName) { // // See if the item already exists in the list. // INT iItemToDelete = (INT)SendMessage(hwndCombo, CB_FINDSTRING, (WPARAM)-1, (LPARAM)pszName); if (CB_ERR == iItemToDelete) { // // Item is not already in the list. Need to add it. // If the list is full, we'll have to drop one off of the end. // INT cItems = (INT)SendMessage(hwndCombo, CB_GETCOUNT, 0, 0); if (CB_ERR != cItems && 0 < cItems && cItems >= cMaxEntries) { iItemToDelete = cItems - 1; } } if (-1 != iItemToDelete) { // // Need to delete an existing item for one of these reasons: // // 1. Promoting an existing item to the head of the list. // Delete it from it's previous location. // 2. Dropping last item from list. // SendMessage(hwndCombo, CB_DELETESTRING, iItemToDelete, 0); } // // Add the new item at the head of the list. // SendMessage(hwndCombo, CB_INSERTSTRING, 0, (LPARAM)pszName); } } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Finder::UserNameEntered Description: A name has been entered from one of the combo boxes. 1. Retrieve the name from the combo. 2. See if it's in the listview and if it is, the listview ensures the item is visible and selects it. 3. Update the toobar combo's list with the new item. This is our MRU list. Add a name string to one of the Find User combo boxes. If the item already exists in the list, it is moved to the top of the list. If the item is not in the list, it is added at the top of the list. If the addition of the new item causes the list's entry count to exceed a specified maximum value, the last item in the list is removed. Arguments: hwndCombo - Hwnd for the combo box to which the name is added. pszName - Address of name string to add. cMaxEntries - Maximum number of entries allowed in combo box. Returns: TRUE = User was found in listview. FALSE = User was not found in listview. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// BOOL DetailsView::Finder::UserNameEntered( HWND hwndCombo ) { TCHAR szName[MAX_PATH] = { TEXT('\0') }; BOOL bUserFoundInListView = FALSE; // // Get the name from the combo edit control. // if (0 < SendMessage(hwndCombo, WM_GETTEXT, (WPARAM)ARRAYSIZE(szName), (LPARAM)szName)) { // // Tell the details view object to highlight this name. // bUserFoundInListView = m_DetailsView.GotoUserName(szName); if (bUserFoundInListView) { // // Add the name to the toolbar combo's listbox. This becomes // our MRU list. Also make sure the visible name in the combo's // edit control is the one last entered. May have been entered // through the "Find User" dialog. // AddNameToCombo(m_hwndToolbarCombo, szName, m_cMaxComboEntries); SendMessage(m_hwndToolbarCombo, WM_SETTEXT, 0, (LPARAM)szName); } else { // // Display a message box to the user stating that the user couldn't // be found in the listview. // CString strMsg(g_hInstDll, IDS_USER_NOT_FOUND_IN_LISTVIEW, szName); DiskQuotaMsgBox(hwndCombo, (LPCTSTR)strMsg, IDS_TITLE_DISK_QUOTA, MB_ICONEXCLAMATION); } } return bUserFoundInListView; } DetailsView::Importer::Importer( DetailsView& DV ) : m_DV(DV), m_bUserCancelled(FALSE), m_bPromptOnReplace(TRUE), m_dlgProgress(IDD_PROGRESS, IDC_PROGRESS_BAR, IDC_TXT_PROGRESS_DESCRIPTION, IDC_TXT_PROGRESS_FILENAME), m_hwndParent(m_DV.m_hwndMain), m_cImported(0) { if (m_dlgProgress.Create(g_hInstDll, m_hwndParent)) { EnableWindow(m_hwndParent, FALSE); m_dlgProgress.SetDescription(MAKEINTRESOURCE(IDS_PROGRESS_IMPORTING)); m_dlgProgress.Show(); } // // Clear any previous undo actions from the view's undo list. // m_DV.m_pUndoList->Clear(); } DetailsView::Importer::~Importer( VOID ) { Destroy(); } VOID DetailsView::Importer::Destroy( VOID ) { if (NULL != m_hwndParent && !IsWindowEnabled(m_hwndParent)) { EnableWindow(m_hwndParent, TRUE); } m_dlgProgress.Destroy(); // // Update the view's "Undo" menu and toolbar button based on the current // contents of the undo list. // m_DV.EnableMenuItem_Undo(0 != m_DV.m_pUndoList->Count()); } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [IDataObject *] Description: Imports user quota records given an IDataObject pointer. Called from DetailsView::Drop(). Arguments: pIDataObject - Pointer to IDataObject interface of object containing import data. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( IDataObject *pIDataObject ) { HRESULT hResult = NO_ERROR; FORMATETC fmt; CStgMedium medium; // // Array to specify the clipboard formats and media types that // we can import from. Ordered by preference. // struct { CLIPFORMAT fmt; DWORD tymed; } rgFmtMedia[] = {{ DataObject::m_CF_NtDiskQuotaExport, TYMED_ISTREAM}, { DataObject::m_CF_NtDiskQuotaExport, TYMED_HGLOBAL}, { CF_HDROP, TYMED_ISTREAM}, { CF_HDROP, TYMED_HGLOBAL}}; DBGASSERT((NULL != pIDataObject)); hResult = E_FAIL; for (INT i = 0; i < ARRAYSIZE(rgFmtMedia); i++) { // // See which of our supported formats/media types the drop // source supports. // DataObject::SetFormatEtc(fmt, rgFmtMedia[i].fmt, rgFmtMedia[i].tymed); // // NOTE: I wanted to call QueryGetData to verify a source's support // for a format. However, it didn't work properly when // pasting an HDROP from the shell. Calling GetData() // directly results in the proper behavior. // hResult = pIDataObject->GetData(&fmt, &medium); if (SUCCEEDED(hResult)) { break; } } if (SUCCEEDED(hResult)) { // // Successfully have dropped data from the source. // Import users from it. // hResult = Import(fmt, medium); } else { DBGERROR((TEXT("PasteFromData: Error 0x%08X, Drop source doesn't support our format/media"), hResult)); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [FORMATETC&, STGMEDIUM&] Description: Imports one or more users from a storage medium. Arguments: fmt - Reference to the FORMATETC structure describing the data format. medium - Reference to the STGMEDIUM structure describing the medium. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( const FORMATETC& fmt, const STGMEDIUM& medium ) { HRESULT hResult = E_FAIL; IStream *pIStream = NULL; if (TYMED_HGLOBAL == medium.tymed) { // // Medium type is an HGLOBAL but our import functions need // a stream. // hResult = CreateStreamOnHGlobal(medium.hGlobal, FALSE, &pIStream); } else if (TYMED_ISTREAM == medium.tymed) { pIStream = medium.pstm; hResult = NO_ERROR; } // // OK. The source can render data in one of our acceptable // formats and medium types. Go ahead and have them render // it onto our stream. // if (NULL != pIStream) { if (DetailsView::DataObject::m_CF_NtDiskQuotaExport == fmt.cfFormat) { // // Stream contains quota record information directly. // Import the records. // Source src(pIStream); Import(src); } else if (CF_HDROP == fmt.cfFormat) { // // Stream contains names of files that potentially // contain quota record information. // HGLOBAL hDrop; hResult = GetHGlobalFromStream(pIStream, &hDrop); if (SUCCEEDED(hResult)) { hResult = Import((HDROP)hDrop); } } } else { DBGERROR((TEXT("PasteFromData: GetData failed with error 0x%08X"), hResult)); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [LPCTSTR] Description: Imports settings for one or more users from a doc file on disk. The doc file contains the import data directly in the stream. After opening and validating the storage and stream, it passes the stream to ImportUsersFromStream. Arguments: pszFilePath - Path to doc file containing import information stream. bUserCancelled - Reference to variable that is returned status indicating if the user cancelled the import operation. Returns: NO_ERROR = Success. S_FALSE = Not a doc file. STG_E_FILENOTFOUND STG_E_OUTOFMEMORY STG_E_ACCESSDENIED STG_E_INVALIDNAME STG_E_TOOMANYOPENFILES Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( LPCTSTR pszFilePath ) { HRESULT hResult = NO_ERROR; BOOL bStreamFailure = FALSE; // FALSE = Storage failure. // // Display the filename in the progress dialog. // m_dlgProgress.SetFileName(pszFilePath); // // Validate and open the file. // if (S_OK != StgIsStorageFile(pszFilePath)) { // // Not a doc file. Assume it was created using drag/drop. // Map the file into memory and import from that. // Contents will be validated during the import process. // MappedFile file; hResult = file.Open(pszFilePath); if (SUCCEEDED(hResult)) { // // This typecast from __int64 to ULONG is OK. Truncation // will not be a problem. There will be no quota import // storages larger than 4GB. // Source src(file.Base(), (ULONG)file.Size()); hResult = Import(src); } } else { IStorage *pStg = NULL; // // It's a doc file. Assume it's one created using OnCmdExport(). // Contents will be validated during the import process. // hResult = StgOpenStorage(pszFilePath, NULL, STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE, NULL, 0, &pStg); if (SUCCEEDED(hResult)) { // // Open the import stream. // IStream *pIStream; hResult = pStg->OpenStream(DetailsView::DataObject::SZ_EXPORT_STREAM_NAME, NULL, STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream); if (SUCCEEDED(hResult)) { // // Import information contained in the stream. // Source src(pIStream); hResult = Import(src); pIStream->Release(); } else { DBGERROR((TEXT("Import: Error 0x%08X opening stream \"%s\""), hResult, DataObject::SZ_EXPORT_STREAM_NAME)); // // Reporting logic below needs to know if it was a stream or storage // failure. // bStreamFailure = TRUE; } pStg->Release(); } else { DBGERROR((TEXT("Import: Error 0x%08X opening storage \"%s\""), hResult, pszFilePath)); } } if (FAILED(hResult)) { UINT iMsg = IDS_IMPORT_STREAM_READ_ERROR; // Generic message. switch(hResult) { case STG_E_FILENOTFOUND: // // Both OpenStream and StgOpenStorage can return // STG_E_FILENOTFOUND. However, they have two completely // different meanings from the user's perspective. // iMsg = bStreamFailure ? IDS_IMPORT_STREAM_INVALID_STREAM : IDS_IMPORT_STREAM_FILENOTFOUND; break; case STG_E_ACCESSDENIED: iMsg = IDS_IMPORT_STREAM_NOACCESS; break; case E_OUTOFMEMORY: case STG_E_INSUFFICIENTMEMORY: iMsg = IDS_IMPORT_STREAM_OUTOFMEMORY; break; case STG_E_INVALIDNAME: iMsg = IDS_IMPORT_STREAM_INVALIDNAME; break; case STG_E_TOOMANYOPENFILES: iMsg = IDS_IMPORT_STREAM_TOOMANYFILES; break; default: break; } DiskQuotaMsgBox(GetTopmostWindow(), iMsg, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [HDROP] Description: Imports settings from one or more doc files specified in a DROPFILES buffer. This is used when someone drops an export file onto the listview. The doc file names are extracted from the HDROP buffer then handed off to ImportUsersFromFile. Arguments: pIStream - Pointer to IStream containing DROPFILES info. bUserCancelled - Reference to variable that is returned status indicating if the user cancelled the import operation. Returns: Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( HDROP hDrop ) { HRESULT hResult = NO_ERROR; TCHAR szFile[MAX_PATH]; DBGASSERT((NULL != hDrop)); // // Get the count of files in the HDROP buffer. // UINT cFiles = DragQueryFile((HDROP)hDrop, (UINT)-1, NULL, 0); if ((UINT)-1 != cFiles) { // // Import users from each file in the HDROP buffer. // Bail out if user cancels operation. // for (INT i = 0; i < (INT)cFiles && !m_bUserCancelled; i++) { DragQueryFile(hDrop, i, szFile, ARRAYSIZE(szFile)); hResult = Import(szFile); } } else { DBGERROR((TEXT("DragQueryFile returned -1"))); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [Source&] Description: Imports settings for one or more users from a Source object. All import functions eventually get their information into a Source object format and call this function. It then separates out the individual user information and calls ImportOneUser() to do the actual import. Arguments: source - Reference to Source containing user import info. Returns: Number of users imported. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( Source& source ) { ULONG cbRead; HRESULT hResult = E_FAIL; try { // // Read and validate the stream signature. // This signature consists of a GUID so that we can validate any // stream used for import of quota information. // GUID guidStreamSignature; if (S_OK != source.Read(&guidStreamSignature, sizeof(guidStreamSignature), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); if (guidStreamSignature == GUID_NtDiskQuotaStream) { // // Read and validate the stream version. // Currently there is only 1 version of stream generated so validation // is simple. If we ever rev the stream format and bump the version // to 2, we should still be able to handle version 1 streams. The // only reason to display an error is if we encounter a totally bogus // stream version number. // DWORD nVersion; if (S_OK != source.Read(&nVersion, sizeof(nVersion), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); if (1 == nVersion) { INT cRecords; // // Read the count of records in the stream. // if (S_OK != source.Read(&cRecords, sizeof(cRecords), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); // // Set up the progress bar to represent this stream. // m_dlgProgress.ProgressBarInit(0, cRecords, 1); for (INT i = 0; !m_bUserCancelled && i < cRecords; i++) { // // Read each record from the stream. // A record consists of a SID-Length value followed by a SID // then followed by the user's quota amount used, threshold // and limit values. Abort loop if user cancels the // operation. // DWORD cbSid; LPBYTE pbSid; if (S_OK != source.Read(&cbSid, sizeof(cbSid), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); pbSid = new BYTE[cbSid]; try { if (NULL != pbSid) { PDISKQUOTA_USER pIUser = NULL; LONGLONG llQuotaThreshold; LONGLONG llQuotaLimit; // // Read in the user's SID. // if (S_OK != source.Read(pbSid, cbSid, &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); // // Read in the user's quota amount used. // This isn't used in the import process but it's in // the stream. Therefore we just dump it into the // threshold buffer. It will be overwritten. // if (S_OK != source.Read(&llQuotaThreshold, sizeof(llQuotaThreshold), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); // // Read in the user's quota threshold. // if (S_OK != source.Read(&llQuotaThreshold, sizeof(llQuotaThreshold), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); // // Read in the user's quota limit. // if (S_OK != source.Read(&llQuotaLimit, sizeof(llQuotaLimit), &cbRead)) throw CFileException(CFileException::read, TEXT(""), 0); // // We have one record of data for a user. // Now import it. // hResult = Import(pbSid, llQuotaThreshold, llQuotaLimit); delete[] pbSid; } } catch(CFileException& fe) { DBGERROR((TEXT("Import: File exception caught while reading import data."))); delete[] pbSid; throw; } catch(CAllocException& ae) { DBGERROR((TEXT("Import: Alloc exception caught while reading import data."))); delete[] pbSid; throw; } } } else { // // Invalid stream version. // Our code should always be able to handle any version // we produce. This code branch should only handle BOGUS // version numbers. In other words, a message like "Can't // understand this version" is not acceptable. // DBGERROR((TEXT("Import: Invalid stream version (%d)."), nVersion)); DiskQuotaMsgBox(GetTopmostWindow(), IDS_IMPORT_STREAM_INVALID_STREAM, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } } else { // // Invalid stream signature. // DBGERROR((TEXT("Import: Invalid stream signature."))); DiskQuotaMsgBox(GetTopmostWindow(), IDS_IMPORT_STREAM_INVALID_STREAM, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); } } catch(CFileException& fe) { DBGERROR((TEXT("Import: File exception caught while reading import data."))); DiskQuotaMsgBox(GetTopmostWindow(), IDS_IMPORT_STREAM_READ_ERROR, IDS_TITLE_DISK_QUOTA, MB_ICONERROR | MB_OK); hResult = HRESULT_FROM_WIN32(ERROR_READ_FAULT); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::Import [LPBYTE, LONGLONG, LONGLONG] Description: Imports a single user into the system given the user's SID and quota settings. This is the single function where all import mechanisms end up. It does the actual importing of the user. Arguments: pbSid - Address of buffer containing user's SID. llQuotaThreshold - User's quota warning threshold setting. llQuotaLimit - User's quota limit setting. Returns: -1 = User pressed "Cancel" in either the "Replace User" dialog or in the progress dialog. 0 = Failed to import user. 1 = User imported. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT DetailsView::Importer::Import( LPBYTE pbSid, LONGLONG llQuotaThreshold, LONGLONG llQuotaLimit ) { INT iResult = 0; HRESULT hResult = NO_ERROR; PDISKQUOTA_USER pIUser = NULL; static BOOL bReplaceExistingUser = FALSE; DBGASSERT((NULL != pbSid)); if (m_bPromptOnReplace) { // // We'll be prompting the user if a record needs replacement. // They'll make a choice through the UI. // Assume for now that we won't be replacing the record. // bReplaceExistingUser = FALSE; } // // Add user to volume's quota file. // hResult = m_DV.m_pQuotaControl->AddUserSid(pbSid, DISKQUOTA_USERNAME_RESOLVE_SYNC, &pIUser); if (SUCCEEDED(hResult)) { // // Either the user was added or already exists. // BOOL bAddNewUser = (S_FALSE != hResult); if (!bAddNewUser) { // // User already exists in the quota file. Find it's entry // in the listview. // DBGASSERT((NULL != pIUser)); pIUser->Release(); INT iItem = m_DV.FindUserBySid(pbSid, &pIUser); if (m_bPromptOnReplace) { TCHAR szLogonName[MAX_USERNAME] = { TEXT('\0') }; TCHAR szDisplayName[MAX_USERNAME] = { TEXT('\0') }; if (-1 != iItem) { // // Listview item found. // Get the account's name string so we can ask the user // if they want to replace it's quota settings. // DBGASSERT((NULL != pIUser)); pIUser->GetName(NULL, 0, szLogonName, ARRAYSIZE(szLogonName), szDisplayName, ARRAYSIZE(szDisplayName)); } CString strTitle(g_hInstDll, IDS_TITLE_DISK_QUOTA); CString strMsg(g_hInstDll, IDS_IMPORT_REPLACE_RECORD, szDisplayName, szLogonName); // // Ask the user if they want to replace the record's // quota settings. // YesNoToAllDialog ynToAllDlg(IDD_YNTOALL); INT_PTR iResponse = ynToAllDlg.CreateAndRun(g_hInstDll, GetTopmostWindow(), strTitle, strMsg); // // If the "Apply to All" checkbox was selected, we set this flag // so that the dialog isn't displayed again until the caller resets // m_bPromptOnReplace to TRUE. // m_bPromptOnReplace = !ynToAllDlg.ApplyToAll(); switch(iResponse) { case IDYES: bReplaceExistingUser = TRUE; break; case IDCANCEL: m_bUserCancelled = TRUE; break; default: break; } } } if (bAddNewUser || bReplaceExistingUser) { DBGASSERT((NULL != pIUser)); // // Write the new quota values because... // // 1. Added a new user record and setting initial values or... // 2. Replacing settings for an existing user. // if (NULL != pIUser) { LONGLONG llQuotaThresholdUndo; LONGLONG llQuotaLimitUndo; if (!bAddNewUser && bReplaceExistingUser) { // // Save the current threshold and limit values for "undo". // Only need information for undo if replacing an existing // user's settings. For performance, only call when we need // the info. // pIUser->GetQuotaThreshold(&llQuotaThresholdUndo); pIUser->GetQuotaLimit(&llQuotaLimitUndo); } // // Set the new threshold and limit values. // pIUser->SetQuotaThreshold(llQuotaThreshold, TRUE); pIUser->SetQuotaLimit(llQuotaLimit, TRUE); if (bAddNewUser) { // // Add the user to the listview and create an UNDO object for the operation. // m_DV.AddUser(pIUser); pIUser->AddRef(); m_DV.m_pQuotaControl->AddRef(); autoptr ptrUndoAdd = new UndoAdd(pIUser, m_DV.m_pQuotaControl); m_DV.m_pUndoList->Add(ptrUndoAdd); ptrUndoAdd.disown(); } if (!bAddNewUser && bReplaceExistingUser) { // // This will update the record to display any changed quota values. // Create an UNDO object for the operation. // m_DV.OnUserNameChanged(pIUser); pIUser->AddRef(); autoptr ptrUndoModify = new UndoModify(pIUser, llQuotaThresholdUndo, llQuotaLimitUndo); m_DV.m_pUndoList->Add(ptrUndoModify); ptrUndoModify.disown(); } } } } if (!m_bUserCancelled) m_bUserCancelled = m_dlgProgress.UserCancelled(); if (SUCCEEDED(hResult)) { m_cImported++; m_dlgProgress.ProgressBarAdvance(); } return hResult; } /////////////////////////////////////////////////////////////////////////////// /* Function: DetailsView::Importer::GetTopmostWindow Description: Returns the HWND of the topmost window in the importer UI. If the UI's progress dialog is visible, the dialog's HWND is returned. Otherwise, the value of m_hwndParent is returned. The Importer uses this function to identify what window should be parent to any error message boxes. Arguments: None. Returns: HWND to use for parent of any messages boxes created by the Importer. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/20/97 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HWND DetailsView::Importer::GetTopmostWindow( VOID ) { return m_dlgProgress.m_hWnd ? m_dlgProgress.m_hWnd : m_hwndParent; } /////////////////////////////////////////////////////////////////////////////// // The following StreamSource functions implement a layer of abstraction // between the import function and the source of the import data. This allows // me to centralize the actual import processing in a single function // without consideration of input source. // There are several Import() overloads but they eventually all call down // to Import(Source&). These functions are very simple so I'm not going // go bother documenting each. I think it's pretty obvious what they do. // Do note the use of the virtual constructor technique allowing the user // to deal only with Source objects and not AnySource, StreamSource or // MemorySource objects. This may be unfamiliar to some. // // [brianau 7/25/97] // /////////////////////////////////////////////////////////////////////////////// // // Source --------------------------------------------------------------------- // DetailsView::Importer::Source::Source( IStream *pStm ) : m_pTheSource(NULL) { // // Create a stream source type object. // m_pTheSource = new StreamSource(pStm); } DetailsView::Importer::Source::Source( LPBYTE pb, ULONG cbMax ) : m_pTheSource(NULL) { // // Create a memory source type object. // m_pTheSource = new MemorySource(pb, cbMax); } DetailsView::Importer::Source::~Source( VOID ) { // // Note: Destructors must be virtual for this to work. // delete m_pTheSource; } HRESULT DetailsView::Importer::Source::Read( LPVOID pvOut, ULONG cb, ULONG *pcbRead ) { HRESULT hr = E_OUTOFMEMORY; if (NULL != m_pTheSource) { // // Delegate the read operation to the properly-typed // subobject. // hr = m_pTheSource->Read(pvOut, cb, pcbRead); } return hr; } // // StreamSource --------------------------------------------------------------- // DetailsView::Importer::StreamSource::StreamSource( IStream *pStm ) : m_pStm(pStm) { // // AddRef the stream pointer. // if (NULL != m_pStm) m_pStm->AddRef(); } DetailsView::Importer::StreamSource::~StreamSource( VOID ) { // // Release the stream pointer. // if (NULL != m_pStm) m_pStm->Release(); } HRESULT DetailsView::Importer::StreamSource::Read( LPVOID pvOut, ULONG cb, ULONG *pcbRead ) { HRESULT hr = E_FAIL; if (NULL != m_pStm) { // // Read data from the stream. // hr = m_pStm->Read(pvOut, cb, pcbRead); } return hr; } // // MemorySource --------------------------------------------------------------- // DetailsView::Importer::MemorySource::MemorySource( LPBYTE pb, ULONG cbMax ) : m_pb(pb), m_cbMax(cbMax) { } HRESULT DetailsView::Importer::MemorySource::Read( LPVOID pvOut, ULONG cb, ULONG *pcbRead ) { HRESULT hr = E_FAIL; if (m_cbMax >= cb) { // // Read data from the memory block. // CopyMemory(pvOut, m_pb, cb); m_pb += cb; m_cbMax -= cb; if (NULL != pcbRead) { *pcbRead = cb; } hr = S_OK; } return hr; }