Skip to content

Commit

Permalink
Show progress dialog during startup (#9255)
Browse files Browse the repository at this point in the history
* Show progress dialog during startup for selection enumeration that can take a long time.

* Updated with better code organization and a timer to ensure the progress dialog does not appear in most cases.

* Update based on PR feedback

* Change progress dialog delay from 1500ms to 2500ms

* Move progress dialog invocation off the main UI thread

Co-authored-by: Chris Davis (EDGE) <chrdavis@microsoft.com>
  • Loading branch information
chrdavis and Chris Davis (EDGE) committed Apr 2, 2021
1 parent 6613522 commit d128939
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 106 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,5 @@ src/common/Telemetry/*.etl
!**/MergeModules/Release/
!**/MergeModules/Debug/
/src/modules/previewpane/SvgThumbnailProvider/$(SolutionDir)$(Platform)/$(Configuration)/modules/FileExplorerPreview/SvgThumbnailProvider.xml
/src/modules/powerrename/ui/RCa24464
/src/modules/powerrename/ui/RCb24464
98 changes: 29 additions & 69 deletions src/modules/powerrename/lib/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SY
return hr;
}

HRESULT _GetShellItemArrayFromDataOject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items)
HRESULT GetShellItemArrayFromDataObject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items)
{
*items = nullptr;
CComPtr<IDataObject> dataObj;
Expand All @@ -292,73 +292,6 @@ HRESULT _GetShellItemArrayFromDataOject(_In_ IUnknown* dataSource, _COM_Outptr_
return hr;
}

HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ IPowerRenameManager* psrm, _In_ int depth = 0)
{
HRESULT hr = E_INVALIDARG;

// We shouldn't get this deep since we only enum the contents of
// regular folders but adding just in case
if ((pesi) && (depth < (MAX_PATH / 2)))
{
hr = S_OK;

ULONG celtFetched;
CComPtr<IShellItem> spsi;
while ((S_OK == pesi->Next(1, &spsi, &celtFetched)) && (SUCCEEDED(hr)))
{
CComPtr<IPowerRenameItemFactory> spsrif;
hr = psrm->GetRenameItemFactory(&spsrif);
if (SUCCEEDED(hr))
{
CComPtr<IPowerRenameItem> spNewItem;
hr = spsrif->Create(spsi, &spNewItem);
if (SUCCEEDED(hr))
{
spNewItem->PutDepth(depth);
hr = psrm->AddItem(spNewItem);
}

if (SUCCEEDED(hr))
{
bool isFolder = false;
if (SUCCEEDED(spNewItem->GetIsFolder(&isFolder)) && isFolder)
{
// Bind to the IShellItem for the IEnumShellItems interface
CComPtr<IEnumShellItems> spesiNext;
hr = spsi->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spesiNext));
if (SUCCEEDED(hr))
{
// Parse the folder contents recursively
hr = _ParseEnumItems(spesiNext, psrm, depth + 1);
}
}
}
}

spsi = nullptr;
}
}

return hr;
}

// Iterate through the data source and add paths to the rotation manager
HRESULT EnumerateDataObject(_In_ IUnknown* dataSource, _In_ IPowerRenameManager* psrm)
{
CComPtr<IShellItemArray> spsia;
HRESULT hr = E_FAIL;
if (SUCCEEDED(_GetShellItemArrayFromDataOject(dataSource, &spsia)))
{
CComPtr<IEnumShellItems> spesi;
if (SUCCEEDED(spsia->EnumItems(&spesi)))
{
hr = _ParseEnumItems(spesi, psrm);
}
}

return hr;
}

BOOL GetEnumeratedFileName(__out_ecount(cchMax) PWSTR pszUniqueName, UINT cchMax, __in PCWSTR pszTemplate, __in_opt PCWSTR pszDir, unsigned long ulMinLong, __inout unsigned long* pulNumUsed)
{
PWSTR pszName = nullptr;
Expand Down Expand Up @@ -528,7 +461,7 @@ bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource)
{
bool hasRenamable = false;
CComPtr<IShellItemArray> spsia;
if (SUCCEEDED(_GetShellItemArrayFromDataOject(dataSource, &spsia)))
if (SUCCEEDED(GetShellItemArrayFromDataObject(dataSource, &spsia)))
{
CComPtr<IEnumShellItems> spesi;
if (SUCCEEDED(spsia->EnumItems(&spesi)))
Expand All @@ -549,3 +482,30 @@ bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource)
}
return hasRenamable;
}

HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
{
WNDCLASS wc = { 0 };
PCWSTR wndClassName = L"MsgWindow";

wc.lpfnWndProc = DefWindowProc;
wc.cbWndExtra = sizeof(void*);
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszClassName = wndClassName;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0, wndClassName, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInst, nullptr);
if (hwnd)
{
SetWindowLongPtr(hwnd, 0, (LONG_PTR)p);
if (pfnWndProc)
{
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)pfnWndProc);
}
}

