@@ -32,6 +32,7 @@ using namespace std;
3232
3333
3434BOOL Gripper::_isRegistered = FALSE ;
35+ bool Gripper::_isOverlayClassRegistered = false ;
3536
3637static HWND hWndServer = NULL ;
3738static HHOOK hookMouse = NULL ;
@@ -481,53 +482,118 @@ void Gripper::doTabReordering(POINT pt)
481482 ::UpdateWindow (_hParent);
482483}
483484
484- // Changed behaviour (jg): Now this function handles erasing of drag-rectangles and drawing of
485- // new ones within one drawing step to the desktop. This is against flickering, but also it is
486- // necessary for the Vista Aero style - because in this case the control is given so much to
487- // the graphics driver, that accesses (especially read accesses) to the desktop window become
488- // too expensive to access it more than absolutely necessary. Besides, usage of the function
489- // ::LockWindowUpdate() was added, because with often redrawn windows in the background we had
490- // inconsistencies while erasing our drag-rectangle (because it could already have been erased
491- // on some places).
485+ // ============================================================================
486+ // Overlay window for drag rectangle rendering (tk)
492487//
493- // Parameter pPt==NULL says that only erasing is wanted and the drag-rectangle is no more needed,
494- // thatswhy this also leads to a call of ::LockWindowUpdate(NULL) to enable drawing by others again.
495- // The previously drawn rectangle is memoried within _rectPrev (and _bPtOldValid says if it already
496- // is valid - did not change this members name because didn't want change too much at once).
488+ // Uses a layered window spanning the entire virtual screen with color-key
489+ // transparency (magenta). The drag rectangle is drawn to a memory DC and
490+ // blitted to the overlay - only the rectangle frame is visible.
497491//
498- // I was too lazy to always draw four rectangles for the four edges of the drag-rectangle - it seems
499- // that drawing an outer rectangle first and then erasing the inner stuff by drawing a second,
500- // smaller rectangle inside seems to be not slower - wich comes not unawaited, because it is mostly
501- // hardware-driven and each single draw has its own fixed costs.
502- //
503- // For further solutions I think we should leave this classic way of dragging and better use
504- // alpha-blending and always move the whole content of the toolbars - so we could leave the
505- // ::LockWindowUpdate() behind us.
506- //
507- // Besides, while debugging into the dragging process please let the ::LockWindowUpdate() out,
508- // by #undef the USE_LOCKWINDOWUPDATE in gripper.h, because it works for your debugging window
509- // as well, of course. Or just try by this #define what difference it makes.
510- //
511- void Gripper::drawRectangle (const POINT* pPt)
492+ // Coordinates: Screen coordinates are translated to overlay-local by
493+ // subtracting the virtual screen origin (SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN).
494+ // ============================================================================
495+
496+ static constexpr COLORREF clrMagenta = RGB(255 , 0 , 255 );
497+
498+ LRESULT CALLBACK Gripper::overlayWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
499+ {
500+ return ::DefWindowProc (hwnd, msg, wParam, lParam);
501+ }
502+
503+ bool Gripper::createOverlayWindow ()
512504{
513- HBRUSH hbrushOrig= NULL ;
514- HBITMAP hbmOrig = NULL ;
515- RECT rc = {};
516- RECT rcNew = {};
517- RECT rcOld = _rcPrev;
505+ if (_hOverlayWnd)
506+ return true ;
507+
508+ if (!_isOverlayClassRegistered)
509+ {
510+ WNDCLASSEX wc = {};
511+ wc.cbSize = sizeof (wc);
512+ wc.lpfnWndProc = overlayWndProc;
513+ wc.hInstance = _hInst;
514+ wc.lpszClassName = L" NppGripperOverlay" ;
515+
516+ if (!::RegisterClassEx (&wc))
517+ return false ;
518+
519+ _isOverlayClassRegistered = true ;
520+ }
518521
519- // Get a screen device context with backstage redrawing disabled - to have a consistently
520- // and stable drawn rectangle while floating - keep in mind, that we must ensure, that
521- // finally ::LockWindowUpdate(NULL) will be called, to enable drawing for others again.
522- if (!_hdc)
522+ _xVirtScreen = ::GetSystemMetrics (SM_XVIRTUALSCREEN);
523+ _yVirtScreen = ::GetSystemMetrics (SM_YVIRTUALSCREEN);
524+ _overlayWidth = ::GetSystemMetrics (SM_CXVIRTUALSCREEN);
525+ _overlayHeight = ::GetSystemMetrics (SM_CYVIRTUALSCREEN);
526+
527+ _hOverlayWnd = ::CreateWindowEx (
528+ WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT,
529+ L" NppGripperOverlay" ,
530+ L" " ,
531+ WS_POPUP,
532+ _xVirtScreen, _yVirtScreen, _overlayWidth, _overlayHeight,
533+ nullptr , nullptr , _hInst, nullptr
534+ );
535+
536+ if (!_hOverlayWnd)
537+ return false ;
538+
539+ ::SetLayeredWindowAttributes (_hOverlayWnd, clrMagenta, 0 , LWA_COLORKEY);
540+
541+ HDC hdcScreen = ::GetDC (nullptr );
542+ _hdcOverlayMem = ::CreateCompatibleDC (hdcScreen);
543+ _hBitmapOverlay = ::CreateCompatibleBitmap (hdcScreen, _overlayWidth, _overlayHeight);
544+ _hOldBitmap = static_cast <HBITMAP>(::SelectObject (_hdcOverlayMem, _hBitmapOverlay));
545+ ::ReleaseDC (nullptr , hdcScreen);
546+
547+ // Fill with transparent color
548+ HBRUSH hBrushTransparent = ::CreateSolidBrush (clrMagenta);
549+ RECT rcFill = { 0 , 0 , _overlayWidth, _overlayHeight };
550+ ::FillRect (_hdcOverlayMem, &rcFill, hBrushTransparent);
551+ ::DeleteObject (hBrushTransparent);
552+
553+ _hdcOverlay = ::GetDC (_hOverlayWnd);
554+
555+ ::ShowWindow (_hOverlayWnd, SW_SHOWNOACTIVATE);
556+ ::BitBlt (_hdcOverlay, 0 , 0 , _overlayWidth, _overlayHeight, _hdcOverlayMem, 0 , 0 , SRCCOPY);
557+
558+ return true ;
559+ }
560+
561+ void Gripper::destroyOverlayWindow ()
562+ {
563+ if (_hdcOverlay && _hOverlayWnd)
564+ {
565+ ::ReleaseDC (_hOverlayWnd, _hdcOverlay);
566+ _hdcOverlay = nullptr ;
567+ }
568+ if (_hdcOverlayMem)
523569 {
524- HWND hWnd = ::GetDesktopWindow ();
525- #if defined (USE_LOCKWINDOWUPDATE)
526- _hdc = ::GetDCEx (hWnd, NULL , ::LockWindowUpdate (hWnd) ? DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE : DCX_WINDOW|DCX_CACHE);
527- #else
528- _hdc = ::GetDCEx (hWnd, NULL , DCX_WINDOW|DCX_CACHE);
529- #endif
570+ if (_hOldBitmap)
571+ ::SelectObject (_hdcOverlayMem, _hOldBitmap);
572+ ::DeleteDC (_hdcOverlayMem);
573+ _hdcOverlayMem = nullptr ;
574+ _hOldBitmap = nullptr ;
530575 }
576+ if (_hBitmapOverlay)
577+ {
578+ ::DeleteObject (_hBitmapOverlay);
579+ _hBitmapOverlay = nullptr ;
580+ }
581+ if (_hOverlayWnd)
582+ {
583+ ::DestroyWindow (_hOverlayWnd);
584+ _hOverlayWnd = nullptr ;
585+ }
586+ }
587+
588+ // Draw drag rectangle using layered overlay window
589+ //
590+ // Uses overlay window approach to fix clipping issues on multi-monitor setups
591+ // where the virtual screen has negative coordinates (Issue #16805).
592+ //
593+ void Gripper::drawRectangle (const POINT* pPt)
594+ {
595+ RECT rcNew = {};
596+ RECT rcOld = _rcPrev;
531597
532598 // Create a brush with the appropriate bitmap pattern to draw our drag rectangle
533599 if (!_hbm)
@@ -537,93 +603,109 @@ void Gripper::drawRectangle(const POINT* pPt)
537603
538604 if (pPt != NULL )
539605 {
540- // Determine whether to draw a solid drag rectangle or checkered
541- // ???(jg) solid or checked ??? - must have been an old comment, I didn't
542- // find here this difference, but at least it's a question of drag-rects size
543- //
606+ // getMovingRect() returns rect where right/bottom = width/height (not coordinates)
544607 getMovingRect (*pPt, &rcNew);
545- _rcPrev= rcNew; // save the new drawn rcNew
546-
547- // note that from here for handling purposes the right and bottom values of the rects
548- // contain width and height - its handsome, but i find it dangerous, but didn't want to
549- // change that already this time.
608+ _rcPrev = rcNew;
550609
551610 if (_bPtOldValid)
552611 {
553- // okay, there already a drag-rect has been drawn - and its position
554- // had been saved within the rectangle _rectPrev, wich already had been
555- // copied into rcOld in the beginning, and a new drag position
556- // is available, too.
557- // If now rcOld and rcNew are the same, just stop further handling to not
558- // draw the same drag-rectangle twice (this really happens, it should be
559- // better avoided anywhere earlier)
560- //
561- if (rcOld.left ==rcNew.left && rcOld.right ==rcNew.right && rcOld.top == rcNew.top && rcOld.bottom ==rcNew.bottom )
612+ if (rcOld.left == rcNew.left && rcOld.right == rcNew.right &&
613+ rcOld.top == rcNew.top && rcOld.bottom == rcNew.bottom )
562614 return ;
563-
564- rc.left = std::min<LONG>(rcOld.left , rcNew.left );
565- rc.top = std::min<LONG>(rcOld.top , rcNew.top );
566- rc.right = std::max<LONG>(rcOld.left + rcOld.right , rcNew.left + rcNew.right );
567- rc.bottom = std::max<LONG>(rcOld.top + rcOld.bottom , rcNew.top + rcNew.bottom );
568- rc.right -= rc.left ;
569- rc.bottom -= rc.top ;
570615 }
571- else rc = rcNew; // only new rect will be drawn
572616 }
573- else rc = rcOld; // only old rect will be drawn - to erase it
574617
575- // now rc contains the rectangle wich encloses all needed, new and/or previous rectangle
576- // because in the following we drive within a memory device context wich is limited to rc,
577- // we have to localize rcNew and rcOld within rc...
578- //
579- rcOld.left = rcOld.left - rc.left ;
580- rcOld.top = rcOld.top - rc.top ;
581- rcNew.left = rcNew.left - rc.left ;
582- rcNew.top = rcNew.top - rc.top ;
618+ // Create overlay window on first draw
619+ if (!_hOverlayWnd)
620+ {
621+ if (!createOverlayWindow ())
622+ return ;
623+ }
583624
584- HDC hdcMem = :: CreateCompatibleDC (_hdc);
585- HBITMAP hBm = :: CreateCompatibleBitmap (_hdc, rc. right , rc. bottom ) ;
586- hbrushOrig = (HBRUSH):: SelectObject (hdcMem, hBm) ;
625+ // Save absolute coordinates before any modifications
626+ RECT rcOldAbsolute = rcOld ;
627+ RECT rcNewAbsolute = rcNew ;
587628
588- ::SetBrushOrgEx (hdcMem, rc.left%8 , rc.top%8 , 0 );
589- hbmOrig = (HBITMAP)::SelectObject (hdcMem, _hbrush);
629+ // Calculate overlay-local coordinates
630+ LONG newOverlayX = rcNewAbsolute.left - _xVirtScreen;
631+ LONG newOverlayY = rcNewAbsolute.top - _yVirtScreen;
632+ LONG newWidth = rcNewAbsolute.right ;
633+ LONG newHeight = rcNewAbsolute.bottom ;
590634
591- ::BitBlt (hdcMem, 0 , 0 , rc.right, rc.bottom, _hdc, rc.left, rc.top, SRCCOPY);
635+ // Erase old rectangle
592636 if (_bPtOldValid)
593- { // erase the old drag-rectangle
594- ::PatBlt (hdcMem, rcOld.left , rcOld.top , rcOld.right , rcOld.bottom , PATINVERT);
595- ::PatBlt (hdcMem, rcOld.left+3 , rcOld.top+3 , rcOld.right-6 , rcOld.bottom-6 , PATINVERT);
637+ {
638+ HBRUSH hBrushTransparent = ::CreateSolidBrush (clrMagenta);
639+ LONG oldOverlayX = rcOldAbsolute.left - _xVirtScreen;
640+ LONG oldOverlayY = rcOldAbsolute.top - _yVirtScreen;
641+ LONG oldWidth = rcOldAbsolute.right ;
642+ LONG oldHeight = rcOldAbsolute.bottom ;
643+ RECT rcClear = { oldOverlayX, oldOverlayY, oldOverlayX + oldWidth, oldOverlayY + oldHeight };
644+ ::FillRect (_hdcOverlayMem, &rcClear, hBrushTransparent);
645+ ::DeleteObject (hBrushTransparent);
596646 }
597647
648+ // Draw new rectangle
598649 if (pPt != NULL )
599- { // draw the new drag-rectangle
600- ::PatBlt (hdcMem, rcNew.left , rcNew.top , rcNew.right , rcNew.bottom , PATINVERT);
601- ::PatBlt (hdcMem, rcNew.left+3 , rcNew.top+3 , rcNew.right-6 , rcNew.bottom-6 , PATINVERT);
650+ {
651+ HBRUSH hBrushTransparent = ::CreateSolidBrush (clrMagenta);
652+ RECT rcFrame = { newOverlayX, newOverlayY, newOverlayX + newWidth, newOverlayY + newHeight };
653+ ::FillRect (_hdcOverlayMem, &rcFrame, hBrushTransparent);
654+ ::DeleteObject (hBrushTransparent);
655+
656+ HBRUSH hBrushGray = ::CreateSolidBrush (RGB (128 , 128 , 128 ));
657+ RECT rcTop = { newOverlayX, newOverlayY, newOverlayX + newWidth, newOverlayY + 3 };
658+ ::FillRect (_hdcOverlayMem, &rcTop, hBrushGray);
659+ RECT rcBottom = { newOverlayX, newOverlayY + newHeight - 3 , newOverlayX + newWidth, newOverlayY + newHeight };
660+ ::FillRect (_hdcOverlayMem, &rcBottom, hBrushGray);
661+ RECT rcLeft = { newOverlayX, newOverlayY, newOverlayX + 3 , newOverlayY + newHeight };
662+ ::FillRect (_hdcOverlayMem, &rcLeft, hBrushGray);
663+ RECT rcRight = { newOverlayX + newWidth - 3 , newOverlayY, newOverlayX + newWidth, newOverlayY + newHeight };
664+ ::FillRect (_hdcOverlayMem, &rcRight, hBrushGray);
665+ ::DeleteObject (hBrushGray);
666+
667+ HBRUSH hOldBrush = static_cast <HBRUSH>(::SelectObject (_hdcOverlayMem, _hbrush));
668+ ::SetBrushOrgEx (_hdcOverlayMem, rcNewAbsolute.left % 8 , rcNewAbsolute.top % 8 , nullptr );
669+ ::PatBlt (_hdcOverlayMem, newOverlayX, newOverlayY, newWidth, 3 , PATINVERT);
670+ ::PatBlt (_hdcOverlayMem, newOverlayX, newOverlayY + newHeight - 3 , newWidth, 3 , PATINVERT);
671+ ::PatBlt (_hdcOverlayMem, newOverlayX, newOverlayY, 3 , newHeight, PATINVERT);
672+ ::PatBlt (_hdcOverlayMem, newOverlayX + newWidth - 3 , newOverlayY, 3 , newHeight, PATINVERT);
673+ ::SelectObject (_hdcOverlayMem, hOldBrush);
674+ }
675+
676+ // Copy to overlay window (only changed region for performance)
677+ LONG copyMinX = newOverlayX;
678+ LONG copyMinY = newOverlayY;
679+ LONG copyMaxX = newOverlayX + newWidth;
680+ LONG copyMaxY = newOverlayY + newHeight;
681+
682+ if (_bPtOldValid)
683+ {
684+ LONG oldOverlayX = rcOldAbsolute.left - _xVirtScreen;
685+ LONG oldOverlayY = rcOldAbsolute.top - _yVirtScreen;
686+ LONG oldWidth = rcOldAbsolute.right ;
687+ LONG oldHeight = rcOldAbsolute.bottom ;
688+
689+ if (oldOverlayX < copyMinX) copyMinX = oldOverlayX;
690+ if (oldOverlayY < copyMinY) copyMinY = oldOverlayY;
691+ if (oldOverlayX + oldWidth > copyMaxX) copyMaxX = oldOverlayX + oldWidth;
692+ if (oldOverlayY + oldHeight > copyMaxY) copyMaxY = oldOverlayY + oldHeight;
602693 }
603- ::BitBlt (_hdc, rc.left, rc.top, rc.right, rc.bottom, hdcMem, 0 , 0 , SRCCOPY);
604694
605- SelectObject (hdcMem, hbrushOrig);
606- SelectObject (hdcMem, hbmOrig);
607- DeleteObject (hBm);
608- DeleteDC (hdcMem);
695+ ::BitBlt (_hdcOverlay, copyMinX, copyMinY, copyMaxX - copyMinX, copyMaxY - copyMinY,
696+ _hdcOverlayMem, copyMinX, copyMinY, SRCCOPY);
609697
610698 if (pPt == NULL )
611699 {
612- #if defined(USE_LOCKWINDOWUPDATE)
613- ::LockWindowUpdate (NULL );
614- #endif
700+ destroyOverlayWindow ();
615701 _bPtOldValid = FALSE ;
616- if (_hdc)
617- {
618- ::ReleaseDC (0 , _hdc);
619- _hdc = NULL ;
620- }
621702 }
622703 else
704+ {
623705 _bPtOldValid = TRUE ;
706+ }
624707}
625708
626-
627709void Gripper::getMousePoints (const POINT& pt, POINT& ptPrev)
628710{
629711 ptPrev = _ptOld;
@@ -842,4 +924,3 @@ void Gripper::initTabInformation()
842924 _tcItem.cchTextMax = 64 ;
843925 ::SendMessage (_hTabSource, TCM_GETITEM, _iItem, reinterpret_cast <LPARAM>(&_tcItem));
844926}
845-
0 commit comments