From: Vladislav Timonin timoninvlad@yandex.ru
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=50338
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=51855 --- dlls/comdlg32/itemdlg.c | 19 ++ dlls/comdlg32/navbar.c | 372 ++++++++++++++++++++++++++++++++++++++++ dlls/comdlg32/navbar.h | 4 + 3 files changed, 395 insertions(+)
diff --git a/dlls/comdlg32/itemdlg.c b/dlls/comdlg32/itemdlg.c index 703f525b7ca..453c83dcf1a 100644 --- a/dlls/comdlg32/itemdlg.c +++ b/dlls/comdlg32/itemdlg.c @@ -2167,6 +2167,15 @@ static LRESULT on_browse_up(FileDialogImpl *This) return FALSE; }
+static LRESULT on_browse_pidl(FileDialogImpl *This, LPARAM lparam) +{ + ITEMIDLIST* pidl = (ITEMIDLIST*)lparam; + + TRACE("%p - pidl %p\n", This, pidl); + IExplorerBrowser_BrowseToIDList(This->peb, pidl, SBSP_ABSOLUTE); + return FALSE; +} + static LRESULT on_command_filetype(FileDialogImpl *This, WPARAM wparam, LPARAM lparam) { if(HIWORD(wparam) == CBN_SELCHANGE) @@ -2243,6 +2252,7 @@ static INT_PTR CALLBACK itemdlg_dlgproc(HWND hwnd, UINT umessage, WPARAM wparam, case NBN_NAVBACK: return on_browse_back(This); case NBN_NAVFORWARD: return on_browse_forward(This); case NBN_NAVUP: return on_browse_up(This); + case NBN_NAVPIDL: return on_browse_pidl(This, lparam); }
return FALSE; @@ -3362,6 +3372,7 @@ static HRESULT WINAPI IExplorerBrowserEvents_fnOnNavigationComplete(IExplorerBro { FileDialogImpl *This = impl_from_IExplorerBrowserEvents(iface); HRESULT hr; + HWND hwnd; TRACE("%p (%p)\n", This, pidlFolder);
if(This->psi_folder) @@ -3373,6 +3384,14 @@ static HRESULT WINAPI IExplorerBrowserEvents_fnOnNavigationComplete(IExplorerBro ERR("Failed to get the current folder.\n"); This->psi_folder = NULL; } + else + { + hwnd = GetDlgItem(This->dlg_hwnd, IDC_NAVBAR); + if (hwnd) + SendMessageW(hwnd, NBM_SETPIDL, 0, (LPARAM)pidlFolder); + else + ERR("Failed to update navbar.\n"); + }
events_OnFolderChange(This);
diff --git a/dlls/comdlg32/navbar.c b/dlls/comdlg32/navbar.c index 1e3f1ec957a..1ab3f5d1120 100644 --- a/dlls/comdlg32/navbar.c +++ b/dlls/comdlg32/navbar.c @@ -23,33 +23,259 @@ #include "navbar.h" #include "commdlg.h" #include "cdlg.h" +#include "shlwapi.h" + +#include "wine/debug.h" +#include "wine/list.h" + +WINE_DEFAULT_DEBUG_CHANNEL(commdlg);
/* private control ids */ #define IDC_NAVBACK 201 #define IDC_NAVFORWARD 202 #define IDC_NAVUP 203 +#define IDC_NAVCRUMB 204
typedef struct { HWND parent_hwnd; HWND container_hwnd; + INT container_h; + INT dpi_x;
HIMAGELIST icons;
HWND back_btn_hwnd; HWND fwd_btn_hwnd; HWND up_btn_hwnd; + + HWND background_hwnd; + INT background_x; + INT background_w; + + struct list crumbs; + INT crumbs_total_n; + INT crumbs_visible_n; } NAVBAR_INFO;
+struct crumb { + struct list entry; + ITEMIDLIST *pidl; + WCHAR display_name[MAX_PATH]; + HWND hwnd; + INT full_w; + INT current_w; + INT x; +}; + +static LRESULT CALLBACK NAVBAR_BACKGROUND_SubclassProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR id_subclass, DWORD_PTR ref_data) +{ + switch (msg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + RECT rc; + HBRUSH old_brush; + HBRUSH bg_brush = GetSysColorBrush(COLOR_BTNFACE); + HBRUSH frame_brush = GetSysColorBrush(COLOR_3DSHADOW); + + GetClientRect(hwnd, &rc); + + /* clear background to avoid border trailing when resizing */ + FillRect(hdc, &rc, bg_brush); + + /* draw 1px frame */ + FrameRect(hdc, &rc, frame_brush); + + /* erase left border, retaining top and bottom pixels */ + old_brush = SelectObject(hdc, bg_brush); + PatBlt(hdc, rc.left, rc.top + 1, 1, rc.bottom - rc.top - 2, PATCOPY); + + SelectObject(hdc, old_brush); + + return FALSE; /* processed */ + } + } + + return DefSubclassProc(hwnd, msg, wparam, lparam); +} + +static void NAVBAR_CalcLayout(NAVBAR_INFO *info) +{ + RECT container_rc, up_button_rc; + struct crumb *crumb1, *crumb2; + INT padding, container_w, + buttons_w, max_crumbs_w, + w = 0, crumbs_visible_n = 0, prev_x; + + if (!GetWindowRect(info->container_hwnd, &container_rc)) + return; + padding = container_rc.left; + + if (!GetClientRect(info->container_hwnd, &container_rc)) + return; + container_w = container_rc.right - container_rc.left; + if (!container_w) + return; + + if (!GetWindowRect(info->up_btn_hwnd, &up_button_rc)) + return; + + buttons_w = up_button_rc.right + 1 - padding; + max_crumbs_w = container_w - buttons_w; + if (max_crumbs_w < 0) + return; + + LIST_FOR_EACH_ENTRY_REV(crumb1, &info->crumbs, struct crumb, entry) + { + INT next_w = w + crumb1->full_w; + crumb1->current_w = crumb1->full_w; + if (next_w > max_crumbs_w) + break; + + w = next_w; + crumbs_visible_n += 1; + } + + /* always show at least 2 crumbs */ + if (crumbs_visible_n < 2) + { + w = 0; + + crumb1 = (struct crumb *)list_tail(&info->crumbs); + if (&crumb1->entry == &info->crumbs) + { + ERR("no crumbs to layout\n"); + return; + } + + crumb2 = (struct crumb *)list_tail(&crumb1->entry); + if (&crumb2->entry == &info->crumbs) /* only 1 crumb in the list */ + { + if (crumbs_visible_n == 0) /* that doesn't fit on screen */ + { + crumb1->current_w = max_crumbs_w; + crumbs_visible_n = 1; + } + + prev_x = buttons_w + crumb1->current_w; + } + else + { + /* at least 2 crumbs in the list, 1 or 0 crumbs on screen */ + /* last crumb should take as much space as possible */ + INT min_crumb_w = MulDiv(56, info->dpi_x, USER_DEFAULT_SCREEN_DPI); + INT crumb2_size = crumb2->full_w; + + if (crumb2_size > min_crumb_w) + crumb2_size = min_crumb_w; + + /* calculate how much extra space is required to fit both crumbs */ + w = (crumb1->full_w + crumb2_size) - max_crumbs_w; + if (w > 0) + { + /* not enough space for last crumb, truncate it */ + crumb1->current_w = max(0, crumb1->full_w - w); + crumb2->current_w = crumb2_size; + } + else + { + /* enough space for last crumb, let the second crumb take leftover space */ + crumb2_size = max_crumbs_w - crumb1->full_w; + if (crumb2_size < crumb2->full_w) + crumb2->current_w = crumb2_size; + } + + crumbs_visible_n = 2; + prev_x = buttons_w + crumb1->current_w + crumb2->current_w; + } + } + else + prev_x = buttons_w + w; + + info->background_x = prev_x; + info->background_w = container_rc.right - prev_x; + + info->crumbs_visible_n = crumbs_visible_n; + + LIST_FOR_EACH_ENTRY_REV(crumb1, &info->crumbs, struct crumb, entry) + { + if (crumbs_visible_n > 0) + { + LONG_PTR style = WS_CHILD | WS_VISIBLE; + + /* if label doesn't fully fit, align it to the left */ + if (crumb1->current_w < crumb1->full_w) + style |= BS_LEFT; + else + style |= BS_CENTER; + + SetWindowLongPtrW(crumb1->hwnd, GWL_STYLE, style); + + crumb1->x = prev_x - crumb1->current_w; + prev_x = crumb1->x; + } + else + break; + + crumbs_visible_n -= 1; + } +} + +static HDWP NAVBAR_DoLayout(NAVBAR_INFO *info, HDWP hdwp) +{ + struct crumb *crumb; + INT can_fit_n = info->crumbs_visible_n; + UINT background_flags = 0; + + LIST_FOR_EACH_ENTRY_REV(crumb, &info->crumbs, struct crumb, entry) + { + UINT flags = 0; + + if (can_fit_n > 0) + flags |= SWP_SHOWWINDOW | SWP_NOCOPYBITS; + else + flags |= SWP_HIDEWINDOW; + + hdwp = DeferWindowPos(hdwp, crumb->hwnd, HWND_TOP, + crumb->x, 0, + crumb->current_w, info->container_h, + flags); + + can_fit_n -= 1; + } + + if (info->background_w > 0) + background_flags |= SWP_SHOWWINDOW | SWP_NOCOPYBITS; + else + background_flags |= SWP_HIDEWINDOW; + + hdwp = DeferWindowPos(hdwp, info->background_hwnd, HWND_TOP, + info->background_x, 0, + info->background_w, info->container_h, + background_flags); + + return hdwp; +} + static LRESULT NAVBAR_Create(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wparam, LPARAM lparam) { CREATESTRUCTW *cs = (CREATESTRUCTW*)lparam; + HDC hdc; INT x; HGDIOBJ gui_font = GetStockObject(DEFAULT_GUI_FONT); HICON icon;
info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(NAVBAR_INFO)); + list_init(&info->crumbs); info->parent_hwnd = GetParent(hwnd); info->container_hwnd = hwnd; + info->container_h = cs->cy; + + hdc = GetDC(0); + info->dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(0, hdc);
info->icons = ImageList_LoadImageW(COMDLG32_hInstance, MAKEINTRESOURCEW(IDB_NAVBAR), 24, 0, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION); @@ -84,6 +310,13 @@ static LRESULT NAVBAR_Create(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wpar SendMessageW(info->up_btn_hwnd, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)icon); DestroyIcon(icon);
+ x += cs->cy + 1; + info->background_hwnd = CreateWindowExW(0, WC_STATICW, NULL, + WS_CHILD | WS_VISIBLE, + x, 0, 0, cs->cy, + hwnd, 0, COMDLG32_hInstance, NULL); + SetWindowSubclass(info->background_hwnd, NAVBAR_BACKGROUND_SubclassProc, 0, (DWORD_PTR)info); + SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)info);
return DefWindowProcW(hwnd, msg, wparam, lparam); @@ -91,17 +324,45 @@ static LRESULT NAVBAR_Create(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wpar
static LRESULT NAVBAR_Destroy(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wparam, LPARAM lparam) { + struct crumb *crumb1, *crumb2; + + LIST_FOR_EACH_ENTRY_SAFE(crumb1, crumb2, &info->crumbs, struct crumb, entry) + { + ITEMIDLIST *pidl = NULL; + pidl = (ITEMIDLIST*)GetWindowLongPtrW(crumb1->hwnd, GWLP_USERDATA); + ILFree(pidl); + + DestroyWindow(crumb1->hwnd); + + list_remove(&crumb1->entry); + HeapFree(GetProcessHeap(), 0, crumb1); + } + SetWindowLongPtrW(hwnd, 0, 0); ImageList_Destroy(info->icons); DestroyWindow(info->back_btn_hwnd); DestroyWindow(info->fwd_btn_hwnd); DestroyWindow(info->up_btn_hwnd); + DestroyWindow(info->background_hwnd);
HeapFree(GetProcessHeap(), 0, info);
return DefWindowProcW(hwnd, msg, wparam, lparam); }
+static LRESULT NAVBAR_Size(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wparam, LPARAM lparam) +{ + HDWP hdwp; + + NAVBAR_CalcLayout(info); + + hdwp = BeginDeferWindowPos(info->crumbs_total_n + 1); + hdwp = NAVBAR_DoLayout(info, hdwp); + EndDeferWindowPos(hdwp); + + return TRUE; +} + static LRESULT NAVBAR_Command(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wparam, LPARAM lparam) { switch (LOWORD(wparam)) @@ -115,8 +376,117 @@ static LRESULT NAVBAR_Command(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wpa case IDC_NAVUP: SendMessageW(info->parent_hwnd, NBN_NAVUP, 0, 0); break; + case IDC_NAVCRUMB: + { + HWND crumb = (HWND)lparam; + ITEMIDLIST *pidl = (ITEMIDLIST*)GetWindowLongPtrW(crumb, GWLP_USERDATA); + SendMessageW(info->parent_hwnd, NBN_NAVPIDL, 0, (LPARAM)pidl); + break; + } + } + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static LRESULT NAVBAR_SetPIDL(HWND hwnd, NAVBAR_INFO *info, UINT msg, WPARAM wparam, LPARAM lparam) +{ + ITEMIDLIST *pidl = (ITEMIDLIST *)lparam; + IShellFolder *desktop; + HGDIOBJ gui_font = GetStockObject(DEFAULT_GUI_FONT); + HRESULT hr; + INT padding = MulDiv(10, info->dpi_x, USER_DEFAULT_SCREEN_DPI); + struct list new_crumbs; + struct crumb *crumb1, *crumb2; + HDWP hdwp; + + TRACE("info %p pidl %p\n", info, pidl); + + hr = SHGetDesktopFolder(&desktop); + if (FAILED(hr)) + { + ERR("failed to get desktop folder\n"); + goto exit; + } + + list_init(&new_crumbs); + info->crumbs_total_n = 0; + pidl = ILClone(pidl); + do + { + STRRET strret; + SIZE full_size; + + crumb1 = HeapAlloc(GetProcessHeap(), 0, sizeof(*crumb1)); + + crumb1->pidl = ILClone(pidl); + IShellFolder_GetDisplayNameOf(desktop, pidl, SHGDN_FORADDRESSBAR, &strret); + StrRetToBufW(&strret, pidl, crumb1->display_name, MAX_PATH); + + crumb1->hwnd = CreateWindowExW(0, WC_BUTTONW, crumb1->display_name, WS_CHILD, + 0, 0, 0, 0, + info->container_hwnd, (HMENU)IDC_NAVCRUMB, COMDLG32_hInstance, NULL); + SendMessageW(crumb1->hwnd, WM_SETFONT, (LPARAM)gui_font, FALSE); + SendMessageW(crumb1->hwnd, BCM_GETIDEALSIZE, 0, (LPARAM)&full_size); + SetWindowLongPtrW(crumb1->hwnd, GWLP_USERDATA, (LPARAM)crumb1->pidl); + + crumb1->full_w = full_size.cx + padding; + crumb1->current_w = crumb1->full_w; + + /* PIDL is iterated from right-to-left, prepend the crumb to store them in left-to-right order */ + list_add_head(&new_crumbs, &crumb1->entry); + info->crumbs_total_n += 1; + } + while (ILRemoveLastID(pidl)); + ILFree(pidl); + + /* reuse existing crumbs */ + crumb1 = LIST_ENTRY((&info->crumbs)->next, struct crumb, entry); + crumb2 = LIST_ENTRY((&new_crumbs)->next, struct crumb, entry); + for (;;) + { + if (&crumb1->entry == &info->crumbs || + &crumb2->entry == &new_crumbs || + !ILIsEqual(crumb2->pidl, crumb1->pidl)) + break; + + DestroyWindow(crumb2->hwnd); + ILFree(crumb2->pidl); + crumb2->pidl = crumb1->pidl; + crumb2->hwnd = crumb1->hwnd; + + crumb1 = LIST_ENTRY(crumb1->entry.next, struct crumb, entry); + crumb2 = LIST_ENTRY(crumb2->entry.next, struct crumb, entry); + } + + /* cleanup unused existing crumbs */ + for (;;) + { + if (&crumb1->entry == &info->crumbs) + break; + + DestroyWindow(crumb1->hwnd); + ILFree(crumb1->pidl); + + crumb1 = LIST_ENTRY(crumb1->entry.next, struct crumb, entry); }
+ LIST_FOR_EACH_ENTRY_SAFE(crumb1, crumb2, &info->crumbs, struct crumb, entry) + { + list_remove(&crumb1->entry); + HeapFree(GetProcessHeap(), 0, crumb1); + } + + new_crumbs.next->prev = &info->crumbs; + new_crumbs.prev->next = &info->crumbs; + info->crumbs = new_crumbs; + + NAVBAR_CalcLayout(info); + + hdwp = BeginDeferWindowPos(info->crumbs_total_n + 1); + hdwp = NAVBAR_DoLayout(info, hdwp); + EndDeferWindowPos(hdwp); + +exit: return DefWindowProcW(hwnd, msg, wparam, lparam); }
@@ -131,7 +501,9 @@ static LRESULT CALLBACK NAVBAR_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LP { case WM_NCCREATE: return NAVBAR_Create(hwnd, info, msg, wparam, lparam); case WM_DESTROY: return NAVBAR_Destroy(hwnd, info, msg, wparam, lparam); + case WM_SIZE: return NAVBAR_Size(hwnd, info, msg, wparam, lparam); case WM_COMMAND: return NAVBAR_Command(hwnd, info, msg, wparam, lparam); + case NBM_SETPIDL: return NAVBAR_SetPIDL(hwnd, info, msg, wparam, lparam); }
exit: diff --git a/dlls/comdlg32/navbar.h b/dlls/comdlg32/navbar.h index 111439ccbad..5b5cb3eac95 100644 --- a/dlls/comdlg32/navbar.h +++ b/dlls/comdlg32/navbar.h @@ -25,10 +25,14 @@
#define WC_NAVBARW L"NavBar"
+/* navbar messages */ +#define NBM_SETPIDL WM_USER + 101 + /* navbar notifications */ #define NBN_NAVBACK WM_USER + 102 #define NBN_NAVFORWARD WM_USER + 103 #define NBN_NAVUP WM_USER + 104 +#define NBN_NAVPIDL WM_USER + 105
/* bitmap ids */ #define IDB_NAVBAR 3000