return hwnd;
}
3 changes: 2 additions & 1 deletion src/modules/powerrename/lib/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour
HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime);
bool isFileTimeUsed(_In_ PCWSTR source);
bool DataObjectContainsRenamableItem(_In_ IUnknown* dataSource);
HRESULT EnumerateDataObject(_In_ IUnknown* pdo, _In_ IPowerRenameManager* psrm);
HRESULT GetShellItemArrayFromDataObject(_In_ IUnknown* dataSource, _COM_Outptr_ IShellItemArray** items);
BOOL GetEnumeratedFileName(
__out_ecount(cchMax) PWSTR pszUniqueName,
UINT cchMax,
__in PCWSTR pszTemplate,
__in_opt PCWSTR pszDir,
unsigned long ulMinLong,
__inout unsigned long* pulNumUsed);
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p);
144 changes: 144 additions & 0 deletions src/modules/powerrename/lib/PowerRenameEnum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include "pch.h"
#include "PowerRenameEnum.h"
#include <ShlGuid.h>
#include <helpers.h>

IFACEMETHODIMP_(ULONG) CPowerRenameEnum::AddRef()
{
return InterlockedIncrement(&m_refCount);
}

IFACEMETHODIMP_(ULONG) CPowerRenameEnum::Release()
{
long refCount = InterlockedDecrement(&m_refCount);

if (refCount == 0)
{
delete this;
}
return refCount;
}

IFACEMETHODIMP CPowerRenameEnum::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppv)
{
static const QITAB qit[] = {
QITABENT(CPowerRenameEnum, IPowerRenameEnum),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}

IFACEMETHODIMP CPowerRenameEnum::Start()
{
m_canceled = false;
CComPtr<IShellItemArray> spsia;
HRESULT hr = GetShellItemArrayFromDataObject(m_spdo, &spsia);
if (SUCCEEDED(hr))
{
CComPtr<IEnumShellItems> spesi;
hr = spsia->EnumItems(&spesi);
if (SUCCEEDED(hr))
{
hr = _ParseEnumItems(spesi);
}
}

return hr;
}

IFACEMETHODIMP CPowerRenameEnum::Cancel()
{
m_canceled = true;
return S_OK;
}

HRESULT CPowerRenameEnum::s_CreateInstance(_In_ IUnknown* pdo, _In_ IPowerRenameManager* pManager, _In_ REFIID iid, _Outptr_ void** resultInterface)
{
*resultInterface = nullptr;

CPowerRenameEnum* newRenameEnum = new CPowerRenameEnum();
HRESULT hr = newRenameEnum ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = newRenameEnum->_Init(pdo, pManager);
if (SUCCEEDED(hr))
{
hr = newRenameEnum->QueryInterface(iid, resultInterface);
}

newRenameEnum->Release();
}
return hr;
}

CPowerRenameEnum::CPowerRenameEnum() :
m_refCount(1)
{
}

CPowerRenameEnum::~CPowerRenameEnum()
{
}

HRESULT CPowerRenameEnum::_Init(_In_ IUnknown* pdo, _In_ IPowerRenameManager* pManager)
{
m_spdo = pdo;
m_spsrm = pManager;
return S_OK;
}

HRESULT CPowerRenameEnum::_ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ int depth)
{
HRESULT hr = E_INVALIDARG;

// We shouldn't get this deep since we only enum the contents of
// regular folders but adding just in case
if ((pesi) && (depth < (MAX_PATH / 2)))
{
hr = S_OK;

ULONG celtFetched;
CComPtr<IShellItem> spsi;
while ((S_OK == pesi->Next(1, &spsi, &celtFetched)) && (SUCCEEDED(hr)))
{
if (m_canceled)
{
return E_ABORT;
}

CComPtr<IPowerRenameItemFactory> spFactory;
hr = m_spsrm->GetRenameItemFactory(&spFactory);
if (SUCCEEDED(hr))
{
CComPtr<IPowerRenameItem> spNewItem;
// Failure may be valid if we come across a shell item that does
// not support a file system path. In that case we simply ignore
// the item.
if (SUCCEEDED(spFactory->Create(spsi, &spNewItem)))
{
spNewItem->PutDepth(depth);
hr = m_spsrm->AddItem(spNewItem);
if (SUCCEEDED(hr))
{
bool isFolder = false;
if (SUCCEEDED(spNewItem->GetIsFolder(&isFolder)) && isFolder)
{
// Bind to the IShellItem for the IEnumShellItems interface
CComPtr<IEnumShellItems> spesiNext;
hr = spsi->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spesiNext));
if (SUCCEEDED(hr))
{
// Parse the folder contents recursively
hr = _ParseEnumItems(spesiNext, depth + 1);
}
}
}
}
}

spsi = nullptr;
}
}

