forked from amazingfate/loongoffice
8f22ce5ea3e62a4837773f6958b8ce7ac96f811a "loplugin:fakebool (clang-cl)" had
tried to use auto to silence the warning about GetMessageW's misused BOOL return
type (where GetMessageW encodes more than just true vs. false), but that no
longer works after Clang 14 trunk
<c9e46219f3>
"[clang] retain type sugar in auto / template argument deduction", so just use
int here as the type for which BOOL is a typedef according to
<https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/9d81be47-232e-42cf-8f0d-7a3b29bf2eb2>
"BOOL".
Change-Id: Ie703676e858dba915de5c4496c02db82a2735e29
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/127298
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
842 lines
25 KiB
C++
842 lines
25 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* 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/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <sal/types.h>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <stdafx.h>
|
|
#include <stddef.h>
|
|
#include <syswinwrapper.hxx>
|
|
|
|
// windowserrorstring.hxx includes postwin.h, which #undef OPAQUE, so "#redef" it
|
|
#include <comphelper/windowserrorstring.hxx>
|
|
#ifdef OPAQUE
|
|
#error OPAQUE should not be defined!?
|
|
#endif
|
|
#define OPAQUE 2
|
|
|
|
static HCURSOR afxCursors[10] = { nullptr, };
|
|
static HBRUSH afxHalftoneBrush = nullptr;
|
|
|
|
namespace {
|
|
|
|
// the struct below is used to determine the qualities of a particular handle
|
|
struct AFX_HANDLEINFO
|
|
{
|
|
size_t nOffsetX; // offset within RECT for X coordinate
|
|
size_t nOffsetY; // offset within RECT for Y coordinate
|
|
int nCenterX; // adjust X by Width()/2 * this number
|
|
int nCenterY; // adjust Y by Height()/2 * this number
|
|
int nHandleX; // adjust X by handle size * this number
|
|
int nHandleY; // adjust Y by handle size * this number
|
|
int nInvertX; // handle converts to this when X inverted
|
|
int nInvertY; // handle converts to this when Y inverted
|
|
};
|
|
|
|
}
|
|
|
|
// this array describes all 8 handles (clock-wise)
|
|
const AFX_HANDLEINFO afxHandleInfo[] =
|
|
{
|
|
// corner handles (top-left, top-right, bottom-right, bottom-left
|
|
{ offsetof(RECT, left), offsetof(RECT, top), 0, 0, 0, 0, 1, 3 },
|
|
{ offsetof(RECT, right), offsetof(RECT, top), 0, 0, -1, 0, 0, 2 },
|
|
{ offsetof(RECT, right), offsetof(RECT, bottom), 0, 0, -1, -1, 3, 1 },
|
|
{ offsetof(RECT, left), offsetof(RECT, bottom), 0, 0, 0, -1, 2, 0 },
|
|
|
|
// side handles (top, right, bottom, left)
|
|
{ offsetof(RECT, left), offsetof(RECT, top), 1, 0, 0, 0, 4, 6 },
|
|
{ offsetof(RECT, right), offsetof(RECT, top), 0, 1, -1, 0, 7, 5 },
|
|
{ offsetof(RECT, left), offsetof(RECT, bottom), 1, 0, 0, -1, 6, 4 },
|
|
{ offsetof(RECT, left), offsetof(RECT, top), 0, 1, 0, 0, 5, 7 }
|
|
};
|
|
|
|
namespace {
|
|
|
|
// the struct below gives us information on the layout of a RECT struct and
|
|
// the relationship between its members
|
|
struct AFX_RECTINFO
|
|
{
|
|
size_t nOffsetAcross; // offset of opposite point (ie. left->right)
|
|
int nSignAcross; // sign relative to that point (ie. add/subtract)
|
|
};
|
|
|
|
}
|
|
|
|
// this array is indexed by the offset of the RECT member / sizeof(int)
|
|
const AFX_RECTINFO afxRectInfo[] =
|
|
{
|
|
{ offsetof(RECT, right), +1 },
|
|
{ offsetof(RECT, bottom), +1 },
|
|
{ offsetof(RECT, left), -1 },
|
|
{ offsetof(RECT, top), -1 },
|
|
};
|
|
|
|
|
|
static HBRUSH HalftoneBrush()
|
|
{
|
|
if (afxHalftoneBrush == nullptr)
|
|
{
|
|
WORD grayPattern[8];
|
|
for (int i = 0; i < 8; i++)
|
|
grayPattern[i] = static_cast<WORD>(0x5555 << (i & 1));
|
|
HBITMAP grayBitmap = CreateBitmap(8, 8, 1, 1, &grayPattern);
|
|
if (grayBitmap != nullptr)
|
|
{
|
|
afxHalftoneBrush = CreatePatternBrush(grayBitmap);
|
|
DeleteObject(grayBitmap);
|
|
}
|
|
}
|
|
return afxHalftoneBrush;
|
|
}
|
|
|
|
|
|
static void DrawDragRect(
|
|
HDC hDC,LPRECT lpRect,SIZE size,
|
|
LPRECT lpRectLast,SIZE sizeLast,
|
|
HBRUSH hBrush = nullptr,HBRUSH hBrushLast = nullptr)
|
|
{
|
|
// first, determine the update region and select it
|
|
HRGN rgnNew;
|
|
HRGN rgnOutside,rgnInside;
|
|
rgnOutside = CreateRectRgnIndirect(lpRect);
|
|
RECT rect = *lpRect;
|
|
InflateRect(&rect,-size.cx, -size.cy);
|
|
IntersectRect(&rect,&rect,lpRect);
|
|
rgnInside = CreateRectRgnIndirect(&rect);
|
|
rgnNew = CreateRectRgn(0, 0, 0, 0);
|
|
CombineRgn(rgnNew,rgnOutside,rgnInside,RGN_XOR);
|
|
|
|
HBRUSH hBrushOld = nullptr;
|
|
if (hBrush == nullptr)
|
|
hBrush = HalftoneBrush();
|
|
if (hBrushLast == nullptr)
|
|
hBrushLast = hBrush;
|
|
|
|
HRGN rgnLast(nullptr);
|
|
HRGN rgnUpdate(nullptr);
|
|
if (lpRectLast != nullptr)
|
|
{
|
|
// find difference between new region and old region
|
|
rgnLast = CreateRectRgn(0, 0, 0, 0);
|
|
SetRectRgn(
|
|
rgnOutside,
|
|
lpRectLast->left,
|
|
lpRectLast->top,
|
|
lpRectLast->right,
|
|
lpRectLast->bottom);
|
|
rect = *lpRectLast;
|
|
InflateRect(&rect,-sizeLast.cx, -sizeLast.cy);
|
|
IntersectRect(&rect,&rect, lpRectLast);
|
|
SetRectRgn(rgnInside,rect.left,rect.top,rect.right,rect.bottom);
|
|
CombineRgn(rgnLast,rgnOutside,rgnInside, RGN_XOR);
|
|
|
|
// // only diff them if brushes are the same
|
|
if (hBrush == hBrushLast)
|
|
{
|
|
rgnUpdate = CreateRectRgn(0, 0, 0, 0);
|
|
CombineRgn(rgnUpdate,rgnLast,rgnNew, RGN_XOR);
|
|
}
|
|
}
|
|
if (hBrush != hBrushLast && lpRectLast != nullptr)
|
|
{
|
|
// brushes are different -- erase old region first
|
|
SelectClipRgn(hDC,rgnLast);
|
|
GetClipBox(hDC,&rect);
|
|
hBrushOld = static_cast<HBRUSH>(SelectObject(hDC,static_cast<HGDIOBJ>(hBrushLast)));
|
|
PatBlt(hDC,rect.left,rect.top,(rect.right-rect.left),(rect.bottom-rect.top),PATINVERT);
|
|
|
|
SelectObject(hDC,static_cast<HGDIOBJ>(hBrushOld));
|
|
hBrushOld = nullptr;
|
|
}
|
|
|
|
// draw into the update/new region
|
|
SelectClipRgn(hDC,rgnUpdate);
|
|
|
|
GetClipBox(hDC,&rect);
|
|
hBrushOld = static_cast<HBRUSH>(SelectObject(hDC, static_cast<HGDIOBJ>(hBrush)));
|
|
PatBlt(hDC,rect.left, rect.top,(rect.right-rect.left),(rect.bottom-rect.top), PATINVERT);
|
|
|
|
// cleanup DC
|
|
if (hBrushOld != nullptr)
|
|
SelectObject(hDC, static_cast<HGDIOBJ>(hBrushOld));
|
|
SelectClipRgn(hDC,nullptr);
|
|
}
|
|
|
|
|
|
void winwrap::TransformRect(LPRECT rect,HWND pWnd,HWND pWndClipTo)
|
|
{
|
|
POINT pt;
|
|
pt.x = rect->left;pt.y = rect->top;
|
|
ClientToScreen(pWnd,&pt);
|
|
ScreenToClient(pWndClipTo,&pt);
|
|
rect->left = pt.x; rect->top = pt.y;
|
|
|
|
pt.x = rect->right;pt.y = rect->bottom;
|
|
ClientToScreen(pWnd,&pt);
|
|
ScreenToClient(pWndClipTo,&pt);
|
|
rect->right = pt.x; rect->bottom = pt.y;
|
|
}
|
|
|
|
|
|
static void NormalizeRect(LPRECT rp)
|
|
{
|
|
if(rp->left > rp->right) {
|
|
UINT tmp = rp->left;
|
|
rp->left = rp->right;
|
|
rp->right = tmp;
|
|
}
|
|
|
|
if(rp->top > rp->bottom) {
|
|
UINT tmp = rp->top;
|
|
rp->top = rp->bottom;
|
|
rp->bottom = tmp;
|
|
}
|
|
}
|
|
|
|
|
|
using namespace winwrap;
|
|
|
|
|
|
Tracker::Tracker()
|
|
{
|
|
}
|
|
|
|
|
|
Tracker::Tracker(LPCRECT lpSrcRect, UINT nStyle)
|
|
{
|
|
Construct();
|
|
CopyRect(&m_rect,lpSrcRect);
|
|
m_nStyle = nStyle;
|
|
}
|
|
|
|
static HBRUSH afxHatchBrush = nullptr;
|
|
static HPEN afxBlackDottedPen = nullptr;
|
|
static int afxHandleSize = 0;
|
|
|
|
|
|
void Tracker::Construct()
|
|
{
|
|
static bool bInitialized = false;
|
|
if (!bInitialized)
|
|
{
|
|
if (afxHatchBrush == nullptr)
|
|
{
|
|
// create the hatch pattern + bitmap
|
|
WORD hatchPattern[8];
|
|
WORD wPattern = 0x1111;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
hatchPattern[i] = wPattern;
|
|
hatchPattern[i+4] = wPattern;
|
|
wPattern <<= 1;
|
|
}
|
|
HBITMAP hatchBitmap = CreateBitmap(8, 8, 1, 1,&hatchPattern);
|
|
|
|
// create black hatched brush
|
|
afxHatchBrush = CreatePatternBrush(hatchBitmap);
|
|
DeleteObject(hatchBitmap);
|
|
}
|
|
|
|
if (afxBlackDottedPen == nullptr)
|
|
{
|
|
// create black dotted pen
|
|
afxBlackDottedPen = CreatePen(PS_DOT, 0, RGB(0, 0, 0));
|
|
}
|
|
|
|
// get default handle size from Windows profile setting
|
|
static const WCHAR szWindows[] = L"windows";
|
|
static const WCHAR szInplaceBorderWidth[] = L"oleinplaceborderwidth";
|
|
afxHandleSize = GetProfileIntW(szWindows, szInplaceBorderWidth, 4);
|
|
bInitialized = true;
|
|
|
|
afxCursors[0] = afxCursors[2] = LoadCursor(nullptr,IDC_SIZENWSE);
|
|
afxCursors[4] = afxCursors[6] = LoadCursor(nullptr,IDC_SIZENS);
|
|
afxCursors[1] = afxCursors[3] = LoadCursor(nullptr,IDC_SIZENESW);
|
|
afxCursors[5] = afxCursors[7] = LoadCursor(nullptr,IDC_SIZEWE);
|
|
afxCursors[8] = LoadCursor(nullptr,IDC_SIZEALL);
|
|
}
|
|
|
|
m_nStyle = 0;
|
|
m_nHandleSize = afxHandleSize;
|
|
m_sizeMin.cy = m_sizeMin.cx = m_nHandleSize*2;
|
|
|
|
SetRectEmpty(&m_rectLast);
|
|
m_sizeLast.cx = m_sizeLast.cy = 0;
|
|
m_bErase = FALSE;
|
|
m_bFinalErase = FALSE;
|
|
}
|
|
|
|
Tracker::~Tracker()
|
|
{
|
|
}
|
|
|
|
|
|
int Tracker::HitTest(POINT point) const
|
|
{
|
|
TrackerHit hitResult = hitNothing;
|
|
|
|
RECT rectTrue;
|
|
GetTrueRect(&rectTrue);
|
|
NormalizeRect(&rectTrue);
|
|
if (PtInRect(&rectTrue,point))
|
|
{
|
|
if ((m_nStyle & (resizeInside|resizeOutside)) != 0)
|
|
hitResult = static_cast<TrackerHit>(HitTestHandles(point));
|
|
else
|
|
hitResult = hitMiddle;
|
|
}
|
|
return hitResult;
|
|
}
|
|
|
|
|
|
BOOL Tracker::SetCursor(HWND pWnd, UINT nHitTest) const
|
|
{
|
|
// trackers should only be in client area
|
|
if (nHitTest != HTCLIENT)
|
|
return FALSE;
|
|
|
|
// convert cursor position to client co-ordinates
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
ScreenToClient(pWnd,&point);
|
|
|
|
// do hittest and normalize hit
|
|
int nHandle = HitTestHandles(point);
|
|
if (nHandle < 0)
|
|
return FALSE;
|
|
|
|
// need to normalize the hittest such that we get proper cursors
|
|
nHandle = NormalizeHit(nHandle);
|
|
|
|
// handle special case of hitting area between handles
|
|
// (logically the same -- handled as a move -- but different cursor)
|
|
if (nHandle == hitMiddle && !PtInRect(&m_rect,point))
|
|
{
|
|
// only for trackers with hatchedBorder (ie. in-place resizing)
|
|
if (m_nStyle & hatchedBorder)
|
|
nHandle = TrackerHit(9);
|
|
}
|
|
|
|
::SetCursor(afxCursors[nHandle]);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL Tracker::Track(HWND hWnd,POINT point,BOOL bAllowInvert,
|
|
HWND hWndClipTo)
|
|
{
|
|
// perform hit testing on the handles
|
|
int nHandle = HitTestHandles(point);
|
|
if (nHandle < 0)
|
|
{
|
|
// didn't hit a handle, so just return FALSE
|
|
return FALSE;
|
|
}
|
|
|
|
// otherwise, call helper function to do the tracking
|
|
m_bAllowInvert = bAllowInvert;
|
|
SetCursor(hWnd,nHandle);
|
|
return TrackHandle(nHandle, hWnd, point, hWndClipTo);
|
|
}
|
|
|
|
|
|
BOOL Tracker::TrackHandle(int nHandle,HWND hWnd,POINT point,HWND hWndClipTo)
|
|
{
|
|
// don't handle if capture already set
|
|
if (GetCapture() != nullptr)
|
|
return FALSE;
|
|
|
|
// save original width & height in pixels
|
|
int nWidth = m_rect.right - m_rect.left;
|
|
int nHeight = m_rect.bottom - m_rect.top;
|
|
|
|
// set capture to the window which received this message
|
|
SetCapture(hWnd);
|
|
UpdateWindow(hWnd);
|
|
if (hWndClipTo != nullptr)
|
|
UpdateWindow(hWndClipTo);
|
|
RECT rectSave = m_rect;
|
|
|
|
// find out what x/y coords we are supposed to modify
|
|
int *px, *py;
|
|
int xDiff, yDiff;
|
|
GetModifyPointers(nHandle, &px, &py, &xDiff, &yDiff);
|
|
xDiff = point.x - xDiff;
|
|
yDiff = point.y - yDiff;
|
|
|
|
// get DC for drawing
|
|
HDC hDrawDC;
|
|
if (hWndClipTo != nullptr)
|
|
{
|
|
// clip to arbitrary window by using adjusted Window DC
|
|
hDrawDC = GetDCEx(hWndClipTo,nullptr, DCX_CACHE);
|
|
}
|
|
else
|
|
{
|
|
// otherwise, just use normal DC
|
|
hDrawDC = GetDC(hWnd);
|
|
}
|
|
|
|
RECT rectOld;
|
|
bool bMoved = false;
|
|
|
|
// get messages until capture lost or cancelled/accepted
|
|
for (;;)
|
|
{
|
|
MSG msg;
|
|
int const bRet = GetMessageW(&msg, nullptr, 0, 0);
|
|
SAL_WARN_IF(-1 == bRet, "embedserv", "GetMessageW failed: " << WindowsErrorString(GetLastError()));
|
|
if (-1 == bRet || 0 == bRet)
|
|
break;
|
|
|
|
if (GetCapture() != hWnd)
|
|
break;
|
|
|
|
switch (msg.message)
|
|
{
|
|
// handle movement/accept messages
|
|
case WM_LBUTTONUP:
|
|
case WM_MOUSEMOVE:
|
|
rectOld = m_rect;
|
|
// handle resize cases (and part of move)
|
|
if (px != nullptr)
|
|
*px = static_cast<int>(static_cast<short>(LOWORD(msg.lParam))) - xDiff;
|
|
if (py != nullptr)
|
|
*py = static_cast<int>(static_cast<short>(HIWORD(msg.lParam))) - yDiff;
|
|
|
|
// handle move case
|
|
if (nHandle == hitMiddle)
|
|
{
|
|
m_rect.right = m_rect.left + nWidth;
|
|
m_rect.bottom = m_rect.top + nHeight;
|
|
}
|
|
// allow caller to adjust the rectangle if necessary
|
|
AdjustRect(nHandle,&m_rect);
|
|
|
|
// only redraw and callback if the rect actually changed!
|
|
m_bFinalErase = (msg.message == WM_LBUTTONUP);
|
|
if (!EqualRect(&rectOld,&m_rect) || m_bFinalErase)
|
|
{
|
|
if (bMoved)
|
|
{
|
|
m_bErase = TRUE;
|
|
DrawTrackerRect(&rectOld,hWndClipTo,hDrawDC,hWnd);
|
|
}
|
|
OnChangedRect(rectOld);
|
|
if (msg.message != WM_LBUTTONUP)
|
|
bMoved = true;
|
|
}
|
|
if (m_bFinalErase)
|
|
goto ExitLoop;
|
|
|
|
if (!EqualRect(&rectOld,&m_rect))
|
|
{
|
|
m_bErase = FALSE;
|
|
DrawTrackerRect(&m_rect,hWndClipTo,hDrawDC,hWnd);
|
|
}
|
|
break;
|
|
|
|
// handle cancel messages
|
|
case WM_KEYDOWN:
|
|
if (msg.wParam != VK_ESCAPE)
|
|
break;
|
|
[[fallthrough]];
|
|
case WM_RBUTTONDOWN:
|
|
if (bMoved)
|
|
{
|
|
m_bErase = m_bFinalErase = TRUE;
|
|
DrawTrackerRect(&m_rect, hWndClipTo, hDrawDC, hWnd);
|
|
}
|
|
m_rect = rectSave;
|
|
goto ExitLoop;
|
|
|
|
// just dispatch rest of the messages
|
|
default:
|
|
DispatchMessageW(&msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ExitLoop:
|
|
if (hWndClipTo != nullptr)
|
|
ReleaseDC(hWndClipTo,hDrawDC);
|
|
else
|
|
ReleaseDC(hWnd,hDrawDC);
|
|
ReleaseCapture();
|
|
|
|
// restore rect in case bMoved is still FALSE
|
|
if (!bMoved)
|
|
m_rect = rectSave;
|
|
m_bFinalErase = FALSE;
|
|
m_bErase = FALSE;
|
|
|
|
// return TRUE only if rect has changed
|
|
return !EqualRect(&rectSave,&m_rect);
|
|
}
|
|
|
|
|
|
void Tracker::OnChangedRect(const RECT& /*rectOld*/)
|
|
{
|
|
}
|
|
|
|
|
|
void Tracker::AdjustRect(int nHandle, LPRECT)
|
|
{
|
|
if(nHandle == hitMiddle)
|
|
return;
|
|
|
|
// convert the handle into locations within m_rect
|
|
int *px, *py;
|
|
GetModifyPointers(nHandle, &px, &py, nullptr, nullptr);
|
|
|
|
// enforce minimum width
|
|
int nNewWidth = m_rect.right - m_rect.left;
|
|
int nAbsWidth = m_bAllowInvert ? abs(nNewWidth) : nNewWidth;
|
|
if (px != nullptr && nAbsWidth < m_sizeMin.cx)
|
|
{
|
|
nNewWidth = nAbsWidth != 0 ? nNewWidth / nAbsWidth : 1;
|
|
const AFX_RECTINFO* pRectInfo =
|
|
&afxRectInfo[px - reinterpret_cast<int*>(&m_rect)];
|
|
*px = *reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&m_rect) + pRectInfo->nOffsetAcross) +
|
|
nNewWidth * m_sizeMin.cx * -pRectInfo->nSignAcross;
|
|
}
|
|
|
|
// enforce minimum height
|
|
int nNewHeight = m_rect.bottom - m_rect.top;
|
|
int nAbsHeight = m_bAllowInvert ? abs(nNewHeight) : nNewHeight;
|
|
if (py != nullptr && nAbsHeight < m_sizeMin.cy)
|
|
{
|
|
nNewHeight = nAbsHeight != 0 ? nNewHeight / nAbsHeight : 1;
|
|
const AFX_RECTINFO* pRectInfo =
|
|
&afxRectInfo[py - reinterpret_cast<int*>(&m_rect)];
|
|
*py = *reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&m_rect) + pRectInfo->nOffsetAcross) +
|
|
nNewHeight * m_sizeMin.cy * -pRectInfo->nSignAcross;
|
|
}
|
|
}
|
|
|
|
|
|
void Tracker::DrawTrackerRect(
|
|
LPRECT lpRect,HWND pWndClipTo,HDC pDC,HWND pWnd)
|
|
{
|
|
// first, normalize the rectangle for drawing
|
|
RECT rect = *lpRect;
|
|
NormalizeRect(&rect);
|
|
|
|
// convert to client coordinates
|
|
if (pWndClipTo != nullptr)
|
|
TransformRect(&rect,pWnd,pWndClipTo);
|
|
|
|
SIZE size;
|
|
size.cx = 0; size.cy = 0;
|
|
if (!m_bFinalErase)
|
|
{
|
|
// otherwise, size depends on the style
|
|
if (m_nStyle & hatchedBorder)
|
|
{
|
|
size.cx = size.cy = std::max(1,GetHandleSize(&rect)-1);
|
|
InflateRect(&rect,size.cx,size.cy);
|
|
}
|
|
else
|
|
{
|
|
size.cx = 1; // CX_BORDER;
|
|
size.cy = 1; // CY_BORDER;
|
|
}
|
|
}
|
|
|
|
// and draw it
|
|
if (m_bFinalErase || !m_bErase)
|
|
DrawDragRect(pDC,&rect,size,&m_rectLast,m_sizeLast);
|
|
|
|
// remember last rectangles
|
|
m_rectLast = rect;
|
|
m_sizeLast = size;
|
|
}
|
|
|
|
|
|
void Tracker::Draw(HDC hDC) const
|
|
{
|
|
// set initial DC state
|
|
SetMapMode(hDC,MM_TEXT);
|
|
SetViewportOrgEx(hDC,0, 0,nullptr);
|
|
SetWindowOrgEx(hDC,0, 0,nullptr);
|
|
|
|
// get normalized rectangle
|
|
RECT rect = m_rect;
|
|
NormalizeRect(&rect);
|
|
|
|
HPEN pOldPen = nullptr;
|
|
HBRUSH pOldBrush = nullptr;
|
|
HGDIOBJ pTemp;
|
|
int nOldROP;
|
|
|
|
// draw lines
|
|
if ((m_nStyle & (dottedLine|solidLine)) != 0)
|
|
{
|
|
if (m_nStyle & dottedLine)
|
|
pOldPen = static_cast<HPEN>(SelectObject(hDC,afxBlackDottedPen));
|
|
else
|
|
pOldPen = static_cast<HPEN>(SelectObject(hDC,reinterpret_cast<HGDIOBJ>(BLACK_PEN)));
|
|
pOldBrush = static_cast<HBRUSH>(SelectObject(hDC,reinterpret_cast<HGDIOBJ>(NULL_BRUSH)));
|
|
nOldROP = SetROP2(hDC,R2_COPYPEN);
|
|
InflateRect(&rect,+1, +1); // borders are one pixel outside
|
|
Rectangle(hDC,rect.left, rect.top, rect.right, rect.bottom);
|
|
SetROP2(hDC,nOldROP);
|
|
}
|
|
|
|
// if hatchBrush is going to be used, need to unrealize it
|
|
if ((m_nStyle & (hatchInside|hatchedBorder)) != 0)
|
|
UnrealizeObject(static_cast<HGDIOBJ>(afxHatchBrush));
|
|
|
|
// hatch inside
|
|
if ((m_nStyle & hatchInside) != 0)
|
|
{
|
|
pTemp = SelectObject(hDC,reinterpret_cast<HGDIOBJ>(NULL_PEN));
|
|
if (pOldPen == nullptr)
|
|
pOldPen = static_cast<HPEN>(pTemp);
|
|
pTemp = SelectObject(hDC,static_cast<HGDIOBJ>(afxHatchBrush));
|
|
if (pOldBrush == nullptr)
|
|
pOldBrush = static_cast<HBRUSH>(pTemp);
|
|
SetBkMode(hDC,TRANSPARENT);
|
|
nOldROP = SetROP2(hDC,R2_MASKNOTPEN);
|
|
Rectangle(hDC,rect.left+1, rect.top+1, rect.right, rect.bottom);
|
|
SetROP2(hDC,nOldROP);
|
|
}
|
|
|
|
// draw hatched border
|
|
if ((m_nStyle & hatchedBorder) != 0)
|
|
{
|
|
pTemp = SelectObject(hDC,static_cast<HGDIOBJ>(afxHatchBrush));
|
|
if (pOldBrush == nullptr)
|
|
pOldBrush = static_cast<HBRUSH>(pTemp);
|
|
SetBkMode(hDC,OPAQUE);
|
|
RECT rectTrue;
|
|
GetTrueRect(&rectTrue);
|
|
PatBlt(hDC,rectTrue.left, rectTrue.top, rectTrue.right-rectTrue.left,
|
|
rect.top-rectTrue.top, 0x000F0001 /* Pn */);
|
|
PatBlt(hDC,rectTrue.left, rect.bottom,
|
|
rectTrue.right-rectTrue.left,
|
|
rectTrue.bottom-rect.bottom, 0x000F0001 /* Pn */);
|
|
PatBlt(hDC,rectTrue.left, rect.top, rect.left-rectTrue.left,
|
|
rect.bottom-rect.top, 0x000F0001 /* Pn */);
|
|
PatBlt(hDC,rect.right, rect.top, rectTrue.right-rect.right,
|
|
rect.bottom-rect.top, 0x000F0001 /* Pn */);
|
|
}
|
|
|
|
// draw resize handles
|
|
if ((m_nStyle & (resizeInside|resizeOutside)) != 0)
|
|
{
|
|
UINT mask = GetHandleMask();
|
|
HBRUSH hbrush = CreateSolidBrush(RGB(0,0,0));
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
if (mask & (1<<i))
|
|
{
|
|
GetHandleRect(static_cast<TrackerHit>(i), &rect);
|
|
// FillSolidRect(hDC,rect, RGB(0, 0, 0));
|
|
FillRect(hDC,&rect,hbrush);
|
|
}
|
|
}
|
|
DeleteObject(hbrush);
|
|
}
|
|
|
|
// cleanup pDC state
|
|
if (pOldPen != nullptr)
|
|
SelectObject(hDC,pOldPen);
|
|
if (pOldBrush != nullptr)
|
|
SelectObject(hDC,pOldBrush);
|
|
RestoreDC(hDC,-1);
|
|
}
|
|
|
|
|
|
void Tracker::GetHandleRect(int nHandle,RECT* pHandleRect) const
|
|
{
|
|
// get normalized rectangle of the tracker
|
|
RECT rectT = m_rect;
|
|
NormalizeRect(&rectT);
|
|
if ((m_nStyle & (solidLine|dottedLine)) != 0)
|
|
InflateRect(&rectT,+1, +1);
|
|
|
|
// since the rectangle itself was normalized, we also have to invert the
|
|
// resize handles.
|
|
nHandle = NormalizeHit(nHandle);
|
|
|
|
// handle case of resize handles outside the tracker
|
|
int size = GetHandleSize();
|
|
if (m_nStyle & resizeOutside)
|
|
InflateRect(&rectT,size-1, size-1);
|
|
|
|
// calculate position of the resize handle
|
|
int nWidth = rectT.right - rectT.left;
|
|
int nHeight = rectT.bottom - rectT.top;
|
|
RECT rect;
|
|
const AFX_HANDLEINFO* pHandleInfo = &afxHandleInfo[nHandle];
|
|
rect.left = *reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&rectT) + pHandleInfo->nOffsetX);
|
|
rect.top = *reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&rectT) + pHandleInfo->nOffsetY);
|
|
rect.left += size * pHandleInfo->nHandleX;
|
|
rect.top += size * pHandleInfo->nHandleY;
|
|
rect.left += pHandleInfo->nCenterX * (nWidth - size) / 2;
|
|
rect.top += pHandleInfo->nCenterY * (nHeight - size) / 2;
|
|
rect.right = rect.left + size;
|
|
rect.bottom = rect.top + size;
|
|
|
|
*pHandleRect = rect;
|
|
}
|
|
|
|
|
|
int Tracker::GetHandleSize(LPRECT lpRect) const
|
|
{
|
|
LPCRECT rect = lpRect == nullptr ? &m_rect : lpRect;
|
|
|
|
int size = m_nHandleSize;
|
|
if (!(m_nStyle & resizeOutside))
|
|
{
|
|
// make sure size is small enough for the size of the rect
|
|
int sizeMax = std::min(abs(rect->right - rect->left),
|
|
abs(rect->bottom - rect->top));
|
|
if (size * 2 > sizeMax)
|
|
size = sizeMax / 2;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
|
|
UINT Tracker::GetHandleMask() const
|
|
{
|
|
UINT mask = 0x0F; // always have 4 corner handles
|
|
int size = m_nHandleSize*3;
|
|
if (abs(m_rect.right - m_rect.left) - size > 4)
|
|
mask |= 0x50;
|
|
if (abs(m_rect.bottom - m_rect.top) - size > 4)
|
|
mask |= 0xA0;
|
|
return mask;
|
|
}
|
|
|
|
|
|
void Tracker::GetTrueRect(LPRECT lpTrueRect) const
|
|
{
|
|
RECT rect = m_rect;
|
|
NormalizeRect(&rect);
|
|
int nInflateBy = 0;
|
|
if ((m_nStyle & (resizeOutside|hatchedBorder)) != 0)
|
|
nInflateBy += GetHandleSize() - 1;
|
|
if ((m_nStyle & (solidLine|dottedLine)) != 0)
|
|
++nInflateBy;
|
|
InflateRect(&rect,nInflateBy, nInflateBy);
|
|
*lpTrueRect = rect;
|
|
}
|
|
|
|
|
|
int Tracker::NormalizeHit(int nHandle) const
|
|
{
|
|
if (nHandle == hitMiddle || nHandle == hitNothing)
|
|
return nHandle;
|
|
const AFX_HANDLEINFO* pHandleInfo = &afxHandleInfo[nHandle];
|
|
if (m_rect.right - m_rect.left < 0)
|
|
{
|
|
nHandle = static_cast<TrackerHit>(pHandleInfo->nInvertX);
|
|
pHandleInfo = &afxHandleInfo[nHandle];
|
|
}
|
|
if (m_rect.bottom - m_rect.top < 0)
|
|
nHandle = static_cast<TrackerHit>(pHandleInfo->nInvertY);
|
|
return nHandle;
|
|
}
|
|
|
|
|
|
int Tracker::HitTestHandles(POINT point) const
|
|
{
|
|
RECT rect;
|
|
UINT mask = GetHandleMask();
|
|
|
|
// see if hit anywhere inside the tracker
|
|
GetTrueRect(&rect);
|
|
if (!PtInRect(&rect,point))
|
|
return hitNothing; // totally missed
|
|
|
|
// see if we hit a handle
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
if (mask & (1<<i))
|
|
{
|
|
GetHandleRect(static_cast<TrackerHit>(i), &rect);
|
|
if (PtInRect(&rect,point))
|
|
return static_cast<TrackerHit>(i);
|
|
}
|
|
}
|
|
|
|
// last of all, check for non-hit outside of object, between resize handles
|
|
if ((m_nStyle & hatchedBorder) == 0)
|
|
{
|
|
rect = m_rect;
|
|
NormalizeRect(&rect);
|
|
if ((m_nStyle & (dottedLine|solidLine)) != 0)
|
|
InflateRect(&rect,+1, +1);
|
|
if (!PtInRect(&rect,point))
|
|
return hitNothing; // must have been between resize handles
|
|
}
|
|
return hitMiddle; // no handle hit, but hit object (or object border)
|
|
}
|
|
|
|
|
|
void Tracker::GetModifyPointers(
|
|
int nHandle, int** ppx, int** ppy, int* px, int* py)
|
|
{
|
|
if (nHandle == hitMiddle)
|
|
nHandle = hitTopLeft; // same as hitting top-left
|
|
|
|
*ppx = nullptr;
|
|
*ppy = nullptr;
|
|
|
|
// fill in the part of the rect that this handle modifies
|
|
// (Note: handles that map to themselves along a given axis when that
|
|
// axis is inverted don't modify the value on that axis)
|
|
|
|
const AFX_HANDLEINFO* pHandleInfo = &afxHandleInfo[nHandle];
|
|
if (pHandleInfo->nInvertX != nHandle)
|
|
{
|
|
*ppx = reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&m_rect) + pHandleInfo->nOffsetX);
|
|
if (px != nullptr)
|
|
*px = **ppx;
|
|
}
|
|
else
|
|
{
|
|
// middle handle on X axis
|
|
if (px != nullptr)
|
|
*px = m_rect.left + (m_rect.left-m_rect.right) / 2;
|
|
}
|
|
if (pHandleInfo->nInvertY != nHandle)
|
|
{
|
|
*ppy = reinterpret_cast<int*>(reinterpret_cast<BYTE*>(&m_rect) + pHandleInfo->nOffsetY);
|
|
if (py != nullptr)
|
|
*py = **ppy;
|
|
}
|
|
else
|
|
{
|
|
// middle handle on Y axis
|
|
if (py != nullptr)
|
|
*py = m_rect.top + (m_rect.top-m_rect.bottom) / 2;
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|