2024-04-18 15:15:22 +02:00

1566 lines
43 KiB
C++

// This file is part of Notepad++ project
// Copyright (C)2021 Don HO <don.h@free.fr>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <stdexcept>
#include "TabBar.h"
#include "Parameters.h"
#define IDC_DRAG_TAB 1404
#define IDC_DRAG_INTERDIT_TAB 1405
#define IDC_DRAG_PLUS_TAB 1406
#define IDC_DRAG_OUT_TAB 1407
bool TabBarPlus::_doDragNDrop = false;
bool TabBarPlus::_drawTopBar = true;
bool TabBarPlus::_drawInactiveTab = true;
bool TabBarPlus::_drawTabCloseButton = false;
bool TabBarPlus::_isDbClk2Close = false;
bool TabBarPlus::_isCtrlVertical = false;
bool TabBarPlus::_isCtrlMultiLine = false;
COLORREF TabBarPlus::_activeTextColour = ::GetSysColor(COLOR_BTNTEXT);
COLORREF TabBarPlus::_activeTopBarFocusedColour = RGB(250, 170, 60);
COLORREF TabBarPlus::_activeTopBarUnfocusedColour = RGB(250, 210, 150);
COLORREF TabBarPlus::_inactiveTextColour = grey;
COLORREF TabBarPlus::_inactiveBgColour = RGB(192, 192, 192);
HWND TabBarPlus::_hwndArray[nbCtrlMax] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
int TabBarPlus::_nbCtrl = 0;
void TabBar::init(HINSTANCE hInst, HWND parent, bool isVertical, bool isMultiLine)
{
Window::init(hInst, parent);
int vertical = isVertical?(TCS_VERTICAL | TCS_MULTILINE | TCS_RIGHTJUSTIFY):0;
_isVertical = isVertical;
_isMultiLine = isMultiLine;
INITCOMMONCONTROLSEX icce{};
icce.dwSize = sizeof(icce);
icce.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icce);
int multiLine = isMultiLine ? TCS_MULTILINE : 0;
int style = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE |\
TCS_FOCUSNEVER | TCS_TABS | WS_TABSTOP | vertical | multiLine;
_hSelf = ::CreateWindowEx(
0,
WC_TABCONTROL,
TEXT("Tab"),
style,
0, 0, 0, 0,
_hParent,
NULL,
_hInst,
0);
if (!_hSelf)
{
throw std::runtime_error("TabBar::init : CreateWindowEx() function return null");
}
if (_hFont == nullptr)
{
const UINT dpi = DPIManagerV2::getDpiForWindow(_hParent);
LOGFONT lf{ DPIManagerV2::getDefaultGUIFontForDpi(dpi) };
lf.lfHeight = DPIManagerV2::scaleFont(8, dpi);
_hFont = ::CreateFontIndirect(&lf);
::SendMessage(_hSelf, WM_SETFONT, reinterpret_cast<WPARAM>(_hFont), 0);
}
}
void TabBar::destroy()
{
if (_hFont)
{
::DeleteObject(_hFont);
_hFont = nullptr;
}
if (_hLargeFont)
{
::DeleteObject(_hLargeFont);
_hLargeFont = nullptr;
}
if (_hVerticalFont)
{
::DeleteObject(_hVerticalFont);
_hVerticalFont = nullptr;
}
if (_hVerticalLargeFont)
{
::DeleteObject(_hVerticalLargeFont);
_hVerticalLargeFont = nullptr;
}
::DestroyWindow(_hSelf);
_hSelf = nullptr;
}
int TabBar::insertAtEnd(const TCHAR *subTabName)
{
TCITEM tie{};
tie.mask = TCIF_TEXT | TCIF_IMAGE;
int index = -1;
if (_hasImgLst)
index = 0;
tie.iImage = index;
tie.pszText = (TCHAR *)subTabName;
return int(::SendMessage(_hSelf, TCM_INSERTITEM, _nbItem++, reinterpret_cast<LPARAM>(&tie)));
}
void TabBar::getCurrentTitle(TCHAR *title, int titleLen)
{
TCITEM tci{};
tci.mask = TCIF_TEXT;
tci.pszText = title;
tci.cchTextMax = titleLen-1;
::SendMessage(_hSelf, TCM_GETITEM, getCurrentTabIndex(), reinterpret_cast<LPARAM>(&tci));
}
void TabBar::setFont(const TCHAR *fontName, int fontSize)
{
if (_hFont)
::DeleteObject(_hFont);
_hFont = ::CreateFont( fontSize, 0,
(_isVertical) ? 900:0,
(_isVertical) ? 900:0,
FW_NORMAL,
0, 0, 0, 0,
0, 0, 0, 0,
fontName);
if (_hFont)
::SendMessage(_hSelf, WM_SETFONT, reinterpret_cast<WPARAM>(_hFont), 0);
}
void TabBar::activateAt(int index) const
{
if (getCurrentTabIndex() != index)
{
// TCM_SETCURFOCUS is busted on WINE/ReactOS for single line (non-TCS_BUTTONS) tabs...
// We need it on Windows for multi-line tabs or multiple tabs can appear pressed.
if (::GetWindowLongPtr(_hSelf, GWL_STYLE) & TCS_BUTTONS)
{
::SendMessage(_hSelf, TCM_SETCURFOCUS, index, 0);
}
::SendMessage(_hSelf, TCM_SETCURSEL, index, 0);
}
}
void TabBar::deletItemAt(size_t index)
{
if (index == _nbItem - 1)
{
//prevent invisible tabs. If last visible tab is removed, other tabs are put in view but not redrawn
//Therefore, scroll one tab to the left if only one tab visible
if (_nbItem > 1)
{
RECT itemRect{};
::SendMessage(_hSelf, TCM_GETITEMRECT, index, reinterpret_cast<LPARAM>(&itemRect));
if (itemRect.left < 5) //if last visible tab, scroll left once (no more than 5px away should be safe, usually 2px depending on the drawing)
{
//To scroll the tab control to the left, use the WM_HSCROLL notification
//Doesn't really seem to be documented anywhere, but the values do match the message parameters
//The up/down control really is just some sort of scrollbar
//There seems to be no negative effect on any internal state of the tab control or the up/down control
int wParam = MAKEWPARAM(SB_THUMBPOSITION, index - 1);
::SendMessage(_hSelf, WM_HSCROLL, wParam, 0);
wParam = MAKEWPARAM(SB_ENDSCROLL, index - 1);
::SendMessage(_hSelf, WM_HSCROLL, wParam, 0);
}
}
}
::SendMessage(_hSelf, TCM_DELETEITEM, index, 0);
_nbItem--;
}
void TabBar::setImageList(HIMAGELIST himl)
{
_hasImgLst = true;
::SendMessage(_hSelf, TCM_SETIMAGELIST, 0, reinterpret_cast<LPARAM>(himl));
}
void TabBar::reSizeTo(RECT & rc2Ajust)
{
RECT rowRect{};
int rowCount = 0, tabsHight = 0;
// Important to do that!
// Otherwise, the window(s) it contains will take all the resouce of CPU
// We don't need to resize the contained windows if they are even invisible anyway
display(rc2Ajust.right > 10);
RECT rc = rc2Ajust;
Window::reSizeTo(rc);
// Do our own calculations because TabCtrl_AdjustRect doesn't work
// on vertical or multi-lined tab controls
rowCount = TabCtrl_GetRowCount(_hSelf);
TabCtrl_GetItemRect(_hSelf, 0, &rowRect);
int larger = _isVertical ? rowRect.right : rowRect.bottom;
int smaller = _isVertical ? rowRect.left : rowRect.top;
int marge = 0;
LONG_PTR style = ::GetWindowLongPtr(_hSelf, GWL_STYLE);
if (rowCount == 1)
{
style &= ~TCS_BUTTONS;
}
else // (rowCount >= 2)
{
style |= TCS_BUTTONS;
marge = (rowCount - 2) * 3; // in TCS_BUTTONS mode, each row has few pixels higher
}
::SetWindowLongPtr(_hSelf, GWL_STYLE, style);
tabsHight = rowCount * (larger - smaller) + marge;
tabsHight += GetSystemMetrics(_isVertical ? SM_CXEDGE : SM_CYEDGE);
if (_isVertical)
{
rc2Ajust.left += tabsHight;
rc2Ajust.right -= tabsHight;
}
else
{
rc2Ajust.top += tabsHight;
rc2Ajust.bottom -= tabsHight;
}
}
void TabBarPlus::destroy()
{
TabBar::destroy();
::DestroyWindow(_tooltips);
_tooltips = NULL;
}
void TabBarPlus::init(HINSTANCE hInst, HWND parent, bool isVertical, bool isMultiLine)
{
Window::init(hInst, parent);
int vertical = isVertical?(TCS_VERTICAL | TCS_MULTILINE | TCS_RIGHTJUSTIFY):0;
_isVertical = isVertical;
_isMultiLine = isMultiLine;
INITCOMMONCONTROLSEX icce{};
icce.dwSize = sizeof(icce);
icce.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icce);
int multiLine = isMultiLine ? TCS_MULTILINE : 0;
int style = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | TCS_FOCUSNEVER | TCS_TABS | vertical | multiLine;
style |= TCS_OWNERDRAWFIXED;
_hSelf = ::CreateWindowEx(
0,
WC_TABCONTROL,
TEXT("Tab"),
style,
0, 0, 0, 0,
_hParent,
NULL,
_hInst,
0);
if (!_hSelf)
{
throw std::runtime_error("TabBarPlus::init : CreateWindowEx() function return null");
}
_tooltips = ::CreateWindowEx(
0,
TOOLTIPS_CLASS,
NULL,
TTS_ALWAYSTIP | TTS_NOPREFIX,
0, 0, 0, 0,
_hParent,
NULL,
_hInst,
0);
if (!_tooltips)
{
throw std::runtime_error("TabBarPlus::init : tooltip CreateWindowEx() function return null");
}
NppDarkMode::setDarkTooltips(_tooltips, NppDarkMode::ToolTipsType::tooltip);
::SendMessage(_hSelf, TCM_SETTOOLTIPS, reinterpret_cast<WPARAM>(_tooltips), 0);
if (!_hwndArray[_nbCtrl])
{
_hwndArray[_nbCtrl] = _hSelf;
_ctrlID = _nbCtrl;
}
else
{
int i = 0;
bool found = false;
for ( ; i < nbCtrlMax && !found ; ++i)
if (!_hwndArray[i])
found = true;
if (!found)
{
_ctrlID = -1;
destroy();
throw std::runtime_error("TabBarPlus::init : Tab Control error - Tab Control # is over its limit");
}
_hwndArray[i] = _hSelf;
_ctrlID = i;
}
++_nbCtrl;
::SetWindowLongPtr(_hSelf, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
_tabBarDefaultProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(_hSelf, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(TabBarPlus_Proc)));
const UINT dpi = DPIManagerV2::getDpiForWindow(_hParent);
LOGFONT lf{ DPIManagerV2::getDefaultGUIFontForDpi(dpi) };
LOGFONT lfVer{ lf };
if (_hFont != nullptr)
{
::DeleteObject(_hFont);
_hFont = nullptr;
}
_hFont = ::CreateFontIndirect(&lf);
lf.lfWeight = FW_HEAVY;
lf.lfHeight = DPIManagerV2::scaleFont(10, dpi);
_hLargeFont = ::CreateFontIndirect(&lf);
lfVer.lfEscapement = 900;
lfVer.lfOrientation = 900;
_hVerticalFont = CreateFontIndirect(&lfVer);
lfVer.lfWeight = FW_HEAVY;
_hVerticalLargeFont = CreateFontIndirect(&lfVer);
}
void TabBarPlus::doOwnerDrawTab()
{
::SendMessage(_hwndArray[0], TCM_SETPADDING, 0, MAKELPARAM(6, 0));
for (int i = 0 ; i < _nbCtrl ; ++i)
{
if (_hwndArray[i])
{
LONG_PTR style = ::GetWindowLongPtr(_hwndArray[i], GWL_STYLE);
if (isOwnerDrawTab())
style |= TCS_OWNERDRAWFIXED;
else
style &= ~TCS_OWNERDRAWFIXED;
::SetWindowLongPtr(_hwndArray[i], GWL_STYLE, style);
::InvalidateRect(_hwndArray[i], NULL, TRUE);
const int paddingSizeDynamicW = NppParameters::getInstance()._dpiManager.scaleX(6);
const int paddingSizePlusClosebuttonDynamicW = NppParameters::getInstance()._dpiManager.scaleX(10);
::SendMessage(_hwndArray[i], TCM_SETPADDING, 0, MAKELPARAM(_drawTabCloseButton ? paddingSizePlusClosebuttonDynamicW : paddingSizeDynamicW, 0));
}
}
}
void TabBarPlus::setColour(COLORREF colour2Set, tabColourIndex i)
{
switch (i)
{
case activeText:
_activeTextColour = colour2Set;
break;
case activeFocusedTop:
_activeTopBarFocusedColour = colour2Set;
break;
case activeUnfocusedTop:
_activeTopBarUnfocusedColour = colour2Set;
break;
case inactiveText:
_inactiveTextColour = colour2Set;
break;
case inactiveBg :
_inactiveBgColour = colour2Set;
break;
default :
return;
}
doOwnerDrawTab();
}
void TabBarPlus::currentTabToStart()
{
int currentTabIndex = getCurrentTabIndex();
if (currentTabIndex <= 0)
return;
for (int i = currentTabIndex, j = currentTabIndex - 1; j >= 0; --i, --j)
{
exchangeTabItemData(i, j);
}
}
void TabBarPlus::currentTabToEnd()
{
int currentTabIndex = getCurrentTabIndex();
if (currentTabIndex >= static_cast<int>(_nbItem))
return;
for (int i = currentTabIndex, j = currentTabIndex + 1; j < static_cast<int>(_nbItem); ++i, ++j)
{
exchangeTabItemData(i, j);
}
}
void TabBarPlus::doVertical()
{
for (int i = 0 ; i < _nbCtrl ; ++i)
{
if (_hwndArray[i])
SendMessage(_hwndArray[i], WM_TABSETSTYLE, isVertical(), TCS_VERTICAL);
}
}
void TabBarPlus::doMultiLine()
{
for (int i = 0 ; i < _nbCtrl ; ++i)
{
if (_hwndArray[i])
SendMessage(_hwndArray[i], WM_TABSETSTYLE, isMultiLine(), TCS_MULTILINE);
}
}
void TabBarPlus::notify(int notifyCode, int tabIndex)
{
TBHDR nmhdr{};
nmhdr._hdr.hwndFrom = _hSelf;
nmhdr._hdr.code = notifyCode;
nmhdr._hdr.idFrom = reinterpret_cast<UINT_PTR>(this);
nmhdr._tabOrigin = tabIndex;
::SendMessage(_hParent, WM_NOTIFY, 0, reinterpret_cast<LPARAM>(&nmhdr));
}
void TabBarPlus::trackMouseEvent(DWORD event2check)
{
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(tme);
tme.dwFlags = event2check;
tme.hwndTrack = _hSelf;
TrackMouseEvent(&tme);
}
LRESULT TabBarPlus::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch (Message)
{
// Custom window message to change tab control style on the fly
case WM_TABSETSTYLE:
{
LONG_PTR style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
if (wParam > 0)
style |= lParam;
else
style &= ~lParam;
_isVertical = ((style & TCS_VERTICAL) != 0);
_isMultiLine = ((style & TCS_MULTILINE) != 0);
::SetWindowLongPtr(hwnd, GWL_STYLE, style);
::InvalidateRect(hwnd, NULL, TRUE);
return TRUE;
}
case NPPM_INTERNAL_REFRESHDARKMODE:
{
NppDarkMode::setDarkTooltips(hwnd, NppDarkMode::ToolTipsType::tabbar);
return TRUE;
}
case WM_MOUSEWHEEL:
{
// ..............................................................................
// MOUSEWHEEL:
// will scroll the tab bar area (similar to Firefox's tab scrolling),
// it only happens if not in multi-line mode and at least one tab is hidden
// ..............................................................................
// CTRL + MOUSEWHEEL:
// will do previous/next tab WITH scroll wrapping (endless loop)
// ..............................................................................
// SHIFT + MOUSEWHEEL:
// if _doDragNDrop is enabled, then moves the tab, otherwise switches
// to previous/next tab WITHOUT scroll wrapping (stops at first/last tab)
// ..............................................................................
// CTRL + SHIFT + MOUSEWHEEL:
// will switch to the first/last tab
// ..............................................................................
if (_isDragging)
return TRUE;
const bool isForward = ((short)HIWORD(wParam)) < 0; // wheel rotation towards the user will be considered as forward direction
const int lastTabIndex = static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETITEMCOUNT, 0, 0) - 1);
if ((wParam & MK_CONTROL) && (wParam & MK_SHIFT))
{
setActiveTab((isForward ? lastTabIndex : 0));
}
else if ((wParam & MK_SHIFT) && _doDragNDrop)
{
int oldTabIndex = static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0));
int newTabIndex = oldTabIndex + (isForward ? 1 : -1);
if (newTabIndex < 0)
{
newTabIndex = lastTabIndex; // wrap scrolling
}
else if (newTabIndex > lastTabIndex)
{
newTabIndex = 0; // wrap scrolling
}
if (oldTabIndex != newTabIndex)
{
exchangeTabItemData(oldTabIndex, newTabIndex);
}
}
else if (wParam & (MK_CONTROL | MK_SHIFT))
{
int tabIndex = static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0) + (isForward ? 1 : -1));
if (tabIndex < 0)
{
if (wParam & MK_CONTROL)
tabIndex = lastTabIndex; // wrap scrolling
else
return TRUE;
}
else if (tabIndex > lastTabIndex)
{
if (wParam & MK_CONTROL)
tabIndex = 0; // wrap scrolling
else
return TRUE;
}
setActiveTab(tabIndex);
}
else if (!_isMultiLine) // don't scroll if in multi-line mode
{
RECT rcTabCtrl{}, rcLastTab{};
::SendMessage(_hSelf, TCM_GETITEMRECT, lastTabIndex, reinterpret_cast<LPARAM>(&rcLastTab));
::GetClientRect(_hSelf, &rcTabCtrl);
// get index of the first visible tab
TC_HITTESTINFO hti{};
LONG xy = NppParameters::getInstance()._dpiManager.scaleX(12); // an arbitrary coordinate inside the first visible tab
hti.pt = { xy, xy };
int scrollTabIndex = static_cast<int32_t>(::SendMessage(_hSelf, TCM_HITTEST, 0, reinterpret_cast<LPARAM>(&hti)));
if (scrollTabIndex < 1 && (_isVertical ? rcLastTab.bottom < rcTabCtrl.bottom : rcLastTab.right < rcTabCtrl.right)) // nothing to scroll
return TRUE;
// maximal width/height of the msctls_updown32 class (arrow box in the tab bar),
// this area may hide parts of the last tab and needs to be excluded
LONG maxLengthUpDownCtrl = NppParameters::getInstance()._dpiManager.scaleX(44); // sufficient static value
// scroll forward as long as the last tab is hidden; scroll backward till the first tab
if ((_isVertical ? ((rcTabCtrl.bottom - rcLastTab.bottom) < maxLengthUpDownCtrl) : ((rcTabCtrl.right - rcLastTab.right) < maxLengthUpDownCtrl)) || !isForward)
{
if (isForward)
++scrollTabIndex;
else
--scrollTabIndex;
if (scrollTabIndex < 0 || scrollTabIndex > lastTabIndex)
return TRUE;
// clear hover state of the close button,
// WM_MOUSEMOVE won't handle this properly since the tab position will change
if (_isCloseHover)
{
_isCloseHover = false;
::InvalidateRect(_hSelf, &_currentHoverTabRect, false);
}
::SendMessage(_hSelf, WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, scrollTabIndex), 0);
}
}
return TRUE;
}
case WM_LBUTTONDOWN :
{
if (::GetWindowLongPtr(_hSelf, GWL_STYLE) & TCS_BUTTONS)
{
int nTab = getTabIndexAt(LOWORD(lParam), HIWORD(lParam));
if (nTab != -1 && nTab != static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0)))
{
setActiveTab(nTab);
}
}
if (_drawTabCloseButton)
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
if (_closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical))
{
_whichCloseClickDown = getTabIndexAt(xPos, yPos);
::SendMessage(_hParent, WM_COMMAND, IDM_VIEW_REFRESHTABAR, 0);
return TRUE;
}
}
::CallWindowProc(_tabBarDefaultProc, hwnd, Message, wParam, lParam);
int currentTabOn = static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0));
if (wParam == 2)
return TRUE;
if (_doDragNDrop)
{
_mightBeDragging = true;
}
notify(NM_CLICK, currentTabOn);
return TRUE;
}
case WM_RBUTTONDOWN : //rightclick selects tab aswell
{
// TCS_BUTTONS doesn't select the tab
if (::GetWindowLongPtr(_hSelf, GWL_STYLE) & TCS_BUTTONS)
{
int nTab = getTabIndexAt(LOWORD(lParam), HIWORD(lParam));
if (nTab != -1 && nTab != static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0)))
{
setActiveTab(nTab);
}
}
::CallWindowProc(_tabBarDefaultProc, hwnd, WM_LBUTTONDOWN, wParam, lParam);
return TRUE;
}
case WM_MOUSEMOVE :
{
if (_mightBeDragging && !_isDragging)
{
// Grrr! Who has stolen focus and eaten the WM_LBUTTONUP?!
if (GetKeyState(VK_LBUTTON) >= 0)
{
_mightBeDragging = false;
_dragCount = 0;
}
else if (++_dragCount > 2)
{
int tabSelected = static_cast<int32_t>(::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0));
if (tabSelected >= 0)
{
_nSrcTab = _nTabDragged = tabSelected;
_isDragging = true;
// TLS_BUTTONS is already captured on Windows and will break on ::SetCapture
// However, this is not the case for WINE/ReactOS and must ::SetCapture
if (::GetCapture() != _hSelf)
{
::SetCapture(hwnd);
}
}
}
}
POINT p{};
p.x = LOWORD(lParam);
p.y = HIWORD(lParam);
if (_isDragging)
{
exchangeItemData(p);
// Get cursor position of "Screen"
// For using the function "WindowFromPoint" afterward!!!
::GetCursorPos(&_draggingPoint);
draggingCursor(_draggingPoint);
return TRUE;
}
else
{
bool isFromTabToTab = false;
int iTabNow = getTabIndexAt(p.x, p.y); // _currentHoverTabItem keeps previous value, and it need to be updated
if (_currentHoverTabItem == iTabNow && _currentHoverTabItem != -1) // mouse moves arround in the same tab
{
// do nothing
}
else if (iTabNow == -1 && _currentHoverTabItem != -1) // mouse is no more on any tab, set hover -1
{
_currentHoverTabItem = -1;
// send mouse leave notif
notify(TCN_MOUSELEAVING, -1);
}
else if (iTabNow != -1 && _currentHoverTabItem == -1) // mouse is just entered in a tab zone
{
_currentHoverTabItem = iTabNow;
notify(TCN_MOUSEHOVERING, _currentHoverTabItem);
}
else if (iTabNow != -1 && _currentHoverTabItem != -1 && _currentHoverTabItem != iTabNow) // mouse is being moved from a tab and entering into another tab
{
isFromTabToTab = true;
_whichCloseClickDown = -1;
// set current hovered
_currentHoverTabItem = iTabNow;
// send mouse enter notif
notify(TCN_MOUSEHOVERSWITCHING, _currentHoverTabItem);
}
else if (iTabNow == -1 && _currentHoverTabItem == -1) // mouse is already outside
{
// do nothing
}
if (_drawTabCloseButton)
{
RECT currentHoverTabRectOld = _currentHoverTabRect;
bool isCloseHoverOld = _isCloseHover;
if (_currentHoverTabItem != -1) // is hovering
{
::SendMessage(_hSelf, TCM_GETITEMRECT, _currentHoverTabItem, reinterpret_cast<LPARAM>(&_currentHoverTabRect));
_isCloseHover = _closeButtonZone.isHit(p.x, p.y, _currentHoverTabRect, _isVertical);
}
else
{
SetRectEmpty(&_currentHoverTabRect);
_isCloseHover = false;
}
if (isFromTabToTab || _isCloseHover != isCloseHoverOld)
{
if (isCloseHoverOld && (isFromTabToTab || !_isCloseHover))
InvalidateRect(hwnd, &currentHoverTabRectOld, FALSE);
if (_isCloseHover)
InvalidateRect(hwnd, &_currentHoverTabRect, FALSE);
}
if (_isCloseHover)
{
// Mouse moves out from close zone will send WM_MOUSELEAVE message
trackMouseEvent(TME_LEAVE);
}
}
// Mouse moves out from tab zone will send WM_MOUSELEAVE message
// but it doesn't track mouse moving from a tab to another
trackMouseEvent(TME_LEAVE);
}
break;
}
case WM_MOUSELEAVE:
{
if (_isCloseHover)
InvalidateRect(hwnd, &_currentHoverTabRect, FALSE);
_currentHoverTabItem = -1;
_whichCloseClickDown = -1;
SetRectEmpty(&_currentHoverTabRect);
_isCloseHover = false;
notify(TCN_MOUSELEAVING, _currentHoverTabItem);
break;
}
case WM_LBUTTONUP :
{
_mightBeDragging = false;
_dragCount = 0;
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
int currentTabOn = getTabIndexAt(xPos, yPos);
if (_isDragging)
{
if (::GetCapture() == _hSelf)
{
::ReleaseCapture();
}
else
{
_isDragging = false;
}
notify(_isDraggingInside?TCN_TABDROPPED:TCN_TABDROPPEDOUTSIDE, currentTabOn);
return TRUE;
}
if (_drawTabCloseButton)
{
if ((_whichCloseClickDown == currentTabOn) && _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical))
{
notify(TCN_TABDELETE, currentTabOn);
_whichCloseClickDown = -1;
// Get the next tab at same position
// If valid tab is found then
// update the current hover tab RECT (_currentHoverTabRect)
// update close hover flag (_isCloseHover), so that x will be highlighted or not based on new _currentHoverTabRect
int nextTab = getTabIndexAt(xPos, yPos);
if (nextTab != -1)
{
::SendMessage(_hSelf, TCM_GETITEMRECT, nextTab, reinterpret_cast<LPARAM>(&_currentHoverTabRect));
_isCloseHover = _closeButtonZone.isHit(xPos, yPos, _currentHoverTabRect, _isVertical);
}
return TRUE;
}
_whichCloseClickDown = -1;
}
break;
}
case WM_CAPTURECHANGED :
{
if (_isDragging)
{
_isDragging = false;
return TRUE;
}
break;
}
case WM_DRAWITEM :
{
drawItem((DRAWITEMSTRUCT *)lParam);
return TRUE;
}
case WM_KEYDOWN :
{
if (wParam == VK_LCONTROL)
::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_PLUS_TAB)));
return TRUE;
}
case WM_MBUTTONUP:
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
int currentTabOn = getTabIndexAt(xPos, yPos);
if (currentTabOn != -1)
notify(TCN_TABDELETE, currentTabOn);
return TRUE;
}
case WM_LBUTTONDBLCLK:
{
if (_isDbClk2Close)
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
int currentTabOn = getTabIndexAt(xPos, yPos);
notify(TCN_TABDELETE, currentTabOn);
}
return TRUE;
}
case WM_ERASEBKGND:
{
if (!NppDarkMode::isEnabled())
{
break;
}
RECT rc{};
GetClientRect(hwnd, &rc);
FillRect((HDC)wParam, &rc, NppDarkMode::getDarkerBackgroundBrush());
return 1;
}
case WM_PAINT:
{
if (!NppDarkMode::isEnabled())
{
break;
}
LONG_PTR dwStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
if (!(dwStyle & TCS_OWNERDRAWFIXED))
{
break;
}
const bool hasMultipleLines = ((dwStyle & TCS_BUTTONS) == TCS_BUTTONS);
PAINTSTRUCT ps{};
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, NppDarkMode::getDarkerBackgroundBrush());
UINT id = ::GetDlgCtrlID(hwnd);
auto holdPen = static_cast<HPEN>(::SelectObject(hdc, NppDarkMode::getEdgePen()));
HRGN holdClip = CreateRectRgn(0, 0, 0, 0);
if (1 != GetClipRgn(hdc, holdClip))
{
DeleteObject(holdClip);
holdClip = nullptr;
}
auto& dpiManager = NppParameters::getInstance()._dpiManager;
int paddingDynamicTwoX = dpiManager.scaleX(2);
int paddingDynamicTwoY = dpiManager.scaleY(2);
int nTabs = TabCtrl_GetItemCount(hwnd);
int nFocusTab = TabCtrl_GetCurFocus(hwnd);
int nSelTab = TabCtrl_GetCurSel(hwnd);
for (int i = 0; i < nTabs; ++i)
{
DRAWITEMSTRUCT dis = { ODT_TAB, id, (UINT)i, ODA_DRAWENTIRE, ODS_DEFAULT, hwnd, hdc, {}, 0 };
TabCtrl_GetItemRect(hwnd, i, &dis.rcItem);
if (i == nFocusTab)
{
dis.itemState |= ODS_FOCUS;
}
if (i == nSelTab)
{
dis.itemState |= ODS_SELECTED;
}
dis.itemState |= ODS_NOFOCUSRECT; // maybe, does it handle it already?
RECT rcIntersect{};
if (IntersectRect(&rcIntersect, &ps.rcPaint, &dis.rcItem))
{
if (!hasMultipleLines)
{
if (_isVertical)
{
POINT edges[] = {
{dis.rcItem.left, dis.rcItem.bottom - 1},
{dis.rcItem.right, dis.rcItem.bottom - 1}
};
if (i != nSelTab && (i != nSelTab - 1))
{
edges[0].x += paddingDynamicTwoX;
}
::Polyline(hdc, edges, _countof(edges));
dis.rcItem.bottom -= 1;
}
else
{
POINT edges[] = {
{dis.rcItem.right - 1, dis.rcItem.top},
{dis.rcItem.right - 1, dis.rcItem.bottom}
};
if (i != nSelTab && (i != nSelTab - 1))
{
edges[0].y += paddingDynamicTwoY;
}
::Polyline(hdc, edges, _countof(edges));
dis.rcItem.right -= 1;
}
}
HRGN hClip = CreateRectRgnIndirect(&dis.rcItem);
SelectClipRgn(hdc, hClip);
drawItem(&dis, NppDarkMode::isEnabled());
DeleteObject(hClip);
SelectClipRgn(hdc, holdClip);
}
}
if (!hasMultipleLines)
{
RECT rcFirstTab{};
TabCtrl_GetItemRect(hwnd, 0, &rcFirstTab);
if (_isVertical)
{
POINT edges[] = {
{rcFirstTab.left, rcFirstTab.top},
{rcFirstTab.right, rcFirstTab.top}
};
if (nSelTab != 0)
{
edges[0].x += paddingDynamicTwoX;
}
::Polyline(hdc, edges, _countof(edges));
}
else
{
POINT edges[] = {
{rcFirstTab.left, rcFirstTab.top},
{rcFirstTab.left, rcFirstTab.bottom}
};
if (nSelTab != 0)
{
edges[0].y += paddingDynamicTwoY;
}
::Polyline(hdc, edges, _countof(edges));
}
}
SelectClipRgn(hdc, holdClip);
if (holdClip)
{
DeleteObject(holdClip);
holdClip = nullptr;
}
SelectObject(hdc, holdPen);
EndPaint(hwnd, &ps);
return 0;
}
case WM_PARENTNOTIFY:
{
switch (LOWORD(wParam))
{
case WM_CREATE:
{
auto hwndUpdown = reinterpret_cast<HWND>(lParam);
if (NppDarkMode::subclassTabUpDownControl(hwndUpdown))
{
return 0;
}
break;
}
}
return 0;
}
}
return ::CallWindowProc(_tabBarDefaultProc, hwnd, Message, wParam, lParam);
}
void TabBarPlus::drawItem(DRAWITEMSTRUCT *pDrawItemStruct, bool isDarkMode)
{
RECT rect = pDrawItemStruct->rcItem;
int nTab = pDrawItemStruct->itemID;
if (nTab < 0)
{
::MessageBox(NULL, TEXT("nTab < 0"), TEXT(""), MB_OK);
}
bool isSelected = (nTab == ::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0));
TCHAR label[MAX_PATH] = { '\0' };
TCITEM tci{};
tci.mask = TCIF_TEXT|TCIF_IMAGE;
tci.pszText = label;
tci.cchTextMax = MAX_PATH-1;
if (!::SendMessage(_hSelf, TCM_GETITEM, nTab, reinterpret_cast<LPARAM>(&tci)))
{
::MessageBox(NULL, TEXT("! TCM_GETITEM"), TEXT(""), MB_OK);
}
const COLORREF colorActiveBg = isDarkMode ? NppDarkMode::getSofterBackgroundColor() : ::GetSysColor(COLOR_BTNFACE);
const COLORREF colorInactiveBgBase = isDarkMode ? NppDarkMode::getBackgroundColor() : ::GetSysColor(COLOR_BTNFACE);
COLORREF colorInactiveBg = liteGrey;
COLORREF colorActiveText = ::GetSysColor(COLOR_BTNTEXT);
COLORREF colorInactiveText = grey;
if (!NppDarkMode::useTabTheme() && isDarkMode)
{
colorInactiveBg = NppDarkMode::getBackgroundColor();
colorActiveText = NppDarkMode::getTextColor();
colorInactiveText = NppDarkMode::getDarkerTextColor();
}
else
{
colorInactiveBg = _inactiveBgColour;
colorActiveText = _activeTextColour;
colorInactiveText = _inactiveTextColour;
}
HDC hDC = pDrawItemStruct->hDC;
int nSavedDC = ::SaveDC(hDC);
::SetBkMode(hDC, TRANSPARENT);
HBRUSH hBrush = ::CreateSolidBrush(colorInactiveBgBase);
::FillRect(hDC, &rect, hBrush);
::DeleteObject((HGDIOBJ)hBrush);
// equalize drawing areas of active and inactive tabs
auto& dpiManager = NppParameters::getInstance()._dpiManager;
int paddingDynamicTwoX = dpiManager.scaleX(2);
int paddingDynamicTwoY = dpiManager.scaleY(2);
if (isSelected && !isDarkMode)
{
// the drawing area of the active tab extends on all borders by default
rect.top += ::GetSystemMetrics(SM_CYEDGE);
rect.bottom -= ::GetSystemMetrics(SM_CYEDGE);
rect.left += ::GetSystemMetrics(SM_CXEDGE);
rect.right -= ::GetSystemMetrics(SM_CXEDGE);
// the active tab is also slightly higher by default (use this to shift the tab cotent up bx two pixels if tobBar is not drawn)
if (_isVertical)
{
rect.left += _drawTopBar ? paddingDynamicTwoX : 0;
rect.right -= _drawTopBar ? 0 : paddingDynamicTwoX;
}
else
{
rect.top += _drawTopBar ? paddingDynamicTwoY : 0;
rect.bottom -= _drawTopBar ? 0 : paddingDynamicTwoY;
}
}
else
{
if (_isVertical)
{
rect.left += paddingDynamicTwoX;
rect.right += paddingDynamicTwoX;
rect.top -= paddingDynamicTwoY;
rect.bottom += paddingDynamicTwoY;
}
else
{
rect.left -= paddingDynamicTwoX;
rect.right += paddingDynamicTwoX;
rect.top += paddingDynamicTwoY;
rect.bottom += paddingDynamicTwoY;
}
}
// the active tab's text with TCS_BUTTONS is lower than normal and gets clipped
const bool hasMultipleLines = ((::GetWindowLongPtr(_hSelf, GWL_STYLE) & TCS_BUTTONS) == TCS_BUTTONS);
if (hasMultipleLines)
{
if (_isVertical)
{
rect.left -= paddingDynamicTwoX;
}
else
{
rect.top -= paddingDynamicTwoY;
}
}
const int individualColourId = getIndividualTabColour(nTab);
// draw highlights on tabs (top bar for active tab / darkened background for inactive tab)
RECT barRect = rect;
if (isSelected)
{
hBrush = ::CreateSolidBrush(colorActiveBg);
::FillRect(hDC, &pDrawItemStruct->rcItem, hBrush);
::DeleteObject(static_cast<HGDIOBJ>(hBrush));
if (_drawTopBar)
{
int topBarHeight = dpiManager.scaleX(4);
if (_isVertical)
{
barRect.left -= (hasMultipleLines && isDarkMode) ? 0 : paddingDynamicTwoX;
barRect.right = barRect.left + topBarHeight;
}
else
{
barRect.top -= (hasMultipleLines && isDarkMode) ? 0 : paddingDynamicTwoY;
barRect.bottom = barRect.top + topBarHeight;
}
if (::SendMessage(_hParent, NPPM_INTERNAL_ISFOCUSEDTAB, 0, reinterpret_cast<LPARAM>(_hSelf)))
{
COLORREF topBarColour = _activeTopBarFocusedColour; // #FAAA3C
if (individualColourId != -1)
{
topBarColour = NppDarkMode::getIndividualTabColour(individualColourId, isDarkMode, true);
}
hBrush = ::CreateSolidBrush(topBarColour);
}
else
hBrush = ::CreateSolidBrush(_activeTopBarUnfocusedColour); // #FAD296
::FillRect(hDC, &barRect, hBrush);
::DeleteObject((HGDIOBJ)hBrush);
}
}
else // inactive tabs
{
RECT rect = hasMultipleLines ? pDrawItemStruct->rcItem : barRect;
COLORREF brushColour{};
if (_drawInactiveTab && individualColourId == -1)
{
brushColour = colorInactiveBg;
}
else if (individualColourId != -1)
{
brushColour = NppDarkMode::getIndividualTabColour(individualColourId, isDarkMode, false);
}
else
{
brushColour = colorActiveBg;
}
hBrush = ::CreateSolidBrush(brushColour);
::FillRect(hDC, &rect, hBrush);
::DeleteObject((HGDIOBJ)hBrush);
}
if (isDarkMode && hasMultipleLines)
{
::FrameRect(hDC, &pDrawItemStruct->rcItem, NppDarkMode::getEdgeBrush());
}
// draw close button
if (_drawTabCloseButton)
{
// 3 status for each inactive tab and selected tab close item :
// normal / hover / pushed
int idCloseImg;
if (_isCloseHover && (_currentHoverTabItem == nTab) && (_whichCloseClickDown == -1)) // hover
idCloseImg = isDarkMode ? IDR_CLOSETAB_HOVER_DM : IDR_CLOSETAB_HOVER;
else if (_isCloseHover && (_currentHoverTabItem == nTab) && (_whichCloseClickDown == _currentHoverTabItem)) // pushed
idCloseImg = isDarkMode ? IDR_CLOSETAB_PUSH_DM : IDR_CLOSETAB_PUSH;
else
idCloseImg = isSelected ? (isDarkMode ? IDR_CLOSETAB_DM : IDR_CLOSETAB) : (isDarkMode ? IDR_CLOSETAB_INACT_DM : IDR_CLOSETAB_INACT);
HDC hdcMemory = ::CreateCompatibleDC(hDC);
HBITMAP hBmp = ::LoadBitmap(_hInst, MAKEINTRESOURCE(idCloseImg));
BITMAP bmp{};
::GetObject(hBmp, sizeof(bmp), &bmp);
_closeButtonZone._width = dpiManager.scaleX(bmp.bmWidth);
_closeButtonZone._height = dpiManager.scaleY(bmp.bmHeight);
RECT buttonRect = _closeButtonZone.getButtonRectFrom(rect, _isVertical);
// StretchBlt will crop image in RTL if there is no stretching, thus move image by -1
const bool isRTL = (::GetWindowLongPtr(::GetParent(_hSelf), GWL_EXSTYLE) & WS_EX_LAYOUTRTL) == WS_EX_LAYOUTRTL;
const int offset = isRTL && (_closeButtonZone._width == bmp.bmWidth) ? -1 : 0;
::SelectObject(hdcMemory, hBmp);
::StretchBlt(hDC, buttonRect.left + offset, buttonRect.top, _closeButtonZone._width, _closeButtonZone._height, hdcMemory, offset, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
::DeleteDC(hdcMemory);
::DeleteObject(hBmp);
}
// draw image
HIMAGELIST hImgLst = (HIMAGELIST)::SendMessage(_hSelf, TCM_GETIMAGELIST, 0, 0);
if (hImgLst && tci.iImage >= 0)
{
IMAGEINFO info{};
ImageList_GetImageInfo(hImgLst, tci.iImage, &info);
RECT& imageRect = info.rcImage;
int fromBorder;
int xPos, yPos;
if (_isVertical)
{
fromBorder = (rect.right - rect.left - (imageRect.right - imageRect.left) + 1) / 2;
xPos = rect.left + fromBorder;
yPos = rect.bottom - fromBorder - (imageRect.bottom - imageRect.top);
rect.bottom -= fromBorder + (imageRect.bottom - imageRect.top);
}
else
{
fromBorder = (rect.bottom - rect.top - (imageRect.bottom - imageRect.top) + 1) / 2;
yPos = rect.top + fromBorder;
xPos = rect.left + fromBorder;
rect.left += fromBorder + (imageRect.right - imageRect.left);
}
ImageList_Draw(hImgLst, tci.iImage, hDC, xPos, yPos, isSelected ? ILD_TRANSPARENT : ILD_SELECTED);
}
// draw text
bool isStandardSize = (::SendMessage(_hParent, NPPM_INTERNAL_ISTABBARREDUCED, 0, 0) == TRUE);
if (isStandardSize)
{
if (_isVertical)
SelectObject(hDC, _hVerticalFont);
else
SelectObject(hDC, _hFont);
}
else
{
if (_isVertical)
SelectObject(hDC, _hVerticalLargeFont);
else
SelectObject(hDC, _hLargeFont);
}
SIZE charPixel{};
::GetTextExtentPoint(hDC, TEXT(" "), 1, &charPixel);
int spaceUnit = charPixel.cx;
TEXTMETRIC textMetrics{};
GetTextMetrics(hDC, &textMetrics);
int textHeight = textMetrics.tmHeight;
int textDescent = textMetrics.tmDescent;
int Flags = DT_SINGLELINE | DT_NOPREFIX;
// This code will read in one character at a time and remove every first ampersand (&).
// ex. If input "test && test &&& test &&&&" then output will be "test & test && test &&&".
// Tab's caption must be encoded like this because otherwise tab control would make tab too small or too big for the text.
TCHAR decodedLabel[MAX_PATH] = { '\0' };
const TCHAR* in = label;
TCHAR* out = decodedLabel;
while (*in != 0)
if (*in == '&')
while (*(++in) == '&')
*out++ = *in;
else
*out++ = *in++;
*out = '\0';
if (_isVertical)
{
// center text horizontally (rotated text is positioned as if it were unrotated, therefore manual positioning is necessary)
Flags |= DT_LEFT;
Flags |= DT_BOTTOM;
rect.left += (rect.right - rect.left - textHeight) / 2;
rect.bottom += textHeight;
// ignoring the descent when centering (text elements below the base line) is more pleasing to the eye
rect.left += textDescent / 2;
rect.right += textDescent / 2;
// 1 space distance to save icon
rect.bottom -= spaceUnit;
}
else
{
// center text vertically
Flags |= DT_LEFT;
Flags |= DT_TOP;
const int paddingText = ((pDrawItemStruct->rcItem.bottom - pDrawItemStruct->rcItem.top) - (textHeight + textDescent)) / 2;
const int paddingDescent = !hasMultipleLines ? ((textDescent + ((isDarkMode || !isSelected) ? 1 : 0)) / 2) : 0;
rect.top = pDrawItemStruct->rcItem.top + paddingText + paddingDescent;
rect.bottom = pDrawItemStruct->rcItem.bottom - paddingText + paddingDescent;
if (isDarkMode || !isSelected || _drawTopBar)
{
rect.top += paddingDynamicTwoY;
}
// 1 space distance to save icon
rect.left += spaceUnit;
}
COLORREF textColor = isSelected ? colorActiveText : colorInactiveText;
::SetTextColor(hDC, textColor);
::DrawText(hDC, decodedLabel, lstrlen(decodedLabel), &rect, Flags);
::RestoreDC(hDC, nSavedDC);
}
void TabBarPlus::draggingCursor(POINT screenPoint)
{
HWND hWin = ::WindowFromPoint(screenPoint);
if (_hSelf == hWin)
::SetCursor(::LoadCursor(NULL, IDC_ARROW));
else
{
TCHAR className[256] = { '\0' };
::GetClassName(hWin, className, 256);
if ((!lstrcmp(className, TEXT("Scintilla"))) || (!lstrcmp(className, WC_TABCONTROL)))
{
if (::GetKeyState(VK_LCONTROL) & 0x80000000)
::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_PLUS_TAB)));
else
::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_TAB)));
}
else if (isPointInParentZone(screenPoint))
::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_INTERDIT_TAB)));
else // drag out of application
::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_OUT_TAB)));
}
}
void TabBarPlus::setActiveTab(int tabIndex)
{
// TCM_SETCURFOCUS is busted on WINE/ReactOS for single line (non-TCS_BUTTONS) tabs...
// We need it on Windows for multi-line tabs or multiple tabs can appear pressed.
if (::GetWindowLongPtr(_hSelf, GWL_STYLE) & TCS_BUTTONS)
{
::SendMessage(_hSelf, TCM_SETCURFOCUS, tabIndex, 0);
}
::SendMessage(_hSelf, TCM_SETCURSEL, tabIndex, 0);
notify(TCN_SELCHANGE, tabIndex);
}
void TabBarPlus::exchangeTabItemData(int oldTab, int newTab)
{
//1. shift their data, and insert the source
TCITEM itemData_nDraggedTab{}, itemData_shift{};
itemData_nDraggedTab.mask = itemData_shift.mask = TCIF_IMAGE | TCIF_TEXT | TCIF_PARAM;
const int stringSize = 256;
TCHAR str1[stringSize] = { '\0' };
TCHAR str2[stringSize] = { '\0' };
itemData_nDraggedTab.pszText = str1;
itemData_nDraggedTab.cchTextMax = (stringSize);
itemData_shift.pszText = str2;
itemData_shift.cchTextMax = (stringSize);
::SendMessage(_hSelf, TCM_GETITEM, oldTab, reinterpret_cast<LPARAM>(&itemData_nDraggedTab));
if (oldTab > newTab)
{
for (int i = oldTab; i > newTab; i--)
{
::SendMessage(_hSelf, TCM_GETITEM, i - 1, reinterpret_cast<LPARAM>(&itemData_shift));
::SendMessage(_hSelf, TCM_SETITEM, i, reinterpret_cast<LPARAM>(&itemData_shift));
}
}
else
{
for (int i = oldTab; i < newTab; ++i)
{
::SendMessage(_hSelf, TCM_GETITEM, i + 1, reinterpret_cast<LPARAM>(&itemData_shift));
::SendMessage(_hSelf, TCM_SETITEM, i, reinterpret_cast<LPARAM>(&itemData_shift));
}
}
::SendMessage(_hSelf, TCM_SETITEM, newTab, reinterpret_cast<LPARAM>(&itemData_nDraggedTab));
// Tell Notepad_plus to notifiy plugins that a D&D operation was done (so doc index has been changed)
::SendMessage(_hParent, NPPM_INTERNAL_DOCORDERCHANGED, 0, oldTab);
//2. set to focus
setActiveTab(newTab);
}
void TabBarPlus::exchangeItemData(POINT point)
{
// Find the destination tab...
int nTab = getTabIndexAt(point);
// The position is over a tab.
//if (hitinfo.flags != TCHT_NOWHERE)
if (nTab != -1)
{
_isDraggingInside = true;
if (nTab != _nTabDragged)
{
if (_previousTabSwapped == nTab)
{
return;
}
exchangeTabItemData(_nTabDragged, nTab);
_previousTabSwapped = _nTabDragged;
_nTabDragged = nTab;
}
else
{
_previousTabSwapped = -1;
}
}
else
{
//::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_TAB)));
_previousTabSwapped = -1;
_isDraggingInside = false;
}
}
CloseButtonZone::CloseButtonZone()
{
// TODO: get width/height of close button dynamically
_width = NppParameters::getInstance()._dpiManager.scaleX(g_TabCloseBtnSize);
_height = _width;
}
bool CloseButtonZone::isHit(int x, int y, const RECT & tabRect, bool isVertical) const
{
RECT buttonRect = getButtonRectFrom(tabRect, isVertical);
if (x >= buttonRect.left && x <= buttonRect.right && y >= buttonRect.top && y <= buttonRect.bottom)
return true;
return false;
}
RECT CloseButtonZone::getButtonRectFrom(const RECT & tabRect, bool isVertical) const
{
RECT buttonRect{};
int fromBorder = 0;
if (isVertical)
{
fromBorder = (tabRect.right - tabRect.left - _width + 1) / 2;
buttonRect.left = tabRect.left + fromBorder;
}
else
{
fromBorder = (tabRect.bottom - tabRect.top - _height + 1) / 2;
buttonRect.left = tabRect.right - fromBorder - _width;
}
buttonRect.top = tabRect.top + fromBorder;
buttonRect.bottom = buttonRect.top + _height;
buttonRect.right = buttonRect.left + _width;
return buttonRect;
}