return hr;
}
34 changes: 34 additions & 0 deletions src/modules/powerrename/lib/PowerRenameEnum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#include "pch.h"
#include "PowerRenameInterfaces.h"
#include <vector>
#include "srwlock.h"

class CPowerRenameEnum :
public IPowerRenameEnum
{
public:
// IUnknown
IFACEMETHODIMP QueryInterface(_In_ REFIID iid, _Outptr_ void** resultInterface);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();

// ISmartRenameEnum
IFACEMETHODIMP Start();
IFACEMETHODIMP Cancel();

public:
static HRESULT s_CreateInstance(_In_ IUnknown* pdo, _In_ IPowerRenameManager* pManager, _In_ REFIID iid, _Outptr_ void** resultInterface);

protected:
CPowerRenameEnum();
virtual ~CPowerRenameEnum();

HRESULT _Init(_In_ IUnknown* pdo, _In_ IPowerRenameManager* pManager);
HRESULT _ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ int depth = 0);

CComPtr<IPowerRenameManager> m_spsrm;
CComPtr<IUnknown> m_spdo;
bool m_canceled = false;
long m_refCount = 0;
};
6 changes: 6 additions & 0 deletions src/modules/powerrename/lib/PowerRenameInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,9 @@ interface __declspec(uuid("04AAFABE-B76E-4E13-993A-B5941F52B139")) IPowerRenameM
IFACEMETHOD(AddMRUString)(_In_ PCWSTR entry) = 0;
};

interface __declspec(uuid("CE8C8616-C1A8-457A-9601-10570F5B9F1F")) IPowerRenameEnum : public IUnknown
{
public:
IFACEMETHOD(Start)() = 0;
IFACEMETHOD(Cancel)() = 0;
};
2 changes: 2 additions & 0 deletions src/modules/powerrename/lib/PowerRenameLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Helpers.h" />
<ClInclude Include="PowerRenameEnum.h" />
<ClInclude Include="PowerRenameItem.h" />
<ClInclude Include="PowerRenameInterfaces.h" />
<ClInclude Include="PowerRenameManager.h" />
Expand All @@ -52,6 +53,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="Helpers.cpp" />
<ClCompile Include="PowerRenameEnum.cpp" />
<ClCompile Include="PowerRenameItem.cpp" />
<ClCompile Include="PowerRenameManager.cpp" />
<ClCompile Include="PowerRenameRegEx.cpp" />
Expand Down
28 changes: 0 additions & 28 deletions src/modules/powerrename/lib/PowerRenameManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,6 @@ namespace fs = std::filesystem;

extern HINSTANCE g_hInst;

HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
{
WNDCLASS wc = { 0 };

PCWSTR wndClassName = L"MsgWindow";

wc.lpfnWndProc = DefWindowProc;
wc.cbWndExtra = sizeof(void*);
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszClassName = wndClassName;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0, wndClassName, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInst, nullptr);
if (hwnd)
{
SetWindowLongPtr(hwnd, 0, (LONG_PTR)p);
if (pfnWndProc)
{
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)pfnWndProc);
}
}

return hwnd;
}

// The default FOF flags to use in the rename operations
#define FOF_DEFAULTFLAGS (FOF_ALLOWUNDO | FOFX_ADDUNDORECORD | FOFX_SHOWELEVATIONPROMPT | FOF_RENAMEONCOLLISION)

Expand Down
1 change: 1 addition & 0 deletions src/modules/powerrename/lib/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <shobjidl.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <ShlObj_core.h>

#include <ProjectTelemetry.h>

1 comment on commit d128939

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misspellings found, please review:

  • CHECKCANCELED
  • IProgress
  • ISmart
  • MARQUEEPROGRESS
  • PROGDLG
  • prpui
  • sppd
  • sppre
  • TIMERID
To accept these changes, run the following commands from this repository on this branch
pushd $(git rev-parse --show-toplevel)
perl -e '
my @expect_files=qw('".github/actions/spell-check/expect.txt"');
@ARGV=@expect_files;
my @stale=qw('"Devagya Drakula Oject RSHIFT YourUserName "');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
  if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
  next if /^(?:$re)(?:(?:\r|\n)*$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spell-check/expect.txt";
use File::Path qw(make_path);
make_path ".github/actions/spell-check";
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"CHECKCANCELED IProgress ISmart MARQUEEPROGRESS PROGDLG prpui rshift sppd sppre TIMERID "');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a) cmp lc($b)} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;'
popd

Please sign in to comment.