diff options
Diffstat (limited to 'widget/windows/TaskbarTabPreview.cpp')
-rw-r--r-- | widget/windows/TaskbarTabPreview.cpp | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp new file mode 100644 index 0000000000..a19a6bf12e --- /dev/null +++ b/widget/windows/TaskbarTabPreview.cpp @@ -0,0 +1,345 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TaskbarTabPreview.h" +#include "nsWindowGfx.h" +#include "nsUXThemeData.h" +#include "WinUtils.h" +#include <nsITaskbarPreviewController.h> + +#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd" + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview) + +const wchar_t* const kWindowClass = L"MozillaTaskbarPreviewClass"; + +TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, + HWND aHWND, nsIDocShell* aShell) + : TaskbarPreview(aTaskbar, aController, aHWND, aShell), + mProxyWindow(nullptr), + mIcon(nullptr), + mRegistered(false) {} + +TaskbarTabPreview::~TaskbarTabPreview() { + if (mIcon) { + ::DestroyIcon(mIcon); + mIcon = nullptr; + } + + // We need to ensure that proxy window disappears or else Bad Things happen. + if (mProxyWindow) Disable(); + + NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!"); + + if (IsWindowAvailable()) { + DetachFromNSWindow(); + } else { + mWnd = nullptr; + } +} + +nsresult TaskbarTabPreview::Init() { + nsresult rv = TaskbarPreview::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + + return hook->AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); +} + +nsresult TaskbarTabPreview::ShowActive(bool active) { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), + "ShowActive called on invisible window or before taskbar calls " + "can be made for this window"); + return FAILED( + mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +HWND& TaskbarTabPreview::PreviewWindow() { return mProxyWindow; } + +nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; } + +void TaskbarTabPreview::EnsureRegistration() { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), + "EnsureRegistration called when it is not safe to do so"); + + (void)UpdateTaskbarProperties(); +} + +NS_IMETHODIMP +TaskbarTabPreview::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetTitle(const nsAString& aTitle) { + mTitle = aTitle; + return mVisible ? UpdateTitle() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetIcon(imgIContainer* icon) { + HICON hIcon = nullptr; + if (icon) { + nsresult rv; + rv = nsWindowGfx::CreateIcon( + icon, false, LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mIcon) ::DestroyIcon(mIcon); + mIcon = hIcon; + mIconImage = icon; + return mVisible ? UpdateIcon() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::GetIcon(imgIContainer** icon) { + NS_IF_ADDREF(*icon = mIconImage); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::Move(nsITaskbarTabPreview* aNext) { + if (aNext == this) return NS_ERROR_INVALID_ARG; + mNext = aNext; + return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK; +} + +nsresult TaskbarTabPreview::UpdateTaskbarProperties() { + if (mRegistered) return NS_OK; + + if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd))) + return NS_ERROR_FAILURE; + + nsresult rv = UpdateNext(); + NS_ENSURE_SUCCESS(rv, rv); + rv = TaskbarPreview::UpdateTaskbarProperties(); + mRegistered = true; + return rv; +} + +LRESULT +TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + RefPtr<TaskbarTabPreview> kungFuDeathGrip(this); + switch (nMsg) { + case WM_CREATE: + TaskbarPreview::EnableCustomDrawing(mProxyWindow, true); + return 0; + case WM_CLOSE: + mController->OnClose(); + return 0; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_ACTIVE) { + // Activate the tab the user selected then restore the main window, + // keeping normal/max window state intact. + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); + if (win) { + nsWindow* parent = win->GetTopLevelWindow(true); + if (parent) { + parent->Show(true); + } + } + } + } + return 0; + case WM_GETICON: + return (LRESULT)mIcon; + case WM_SYSCOMMAND: + // Send activation events to the top level window and select the proper + // tab through the controller. + if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) { + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + // Note, restoring an iconic, maximized window here will only + // activate the maximized window. This is not a bug, it's default + // windows behavior. + ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + } + return 0; + } + // Forward everything else to the top level window. Do not forward + // close since that's intended for the tab. When the preview proxy + // closes, we'll close the tab above. + return wParam == SC_CLOSE + ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam) + : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + return 0; + } + return TaskbarPreview::WndProc(nMsg, wParam, lParam); +} + +/* static */ +LRESULT CALLBACK TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg, + WPARAM wParam, + LPARAM lParam) { + TaskbarTabPreview* preview(nullptr); + if (nMsg == WM_CREATE) { + CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam); + preview = reinterpret_cast<TaskbarTabPreview*>(cs->lpCreateParams); + if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview)) + NS_ERROR("Could not associate native window with tab preview"); + preview->mProxyWindow = hWnd; + } else { + preview = reinterpret_cast<TaskbarTabPreview*>( + ::GetPropW(hWnd, TASKBARPREVIEW_HWNDID)); + if (nMsg == WM_DESTROY) ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID); + } + + if (preview) return preview->WndProc(nMsg, wParam, lParam); + return ::DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +nsresult TaskbarTabPreview::Enable() { + WNDCLASSW wc; + HINSTANCE module = GetModuleHandle(nullptr); + + if (!GetClassInfoW(module, kWindowClass, &wc)) { + wc.style = 0; + wc.lpfnWndProc = GlobalWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = module; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = (HBRUSH) nullptr; + wc.lpszMenuName = (LPCWSTR) nullptr; + wc.lpszClassName = kWindowClass; + RegisterClassW(&wc); + } + ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow", + WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, nullptr, nullptr, + module, this); + // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND + if (!mProxyWindow) return NS_ERROR_INVALID_ARG; + + UpdateProxyWindowStyle(); + + nsresult rv = TaskbarPreview::Enable(); + nsresult rvUpdate; + rvUpdate = UpdateTitle(); + if (NS_FAILED(rvUpdate)) rv = rvUpdate; + + rvUpdate = UpdateIcon(); + if (NS_FAILED(rvUpdate)) rv = rvUpdate; + + return rv; +} + +nsresult TaskbarTabPreview::Disable() { + // TaskbarPreview::Disable assumes that mWnd is valid but this method can be + // called when it is null iff the nsWindow has already been destroyed and we + // are still visible for some reason during object destruction. + if (mWnd) TaskbarPreview::Disable(); + + if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) return NS_ERROR_FAILURE; + mRegistered = false; + + // TaskbarPreview::WndProc will set mProxyWindow to null + if (!DestroyWindow(mProxyWindow)) return NS_ERROR_FAILURE; + mProxyWindow = nullptr; + return NS_OK; +} + +void TaskbarTabPreview::DetachFromNSWindow() { + (void)SetVisible(false); + if (WindowHook* hook = GetWindowHook()) { + hook->RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); + } + TaskbarPreview::DetachFromNSWindow(); +} + +/* static */ +bool TaskbarTabPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT* aResult) { + if (nMsg == WM_WINDOWPOSCHANGED) { + TaskbarTabPreview* preview = reinterpret_cast<TaskbarTabPreview*>(aContext); + WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam); + if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED)) + preview->UpdateProxyWindowStyle(); + } else { + MOZ_ASSERT_UNREACHABLE( + "Style changed hook fired on non-style changed " + "message"); + } + return false; +} + +void TaskbarTabPreview::UpdateProxyWindowStyle() { + if (!mProxyWindow) return; + + DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE; + DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE); + + DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE); + proxyStyle &= ~minMaxMask; + proxyStyle |= windowStyle & minMaxMask; + SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle); + + DWORD exStyle = + (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0; + SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle); +} + +nsresult TaskbarTabPreview::UpdateTitle() { + NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview"); + + if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult TaskbarTabPreview::UpdateIcon() { + NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview"); + + ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon); + + return NS_OK; +} + +nsresult TaskbarTabPreview::UpdateNext() { + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, + "UpdateNext called on invisible tab preview"); + HWND hNext = nullptr; + if (mNext) { + bool visible; + nsresult rv = mNext->GetVisible(&visible); + + NS_ENSURE_SUCCESS(rv, rv); + + // Can only move next to enabled previews + if (!visible) return NS_ERROR_FAILURE; + + hNext = (HWND)mNext->GetHWND(); + + // hNext must be registered with the taskbar if the call is to succeed + mNext->EnsureRegistration(); + } + if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +} // namespace widget +} // namespace mozilla |